@woosh/meep-engine 3.18.0 → 3.19.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 (120) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -1
  3. package/editor/view/particles/effect/ParticleCurveEditorView.js +303 -120
  4. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -1
  5. package/editor/view/particles/effect/ParticleGradientEditorView.js +108 -63
  6. package/editor/view/particles/effect/ParticleGraphEditorView.js +1 -1
  7. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  8. package/editor/view/particles/effect/ParticleNodeParametersView.js +3 -1
  9. package/editor/view/particles/effect/particle-editor.css +60 -30
  10. package/package.json +1 -2
  11. package/samples/engine/README.md +1 -1
  12. package/src/core/binary/compression/decompress_bytes.d.ts +13 -0
  13. package/src/core/binary/compression/decompress_bytes.d.ts.map +1 -0
  14. package/src/core/binary/compression/decompress_bytes.js +28 -0
  15. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +67 -22
  16. package/src/engine/asset/loaders/image/ImageDecoderWorker.js +12 -27
  17. package/src/engine/asset/loaders/image/prototypePNG.js +8 -7
  18. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  19. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
  20. package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts +266 -0
  21. package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts.map +1 -0
  22. package/src/engine/graphics/ecs/particles/ParticleEffect.js +455 -0
  23. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts +58 -0
  24. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts.map +1 -0
  25. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.js +219 -0
  26. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +178 -25
  27. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  28. package/src/engine/graphics3/GPUParticleEmitterSystem.js +809 -310
  29. package/src/format/image/png/PNGReader.d.ts +7 -6
  30. package/src/format/image/png/PNGReader.d.ts.map +1 -1
  31. package/src/format/image/png/PNGReader.js +13 -12
  32. package/src/format/image/png/chunk/png_chunk_decode_iTXt.d.ts +3 -2
  33. package/src/format/image/png/chunk/png_chunk_decode_iTXt.d.ts.map +1 -1
  34. package/src/format/image/png/chunk/png_chunk_decode_iTXt.js +5 -4
  35. package/src/format/image/png/chunk/png_chunk_decode_zTXt.d.ts +3 -2
  36. package/src/format/image/png/chunk/png_chunk_decode_zTXt.d.ts.map +1 -1
  37. package/src/format/image/png/chunk/png_chunk_decode_zTXt.js +5 -4
  38. package/src/format/image/png/png_inflate.d.ts +3 -3
  39. package/src/format/image/png/png_inflate.d.ts.map +1 -1
  40. package/src/format/image/png/png_inflate.js +29 -39
  41. package/src/format/texture/ktx2/ktx2_read.d.ts +4 -4
  42. package/src/format/texture/ktx2/ktx2_read.d.ts.map +1 -1
  43. package/src/format/texture/ktx2/ktx2_read.js +18 -21
  44. package/src/shade/playground/particle_ecs/README.md +203 -0
  45. package/src/shade/playground/particle_ecs/bonfire_editor.d.ts +39 -0
  46. package/src/shade/playground/particle_ecs/bonfire_editor.d.ts.map +1 -0
  47. package/src/shade/playground/particle_ecs/bonfire_editor.js +315 -0
  48. package/src/shade/playground/particle_ecs/bonfire_effects.d.ts +145 -0
  49. package/src/shade/playground/particle_ecs/bonfire_effects.d.ts.map +1 -0
  50. package/src/shade/playground/particle_ecs/bonfire_effects.js +202 -0
  51. package/src/shade/playground/particle_ecs/bonfire_sprites.d.ts +23 -0
  52. package/src/shade/playground/particle_ecs/bonfire_sprites.d.ts.map +1 -0
  53. package/src/shade/playground/particle_ecs/bonfire_sprites.js +315 -0
  54. package/src/shade/playground/particle_ecs/bonfire_world.d.ts +86 -0
  55. package/src/shade/playground/particle_ecs/bonfire_world.d.ts.map +1 -0
  56. package/src/shade/playground/particle_ecs/bonfire_world.js +303 -0
  57. package/src/shade/playground/particle_ecs/effects/embers.json +1634 -0
  58. package/src/shade/playground/particle_ecs/effects/flame.json +1882 -0
  59. package/src/shade/playground/particle_ecs/effects/smoke.json +1860 -0
  60. package/src/shade/playground/particle_ecs/effects/soot.json +1606 -0
  61. package/src/shade/playground/particle_ecs/index.html +330 -0
  62. package/src/shade/playground/particle_ecs/main.d.ts +2 -0
  63. package/src/shade/playground/particle_ecs/main.d.ts.map +1 -0
  64. package/src/shade/playground/particle_ecs/main.js +566 -0
  65. package/src/shade/playground/particle_ecs/moonlit_environment.d.ts +16 -0
  66. package/src/shade/playground/particle_ecs/moonlit_environment.d.ts.map +1 -0
  67. package/src/shade/playground/particle_ecs/moonlit_environment.js +144 -0
  68. package/src/shade/playground/particle_editor/README.md +5 -4
  69. package/src/shade/playground/profile_hotkey.d.ts +58 -0
  70. package/src/shade/playground/profile_hotkey.d.ts.map +1 -0
  71. package/src/shade/playground/profile_hotkey.js +325 -0
  72. package/src/shade/playground/ssr_variance/README.md +105 -0
  73. package/src/shade/playground/ssr_variance/capture.d.ts +16 -0
  74. package/src/shade/playground/ssr_variance/capture.d.ts.map +1 -0
  75. package/src/shade/playground/ssr_variance/capture.js +96 -0
  76. package/src/shade/playground/ssr_variance/index.html +22 -0
  77. package/src/shade/playground/ssr_variance/main.d.ts +2 -0
  78. package/src/shade/playground/ssr_variance/main.d.ts.map +1 -0
  79. package/src/shade/playground/ssr_variance/main.js +246 -0
  80. package/src/shade/playground/ssr_variance/reference.d.ts +18 -0
  81. package/src/shade/playground/ssr_variance/reference.d.ts.map +1 -0
  82. package/src/shade/playground/ssr_variance/reference.js +78 -0
  83. package/src/shade/playground/ssr_variance/scene.d.ts +11 -0
  84. package/src/shade/playground/ssr_variance/scene.d.ts.map +1 -0
  85. package/src/shade/playground/ssr_variance/scene.js +61 -0
  86. package/src/shade/playground/ssr_variance/statistics.d.ts +44 -0
  87. package/src/shade/playground/ssr_variance/statistics.d.ts.map +1 -0
  88. package/src/shade/playground/ssr_variance/statistics.js +51 -0
  89. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts.map +1 -1
  90. package/src/shade/renderer/loader/gltf/tiny-gltf.js +9 -3
  91. package/src/shade/renderer/loader/usd/usd_decode_image.d.ts +4 -4
  92. package/src/shade/renderer/loader/usd/usd_decode_image.d.ts.map +1 -1
  93. package/src/shade/renderer/loader/usd/usd_decode_image.js +4 -4
  94. package/src/shade/renderer/particles/DESIGN.md +748 -703
  95. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +11 -4
  96. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  97. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +431 -424
  98. package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
  99. package/src/shade/renderer/postprocess/ssr/SSR.js +2 -1
  100. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.d.ts.map +1 -1
  101. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.js +0 -6
  102. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.d.ts.map +1 -1
  103. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.js +19 -5
  104. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts +4 -0
  105. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -0
  106. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +133 -0
  107. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts +0 -8
  108. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
  109. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +16 -113
  110. package/src/shade/renderer/texture/source/texel_data_from_ktx2.d.ts +2 -2
  111. package/src/shade/renderer/texture/source/texel_data_from_ktx2.d.ts.map +1 -1
  112. package/src/shade/renderer/texture/source/texel_data_from_ktx2.js +3 -3
  113. package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts +0 -20
  114. package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts.map +0 -1
  115. package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.d.ts +0 -14
  116. package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.d.ts.map +0 -1
  117. package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts +0 -50
  118. package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts.map +0 -1
  119. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts +0 -17
  120. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts.map +0 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @typedef {object} BonfireWorld
3
+ * @property {number} flame entity
4
+ * @property {number} smoke entity
5
+ * @property {number} embers entity
6
+ * @property {number} soot entity
7
+ * @property {import("../../../engine/graphics/ecs/particles/ParticleEffect.js").ParticleEffect[]} effects
8
+ * the four components, in flame / smoke / embers / soot order
9
+ * @property {Light} firelight the point light at the fire, for {@link animate_bonfire} to flicker
10
+ * @property {number[]} entities everything built, for teardown
11
+ */
12
+ /**
13
+ * Build the whole set.
14
+ *
15
+ * Async because the meshlet builder runs on a WASM instance that instantiates asynchronously at
16
+ * import; building geometry before it is ready reads an instance that is not there yet.
17
+ *
18
+ * @param {import("../../../engine/ecs/EntityComponentDataset.js").EntityComponentDataset} dataset
19
+ * @param {object} [options]
20
+ * @param {import("./bonfire_sprites.js").BonfireSprites} [options.sprites] image URLs for the four
21
+ * effects; omitted, they draw as untextured quads, which is what the headless tests use
22
+ * @returns {Promise<BonfireWorld>}
23
+ */
24
+ export function build_bonfire_world(dataset: import("../../../engine/ecs/EntityComponentDataset.js").EntityComponentDataset, { sprites }?: {
25
+ sprites?: import("./bonfire_sprites.js").BonfireSprites;
26
+ }): Promise<BonfireWorld>;
27
+ /**
28
+ * Flicker the firelight.
29
+ *
30
+ * Three sine waves with no common period, so it never repeats audibly — the classic cheap fire
31
+ * flicker, and it is here rather than in the effects because it is the *light* that flickers: the
32
+ * particles already flicker on their own, and a light that held still while they did would read as
33
+ * a lamp with a fire drawn in front of it.
34
+ *
35
+ * @param {BonfireWorld} world
36
+ * @param {number} time seconds since start
37
+ */
38
+ export function animate_bonfire(world: BonfireWorld, time: number): void;
39
+ /**
40
+ * The scene the page shows, built as entities.
41
+ *
42
+ * **Every visible thing here is a `Transform64` and one more component**, which is the whole point
43
+ * of the page: the ground and the logs are `ShadedGeometry`, the moon and the firelight are `Light`,
44
+ * and the flame, smoke, embers and soot are `ParticleEffect`. Four systems observe those pairs and
45
+ * put rows in one `Scene`. Nothing in this file touches the scene graph, and nothing about placing
46
+ * a fire is different from placing a rock.
47
+ *
48
+ * That is what the rework bought. Before it, the particle system observed the scene's emitter nodes
49
+ * directly, so an effect was the one thing in a level that could not be spawned, moved, parented or
50
+ * destroyed by the machinery everything else uses.
51
+ */
52
+ /** Where the fire is. Everything else in the set is arranged around it. */
53
+ export const FIRE_POSITION: Vector3;
54
+ export type BonfireWorld = {
55
+ /**
56
+ * entity
57
+ */
58
+ flame: number;
59
+ /**
60
+ * entity
61
+ */
62
+ smoke: number;
63
+ /**
64
+ * entity
65
+ */
66
+ embers: number;
67
+ /**
68
+ * entity
69
+ */
70
+ soot: number;
71
+ /**
72
+ * the four components, in flame / smoke / embers / soot order
73
+ */
74
+ effects: import("../../../engine/graphics/ecs/particles/ParticleEffect.js").ParticleEffect[];
75
+ /**
76
+ * the point light at the fire, for {@link animate_bonfire } to flicker
77
+ */
78
+ firelight: Light;
79
+ /**
80
+ * everything built, for teardown
81
+ */
82
+ entities: number[];
83
+ };
84
+ import Vector3 from "../../../core/geom/Vector3.js";
85
+ import { Light } from "../../../engine/graphics/ecs/light/Light.js";
86
+ //# sourceMappingURL=bonfire_world.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bonfire_world.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_ecs/bonfire_world.js"],"names":[],"mappings":"AA8CA;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;GAWG;AACH,6CANW,OAAO,+CAA+C,EAAE,sBAAsB;IAEtB,OAAO,GAA/D,OAAO,sBAAsB,EAAE,cAAc;IAE3C,QAAQ,YAAY,CAAC,CAuIjC;AAED;;;;;;;;;;GAUG;AACH,uCAHW,YAAY,QACZ,MAAM,QAShB;AA3MD;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,oCAAkD;;;;;WAapC,MAAM;;;;WACN,MAAM;;;;YACN,MAAM;;;;UACN,MAAM;;;;aACN,OAAO,0DAA0D,EAAE,cAAc,EAAE;;;;eAEnF,KAAK;;;;cACL,MAAM,EAAE;;oBArDF,+BAA+B;sBAG7B,6CAA6C"}
@@ -0,0 +1,303 @@
1
+ import { Color } from "../../../core/color/Color.js";
2
+ import Quaternion from "../../../core/geom/Quaternion.js";
3
+ import Vector3 from "../../../core/geom/Vector3.js";
4
+ import { Entity } from "../../../engine/ecs/Entity.js";
5
+ import { Transform64 } from "../../../engine/ecs/transform/Transform64.js";
6
+ import { Light } from "../../../engine/graphics/ecs/light/Light.js";
7
+ import { LightType } from "../../../engine/graphics/ecs/light/LightType.js";
8
+ import { ShadedGeometry } from "../../../engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
9
+ import { MeshletGeometry } from "../../renderer/geometry/MeshletGeometry.js";
10
+ import { MeshoptClusterizer } from "../../renderer/geometry/meshlet/build/meshoptimizer/meshopt_clusterizer.js";
11
+ import { meshlet_geometry_build_from_geometry } from "../../renderer/geometry/meshlet_geometry_build_from_geometry.js";
12
+ import { make_box_geometry } from "../../renderer/geometry/primitives/make_box_geometry.js";
13
+ import { make_cylinder_geometry } from "../../renderer/geometry/primitives/make_cylinder_geometry.js";
14
+ import { make_plane_geometry } from "../../renderer/geometry/primitives/make_plane_geometry.js";
15
+ import { ShadeDrawSide } from "../../renderer/material/ShadeDrawSide.js";
16
+ import { StandardShadeMaterial } from "../../renderer/material/StandardShadeMaterial.js";
17
+ import { TransparencyMode } from "../../renderer/material/TransparencyMode.js";
18
+ import { bonfire_effects } from "./bonfire_effects.js";
19
+ import { MOON_DIRECTION } from "./moonlit_environment.js";
20
+
21
+ /**
22
+ * The scene the page shows, built as entities.
23
+ *
24
+ * **Every visible thing here is a `Transform64` and one more component**, which is the whole point
25
+ * of the page: the ground and the logs are `ShadedGeometry`, the moon and the firelight are `Light`,
26
+ * and the flame, smoke, embers and soot are `ParticleEffect`. Four systems observe those pairs and
27
+ * put rows in one `Scene`. Nothing in this file touches the scene graph, and nothing about placing
28
+ * a fire is different from placing a rock.
29
+ *
30
+ * That is what the rework bought. Before it, the particle system observed the scene's emitter nodes
31
+ * directly, so an effect was the one thing in a level that could not be spawned, moved, parented or
32
+ * destroyed by the machinery everything else uses.
33
+ */
34
+
35
+ /** Where the fire is. Everything else in the set is arranged around it. */
36
+ export const FIRE_POSITION = new Vector3(0, 0, 0);
37
+
38
+ /** Radius of the ring of stones, in metres. */
39
+ const PIT_RADIUS = 0.85;
40
+
41
+ /**
42
+ * Baseline intensity of the firelight, before the flicker.
43
+ * @type {number}
44
+ */
45
+ const FIRELIGHT_INTENSITY = 3.5;
46
+
47
+ /**
48
+ * @typedef {object} BonfireWorld
49
+ * @property {number} flame entity
50
+ * @property {number} smoke entity
51
+ * @property {number} embers entity
52
+ * @property {number} soot entity
53
+ * @property {import("../../../engine/graphics/ecs/particles/ParticleEffect.js").ParticleEffect[]} effects
54
+ * the four components, in flame / smoke / embers / soot order
55
+ * @property {Light} firelight the point light at the fire, for {@link animate_bonfire} to flicker
56
+ * @property {number[]} entities everything built, for teardown
57
+ */
58
+
59
+ /**
60
+ * Build the whole set.
61
+ *
62
+ * Async because the meshlet builder runs on a WASM instance that instantiates asynchronously at
63
+ * import; building geometry before it is ready reads an instance that is not there yet.
64
+ *
65
+ * @param {import("../../../engine/ecs/EntityComponentDataset.js").EntityComponentDataset} dataset
66
+ * @param {object} [options]
67
+ * @param {import("./bonfire_sprites.js").BonfireSprites} [options.sprites] image URLs for the four
68
+ * effects; omitted, they draw as untextured quads, which is what the headless tests use
69
+ * @returns {Promise<BonfireWorld>}
70
+ */
71
+ export async function build_bonfire_world(dataset, { sprites = {} } = {}) {
72
+ await MeshoptClusterizer.ready;
73
+
74
+ /** @type {number[]} */
75
+ const entities = [];
76
+
77
+ /**
78
+ * @param {Entity} builder
79
+ * @returns {number}
80
+ */
81
+ const build = builder => {
82
+ builder.build(dataset);
83
+ entities.push(builder.id);
84
+
85
+ return builder.id;
86
+ };
87
+
88
+ // --- the ground, the pit and the fuel ------------------------------------------------------
89
+
90
+ const ground_material = material(new Color(0.12, 0.113, 0.10, 1), { roughness: 0.95 });
91
+ const stone_material = material(new Color(0.14, 0.14, 0.15, 1), { roughness: 0.8 });
92
+ const log_material = material(new Color(0.17, 0.10, 0.065, 1), { roughness: 0.85 });
93
+
94
+ build(new Entity()
95
+ .add(placed(new Vector3(0, 0, 0), Quaternion.fromAxisAngle(Vector3.right, -Math.PI / 2)))
96
+ .add(shaded(meshlets(make_plane_geometry(200, 200, 1, 1)), ground_material)));
97
+
98
+ const stone = meshlets(make_box_geometry(0.34, 0.26, 0.28));
99
+
100
+ for (let i = 0; i < 11; i++) {
101
+ const angle = (i / 11) * Math.PI * 2;
102
+ const wobble = 0.9 + ((i * 37) % 7) / 24;
103
+
104
+ build(new Entity()
105
+ .add(placed(
106
+ new Vector3(
107
+ FIRE_POSITION.x + Math.cos(angle) * PIT_RADIUS * wobble,
108
+ 0.1,
109
+ FIRE_POSITION.z + Math.sin(angle) * PIT_RADIUS * wobble
110
+ ),
111
+ Quaternion.fromAxisAngle(Vector3.up, angle + wobble)
112
+ ))
113
+ .add(shaded(stone, stone_material)));
114
+ }
115
+
116
+ // Logs leaning into a teepee, which is where the flame's pinch comes from visually: the fire is
117
+ // widest at the fuel and narrows above it.
118
+ const log = meshlets(make_cylinder_geometry(0.075, 0.1, 1.5, 8, 1));
119
+
120
+ for (let i = 0; i < 5; i++) {
121
+ const angle = (i / 5) * Math.PI * 2 + 0.4;
122
+ const lean = 0.42;
123
+
124
+ build(new Entity()
125
+ .add(placed(
126
+ new Vector3(
127
+ FIRE_POSITION.x + Math.cos(angle) * 0.34,
128
+ 0.62,
129
+ FIRE_POSITION.z + Math.sin(angle) * 0.34
130
+ ),
131
+ // rotate +Y toward the outward lean: the axis is up x outward
132
+ Quaternion.fromAxisAngle(
133
+ new Vector3(Math.sin(angle), 0, -Math.cos(angle)),
134
+ lean
135
+ )
136
+ ))
137
+ .add(shaded(log, log_material)));
138
+ }
139
+
140
+ // two spent logs lying across the pit
141
+ for (const [offset, angle] of [[new Vector3(0.1, 0.09, -0.55), 0.7], [new Vector3(-0.35, 0.09, 0.4), 2.3]]) {
142
+ build(new Entity()
143
+ .add(placed(
144
+ new Vector3(FIRE_POSITION.x + offset.x, offset.y, FIRE_POSITION.z + offset.z),
145
+ Quaternion.fromAxisAngle(new Vector3(Math.sin(angle), 0, -Math.cos(angle)), Math.PI / 2)
146
+ ))
147
+ .add(shaded(log, log_material)));
148
+ }
149
+
150
+ // --- the lights ----------------------------------------------------------------------------
151
+
152
+ const moon = new Light();
153
+
154
+ moon.type.set(LightType.DIRECTION);
155
+ moon.color.setRGB(0.62, 0.72, 1.0);
156
+ moon.intensity.set(1.1);
157
+ // an angular radius, for a directional source: the moon's is about half a degree, and a source
158
+ // with some size is what softens the terminator and the shadow's edge
159
+ moon.radius.set(0.0087);
160
+ moon.castShadow.set(true);
161
+
162
+ build(new Entity()
163
+ .add(placed(new Vector3(0, 12, 0), look_along(MOON_DIRECTION.clone().negate())))
164
+ .add(moon));
165
+
166
+ // The fire's own light, and the thing that makes the set look lit by the fire rather than lit
167
+ // near it: a warm point source inside the flame, flickered by {@link animate_bonfire}.
168
+ const firelight = new Light();
169
+
170
+ firelight.type.set(LightType.POINT);
171
+ firelight.color.setRGB(1.0, 0.52, 0.19);
172
+ firelight.intensity.set(FIRELIGHT_INTENSITY);
173
+ firelight.distance.set(18);
174
+ // the flame is roughly a third of a metre across, and a source with size is what keeps the
175
+ // stones from each carrying one mirror-sharp highlight
176
+ firelight.radius.set(0.35);
177
+
178
+ build(new Entity()
179
+ .add(placed(new Vector3(FIRE_POSITION.x, 0.55, FIRE_POSITION.z)))
180
+ .add(firelight));
181
+
182
+ // --- the fire ------------------------------------------------------------------------------
183
+
184
+ const effects = bonfire_effects(sprites);
185
+
186
+ /**
187
+ * @param {import("../../../engine/graphics/ecs/particles/ParticleEffect.js").ParticleEffect} effect
188
+ * @param {number} height
189
+ * @returns {number}
190
+ */
191
+ const emitter = (effect, height) => build(new Entity()
192
+ .add(placed(new Vector3(FIRE_POSITION.x, FIRE_POSITION.y + height, FIRE_POSITION.z)))
193
+ .add(effect));
194
+
195
+ return {
196
+ flame: emitter(effects.flame, 0.16),
197
+ smoke: emitter(effects.smoke, 0.16),
198
+ embers: emitter(effects.embers, 0.16),
199
+ soot: emitter(effects.soot, 0.16),
200
+ effects: [effects.flame, effects.smoke, effects.embers, effects.soot],
201
+ firelight,
202
+ entities,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Flicker the firelight.
208
+ *
209
+ * Three sine waves with no common period, so it never repeats audibly — the classic cheap fire
210
+ * flicker, and it is here rather than in the effects because it is the *light* that flickers: the
211
+ * particles already flicker on their own, and a light that held still while they did would read as
212
+ * a lamp with a fire drawn in front of it.
213
+ *
214
+ * @param {BonfireWorld} world
215
+ * @param {number} time seconds since start
216
+ */
217
+ export function animate_bonfire(world, time) {
218
+ const wobble =
219
+ Math.sin(time * 8.7) * 0.13
220
+ + Math.sin(time * 3.1 + 1.7) * 0.09
221
+ + Math.sin(time * 17.3 + 0.4) * 0.05;
222
+
223
+ world.firelight.intensity.set(FIRELIGHT_INTENSITY * (1 + wobble));
224
+ }
225
+
226
+ // -------------------------------------------------------------------------------------------
227
+ // Helpers
228
+ // -------------------------------------------------------------------------------------------
229
+
230
+ /**
231
+ * @param {Vector3} position
232
+ * @param {Quaternion} [rotation]
233
+ * @returns {Transform64}
234
+ */
235
+ function placed(position, rotation = new Quaternion()) {
236
+ const transform = new Transform64();
237
+
238
+ transform.setTranslation(position.x, position.y, position.z);
239
+ transform.setRotation(rotation.x, rotation.y, rotation.z, rotation.w);
240
+ transform.updateMatrix();
241
+
242
+ return transform;
243
+ }
244
+
245
+ /**
246
+ * A rotation that points local **forward** along `direction`, which is how a `DirectionalLight`
247
+ * takes its direction — it has no direction field, only a transform.
248
+ *
249
+ * @param {Vector3} direction unit
250
+ * @returns {Quaternion}
251
+ */
252
+ function look_along(direction) {
253
+ const q = new Quaternion();
254
+
255
+ q.fromUnitVectors(Vector3.forward, direction);
256
+ q.normalize();
257
+
258
+ return q;
259
+ }
260
+
261
+ /**
262
+ * @param {MeshletGeometry} geometry
263
+ * @param {StandardShadeMaterial} material_instance
264
+ * @returns {ShadedGeometry}
265
+ */
266
+ function shaded(geometry, material_instance) {
267
+ const component = new ShadedGeometry();
268
+
269
+ component.geometry = geometry;
270
+ component.material = material_instance;
271
+
272
+ return component;
273
+ }
274
+
275
+ /**
276
+ * @param {object} geometry
277
+ * @returns {MeshletGeometry}
278
+ */
279
+ function meshlets(geometry) {
280
+ const built = new MeshletGeometry();
281
+
282
+ meshlet_geometry_build_from_geometry(geometry, built);
283
+
284
+ return built;
285
+ }
286
+
287
+ /**
288
+ * @param {Color} diffuse
289
+ * @param {object} [options]
290
+ * @param {number} [options.roughness]
291
+ * @returns {StandardShadeMaterial}
292
+ */
293
+ function material(diffuse, { roughness = 0.8 } = {}) {
294
+ const result = new StandardShadeMaterial();
295
+
296
+ result.diffuse_color.copy(diffuse);
297
+ result.roughness_factor = roughness;
298
+ result.metallic_factor = 0;
299
+ result.transparency_mode = TransparencyMode.Opaque;
300
+ result.draw_side = ShadeDrawSide.Double;
301
+
302
+ return result;
303
+ }