gloop-sim 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -2
  3. package/dist/assert.d.ts +1 -0
  4. package/dist/assert.js +6 -0
  5. package/dist/assert.js.map +1 -0
  6. package/dist/backend/cpu.d.ts +2 -0
  7. package/dist/backend/cpu.js +23 -0
  8. package/dist/backend/cpu.js.map +1 -0
  9. package/dist/backend/gpu.d.ts +2 -0
  10. package/dist/backend/gpu.js +145 -0
  11. package/dist/backend/gpu.js.map +1 -0
  12. package/dist/backend/index.d.ts +14 -0
  13. package/dist/backend/index.js +23 -0
  14. package/dist/backend/index.js.map +1 -0
  15. package/dist/device.d.ts +11 -0
  16. package/dist/device.js +66 -0
  17. package/dist/device.js.map +1 -0
  18. package/dist/index.d.ts +18 -0
  19. package/dist/index.js +18 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/initializer/fixed.d.ts +2 -0
  22. package/dist/initializer/fixed.js +12 -0
  23. package/dist/initializer/fixed.js.map +1 -0
  24. package/dist/initializer/index.d.ts +14 -0
  25. package/dist/initializer/index.js +23 -0
  26. package/dist/initializer/index.js.map +1 -0
  27. package/dist/initializer/random.d.ts +2 -0
  28. package/dist/initializer/random.js +16 -0
  29. package/dist/initializer/random.js.map +1 -0
  30. package/dist/integrator/euler.d.ts +2 -0
  31. package/dist/integrator/euler.js +34 -0
  32. package/dist/integrator/euler.js.map +1 -0
  33. package/dist/integrator/index.d.ts +14 -0
  34. package/dist/integrator/index.js +23 -0
  35. package/dist/integrator/index.js.map +1 -0
  36. package/dist/integrator/semi-implicit-euler.d.ts +2 -0
  37. package/dist/integrator/semi-implicit-euler.js +36 -0
  38. package/dist/integrator/semi-implicit-euler.js.map +1 -0
  39. package/dist/model/elastic-network/cpu.d.ts +2 -0
  40. package/dist/model/elastic-network/cpu.js +90 -0
  41. package/dist/model/elastic-network/cpu.js.map +1 -0
  42. package/dist/model/elastic-network/gpu.d.ts +2 -0
  43. package/dist/model/elastic-network/gpu.js +114 -0
  44. package/dist/model/elastic-network/gpu.js.map +1 -0
  45. package/dist/model/elastic-network/index.d.ts +2 -0
  46. package/dist/model/elastic-network/index.js +69 -0
  47. package/dist/model/elastic-network/index.js.map +1 -0
  48. package/dist/model/index.d.ts +15 -0
  49. package/dist/model/index.js +24 -0
  50. package/dist/model/index.js.map +1 -0
  51. package/dist/model/inertial.d.ts +2 -0
  52. package/dist/model/inertial.js +17 -0
  53. package/dist/model/inertial.js.map +1 -0
  54. package/dist/render/backend/channels.d.ts +17 -0
  55. package/dist/render/backend/channels.js +35 -0
  56. package/dist/render/backend/channels.js.map +1 -0
  57. package/dist/render/backend/cloud.d.ts +77 -0
  58. package/dist/render/backend/cloud.js +29 -0
  59. package/dist/render/backend/cloud.js.map +1 -0
  60. package/dist/render/backend/compile.d.ts +31 -0
  61. package/dist/render/backend/compile.js +73 -0
  62. package/dist/render/backend/compile.js.map +1 -0
  63. package/dist/render/backend/glyph.d.ts +8 -0
  64. package/dist/render/backend/glyph.js +38 -0
  65. package/dist/render/backend/glyph.js.map +1 -0
  66. package/dist/render/backend/glyphs.d.ts +6 -0
  67. package/dist/render/backend/glyphs.js +146 -0
  68. package/dist/render/backend/glyphs.js.map +1 -0
  69. package/dist/render/backend/gpu.d.ts +12 -0
  70. package/dist/render/backend/gpu.js +278 -0
  71. package/dist/render/backend/gpu.js.map +1 -0
  72. package/dist/render/backend/material.d.ts +30 -0
  73. package/dist/render/backend/material.js +57 -0
  74. package/dist/render/backend/material.js.map +1 -0
  75. package/dist/render/backend/particles.d.ts +5 -0
  76. package/dist/render/backend/particles.js +22 -0
  77. package/dist/render/backend/particles.js.map +1 -0
  78. package/dist/render/backend/post.d.ts +29 -0
  79. package/dist/render/backend/post.js +77 -0
  80. package/dist/render/backend/post.js.map +1 -0
  81. package/dist/render/backend/solids.d.ts +10 -0
  82. package/dist/render/backend/solids.js +134 -0
  83. package/dist/render/backend/solids.js.map +1 -0
  84. package/dist/render/camera.d.ts +73 -0
  85. package/dist/render/camera.js +32 -0
  86. package/dist/render/camera.js.map +1 -0
  87. package/dist/render/channel.d.ts +25 -0
  88. package/dist/render/channel.js +9 -0
  89. package/dist/render/channel.js.map +1 -0
  90. package/dist/render/color.d.ts +34 -0
  91. package/dist/render/color.js +56 -0
  92. package/dist/render/color.js.map +1 -0
  93. package/dist/render/effect.d.ts +81 -0
  94. package/dist/render/effect.js +30 -0
  95. package/dist/render/effect.js.map +1 -0
  96. package/dist/render/frame.d.ts +60 -0
  97. package/dist/render/frame.js +52 -0
  98. package/dist/render/frame.js.map +1 -0
  99. package/dist/render/index.d.ts +12 -0
  100. package/dist/render/index.js +2 -0
  101. package/dist/render/index.js.map +1 -0
  102. package/dist/render/layer.d.ts +90 -0
  103. package/dist/render/layer.js +54 -0
  104. package/dist/render/layer.js.map +1 -0
  105. package/dist/render/ramp.d.ts +93 -0
  106. package/dist/render/ramp.js +120 -0
  107. package/dist/render/ramp.js.map +1 -0
  108. package/dist/render/sampler.d.ts +34 -0
  109. package/dist/render/sampler.js +53 -0
  110. package/dist/render/sampler.js.map +1 -0
  111. package/dist/render/signal.d.ts +66 -0
  112. package/dist/render/signal.js +51 -0
  113. package/dist/render/signal.js.map +1 -0
  114. package/dist/render/sources.d.ts +33 -0
  115. package/dist/render/sources.js +46 -0
  116. package/dist/render/sources.js.map +1 -0
  117. package/dist/render/springs/anchors.d.ts +37 -0
  118. package/dist/render/springs/anchors.js +34 -0
  119. package/dist/render/springs/anchors.js.map +1 -0
  120. package/dist/render/springs/construction.d.ts +30 -0
  121. package/dist/render/springs/construction.js +66 -0
  122. package/dist/render/springs/construction.js.map +1 -0
  123. package/dist/render/springs/impulses.d.ts +45 -0
  124. package/dist/render/springs/impulses.js +144 -0
  125. package/dist/render/springs/impulses.js.map +1 -0
  126. package/dist/render/springs/index.d.ts +9 -0
  127. package/dist/render/springs/index.js +2 -0
  128. package/dist/render/springs/index.js.map +1 -0
  129. package/dist/render/springs/link.d.ts +51 -0
  130. package/dist/render/springs/link.js +42 -0
  131. package/dist/render/springs/link.js.map +1 -0
  132. package/dist/render/springs/links.d.ts +70 -0
  133. package/dist/render/springs/links.js +210 -0
  134. package/dist/render/springs/links.js.map +1 -0
  135. package/dist/render/springs/palette.d.ts +36 -0
  136. package/dist/render/springs/palette.js +22 -0
  137. package/dist/render/springs/palette.js.map +1 -0
  138. package/dist/render/springs/preset.d.ts +65 -0
  139. package/dist/render/springs/preset.js +114 -0
  140. package/dist/render/springs/preset.js.map +1 -0
  141. package/dist/render/springs/segment.d.ts +40 -0
  142. package/dist/render/springs/segment.js +81 -0
  143. package/dist/render/springs/segment.js.map +1 -0
  144. package/dist/render/springs/source.d.ts +35 -0
  145. package/dist/render/springs/source.js +70 -0
  146. package/dist/render/springs/source.js.map +1 -0
  147. package/dist/render/springs/springs.d.ts +40 -0
  148. package/dist/render/springs/springs.js +34 -0
  149. package/dist/render/springs/springs.js.map +1 -0
  150. package/dist/render/springs/strain.d.ts +21 -0
  151. package/dist/render/springs/strain.js +39 -0
  152. package/dist/render/springs/strain.js.map +1 -0
  153. package/dist/render/stage.d.ts +96 -0
  154. package/dist/render/stage.js +2 -0
  155. package/dist/render/stage.js.map +1 -0
  156. package/dist/render/view.d.ts +63 -0
  157. package/dist/render/view.js +63 -0
  158. package/dist/render/view.js.map +1 -0
  159. package/dist/swarm.d.ts +5 -0
  160. package/dist/swarm.js +24 -0
  161. package/dist/swarm.js.map +1 -0
  162. package/dist/types.d.ts +290 -0
  163. package/dist/types.js +2 -0
  164. package/dist/types.js.map +1 -0
  165. package/docs/assets/logo.svg +112 -0
  166. package/package.json +102 -3
@@ -0,0 +1,34 @@
1
+ import { assert } from "../assert.js";
2
+ function addScaled(a, b, scale) {
3
+ return [a[0] + b[0] * scale, a[1] + b[1] * scale, a[2] + b[2] * scale];
4
+ }
5
+ export function explicitEuler() {
6
+ return {
7
+ cpu: {
8
+ step({ particles, accelerations, seconds }) {
9
+ return {
10
+ count: particles.count,
11
+ positions: particles.positions.map((position, particle) => {
12
+ const velocity = particles.velocities[particle];
13
+ assert(velocity !== undefined, `particle ${particle} has no velocity`);
14
+ return addScaled(position, velocity, seconds);
15
+ }),
16
+ velocities: particles.velocities.map((velocity, particle) => {
17
+ const acceleration = accelerations[particle];
18
+ assert(acceleration !== undefined, `particle ${particle} has no acceleration`);
19
+ return addScaled(velocity, acceleration, seconds);
20
+ }),
21
+ };
22
+ },
23
+ },
24
+ gpu: {
25
+ step({ particle, acceleration, seconds }) {
26
+ return {
27
+ position: particle.position.add(particle.velocity.mul(seconds)),
28
+ velocity: particle.velocity.add(acceleration.mul(seconds)),
29
+ };
30
+ },
31
+ },
32
+ };
33
+ }
34
+ //# sourceMappingURL=euler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"euler.js","sourceRoot":"","sources":["../../src/integrator/euler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAUrC,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS,EAAE,KAAa;IACpD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,GAAG,EAAE;YACH,IAAI,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAwB;gBAC9D,OAAO;oBACL,KAAK,EAAE,SAAS,CAAC,KAAK;oBACtB,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;wBACxD,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;wBAChD,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,YAAY,QAAQ,kBAAkB,CAAC,CAAC;wBACvE,OAAO,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;oBAChD,CAAC,CAAC;oBACF,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;wBAC1D,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;wBAC7C,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,YAAY,QAAQ,sBAAsB,CAAC,CAAC;wBAC/E,OAAO,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;oBACpD,CAAC,CAAC;iBACH,CAAC;YACJ,CAAC;SACF;QACD,GAAG,EAAE;YACH,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAwB;gBAC5D,OAAO;oBACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAC/D,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;iBAC3D,CAAC;YACJ,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { assert } from \"@/assert.js\";\nimport type {\n CpuIntegratorOptions,\n GpuIntegratorOptions,\n GpuParticle,\n Integrator,\n Particles,\n Vector,\n} from \"@/types.js\";\n\nfunction addScaled(a: Vector, b: Vector, scale: number): Vector {\n return [a[0] + b[0] * scale, a[1] + b[1] * scale, a[2] + b[2] * scale];\n}\n\nexport function explicitEuler(): Integrator {\n return {\n cpu: {\n step({ particles, accelerations, seconds }: CpuIntegratorOptions): Particles {\n return {\n count: particles.count,\n positions: particles.positions.map((position, particle) => {\n const velocity = particles.velocities[particle];\n assert(velocity !== undefined, `particle ${particle} has no velocity`);\n return addScaled(position, velocity, seconds);\n }),\n velocities: particles.velocities.map((velocity, particle) => {\n const acceleration = accelerations[particle];\n assert(acceleration !== undefined, `particle ${particle} has no acceleration`);\n return addScaled(velocity, acceleration, seconds);\n }),\n };\n },\n },\n gpu: {\n step({ particle, acceleration, seconds }: GpuIntegratorOptions): GpuParticle {\n return {\n position: particle.position.add(particle.velocity.mul(seconds)),\n velocity: particle.velocity.add(acceleration.mul(seconds)),\n };\n },\n },\n };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { Integrator } from "../types.js";
2
+ /**
3
+ * Advances positions and velocities under a set of accelerations.
4
+ */
5
+ export declare namespace integrator {
6
+ /**
7
+ * An integrator that uses the explicit Euler integration scheme.
8
+ */
9
+ function explicitEuler(): Integrator;
10
+ /**
11
+ * An integrator that uses the semi-implicit Euler integration scheme.
12
+ */
13
+ function semiImplicitEuler(): Integrator;
14
+ }
@@ -0,0 +1,23 @@
1
+ import { explicitEuler as createExplicitEuler } from "../integrator/euler.js";
2
+ import { semiImplicitEuler as createSemiImplicitEuler } from "../integrator/semi-implicit-euler.js";
3
+ /**
4
+ * Advances positions and velocities under a set of accelerations.
5
+ */
6
+ export var integrator;
7
+ (function (integrator) {
8
+ /**
9
+ * An integrator that uses the explicit Euler integration scheme.
10
+ */
11
+ function explicitEuler() {
12
+ return createExplicitEuler();
13
+ }
14
+ integrator.explicitEuler = explicitEuler;
15
+ /**
16
+ * An integrator that uses the semi-implicit Euler integration scheme.
17
+ */
18
+ function semiImplicitEuler() {
19
+ return createSemiImplicitEuler();
20
+ }
21
+ integrator.semiImplicitEuler = semiImplicitEuler;
22
+ })(integrator || (integrator = {}));
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integrator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,iBAAiB,IAAI,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAGnG;;GAEG;AACH,MAAM,KAAW,UAAU,CAc1B;AAdD,WAAiB,UAAU;IACzB;;OAEG;IACH,SAAgB,aAAa;QAC3B,OAAO,mBAAmB,EAAE,CAAC;IAC/B,CAAC;IAFe,wBAAa,gBAE5B,CAAA;IAED;;OAEG;IACH,SAAgB,iBAAiB;QAC/B,OAAO,uBAAuB,EAAE,CAAC;IACnC,CAAC;IAFe,4BAAiB,oBAEhC,CAAA;AACH,CAAC,EAdgB,UAAU,KAAV,UAAU,QAc1B","sourcesContent":["import { explicitEuler as createExplicitEuler } from \"@/integrator/euler.js\";\nimport { semiImplicitEuler as createSemiImplicitEuler } from \"@/integrator/semi-implicit-euler.js\";\nimport type { Integrator } from \"@/types.js\";\n\n/**\n * Advances positions and velocities under a set of accelerations.\n */\nexport namespace integrator {\n /**\n * An integrator that uses the explicit Euler integration scheme.\n */\n export function explicitEuler(): Integrator {\n return createExplicitEuler();\n }\n\n /**\n * An integrator that uses the semi-implicit Euler integration scheme.\n */\n export function semiImplicitEuler(): Integrator {\n return createSemiImplicitEuler();\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Integrator } from "../types.js";
2
+ export declare function semiImplicitEuler(): Integrator;
@@ -0,0 +1,36 @@
1
+ import { assert } from "../assert.js";
2
+ function addScaled(a, b, scale) {
3
+ return [a[0] + b[0] * scale, a[1] + b[1] * scale, a[2] + b[2] * scale];
4
+ }
5
+ export function semiImplicitEuler() {
6
+ return {
7
+ cpu: {
8
+ step({ particles, accelerations, seconds }) {
9
+ const velocities = particles.velocities.map((velocity, particle) => {
10
+ const acceleration = accelerations[particle];
11
+ assert(acceleration !== undefined, `particle ${particle} has no acceleration`);
12
+ return addScaled(velocity, acceleration, seconds);
13
+ });
14
+ return {
15
+ count: particles.count,
16
+ positions: particles.positions.map((position, particle) => {
17
+ const velocity = velocities[particle];
18
+ assert(velocity !== undefined, `particle ${particle} has no velocity`);
19
+ return addScaled(position, velocity, seconds);
20
+ }),
21
+ velocities,
22
+ };
23
+ },
24
+ },
25
+ gpu: {
26
+ step({ particle, acceleration, seconds }) {
27
+ const velocity = particle.velocity.add(acceleration.mul(seconds));
28
+ return {
29
+ position: particle.position.add(velocity.mul(seconds)),
30
+ velocity,
31
+ };
32
+ },
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=semi-implicit-euler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semi-implicit-euler.js","sourceRoot":"","sources":["../../src/integrator/semi-implicit-euler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAUrC,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS,EAAE,KAAa;IACpD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,GAAG,EAAE;YACH,IAAI,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAwB;gBAC9D,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;oBACjE,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;oBAC7C,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,YAAY,QAAQ,sBAAsB,CAAC,CAAC;oBAC/E,OAAO,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;gBACpD,CAAC,CAAC,CAAC;gBAEH,OAAO;oBACL,KAAK,EAAE,SAAS,CAAC,KAAK;oBACtB,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;wBACxD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;wBACtC,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,YAAY,QAAQ,kBAAkB,CAAC,CAAC;wBACvE,OAAO,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;oBAChD,CAAC,CAAC;oBACF,UAAU;iBACX,CAAC;YACJ,CAAC;SACF;QACD,GAAG,EAAE;YACH,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAwB;gBAC5D,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAClE,OAAO;oBACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACtD,QAAQ;iBACT,CAAC;YACJ,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { assert } from \"@/assert.js\";\nimport type {\n CpuIntegratorOptions,\n GpuIntegratorOptions,\n GpuParticle,\n Integrator,\n Particles,\n Vector,\n} from \"@/types.js\";\n\nfunction addScaled(a: Vector, b: Vector, scale: number): Vector {\n return [a[0] + b[0] * scale, a[1] + b[1] * scale, a[2] + b[2] * scale];\n}\n\nexport function semiImplicitEuler(): Integrator {\n return {\n cpu: {\n step({ particles, accelerations, seconds }: CpuIntegratorOptions): Particles {\n const velocities = particles.velocities.map((velocity, particle) => {\n const acceleration = accelerations[particle];\n assert(acceleration !== undefined, `particle ${particle} has no acceleration`);\n return addScaled(velocity, acceleration, seconds);\n });\n\n return {\n count: particles.count,\n positions: particles.positions.map((position, particle) => {\n const velocity = velocities[particle];\n assert(velocity !== undefined, `particle ${particle} has no velocity`);\n return addScaled(position, velocity, seconds);\n }),\n velocities,\n };\n },\n },\n gpu: {\n step({ particle, acceleration, seconds }: GpuIntegratorOptions): GpuParticle {\n const velocity = particle.velocity.add(acceleration.mul(seconds));\n return {\n position: particle.position.add(velocity.mul(seconds)),\n velocity,\n };\n },\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { CpuModel, ElasticNetworkOptions } from "../../types.js";
2
+ export declare function cpu(options: Required<ElasticNetworkOptions>): CpuModel;
@@ -0,0 +1,90 @@
1
+ import { assert } from "../../assert.js";
2
+ var compute;
3
+ (function (compute) {
4
+ function subtract(a, b) {
5
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
6
+ }
7
+ compute.subtract = subtract;
8
+ function scale(vector, factor) {
9
+ return [vector[0] * factor, vector[1] * factor, vector[2] * factor];
10
+ }
11
+ compute.scale = scale;
12
+ function dot(a, b) {
13
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
14
+ }
15
+ compute.dot = dot;
16
+ function magnitude(vector) {
17
+ return Math.hypot(vector[0], vector[1], vector[2]);
18
+ }
19
+ compute.magnitude = magnitude;
20
+ })(compute || (compute = {}));
21
+ var forces;
22
+ (function (forces) {
23
+ function accumulate({ positions, velocities }, connections, stiffness, springDamping) {
24
+ const springForces = positions.map(() => [0, 0, 0]);
25
+ for (const { between, restLength } of connections) {
26
+ const [i, j] = between;
27
+ const [xi, xj, vi, vj] = [positions[i], positions[j], velocities[i], velocities[j]];
28
+ assert(xi !== undefined && xj !== undefined, `connection references a missing particle position`);
29
+ assert(vi !== undefined && vj !== undefined, `connection references a missing particle velocity`);
30
+ const positionDifference = compute.subtract(xj, xi);
31
+ const velocityDifference = compute.subtract(vj, vi);
32
+ const force = spring(positionDifference, velocityDifference, restLength, stiffness, springDamping);
33
+ if (force === undefined) {
34
+ continue;
35
+ }
36
+ const [forceI, forceJ] = [springForces[i], springForces[j]];
37
+ assert(forceI !== undefined && forceJ !== undefined, `connection references a missing particle`);
38
+ applyNewtonsThirdLaw(forceI, forceJ, force);
39
+ }
40
+ return springForces;
41
+ }
42
+ forces.accumulate = accumulate;
43
+ function acceleration(position, velocity, springForce, mass, restPosition, anchorStrength, globalDamping) {
44
+ const pull = anchor(position, restPosition, anchorStrength);
45
+ const drag = damping(velocity, globalDamping);
46
+ const net = (axis) => (springForce[axis] - pull[axis] - drag[axis]) / mass;
47
+ return [net(0), net(1), net(2)];
48
+ }
49
+ forces.acceleration = acceleration;
50
+ function spring(positionDifference, velocityDifference, restLength, stiffness, springDamping) {
51
+ const distance = compute.magnitude(positionDifference);
52
+ if (distance === 0) {
53
+ return undefined;
54
+ }
55
+ const normal = compute.scale(positionDifference, 1 / distance);
56
+ const scalar = stiffness * (distance - restLength) + springDamping * compute.dot(velocityDifference, normal);
57
+ return compute.scale(normal, scalar);
58
+ }
59
+ function anchor(position, restPosition, anchorStrength) {
60
+ if (restPosition === undefined) {
61
+ return [0, 0, 0];
62
+ }
63
+ return compute.scale(compute.subtract(position, restPosition), anchorStrength);
64
+ }
65
+ function damping(velocity, globalDamping) {
66
+ return compute.scale(velocity, globalDamping);
67
+ }
68
+ function applyNewtonsThirdLaw(forceI, forceJ, force) {
69
+ for (const axis of [0, 1, 2]) {
70
+ forceI[axis] += force[axis];
71
+ forceJ[axis] -= force[axis];
72
+ }
73
+ }
74
+ })(forces || (forces = {}));
75
+ export function cpu(options) {
76
+ const { masses, connections, restPositions, stiffness, springDamping, anchorStrength, globalDamping } = options;
77
+ return {
78
+ accelerate({ count, positions, velocities }) {
79
+ const springForces = forces.accumulate({ count, positions, velocities }, connections, stiffness, springDamping);
80
+ return positions.map((position, particle) => {
81
+ const [force, mass, velocity] = [springForces[particle], masses[particle], velocities[particle]];
82
+ assert(force !== undefined, `particle ${particle} has no accumulated force`);
83
+ assert(mass !== undefined, `particle ${particle} has no mass`);
84
+ assert(velocity !== undefined, `particle ${particle} has no velocity`);
85
+ return forces.acceleration(position, velocity, force, mass, restPositions[particle], anchorStrength, globalDamping);
86
+ });
87
+ },
88
+ };
89
+ }
90
+ //# sourceMappingURL=cpu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpu.js","sourceRoot":"","sources":["../../../src/model/elastic-network/cpu.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from \"@/assert.js\";\nimport type { Connection, CpuModel, ElasticNetworkOptions, Particles, Vector } from \"@/types.js\";\n\nnamespace compute {\n export function subtract(a: Vector, b: Vector): Vector {\n return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];\n }\n\n export function scale(vector: Vector, factor: number): Vector {\n return [vector[0] * factor, vector[1] * factor, vector[2] * factor];\n }\n\n export function dot(a: Vector, b: Vector): number {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n }\n\n export function magnitude(vector: Vector): number {\n return Math.hypot(vector[0], vector[1], vector[2]);\n }\n}\n\nnamespace forces {\n export function accumulate(\n { positions, velocities }: Particles,\n connections: readonly Connection[],\n stiffness: number,\n springDamping: number,\n ): [number, number, number][] {\n const springForces: [number, number, number][] = positions.map(() => [0, 0, 0]);\n\n for (const { between, restLength } of connections) {\n const [i, j] = between;\n const [xi, xj, vi, vj] = [positions[i], positions[j], velocities[i], velocities[j]];\n assert(xi !== undefined && xj !== undefined, `connection references a missing particle position`);\n assert(vi !== undefined && vj !== undefined, `connection references a missing particle velocity`);\n\n const positionDifference = compute.subtract(xj, xi);\n const velocityDifference = compute.subtract(vj, vi);\n const force = spring(positionDifference, velocityDifference, restLength, stiffness, springDamping);\n if (force === undefined) {\n continue;\n }\n\n const [forceI, forceJ] = [springForces[i], springForces[j]];\n assert(forceI !== undefined && forceJ !== undefined, `connection references a missing particle`);\n applyNewtonsThirdLaw(forceI, forceJ, force);\n }\n\n return springForces;\n }\n\n export function acceleration(\n position: Vector,\n velocity: Vector,\n springForce: Vector,\n mass: number,\n restPosition: Vector | undefined,\n anchorStrength: number,\n globalDamping: number,\n ): Vector {\n const pull = anchor(position, restPosition, anchorStrength);\n const drag = damping(velocity, globalDamping);\n const net = (axis: 0 | 1 | 2): number => (springForce[axis] - pull[axis] - drag[axis]) / mass;\n return [net(0), net(1), net(2)];\n }\n\n function spring(\n positionDifference: Vector,\n velocityDifference: Vector,\n restLength: number,\n stiffness: number,\n springDamping: number,\n ): Vector | undefined {\n const distance = compute.magnitude(positionDifference);\n if (distance === 0) {\n return undefined;\n }\n const normal = compute.scale(positionDifference, 1 / distance);\n const scalar = stiffness * (distance - restLength) + springDamping * compute.dot(velocityDifference, normal);\n return compute.scale(normal, scalar);\n }\n\n function anchor(position: Vector, restPosition: Vector | undefined, anchorStrength: number): Vector {\n if (restPosition === undefined) {\n return [0, 0, 0];\n }\n return compute.scale(compute.subtract(position, restPosition), anchorStrength);\n }\n\n function damping(velocity: Vector, globalDamping: number): Vector {\n return compute.scale(velocity, globalDamping);\n }\n\n function applyNewtonsThirdLaw(\n forceI: [number, number, number],\n forceJ: [number, number, number],\n force: Vector,\n ): void {\n for (const axis of [0, 1, 2] as const) {\n forceI[axis] += force[axis];\n forceJ[axis] -= force[axis];\n }\n }\n}\n\nexport function cpu(options: Required<ElasticNetworkOptions>): CpuModel {\n const { masses, connections, restPositions, stiffness, springDamping, anchorStrength, globalDamping } = options;\n\n return {\n accelerate({ count, positions, velocities }: Particles): readonly Vector[] {\n const springForces = forces.accumulate({ count, positions, velocities }, connections, stiffness, springDamping);\n\n return positions.map((position, particle) => {\n const [force, mass, velocity] = [springForces[particle], masses[particle], velocities[particle]];\n assert(force !== undefined, `particle ${particle} has no accumulated force`);\n assert(mass !== undefined, `particle ${particle} has no mass`);\n assert(velocity !== undefined, `particle ${particle} has no velocity`);\n\n return forces.acceleration(\n position,\n velocity,\n force,\n mass,\n restPositions[particle],\n anchorStrength,\n globalDamping,\n );\n });\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ElasticNetworkOptions, GpuModel } from "../../types.js";
2
+ export declare function gpuModel(options: Required<ElasticNetworkOptions>): GpuModel;
@@ -0,0 +1,114 @@
1
+ import { If, Loop, storage, vec3 } from "three/tsl";
2
+ import { StorageBufferAttribute } from "three/webgpu";
3
+ import { assert } from "../../assert.js";
4
+ var gpu;
5
+ (function (gpu) {
6
+ const FLOATS_PER_FLOAT = 1;
7
+ const FLOATS_PER_VEC4 = 4;
8
+ function vector(vectors) {
9
+ const array = new Float32Array(vectors.length * FLOATS_PER_VEC4);
10
+ for (const [index, vector] of vectors.entries()) {
11
+ array.set(vector, index * FLOATS_PER_VEC4);
12
+ }
13
+ return storage(new StorageBufferAttribute(array, FLOATS_PER_VEC4), "vec4", vectors.length);
14
+ }
15
+ gpu.vector = vector;
16
+ function float(values) {
17
+ return storage(new StorageBufferAttribute(Float32Array.from(values), FLOATS_PER_FLOAT), "float", values.length);
18
+ }
19
+ gpu.float = float;
20
+ function uint(values) {
21
+ return storage(new StorageBufferAttribute(Uint32Array.from(values), FLOATS_PER_FLOAT), "uint", values.length);
22
+ }
23
+ gpu.uint = uint;
24
+ })(gpu || (gpu = {}));
25
+ function springsOf(connections, particleCount) {
26
+ const springs = Array.from({ length: particleCount }, () => []);
27
+ for (const { between, restLength } of connections) {
28
+ const [i, j] = between;
29
+ const [springsI, springsJ] = [springs[i], springs[j]];
30
+ assert(springsI !== undefined && springsJ !== undefined, `connection references a missing particle`);
31
+ springsI.push({ neighbor: j, restLength });
32
+ springsJ.push({ neighbor: i, restLength });
33
+ }
34
+ return springs;
35
+ }
36
+ // Each spring is listed under both of its particles, so a particle's springs
37
+ // are the contiguous range offsets[particle]..offsets[particle + 1].
38
+ function adjacency(connections, particleCount) {
39
+ const springs = springsOf(connections, particleCount);
40
+ const offsets = [0];
41
+ let total = 0;
42
+ for (const own of springs) {
43
+ total += own.length;
44
+ offsets.push(total);
45
+ }
46
+ const flat = springs.flat();
47
+ return {
48
+ offsets: gpu.uint(offsets),
49
+ neighbors: gpu.uint(flat.map(({ neighbor }) => neighbor)),
50
+ restLengths: gpu.float(flat.map(({ restLength }) => restLength)),
51
+ };
52
+ }
53
+ var forces;
54
+ (function (forces) {
55
+ function spring(options) {
56
+ const { particles, index, position, velocity, connections, particleCount, stiffness, springDamping } = options;
57
+ const springForce = vec3(0, 0, 0).toVar();
58
+ if (connections.length === 0) {
59
+ return springForce;
60
+ }
61
+ const { offsets, neighbors, restLengths } = adjacency(connections, particleCount);
62
+ const range = { start: offsets.element(index), end: offsets.element(index.add(1)), type: "uint" };
63
+ Loop(range, ({ i }) => {
64
+ const neighbor = neighbors.element(i);
65
+ const delta = particles.position(neighbor).sub(position);
66
+ const distance = delta.length();
67
+ If(distance.greaterThan(0), () => {
68
+ const normal = delta.div(distance);
69
+ const along = particles.velocity(neighbor).sub(velocity).dot(normal);
70
+ const scalar = distance.sub(restLengths.element(i)).mul(stiffness).add(along.mul(springDamping));
71
+ springForce.addAssign(normal.mul(scalar));
72
+ });
73
+ });
74
+ return springForce;
75
+ }
76
+ forces.spring = spring;
77
+ function anchor(options) {
78
+ const { position, index, restPositions, anchorStrength } = options;
79
+ if (restPositions.length === 0) {
80
+ return vec3(0, 0, 0);
81
+ }
82
+ const rest = gpu.vector(restPositions).element(index).xyz;
83
+ return position.sub(rest).mul(anchorStrength);
84
+ }
85
+ forces.anchor = anchor;
86
+ function damping(velocity, globalDamping) {
87
+ return velocity.mul(globalDamping);
88
+ }
89
+ forces.damping = damping;
90
+ })(forces || (forces = {}));
91
+ export function gpuModel(options) {
92
+ const { masses, connections, restPositions, stiffness, springDamping, anchorStrength, globalDamping } = options;
93
+ return {
94
+ acceleration({ particles, index }) {
95
+ const position = particles.position(index).toVar();
96
+ const velocity = particles.velocity(index).toVar();
97
+ const springForce = forces.spring({
98
+ particles,
99
+ index,
100
+ position,
101
+ velocity,
102
+ connections,
103
+ particleCount: masses.length,
104
+ stiffness,
105
+ springDamping,
106
+ });
107
+ const drag = forces.damping(velocity, globalDamping);
108
+ const pull = forces.anchor({ position, index, restPositions, anchorStrength });
109
+ const net = springForce.sub(drag).sub(pull);
110
+ return net.div(gpu.float(masses).element(index));
111
+ },
112
+ };
113
+ }
114
+ //# sourceMappingURL=gpu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu.js","sourceRoot":"","sources":["../../../src/model/elastic-network/gpu.ts"],"names":[],"mappings":"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","sourcesContent":["import { If, Loop, storage, vec3 } from \"three/tsl\";\nimport { type Node, StorageBufferAttribute } from \"three/webgpu\";\nimport { assert } from \"@/assert.js\";\nimport type { Connection, ElasticNetworkOptions, GpuModel, GpuParticles, Vector } from \"@/types.js\";\n\nnamespace gpu {\n const FLOATS_PER_FLOAT = 1;\n const FLOATS_PER_VEC4 = 4;\n\n export function vector(vectors: readonly Vector[]) {\n const array = new Float32Array(vectors.length * FLOATS_PER_VEC4);\n for (const [index, vector] of vectors.entries()) {\n array.set(vector, index * FLOATS_PER_VEC4);\n }\n return storage(new StorageBufferAttribute(array, FLOATS_PER_VEC4), \"vec4\", vectors.length);\n }\n\n export function float(values: readonly number[]) {\n return storage(new StorageBufferAttribute(Float32Array.from(values), FLOATS_PER_FLOAT), \"float\", values.length);\n }\n\n export function uint(values: readonly number[]) {\n return storage(new StorageBufferAttribute(Uint32Array.from(values), FLOATS_PER_FLOAT), \"uint\", values.length);\n }\n}\n\ninterface Spring {\n neighbor: number;\n restLength: number;\n}\n\nfunction springsOf(connections: readonly Connection[], particleCount: number): Spring[][] {\n const springs: Spring[][] = Array.from({ length: particleCount }, () => []);\n\n for (const { between, restLength } of connections) {\n const [i, j] = between;\n const [springsI, springsJ] = [springs[i], springs[j]];\n assert(springsI !== undefined && springsJ !== undefined, `connection references a missing particle`);\n springsI.push({ neighbor: j, restLength });\n springsJ.push({ neighbor: i, restLength });\n }\n\n return springs;\n}\n\n// Each spring is listed under both of its particles, so a particle's springs\n// are the contiguous range offsets[particle]..offsets[particle + 1].\nfunction adjacency(connections: readonly Connection[], particleCount: number) {\n const springs = springsOf(connections, particleCount);\n const offsets = [0];\n let total = 0;\n for (const own of springs) {\n total += own.length;\n offsets.push(total);\n }\n const flat = springs.flat();\n\n return {\n offsets: gpu.uint(offsets),\n neighbors: gpu.uint(flat.map(({ neighbor }) => neighbor)),\n restLengths: gpu.float(flat.map(({ restLength }) => restLength)),\n };\n}\n\nnamespace forces {\n export function spring(options: {\n particles: GpuParticles;\n index: Node<\"uint\">;\n position: Node<\"vec3\">;\n velocity: Node<\"vec3\">;\n connections: readonly Connection[];\n particleCount: number;\n stiffness: number;\n springDamping: number;\n }) {\n const { particles, index, position, velocity, connections, particleCount, stiffness, springDamping } = options;\n const springForce = vec3(0, 0, 0).toVar();\n if (connections.length === 0) {\n return springForce;\n }\n\n const { offsets, neighbors, restLengths } = adjacency(connections, particleCount);\n const range = { start: offsets.element(index), end: offsets.element(index.add(1)), type: \"uint\" } as const;\n\n Loop(range, ({ i }) => {\n const neighbor = neighbors.element(i);\n const delta = particles.position(neighbor).sub(position);\n const distance = delta.length();\n\n If(distance.greaterThan(0), () => {\n const normal = delta.div(distance);\n const along = particles.velocity(neighbor).sub(velocity).dot(normal);\n const scalar = distance.sub(restLengths.element(i)).mul(stiffness).add(along.mul(springDamping));\n springForce.addAssign(normal.mul(scalar));\n });\n });\n\n return springForce;\n }\n\n export function anchor(options: {\n position: Node<\"vec3\">;\n index: Node<\"uint\">;\n restPositions: readonly Vector[];\n anchorStrength: number;\n }) {\n const { position, index, restPositions, anchorStrength } = options;\n if (restPositions.length === 0) {\n return vec3(0, 0, 0);\n }\n const rest = gpu.vector(restPositions).element(index).xyz;\n return position.sub(rest).mul(anchorStrength);\n }\n\n export function damping(velocity: Node<\"vec3\">, globalDamping: number) {\n return velocity.mul(globalDamping);\n }\n}\n\nexport function gpuModel(options: Required<ElasticNetworkOptions>): GpuModel {\n const { masses, connections, restPositions, stiffness, springDamping, anchorStrength, globalDamping } = options;\n\n return {\n acceleration({ particles, index }) {\n const position = particles.position(index).toVar();\n const velocity = particles.velocity(index).toVar();\n\n const springForce = forces.spring({\n particles,\n index,\n position,\n velocity,\n connections,\n particleCount: masses.length,\n stiffness,\n springDamping,\n });\n const drag = forces.damping(velocity, globalDamping);\n const pull = forces.anchor({ position, index, restPositions, anchorStrength });\n\n const net = springForce.sub(drag).sub(pull);\n return net.div(gpu.float(masses).element(index));\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ElasticNetworkOptions, Model } from "../../types.js";
2
+ export declare function elasticNetwork(options: ElasticNetworkOptions): Model;
@@ -0,0 +1,69 @@
1
+ import { assert } from "../../assert.js";
2
+ import { cpu } from "../../model/elastic-network/cpu.js";
3
+ import { gpuModel } from "../../model/elastic-network/gpu.js";
4
+ var validate;
5
+ (function (validate) {
6
+ function network(options) {
7
+ const { masses, connections = [], restPositions = [], stiffness = 0, springDamping = 0, anchorStrength = 0, globalDamping = 0, } = options;
8
+ validate.mass(masses);
9
+ validate.coefficients({ stiffness, springDamping, anchorStrength, globalDamping });
10
+ validate.restPositions(restPositions, masses.length);
11
+ validate.connections(connections, masses.length);
12
+ return {
13
+ masses,
14
+ connections,
15
+ restPositions,
16
+ stiffness,
17
+ springDamping,
18
+ anchorStrength,
19
+ globalDamping,
20
+ };
21
+ }
22
+ validate.network = network;
23
+ function mass(masses) {
24
+ for (const [particle, mass] of masses.entries()) {
25
+ assert(Number.isFinite(mass) && mass > 0, `mass ${particle} must be positive and finite, but is ${mass}`);
26
+ }
27
+ }
28
+ validate.mass = mass;
29
+ function coefficients(coefficients) {
30
+ for (const [name, value] of Object.entries(coefficients)) {
31
+ assert(Number.isFinite(value), `${name} must be finite, but is ${value}`);
32
+ assert(value >= 0, `${name} must be non-negative, but is ${value}`);
33
+ }
34
+ }
35
+ validate.coefficients = coefficients;
36
+ function restPositions(restPositions, count) {
37
+ if (restPositions.length === 0) {
38
+ return;
39
+ }
40
+ assert(restPositions.length === count, `expected ${count} rest positions, but got ${restPositions.length}`);
41
+ }
42
+ validate.restPositions = restPositions;
43
+ function connections(connections, count) {
44
+ const seen = new Set();
45
+ for (const [index, { between, restLength }] of connections.entries()) {
46
+ const [i, j] = between;
47
+ assert(Number.isInteger(i) && i >= 0 && i < count, `connection ${index} references particle ${i}, which is outside the range 0..${count - 1}`);
48
+ assert(Number.isInteger(j) && j >= 0 && j < count, `connection ${index} references particle ${j}, which is outside the range 0..${count - 1}`);
49
+ assert(i !== j, `connection ${index} joins particle ${i} to itself`);
50
+ assert(Number.isFinite(restLength) && restLength > 0, `connection ${index} rest length must be positive and finite, but is ${restLength}`);
51
+ const key = i < j ? `${i}-${j}` : `${j}-${i}`;
52
+ assert(!seen.has(key), `connection ${index} duplicates the spring between particles ${i} and ${j}`);
53
+ seen.add(key);
54
+ }
55
+ }
56
+ validate.connections = connections;
57
+ })(validate || (validate = {}));
58
+ export function elasticNetwork(options) {
59
+ const validated = validate.network(options);
60
+ const { masses } = validated;
61
+ return {
62
+ validate({ particleCount }) {
63
+ assert(masses.length === particleCount, `expected ${particleCount} masses, one per particle, but got ${masses.length}`);
64
+ },
65
+ cpu: cpu(validated),
66
+ gpu: gpuModel(validated),
67
+ };
68
+ }
69
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/model/elastic-network/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,MAAM,gCAAgC,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAG1D,IAAU,QAAQ,CAyEjB;AAzED,WAAU,QAAQ;IAChB,SAAgB,OAAO,CAAC,OAA8B;QACpD,MAAM,EACJ,MAAM,EACN,WAAW,GAAG,EAAE,EAChB,aAAa,GAAG,EAAE,EAClB,SAAS,GAAG,CAAC,EACb,aAAa,GAAG,CAAC,EACjB,cAAc,GAAG,CAAC,EAClB,aAAa,GAAG,CAAC,GAClB,GAAG,OAAO,CAAC;QAEZ,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,CAAC;QACnF,QAAQ,CAAC,aAAa,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACrD,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAEjD,OAAO;YACL,MAAM;YACN,WAAW;YACX,aAAa;YACb,SAAS;YACT,aAAa;YACb,cAAc;YACd,aAAa;SACd,CAAC;IACJ,CAAC;IAzBe,gBAAO,UAyBtB,CAAA;IAED,SAAgB,IAAI,CAAC,MAAyB;QAC5C,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;YAChD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,QAAQ,QAAQ,wCAAwC,IAAI,EAAE,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;IAJe,aAAI,OAInB,CAAA;IAED,SAAgB,YAAY,CAAC,YAAoC;QAC/D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,2BAA2B,KAAK,EAAE,CAAC,CAAC;YAC1E,MAAM,CAAC,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,iCAAiC,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IALe,qBAAY,eAK3B,CAAA;IAED,SAAgB,aAAa,CAAC,aAAgC,EAAE,KAAa;QAC3E,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK,EAAE,YAAY,KAAK,4BAA4B,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9G,CAAC;IALe,sBAAa,gBAK5B,CAAA;IAED,SAAgB,WAAW,CAAC,WAAkC,EAAE,KAAa;QAC3E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAE/B,KAAK,MAAM,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;YACrE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC;YAEvB,MAAM,CACJ,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,EAC1C,cAAc,KAAK,wBAAwB,CAAC,mCAAmC,KAAK,GAAG,CAAC,EAAE,CAC3F,CAAC;YACF,MAAM,CACJ,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,EAC1C,cAAc,KAAK,wBAAwB,CAAC,mCAAmC,KAAK,GAAG,CAAC,EAAE,CAC3F,CAAC;YACF,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,cAAc,KAAK,mBAAmB,CAAC,YAAY,CAAC,CAAC;YACrE,MAAM,CACJ,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,EAC7C,cAAc,KAAK,oDAAoD,UAAU,EAAE,CACpF,CAAC;YAEF,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,cAAc,KAAK,4CAA4C,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACpG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAxBe,oBAAW,cAwB1B,CAAA;AACH,CAAC,EAzES,QAAQ,KAAR,QAAQ,QAyEjB;AAED,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAE7B,OAAO;QACL,QAAQ,CAAC,EAAE,aAAa,EAA0B;YAChD,MAAM,CACJ,MAAM,CAAC,MAAM,KAAK,aAAa,EAC/B,YAAY,aAAa,sCAAsC,MAAM,CAAC,MAAM,EAAE,CAC/E,CAAC;QACJ,CAAC;QACD,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC;QACnB,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC;KACzB,CAAC;AACJ,CAAC","sourcesContent":["import { assert } from \"@/assert.js\";\nimport { cpu } from \"@/model/elastic-network/cpu.js\";\nimport { gpuModel } from \"@/model/elastic-network/gpu.js\";\nimport type { Connection, ElasticNetworkOptions, Model, ModelValidationContext, Vector } from \"@/types.js\";\n\nnamespace validate {\n export function network(options: ElasticNetworkOptions): Required<ElasticNetworkOptions> {\n const {\n masses,\n connections = [],\n restPositions = [],\n stiffness = 0,\n springDamping = 0,\n anchorStrength = 0,\n globalDamping = 0,\n } = options;\n\n validate.mass(masses);\n validate.coefficients({ stiffness, springDamping, anchorStrength, globalDamping });\n validate.restPositions(restPositions, masses.length);\n validate.connections(connections, masses.length);\n\n return {\n masses,\n connections,\n restPositions,\n stiffness,\n springDamping,\n anchorStrength,\n globalDamping,\n };\n }\n\n export function mass(masses: readonly number[]): void {\n for (const [particle, mass] of masses.entries()) {\n assert(Number.isFinite(mass) && mass > 0, `mass ${particle} must be positive and finite, but is ${mass}`);\n }\n }\n\n export function coefficients(coefficients: Record<string, number>): void {\n for (const [name, value] of Object.entries(coefficients)) {\n assert(Number.isFinite(value), `${name} must be finite, but is ${value}`);\n assert(value >= 0, `${name} must be non-negative, but is ${value}`);\n }\n }\n\n export function restPositions(restPositions: readonly Vector[], count: number): void {\n if (restPositions.length === 0) {\n return;\n }\n assert(restPositions.length === count, `expected ${count} rest positions, but got ${restPositions.length}`);\n }\n\n export function connections(connections: readonly Connection[], count: number): void {\n const seen = new Set<string>();\n\n for (const [index, { between, restLength }] of connections.entries()) {\n const [i, j] = between;\n\n assert(\n Number.isInteger(i) && i >= 0 && i < count,\n `connection ${index} references particle ${i}, which is outside the range 0..${count - 1}`,\n );\n assert(\n Number.isInteger(j) && j >= 0 && j < count,\n `connection ${index} references particle ${j}, which is outside the range 0..${count - 1}`,\n );\n assert(i !== j, `connection ${index} joins particle ${i} to itself`);\n assert(\n Number.isFinite(restLength) && restLength > 0,\n `connection ${index} rest length must be positive and finite, but is ${restLength}`,\n );\n\n const key = i < j ? `${i}-${j}` : `${j}-${i}`;\n assert(!seen.has(key), `connection ${index} duplicates the spring between particles ${i} and ${j}`);\n seen.add(key);\n }\n }\n}\n\nexport function elasticNetwork(options: ElasticNetworkOptions): Model {\n const validated = validate.network(options);\n const { masses } = validated;\n\n return {\n validate({ particleCount }: ModelValidationContext): void {\n assert(\n masses.length === particleCount,\n `expected ${particleCount} masses, one per particle, but got ${masses.length}`,\n );\n },\n cpu: cpu(validated),\n gpu: gpuModel(validated),\n };\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import type { ElasticNetworkOptions, Model } from "../types.js";
2
+ /**
3
+ * Defines the forces acting on a swarm's particles.
4
+ */
5
+ export declare namespace model {
6
+ /**
7
+ * Applies no forces, so particles coast at their current velocity.
8
+ */
9
+ function inertial(): Model;
10
+ /**
11
+ * A network of particles joined by fixed springs and dampers, each optionally anchored to a
12
+ * rest position. The environment also provides drag force proportional to the particle's velocity.
13
+ */
14
+ function elasticNetwork(options: ElasticNetworkOptions): Model;
15
+ }
@@ -0,0 +1,24 @@
1
+ import { elasticNetwork as createElasticNetwork } from "../model/elastic-network/index.js";
2
+ import { inertial as createInertial } from "../model/inertial.js";
3
+ /**
4
+ * Defines the forces acting on a swarm's particles.
5
+ */
6
+ export var model;
7
+ (function (model) {
8
+ /**
9
+ * Applies no forces, so particles coast at their current velocity.
10
+ */
11
+ function inertial() {
12
+ return createInertial();
13
+ }
14
+ model.inertial = inertial;
15
+ /**
16
+ * A network of particles joined by fixed springs and dampers, each optionally anchored to a
17
+ * rest position. The environment also provides drag force proportional to the particle's velocity.
18
+ */
19
+ function elasticNetwork(options) {
20
+ return createElasticNetwork(options);
21
+ }
22
+ model.elasticNetwork = elasticNetwork;
23
+ })(model || (model = {}));
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,IAAI,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAC1F,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGjE;;GAEG;AACH,MAAM,KAAW,KAAK,CAerB;AAfD,WAAiB,KAAK;IACpB;;OAEG;IACH,SAAgB,QAAQ;QACtB,OAAO,cAAc,EAAE,CAAC;IAC1B,CAAC;IAFe,cAAQ,WAEvB,CAAA;IAED;;;OAGG;IACH,SAAgB,cAAc,CAAC,OAA8B;QAC3D,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAFe,oBAAc,iBAE7B,CAAA;AACH,CAAC,EAfgB,KAAK,KAAL,KAAK,QAerB","sourcesContent":["import { elasticNetwork as createElasticNetwork } from \"@/model/elastic-network/index.js\";\nimport { inertial as createInertial } from \"@/model/inertial.js\";\nimport type { ElasticNetworkOptions, Model } from \"@/types.js\";\n\n/**\n * Defines the forces acting on a swarm's particles.\n */\nexport namespace model {\n /**\n * Applies no forces, so particles coast at their current velocity.\n */\n export function inertial(): Model {\n return createInertial();\n }\n\n /**\n * A network of particles joined by fixed springs and dampers, each optionally anchored to a\n * rest position. The environment also provides drag force proportional to the particle's velocity.\n */\n export function elasticNetwork(options: ElasticNetworkOptions): Model {\n return createElasticNetwork(options);\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Model } from "../types.js";
2
+ export declare function inertial(): Model;
@@ -0,0 +1,17 @@
1
+ import { vec3 } from "three/tsl";
2
+ const ZERO = [0, 0, 0];
3
+ export function inertial() {
4
+ return {
5
+ cpu: {
6
+ accelerate(particles) {
7
+ return particles.positions.map(() => ZERO);
8
+ },
9
+ },
10
+ gpu: {
11
+ acceleration() {
12
+ return vec3(0, 0, 0);
13
+ },
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=inertial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inertial.js","sourceRoot":"","sources":["../../src/model/inertial.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,IAAI,GAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/B,MAAM,UAAU,QAAQ;IACtB,OAAO;QACL,GAAG,EAAE;YACH,UAAU,CAAC,SAAoB;gBAC7B,OAAO,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAC7C,CAAC;SACF;QACD,GAAG,EAAE;YACH,YAAY;gBACV,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACvB,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { vec3 } from \"three/tsl\";\nimport type { Model, Particles, Vector } from \"@/types.js\";\n\nconst ZERO: Vector = [0, 0, 0];\n\nexport function inertial(): Model {\n return {\n cpu: {\n accelerate(particles: Particles): readonly Vector[] {\n return particles.positions.map(() => ZERO);\n },\n },\n gpu: {\n acceleration() {\n return vec3(0, 0, 0);\n },\n },\n };\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { ScalarNode } from "../../render/backend/compile.js";
2
+ import type { Channel } from "../../render/channel.js";
3
+ import { type ColorValue, type ScalarValue } from "../../render/ramp.js";
4
+ import type { Source } from "../../render/sources.js";
5
+ /**
6
+ * All distinct channels that the specified values are based on.
7
+ */
8
+ export declare function channelsOf<S extends Source>(values: readonly (ScalarValue<S> | ColorValue<S> | undefined)[]): readonly Channel<S>[];
9
+ /**
10
+ * The name a channel's per-element values are uploaded under for the shader to
11
+ * read, such as `aSpeed` for a `speed` channel.
12
+ */
13
+ export declare function attributeNameOf<S extends Source>(channel: Channel<S>): string;
14
+ /**
15
+ * The shader-graph node that reads a channel's uploaded per-element values.
16
+ */
17
+ export declare function channelAttribute<S extends Source>(channel: Channel<S>): ScalarNode;
@@ -0,0 +1,35 @@
1
+ import { attribute } from "three/tsl";
2
+ import { assert } from "../../assert.js";
3
+ import { ramp } from "../../render/ramp.js";
4
+ /**
5
+ * All distinct channels that the specified values are based on.
6
+ */
7
+ export function channelsOf(values) {
8
+ const found = [];
9
+ for (const value of values) {
10
+ if (value === undefined || !ramp.is(value)) {
11
+ continue;
12
+ }
13
+ const { channel } = value.by;
14
+ if (!found.includes(channel)) {
15
+ found.push(channel);
16
+ }
17
+ }
18
+ return found;
19
+ }
20
+ /**
21
+ * The name a channel's per-element values are uploaded under for the shader to
22
+ * read, such as `aSpeed` for a `speed` channel.
23
+ */
24
+ export function attributeNameOf(channel) {
25
+ const initial = channel.name[0];
26
+ assert(initial !== undefined, "a channel name cannot be empty");
27
+ return `a${initial.toUpperCase()}${channel.name.slice(1)}`;
28
+ }
29
+ /**
30
+ * The shader-graph node that reads a channel's uploaded per-element values.
31
+ */
32
+ export function channelAttribute(channel) {
33
+ return attribute(attributeNameOf(channel), "float");
34
+ }
35
+ //# sourceMappingURL=channels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channels.js","sourceRoot":"","sources":["../../../src/render/backend/channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAmB,IAAI,EAAoB,MAAM,kBAAkB,CAAC;AAG3E;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,MAA+D;IAE/D,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAmB,OAAmB;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,gCAAgC,CAAC,CAAC;IAChE,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAmB,OAAmB;IACpE,OAAO,SAAS,CAAU,eAAe,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["import { attribute } from \"three/tsl\";\nimport { assert } from \"@/assert.js\";\nimport type { ScalarNode } from \"@/render/backend/compile.js\";\nimport type { Channel } from \"@/render/channel.js\";\nimport { type ColorValue, ramp, type ScalarValue } from \"@/render/ramp.js\";\nimport type { Source } from \"@/render/sources.js\";\n\n/**\n * All distinct channels that the specified values are based on.\n */\nexport function channelsOf<S extends Source>(\n values: readonly (ScalarValue<S> | ColorValue<S> | undefined)[],\n): readonly Channel<S>[] {\n const found: Channel<S>[] = [];\n for (const value of values) {\n if (value === undefined || !ramp.is(value)) {\n continue;\n }\n const { channel } = value.by;\n if (!found.includes(channel)) {\n found.push(channel);\n }\n }\n return found;\n}\n\n/**\n * The name a channel's per-element values are uploaded under for the shader to\n * read, such as `aSpeed` for a `speed` channel.\n */\nexport function attributeNameOf<S extends Source>(channel: Channel<S>): string {\n const initial = channel.name[0];\n assert(initial !== undefined, \"a channel name cannot be empty\");\n return `a${initial.toUpperCase()}${channel.name.slice(1)}`;\n}\n\n/**\n * The shader-graph node that reads a channel's uploaded per-element values.\n */\nexport function channelAttribute<S extends Source>(channel: Channel<S>): ScalarNode {\n return attribute<\"float\">(attributeNameOf(channel), \"float\");\n}\n"]}