@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,725 @@
1
+ import { assert } from "../../../core/assert.js";
2
+ import { ALLOCATOR_NO_SPACE } from "../../../core/binary/allocator/OffsetAllocator.js";
3
+ import { GPUDatabase } from "../buffer/table/GPUDatabase.js";
4
+ import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
5
+ import { TRAIL_KNOT_WORDS, TRAIL_TICKS_PER_SECOND, trail_knot_write } from "./data/TRAIL_KNOT.js";
6
+ import { TRAIL_DATABASE_INITIAL_SIZE, TRAIL_DATABASE_SPEC, TRAIL_STATE_TABLE, TRAIL_TABLE } from "./data/TRAIL_DATABASE_SPEC.js";
7
+ import { TRAIL_FLAG } from "./data/TRAIL_RECORD.js";
8
+ import { TRAIL_STATE_UNSEEDED } from "./data/TRAIL_STATE.js";
9
+ import { TrailFadeCurves } from "./runtime/TrailFadeCurves.js";
10
+ import { TrailKnotPool } from "./runtime/TrailKnotPool.js";
11
+ import { TrailParameters } from "./runtime/TrailParameters.js";
12
+ import { graph_trails_simulate } from "./graph_trails.js";
13
+ import { TRAIL_FRAME_STRUCT } from "./shaders/chunk_trail_vertex.js";
14
+
15
+ /**
16
+ * Ticks past which an age is no longer exact: half the u32 range, so `max_age + a frame` stays under the
17
+ * wrap with room for the frame.
18
+ * @type {number}
19
+ */
20
+ const TICK_AGE_LIMIT = 2 ** 31;
21
+
22
+ /**
23
+ * Scratch for an authored knot's frame and position.
24
+ * @type {Float64Array}
25
+ */
26
+ const scratch_frame = new Float64Array(6);
27
+ const scratch_position = new Float64Array(3);
28
+
29
+ /**
30
+ * Everything the runtime holds for one trail. Plain bookkeeping: the trail's look is its
31
+ * {@link TrailParameters}, which the owner keeps.
32
+ */
33
+ export class GPUTrailContext {
34
+ /** @type {number} row in the trail and state tables */
35
+ row = -1;
36
+
37
+ /** @type {number} the `transforms` row the head follows */
38
+ node = 0;
39
+
40
+ /** @type {import("../../../core/binary/allocator/OffsetAllocator.js").Allocation|null} the knot run */
41
+ knots = null;
42
+
43
+ /** @type {import("./runtime/TrailFadeCurves.js").TrailFadeCurve|null} */
44
+ fade = null;
45
+
46
+ /** @type {number} index of the trail's radial class */
47
+ draw_class = -1;
48
+
49
+ /** @type {number} what the record's `seed_epoch` says */
50
+ epoch = 1;
51
+
52
+ /** @type {boolean} the pending seed is a `clear()`, which keeps the trail's birth and frames */
53
+ reseed = false;
54
+
55
+ /** @type {TrailParameters} what was last staged */
56
+ staged = new TrailParameters();
57
+ }
58
+
59
+ /**
60
+ * A radial count in use, and the trails using it: a draw class, one indexed draw per pass.
61
+ */
62
+ export class TrailDrawClass {
63
+ /** @type {number} */
64
+ radial = 0;
65
+
66
+ /** @type {number} trails in the class */
67
+ references = 0;
68
+
69
+ /** @type {number} the most segments the class's trails can show: their knots less one each */
70
+ segments = 0;
71
+ }
72
+
73
+ /**
74
+ * The frame uniform the trails' vertex stage reads: the clock, in ticks.
75
+ * @type {number}
76
+ */
77
+ const FRAME_UNIFORM_BYTES = TRAIL_FRAME_STRUCT.size;
78
+
79
+ /**
80
+ * The GPU trails of one scene context: the trail and state tables, the knot pool, the fade curves, and
81
+ * the clock the knots are aged by.
82
+ *
83
+ * **The CPU registers and stages; the GPU simulates.** {@link add} claims a row, a run of knots and the
84
+ * trail's fade curve and radial class, and asks for a seed; {@link stage} writes the record when the
85
+ * trail's parameters change; {@link remove} lets everything go. No knot is written on the CPU, except
86
+ * the knots of an authored trail, which are uploaded once when it is added. Everything per frame — the
87
+ * head, commits, aging — is the GPU's.
88
+ *
89
+ * **Time is the owner's.** {@link advance} moves the clock by the simulation time the owner was given,
90
+ * so a paused game holds its trails still, and the knots age by ticks of it (`TRAIL_TICKS_PER_SECOND`).
91
+ */
92
+ export class GPUTrailSystem {
93
+ /**
94
+ * @type {import("../GraphicsContext.js").GraphicsContext}
95
+ */
96
+ #graphics;
97
+
98
+ /**
99
+ * @type {GPUDatabase}
100
+ */
101
+ #database;
102
+
103
+ /**
104
+ * @type {TrailKnotPool}
105
+ */
106
+ #knots;
107
+
108
+ /**
109
+ * @type {TrailFadeCurves}
110
+ */
111
+ #fades;
112
+
113
+ /**
114
+ * @type {Map<number, GPUTrailContext>} by row
115
+ */
116
+ #trails = new Map();
117
+
118
+ /**
119
+ * Radial classes by radial count.
120
+ * @type {Map<number, TrailDrawClass>}
121
+ */
122
+ #classes_by_radial = new Map();
123
+
124
+ /**
125
+ * Radial classes by index; a hole is a class no trail uses, free to be handed out again.
126
+ * @type {(TrailDrawClass|undefined)[]}
127
+ */
128
+ #classes = [];
129
+
130
+ /**
131
+ * Trails the GPU is to seed in the next frame recorded.
132
+ * @type {GPUTrailContext[]}
133
+ */
134
+ #seeds = [];
135
+
136
+ /**
137
+ * A static index buffer per radial count: the triangles of one segment, two rings of `radial + 1`
138
+ * vertices.
139
+ * @type {Map<number, GPUBuffer>}
140
+ */
141
+ #index_buffers = new Map();
142
+
143
+ /**
144
+ * The clock as the vertex stage reads it, written once a frame.
145
+ * @type {GPUBuffer}
146
+ */
147
+ #frame_uniform;
148
+
149
+ /**
150
+ * @type {Uint32Array}
151
+ */
152
+ #frame_words = new Uint32Array(FRAME_UNIFORM_BYTES / 4);
153
+
154
+ /**
155
+ * Seconds of simulation time the clock has been advanced by. A double, so the tick count taken from
156
+ * it does not drift however long the game runs.
157
+ * @type {number}
158
+ */
159
+ #time = 0;
160
+
161
+ /**
162
+ * @type {Object}
163
+ */
164
+ #record = {
165
+ node: 0,
166
+ knot_base: 0,
167
+ knot_count: 0,
168
+ radial: 0,
169
+ flags: 0,
170
+ seed_epoch: 0,
171
+ commit_ticks: 0,
172
+ max_age_ticks: 0,
173
+ max_age: 0,
174
+ width: 0,
175
+ spawn_distance: 0,
176
+ fade_first: 0,
177
+ fade_count: 0,
178
+ draw_class: 0,
179
+ offset: new Float32Array(3),
180
+ color: new Float32Array(4),
181
+ };
182
+
183
+ /**
184
+ * @param {import("../GraphicsContext.js").GraphicsContext} graphics
185
+ */
186
+ constructor(graphics) {
187
+ assert.defined(graphics, 'graphics');
188
+
189
+ const device = graphics.device;
190
+
191
+ this.#graphics = graphics;
192
+ this.#database = new GPUDatabase({
193
+ device,
194
+ definition: TRAIL_DATABASE_SPEC,
195
+ label: "Trails",
196
+ initial_data_size_bytes: TRAIL_DATABASE_INITIAL_SIZE,
197
+ });
198
+ this.#knots = new TrailKnotPool(device);
199
+ this.#fades = new TrailFadeCurves(device);
200
+ this.#frame_uniform = device.createBuffer({
201
+ label: "Trails / frame",
202
+ size: FRAME_UNIFORM_BYTES,
203
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
204
+ });
205
+ }
206
+
207
+ /** @returns {import("../GraphicsContext.js").GraphicsContext} */
208
+ get graphics() {
209
+ return this.#graphics;
210
+ }
211
+
212
+ /** @returns {GPUBuffer} the clock as the vertex stage reads it */
213
+ get frame_uniform() {
214
+ return this.#frame_uniform;
215
+ }
216
+
217
+ /**
218
+ * The draw classes by index, a hole where no trail uses one.
219
+ * @returns {(TrailDrawClass|undefined)[]}
220
+ */
221
+ get draw_classes() {
222
+ return this.#classes;
223
+ }
224
+
225
+ /**
226
+ * The triangles of one segment of a tube of `radial` segments: two rings of `radial + 1` vertices,
227
+ * the head-ward ring first. Both faces are drawn, so the winding carries nothing.
228
+ *
229
+ * @param {number} radial
230
+ * @returns {GPUBuffer} `uint32` indices, `6 × radial` of them
231
+ */
232
+ index_buffer(radial) {
233
+ let buffer = this.#index_buffers.get(radial);
234
+
235
+ if (buffer !== undefined) {
236
+ return buffer;
237
+ }
238
+
239
+ const indices = new Uint32Array(radial * 6);
240
+ const ring = radial + 1;
241
+
242
+ for (let r = 0; r < radial; r++) {
243
+ const a = r;
244
+ const b = ring + r;
245
+ const c = ring + r + 1;
246
+ const d = r + 1;
247
+
248
+ indices.set([a, b, d, b, c, d], r * 6);
249
+ }
250
+
251
+ const device = this.#graphics.device;
252
+
253
+ buffer = device.createBuffer({
254
+ label: `Trails / indices / ${radial}`,
255
+ size: indices.byteLength,
256
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
257
+ });
258
+
259
+ device.queue.writeBuffer(buffer, 0, indices.buffer, 0, indices.byteLength);
260
+
261
+ this.#index_buffers.set(radial, buffer);
262
+
263
+ return buffer;
264
+ }
265
+
266
+ /** @returns {GPUDatabase} the trail and state tables */
267
+ get database() {
268
+ return this.#database;
269
+ }
270
+
271
+ /** @returns {import("../buffer/table/GPUTypedTable.js").GPUTypedTable} */
272
+ get table() {
273
+ return this.#database.get(TRAIL_TABLE);
274
+ }
275
+
276
+ /** @returns {import("../buffer/table/GPUTypedTable.js").GPUTypedTable} */
277
+ get state_table() {
278
+ return this.#database.get(TRAIL_STATE_TABLE);
279
+ }
280
+
281
+ /** @returns {TrailKnotPool} */
282
+ get knots() {
283
+ return this.#knots;
284
+ }
285
+
286
+ /** @returns {TrailFadeCurves} */
287
+ get fades() {
288
+ return this.#fades;
289
+ }
290
+
291
+ /** @returns {number} trails registered */
292
+ get count() {
293
+ return this.#trails.size;
294
+ }
295
+
296
+ /**
297
+ * @returns {number} the clock, in ticks, wrapped to a u32
298
+ */
299
+ get now() {
300
+ return Math.floor(this.#time * TRAIL_TICKS_PER_SECOND) >>> 0;
301
+ }
302
+
303
+ /**
304
+ * Radial counts by class index, a hole where no trail uses one.
305
+ * @returns {(number|undefined)[]}
306
+ */
307
+ get class_radials() {
308
+ return this.#classes.map(c => c === undefined ? undefined : c.radial);
309
+ }
310
+
311
+ /**
312
+ * @returns {GPUTrailContext[]} trails waiting for the GPU to seed them; taken by the frame that seeds them
313
+ */
314
+ get pending_seeds() {
315
+ return this.#seeds;
316
+ }
317
+
318
+ /**
319
+ * Move the clock on.
320
+ *
321
+ * @param {number} seconds simulation time since the last call
322
+ */
323
+ advance(seconds) {
324
+ assert.greaterThanOrEqual(seconds, 0, 'seconds');
325
+
326
+ this.#time += seconds;
327
+ }
328
+
329
+ /**
330
+ * Register a trail whose head follows `node`.
331
+ *
332
+ * @param {TrailParameters} parameters
333
+ * @param {number} node the `transforms` row of the trail's entity
334
+ * @returns {GPUTrailContext|null} null when the knot pool cannot house the trail on this device
335
+ */
336
+ add(parameters, node) {
337
+ assert.isNonNegativeInteger(node, 'node');
338
+ assert.notNull(parameters.fade, 'parameters.fade');
339
+
340
+ const knots = this.#knots.allocate(parameters.knot_count);
341
+
342
+ if (knots.offset === ALLOCATOR_NO_SPACE) {
343
+ return null;
344
+ }
345
+
346
+ const context = new GPUTrailContext();
347
+
348
+ context.node = node;
349
+ context.knots = knots;
350
+ context.fade = this.#fades.acquire(parameters.fade);
351
+ context.draw_class = this.#acquire_class(parameters.radial, parameters.knot_count - 1);
352
+ context.staged.copy(parameters);
353
+
354
+ context.row = this.table.add(this.#write_record(context, parameters));
355
+
356
+ this.#trails.set(context.row, context);
357
+
358
+ this.#seed(context);
359
+
360
+ return context;
361
+ }
362
+
363
+ /**
364
+ * Bring a trail's record up to date with `parameters`. A change to a build-time parameter rebuilds
365
+ * the trail: a new knot run, and a seed.
366
+ *
367
+ * @param {GPUTrailContext} context
368
+ * @param {TrailParameters} parameters
369
+ * @returns {boolean} false when a rebuild could not be housed; the trail keeps its old build
370
+ */
371
+ stage(context, parameters) {
372
+ assert.equal(this.#trails.get(context.row), context, 'the trail is not registered here');
373
+
374
+ const staged = context.staged;
375
+
376
+ if (!staged.same_build(parameters)) {
377
+ if (staged.knot_count !== parameters.knot_count) {
378
+ const knots = this.#knots.allocate(parameters.knot_count);
379
+
380
+ if (knots.offset === ALLOCATOR_NO_SPACE) {
381
+ return false;
382
+ }
383
+
384
+ this.#knots.free(context.knots);
385
+ context.knots = knots;
386
+ }
387
+
388
+ // acquired before the old is released, so a class the trail stays in is never let go
389
+ const draw_class = this.#acquire_class(parameters.radial, parameters.knot_count - 1);
390
+
391
+ this.#release_class(context.draw_class, staged.knot_count - 1);
392
+ context.draw_class = draw_class;
393
+
394
+ context.reseed = false;
395
+ context.epoch++;
396
+ }
397
+
398
+ if (!context.fade.curve.equals(parameters.fade)) {
399
+ const fade = this.#fades.acquire(parameters.fade);
400
+
401
+ this.#fades.release(context.fade);
402
+ context.fade = fade;
403
+ }
404
+
405
+ const rebuild = !staged.same_build(parameters);
406
+
407
+ staged.copy(parameters);
408
+
409
+ this.table.set(context.row, this.#write_record(context, parameters));
410
+
411
+ if (rebuild) {
412
+ this.#seed(context);
413
+ }
414
+
415
+ return true;
416
+ }
417
+
418
+ /**
419
+ * Point a trail at another `transforms` row.
420
+ *
421
+ * @param {GPUTrailContext} context
422
+ * @param {number} node
423
+ */
424
+ set_node(context, node) {
425
+ assert.isNonNegativeInteger(node, 'node');
426
+
427
+ if (context.node === node) {
428
+ return;
429
+ }
430
+
431
+ context.node = node;
432
+
433
+ this.table.set(context.row, this.#write_record(context, context.staged));
434
+ }
435
+
436
+ /**
437
+ * Expire every knot and collapse the tube onto the head at the next frame, keeping the trail's birth
438
+ * and its knots' frames: `Trail3D.clear()`.
439
+ *
440
+ * @param {GPUTrailContext} context
441
+ */
442
+ clear(context) {
443
+ context.epoch++;
444
+ context.reseed = true;
445
+
446
+ this.table.set(context.row, this.#write_record(context, context.staged));
447
+
448
+ if (!this.#seeds.includes(context)) {
449
+ this.#seeds.push(context);
450
+ }
451
+ }
452
+
453
+ /**
454
+ * @param {GPUTrailContext} context
455
+ */
456
+ remove(context) {
457
+ assert.equal(this.#trails.get(context.row), context, 'the trail is not registered here');
458
+
459
+ this.table.remove(context.row);
460
+ this.state_table.remove(context.row);
461
+
462
+ this.#knots.free(context.knots);
463
+ this.#fades.release(context.fade);
464
+ this.#release_class(context.draw_class, context.staged.knot_count - 1);
465
+
466
+ const pending = this.#seeds.indexOf(context);
467
+
468
+ if (pending !== -1) {
469
+ this.#seeds.splice(pending, 1);
470
+ }
471
+
472
+ this.#trails.delete(context.row);
473
+
474
+ context.row = -1;
475
+ context.knots = null;
476
+ context.fade = null;
477
+ context.draw_class = -1;
478
+ }
479
+
480
+ /**
481
+ * Send what changed since the last frame: the records and states staged, the fade curves.
482
+ *
483
+ * @returns {GPUBuffer} the fade key buffer, as it now is on the device
484
+ */
485
+ upload() {
486
+ const database = this.#database;
487
+
488
+ if (database.has_pending_upload) {
489
+ const cmd = ShadeGPUCommandContext.create(this.#graphics, "trails/upload");
490
+
491
+ database.update(cmd);
492
+
493
+ cmd.finish();
494
+ }
495
+
496
+ return this.#fades.flush();
497
+ }
498
+
499
+ /**
500
+ * Take the trails waiting for the GPU to seed them, as rows.
501
+ *
502
+ * @returns {Uint32Array}
503
+ */
504
+ take_seeds() {
505
+ const seeds = this.#seeds;
506
+ const rows = new Uint32Array(seeds.length);
507
+
508
+ for (let i = 0; i < seeds.length; i++) {
509
+ rows[i] = seeds[i].row;
510
+ }
511
+
512
+ seeds.length = 0;
513
+
514
+ return rows;
515
+ }
516
+
517
+ /**
518
+ * Upload what changed and record one frame of the trails into `graph`: the seeds asked for, the
519
+ * update, and the scan of live segments the per-view passes dispatch from. Once per frame, after
520
+ * the hierarchy has composed this frame's matrices.
521
+ *
522
+ * @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} graph
523
+ * @param {number} scene_database graph handle of the scene database
524
+ * @returns {import("./graph_trails.js").TrailSimulationHandles}
525
+ */
526
+ record_simulation(graph, scene_database) {
527
+ const fade_keys = this.upload();
528
+
529
+ // a persistent buffer: written ahead of the frame's commands, read by its draws
530
+ this.#frame_words[0] = this.now;
531
+ this.#graphics.device.queue.writeBuffer(this.#frame_uniform, 0, this.#frame_words.buffer, 0, FRAME_UNIFORM_BYTES);
532
+
533
+ return graph_trails_simulate({
534
+ graph,
535
+ scene_database,
536
+ runtime: this,
537
+ fade_keys,
538
+ seeds: this.take_seeds(),
539
+ });
540
+ }
541
+
542
+ destroy() {
543
+ this.#database.destroy();
544
+ this.#knots.destroy();
545
+ this.#fades.destroy();
546
+ this.#frame_uniform.destroy();
547
+
548
+ for (const buffer of this.#index_buffers.values()) {
549
+ buffer.destroy();
550
+ }
551
+
552
+ this.#index_buffers.clear();
553
+ this.#trails.clear();
554
+ this.#seeds.length = 0;
555
+ }
556
+
557
+ /**
558
+ * Queue a GPU seed, or for an authored trail lay its knots down now.
559
+ *
560
+ * @param {GPUTrailContext} context
561
+ */
562
+ #seed(context) {
563
+ const parameters = context.staged;
564
+
565
+ if (parameters.authored === null) {
566
+ this.state_table.set(context.row, TRAIL_STATE_UNSEEDED);
567
+
568
+ if (!this.#seeds.includes(context)) {
569
+ this.#seeds.push(context);
570
+ }
571
+
572
+ return;
573
+ }
574
+
575
+ const authored = parameters.authored;
576
+ const count = authored.count;
577
+ const now = this.now;
578
+
579
+ assert.equal(count, parameters.knot_count, 'an authored trail has as many knots as its layout');
580
+
581
+ const words = new Uint32Array(count * TRAIL_KNOT_WORDS);
582
+ const floats = new Float32Array(words.buffer);
583
+
584
+ for (let i = 0; i < count; i++) {
585
+ authored.getPosition(scratch_position, i);
586
+ authored.getFrame(scratch_frame, i);
587
+
588
+ const age_ticks = Math.round(authored.getAge(i) * TRAIL_TICKS_PER_SECOND);
589
+
590
+ trail_knot_write(
591
+ words, floats, i,
592
+ scratch_position[0], scratch_position[1], scratch_position[2],
593
+ (now - age_ticks) >>> 0,
594
+ scratch_frame,
595
+ authored.getThickness(i),
596
+ context.row
597
+ );
598
+ }
599
+
600
+ this.#knots.write(context.knots.offset, words);
601
+
602
+ // laid down here, so seeded already: every knot is a candidate, the head is the first
603
+ this.state_table.set(context.row, {
604
+ head: 0,
605
+ live: count,
606
+ last_commit: now,
607
+ birth: now,
608
+ seeded_epoch: context.epoch,
609
+ distance: 0,
610
+ });
611
+
612
+ const pending = this.#seeds.indexOf(context);
613
+
614
+ if (pending !== -1) {
615
+ this.#seeds.splice(pending, 1);
616
+ }
617
+ }
618
+
619
+ /**
620
+ * @param {GPUTrailContext} context
621
+ * @param {TrailParameters} parameters
622
+ * @returns {Object} the record, for the table to copy
623
+ */
624
+ #write_record(context, parameters) {
625
+ const record = this.#record;
626
+
627
+ const max_age_ticks = Math.floor(parameters.max_age * TRAIL_TICKS_PER_SECOND);
628
+
629
+ assert.lessThan(max_age_ticks, TICK_AGE_LIMIT, 'maxAge past what a u32 tick count ages exactly');
630
+
631
+ let flags = 0;
632
+
633
+ if (parameters.spawning) {
634
+ flags |= TRAIL_FLAG.SPAWNING;
635
+ }
636
+
637
+ if (parameters.distance_mode) {
638
+ flags |= TRAIL_FLAG.DISTANCE;
639
+ }
640
+
641
+ if (parameters.authored !== null) {
642
+ flags |= TRAIL_FLAG.AUTHORED;
643
+ }
644
+
645
+ if (parameters.warmup) {
646
+ flags |= TRAIL_FLAG.WARMUP;
647
+ }
648
+
649
+ if (parameters.fade.evaluate(1) * parameters.color[3] > 0) {
650
+ flags |= TRAIL_FLAG.TAIL_VISIBLE;
651
+ }
652
+
653
+ if (context.reseed) {
654
+ flags |= TRAIL_FLAG.RESEED;
655
+ }
656
+
657
+ record.node = context.node;
658
+ record.knot_base = context.knots.offset;
659
+ record.knot_count = parameters.knot_count;
660
+ record.radial = parameters.radial;
661
+ record.flags = flags;
662
+ record.seed_epoch = context.epoch;
663
+ record.commit_ticks = Math.floor(parameters.max_age / parameters.knot_count * TRAIL_TICKS_PER_SECOND);
664
+ record.max_age_ticks = max_age_ticks;
665
+ record.max_age = parameters.max_age;
666
+ record.width = parameters.width;
667
+ record.spawn_distance = parameters.spawn_distance;
668
+ record.fade_first = context.fade.first;
669
+ record.fade_count = context.fade.count;
670
+ record.draw_class = context.draw_class;
671
+ record.offset.set(parameters.offset);
672
+ record.color.set(parameters.color);
673
+
674
+ return record;
675
+ }
676
+
677
+ /**
678
+ * @param {number} radial
679
+ * @param {number} segments the trail's knots less one
680
+ * @returns {number} the class index
681
+ */
682
+ #acquire_class(radial, segments) {
683
+ assert.greaterThanOrEqual(radial, 3, 'a tube ring needs at least 3 radial segments');
684
+
685
+ let draw_class = this.#classes_by_radial.get(radial);
686
+
687
+ if (draw_class === undefined) {
688
+ draw_class = new TrailDrawClass();
689
+ draw_class.radial = radial;
690
+
691
+ this.#classes_by_radial.set(radial, draw_class);
692
+
693
+ let index = this.#classes.indexOf(undefined);
694
+
695
+ if (index === -1) {
696
+ index = this.#classes.length;
697
+ }
698
+
699
+ this.#classes[index] = draw_class;
700
+ }
701
+
702
+ draw_class.references++;
703
+ draw_class.segments += segments;
704
+
705
+ return this.#classes.indexOf(draw_class);
706
+ }
707
+
708
+ /**
709
+ * @param {number} index
710
+ * @param {number} segments the trail's knots less one, as it was acquired with
711
+ */
712
+ #release_class(index, segments) {
713
+ const draw_class = this.#classes[index];
714
+
715
+ draw_class.references--;
716
+ draw_class.segments -= segments;
717
+
718
+ if (draw_class.references > 0) {
719
+ return;
720
+ }
721
+
722
+ this.#classes_by_radial.delete(draw_class.radial);
723
+ this.#classes[index] = undefined;
724
+ }
725
+ }