@woosh/meep-engine 3.18.0 → 3.20.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.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleCurveEditorView.js +303 -120
- package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleGradientEditorView.js +108 -63
- package/editor/view/particles/effect/ParticleGraphEditorView.js +2 -2
- package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleNodeParametersView.js +7 -46
- package/editor/view/particles/effect/particle-editor.css +60 -30
- package/package.json +1 -2
- package/samples/engine/README.md +1 -1
- package/src/core/binary/compression/decompress_bytes.d.ts +13 -0
- package/src/core/binary/compression/decompress_bytes.d.ts.map +1 -0
- package/src/core/binary/compression/decompress_bytes.js +28 -0
- package/src/core/collection/array/array_group_by.d.ts.map +1 -1
- package/src/core/collection/array/array_group_by.js +2 -11
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +4 -9
- package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +67 -22
- package/src/core/model/object/optional_clone.d.ts +7 -0
- package/src/core/model/object/optional_clone.d.ts.map +1 -0
- package/src/core/model/object/optional_equality.d.ts +10 -0
- package/src/core/model/object/optional_equality.d.ts.map +1 -0
- package/src/{shade/descriptor/util → core/model/object}/optional_equality.js +2 -2
- package/src/core/model/object/optional_hash.d.ts +9 -0
- package/src/core/model/object/optional_hash.d.ts.map +1 -0
- package/src/{shade/descriptor/util → core/model/object}/optional_hash.js +3 -3
- package/src/engine/asset/Asset.d.ts +1 -1
- package/src/engine/asset/Asset.d.ts.map +1 -1
- package/src/engine/asset/Asset.js +3 -1
- package/src/engine/asset/loaders/image/ImageDecoderWorker.js +12 -27
- package/src/engine/asset/loaders/image/prototypePNG.js +8 -7
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts.map +1 -1
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +9 -10
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts +266 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts.map +1 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.js +439 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts +58 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.js +219 -0
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +178 -25
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +809 -310
- package/src/format/image/png/PNGReader.d.ts +7 -6
- package/src/format/image/png/PNGReader.d.ts.map +1 -1
- package/src/format/image/png/PNGReader.js +13 -12
- package/src/format/image/png/chunk/png_chunk_decode_iTXt.d.ts +3 -2
- package/src/format/image/png/chunk/png_chunk_decode_iTXt.d.ts.map +1 -1
- package/src/format/image/png/chunk/png_chunk_decode_iTXt.js +5 -4
- package/src/format/image/png/chunk/png_chunk_decode_zTXt.d.ts +3 -2
- package/src/format/image/png/chunk/png_chunk_decode_zTXt.d.ts.map +1 -1
- package/src/format/image/png/chunk/png_chunk_decode_zTXt.js +5 -4
- package/src/format/image/png/png_inflate.d.ts +3 -3
- package/src/format/image/png/png_inflate.d.ts.map +1 -1
- package/src/format/image/png/png_inflate.js +29 -39
- package/src/format/texture/ktx2/ktx2_read.d.ts +4 -4
- package/src/format/texture/ktx2/ktx2_read.d.ts.map +1 -1
- package/src/format/texture/ktx2/ktx2_read.js +18 -21
- package/src/shade/descriptor/binding/BindGroupLayoutDescriptor.js +1 -1
- package/src/shade/descriptor/binding/BindGroupLayoutEntry.js +3 -3
- package/src/shade/descriptor/pipeline/PipelineDescriptorBase.js +1 -1
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +1 -1
- package/src/shade/descriptor/pipeline/render/ColorTargetState.js +3 -3
- package/src/shade/descriptor/pipeline/render/FragmentState.js +1 -1
- package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.js +3 -3
- package/src/shade/playground/particle_ecs/README.md +203 -0
- package/src/shade/playground/particle_ecs/bonfire_editor.d.ts +39 -0
- package/src/shade/playground/particle_ecs/bonfire_editor.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_editor.js +315 -0
- package/src/shade/playground/particle_ecs/bonfire_effects.d.ts +145 -0
- package/src/shade/playground/particle_ecs/bonfire_effects.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_effects.js +202 -0
- package/src/shade/playground/particle_ecs/bonfire_sprites.d.ts +23 -0
- package/src/shade/playground/particle_ecs/bonfire_sprites.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_sprites.js +315 -0
- package/src/shade/playground/particle_ecs/bonfire_world.d.ts +86 -0
- package/src/shade/playground/particle_ecs/bonfire_world.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_world.js +303 -0
- package/src/shade/playground/particle_ecs/effects/embers.json +1634 -0
- package/src/shade/playground/particle_ecs/effects/flame.json +1882 -0
- package/src/shade/playground/particle_ecs/effects/smoke.json +1860 -0
- package/src/shade/playground/particle_ecs/effects/soot.json +1606 -0
- package/src/shade/playground/particle_ecs/index.html +330 -0
- package/src/shade/playground/particle_ecs/main.d.ts +2 -0
- package/src/shade/playground/particle_ecs/main.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/main.js +566 -0
- package/src/shade/playground/particle_ecs/moonlit_environment.d.ts +16 -0
- package/src/shade/playground/particle_ecs/moonlit_environment.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/moonlit_environment.js +144 -0
- package/src/shade/playground/particle_editor/README.md +5 -4
- package/src/shade/playground/particle_system/particle_prototype.d.ts +7 -9
- package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
- package/src/shade/playground/particle_system/particle_prototype.js +7 -9
- package/src/shade/playground/profile_hotkey.d.ts +58 -0
- package/src/shade/playground/profile_hotkey.d.ts.map +1 -0
- package/src/shade/playground/profile_hotkey.js +325 -0
- package/src/shade/playground/ssr_motion/README.md +22 -0
- package/src/shade/playground/ssr_motion/index.html +18 -0
- package/src/shade/playground/ssr_motion/main.d.ts +2 -0
- package/src/shade/playground/ssr_motion/main.d.ts.map +1 -0
- package/src/shade/playground/ssr_motion/main.js +149 -0
- package/src/shade/playground/ssr_variance/README.md +105 -0
- package/src/shade/playground/ssr_variance/capture.d.ts +16 -0
- package/src/shade/playground/ssr_variance/capture.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/capture.js +96 -0
- package/src/shade/playground/ssr_variance/index.html +23 -0
- package/src/shade/playground/ssr_variance/main.d.ts +2 -0
- package/src/shade/playground/ssr_variance/main.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/main.js +246 -0
- package/src/shade/playground/ssr_variance/reference.d.ts +18 -0
- package/src/shade/playground/ssr_variance/reference.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/reference.js +78 -0
- package/src/shade/playground/ssr_variance/scene.d.ts +11 -0
- package/src/shade/playground/ssr_variance/scene.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/scene.js +61 -0
- package/src/shade/playground/ssr_variance/statistics.d.ts +44 -0
- package/src/shade/playground/ssr_variance/statistics.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/statistics.js +51 -0
- package/src/shade/renderer/geometry/Geometry.d.ts.map +1 -1
- package/src/shade/renderer/geometry/Geometry.js +4 -7
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/tiny-gltf.js +9 -3
- package/src/shade/renderer/loader/usd/usd_decode_image.d.ts +4 -4
- package/src/shade/renderer/loader/usd/usd_decode_image.d.ts.map +1 -1
- package/src/shade/renderer/loader/usd/usd_decode_image.js +4 -4
- package/src/shade/renderer/material/StandardShadeMaterial.js +2 -2
- package/src/shade/renderer/particles/DESIGN.md +745 -702
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +3 -1
- package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +1 -1
- package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +9 -15
- package/src/shade/renderer/particles/graph/particle_group_library.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_group_library.js +8 -3
- package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +0 -2
- package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_node_parameters.js +0 -9
- package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_node_presentation.js +0 -10
- package/src/shade/renderer/particles/graph/particle_ramp.d.ts +2 -8
- package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_ramp.js +2 -8
- package/src/shade/renderer/particles/graph/validate_particle_graph.js +0 -6
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles_avboit.js +13 -4
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts +1 -12
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/InstructionStream.js +1 -12
- package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +54 -60
- package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/ParticleVMISA.js +9 -17
- package/src/shade/renderer/particles/isa/particle_assembly.d.ts +0 -1
- package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/particle_assembly.js +2 -16
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +1 -1
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +1 -1
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +0 -14
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +3 -17
- package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +15 -36
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +1 -5
- package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +1 -8
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +2 -11
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +2 -11
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +11 -4
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/ParticleEmitter.js +431 -424
- package/src/shade/renderer/particles/shaders/chunk_particle_avboit_contribution.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_avboit_contribution.js +13 -0
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts +3 -6
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +49 -17
- package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.js +0 -2
- package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +0 -2
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +1 -5
- package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +0 -2
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +1 -3
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.js +4 -20
- package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/chunk_particle_vm.js +2 -5
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.d.ts.map +1 -1
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.js +0 -2
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.d.ts.map +1 -1
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.js +0 -2
- package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/SSR.js +2 -1
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.js +20 -5
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.js +19 -5
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts +4 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +137 -0
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts +0 -8
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +18 -114
- package/src/shade/renderer/scene/Mesh.js +1 -1
- package/src/shade/renderer/texture/ShadeTexture.js +1 -1
- package/src/shade/renderer/texture/source/texel_data_from_ktx2.d.ts +2 -2
- package/src/shade/renderer/texture/source/texel_data_from_ktx2.d.ts.map +1 -1
- package/src/shade/renderer/texture/source/texel_data_from_ktx2.js +3 -3
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts +0 -19
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +0 -30
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.d.ts +0 -11
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.js +0 -13
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts +0 -20
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.d.ts +0 -14
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts +0 -50
- package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts +0 -17
- package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts.map +0 -1
- /package/src/{shade/descriptor/util → core/model/object}/optional_clone.js +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @returns {NodeDescriptionVisualRegistry}
|
|
3
|
+
*/
|
|
4
|
+
export function particle_node_visuals(): NodeDescriptionVisualRegistry;
|
|
5
|
+
/**
|
|
6
|
+
* Open one of the saved effects as a document.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} name see {@link BONFIRE_EFFECT_NAMES}
|
|
9
|
+
* @param {object} [json] an effect to open instead of the saved one — what the page hands over when
|
|
10
|
+
* somebody imports a file they downloaded earlier
|
|
11
|
+
* @returns {ParticleEffectDocument}
|
|
12
|
+
*/
|
|
13
|
+
export function bonfire_effect_document(name: string, json?: object): ParticleEffectDocument;
|
|
14
|
+
/**
|
|
15
|
+
* All four, as documents.
|
|
16
|
+
*
|
|
17
|
+
* @returns {Object<string, ParticleEffectDocument>}
|
|
18
|
+
*/
|
|
19
|
+
export function bonfire_effect_documents(): {
|
|
20
|
+
[x: string]: ParticleEffectDocument;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The ECS component a document describes, compiled.
|
|
24
|
+
*
|
|
25
|
+
* `null` when the effect does not compile. A graph an author has half-rewired is the ordinary case
|
|
26
|
+
* for that, so it is the caller's business to say so rather than this one's to throw — which is why
|
|
27
|
+
* the page can leave the previous effect burning while the editor shows the error.
|
|
28
|
+
*
|
|
29
|
+
* @param {ParticleEffectDocument} document
|
|
30
|
+
* @param {string|null} [texture] image URL to draw with, used when the document names none of its
|
|
31
|
+
* own. The page's sprites are generated at runtime, so they cannot be in the file
|
|
32
|
+
* @returns {ParticleEffect|null}
|
|
33
|
+
*/
|
|
34
|
+
export function bonfire_effect_component(document: ParticleEffectDocument, texture?: string | null): ParticleEffect | null;
|
|
35
|
+
/**
|
|
36
|
+
* The four effects, ready to go on entities.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} [textures] image URLs to draw with, `null` for an untextured (flat quad) effect
|
|
39
|
+
* @param {string|null} [textures.flame]
|
|
40
|
+
* @param {string|null} [textures.smoke]
|
|
41
|
+
* @param {string|null} [textures.ember]
|
|
42
|
+
* @param {string|null} [textures.soot]
|
|
43
|
+
* @returns {{flame: ParticleEffect, smoke: ParticleEffect, embers: ParticleEffect, soot: ParticleEffect}}
|
|
44
|
+
*/
|
|
45
|
+
export function bonfire_effects(textures?: {
|
|
46
|
+
flame?: string | null;
|
|
47
|
+
smoke?: string | null;
|
|
48
|
+
ember?: string | null;
|
|
49
|
+
soot?: string | null;
|
|
50
|
+
}): {
|
|
51
|
+
flame: ParticleEffect;
|
|
52
|
+
smoke: ParticleEffect;
|
|
53
|
+
embers: ParticleEffect;
|
|
54
|
+
soot: ParticleEffect;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The sprite URLs keyed the way the effects are, so the page and the editor agree on which image
|
|
58
|
+
* belongs to which effect. `bonfire_sprites()` names the ember one in the singular.
|
|
59
|
+
*
|
|
60
|
+
* @param {object} [textures]
|
|
61
|
+
* @returns {Object<string, string|null>}
|
|
62
|
+
*/
|
|
63
|
+
export function bonfire_sprite_urls(textures?: object): {
|
|
64
|
+
[x: string]: string | null;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* A bonfire, as four saved particle effects.
|
|
68
|
+
*
|
|
69
|
+
* Flame, smoke, embers and soot: four effects that read as one fire because they were authored
|
|
70
|
+
* against one another — the smoke starts where the flame stops being bright, the embers leave from
|
|
71
|
+
* inside the flame core and outlive it, and the soot rides the same plume as the smoke and then
|
|
72
|
+
* falls out of it.
|
|
73
|
+
*
|
|
74
|
+
* ## They are documents, not code
|
|
75
|
+
*
|
|
76
|
+
* Each is a {@link ParticleEffectDocument} saved as JSON in `effects/`: the per-particle attribute
|
|
77
|
+
* declarations, the INIT and UPDATE node graphs with the positions they were arranged at, and the
|
|
78
|
+
* emitter settings. That is the whole effect, and it is what the editor opens — press `E` on the
|
|
79
|
+
* page, change something, download the result, drop it over the file, reload. There is no porting
|
|
80
|
+
* step and no authoring code to keep in step with what was edited, because the file the editor
|
|
81
|
+
* writes **is** the effect.
|
|
82
|
+
*
|
|
83
|
+
* Which is the same claim `ParticleEffectSerializationAdapter` makes about a level: the VM's
|
|
84
|
+
* instruction set is a durable format, so an effect is content. These documents keep the *graph*
|
|
85
|
+
* rather than the bytecode because they are the authoring form — a level saves the compiled program,
|
|
86
|
+
* an author's file saves what it was compiled from.
|
|
87
|
+
*
|
|
88
|
+
* ## How each effect is built
|
|
89
|
+
*
|
|
90
|
+
* Every one of them has the same skeleton, which is the skeleton of nearly every particle effect:
|
|
91
|
+
*
|
|
92
|
+
* - **INIT** places the particle, gives it a velocity, and rolls its lifetime, its base size and a
|
|
93
|
+
* personal `seed`. There is no built-in age or lifetime — the effect declares what it needs, which
|
|
94
|
+
* is why `age` and `life` are ordinary attributes.
|
|
95
|
+
* - **UPDATE** advances the age, kills the particle when it runs out, integrates the forces, and
|
|
96
|
+
* drives the drawn size and colour from `age / life` through a ramp — a curve for the size and a
|
|
97
|
+
* gradient for the colour, both written into the program as arithmetic rather than sampled from a
|
|
98
|
+
* table the host has to keep in step.
|
|
99
|
+
*
|
|
100
|
+
* Two things are worth knowing before editing one:
|
|
101
|
+
*
|
|
102
|
+
* - **One store per attribute per phase.** Every store runs and nothing orders them, so a second
|
|
103
|
+
* store does not refine the first, it replaces it — which is how the smoke's drag silently ate its
|
|
104
|
+
* own velocity integration until the editor's validator was pointed at it. The graph editor calls
|
|
105
|
+
* that an error; nothing at runtime does.
|
|
106
|
+
* - **The register budget is shared.** The VM keeps its file in shader registers, and one program
|
|
107
|
+
* needing more than `PARTICLE_VM_FAST_REGISTER_SLOTS` swaps emit and simulate for storage-backed
|
|
108
|
+
* variants *for the whole system* — so an effect that grows past it slows down every effect in the
|
|
109
|
+
* scene rather than itself. `bonfire.spec.js` pins it.
|
|
110
|
+
*
|
|
111
|
+
* The turbulence in all four is three decorrelated `simplex3` samples read as a vector, at a point
|
|
112
|
+
* that drifts with time so the field scrolls through the rising column. Deliberately not the
|
|
113
|
+
* library's `curl3`, which is twelve samples for a divergence-free field: over a metre of flame
|
|
114
|
+
* nobody can tell, and the budget above is shared.
|
|
115
|
+
*
|
|
116
|
+
* ## Lighting
|
|
117
|
+
*
|
|
118
|
+
* None of these opt into `EMITTER_FLAG.LIGHTING`, and that is not an oversight: the shipped lighting
|
|
119
|
+
* hook is `chunk_particle_lighting_unlit`, a pass-through, so the flag would cost a bit in a record
|
|
120
|
+
* and change no pixel. The smoke and the soot are *painted* lit instead — their gradients carry the
|
|
121
|
+
* fire's warmth low down where the fire would light them and the moon's blue higher up, over the
|
|
122
|
+
* particle's own age. When the lit hook lands the gradients are what to reconsider.
|
|
123
|
+
*/
|
|
124
|
+
/**
|
|
125
|
+
* The four saved documents, by the key the page knows them by.
|
|
126
|
+
*
|
|
127
|
+
* Exported as the raw JSON rather than as documents, because a document is mutable and every caller
|
|
128
|
+
* that opens one wants its own.
|
|
129
|
+
*
|
|
130
|
+
* @type {Object<string, object>}
|
|
131
|
+
*/
|
|
132
|
+
export const BONFIRE_EFFECT_JSON: {
|
|
133
|
+
[x: string]: object;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* The order the page offers them in, which is also least to most incidental: the flame is the thing,
|
|
137
|
+
* and the soot is the detail that makes it read.
|
|
138
|
+
*
|
|
139
|
+
* @type {string[]}
|
|
140
|
+
*/
|
|
141
|
+
export const BONFIRE_EFFECT_NAMES: string[];
|
|
142
|
+
import { NodeDescriptionVisualRegistry } from "../../../core/model/node-graph/visual/NodeDescriptionVisualRegistry.js";
|
|
143
|
+
import { ParticleEffectDocument } from "../../../../editor/particles/effect/ParticleEffectDocument.js";
|
|
144
|
+
import { ParticleEffect } from "../../../engine/graphics/ecs/particles/ParticleEffect.js";
|
|
145
|
+
//# sourceMappingURL=bonfire_effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bonfire_effects.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_ecs/bonfire_effects.js"],"names":[],"mappings":"AAyGA;;GAEG;AACH,yCAFa,6BAA6B,CAUzC;AAED;;;;;;;GAOG;AACH,8CALW,MAAM,SACN,MAAM,GAEJ,sBAAsB,CAIlC;AAED;;;;GAIG;AACH;QAFoB,MAAM,GAAE,sBAAsB;EAIjD;AAED;;;;;;;;;;;GAWG;AACH,mDALW,sBAAsB,YACtB,MAAM,GAAC,IAAI,GAET,cAAc,GAAC,IAAI,CAY/B;AAED;;;;;;;;;GASG;AACH;IANkC,KAAK,GAA5B,MAAM,GAAC,IAAI;IACY,KAAK,GAA5B,MAAM,GAAC,IAAI;IACY,KAAK,GAA5B,MAAM,GAAC,IAAI;IACY,IAAI,GAA3B,MAAM,GAAC,IAAI;IACT;IAAC,KAAK,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAC,CAcxG;AAED;;;;;;GAMG;AACH,+CAHW,MAAM;QACG,MAAM,GAAE,MAAM,GAAC,IAAI;EAStC;AA7LD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH;;;;;;;GAOG;AACH;QAFiB,MAAM,GAAE,MAAM;EAO5B;AAEH;;;;;GAKG;AACH,mCAFU,MAAM,EAAE,CAEsE;8CA1FjF,wEAAwE;uCAExC,+DAA+D;+BADvE,0DAA0D"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NodeDescriptionVisualRegistry
|
|
3
|
+
} from "../../../core/model/node-graph/visual/NodeDescriptionVisualRegistry.js";
|
|
4
|
+
import { ParticleEffect } from "../../../engine/graphics/ecs/particles/ParticleEffect.js";
|
|
5
|
+
import { ParticleEffectDocument } from "../../../../editor/particles/effect/ParticleEffectDocument.js";
|
|
6
|
+
import { PARTICLE_NODE_REGISTRY } from "../../renderer/particles/graph/ParticleNodeRegistry.js";
|
|
7
|
+
import "../../renderer/particles/graph/particle_node_library.js";
|
|
8
|
+
import flame_json from "./effects/flame.json";
|
|
9
|
+
import smoke_json from "./effects/smoke.json";
|
|
10
|
+
import embers_json from "./effects/embers.json";
|
|
11
|
+
import soot_json from "./effects/soot.json";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A bonfire, as four saved particle effects.
|
|
15
|
+
*
|
|
16
|
+
* Flame, smoke, embers and soot: four effects that read as one fire because they were authored
|
|
17
|
+
* against one another — the smoke starts where the flame stops being bright, the embers leave from
|
|
18
|
+
* inside the flame core and outlive it, and the soot rides the same plume as the smoke and then
|
|
19
|
+
* falls out of it.
|
|
20
|
+
*
|
|
21
|
+
* ## They are documents, not code
|
|
22
|
+
*
|
|
23
|
+
* Each is a {@link ParticleEffectDocument} saved as JSON in `effects/`: the per-particle attribute
|
|
24
|
+
* declarations, the INIT and UPDATE node graphs with the positions they were arranged at, and the
|
|
25
|
+
* emitter settings. That is the whole effect, and it is what the editor opens — press `E` on the
|
|
26
|
+
* page, change something, download the result, drop it over the file, reload. There is no porting
|
|
27
|
+
* step and no authoring code to keep in step with what was edited, because the file the editor
|
|
28
|
+
* writes **is** the effect.
|
|
29
|
+
*
|
|
30
|
+
* Which is the same claim `ParticleEffectSerializationAdapter` makes about a level: the VM's
|
|
31
|
+
* instruction set is a durable format, so an effect is content. These documents keep the *graph*
|
|
32
|
+
* rather than the bytecode because they are the authoring form — a level saves the compiled program,
|
|
33
|
+
* an author's file saves what it was compiled from.
|
|
34
|
+
*
|
|
35
|
+
* ## How each effect is built
|
|
36
|
+
*
|
|
37
|
+
* Every one of them has the same skeleton, which is the skeleton of nearly every particle effect:
|
|
38
|
+
*
|
|
39
|
+
* - **INIT** places the particle, gives it a velocity, and rolls its lifetime, its base size and a
|
|
40
|
+
* personal `seed`. There is no built-in age or lifetime — the effect declares what it needs, which
|
|
41
|
+
* is why `age` and `life` are ordinary attributes.
|
|
42
|
+
* - **UPDATE** advances the age, kills the particle when it runs out, integrates the forces, and
|
|
43
|
+
* drives the drawn size and colour from `age / life` through a ramp — a curve for the size and a
|
|
44
|
+
* gradient for the colour, both written into the program as arithmetic rather than sampled from a
|
|
45
|
+
* table the host has to keep in step.
|
|
46
|
+
*
|
|
47
|
+
* Two things are worth knowing before editing one:
|
|
48
|
+
*
|
|
49
|
+
* - **One store per attribute per phase.** Every store runs and nothing orders them, so a second
|
|
50
|
+
* store does not refine the first, it replaces it — which is how the smoke's drag silently ate its
|
|
51
|
+
* own velocity integration until the editor's validator was pointed at it. The graph editor calls
|
|
52
|
+
* that an error; nothing at runtime does.
|
|
53
|
+
* - **The register budget is shared.** The VM keeps its file in shader registers, and one program
|
|
54
|
+
* needing more than `PARTICLE_VM_FAST_REGISTER_SLOTS` swaps emit and simulate for storage-backed
|
|
55
|
+
* variants *for the whole system* — so an effect that grows past it slows down every effect in the
|
|
56
|
+
* scene rather than itself. `bonfire.spec.js` pins it.
|
|
57
|
+
*
|
|
58
|
+
* The turbulence in all four is three decorrelated `simplex3` samples read as a vector, at a point
|
|
59
|
+
* that drifts with time so the field scrolls through the rising column. Deliberately not the
|
|
60
|
+
* library's `curl3`, which is twelve samples for a divergence-free field: over a metre of flame
|
|
61
|
+
* nobody can tell, and the budget above is shared.
|
|
62
|
+
*
|
|
63
|
+
* ## Lighting
|
|
64
|
+
*
|
|
65
|
+
* None of these opt into `EMITTER_FLAG.LIGHTING`, and that is not an oversight: the shipped lighting
|
|
66
|
+
* hook is `chunk_particle_lighting_unlit`, a pass-through, so the flag would cost a bit in a record
|
|
67
|
+
* and change no pixel. The smoke and the soot are *painted* lit instead — their gradients carry the
|
|
68
|
+
* fire's warmth low down where the fire would light them and the moon's blue higher up, over the
|
|
69
|
+
* particle's own age. When the lit hook lands the gradients are what to reconsider.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The four saved documents, by the key the page knows them by.
|
|
74
|
+
*
|
|
75
|
+
* Exported as the raw JSON rather than as documents, because a document is mutable and every caller
|
|
76
|
+
* that opens one wants its own.
|
|
77
|
+
*
|
|
78
|
+
* @type {Object<string, object>}
|
|
79
|
+
*/
|
|
80
|
+
export const BONFIRE_EFFECT_JSON = Object.freeze({
|
|
81
|
+
flame: flame_json,
|
|
82
|
+
smoke: smoke_json,
|
|
83
|
+
embers: embers_json,
|
|
84
|
+
soot: soot_json,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The order the page offers them in, which is also least to most incidental: the flame is the thing,
|
|
89
|
+
* and the soot is the detail that makes it read.
|
|
90
|
+
*
|
|
91
|
+
* @type {string[]}
|
|
92
|
+
*/
|
|
93
|
+
export const BONFIRE_EFFECT_NAMES = Object.freeze(["flame", "smoke", "embers", "soot"]);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A visual registry covering every particle node type, which a document needs in order to size the
|
|
97
|
+
* nodes it loads.
|
|
98
|
+
*
|
|
99
|
+
* One, shared and built on demand: it is a function of the node library rather than of any document,
|
|
100
|
+
* and building it per document would generate a visual description per node type per effect.
|
|
101
|
+
*
|
|
102
|
+
* @type {NodeDescriptionVisualRegistry|null}
|
|
103
|
+
*/
|
|
104
|
+
let node_visuals = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @returns {NodeDescriptionVisualRegistry}
|
|
108
|
+
*/
|
|
109
|
+
export function particle_node_visuals() {
|
|
110
|
+
if (node_visuals === null) {
|
|
111
|
+
node_visuals = new NodeDescriptionVisualRegistry();
|
|
112
|
+
|
|
113
|
+
PARTICLE_NODE_REGISTRY.getNodesAsArray().forEach(description => node_visuals.generate(description));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return node_visuals;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Open one of the saved effects as a document.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} name see {@link BONFIRE_EFFECT_NAMES}
|
|
123
|
+
* @param {object} [json] an effect to open instead of the saved one — what the page hands over when
|
|
124
|
+
* somebody imports a file they downloaded earlier
|
|
125
|
+
* @returns {ParticleEffectDocument}
|
|
126
|
+
*/
|
|
127
|
+
export function bonfire_effect_document(name, json = BONFIRE_EFFECT_JSON[name]) {
|
|
128
|
+
return new ParticleEffectDocument().fromJSON(json, particle_node_visuals());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* All four, as documents.
|
|
133
|
+
*
|
|
134
|
+
* @returns {Object<string, ParticleEffectDocument>}
|
|
135
|
+
*/
|
|
136
|
+
export function bonfire_effect_documents() {
|
|
137
|
+
return Object.fromEntries(BONFIRE_EFFECT_NAMES.map(name => [name, bonfire_effect_document(name)]));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The ECS component a document describes, compiled.
|
|
142
|
+
*
|
|
143
|
+
* `null` when the effect does not compile. A graph an author has half-rewired is the ordinary case
|
|
144
|
+
* for that, so it is the caller's business to say so rather than this one's to throw — which is why
|
|
145
|
+
* the page can leave the previous effect burning while the editor shows the error.
|
|
146
|
+
*
|
|
147
|
+
* @param {ParticleEffectDocument} document
|
|
148
|
+
* @param {string|null} [texture] image URL to draw with, used when the document names none of its
|
|
149
|
+
* own. The page's sprites are generated at runtime, so they cannot be in the file
|
|
150
|
+
* @returns {ParticleEffect|null}
|
|
151
|
+
*/
|
|
152
|
+
export function bonfire_effect_component(document, texture = null) {
|
|
153
|
+
const compilation = document.compile();
|
|
154
|
+
|
|
155
|
+
if (compilation.program === null || compilation.layout === null) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const config = document.emitterConfig(compilation);
|
|
160
|
+
|
|
161
|
+
return ParticleEffect.from({ ...config, texture: config.texture ?? texture });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The four effects, ready to go on entities.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} [textures] image URLs to draw with, `null` for an untextured (flat quad) effect
|
|
168
|
+
* @param {string|null} [textures.flame]
|
|
169
|
+
* @param {string|null} [textures.smoke]
|
|
170
|
+
* @param {string|null} [textures.ember]
|
|
171
|
+
* @param {string|null} [textures.soot]
|
|
172
|
+
* @returns {{flame: ParticleEffect, smoke: ParticleEffect, embers: ParticleEffect, soot: ParticleEffect}}
|
|
173
|
+
*/
|
|
174
|
+
export function bonfire_effects(textures = {}) {
|
|
175
|
+
const sprites = bonfire_sprite_urls(textures);
|
|
176
|
+
|
|
177
|
+
return Object.fromEntries(BONFIRE_EFFECT_NAMES.map(name => {
|
|
178
|
+
const component = bonfire_effect_component(bonfire_effect_document(name), sprites[name]);
|
|
179
|
+
|
|
180
|
+
if (component === null) {
|
|
181
|
+
throw new Error(`the saved '${name}' effect does not compile — see effects/${name}.json`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return [name, component];
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The sprite URLs keyed the way the effects are, so the page and the editor agree on which image
|
|
190
|
+
* belongs to which effect. `bonfire_sprites()` names the ember one in the singular.
|
|
191
|
+
*
|
|
192
|
+
* @param {object} [textures]
|
|
193
|
+
* @returns {Object<string, string|null>}
|
|
194
|
+
*/
|
|
195
|
+
export function bonfire_sprite_urls(textures = {}) {
|
|
196
|
+
return {
|
|
197
|
+
flame: textures.flame ?? null,
|
|
198
|
+
smoke: textures.smoke ?? null,
|
|
199
|
+
embers: textures.ember ?? null,
|
|
200
|
+
soot: textures.soot ?? null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} BonfireSprites
|
|
3
|
+
* @property {string} flame
|
|
4
|
+
* @property {string} smoke
|
|
5
|
+
* @property {string} ember
|
|
6
|
+
* @property {string} soot
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Draw the four sprites.
|
|
10
|
+
*
|
|
11
|
+
* Browser only — it draws through a `<canvas>`. The effects take their URLs as arguments precisely
|
|
12
|
+
* so that everything else about them can be built and tested without one.
|
|
13
|
+
*
|
|
14
|
+
* @returns {BonfireSprites}
|
|
15
|
+
*/
|
|
16
|
+
export function bonfire_sprites(): BonfireSprites;
|
|
17
|
+
export type BonfireSprites = {
|
|
18
|
+
flame: string;
|
|
19
|
+
smoke: string;
|
|
20
|
+
ember: string;
|
|
21
|
+
soot: string;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=bonfire_sprites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bonfire_sprites.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_ecs/bonfire_sprites.js"],"names":[],"mappings":"AAmBA;;;;;;GAMG;AAEH;;;;;;;GAOG;AACH,mCAFa,cAAc,CAS1B;;WArBa,MAAM;WACN,MAAM;WACN,MAAM;UACN,MAAM"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four sprites the bonfire draws with, generated rather than shipped.
|
|
3
|
+
*
|
|
4
|
+
* They are PNG `data:` URLs, which is the point of doing it this way: the effects name a URL like
|
|
5
|
+
* any other effect would, `AssetManager` fetches and decodes it like any other image, and
|
|
6
|
+
* `GPUParticleEmitterSystem` packs it into the scene's sprite atlas like any other sprite. Nothing
|
|
7
|
+
* about the path is special-cased for a generated image, so the page exercises the real one — and
|
|
8
|
+
* it does it without a binary asset in the repository, which is what a playground under
|
|
9
|
+
* `test_assets/`-free source control needs.
|
|
10
|
+
*
|
|
11
|
+
* Generation is deterministic: one seeded generator, so the same page always draws the same fire.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Size of the flame and smoke sprites, in texels. Big enough to be blurred, small enough to pack. */
|
|
15
|
+
const LARGE = 128;
|
|
16
|
+
|
|
17
|
+
/** Size of the ember and soot sprites. They are drawn a few pixels across; detail here is wasted. */
|
|
18
|
+
const SMALL = 32;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} BonfireSprites
|
|
22
|
+
* @property {string} flame
|
|
23
|
+
* @property {string} smoke
|
|
24
|
+
* @property {string} ember
|
|
25
|
+
* @property {string} soot
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Draw the four sprites.
|
|
30
|
+
*
|
|
31
|
+
* Browser only — it draws through a `<canvas>`. The effects take their URLs as arguments precisely
|
|
32
|
+
* so that everything else about them can be built and tested without one.
|
|
33
|
+
*
|
|
34
|
+
* @returns {BonfireSprites}
|
|
35
|
+
*/
|
|
36
|
+
export function bonfire_sprites() {
|
|
37
|
+
return {
|
|
38
|
+
flame: flame_sprite(),
|
|
39
|
+
smoke: smoke_sprite(),
|
|
40
|
+
ember: ember_sprite(),
|
|
41
|
+
soot: soot_sprite(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A soft round puff with a bright core: what a lick of flame is made of, additively.
|
|
47
|
+
*
|
|
48
|
+
* The falloff is `(1 - r)^2` rather than linear, so a hundred overlapping puffs sum into a body with
|
|
49
|
+
* a hot centre instead of a uniform blob — the shape of the falloff is what the fire's silhouette
|
|
50
|
+
* comes out of, far more than the shape of any one particle.
|
|
51
|
+
*
|
|
52
|
+
* @returns {string} PNG data URL
|
|
53
|
+
*/
|
|
54
|
+
function flame_sprite() {
|
|
55
|
+
return draw(LARGE, (x, y) => {
|
|
56
|
+
const r = Math.hypot(x, y);
|
|
57
|
+
|
|
58
|
+
if (r >= 1) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const falloff = (1 - r) * (1 - r);
|
|
63
|
+
|
|
64
|
+
return falloff * (0.55 + 0.45 * (1 - r));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A cloudy puff: a radial mask with several octaves of value noise cut into it, so a smoke particle
|
|
70
|
+
* has structure of its own and a column of them does not read as a stack of discs.
|
|
71
|
+
*
|
|
72
|
+
* @returns {string} PNG data URL
|
|
73
|
+
*/
|
|
74
|
+
function smoke_sprite() {
|
|
75
|
+
const noise = value_noise_field(LARGE, 0x51ce);
|
|
76
|
+
|
|
77
|
+
return draw(LARGE, (x, y, ix, iy) => {
|
|
78
|
+
const r = Math.hypot(x, y);
|
|
79
|
+
|
|
80
|
+
if (r >= 1) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// A long shoulder rather than a plateau, and noise deep enough to break what is left of
|
|
85
|
+
// the circle: a smoke particle whose own outline is visible is a smoke particle you can
|
|
86
|
+
// count, and a column of countable puffs is the thing that reads as "particles" rather
|
|
87
|
+
// than as smoke.
|
|
88
|
+
const mask = smoothstep(1, 0.05, r);
|
|
89
|
+
const detail = 0.18 + 0.82 * noise[iy * LARGE + ix];
|
|
90
|
+
|
|
91
|
+
return mask * mask * detail;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A tight bright dot with a faint halo — a cinder, and the glow around it.
|
|
97
|
+
*
|
|
98
|
+
* @returns {string} PNG data URL
|
|
99
|
+
*/
|
|
100
|
+
function ember_sprite() {
|
|
101
|
+
return draw(SMALL, (x, y) => {
|
|
102
|
+
const r = Math.hypot(x, y);
|
|
103
|
+
|
|
104
|
+
if (r >= 1) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const core = smoothstep(0.45, 0.0, r);
|
|
109
|
+
const halo = (1 - r) * (1 - r) * 0.35;
|
|
110
|
+
|
|
111
|
+
return Math.min(1, core + halo);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* An irregular flake with soft edges: soot is not round, and a flake that tumbles has to look
|
|
117
|
+
* different from one moment to the next for the tumble to be visible at all.
|
|
118
|
+
*
|
|
119
|
+
* @returns {string} PNG data URL
|
|
120
|
+
*/
|
|
121
|
+
function soot_sprite() {
|
|
122
|
+
const random = seeded_random(0x50075);
|
|
123
|
+
const lobes = Array.from({ length: 5 }, () => 0.55 + random() * 0.45);
|
|
124
|
+
|
|
125
|
+
return draw(SMALL, (x, y) => {
|
|
126
|
+
const r = Math.hypot(x, y);
|
|
127
|
+
const angle = Math.atan2(y, x);
|
|
128
|
+
|
|
129
|
+
// A radius that varies with the angle, interpolated around the five lobes.
|
|
130
|
+
const t = (angle / (Math.PI * 2) + 1) % 1 * lobes.length;
|
|
131
|
+
const i = Math.floor(t);
|
|
132
|
+
const edge = lerp(lobes[i], lobes[(i + 1) % lobes.length], t - i);
|
|
133
|
+
|
|
134
|
+
return smoothstep(edge, edge * 0.55, r);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Rasterize an alpha function into a white PNG.
|
|
140
|
+
*
|
|
141
|
+
* White because the colour is the particle's — every one of these effects drives `color` from a
|
|
142
|
+
* gradient over the particle's age, and the sprite's job is the shape it comes out in. `x` and `y`
|
|
143
|
+
* are in `[-1, 1]` at texel centres; `ix` and `iy` are the texel.
|
|
144
|
+
*
|
|
145
|
+
* @param {number} size
|
|
146
|
+
* @param {function(number, number, number, number): number} alpha
|
|
147
|
+
* @returns {string} PNG data URL
|
|
148
|
+
*/
|
|
149
|
+
function draw(size, alpha) {
|
|
150
|
+
const canvas = document.createElement("canvas");
|
|
151
|
+
|
|
152
|
+
canvas.width = size;
|
|
153
|
+
canvas.height = size;
|
|
154
|
+
|
|
155
|
+
const context = canvas.getContext("2d");
|
|
156
|
+
const image = context.createImageData(size, size);
|
|
157
|
+
const data = image.data;
|
|
158
|
+
|
|
159
|
+
for (let iy = 0; iy < size; iy++) {
|
|
160
|
+
for (let ix = 0; ix < size; ix++) {
|
|
161
|
+
const x = (ix + 0.5) / size * 2 - 1;
|
|
162
|
+
const y = (iy + 0.5) / size * 2 - 1;
|
|
163
|
+
const offset = (iy * size + ix) * 4;
|
|
164
|
+
|
|
165
|
+
data[offset] = 255;
|
|
166
|
+
data[offset + 1] = 255;
|
|
167
|
+
data[offset + 2] = 255;
|
|
168
|
+
data[offset + 3] = Math.round(clamp(alpha(x, y, ix, iy), 0, 1) * 255);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
context.putImageData(image, 0, 0);
|
|
173
|
+
|
|
174
|
+
return canvas.toDataURL("image/png");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Three octaves of tiling value noise in `[0, 1]`, one value per texel.
|
|
179
|
+
*
|
|
180
|
+
* Tiling, because the field is sampled over the whole sprite and a seam across the middle of a
|
|
181
|
+
* smoke puff is the one artefact that would be visible from any distance.
|
|
182
|
+
*
|
|
183
|
+
* @param {number} size
|
|
184
|
+
* @param {number} seed
|
|
185
|
+
* @returns {Float32Array}
|
|
186
|
+
*/
|
|
187
|
+
function value_noise_field(size, seed) {
|
|
188
|
+
const field = new Float32Array(size * size);
|
|
189
|
+
|
|
190
|
+
let amplitude = 1;
|
|
191
|
+
let total = 0;
|
|
192
|
+
|
|
193
|
+
for (const period of [4, 8, 16]) {
|
|
194
|
+
const lattice = lattice_values(period, seed + period);
|
|
195
|
+
|
|
196
|
+
for (let y = 0; y < size; y++) {
|
|
197
|
+
for (let x = 0; x < size; x++) {
|
|
198
|
+
field[y * size + x] += amplitude * sample_lattice(
|
|
199
|
+
lattice, period, x / size * period, y / size * period
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
total += amplitude;
|
|
205
|
+
amplitude *= 0.55;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < field.length; i++) {
|
|
209
|
+
field[i] /= total;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return field;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {number} period
|
|
217
|
+
* @param {number} seed
|
|
218
|
+
* @returns {Float32Array} `period * period` values in `[0, 1]`
|
|
219
|
+
*/
|
|
220
|
+
function lattice_values(period, seed) {
|
|
221
|
+
const random = seeded_random(seed);
|
|
222
|
+
const values = new Float32Array(period * period);
|
|
223
|
+
|
|
224
|
+
for (let i = 0; i < values.length; i++) {
|
|
225
|
+
values[i] = random();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return values;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Smooth bilinear lookup, wrapping at the period so the field tiles.
|
|
233
|
+
*
|
|
234
|
+
* @param {Float32Array} lattice
|
|
235
|
+
* @param {number} period
|
|
236
|
+
* @param {number} x
|
|
237
|
+
* @param {number} y
|
|
238
|
+
* @returns {number}
|
|
239
|
+
*/
|
|
240
|
+
function sample_lattice(lattice, period, x, y) {
|
|
241
|
+
const x0 = Math.floor(x);
|
|
242
|
+
const y0 = Math.floor(y);
|
|
243
|
+
const fx = smootherstep(x - x0);
|
|
244
|
+
const fy = smootherstep(y - y0);
|
|
245
|
+
|
|
246
|
+
const at = (ix, iy) => lattice[((iy % period) + period) % period * period + ((ix % period) + period) % period];
|
|
247
|
+
|
|
248
|
+
return lerp(
|
|
249
|
+
lerp(at(x0, y0), at(x0 + 1, y0), fx),
|
|
250
|
+
lerp(at(x0, y0 + 1), at(x0 + 1, y0 + 1), fx),
|
|
251
|
+
fy
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* mulberry32 — small, fast, and good enough for texture noise. Named so that "deterministic" above
|
|
257
|
+
* is a claim about a specific generator rather than about `Math.random`.
|
|
258
|
+
*
|
|
259
|
+
* @param {number} seed
|
|
260
|
+
* @returns {function(): number} uniform in `[0, 1)`
|
|
261
|
+
*/
|
|
262
|
+
function seeded_random(seed) {
|
|
263
|
+
let state = seed >>> 0;
|
|
264
|
+
|
|
265
|
+
return () => {
|
|
266
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
267
|
+
|
|
268
|
+
let t = state;
|
|
269
|
+
|
|
270
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
271
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
272
|
+
|
|
273
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @param {number} edge0
|
|
279
|
+
* @param {number} edge1
|
|
280
|
+
* @param {number} x
|
|
281
|
+
* @returns {number}
|
|
282
|
+
*/
|
|
283
|
+
function smoothstep(edge0, edge1, x) {
|
|
284
|
+
const t = clamp((x - edge0) / (edge1 - edge0), 0, 1);
|
|
285
|
+
|
|
286
|
+
return t * t * (3 - 2 * t);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param {number} t
|
|
291
|
+
* @returns {number}
|
|
292
|
+
*/
|
|
293
|
+
function smootherstep(t) {
|
|
294
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @param {number} a
|
|
299
|
+
* @param {number} b
|
|
300
|
+
* @param {number} t
|
|
301
|
+
* @returns {number}
|
|
302
|
+
*/
|
|
303
|
+
function lerp(a, b, t) {
|
|
304
|
+
return a + (b - a) * t;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @param {number} value
|
|
309
|
+
* @param {number} low
|
|
310
|
+
* @param {number} high
|
|
311
|
+
* @returns {number}
|
|
312
|
+
*/
|
|
313
|
+
function clamp(value, low, high) {
|
|
314
|
+
return value < low ? low : (value > high ? high : value);
|
|
315
|
+
}
|