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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zurab Mujirishvili
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,112 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <a href="https://zurabm.gitlab.io/gloop/">
3
+ <img src="https://cdn.jsdelivr.net/npm/gloop-sim/docs/assets/logo.svg" width="180" alt="Gloop logo">
4
+ </a>
5
+ </p>
2
6
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
7
+ [![Pipeline Status](https://gitlab.com/zurabm/gloop/badges/main/pipeline.svg)](https://gitlab.com/zurabm/gloop/-/pipelines) [![Coverage](https://gitlab.com/zurabm/gloop/badges/main/coverage.svg)](https://gitlab.com/zurabm/gloop/-/pipelines) [![npm](https://img.shields.io/npm/v/gloop-sim)](https://www.npmjs.com/package/gloop-sim) [![Docs](https://img.shields.io/badge/docs-zurabm.gitlab.io%2Fgloop-blue)](https://zurabm.gitlab.io/gloop/) [![License](https://img.shields.io/npm/l/gloop-sim)](https://gitlab.com/zurabm/gloop/-/blob/main/LICENSE)
8
+
9
+ # Gloop: A Swarm Simulation Library for the Browser
10
+
11
+ Gloop simulates and renders particle swarms in the browser. A swarm is built from four parts: an initializer that places the particles, a model that defines the forces between them, an integrator that advances them in time, and a backend that runs the computation (using the CPU or GPU).
12
+
13
+ The rendering API draws a swarm with [three.js](https://threejs.org/). The [docs site](https://zurabm.gitlab.io/gloop/) embeds an interactive lab where you can run swarms of different sizes and configurations.
14
+
15
+ > Gloop is in early development, so some features are missing and parts of the API may change. You can help by [reporting issues](https://gitlab.com/zurabm/gloop/-/issues) or contributing fixes and features.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add gloop-sim three@0.185.1
21
+ ```
22
+
23
+ `three` is a required peer dependency. The GPU backend needs WebGPU to be enabled in your browser.
24
+
25
+ ## Quick Start
26
+
27
+ Here's a swarm of three particles connected by springs, simulated on the CPU:
28
+
29
+ ```ts
30
+ import { gloop } from "gloop-sim";
31
+
32
+ const swarm = await gloop.swarm({
33
+ particleCount: 3,
34
+ initializer: gloop.initializer.fixed({
35
+ positions: [
36
+ [0, 0, 0],
37
+ [1, 0, 0],
38
+ [0, 1, 0],
39
+ ],
40
+ velocities: [
41
+ [0, 0, 0],
42
+ [0, 0, 0],
43
+ [0, 0, 0],
44
+ ],
45
+ }),
46
+ model: gloop.model.elasticNetwork({
47
+ masses: [1, 1, 1],
48
+ connections: [
49
+ { between: [0, 1], restLength: 1 },
50
+ { between: [0, 2], restLength: 1 },
51
+ { between: [1, 2], restLength: Math.SQRT2 },
52
+ ],
53
+ stiffness: 40,
54
+ springDamping: 1.5,
55
+ }),
56
+ integrator: gloop.integrator.semiImplicitEuler(),
57
+ backend: gloop.backend.cpu(),
58
+ });
59
+ ```
60
+
61
+ Replacing `gloop.backend.cpu()` with `gloop.backend.gpu()` runs the same swarm on the GPU.
62
+
63
+ <details>
64
+ <summary><strong>Advancing the swarm and reading its state</strong></summary>
65
+
66
+ <br>
67
+
68
+ `advance` steps the simulation forward by the given amount of simulated time. `snapshot` reads the current state back, which is asynchronous because the GPU backend has to copy it from the device.
69
+
70
+ ```ts
71
+ for (let step = 0; step < 30; step++) {
72
+ swarm.advance({ time: 1 / 60 });
73
+ }
74
+
75
+ const state = await swarm.snapshot();
76
+ console.log(state.time, state.particles.positions);
77
+ ```
78
+
79
+ </details>
80
+
81
+ For a step-by-step walkthrough, see the [Getting started guide](https://zurabm.gitlab.io/gloop/guides/getting-started/).
82
+
83
+ ## Documentation
84
+
85
+ | | |
86
+ | ------------------------------------------------------------------------- | ------------------------------------------------------- |
87
+ | [Getting started](https://zurabm.gitlab.io/gloop/guides/getting-started/) | Installation, a first swarm, switching to the GPU |
88
+ | [Guides](https://zurabm.gitlab.io/gloop/guides/) | Core concepts, models and forces, integrators, backends |
89
+ | [Rendering](https://zurabm.gitlab.io/gloop/guides/rendering/) | Drawing a swarm and its springs with three.js |
90
+ | [API Reference](https://zurabm.gitlab.io/gloop/reference/core/) | Every exported function and type |
91
+
92
+ ## Benchmarks
93
+
94
+ ```bash
95
+ pnpm bench
96
+ ```
97
+
98
+ This opens Chromium with WebGPU enabled and times both backends across a range of swarm sizes. Each run is archived to `generated/bench/runs/`, and the report is written to `generated/bench/report.html` with a text summary in `generated/bench/summary.md`.
99
+
100
+ To compare two archived runs, rebuild the report with one as the baseline:
101
+
102
+ ```bash
103
+ pnpm bench:report --run generated/bench/runs/<new>.json --baseline generated/bench/runs/<old>.json
104
+ ```
105
+
106
+ ## Contributing
107
+
108
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
109
+
110
+ ## License
111
+
112
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1 @@
1
+ export declare function assert(condition: unknown, message: string): asserts condition;
package/dist/assert.js ADDED
@@ -0,0 +1,6 @@
1
+ export function assert(condition, message) {
2
+ if (!condition) {
3
+ throw new Error(message);
4
+ }
5
+ }
6
+ //# sourceMappingURL=assert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert.js","sourceRoot":"","sources":["../src/assert.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,MAAM,CAAC,SAAkB,EAAE,OAAe;IACxD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC","sourcesContent":["export function assert(condition: unknown, message: string): asserts condition {\n if (!condition) {\n throw new Error(message);\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Backend } from "../types.js";
2
+ export declare function createCpuBackend(): Backend;
@@ -0,0 +1,23 @@
1
+ import { assert } from "../assert.js";
2
+ export function createCpuBackend() {
3
+ return {
4
+ compile({ particles, model, integrator }) {
5
+ const cpuModel = model.cpu;
6
+ assert(cpuModel !== undefined, "the model has no cpu implementation");
7
+ let current = particles;
8
+ return Promise.resolve({
9
+ step(seconds) {
10
+ current = integrator.cpu.step({
11
+ particles: current,
12
+ accelerations: cpuModel.accelerate(current),
13
+ seconds,
14
+ });
15
+ },
16
+ snapshot() {
17
+ return Promise.resolve(current);
18
+ },
19
+ });
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=cpu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpu.js","sourceRoot":"","sources":["../../src/backend/cpu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAkB;YACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;YAC3B,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,qCAAqC,CAAC,CAAC;YAEtE,IAAI,OAAO,GAAG,SAAS,CAAC;YAExB,OAAO,OAAO,CAAC,OAAO,CAAC;gBACrB,IAAI,CAAC,OAAe;oBAClB,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;wBAC5B,SAAS,EAAE,OAAO;wBAClB,aAAa,EAAE,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;wBAC3C,OAAO;qBACR,CAAC,CAAC;gBACL,CAAC;gBACD,QAAQ;oBACN,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAClC,CAAC;aACF,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { assert } from \"@/assert.js\";\nimport type { Backend, CompileOptions, Executable, Particles } from \"@/types.js\";\n\nexport function createCpuBackend(): Backend {\n return {\n compile({ particles, model, integrator }: CompileOptions): Promise<Executable> {\n const cpuModel = model.cpu;\n assert(cpuModel !== undefined, \"the model has no cpu implementation\");\n\n let current = particles;\n\n return Promise.resolve({\n step(seconds: number): void {\n current = integrator.cpu.step({\n particles: current,\n accelerations: cpuModel.accelerate(current),\n seconds,\n });\n },\n snapshot(): Promise<Particles> {\n return Promise.resolve(current);\n },\n });\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Backend, GpuBackendOptions } from "../types.js";
2
+ export declare function createGpuBackend({ device }?: GpuBackendOptions): Backend;
@@ -0,0 +1,145 @@
1
+ import { Fn, instanceIndex, storage, uniform, vec4 } from "three/tsl";
2
+ import { StorageBufferAttribute, WebGPURenderer } from "three/webgpu";
3
+ import { assert } from "../assert.js";
4
+ function bind(attribute, count) {
5
+ return storage(attribute, "vec4", count);
6
+ }
7
+ function secondsBuffer() {
8
+ return uniform(0);
9
+ }
10
+ var buffer;
11
+ (function (buffer) {
12
+ const FLOATS_PER_VEC4 = 4;
13
+ function vector(vectors) {
14
+ const array = new Float32Array(vectors.length * FLOATS_PER_VEC4);
15
+ for (const [particle, vector] of vectors.entries()) {
16
+ array.set(vector, particle * FLOATS_PER_VEC4);
17
+ }
18
+ return new StorageBufferAttribute(array, FLOATS_PER_VEC4);
19
+ }
20
+ buffer.vector = vector;
21
+ function zeros(particleCount) {
22
+ return new StorageBufferAttribute(new Float32Array(particleCount * FLOATS_PER_VEC4), FLOATS_PER_VEC4);
23
+ }
24
+ buffer.zeros = zeros;
25
+ function decode(source, particleCount) {
26
+ const array = new Float32Array(source);
27
+ const vectors = [];
28
+ for (let particle = 0; particle < particleCount; particle++) {
29
+ const [x, y, z] = [
30
+ array[particle * FLOATS_PER_VEC4],
31
+ array[particle * FLOATS_PER_VEC4 + 1],
32
+ array[particle * FLOATS_PER_VEC4 + 2],
33
+ ];
34
+ assert(x !== undefined && y !== undefined && z !== undefined, `particle ${particle} is missing from the readback`);
35
+ vectors.push([x, y, z]);
36
+ }
37
+ return vectors;
38
+ }
39
+ buffer.decode = decode;
40
+ })(buffer || (buffer = {}));
41
+ function allocate(particles) {
42
+ const count = particles.count;
43
+ const positionAttribute = buffer.vector(particles.positions);
44
+ const velocityAttribute = buffer.vector(particles.velocities);
45
+ const accelerationAttribute = buffer.zeros(count);
46
+ return {
47
+ count,
48
+ positionAttribute,
49
+ velocityAttribute,
50
+ positions: bind(positionAttribute, count),
51
+ velocities: bind(velocityAttribute, count),
52
+ accelerations: bind(accelerationAttribute, count),
53
+ };
54
+ }
55
+ var pass;
56
+ (function (pass) {
57
+ function accelerate(model, buffers) {
58
+ const particles = {
59
+ count: buffers.count,
60
+ position: (index) => buffers.positions.element(index).xyz,
61
+ velocity: (index) => buffers.velocities.element(index).xyz,
62
+ };
63
+ return Fn(() => {
64
+ buffers.accelerations
65
+ .element(instanceIndex)
66
+ .assign(vec4(model.acceleration({ particles, index: instanceIndex }), 0));
67
+ })().compute(buffers.count);
68
+ }
69
+ pass.accelerate = accelerate;
70
+ function integrate(integrator, buffers, seconds) {
71
+ return Fn(() => {
72
+ const particle = {
73
+ position: buffers.positions.element(instanceIndex).xyz.toVar(),
74
+ velocity: buffers.velocities.element(instanceIndex).xyz.toVar(),
75
+ };
76
+ const next = integrator.gpu.step({
77
+ particle,
78
+ acceleration: buffers.accelerations.element(instanceIndex).xyz,
79
+ seconds,
80
+ });
81
+ buffers.positions.element(instanceIndex).assign(vec4(next.position, 0));
82
+ buffers.velocities.element(instanceIndex).assign(vec4(next.velocity, 0));
83
+ })().compute(buffers.count);
84
+ }
85
+ pass.integrate = integrate;
86
+ })(pass || (pass = {}));
87
+ function executable(renderer, buffers, seconds, accelerate, integrate) {
88
+ let dispatched = false;
89
+ async function latestBuffer(attribute) {
90
+ return dispatched ? renderer.getArrayBufferAsync(attribute) : attribute.array.buffer;
91
+ }
92
+ return {
93
+ step(time) {
94
+ // Dispatching over a zero-sized storage buffer is a WebGPU validation error.
95
+ if (buffers.count === 0) {
96
+ return;
97
+ }
98
+ seconds.value = time;
99
+ renderer.compute([accelerate, integrate]);
100
+ dispatched = true;
101
+ },
102
+ async snapshot() {
103
+ const [positionBuffer, velocityBuffer] = await Promise.all([
104
+ latestBuffer(buffers.positionAttribute),
105
+ latestBuffer(buffers.velocityAttribute),
106
+ ]);
107
+ return {
108
+ count: buffers.count,
109
+ positions: buffer.decode(positionBuffer, buffers.count),
110
+ velocities: buffer.decode(velocityBuffer, buffers.count),
111
+ };
112
+ },
113
+ };
114
+ }
115
+ class SharedRenderer {
116
+ device;
117
+ renderer;
118
+ constructor(device) {
119
+ this.device = device;
120
+ }
121
+ acquire() {
122
+ this.renderer ??= this.initialize();
123
+ return this.renderer;
124
+ }
125
+ async initialize() {
126
+ const renderer = new WebGPURenderer({ forceWebGL: false, device: this.device });
127
+ await renderer.init();
128
+ return renderer;
129
+ }
130
+ }
131
+ export function createGpuBackend({ device } = {}) {
132
+ const shared = new SharedRenderer(device);
133
+ return {
134
+ async compile({ particles, model, integrator }) {
135
+ const gpuModel = model.gpu;
136
+ assert(gpuModel !== undefined, "the model has no gpu implementation");
137
+ const buffers = allocate(particles);
138
+ const seconds = secondsBuffer();
139
+ const accelerate = pass.accelerate(gpuModel, buffers);
140
+ const integrate = pass.integrate(integrator, buffers, seconds);
141
+ return executable(await shared.acquire(), buffers, seconds, accelerate, integrate);
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=gpu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu.js","sourceRoot":"","sources":["../../src/backend/gpu.ts"],"names":[],"mappings":"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","sourcesContent":["import { Fn, instanceIndex, storage, uniform, vec4 } from \"three/tsl\";\nimport { StorageBufferAttribute, WebGPURenderer } from \"three/webgpu\";\nimport { assert } from \"@/assert.js\";\nimport type {\n Backend,\n CompileOptions,\n Device,\n Executable,\n GpuBackendOptions,\n GpuParticles,\n Particles,\n Vector,\n} from \"@/types.js\";\n\nfunction bind(attribute: StorageBufferAttribute, count: number) {\n return storage(attribute, \"vec4\", count);\n}\n\nfunction secondsBuffer() {\n return uniform(0);\n}\n\ntype StorageNode = ReturnType<typeof bind>;\ntype SecondsBuffer = ReturnType<typeof secondsBuffer>;\ntype GpuModel = NonNullable<CompileOptions[\"model\"][\"gpu\"]>;\ntype Integrator = CompileOptions[\"integrator\"];\ntype ComputePass = ReturnType<typeof pass.accelerate>;\n\ninterface Buffers {\n count: number;\n positionAttribute: StorageBufferAttribute;\n velocityAttribute: StorageBufferAttribute;\n positions: StorageNode;\n velocities: StorageNode;\n accelerations: StorageNode;\n}\n\nnamespace buffer {\n const FLOATS_PER_VEC4 = 4;\n\n export function vector(vectors: readonly Vector[]): StorageBufferAttribute {\n const array = new Float32Array(vectors.length * FLOATS_PER_VEC4);\n\n for (const [particle, vector] of vectors.entries()) {\n array.set(vector, particle * FLOATS_PER_VEC4);\n }\n\n return new StorageBufferAttribute(array, FLOATS_PER_VEC4);\n }\n\n export function zeros(particleCount: number): StorageBufferAttribute {\n return new StorageBufferAttribute(new Float32Array(particleCount * FLOATS_PER_VEC4), FLOATS_PER_VEC4);\n }\n\n export function decode(source: ArrayBufferLike, particleCount: number): readonly Vector[] {\n const array = new Float32Array(source);\n const vectors: Vector[] = [];\n\n for (let particle = 0; particle < particleCount; particle++) {\n const [x, y, z] = [\n array[particle * FLOATS_PER_VEC4],\n array[particle * FLOATS_PER_VEC4 + 1],\n array[particle * FLOATS_PER_VEC4 + 2],\n ];\n\n assert(\n x !== undefined && y !== undefined && z !== undefined,\n `particle ${particle} is missing from the readback`,\n );\n\n vectors.push([x, y, z]);\n }\n\n return vectors;\n }\n}\n\nfunction allocate(particles: Particles): Buffers {\n const count = particles.count;\n const positionAttribute = buffer.vector(particles.positions);\n const velocityAttribute = buffer.vector(particles.velocities);\n const accelerationAttribute = buffer.zeros(count);\n\n return {\n count,\n positionAttribute,\n velocityAttribute,\n positions: bind(positionAttribute, count),\n velocities: bind(velocityAttribute, count),\n accelerations: bind(accelerationAttribute, count),\n };\n}\n\nnamespace pass {\n export function accelerate(model: GpuModel, buffers: Buffers) {\n const particles: GpuParticles = {\n count: buffers.count,\n position: (index) => buffers.positions.element(index).xyz,\n velocity: (index) => buffers.velocities.element(index).xyz,\n };\n\n return Fn(() => {\n buffers.accelerations\n .element(instanceIndex)\n .assign(vec4(model.acceleration({ particles, index: instanceIndex }), 0));\n })().compute(buffers.count);\n }\n\n export function integrate(integrator: Integrator, buffers: Buffers, seconds: SecondsBuffer) {\n return Fn(() => {\n const particle = {\n position: buffers.positions.element(instanceIndex).xyz.toVar(),\n velocity: buffers.velocities.element(instanceIndex).xyz.toVar(),\n };\n const next = integrator.gpu.step({\n particle,\n acceleration: buffers.accelerations.element(instanceIndex).xyz,\n seconds,\n });\n\n buffers.positions.element(instanceIndex).assign(vec4(next.position, 0));\n buffers.velocities.element(instanceIndex).assign(vec4(next.velocity, 0));\n })().compute(buffers.count);\n }\n}\n\nfunction executable(\n renderer: WebGPURenderer,\n buffers: Buffers,\n seconds: SecondsBuffer,\n accelerate: ComputePass,\n integrate: ComputePass,\n): Executable {\n let dispatched = false;\n\n async function latestBuffer(attribute: StorageBufferAttribute): Promise<ArrayBufferLike> {\n return dispatched ? renderer.getArrayBufferAsync(attribute) : attribute.array.buffer;\n }\n\n return {\n step(time: number): void {\n // Dispatching over a zero-sized storage buffer is a WebGPU validation error.\n if (buffers.count === 0) {\n return;\n }\n\n seconds.value = time;\n renderer.compute([accelerate, integrate]);\n dispatched = true;\n },\n async snapshot(): Promise<Particles> {\n const [positionBuffer, velocityBuffer] = await Promise.all([\n latestBuffer(buffers.positionAttribute),\n latestBuffer(buffers.velocityAttribute),\n ]);\n\n return {\n count: buffers.count,\n positions: buffer.decode(positionBuffer, buffers.count),\n velocities: buffer.decode(velocityBuffer, buffers.count),\n };\n },\n };\n}\n\nclass SharedRenderer {\n private renderer: Promise<WebGPURenderer> | undefined;\n\n constructor(private readonly device: Device | undefined) {}\n\n acquire(): Promise<WebGPURenderer> {\n this.renderer ??= this.initialize();\n return this.renderer;\n }\n\n private async initialize(): Promise<WebGPURenderer> {\n const renderer = new WebGPURenderer({ forceWebGL: false, device: this.device });\n await renderer.init();\n return renderer;\n }\n}\n\nexport function createGpuBackend({ device }: GpuBackendOptions = {}): Backend {\n const shared = new SharedRenderer(device);\n\n return {\n async compile({ particles, model, integrator }: CompileOptions): Promise<Executable> {\n const gpuModel = model.gpu;\n assert(gpuModel !== undefined, \"the model has no gpu implementation\");\n\n const buffers = allocate(particles);\n const seconds = secondsBuffer();\n\n const accelerate = pass.accelerate(gpuModel, buffers);\n const integrate = pass.integrate(integrator, buffers, seconds);\n\n return executable(await shared.acquire(), buffers, seconds, accelerate, integrate);\n },\n };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { Backend, GpuBackendOptions } from "../types.js";
2
+ /**
3
+ * Executes a model and integrator over a swarm's state.
4
+ */
5
+ export declare namespace backend {
6
+ /**
7
+ * A backend for running swarm simulations on the CPU.
8
+ */
9
+ function cpu(): Backend;
10
+ /**
11
+ * A backend for running swarm simulations on the GPU using TSL.
12
+ */
13
+ function gpu(options?: GpuBackendOptions): Backend;
14
+ }
@@ -0,0 +1,23 @@
1
+ import { createCpuBackend } from "../backend/cpu.js";
2
+ import { createGpuBackend } from "../backend/gpu.js";
3
+ /**
4
+ * Executes a model and integrator over a swarm's state.
5
+ */
6
+ export var backend;
7
+ (function (backend) {
8
+ /**
9
+ * A backend for running swarm simulations on the CPU.
10
+ */
11
+ function cpu() {
12
+ return createCpuBackend();
13
+ }
14
+ backend.cpu = cpu;
15
+ /**
16
+ * A backend for running swarm simulations on the GPU using TSL.
17
+ */
18
+ function gpu(options = {}) {
19
+ return createGpuBackend(options);
20
+ }
21
+ backend.gpu = gpu;
22
+ })(backend || (backend = {}));
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD;;GAEG;AACH,MAAM,KAAW,OAAO,CAcvB;AAdD,WAAiB,OAAO;IACtB;;OAEG;IACH,SAAgB,GAAG;QACjB,OAAO,gBAAgB,EAAE,CAAC;IAC5B,CAAC;IAFe,WAAG,MAElB,CAAA;IAED;;OAEG;IACH,SAAgB,GAAG,CAAC,UAA6B,EAAE;QACjD,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAFe,WAAG,MAElB,CAAA;AACH,CAAC,EAdgB,OAAO,KAAP,OAAO,QAcvB","sourcesContent":["import { createCpuBackend } from \"@/backend/cpu.js\";\nimport { createGpuBackend } from \"@/backend/gpu.js\";\nimport type { Backend, GpuBackendOptions } from \"@/types.js\";\n\n/**\n * Executes a model and integrator over a swarm's state.\n */\nexport namespace backend {\n /**\n * A backend for running swarm simulations on the CPU.\n */\n export function cpu(): Backend {\n return createCpuBackend();\n }\n\n /**\n * A backend for running swarm simulations on the GPU using TSL.\n */\n export function gpu(options: GpuBackendOptions = {}): Backend {\n return createGpuBackend(options);\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { Device } from "./types.js";
2
+ /**
3
+ * Acquires the devices a backend runs on.
4
+ */
5
+ export declare namespace device {
6
+ /**
7
+ * Acquires a WebGPU device, or `undefined` when this browser has no GPU to
8
+ * run on or an adapter that would rasterize on the CPU.
9
+ */
10
+ function gpu(): Promise<Device | undefined>;
11
+ }
package/dist/device.js ADDED
@@ -0,0 +1,66 @@
1
+ // Adapter names that indicate that the browser is rasterizing WebGPU on the CPU.
2
+ // These devices are slower than the CPU backend, so we assume it's the same as no device.
3
+ const SOFTWARE_ADAPTERS = ["basic render driver", "lavapipe", "llvmpipe", "software", "swiftshader", "warp"];
4
+ function navigatorGpu() {
5
+ if (typeof navigator === "undefined") {
6
+ return undefined;
7
+ }
8
+ return navigator.gpu;
9
+ }
10
+ async function gpuAdapter() {
11
+ const gpu = navigatorGpu();
12
+ if (gpu === undefined) {
13
+ return undefined;
14
+ }
15
+ let adapter;
16
+ try {
17
+ adapter = await gpu.requestAdapter({ powerPreference: "high-performance" });
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ if (adapter === null || adapter.info.isFallbackAdapter) {
23
+ return undefined;
24
+ }
25
+ return adapter;
26
+ }
27
+ function isSoftwareAdapter(adapter) {
28
+ // Browsers fill these in unevenly, so every field the adapter does report is
29
+ // searched for a known software rasterizer.
30
+ const { vendor, architecture, device, description } = adapter.info;
31
+ const reported = [vendor, architecture, device, description].join(" ").toLowerCase();
32
+ return SOFTWARE_ADAPTERS.some((name) => reported.includes(name));
33
+ }
34
+ async function hardwareAdapter() {
35
+ const adapter = await gpuAdapter();
36
+ if (adapter === undefined || isSoftwareAdapter(adapter)) {
37
+ return undefined;
38
+ }
39
+ return adapter;
40
+ }
41
+ /**
42
+ * Acquires the devices a backend runs on.
43
+ */
44
+ export var device;
45
+ (function (device) {
46
+ /**
47
+ * Acquires a WebGPU device, or `undefined` when this browser has no GPU to
48
+ * run on or an adapter that would rasterize on the CPU.
49
+ */
50
+ async function gpu() {
51
+ const adapter = await hardwareAdapter();
52
+ if (adapter === undefined) {
53
+ return undefined;
54
+ }
55
+ try {
56
+ // Every feature the adapter supports is requested to avoid putting
57
+ // the renderer in compatibility mode.
58
+ return await adapter.requestDevice({ requiredFeatures: [...adapter.features] });
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ device.gpu = gpu;
65
+ })(device || (device = {}));
66
+ //# sourceMappingURL=device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,0FAA0F;AAC1F,MAAM,iBAAiB,GAAG,CAAC,qBAAqB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AAE7G,SAAS,YAAY;IACnB,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,SAAS,CAAC,GAAG,CAAC;AACvB,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAmB;IAC5C,6EAA6E;IAC7E,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACnE,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACrF,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAC;IAEnC,IAAI,OAAO,KAAK,SAAS,IAAI,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,KAAW,MAAM,CAmBtB;AAnBD,WAAiB,MAAM;IACrB;;;OAGG;IACI,KAAK,UAAU,GAAG;QACvB,MAAM,OAAO,GAAG,MAAM,eAAe,EAAE,CAAC;QACxC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,CAAC;YACH,mEAAmE;YACnE,sCAAsC;YACtC,OAAO,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,gBAAgB,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAqB,EAAE,CAAC,CAAC;QACtG,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAbqB,UAAG,MAaxB,CAAA;AACH,CAAC,EAnBgB,MAAM,KAAN,MAAM,QAmBtB","sourcesContent":["import type { Device } from \"@/types.js\";\n\n// Adapter names that indicate that the browser is rasterizing WebGPU on the CPU.\n// These devices are slower than the CPU backend, so we assume it's the same as no device.\nconst SOFTWARE_ADAPTERS = [\"basic render driver\", \"lavapipe\", \"llvmpipe\", \"software\", \"swiftshader\", \"warp\"];\n\nfunction navigatorGpu(): GPU | undefined {\n if (typeof navigator === \"undefined\") {\n return undefined;\n }\n return navigator.gpu;\n}\n\nasync function gpuAdapter(): Promise<GPUAdapter | undefined> {\n const gpu = navigatorGpu();\n if (gpu === undefined) {\n return undefined;\n }\n\n let adapter: GPUAdapter | null;\n try {\n adapter = await gpu.requestAdapter({ powerPreference: \"high-performance\" });\n } catch {\n return undefined;\n }\n\n if (adapter === null || adapter.info.isFallbackAdapter) {\n return undefined;\n }\n\n return adapter;\n}\n\nfunction isSoftwareAdapter(adapter: GPUAdapter): boolean {\n // Browsers fill these in unevenly, so every field the adapter does report is\n // searched for a known software rasterizer.\n const { vendor, architecture, device, description } = adapter.info;\n const reported = [vendor, architecture, device, description].join(\" \").toLowerCase();\n return SOFTWARE_ADAPTERS.some((name) => reported.includes(name));\n}\n\nasync function hardwareAdapter(): Promise<GPUAdapter | undefined> {\n const adapter = await gpuAdapter();\n\n if (adapter === undefined || isSoftwareAdapter(adapter)) {\n return undefined;\n }\n\n return adapter;\n}\n\n/**\n * Acquires the devices a backend runs on.\n */\nexport namespace device {\n /**\n * Acquires a WebGPU device, or `undefined` when this browser has no GPU to\n * run on or an adapter that would rasterize on the CPU.\n */\n export async function gpu(): Promise<Device | undefined> {\n const adapter = await hardwareAdapter();\n if (adapter === undefined) {\n return undefined;\n }\n\n try {\n // Every feature the adapter supports is requested to avoid putting\n // the renderer in compatibility mode.\n return await adapter.requestDevice({ requiredFeatures: [...adapter.features] as GPUFeatureName[] });\n } catch {\n return undefined;\n }\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { backend } from "./backend/index.js";
2
+ import { device } from "./device.js";
3
+ import { initializer } from "./initializer/index.js";
4
+ import { integrator } from "./integrator/index.js";
5
+ import { model } from "./model/index.js";
6
+ import { swarm } from "./swarm.js";
7
+ export type { AdvanceOptions, Backend, CompileOptions, Connection, CpuIntegrator, CpuIntegratorOptions, CpuModel, Device, ElasticNetworkOptions, Executable, FixedInitializerOptions, GpuBackendOptions, GpuIntegrator, GpuIntegratorOptions, GpuModel, GpuModelOptions, GpuParticle, GpuParticles, Initializer, Integrator, Model, ModelValidationContext, Particles, Swarm, SwarmOptions, SwarmState, Vector, } from "./types.js";
8
+ /**
9
+ * The library's public namespace.
10
+ */
11
+ export declare const gloop: {
12
+ readonly swarm: typeof swarm;
13
+ readonly initializer: typeof initializer;
14
+ readonly model: typeof model;
15
+ readonly integrator: typeof integrator;
16
+ readonly backend: typeof backend;
17
+ readonly device: typeof device;
18
+ };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { backend } from "./backend/index.js";
2
+ import { device } from "./device.js";
3
+ import { initializer } from "./initializer/index.js";
4
+ import { integrator } from "./integrator/index.js";
5
+ import { model } from "./model/index.js";
6
+ import { swarm } from "./swarm.js";
7
+ /**
8
+ * The library's public namespace.
9
+ */
10
+ export const gloop = {
11
+ swarm,
12
+ initializer,
13
+ model,
14
+ integrator,
15
+ backend,
16
+ device,
17
+ };
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAgCnC;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK;IACL,WAAW;IACX,KAAK;IACL,UAAU;IACV,OAAO;IACP,MAAM;CACE,CAAC","sourcesContent":["import { backend } from \"@/backend/index.js\";\nimport { device } from \"@/device.js\";\nimport { initializer } from \"@/initializer/index.js\";\nimport { integrator } from \"@/integrator/index.js\";\nimport { model } from \"@/model/index.js\";\nimport { swarm } from \"@/swarm.js\";\n\nexport type {\n AdvanceOptions,\n Backend,\n CompileOptions,\n Connection,\n CpuIntegrator,\n CpuIntegratorOptions,\n CpuModel,\n Device,\n ElasticNetworkOptions,\n Executable,\n FixedInitializerOptions,\n GpuBackendOptions,\n GpuIntegrator,\n GpuIntegratorOptions,\n GpuModel,\n GpuModelOptions,\n GpuParticle,\n GpuParticles,\n Initializer,\n Integrator,\n Model,\n ModelValidationContext,\n Particles,\n Swarm,\n SwarmOptions,\n SwarmState,\n Vector,\n} from \"@/types.js\";\n\n/**\n * The library's public namespace.\n */\nexport const gloop = {\n swarm,\n initializer,\n model,\n integrator,\n backend,\n device,\n} as const;\n"]}
@@ -0,0 +1,2 @@
1
+ import type { FixedInitializerOptions, Initializer } from "../types.js";
2
+ export declare function fixed(options: FixedInitializerOptions): Initializer;
@@ -0,0 +1,12 @@
1
+ export function fixed(options) {
2
+ return {
3
+ initialize(particleCount) {
4
+ return {
5
+ count: particleCount,
6
+ positions: options.positions.slice(0, particleCount),
7
+ velocities: options.velocities.slice(0, particleCount),
8
+ };
9
+ },
10
+ };
11
+ }
12
+ //# sourceMappingURL=fixed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixed.js","sourceRoot":"","sources":["../../src/initializer/fixed.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,KAAK,CAAC,OAAgC;IACpD,OAAO;QACL,UAAU,CAAC,aAAqB;YAC9B,OAAO;gBACL,KAAK,EAAE,aAAa;gBACpB,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;gBACpD,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;aACvD,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { FixedInitializerOptions, Initializer, Particles } from \"@/types.js\";\n\nexport function fixed(options: FixedInitializerOptions): Initializer {\n return {\n initialize(particleCount: number): Particles {\n return {\n count: particleCount,\n positions: options.positions.slice(0, particleCount),\n velocities: options.velocities.slice(0, particleCount),\n };\n },\n };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { FixedInitializerOptions, Initializer } from "../types.js";
2
+ /**
3
+ * Supplies the starting positions and velocities of a swarm's particles.
4
+ */
5
+ export declare namespace initializer {
6
+ /**
7
+ * Uses caller-supplied positions and velocities to initialize the swarm.
8
+ */
9
+ function fixed(options: FixedInitializerOptions): Initializer;
10
+ /**
11
+ * Scatters particles at rest through the unit cube centered on the origin.
12
+ */
13
+ function random(): Initializer;
14
+ }
@@ -0,0 +1,23 @@
1
+ import { fixed as createFixed } from "../initializer/fixed.js";
2
+ import { random as createRandom } from "../initializer/random.js";
3
+ /**
4
+ * Supplies the starting positions and velocities of a swarm's particles.
5
+ */
6
+ export var initializer;
7
+ (function (initializer) {
8
+ /**
9
+ * Uses caller-supplied positions and velocities to initialize the swarm.
10
+ */
11
+ function fixed(options) {
12
+ return createFixed(options);
13
+ }
14
+ initializer.fixed = fixed;
15
+ /**
16
+ * Scatters particles at rest through the unit cube centered on the origin.
17
+ */
18
+ function random() {
19
+ return createRandom();
20
+ }
21
+ initializer.random = random;
22
+ })(initializer || (initializer = {}));
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/initializer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGjE;;GAEG;AACH,MAAM,KAAW,WAAW,CAc3B;AAdD,WAAiB,WAAW;IAC1B;;OAEG;IACH,SAAgB,KAAK,CAAC,OAAgC;QACpD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAFe,iBAAK,QAEpB,CAAA;IAED;;OAEG;IACH,SAAgB,MAAM;QACpB,OAAO,YAAY,EAAE,CAAC;IACxB,CAAC;IAFe,kBAAM,SAErB,CAAA;AACH,CAAC,EAdgB,WAAW,KAAX,WAAW,QAc3B","sourcesContent":["import { fixed as createFixed } from \"@/initializer/fixed.js\";\nimport { random as createRandom } from \"@/initializer/random.js\";\nimport type { FixedInitializerOptions, Initializer } from \"@/types.js\";\n\n/**\n * Supplies the starting positions and velocities of a swarm's particles.\n */\nexport namespace initializer {\n /**\n * Uses caller-supplied positions and velocities to initialize the swarm.\n */\n export function fixed(options: FixedInitializerOptions): Initializer {\n return createFixed(options);\n }\n\n /**\n * Scatters particles at rest through the unit cube centered on the origin.\n */\n export function random(): Initializer {\n return createRandom();\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Initializer } from "../types.js";
2
+ export declare function random(): Initializer;
@@ -0,0 +1,16 @@
1
+ const ZERO = [0, 0, 0];
2
+ function randomPosition() {
3
+ return [Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5];
4
+ }
5
+ export function random() {
6
+ return {
7
+ initialize(particleCount) {
8
+ return {
9
+ count: particleCount,
10
+ positions: Array.from({ length: particleCount }, randomPosition),
11
+ velocities: Array.from({ length: particleCount }, () => ZERO),
12
+ };
13
+ },
14
+ };
15
+ }
16
+ //# sourceMappingURL=random.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random.js","sourceRoot":"","sources":["../../src/initializer/random.ts"],"names":[],"mappings":"AAEA,MAAM,IAAI,GAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/B,SAAS,cAAc;IACrB,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,MAAM;IACpB,OAAO;QACL,UAAU,CAAC,aAAqB;YAC9B,OAAO;gBACL,KAAK,EAAE,aAAa;gBACpB,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,cAAc,CAAC;gBAChE,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC;aAC9D,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { Initializer, Particles, Vector } from \"@/types.js\";\n\nconst ZERO: Vector = [0, 0, 0];\n\nfunction randomPosition(): Vector {\n return [Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5];\n}\n\nexport function random(): Initializer {\n return {\n initialize(particleCount: number): Particles {\n return {\n count: particleCount,\n positions: Array.from({ length: particleCount }, randomPosition),\n velocities: Array.from({ length: particleCount }, () => ZERO),\n };\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Integrator } from "../types.js";
2
+ export declare function explicitEuler(): Integrator;