@woosh/meep-engine 3.68.0 → 3.69.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 (190) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts +2 -17
  3. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts.map +1 -1
  4. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +3 -18
  5. package/package.json +1 -1
  6. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +33 -65
  7. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  8. package/src/engine/graphics/ecs/trail3d/Trail3D.js +37 -245
  9. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.d.ts +0 -4
  10. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +4 -10
  11. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.d.ts +13 -10
  12. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.d.ts.map +1 -1
  13. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +28 -37
  14. package/src/engine/graphics/ecs/trail3d/trail_knot_count.d.ts +10 -0
  15. package/src/engine/graphics/ecs/trail3d/trail_knot_count.d.ts.map +1 -0
  16. package/src/engine/graphics/ecs/trail3d/trail_knot_count.js +27 -0
  17. package/src/engine/graphics/trail/TrailKnots.d.ts +62 -0
  18. package/src/engine/graphics/trail/TrailKnots.d.ts.map +1 -0
  19. package/src/engine/graphics/trail/TrailKnots.js +137 -0
  20. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.d.ts +1 -1
  21. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +1 -1
  22. package/src/engine/graphics3/EntityRowHold.d.ts +48 -0
  23. package/src/engine/graphics3/EntityRowHold.d.ts.map +1 -0
  24. package/src/engine/graphics3/EntityRowHold.js +128 -0
  25. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +6 -2
  26. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  27. package/src/engine/graphics3/GPUParticleEmitterSystem.js +11 -44
  28. package/src/engine/graphics3/Trail3DSystem.d.ts +69 -35
  29. package/src/engine/graphics3/Trail3DSystem.d.ts.map +1 -1
  30. package/src/engine/graphics3/Trail3DSystem.js +380 -81
  31. package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts +17 -0
  32. package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts.map +1 -1
  33. package/src/engine/physics/fluid/ecs/FluidObstacle.js +22 -2
  34. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +6 -5
  35. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -1
  36. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +14 -3
  37. package/src/engine/physics/fluid/obstacle/FLUID_OBSTACLE_RECORD_STRUCT.d.ts +9 -1
  38. package/src/engine/physics/fluid/obstacle/FLUID_OBSTACLE_RECORD_STRUCT.d.ts.map +1 -1
  39. package/src/engine/physics/fluid/obstacle/FLUID_OBSTACLE_RECORD_STRUCT.js +11 -1
  40. package/src/engine/physics/fluid/obstacle/fluid_obstacle_record_create.d.ts +5 -0
  41. package/src/engine/physics/fluid/obstacle/fluid_obstacle_record_create.d.ts.map +1 -1
  42. package/src/engine/physics/fluid/obstacle/fluid_obstacle_record_create.js +2 -0
  43. package/src/engine/physics/fluid/obstacle/fluid_obstacle_write_gpu_record.d.ts +4 -3
  44. package/src/engine/physics/fluid/obstacle/fluid_obstacle_write_gpu_record.d.ts.map +1 -1
  45. package/src/engine/physics/fluid/obstacle/fluid_obstacle_write_gpu_record.js +11 -3
  46. package/src/shade/playground/trails/index.html +50 -0
  47. package/src/shade/playground/trails/main.d.ts +2 -0
  48. package/src/shade/playground/trails/main.d.ts.map +1 -0
  49. package/src/shade/playground/trails/main.js +258 -0
  50. package/src/shade/renderer/animation/animation_curve_write_keys.d.ts +18 -0
  51. package/src/shade/renderer/animation/animation_curve_write_keys.d.ts.map +1 -0
  52. package/src/shade/renderer/animation/animation_curve_write_keys.js +34 -0
  53. package/src/shade/renderer/deferred/tonemap/ColorGrade.d.ts.map +1 -1
  54. package/src/shade/renderer/deferred/tonemap/ColorGrade.js +13 -36
  55. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +2 -2
  56. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.d.ts +2 -13
  57. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.d.ts.map +1 -1
  58. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.js +6 -21
  59. package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.d.ts +17 -6
  60. package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.d.ts.map +1 -1
  61. package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.js +38 -178
  62. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.d.ts +7 -9
  63. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.d.ts.map +1 -1
  64. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.js +17 -106
  65. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.d.ts +7 -17
  66. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.d.ts.map +1 -1
  67. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.js +18 -118
  68. package/src/shade/renderer/fluid/forcing/FLUID_FORCING.d.ts +14 -0
  69. package/src/shade/renderer/fluid/forcing/FLUID_FORCING.d.ts.map +1 -1
  70. package/src/shade/renderer/fluid/forcing/FLUID_FORCING.js +18 -0
  71. package/src/shade/renderer/fluid/forcing/FLUID_FORCING_APPLY_STRUCT.d.ts +3 -1
  72. package/src/shade/renderer/fluid/forcing/FLUID_FORCING_APPLY_STRUCT.d.ts.map +1 -1
  73. package/src/shade/renderer/fluid/forcing/FLUID_FORCING_APPLY_STRUCT.js +4 -1
  74. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +9 -0
  75. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts.map +1 -1
  76. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.js +20 -4
  77. package/src/shade/renderer/fluid/forcing/chunk_fluid_forcing_cell.d.ts +11 -2
  78. package/src/shade/renderer/fluid/forcing/chunk_fluid_forcing_cell.d.ts.map +1 -1
  79. package/src/shade/renderer/fluid/forcing/chunk_fluid_forcing_cell.js +59 -2
  80. package/src/shade/renderer/fluid/forcing/shader_fluid_forcing_apply.d.ts +4 -1
  81. package/src/shade/renderer/fluid/forcing/shader_fluid_forcing_apply.d.ts.map +1 -1
  82. package/src/shade/renderer/fluid/forcing/shader_fluid_forcing_apply.js +367 -341
  83. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.d.ts +6 -2
  84. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.d.ts.map +1 -1
  85. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.js +339 -282
  86. package/src/shade/renderer/grass/press/shader_grass_press_mark.d.ts +3 -2
  87. package/src/shade/renderer/grass/press/shader_grass_press_mark.d.ts.map +1 -1
  88. package/src/shade/renderer/grass/press/shader_grass_press_mark.js +5 -3
  89. package/src/shade/renderer/rasterize/native/avboit/avboit_unlit_draw.d.ts +49 -0
  90. package/src/shade/renderer/rasterize/native/avboit/avboit_unlit_draw.d.ts.map +1 -0
  91. package/src/shade/renderer/rasterize/native/avboit/avboit_unlit_draw.js +144 -0
  92. package/src/shade/renderer/rasterize/native/avboit/avboit_unlit_splat.d.ts +71 -0
  93. package/src/shade/renderer/rasterize/native/avboit/avboit_unlit_splat.d.ts.map +1 -0
  94. package/src/shade/renderer/rasterize/native/avboit/avboit_unlit_splat.js +152 -0
  95. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unlit_varyings.d.ts +21 -0
  96. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unlit_varyings.d.ts.map +1 -0
  97. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unlit_varyings.js +30 -0
  98. package/src/shade/renderer/rasterize/native/avboit/graph_avboit_unlit.d.ts +101 -0
  99. package/src/shade/renderer/rasterize/native/avboit/graph_avboit_unlit.d.ts.map +1 -0
  100. package/src/shade/renderer/rasterize/native/avboit/graph_avboit_unlit.js +306 -0
  101. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts +9 -0
  102. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts.map +1 -1
  103. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.js +11 -4
  104. package/src/shade/renderer/trails/GPUTrailAVBOITChannel.d.ts +69 -0
  105. package/src/shade/renderer/trails/GPUTrailAVBOITChannel.d.ts.map +1 -0
  106. package/src/shade/renderer/trails/GPUTrailAVBOITChannel.js +129 -0
  107. package/src/shade/renderer/trails/GPUTrailSystem.d.ts +164 -0
  108. package/src/shade/renderer/trails/GPUTrailSystem.d.ts.map +1 -0
  109. package/src/shade/renderer/trails/GPUTrailSystem.js +725 -0
  110. package/src/shade/renderer/trails/data/TRAIL_DATABASE_SPEC.d.ts +14 -0
  111. package/src/shade/renderer/trails/data/TRAIL_DATABASE_SPEC.d.ts.map +1 -0
  112. package/src/shade/renderer/trails/data/TRAIL_DATABASE_SPEC.js +38 -0
  113. package/src/shade/renderer/trails/data/TRAIL_KNOT.d.ts +66 -0
  114. package/src/shade/renderer/trails/data/TRAIL_KNOT.d.ts.map +1 -0
  115. package/src/shade/renderer/trails/data/TRAIL_KNOT.js +154 -0
  116. package/src/shade/renderer/trails/data/TRAIL_RECORD.d.ts +19 -0
  117. package/src/shade/renderer/trails/data/TRAIL_RECORD.d.ts.map +1 -0
  118. package/src/shade/renderer/trails/data/TRAIL_RECORD.js +68 -0
  119. package/src/shade/renderer/trails/data/TRAIL_STATE.d.ts +18 -0
  120. package/src/shade/renderer/trails/data/TRAIL_STATE.d.ts.map +1 -0
  121. package/src/shade/renderer/trails/data/TRAIL_STATE.js +43 -0
  122. package/src/shade/renderer/trails/graph_trails.d.ts +58 -0
  123. package/src/shade/renderer/trails/graph_trails.d.ts.map +1 -0
  124. package/src/shade/renderer/trails/graph_trails.js +155 -0
  125. package/src/shade/renderer/trails/graph_trails_avboit.d.ts +126 -0
  126. package/src/shade/renderer/trails/graph_trails_avboit.d.ts.map +1 -0
  127. package/src/shade/renderer/trails/graph_trails_avboit.js +300 -0
  128. package/src/shade/renderer/trails/runtime/TrailFadeCurves.d.ts +56 -0
  129. package/src/shade/renderer/trails/runtime/TrailFadeCurves.d.ts.map +1 -0
  130. package/src/shade/renderer/trails/runtime/TrailFadeCurves.js +220 -0
  131. package/src/shade/renderer/trails/runtime/TrailKnotPool.d.ts +49 -0
  132. package/src/shade/renderer/trails/runtime/TrailKnotPool.d.ts.map +1 -0
  133. package/src/shade/renderer/trails/runtime/TrailKnotPool.js +162 -0
  134. package/src/shade/renderer/trails/runtime/TrailParameters.d.ts +52 -0
  135. package/src/shade/renderer/trails/runtime/TrailParameters.d.ts.map +1 -0
  136. package/src/shade/renderer/trails/runtime/TrailParameters.js +126 -0
  137. package/src/shade/renderer/trails/shaders/chunk_trail_frame_transport.d.ts +23 -0
  138. package/src/shade/renderer/trails/shaders/chunk_trail_frame_transport.d.ts.map +1 -0
  139. package/src/shade/renderer/trails/shaders/chunk_trail_frame_transport.js +57 -0
  140. package/src/shade/renderer/trails/shaders/chunk_trail_knot_alpha.d.ts +14 -0
  141. package/src/shade/renderer/trails/shaders/chunk_trail_knot_alpha.d.ts.map +1 -0
  142. package/src/shade/renderer/trails/shaders/chunk_trail_knot_alpha.js +40 -0
  143. package/src/shade/renderer/trails/shaders/chunk_trail_seed.d.ts +18 -0
  144. package/src/shade/renderer/trails/shaders/chunk_trail_seed.d.ts.map +1 -0
  145. package/src/shade/renderer/trails/shaders/chunk_trail_seed.js +64 -0
  146. package/src/shade/renderer/trails/shaders/chunk_trail_segment.d.ts +14 -0
  147. package/src/shade/renderer/trails/shaders/chunk_trail_segment.d.ts.map +1 -0
  148. package/src/shade/renderer/trails/shaders/chunk_trail_segment.js +51 -0
  149. package/src/shade/renderer/trails/shaders/chunk_trail_update.d.ts +48 -0
  150. package/src/shade/renderer/trails/shaders/chunk_trail_update.d.ts.map +1 -0
  151. package/src/shade/renderer/trails/shaders/chunk_trail_update.js +197 -0
  152. package/src/shade/renderer/trails/shaders/chunk_trail_vertex.d.ts +34 -0
  153. package/src/shade/renderer/trails/shaders/chunk_trail_vertex.d.ts.map +1 -0
  154. package/src/shade/renderer/trails/shaders/chunk_trail_vertex.js +111 -0
  155. package/src/shade/renderer/trails/shaders/shader_trail_avboit.d.ts +24 -0
  156. package/src/shade/renderer/trails/shaders/shader_trail_avboit.d.ts.map +1 -0
  157. package/src/shade/renderer/trails/shaders/shader_trail_avboit.js +78 -0
  158. package/src/shade/renderer/trails/shaders/shader_trail_avboit_segments.d.ts +23 -0
  159. package/src/shade/renderer/trails/shaders/shader_trail_avboit_segments.d.ts.map +1 -0
  160. package/src/shade/renderer/trails/shaders/shader_trail_avboit_segments.js +130 -0
  161. package/src/shade/renderer/trails/shaders/shader_trail_draw_args.d.ts +29 -0
  162. package/src/shade/renderer/trails/shaders/shader_trail_draw_args.d.ts.map +1 -0
  163. package/src/shade/renderer/trails/shaders/shader_trail_draw_args.js +78 -0
  164. package/src/shade/renderer/trails/shaders/shader_trail_seed.d.ts +18 -0
  165. package/src/shade/renderer/trails/shaders/shader_trail_seed.d.ts.map +1 -0
  166. package/src/shade/renderer/trails/shaders/shader_trail_seed.js +79 -0
  167. package/src/shade/renderer/trails/shaders/shader_trail_update.d.ts +20 -0
  168. package/src/shade/renderer/trails/shaders/shader_trail_update.d.ts.map +1 -0
  169. package/src/shade/renderer/trails/shaders/shader_trail_update.js +101 -0
  170. package/src/engine/graphics/ecs/trail3d/advance_trail.d.ts +0 -18
  171. package/src/engine/graphics/ecs/trail3d/advance_trail.d.ts.map +0 -1
  172. package/src/engine/graphics/ecs/trail3d/advance_trail.js +0 -42
  173. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.d.ts +0 -14
  174. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.d.ts.map +0 -1
  175. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +0 -75
  176. package/src/engine/graphics/ecs/trail3d/trail_head_position.d.ts +0 -17
  177. package/src/engine/graphics/ecs/trail3d/trail_head_position.d.ts.map +0 -1
  178. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +0 -19
  179. package/src/engine/graphics/trail/tube/TubeX.d.ts +0 -186
  180. package/src/engine/graphics/trail/tube/TubeX.d.ts.map +0 -1
  181. package/src/engine/graphics/trail/tube/TubeX.js +0 -575
  182. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts +0 -16
  183. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts.map +0 -1
  184. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +0 -34
  185. package/src/engine/graphics/trail/tube/tube_attributes_spec.d.ts +0 -27
  186. package/src/engine/graphics/trail/tube/tube_attributes_spec.d.ts.map +0 -1
  187. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +0 -70
  188. package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts +0 -38
  189. package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts.map +0 -1
  190. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +0 -119
@@ -0,0 +1,62 @@
1
+ /**
2
+ * A trail laid down whole by its author instead of by an entity's travel: a stroke, a helix. Each knot
3
+ * has a position, the frame its ring stands in, a thickness and the age it starts with. The knots never
4
+ * move; they only age, and fade by the trail's curve as they do.
5
+ *
6
+ * Plain data on the component: whatever draws the trail uploads it when the trail is linked, and again
7
+ * when {@link version} moves. Ages are seconds, as `Trail3D.maxAge` is.
8
+ */
9
+ export class TrailKnots {
10
+ /**
11
+ * Moved by every write, so a consumer holding an upload can tell it is stale.
12
+ * @type {number}
13
+ */
14
+ version: number;
15
+ /**
16
+ * @returns {number} knots
17
+ */
18
+ get count(): number;
19
+ /**
20
+ * Resize to `count` knots. Knots kept keep their values; new ones are zero.
21
+ *
22
+ * @param {number} count at least 2, the two ends of one segment
23
+ */
24
+ setCount(count: number): void;
25
+ /**
26
+ * @param {number} index
27
+ * @param {number} x
28
+ * @param {number} y
29
+ * @param {number} z
30
+ * @param {number} nx ring frame's first axis, unit
31
+ * @param {number} ny
32
+ * @param {number} nz
33
+ * @param {number} bx second axis, unit, orthogonal to the first and to the tube's direction
34
+ * @param {number} by
35
+ * @param {number} bz
36
+ * @param {number} thickness tube diameter
37
+ * @param {number} age seconds the knot is born having lived
38
+ */
39
+ setKnot(index: number, x: number, y: number, z: number, nx: number, ny: number, nz: number, bx: number, by: number, bz: number, thickness: number, age: number): void;
40
+ /**
41
+ * @param {number[]|Float32Array|Float64Array} out
42
+ * @param {number} index
43
+ */
44
+ getPosition(out: number[] | Float32Array | Float64Array, index: number): void;
45
+ /**
46
+ * @param {number[]|Float32Array|Float64Array} out `[nx, ny, nz, bx, by, bz]`
47
+ * @param {number} index
48
+ */
49
+ getFrame(out: number[] | Float32Array | Float64Array, index: number): void;
50
+ /**
51
+ * @param {number} index
52
+ * @returns {number}
53
+ */
54
+ getThickness(index: number): number;
55
+ /**
56
+ * @param {number} index
57
+ * @returns {number} seconds
58
+ */
59
+ getAge(index: number): number;
60
+ #private;
61
+ }
62
+ //# sourceMappingURL=TrailKnots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrailKnots.d.ts","sourceRoot":"","sources":["../../../../../src/engine/graphics/trail/TrailKnots.js"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH;IAWI;;;OAGG;IACH,SAFU,MAAM,CAEJ;IAEZ;;OAEG;IACH,oBAEC;IAED;;;;OAIG;IACH,gBAFW,MAAM,QAgBhB;IAED;;;;;;;;;;;;;OAaG;IACH,eAbW,MAAM,KACN,MAAM,KACN,MAAM,KACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,aACN,MAAM,OACN,MAAM,QAsBhB;IAED;;;OAGG;IACH,iBAHW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,SAClC,MAAM,QAQhB;IAED;;;OAGG;IACH,cAHW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,SAClC,MAAM,QAQhB;IAED;;;OAGG;IACH,oBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,cAHW,MAAM,GACJ,MAAM,CAIlB;;CACJ"}
@@ -0,0 +1,137 @@
1
+ import { assert } from "../../../core/assert.js";
2
+
3
+ /**
4
+ * Numbers per knot: position (3), normal (3), binormal (3), thickness, age.
5
+ * @type {number}
6
+ */
7
+ const KNOT_STRIDE = 11;
8
+
9
+ /**
10
+ * A trail laid down whole by its author instead of by an entity's travel: a stroke, a helix. Each knot
11
+ * has a position, the frame its ring stands in, a thickness and the age it starts with. The knots never
12
+ * move; they only age, and fade by the trail's curve as they do.
13
+ *
14
+ * Plain data on the component: whatever draws the trail uploads it when the trail is linked, and again
15
+ * when {@link version} moves. Ages are seconds, as `Trail3D.maxAge` is.
16
+ */
17
+ export class TrailKnots {
18
+ /**
19
+ * @type {Float64Array}
20
+ */
21
+ #data = new Float64Array(0);
22
+
23
+ /**
24
+ * @type {number}
25
+ */
26
+ #count = 0;
27
+
28
+ /**
29
+ * Moved by every write, so a consumer holding an upload can tell it is stale.
30
+ * @type {number}
31
+ */
32
+ version = 0;
33
+
34
+ /**
35
+ * @returns {number} knots
36
+ */
37
+ get count() {
38
+ return this.#count;
39
+ }
40
+
41
+ /**
42
+ * Resize to `count` knots. Knots kept keep their values; new ones are zero.
43
+ *
44
+ * @param {number} count at least 2, the two ends of one segment
45
+ */
46
+ setCount(count) {
47
+ assert.isNonNegativeInteger(count, 'count');
48
+ assert.greaterThanOrEqual(count, 2, 'a trail needs at least two knots');
49
+
50
+ if (this.#data.length < count * KNOT_STRIDE) {
51
+ const data = new Float64Array(count * KNOT_STRIDE);
52
+
53
+ data.set(this.#data.subarray(0, this.#count * KNOT_STRIDE));
54
+
55
+ this.#data = data;
56
+ }
57
+
58
+ this.#count = count;
59
+ this.version++;
60
+ }
61
+
62
+ /**
63
+ * @param {number} index
64
+ * @param {number} x
65
+ * @param {number} y
66
+ * @param {number} z
67
+ * @param {number} nx ring frame's first axis, unit
68
+ * @param {number} ny
69
+ * @param {number} nz
70
+ * @param {number} bx second axis, unit, orthogonal to the first and to the tube's direction
71
+ * @param {number} by
72
+ * @param {number} bz
73
+ * @param {number} thickness tube diameter
74
+ * @param {number} age seconds the knot is born having lived
75
+ */
76
+ setKnot(index, x, y, z, nx, ny, nz, bx, by, bz, thickness, age) {
77
+ assert.isNonNegativeInteger(index, 'index');
78
+ assert.lessThan(index, this.#count, 'index');
79
+
80
+ const d = this.#data;
81
+ const at = index * KNOT_STRIDE;
82
+
83
+ d[at] = x;
84
+ d[at + 1] = y;
85
+ d[at + 2] = z;
86
+ d[at + 3] = nx;
87
+ d[at + 4] = ny;
88
+ d[at + 5] = nz;
89
+ d[at + 6] = bx;
90
+ d[at + 7] = by;
91
+ d[at + 8] = bz;
92
+ d[at + 9] = thickness;
93
+ d[at + 10] = age;
94
+
95
+ this.version++;
96
+ }
97
+
98
+ /**
99
+ * @param {number[]|Float32Array|Float64Array} out
100
+ * @param {number} index
101
+ */
102
+ getPosition(out, index) {
103
+ const at = index * KNOT_STRIDE;
104
+
105
+ out[0] = this.#data[at];
106
+ out[1] = this.#data[at + 1];
107
+ out[2] = this.#data[at + 2];
108
+ }
109
+
110
+ /**
111
+ * @param {number[]|Float32Array|Float64Array} out `[nx, ny, nz, bx, by, bz]`
112
+ * @param {number} index
113
+ */
114
+ getFrame(out, index) {
115
+ const at = index * KNOT_STRIDE + 3;
116
+
117
+ for (let i = 0; i < 6; i++) {
118
+ out[i] = this.#data[at + i];
119
+ }
120
+ }
121
+
122
+ /**
123
+ * @param {number} index
124
+ * @returns {number}
125
+ */
126
+ getThickness(index) {
127
+ return this.#data[index * KNOT_STRIDE + 9];
128
+ }
129
+
130
+ /**
131
+ * @param {number} index
132
+ * @returns {number} seconds
133
+ */
134
+ getAge(index) {
135
+ return this.#data[index * KNOT_STRIDE + 10];
136
+ }
137
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Material configuration for a {@link TubeX}. Used as a cache key in
2
+ * Material configuration for a {@link Trail3D}'s tube. Used as a cache key in
3
3
  * {@link TubeXPlugin}, so any field that changes the compiled material (texture,
4
4
  * depth-write mode, lighting) participates in {@link equals}/{@link hash}.
5
5
  */
@@ -2,7 +2,7 @@ import { assert } from "../../../../core/assert.js";
2
2
  import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
3
3
 
4
4
  /**
5
- * Material configuration for a {@link TubeX}. Used as a cache key in
5
+ * Material configuration for a {@link Trail3D}'s tube. Used as a cache key in
6
6
  * {@link TubeXPlugin}, so any field that changes the compiled material (texture,
7
7
  * depth-write mode, lighting) participates in {@link equals}/{@link hash}.
8
8
  */
@@ -0,0 +1,48 @@
1
+ /**
2
+ * One system's hold on an entity's `transforms` row in a scene context, kept current as the entity
3
+ * moves: for a system whose GPU work reads where an entity is — a particle emitter, a trail's head.
4
+ *
5
+ * The listener on {@link TRANSFORM64_EVENT_CHANGE} stands from construction to {@link dispose}, whether
6
+ * or not a row is held, because an entity is linked before there is a scene context to hold a row in: a
7
+ * level loads before the device is up, and a device restart replaces the context under every entity.
8
+ * While a row is held in the context that is current, a move marks it, and the context's next build
9
+ * rewrites it from the transform. A Transform64 has no signals of its own; whoever writes it announces
10
+ * the change, and this wakes on that.
11
+ *
12
+ * {@link acquire} and {@link release} are the hold itself. A hold in a context that has since been
13
+ * replaced went with that context, so releasing it gives nothing back.
14
+ */
15
+ export class EntityRowHold {
16
+ /**
17
+ * @param {import("../ecs/EntityComponentDataset.js").EntityComponentDataset} dataset
18
+ * @param {number} entity
19
+ * @param {function(): (import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null)} current
20
+ * the scene context the entity's scene renders through now
21
+ */
22
+ constructor(dataset: import("../ecs/EntityComponentDataset.js").EntityComponentDataset, entity: number, current: () => (import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext | null));
23
+ /**
24
+ * @returns {number}
25
+ */
26
+ get entity(): number;
27
+ /**
28
+ * @returns {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null} where the row
29
+ * is held, or null when it is not
30
+ */
31
+ get context(): import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext;
32
+ /**
33
+ * Hold the entity's row in `context`, letting go of any hold in another.
34
+ *
35
+ * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} context
36
+ */
37
+ acquire(context: import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext): void;
38
+ /**
39
+ * Let go of the row, if one is held in the context that is current.
40
+ */
41
+ release(): void;
42
+ /**
43
+ * Release, and stop listening.
44
+ */
45
+ dispose(): void;
46
+ #private;
47
+ }
48
+ //# sourceMappingURL=EntityRowHold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityRowHold.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/EntityRowHold.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH;IAuBI;;;;;OAKG;IACH,qBALW,OAAO,kCAAkC,EAAE,sBAAsB,UACjE,MAAM,iBACM,CAAC,OAAO,+CAA+C,EAAE,eAAe,GAAC,IAAI,CAAC,EAapG;IAED;;OAEG;IACH,qBAEC;IAED;;;OAGG;IACH,uFAEC;IAED;;;;OAIG;IACH,iBAFW,OAAO,+CAA+C,EAAE,eAAe,QAcjF;IAED;;OAEG;IACH,gBAYC;IAED;;OAEG;IACH,gBAIC;;CAWJ"}
@@ -0,0 +1,128 @@
1
+ import { assert } from "../../core/assert.js";
2
+ import { TRANSFORM64_EVENT_CHANGE } from "../ecs/transform/TRANSFORM64_EVENT_CHANGE.js";
3
+
4
+ /**
5
+ * One system's hold on an entity's `transforms` row in a scene context, kept current as the entity
6
+ * moves: for a system whose GPU work reads where an entity is — a particle emitter, a trail's head.
7
+ *
8
+ * The listener on {@link TRANSFORM64_EVENT_CHANGE} stands from construction to {@link dispose}, whether
9
+ * or not a row is held, because an entity is linked before there is a scene context to hold a row in: a
10
+ * level loads before the device is up, and a device restart replaces the context under every entity.
11
+ * While a row is held in the context that is current, a move marks it, and the context's next build
12
+ * rewrites it from the transform. A Transform64 has no signals of its own; whoever writes it announces
13
+ * the change, and this wakes on that.
14
+ *
15
+ * {@link acquire} and {@link release} are the hold itself. A hold in a context that has since been
16
+ * replaced went with that context, so releasing it gives nothing back.
17
+ */
18
+ export class EntityRowHold {
19
+ /**
20
+ * @type {import("../ecs/EntityComponentDataset.js").EntityComponentDataset}
21
+ */
22
+ #dataset;
23
+
24
+ /**
25
+ * @type {number}
26
+ */
27
+ #entity;
28
+
29
+ /**
30
+ * Answers which scene context is current.
31
+ * @type {function(): (import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null)}
32
+ */
33
+ #current;
34
+
35
+ /**
36
+ * The context the row is held in, or null.
37
+ * @type {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null}
38
+ */
39
+ #context = null;
40
+
41
+ /**
42
+ * @param {import("../ecs/EntityComponentDataset.js").EntityComponentDataset} dataset
43
+ * @param {number} entity
44
+ * @param {function(): (import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null)} current
45
+ * the scene context the entity's scene renders through now
46
+ */
47
+ constructor(dataset, entity, current) {
48
+ assert.defined(dataset, 'dataset');
49
+ assert.isNonNegativeInteger(entity, 'entity');
50
+ assert.isFunction(current, 'current');
51
+
52
+ this.#dataset = dataset;
53
+ this.#entity = entity;
54
+ this.#current = current;
55
+
56
+ dataset.addEntityEventListener(entity, TRANSFORM64_EVENT_CHANGE, this.#moved, this);
57
+ }
58
+
59
+ /**
60
+ * @returns {number}
61
+ */
62
+ get entity() {
63
+ return this.#entity;
64
+ }
65
+
66
+ /**
67
+ * @returns {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null} where the row
68
+ * is held, or null when it is not
69
+ */
70
+ get context() {
71
+ return this.#context;
72
+ }
73
+
74
+ /**
75
+ * Hold the entity's row in `context`, letting go of any hold in another.
76
+ *
77
+ * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} context
78
+ */
79
+ acquire(context) {
80
+ assert.defined(context, 'context');
81
+
82
+ if (this.#context === context) {
83
+ return;
84
+ }
85
+
86
+ this.release();
87
+
88
+ context.rows.acquire(this.#entity);
89
+
90
+ this.#context = context;
91
+ }
92
+
93
+ /**
94
+ * Let go of the row, if one is held in the context that is current.
95
+ */
96
+ release() {
97
+ const context = this.#context;
98
+
99
+ if (context === null) {
100
+ return;
101
+ }
102
+
103
+ if (context === this.#current()) {
104
+ context.rows.release(this.#entity);
105
+ }
106
+
107
+ this.#context = null;
108
+ }
109
+
110
+ /**
111
+ * Release, and stop listening.
112
+ */
113
+ dispose() {
114
+ this.release();
115
+
116
+ this.#dataset.removeEntityEventListener(this.#entity, TRANSFORM64_EVENT_CHANGE, this.#moved, this);
117
+ }
118
+
119
+ #moved() {
120
+ const context = this.#context;
121
+
122
+ if (context === null || context !== this.#current()) {
123
+ return;
124
+ }
125
+
126
+ context.rows.mark_world(this.#entity);
127
+ }
128
+ }
@@ -32,7 +32,7 @@ export function apply_particle_effect(emitter: ParticleEmitter, effect: Particle
32
32
  * @property {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null} context
33
33
  * the scene context the emitter is registered with, at the entity's row; null until a frame has
34
34
  * registered it, or after a device restart took that context away
35
- * @property {function():void} on_transform_changed
35
+ * @property {EntityRowHold} hold the entity's row, held in {@link context} while the emitter is registered
36
36
  * @property {string|null} url last observed {@link ParticleEffect#texture}
37
37
  * @property {import("../graphics/texture/atlas/ManagedTextureAtlas.js").ManagedAtlasReference|null} texture shared image reference, or null for an untextured effect
38
38
  * @property {Map<string, {entity: number, channel: number, live: boolean, on_removed: function|null, layout: import("../../shade/renderer/particles/layout/ParticleLayout.js").ParticleLayout|null}>} bound
@@ -248,7 +248,10 @@ export type ParticleEmitterEntry = {
248
248
  * registered it, or after a device restart took that context away
249
249
  */
250
250
  context: import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext | null;
251
- on_transform_changed: () => void;
251
+ /**
252
+ * the entity's row, held in {@link context } while the emitter is registered
253
+ */
254
+ hold: EntityRowHold;
252
255
  /**
253
256
  * last observed {@link ParticleEffecttexture }
254
257
  */
@@ -280,4 +283,5 @@ import { ParticleEffect } from "../graphics/ecs/particles/ParticleEffect.js";
280
283
  import { System } from "../ecs/System.js";
281
284
  import { Transform64 } from "../ecs/transform/Transform64.js";
282
285
  import { SpriteAtlas } from "../graphics/texture/atlas/SpriteAtlas.js";
286
+ import { EntityRowHold } from "./EntityRowHold.js";
283
287
  //# sourceMappingURL=GPUParticleEmitterSystem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GPUParticleEmitterSystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/GPUParticleEmitterSystem.js"],"names":[],"mappings":"AA6tCA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,+CAJW,eAAe,UACf,cAAc,GACZ,OAAO,CAyGnB;AA1zCD;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH;IA4FI;;;;;OAKG;IACH,sBALW,OAAO,qBAAqB,EAAE,cAAc,SAC5C,OAAO,qCAAqC,EAAE,KAAK,gEAoB7D;IAjHD,6DAA6C;IAE7C;;;OAGG;IACH,OAFU,WAAW,CAEkC;IA6GvD;;;;;;OAMG;IACH,iEAEC;IAED;;;;;OAKG;IACH,kCAEC;IAED;;;;;;;;;;OAUG;IACH,mBAJW,MAAM,GACJ,eAAe,GAAC,IAAI,CAWhC;IAED;;;;;;;;;;;OAWG;IACH,cAHW,MAAM,SACN,MAAM,QA4BhB;IAED;;;;;;;;;;;;OAYG;IACH,uBAHW,MAAM,UACN,UAAU,MAAM,CAAC,QA8B3B;IAED;;;;;;;;;;;;;OAaG;IACH,wBAJW,MAAM,UACN,MAAM,WACN,MAAM,QAOhB;IAED;;;;;OAKG;IACH,0BAFW,MAAM,QAIhB;IA8PD,+BAA+B;IAC/B,YADc,QAAQ,IAAI,CAAC,CAuB1B;IA0ED;;;;OAIG;IACH,aAJW,cAAc,aACd,WAAW,UACX,MAAM,QAuChB;IAED;;;;OAIG;IACH,eAJW,cAAc,aACd,WAAW,UACX,MAAM,QAIhB;IAED;;;;;;;OAOG;IACH,4BAHW,MAAM,GACJ,IAAI,CAgBhB;IAoCD;;;;;;;;;;;;OAYG;IACH,eAHW,OAAO,gDAAgD,EAAE,YAAY,GACnE,IAAI,CAgDhB;;CAoVJ;;YAvrCa,MAAM;;;;YACN,cAAc;;;;eACd,WAAW;;;;aACX,eAAe,GAAC,IAAI;;;;;aACpB,OAAO,+CAA+C,EAAE,eAAe,GAAC,IAAI;gCAGjE,IAAI;;;;SACf,MAAM,GAAC,IAAI;;;;aACX,OAAO,kDAAkD,EAAE,qBAAqB,GAAC,IAAI;;;;;;;WACrF,IAAI,MAAM,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,WAAS,IAAI,CAAC;QAAC,MAAM,EAAE,OAAO,yDAAyD,EAAE,cAAc,GAAC,IAAI,CAAA;KAAC,CAAC;;;;qBAKvL,OAAO,uDAAuD,EAAE,eAAe,GAAC,IAAI;;gCA/ClE,2DAA2D;+BAU5D,6CAA6C;uBARrD,kBAAkB;4BAEb,iCAAiC;4BAQjC,0CAA0C"}
1
+ {"version":3,"file":"GPUParticleEmitterSystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/GPUParticleEmitterSystem.js"],"names":[],"mappings":"AA4rCA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,+CAJW,eAAe,UACf,cAAc,GACZ,OAAO,CAyGnB;AAzxCD;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH;IA4FI;;;;;OAKG;IACH,sBALW,OAAO,qBAAqB,EAAE,cAAc,SAC5C,OAAO,qCAAqC,EAAE,KAAK,gEAoB7D;IAjHD,6DAA6C;IAE7C;;;OAGG;IACH,OAFU,WAAW,CAEkC;IA6GvD;;;;;;OAMG;IACH,iEAEC;IAED;;;;;OAKG;IACH,kCAEC;IAED;;;;;;;;;;OAUG;IACH,mBAJW,MAAM,GACJ,eAAe,GAAC,IAAI,CAWhC;IAED;;;;;;;;;;;OAWG;IACH,cAHW,MAAM,SACN,MAAM,QA4BhB;IAED;;;;;;;;;;;;OAYG;IACH,uBAHW,MAAM,UACN,UAAU,MAAM,CAAC,QA8B3B;IAED;;;;;;;;;;;;;OAaG;IACH,wBAJW,MAAM,UACN,MAAM,WACN,MAAM,QAOhB;IAED;;;;;OAKG;IACH,0BAFW,MAAM,QAIhB;IA8PD,+BAA+B;IAC/B,YADc,QAAQ,IAAI,CAAC,CAuB1B;IA0ED;;;;OAIG;IACH,aAJW,cAAc,aACd,WAAW,UACX,MAAM,QA6BhB;IAED;;;;OAIG;IACH,eAJW,cAAc,aACd,WAAW,UACX,MAAM,QAIhB;IAED;;;;;;;OAOG;IACH,4BAHW,MAAM,GACJ,IAAI,CAgBhB;IAoCD;;;;;;;;;;;;OAYG;IACH,eAHW,OAAO,gDAAgD,EAAE,YAAY,GACnE,IAAI,CAgDhB;;CA6TJ;;YAtpCa,MAAM;;;;YACN,cAAc;;;;eACd,WAAW;;;;aACX,eAAe,GAAC,IAAI;;;;;aACpB,OAAO,+CAA+C,EAAE,eAAe,GAAC,IAAI;;;;UAG5E,aAAa;;;;SACb,MAAM,GAAC,IAAI;;;;aACX,OAAO,kDAAkD,EAAE,qBAAqB,GAAC,IAAI;;;;;;;WACrF,IAAI,MAAM,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,WAAS,IAAI,CAAC;QAAC,MAAM,EAAE,OAAO,yDAAyD,EAAE,cAAc,GAAC,IAAI,CAAA;KAAC,CAAC;;;;qBAKvL,OAAO,uDAAuD,EAAE,eAAe,GAAC,IAAI;;gCA/ClE,2DAA2D;+BAU5D,6CAA6C;uBARrD,kBAAkB;4BAGb,iCAAiC;4BAOjC,0CAA0C;8BATxC,oBAAoB"}
@@ -5,10 +5,10 @@ import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.
5
5
  import { ParticleEmitter } from "../../shade/renderer/particles/runtime/ParticleEmitter.js";
6
6
  import { GPUTextureContext } from "../../shade/renderer/texture/GPUTextureContext.js";
7
7
  import { System } from "../ecs/System.js";
8
+ import { EntityRowHold } from "./EntityRowHold.js";
8
9
  import Name from "../ecs/name/Name.js";
9
10
  import { Transform64 } from "../ecs/transform/Transform64.js";
10
11
  import { EventType } from "../ecs/EventType.js";
11
- import { TRANSFORM64_EVENT_CHANGE } from "../ecs/transform/TRANSFORM64_EVENT_CHANGE.js";
12
12
  import { row_of_entity } from "../../shade/renderer/scene/rows/GPUSceneRows.js";
13
13
  import { GameAssetType } from "../asset/GameAssetType.js";
14
14
  import { ImageRGBADataLoader } from "../asset/loaders/image/ImageRGBADataLoader.js";
@@ -41,7 +41,7 @@ const BURST_QUEUE_LIMIT = 256;
41
41
  * @property {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null} context
42
42
  * the scene context the emitter is registered with, at the entity's row; null until a frame has
43
43
  * registered it, or after a device restart took that context away
44
- * @property {function():void} on_transform_changed
44
+ * @property {EntityRowHold} hold the entity's row, held in {@link context} while the emitter is registered
45
45
  * @property {string|null} url last observed {@link ParticleEffect#texture}
46
46
  * @property {import("../graphics/texture/atlas/ManagedTextureAtlas.js").ManagedAtlasReference|null} texture shared image reference, or null for an untextured effect
47
47
  * @property {Map<string, {entity: number, channel: number, live: boolean, on_removed: function|null, layout: import("../../shade/renderer/particles/layout/ParticleLayout.js").ParticleLayout|null}>} bound
@@ -744,7 +744,7 @@ export class GPUParticleEmitterSystem extends System {
744
744
  transform,
745
745
  emitter: null,
746
746
  context: null,
747
- on_transform_changed: null,
747
+ hold: null,
748
748
  url: null,
749
749
  texture: null,
750
750
  bound: new Map(),
@@ -753,18 +753,8 @@ export class GPUParticleEmitterSystem extends System {
753
753
 
754
754
  this.#entries.set(entity, entry);
755
755
 
756
- /*
757
- The emitter is placed the moment its transform moves rather than on a poll. A Transform64
758
- has no signals of its own, so the announcement comes from the dataset: whoever wrote the
759
- transform sends TRANSFORM64_EVENT_CHANGE and this wakes on it. Coarser than watching
760
- position, rotation and scale separately — a scale write wakes a placement that reads the
761
- whole transform anyway — and that is the trade the type makes everywhere.
762
- */
763
- entry.on_transform_changed = () => this.#place(entry);
764
-
765
- this.#dataset.addEntityEventListener(
766
- entity, TRANSFORM64_EVENT_CHANGE, entry.on_transform_changed
767
- );
756
+ // The emitter is placed the moment its transform moves rather than on a poll: see EntityRowHold.
757
+ entry.hold = new EntityRowHold(this.#dataset, entity, () => this.#graphics.scene_context(this.#scene));
768
758
 
769
759
  // Everything else — building the emitter, acquiring the texture — is what a frame does
770
760
  // anyway, and doing it here as well would be a second copy of it that has to stay in step.
@@ -916,14 +906,12 @@ export class GPUParticleEmitterSystem extends System {
916
906
  * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} scene_context
917
907
  */
918
908
  #register(particles, scene_context) {
919
- const rows = scene_context.rows;
920
-
921
909
  for (const entry of this.#entries.values()) {
922
910
  if (entry.emitter === null || entry.context === scene_context || entry.emitter.program === entry.refused_program) {
923
911
  continue;
924
912
  }
925
913
 
926
- rows.acquire(entry.entity);
914
+ entry.hold.acquire(scene_context);
927
915
 
928
916
  try {
929
917
  particles.add_emitter(entry.emitter, row_of_entity(entry.entity));
@@ -931,7 +919,7 @@ export class GPUParticleEmitterSystem extends System {
931
919
  // The program's global imports do not link against the scene's global fields. One
932
920
  // effect must not stop every other one: it is reported, and not tried again until
933
921
  // its program changes.
934
- rows.release(entry.entity);
922
+ entry.hold.release();
935
923
  entry.refused_program = entry.emitter.program;
936
924
  warn_limited(`GPUParticleEmitterSystem: an effect was refused: ${e.message}`);
937
925
  continue;
@@ -1073,21 +1061,6 @@ export class GPUParticleEmitterSystem extends System {
1073
1061
  }
1074
1062
  }
1075
1063
 
1076
- /**
1077
- * The entity moved: its row is rewritten from its transform by the context that holds it.
1078
- *
1079
- * @param {ParticleEmitterEntry} entry
1080
- */
1081
- #place(entry) {
1082
- const context = entry.context;
1083
-
1084
- if (context === null || context !== this.#graphics.scene_context(this.#scene)) {
1085
- return;
1086
- }
1087
-
1088
- context.rows.mark_world(entry.entity);
1089
- }
1090
-
1091
1064
  /**
1092
1065
  * Retire an entity's emitter and let go of its row, leaving the entry linked.
1093
1066
  *
@@ -1111,14 +1084,15 @@ export class GPUParticleEmitterSystem extends System {
1111
1084
  particles.remove_emitter(emitter);
1112
1085
  }
1113
1086
 
1114
- context.rows.release(entry.entity);
1115
-
1116
1087
  // the registry dropped the bindings with the emitter; the rows they held go too
1117
1088
  for (const binding of entry.bound.values()) {
1118
1089
  this.#let_go(context, binding);
1119
1090
  }
1120
1091
  }
1121
1092
 
1093
+ // a hold in a context that has been replaced went with it, and releasing it gives nothing back
1094
+ entry.hold.release();
1095
+
1122
1096
  entry.context = null;
1123
1097
  this.#forget_bindings(entry);
1124
1098
 
@@ -1137,15 +1111,8 @@ export class GPUParticleEmitterSystem extends System {
1137
1111
  return;
1138
1112
  }
1139
1113
 
1140
- if (entry.on_transform_changed !== null) {
1141
- this.#dataset.removeEntityEventListener(
1142
- entity, TRANSFORM64_EVENT_CHANGE, entry.on_transform_changed
1143
- );
1144
-
1145
- entry.on_transform_changed = null;
1146
- }
1147
-
1148
1114
  this.#detach_emitter(entry);
1115
+ entry.hold.dispose();
1149
1116
  entry.texture?.release();
1150
1117
 
1151
1118
  entry.url = null;