gloop-sim 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -2
- package/dist/assert.d.ts +1 -0
- package/dist/assert.js +6 -0
- package/dist/assert.js.map +1 -0
- package/dist/backend/cpu.d.ts +2 -0
- package/dist/backend/cpu.js +23 -0
- package/dist/backend/cpu.js.map +1 -0
- package/dist/backend/gpu.d.ts +2 -0
- package/dist/backend/gpu.js +145 -0
- package/dist/backend/gpu.js.map +1 -0
- package/dist/backend/index.d.ts +14 -0
- package/dist/backend/index.js +23 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/device.d.ts +11 -0
- package/dist/device.js +66 -0
- package/dist/device.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/initializer/fixed.d.ts +2 -0
- package/dist/initializer/fixed.js +12 -0
- package/dist/initializer/fixed.js.map +1 -0
- package/dist/initializer/index.d.ts +14 -0
- package/dist/initializer/index.js +23 -0
- package/dist/initializer/index.js.map +1 -0
- package/dist/initializer/random.d.ts +2 -0
- package/dist/initializer/random.js +16 -0
- package/dist/initializer/random.js.map +1 -0
- package/dist/integrator/euler.d.ts +2 -0
- package/dist/integrator/euler.js +34 -0
- package/dist/integrator/euler.js.map +1 -0
- package/dist/integrator/index.d.ts +14 -0
- package/dist/integrator/index.js +23 -0
- package/dist/integrator/index.js.map +1 -0
- package/dist/integrator/semi-implicit-euler.d.ts +2 -0
- package/dist/integrator/semi-implicit-euler.js +36 -0
- package/dist/integrator/semi-implicit-euler.js.map +1 -0
- package/dist/model/elastic-network/cpu.d.ts +2 -0
- package/dist/model/elastic-network/cpu.js +90 -0
- package/dist/model/elastic-network/cpu.js.map +1 -0
- package/dist/model/elastic-network/gpu.d.ts +2 -0
- package/dist/model/elastic-network/gpu.js +114 -0
- package/dist/model/elastic-network/gpu.js.map +1 -0
- package/dist/model/elastic-network/index.d.ts +2 -0
- package/dist/model/elastic-network/index.js +69 -0
- package/dist/model/elastic-network/index.js.map +1 -0
- package/dist/model/index.d.ts +15 -0
- package/dist/model/index.js +24 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/inertial.d.ts +2 -0
- package/dist/model/inertial.js +17 -0
- package/dist/model/inertial.js.map +1 -0
- package/dist/render/backend/channels.d.ts +17 -0
- package/dist/render/backend/channels.js +35 -0
- package/dist/render/backend/channels.js.map +1 -0
- package/dist/render/backend/cloud.d.ts +77 -0
- package/dist/render/backend/cloud.js +29 -0
- package/dist/render/backend/cloud.js.map +1 -0
- package/dist/render/backend/compile.d.ts +31 -0
- package/dist/render/backend/compile.js +73 -0
- package/dist/render/backend/compile.js.map +1 -0
- package/dist/render/backend/glyph.d.ts +8 -0
- package/dist/render/backend/glyph.js +38 -0
- package/dist/render/backend/glyph.js.map +1 -0
- package/dist/render/backend/glyphs.d.ts +6 -0
- package/dist/render/backend/glyphs.js +146 -0
- package/dist/render/backend/glyphs.js.map +1 -0
- package/dist/render/backend/gpu.d.ts +12 -0
- package/dist/render/backend/gpu.js +278 -0
- package/dist/render/backend/gpu.js.map +1 -0
- package/dist/render/backend/material.d.ts +30 -0
- package/dist/render/backend/material.js +57 -0
- package/dist/render/backend/material.js.map +1 -0
- package/dist/render/backend/particles.d.ts +5 -0
- package/dist/render/backend/particles.js +22 -0
- package/dist/render/backend/particles.js.map +1 -0
- package/dist/render/backend/post.d.ts +29 -0
- package/dist/render/backend/post.js +77 -0
- package/dist/render/backend/post.js.map +1 -0
- package/dist/render/backend/solids.d.ts +10 -0
- package/dist/render/backend/solids.js +134 -0
- package/dist/render/backend/solids.js.map +1 -0
- package/dist/render/camera.d.ts +73 -0
- package/dist/render/camera.js +32 -0
- package/dist/render/camera.js.map +1 -0
- package/dist/render/channel.d.ts +25 -0
- package/dist/render/channel.js +9 -0
- package/dist/render/channel.js.map +1 -0
- package/dist/render/color.d.ts +34 -0
- package/dist/render/color.js +56 -0
- package/dist/render/color.js.map +1 -0
- package/dist/render/effect.d.ts +81 -0
- package/dist/render/effect.js +30 -0
- package/dist/render/effect.js.map +1 -0
- package/dist/render/frame.d.ts +60 -0
- package/dist/render/frame.js +52 -0
- package/dist/render/frame.js.map +1 -0
- package/dist/render/index.d.ts +12 -0
- package/dist/render/index.js +2 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/layer.d.ts +90 -0
- package/dist/render/layer.js +54 -0
- package/dist/render/layer.js.map +1 -0
- package/dist/render/ramp.d.ts +93 -0
- package/dist/render/ramp.js +120 -0
- package/dist/render/ramp.js.map +1 -0
- package/dist/render/sampler.d.ts +34 -0
- package/dist/render/sampler.js +53 -0
- package/dist/render/sampler.js.map +1 -0
- package/dist/render/signal.d.ts +66 -0
- package/dist/render/signal.js +51 -0
- package/dist/render/signal.js.map +1 -0
- package/dist/render/sources.d.ts +33 -0
- package/dist/render/sources.js +46 -0
- package/dist/render/sources.js.map +1 -0
- package/dist/render/springs/anchors.d.ts +37 -0
- package/dist/render/springs/anchors.js +34 -0
- package/dist/render/springs/anchors.js.map +1 -0
- package/dist/render/springs/construction.d.ts +30 -0
- package/dist/render/springs/construction.js +66 -0
- package/dist/render/springs/construction.js.map +1 -0
- package/dist/render/springs/impulses.d.ts +45 -0
- package/dist/render/springs/impulses.js +144 -0
- package/dist/render/springs/impulses.js.map +1 -0
- package/dist/render/springs/index.d.ts +9 -0
- package/dist/render/springs/index.js +2 -0
- package/dist/render/springs/index.js.map +1 -0
- package/dist/render/springs/link.d.ts +51 -0
- package/dist/render/springs/link.js +42 -0
- package/dist/render/springs/link.js.map +1 -0
- package/dist/render/springs/links.d.ts +70 -0
- package/dist/render/springs/links.js +210 -0
- package/dist/render/springs/links.js.map +1 -0
- package/dist/render/springs/palette.d.ts +36 -0
- package/dist/render/springs/palette.js +22 -0
- package/dist/render/springs/palette.js.map +1 -0
- package/dist/render/springs/preset.d.ts +65 -0
- package/dist/render/springs/preset.js +114 -0
- package/dist/render/springs/preset.js.map +1 -0
- package/dist/render/springs/segment.d.ts +40 -0
- package/dist/render/springs/segment.js +81 -0
- package/dist/render/springs/segment.js.map +1 -0
- package/dist/render/springs/source.d.ts +35 -0
- package/dist/render/springs/source.js +70 -0
- package/dist/render/springs/source.js.map +1 -0
- package/dist/render/springs/springs.d.ts +40 -0
- package/dist/render/springs/springs.js +34 -0
- package/dist/render/springs/springs.js.map +1 -0
- package/dist/render/springs/strain.d.ts +21 -0
- package/dist/render/springs/strain.js +39 -0
- package/dist/render/springs/strain.js.map +1 -0
- package/dist/render/stage.d.ts +96 -0
- package/dist/render/stage.js +2 -0
- package/dist/render/stage.js.map +1 -0
- package/dist/render/view.d.ts +63 -0
- package/dist/render/view.js +63 -0
- package/dist/render/view.js.map +1 -0
- package/dist/swarm.d.ts +5 -0
- package/dist/swarm.js +24 -0
- package/dist/swarm.js.map +1 -0
- package/dist/types.d.ts +290 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/assets/logo.svg +112 -0
- package/package.json +102 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.js","sourceRoot":"","sources":["../../src/render/frame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,MAAM,GAAG,CAAC,CAAC;AAqCjB,MAAM,UAAU;IACe;IAA7B,YAA6B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAErD,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACrC,CAAC;IAED,CAAC,CAAC,QAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IAED,CAAC,CAAC,QAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IAED,CAAC,CAAC,QAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IAED,EAAE,CAAC,QAAgB;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,GAAG,CAAC,QAAgB,EAAE,QAA2C;QAC/D,MAAM,IAAI,GAAG,QAAQ,GAAG,MAAM,CAAC;QAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IAEO,IAAI,CAAC,QAAgB,EAAE,IAAY;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;QACpD,MAAM,CACJ,KAAK,KAAK,SAAS,EACnB,YAAY,QAAQ,4CAA4C,IAAI,CAAC,MAAM,CAAC,MAAM,SAAS,CAC5F,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,MAAM,KAAW,eAAe,CAc/B;AAdD,WAAiB,eAAe;IAC9B;;OAEG;IACH,SAAgB,EAAE,CAAC,MAAoB;QACrC,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAFe,kBAAE,KAEjB,CAAA;IAED;;OAEG;IACH,SAAgB,QAAQ,CAAC,KAAa;QACpC,OAAO,IAAI,YAAY,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;IAC1C,CAAC;IAFe,wBAAQ,WAEvB,CAAA;AACH,CAAC,EAdgB,eAAe,KAAf,eAAe,QAc/B","sourcesContent":["import { assert } from \"@/assert.js\";\n\nconst STRIDE = 3;\n\n/**\n * A view over a position buffer.\n */\nexport interface PackedPositions {\n /**\n * The number of particles the buffer holds.\n */\n readonly count: number;\n\n /**\n * The x coordinate of a particle at the specified index.\n */\n x(particle: number): number;\n\n /**\n * The y coordinate of a particle at the specified index.\n */\n y(particle: number): number;\n\n /**\n * The z coordinate of a particle at the specified index.\n */\n z(particle: number): number;\n\n /**\n * A particle's coordinates as a tuple.\n */\n at(particle: number): readonly [number, number, number];\n\n /**\n * Updates a particle's coordinates from an `[x, y, z]` tuple.\n */\n set(particle: number, position: readonly [number, number, number]): void;\n}\n\nclass PackedView implements PackedPositions {\n constructor(private readonly buffer: Float32Array) {}\n\n get count(): number {\n return this.buffer.length / STRIDE;\n }\n\n x(particle: number): number {\n return this.read(particle, 0);\n }\n\n y(particle: number): number {\n return this.read(particle, 1);\n }\n\n z(particle: number): number {\n return this.read(particle, 2);\n }\n\n at(particle: number): readonly [number, number, number] {\n return [this.x(particle), this.y(particle), this.z(particle)];\n }\n\n set(particle: number, position: readonly [number, number, number]): void {\n const base = particle * STRIDE;\n this.buffer[base] = position[0];\n this.buffer[base + 1] = position[1];\n this.buffer[base + 2] = position[2];\n }\n\n private read(particle: number, axis: number): number {\n const value = this.buffer[particle * STRIDE + axis];\n assert(\n value !== undefined,\n `particle ${particle} is out of range in a position buffer of ${this.buffer.length} floats`,\n );\n return value;\n }\n}\n\nexport namespace PackedPositions {\n /**\n * Wraps a packed position buffer so it can be read and written by particle.\n */\n export function of(buffer: Float32Array): PackedPositions {\n return new PackedView(buffer);\n }\n\n /**\n * Allocates a packed position buffer sized to hold `count` particles.\n */\n export function allocate(count: number): Float32Array<ArrayBuffer> {\n return new Float32Array(count * STRIDE);\n }\n}\n\n/**\n * A snapshot of the simulation state at a given point in time.\n */\nexport interface Frame {\n /**\n * The simulated time of this frame, in seconds.\n */\n readonly time: number;\n\n /**\n * Particle positions as a packed `x, y, z` buffer.\n */\n readonly positions: Float32Array;\n\n /**\n * Per-particle speed (velocity magnitude).\n */\n readonly speeds: Float32Array;\n\n /**\n * The number of particles in the swarm.\n */\n readonly particleCount: number;\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { Camera, OrthographicCamera, PerspectiveCamera, Vec3 } from "../render/camera.js";
|
|
2
|
+
export type { Channel } from "../render/channel.js";
|
|
3
|
+
export type { Color, Rgba } from "../render/color.js";
|
|
4
|
+
export type { BloomEffect, BloomOptions, Effect, VignetteEffect, VignetteOptions } from "../render/effect.js";
|
|
5
|
+
export type { Frame } from "../render/frame.js";
|
|
6
|
+
export type { FlatGlyph, Layer, LayerObject, ParticleGlyph, ParticlesLayer, SolidGlyph } from "../render/layer.js";
|
|
7
|
+
export type { ColorInput, ColorRamp, ColorValue, Constant, Ramp, ScalarInput, ScalarRamp, ScalarValue, Stop, ValueSource, } from "../render/ramp.js";
|
|
8
|
+
export type { Sample, Sampler, SamplerOptions } from "../render/sampler.js";
|
|
9
|
+
export type { Signal, SignalOptions } from "../render/signal.js";
|
|
10
|
+
export type { ParticleSource, Source } from "../render/sources.js";
|
|
11
|
+
export type { RenderBackend, Scene, Stage, StageOptions } from "../render/stage.js";
|
|
12
|
+
export { view } from "../render/view.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export type { Camera, OrthographicCamera, PerspectiveCamera, Vec3 } from \"@/render/camera.js\";\nexport type { Channel } from \"@/render/channel.js\";\nexport type { Color, Rgba } from \"@/render/color.js\";\nexport type { BloomEffect, BloomOptions, Effect, VignetteEffect, VignetteOptions } from \"@/render/effect.js\";\nexport type { Frame } from \"@/render/frame.js\";\nexport type { FlatGlyph, Layer, LayerObject, ParticleGlyph, ParticlesLayer, SolidGlyph } from \"@/render/layer.js\";\nexport type {\n ColorInput,\n ColorRamp,\n ColorValue,\n Constant,\n Ramp,\n ScalarInput,\n ScalarRamp,\n ScalarValue,\n Stop,\n ValueSource,\n} from \"@/render/ramp.js\";\nexport type { Sample, Sampler, SamplerOptions } from \"@/render/sampler.js\";\nexport type { Signal, SignalOptions } from \"@/render/signal.js\";\nexport type { ParticleSource, Source } from \"@/render/sources.js\";\nexport type { RenderBackend, Scene, Stage, StageOptions } from \"@/render/stage.js\";\nexport { view } from \"@/render/view.js\";\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Object3D } from "three/webgpu";
|
|
2
|
+
import type { Frame } from "../render/frame.js";
|
|
3
|
+
import { type ColorInput, type ColorValue, type ScalarInput, type ScalarValue } from "../render/ramp.js";
|
|
4
|
+
import type { ParticleSource, Source } from "../render/sources.js";
|
|
5
|
+
/**
|
|
6
|
+
* A built layer that is added to the scene and updated per frame.
|
|
7
|
+
*/
|
|
8
|
+
export interface LayerObject {
|
|
9
|
+
/**
|
|
10
|
+
* The underlying three.js object that is added to the scene.
|
|
11
|
+
*/
|
|
12
|
+
readonly object: Object3D;
|
|
13
|
+
/**
|
|
14
|
+
* Updates the layer's object with the given frame data.
|
|
15
|
+
*/
|
|
16
|
+
update(frame: Frame): void;
|
|
17
|
+
/**
|
|
18
|
+
* Disposes the layer's resources.
|
|
19
|
+
*/
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
export interface Layer<S extends Source> {
|
|
23
|
+
/**
|
|
24
|
+
* The list this layer draws along.
|
|
25
|
+
*/
|
|
26
|
+
readonly source: S;
|
|
27
|
+
/**
|
|
28
|
+
* Creates the three object and per-frame updater this layer needs.
|
|
29
|
+
*/
|
|
30
|
+
build(): LayerObject;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A glyph drawn as a flat shape that turns to face the camera.
|
|
34
|
+
*/
|
|
35
|
+
export type FlatGlyph = "cross" | "circle" | "diamond" | "point" | "reticle" | "gyroscope";
|
|
36
|
+
/**
|
|
37
|
+
* A glyph drawn as a solid shape, shaded by which way its surface faces the
|
|
38
|
+
* camera.
|
|
39
|
+
*/
|
|
40
|
+
export type SolidGlyph = "sphere" | "octahedron" | "axes" | "gimbal";
|
|
41
|
+
/**
|
|
42
|
+
* The glyph a particle is drawn as, either flat or solid.
|
|
43
|
+
*/
|
|
44
|
+
export type ParticleGlyph = FlatGlyph | SolidGlyph;
|
|
45
|
+
export interface ParticlesLayer extends Layer<ParticleSource> {
|
|
46
|
+
/**
|
|
47
|
+
* The shape of the glyphs used to represent particles.
|
|
48
|
+
*/
|
|
49
|
+
readonly shape: ParticleGlyph;
|
|
50
|
+
/**
|
|
51
|
+
* The color of the particle glyphs.
|
|
52
|
+
*/
|
|
53
|
+
readonly color: ColorValue;
|
|
54
|
+
/**
|
|
55
|
+
* The size of the particle glyphs.
|
|
56
|
+
*/
|
|
57
|
+
readonly size: ScalarValue;
|
|
58
|
+
/**
|
|
59
|
+
* The opacity of the particle glyphs.
|
|
60
|
+
*/
|
|
61
|
+
readonly opacity: ScalarValue;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Converts a scalar input to a constant value if possible, otherwise returns undefined.
|
|
65
|
+
*/
|
|
66
|
+
export declare function constant<S extends Source>(input: ScalarInput<S> | undefined): number | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Verifies that the specified value is a unit scalar (i.e. `0..1`).
|
|
69
|
+
*/
|
|
70
|
+
export declare function verifyIsUnit(value: number | undefined, name: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Verifies that the specified value is a positive scalar (i.e. `> 0`).
|
|
73
|
+
*/
|
|
74
|
+
export declare function verifyIsPositive(value: number | undefined, name: string): void;
|
|
75
|
+
/**
|
|
76
|
+
* Verifies that the specified value is a non-negative scalar (i.e. `>= 0`).
|
|
77
|
+
*/
|
|
78
|
+
export declare function verifyIsNonNegative(value: number | undefined, name: string): void;
|
|
79
|
+
export declare namespace layer {
|
|
80
|
+
/**
|
|
81
|
+
* A particles layer, drawing one glyph per particle.
|
|
82
|
+
*/
|
|
83
|
+
function particles({ from, shape, color, size, opacity, }: {
|
|
84
|
+
from: ParticleSource;
|
|
85
|
+
shape?: ParticleGlyph;
|
|
86
|
+
color: ColorInput;
|
|
87
|
+
size?: ScalarInput;
|
|
88
|
+
opacity?: ScalarInput;
|
|
89
|
+
}): ParticlesLayer;
|
|
90
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
import { buildParticles } from "../render/backend/particles.js";
|
|
3
|
+
import { colorValue, scalarValue, } from "../render/ramp.js";
|
|
4
|
+
/**
|
|
5
|
+
* Converts a scalar input to a constant value if possible, otherwise returns undefined.
|
|
6
|
+
*/
|
|
7
|
+
export function constant(input) {
|
|
8
|
+
return typeof input === "number" ? input : undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Verifies that the specified value is a unit scalar (i.e. `0..1`).
|
|
12
|
+
*/
|
|
13
|
+
export function verifyIsUnit(value, name) {
|
|
14
|
+
if (value !== undefined) {
|
|
15
|
+
assert(value >= 0 && value <= 1, `${name} must be within 0..1, but is ${value}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Verifies that the specified value is a positive scalar (i.e. `> 0`).
|
|
20
|
+
*/
|
|
21
|
+
export function verifyIsPositive(value, name) {
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
assert(Number.isFinite(value) && value > 0, `${name} must be positive and finite, but is ${value}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Verifies that the specified value is a non-negative scalar (i.e. `>= 0`).
|
|
28
|
+
*/
|
|
29
|
+
export function verifyIsNonNegative(value, name) {
|
|
30
|
+
if (value !== undefined) {
|
|
31
|
+
assert(Number.isFinite(value) && value >= 0, `${name} must be non-negative and finite, but is ${value}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export var layer;
|
|
35
|
+
(function (layer) {
|
|
36
|
+
/**
|
|
37
|
+
* A particles layer, drawing one glyph per particle.
|
|
38
|
+
*/
|
|
39
|
+
function particles({ from, shape = "cross", color, size = 2, opacity = 1, }) {
|
|
40
|
+
verifyIsPositive(constant(size), "particles size");
|
|
41
|
+
verifyIsUnit(constant(opacity), "particles opacity");
|
|
42
|
+
const spec = {
|
|
43
|
+
source: from,
|
|
44
|
+
shape,
|
|
45
|
+
color: colorValue(color),
|
|
46
|
+
size: scalarValue(size),
|
|
47
|
+
opacity: scalarValue(opacity),
|
|
48
|
+
build: () => buildParticles(spec),
|
|
49
|
+
};
|
|
50
|
+
return spec;
|
|
51
|
+
}
|
|
52
|
+
layer.particles = particles;
|
|
53
|
+
})(layer || (layer = {}));
|
|
54
|
+
//# sourceMappingURL=layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer.js","sourceRoot":"","sources":["../../src/render/layer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,EAGL,UAAU,EAGV,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAyE1B;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAmB,KAAiC;IAC1E,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB,EAAE,IAAY;IAClE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,gCAAgC,KAAK,EAAE,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAyB,EAAE,IAAY;IACtE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,GAAG,IAAI,wCAAwC,KAAK,EAAE,CAAC,CAAC;IACtG,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAyB,EAAE,IAAY;IACzE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,4CAA4C,KAAK,EAAE,CAAC,CAAC;IAC3G,CAAC;AACH,CAAC;AAED,MAAM,KAAW,KAAK,CA6BrB;AA7BD,WAAiB,KAAK;IACpB;;OAEG;IACH,SAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,GAAG,OAAO,EACf,KAAK,EACL,IAAI,GAAG,CAAC,EACR,OAAO,GAAG,CAAC,GAOZ;QACC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACnD,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACrD,MAAM,IAAI,GAAmB;YAC3B,MAAM,EAAE,IAAI;YACZ,KAAK;YACL,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC;YACxB,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;YACvB,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,KAAK,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC;SAClC,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAxBe,eAAS,YAwBxB,CAAA;AACH,CAAC,EA7BgB,KAAK,KAAL,KAAK,QA6BrB","sourcesContent":["import type { Object3D } from \"three/webgpu\";\nimport { assert } from \"@/assert.js\";\nimport { buildParticles } from \"@/render/backend/particles.js\";\nimport type { Frame } from \"@/render/frame.js\";\nimport {\n type ColorInput,\n type ColorValue,\n colorValue,\n type ScalarInput,\n type ScalarValue,\n scalarValue,\n} from \"@/render/ramp.js\";\nimport type { ParticleSource, Source } from \"@/render/sources.js\";\n\n/**\n * A built layer that is added to the scene and updated per frame.\n */\nexport interface LayerObject {\n /**\n * The underlying three.js object that is added to the scene.\n */\n readonly object: Object3D;\n\n /**\n * Updates the layer's object with the given frame data.\n */\n update(frame: Frame): void;\n\n /**\n * Disposes the layer's resources.\n */\n dispose(): void;\n}\n\nexport interface Layer<S extends Source> {\n /**\n * The list this layer draws along.\n */\n readonly source: S;\n\n /**\n * Creates the three object and per-frame updater this layer needs.\n */\n build(): LayerObject;\n}\n\n/**\n * A glyph drawn as a flat shape that turns to face the camera.\n */\nexport type FlatGlyph = \"cross\" | \"circle\" | \"diamond\" | \"point\" | \"reticle\" | \"gyroscope\";\n\n/**\n * A glyph drawn as a solid shape, shaded by which way its surface faces the\n * camera.\n */\nexport type SolidGlyph = \"sphere\" | \"octahedron\" | \"axes\" | \"gimbal\";\n\n/**\n * The glyph a particle is drawn as, either flat or solid.\n */\nexport type ParticleGlyph = FlatGlyph | SolidGlyph;\n\nexport interface ParticlesLayer extends Layer<ParticleSource> {\n /**\n * The shape of the glyphs used to represent particles.\n */\n readonly shape: ParticleGlyph;\n\n /**\n * The color of the particle glyphs.\n */\n readonly color: ColorValue;\n\n /**\n * The size of the particle glyphs.\n */\n readonly size: ScalarValue;\n\n /**\n * The opacity of the particle glyphs.\n */\n readonly opacity: ScalarValue;\n}\n\n/**\n * Converts a scalar input to a constant value if possible, otherwise returns undefined.\n */\nexport function constant<S extends Source>(input: ScalarInput<S> | undefined): number | undefined {\n return typeof input === \"number\" ? input : undefined;\n}\n\n/**\n * Verifies that the specified value is a unit scalar (i.e. `0..1`).\n */\nexport function verifyIsUnit(value: number | undefined, name: string): void {\n if (value !== undefined) {\n assert(value >= 0 && value <= 1, `${name} must be within 0..1, but is ${value}`);\n }\n}\n\n/**\n * Verifies that the specified value is a positive scalar (i.e. `> 0`).\n */\nexport function verifyIsPositive(value: number | undefined, name: string): void {\n if (value !== undefined) {\n assert(Number.isFinite(value) && value > 0, `${name} must be positive and finite, but is ${value}`);\n }\n}\n\n/**\n * Verifies that the specified value is a non-negative scalar (i.e. `>= 0`).\n */\nexport function verifyIsNonNegative(value: number | undefined, name: string): void {\n if (value !== undefined) {\n assert(Number.isFinite(value) && value >= 0, `${name} must be non-negative and finite, but is ${value}`);\n }\n}\n\nexport namespace layer {\n /**\n * A particles layer, drawing one glyph per particle.\n */\n export function particles({\n from,\n shape = \"cross\",\n color,\n size = 2,\n opacity = 1,\n }: {\n from: ParticleSource;\n shape?: ParticleGlyph;\n color: ColorInput;\n size?: ScalarInput;\n opacity?: ScalarInput;\n }): ParticlesLayer {\n verifyIsPositive(constant(size), \"particles size\");\n verifyIsUnit(constant(opacity), \"particles opacity\");\n const spec: ParticlesLayer = {\n source: from,\n shape,\n color: colorValue(color),\n size: scalarValue(size),\n opacity: scalarValue(opacity),\n build: () => buildParticles(spec),\n };\n return spec;\n }\n}\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type Color, type Rgba } from "../render/color.js";
|
|
2
|
+
import { type Signal } from "../render/signal.js";
|
|
3
|
+
import type { Source } from "../render/sources.js";
|
|
4
|
+
/**
|
|
5
|
+
* A single control point for the mapping of a ramp.
|
|
6
|
+
*/
|
|
7
|
+
export interface Stop<T> {
|
|
8
|
+
/**
|
|
9
|
+
* Where on the signal's `0..1` axis this control point sits.
|
|
10
|
+
*/
|
|
11
|
+
readonly at: number;
|
|
12
|
+
/**
|
|
13
|
+
* What the signal's value should be mapped to at this position.
|
|
14
|
+
*/
|
|
15
|
+
readonly value: T;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A mapping of a signal's `0..1` parameter to some kind of output value `T`.
|
|
19
|
+
*/
|
|
20
|
+
export interface Ramp<T, S extends Source> {
|
|
21
|
+
/**
|
|
22
|
+
* The signal supplying normalized values.
|
|
23
|
+
*/
|
|
24
|
+
readonly by: Signal<S>;
|
|
25
|
+
/**
|
|
26
|
+
* The control points, in ascending position order, with at least two stops.
|
|
27
|
+
*/
|
|
28
|
+
readonly stops: readonly Stop<T>[];
|
|
29
|
+
}
|
|
30
|
+
export interface ValueSource<Out, S extends Source> {
|
|
31
|
+
/**
|
|
32
|
+
* Prepares the rendering data source for sampling, returning a function
|
|
33
|
+
* providing the value of some property for an element at a given index.
|
|
34
|
+
*/
|
|
35
|
+
prepare(source: S): (index: number) => Out;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A fixed value that is the same for every element.
|
|
39
|
+
*/
|
|
40
|
+
export type Constant<T, Out = T> = ValueSource<Out, Source> & {
|
|
41
|
+
readonly value: T;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A ramp providing scalar values for each element.
|
|
45
|
+
*/
|
|
46
|
+
export type ScalarRamp<S extends Source> = Ramp<number, S> & ValueSource<number, S>;
|
|
47
|
+
/**
|
|
48
|
+
* A ramp providing a color value for each element.
|
|
49
|
+
*/
|
|
50
|
+
export type ColorRamp<S extends Source> = Ramp<Color, S> & ValueSource<Rgba, S>;
|
|
51
|
+
/**
|
|
52
|
+
* A value source providing a scalar value for each element.
|
|
53
|
+
*/
|
|
54
|
+
export type ScalarValue<S extends Source = Source> = Constant<number> | ScalarRamp<S>;
|
|
55
|
+
/**
|
|
56
|
+
* A value source providing a color value for each element.
|
|
57
|
+
*/
|
|
58
|
+
export type ColorValue<S extends Source = Source> = Constant<Color, Rgba> | ColorRamp<S>;
|
|
59
|
+
/**
|
|
60
|
+
* What a caller may write for a scalar layer property.
|
|
61
|
+
*/
|
|
62
|
+
export type ScalarInput<S extends Source = Source> = number | ScalarRamp<S>;
|
|
63
|
+
/**
|
|
64
|
+
* What a caller may write for a color layer property.
|
|
65
|
+
*/
|
|
66
|
+
export type ColorInput<S extends Source = Source> = Color | ColorRamp<S>;
|
|
67
|
+
/**
|
|
68
|
+
* A namespace for predefined ramps.
|
|
69
|
+
*/
|
|
70
|
+
export declare namespace ramp {
|
|
71
|
+
/**
|
|
72
|
+
* A ramp that interpolates a color across `stops` as the signal `by` moves through `0..1`.
|
|
73
|
+
* Signal values outside of the specified stop range are clamped to the nearest endpoint.
|
|
74
|
+
*/
|
|
75
|
+
function color<S extends Source>({ by, stops }: Ramp<Color, S>): ColorRamp<S>;
|
|
76
|
+
/**
|
|
77
|
+
* A ramp that interpolates a number across `stops` as the signal `by` moves through `0..1`.
|
|
78
|
+
* Signal values outside of the specified stop range are clamped to the nearest endpoint.
|
|
79
|
+
*/
|
|
80
|
+
function scalar<S extends Source>({ by, stops }: Ramp<number, S>): ScalarRamp<S>;
|
|
81
|
+
/**
|
|
82
|
+
* Whether a layer property is a ramp.
|
|
83
|
+
*/
|
|
84
|
+
function is<S extends Source>(value: ScalarValue<S> | ColorValue<S>): value is ScalarRamp<S> | ColorRamp<S>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Wraps a scalar property, such that it has the same interface as a ramp.
|
|
88
|
+
*/
|
|
89
|
+
export declare function scalarValue<S extends Source>(input: ScalarInput<S>): ScalarValue<S>;
|
|
90
|
+
/**
|
|
91
|
+
* Wraps a color property, such that it has the same interface as a ramp.
|
|
92
|
+
*/
|
|
93
|
+
export declare function colorValue<S extends Source>(input: ColorInput<S>): ColorValue<S>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
import { color as srgb } from "../render/color.js";
|
|
3
|
+
import { normalizeSignal } from "../render/signal.js";
|
|
4
|
+
function validateStops(stops) {
|
|
5
|
+
assert(stops.length >= 2, `a ramp needs at least two stops, but has ${stops.length}`);
|
|
6
|
+
let previous = Number.NEGATIVE_INFINITY;
|
|
7
|
+
for (const [index, { at }] of stops.entries()) {
|
|
8
|
+
assert(Number.isFinite(at), `ramp stop ${index} position must be finite, but is ${at}`);
|
|
9
|
+
assert(at > previous, `ramp stop ${index} position ${at} must be greater than the previous stop`);
|
|
10
|
+
previous = at;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function bracket(stops, t) {
|
|
14
|
+
const first = stops[0];
|
|
15
|
+
const last = stops[stops.length - 1];
|
|
16
|
+
assert(first !== undefined && last !== undefined, "a ramp must have stops to be sampled");
|
|
17
|
+
if (t <= first.at) {
|
|
18
|
+
return { from: first, to: first, local: 0 };
|
|
19
|
+
}
|
|
20
|
+
if (t >= last.at) {
|
|
21
|
+
return { from: last, to: last, local: 0 };
|
|
22
|
+
}
|
|
23
|
+
for (let index = 1; index < stops.length; index++) {
|
|
24
|
+
const to = stops[index];
|
|
25
|
+
const from = stops[index - 1];
|
|
26
|
+
assert(from !== undefined && to !== undefined, `ramp stop ${index} is missing`);
|
|
27
|
+
if (t <= to.at) {
|
|
28
|
+
return { from, to, local: (t - from.at) / (to.at - from.at) };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
assert(false, `no ramp segment contains parameter ${t}`);
|
|
32
|
+
}
|
|
33
|
+
function prepareParameter(by, source) {
|
|
34
|
+
const values = by.channel.values(source);
|
|
35
|
+
return (index) => {
|
|
36
|
+
const raw = values[index];
|
|
37
|
+
assert(raw !== undefined, `${by.channel.name} channel has no sample at index ${index}`);
|
|
38
|
+
return normalizeSignal(by, raw);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A namespace for predefined ramps.
|
|
43
|
+
*/
|
|
44
|
+
export var ramp;
|
|
45
|
+
(function (ramp) {
|
|
46
|
+
/**
|
|
47
|
+
* A ramp that interpolates a color across `stops` as the signal `by` moves through `0..1`.
|
|
48
|
+
* Signal values outside of the specified stop range are clamped to the nearest endpoint.
|
|
49
|
+
*/
|
|
50
|
+
function color({ by, stops }) {
|
|
51
|
+
validateStops(stops);
|
|
52
|
+
const parsed = stops.map(({ at, value }) => ({ at, value: srgb.parse(value) }));
|
|
53
|
+
function sample(t) {
|
|
54
|
+
const { from, to, local } = bracket(parsed, t);
|
|
55
|
+
return srgb.mix({ from: from.value, to: to.value, t: local });
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
by,
|
|
59
|
+
stops,
|
|
60
|
+
prepare(source) {
|
|
61
|
+
const parameter = prepareParameter(by, source);
|
|
62
|
+
return (index) => sample(parameter(index));
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
ramp.color = color;
|
|
67
|
+
/**
|
|
68
|
+
* A ramp that interpolates a number across `stops` as the signal `by` moves through `0..1`.
|
|
69
|
+
* Signal values outside of the specified stop range are clamped to the nearest endpoint.
|
|
70
|
+
*/
|
|
71
|
+
function scalar({ by, stops }) {
|
|
72
|
+
validateStops(stops);
|
|
73
|
+
function sample(t) {
|
|
74
|
+
const { from, to, local } = bracket(stops, t);
|
|
75
|
+
return from.value + (to.value - from.value) * local;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
by,
|
|
79
|
+
stops,
|
|
80
|
+
prepare(source) {
|
|
81
|
+
const parameter = prepareParameter(by, source);
|
|
82
|
+
return (index) => sample(parameter(index));
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
ramp.scalar = scalar;
|
|
87
|
+
/**
|
|
88
|
+
* Whether a layer property is a ramp.
|
|
89
|
+
*/
|
|
90
|
+
function is(value) {
|
|
91
|
+
return "stops" in value;
|
|
92
|
+
}
|
|
93
|
+
ramp.is = is;
|
|
94
|
+
})(ramp || (ramp = {}));
|
|
95
|
+
/**
|
|
96
|
+
* Wraps a scalar property, such that it has the same interface as a ramp.
|
|
97
|
+
*/
|
|
98
|
+
export function scalarValue(input) {
|
|
99
|
+
if (typeof input !== "number") {
|
|
100
|
+
return input;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
value: input,
|
|
104
|
+
prepare: () => () => input,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Wraps a color property, such that it has the same interface as a ramp.
|
|
109
|
+
*/
|
|
110
|
+
export function colorValue(input) {
|
|
111
|
+
if (typeof input !== "string") {
|
|
112
|
+
return input;
|
|
113
|
+
}
|
|
114
|
+
const parsed = srgb.parse(input);
|
|
115
|
+
return {
|
|
116
|
+
value: input,
|
|
117
|
+
prepare: () => () => parsed,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=ramp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ramp.js","sourceRoot":"","sources":["../../src/render/ramp.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from \"@/assert.js\";\nimport { type Color, type Rgba, color as srgb } from \"@/render/color.js\";\nimport { normalizeSignal, type Signal } from \"@/render/signal.js\";\nimport type { Source } from \"@/render/sources.js\";\n\n/**\n * A single control point for the mapping of a ramp.\n */\nexport interface Stop<T> {\n /**\n * Where on the signal's `0..1` axis this control point sits.\n */\n readonly at: number;\n\n /**\n * What the signal's value should be mapped to at this position.\n */\n readonly value: T;\n}\n\n/**\n * A mapping of a signal's `0..1` parameter to some kind of output value `T`.\n */\nexport interface Ramp<T, S extends Source> {\n /**\n * The signal supplying normalized values.\n */\n readonly by: Signal<S>;\n\n /**\n * The control points, in ascending position order, with at least two stops.\n */\n readonly stops: readonly Stop<T>[];\n}\n\nexport interface ValueSource<Out, S extends Source> {\n /**\n * Prepares the rendering data source for sampling, returning a function\n * providing the value of some property for an element at a given index.\n */\n prepare(source: S): (index: number) => Out;\n}\n\n/**\n * A fixed value that is the same for every element.\n */\nexport type Constant<T, Out = T> = ValueSource<Out, Source> & {\n readonly value: T;\n};\n\n/**\n * A ramp providing scalar values for each element.\n */\nexport type ScalarRamp<S extends Source> = Ramp<number, S> & ValueSource<number, S>;\n\n/**\n * A ramp providing a color value for each element.\n */\nexport type ColorRamp<S extends Source> = Ramp<Color, S> & ValueSource<Rgba, S>;\n\n/**\n * A value source providing a scalar value for each element.\n */\nexport type ScalarValue<S extends Source = Source> = Constant<number> | ScalarRamp<S>;\n\n/**\n * A value source providing a color value for each element.\n */\nexport type ColorValue<S extends Source = Source> = Constant<Color, Rgba> | ColorRamp<S>;\n\n/**\n * What a caller may write for a scalar layer property.\n */\nexport type ScalarInput<S extends Source = Source> = number | ScalarRamp<S>;\n\n/**\n * What a caller may write for a color layer property.\n */\nexport type ColorInput<S extends Source = Source> = Color | ColorRamp<S>;\n\nfunction validateStops<T>(stops: readonly Stop<T>[]): void {\n assert(stops.length >= 2, `a ramp needs at least two stops, but has ${stops.length}`);\n\n let previous = Number.NEGATIVE_INFINITY;\n for (const [index, { at }] of stops.entries()) {\n assert(Number.isFinite(at), `ramp stop ${index} position must be finite, but is ${at}`);\n assert(at > previous, `ramp stop ${index} position ${at} must be greater than the previous stop`);\n previous = at;\n }\n}\n\nfunction bracket<T>(stops: readonly Stop<T>[], t: number): { from: Stop<T>; to: Stop<T>; local: number } {\n const first = stops[0];\n const last = stops[stops.length - 1];\n assert(first !== undefined && last !== undefined, \"a ramp must have stops to be sampled\");\n\n if (t <= first.at) {\n return { from: first, to: first, local: 0 };\n }\n if (t >= last.at) {\n return { from: last, to: last, local: 0 };\n }\n\n for (let index = 1; index < stops.length; index++) {\n const to = stops[index];\n const from = stops[index - 1];\n assert(from !== undefined && to !== undefined, `ramp stop ${index} is missing`);\n if (t <= to.at) {\n return { from, to, local: (t - from.at) / (to.at - from.at) };\n }\n }\n\n assert(false, `no ramp segment contains parameter ${t}`);\n}\n\nfunction prepareParameter<S extends Source>(by: Signal<S>, source: S): (index: number) => number {\n const values = by.channel.values(source);\n return (index) => {\n const raw = values[index];\n assert(raw !== undefined, `${by.channel.name} channel has no sample at index ${index}`);\n return normalizeSignal(by, raw);\n };\n}\n\n/**\n * A namespace for predefined ramps.\n */\nexport namespace ramp {\n /**\n * A ramp that interpolates a color across `stops` as the signal `by` moves through `0..1`.\n * Signal values outside of the specified stop range are clamped to the nearest endpoint.\n */\n export function color<S extends Source>({ by, stops }: Ramp<Color, S>): ColorRamp<S> {\n validateStops(stops);\n const parsed = stops.map(({ at, value }) => ({ at, value: srgb.parse(value) }));\n\n function sample(t: number): Rgba {\n const { from, to, local } = bracket(parsed, t);\n return srgb.mix({ from: from.value, to: to.value, t: local });\n }\n\n return {\n by,\n stops,\n prepare(source) {\n const parameter = prepareParameter(by, source);\n return (index) => sample(parameter(index));\n },\n };\n }\n\n /**\n * A ramp that interpolates a number across `stops` as the signal `by` moves through `0..1`.\n * Signal values outside of the specified stop range are clamped to the nearest endpoint.\n */\n export function scalar<S extends Source>({ by, stops }: Ramp<number, S>): ScalarRamp<S> {\n validateStops(stops);\n\n function sample(t: number): number {\n const { from, to, local } = bracket(stops, t);\n return from.value + (to.value - from.value) * local;\n }\n\n return {\n by,\n stops,\n prepare(source) {\n const parameter = prepareParameter(by, source);\n return (index) => sample(parameter(index));\n },\n };\n }\n\n /**\n * Whether a layer property is a ramp.\n */\n export function is<S extends Source>(value: ScalarValue<S> | ColorValue<S>): value is ScalarRamp<S> | ColorRamp<S> {\n return \"stops\" in value;\n }\n}\n\n/**\n * Wraps a scalar property, such that it has the same interface as a ramp.\n */\nexport function scalarValue<S extends Source>(input: ScalarInput<S>): ScalarValue<S> {\n if (typeof input !== \"number\") {\n return input;\n }\n return {\n value: input,\n prepare: () => () => input,\n };\n}\n\n/**\n * Wraps a color property, such that it has the same interface as a ramp.\n */\nexport function colorValue<S extends Source>(input: ColorInput<S>): ColorValue<S> {\n if (typeof input !== \"string\") {\n return input;\n }\n const parsed = srgb.parse(input);\n return {\n value: input,\n prepare: () => () => parsed,\n };\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Frame } from "../render/frame.js";
|
|
2
|
+
import type { Swarm, SwarmState } from "../types.js";
|
|
3
|
+
export interface SamplerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The swarm to sample state from.
|
|
6
|
+
*/
|
|
7
|
+
readonly swarm: Swarm;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The result of reading the swarm's state for a single frame.
|
|
11
|
+
*/
|
|
12
|
+
export interface Sample extends Frame {
|
|
13
|
+
/**
|
|
14
|
+
* The snapshot the buffers were filled from.
|
|
15
|
+
*/
|
|
16
|
+
readonly state: SwarmState;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generates {@link Sample}s by reading a swarm's observable state.
|
|
20
|
+
*/
|
|
21
|
+
export declare class Sampler {
|
|
22
|
+
private readonly swarm;
|
|
23
|
+
private positionBuffer;
|
|
24
|
+
private speedBuffer;
|
|
25
|
+
private inFlight;
|
|
26
|
+
private latest;
|
|
27
|
+
constructor({ swarm }: SamplerOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Reads the swarm's state for a single frame and returns a {@link Sample}. On
|
|
30
|
+
* subsequent calls, returns the most recent sample immediately.
|
|
31
|
+
*/
|
|
32
|
+
sample(): Promise<Sample>;
|
|
33
|
+
private readState;
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
import { PackedPositions } from "../render/frame.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generates {@link Sample}s by reading a swarm's observable state.
|
|
5
|
+
*/
|
|
6
|
+
export class Sampler {
|
|
7
|
+
swarm;
|
|
8
|
+
positionBuffer = new Float32Array(0);
|
|
9
|
+
speedBuffer = new Float32Array(0);
|
|
10
|
+
inFlight = null;
|
|
11
|
+
latest = null;
|
|
12
|
+
constructor({ swarm }) {
|
|
13
|
+
this.swarm = swarm;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads the swarm's state for a single frame and returns a {@link Sample}. On
|
|
17
|
+
* subsequent calls, returns the most recent sample immediately.
|
|
18
|
+
*/
|
|
19
|
+
sample() {
|
|
20
|
+
this.inFlight ??= this.readState().finally(() => {
|
|
21
|
+
this.inFlight = null;
|
|
22
|
+
});
|
|
23
|
+
const latest = this.latest;
|
|
24
|
+
return latest === null ? this.inFlight : Promise.resolve(latest);
|
|
25
|
+
}
|
|
26
|
+
async readState() {
|
|
27
|
+
const state = await this.swarm.snapshot();
|
|
28
|
+
const { positions, velocities } = state.particles;
|
|
29
|
+
const particleCount = positions.length;
|
|
30
|
+
assert(velocities.length === particleCount, `snapshot has ${particleCount} positions, but ${velocities.length} velocities`);
|
|
31
|
+
if (this.speedBuffer.length !== particleCount) {
|
|
32
|
+
this.positionBuffer = PackedPositions.allocate(particleCount);
|
|
33
|
+
this.speedBuffer = new Float32Array(particleCount);
|
|
34
|
+
}
|
|
35
|
+
const points = PackedPositions.of(this.positionBuffer);
|
|
36
|
+
for (let particle = 0; particle < particleCount; particle++) {
|
|
37
|
+
const position = positions[particle];
|
|
38
|
+
const velocity = velocities[particle];
|
|
39
|
+
assert(position !== undefined && velocity !== undefined, `particle ${particle} is missing from the snapshot`);
|
|
40
|
+
points.set(particle, position);
|
|
41
|
+
this.speedBuffer[particle] = Math.hypot(velocity[0], velocity[1], velocity[2]);
|
|
42
|
+
}
|
|
43
|
+
this.latest = {
|
|
44
|
+
time: state.time,
|
|
45
|
+
positions: this.positionBuffer,
|
|
46
|
+
speeds: this.speedBuffer,
|
|
47
|
+
particleCount,
|
|
48
|
+
state,
|
|
49
|
+
};
|
|
50
|
+
return this.latest;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=sampler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler.js","sourceRoot":"","sources":["../../src/render/sampler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAc,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAoBhE;;GAEG;AACH,MAAM,OAAO,OAAO;IACD,KAAK,CAAQ;IAEtB,cAAc,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IACrC,WAAW,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IAElC,QAAQ,GAA2B,IAAI,CAAC;IACxC,MAAM,GAAkB,IAAI,CAAC;IAErC,YAAY,EAAE,KAAK,EAAkB;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,MAAM;QACJ,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,CAAC;IAEO,KAAK,CAAC,SAAS;QACrB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC;QAElD,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC;QACvC,MAAM,CACJ,UAAU,CAAC,MAAM,KAAK,aAAa,EACnC,gBAAgB,aAAa,mBAAmB,UAAU,CAAC,MAAM,aAAa,CAC/E,CAAC;QAEF,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAC9C,IAAI,CAAC,cAAc,GAAG,eAAe,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;YAC9D,IAAI,CAAC,WAAW,GAAG,IAAI,YAAY,CAAC,aAAa,CAAC,CAAC;QACrD,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAEvD,KAAK,IAAI,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,aAAa,EAAE,QAAQ,EAAE,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACrC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;YACtC,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,YAAY,QAAQ,+BAA+B,CAAC,CAAC;YAC9G,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC/B,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,CAAC;QAED,IAAI,CAAC,MAAM,GAAG;YACZ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,IAAI,CAAC,cAAc;YAC9B,MAAM,EAAE,IAAI,CAAC,WAAW;YACxB,aAAa;YACb,KAAK;SACN,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF","sourcesContent":["import { assert } from \"@/assert.js\";\nimport { type Frame, PackedPositions } from \"@/render/frame.js\";\nimport type { Swarm, SwarmState } from \"@/types.js\";\n\nexport interface SamplerOptions {\n /**\n * The swarm to sample state from.\n */\n readonly swarm: Swarm;\n}\n\n/**\n * The result of reading the swarm's state for a single frame.\n */\nexport interface Sample extends Frame {\n /**\n * The snapshot the buffers were filled from.\n */\n readonly state: SwarmState;\n}\n\n/**\n * Generates {@link Sample}s by reading a swarm's observable state.\n */\nexport class Sampler {\n private readonly swarm: Swarm;\n\n private positionBuffer = new Float32Array(0);\n private speedBuffer = new Float32Array(0);\n\n private inFlight: Promise<Sample> | null = null;\n private latest: Sample | null = null;\n\n constructor({ swarm }: SamplerOptions) {\n this.swarm = swarm;\n }\n\n /**\n * Reads the swarm's state for a single frame and returns a {@link Sample}. On\n * subsequent calls, returns the most recent sample immediately.\n */\n sample(): Promise<Sample> {\n this.inFlight ??= this.readState().finally(() => {\n this.inFlight = null;\n });\n const latest = this.latest;\n return latest === null ? this.inFlight : Promise.resolve(latest);\n }\n\n private async readState(): Promise<Sample> {\n const state = await this.swarm.snapshot();\n const { positions, velocities } = state.particles;\n\n const particleCount = positions.length;\n assert(\n velocities.length === particleCount,\n `snapshot has ${particleCount} positions, but ${velocities.length} velocities`,\n );\n\n if (this.speedBuffer.length !== particleCount) {\n this.positionBuffer = PackedPositions.allocate(particleCount);\n this.speedBuffer = new Float32Array(particleCount);\n }\n\n const points = PackedPositions.of(this.positionBuffer);\n\n for (let particle = 0; particle < particleCount; particle++) {\n const position = positions[particle];\n const velocity = velocities[particle];\n assert(position !== undefined && velocity !== undefined, `particle ${particle} is missing from the snapshot`);\n points.set(particle, position);\n this.speedBuffer[particle] = Math.hypot(velocity[0], velocity[1], velocity[2]);\n }\n\n this.latest = {\n time: state.time,\n positions: this.positionBuffer,\n speeds: this.speedBuffer,\n particleCount,\n state,\n };\n return this.latest;\n }\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Channel } from "../render/channel.js";
|
|
2
|
+
import type { Source } from "../render/sources.js";
|
|
3
|
+
export interface ParticleSpeedSource extends Source {
|
|
4
|
+
/**
|
|
5
|
+
* Each particle's velocity magnitude for the current frame, in scene units
|
|
6
|
+
* per second.
|
|
7
|
+
*/
|
|
8
|
+
speeds(): Float32Array;
|
|
9
|
+
}
|
|
10
|
+
export interface ParticleDepthSource extends Source {
|
|
11
|
+
/**
|
|
12
|
+
* Each particle's distance into the scene along the view axis for the current
|
|
13
|
+
* frame, in scene units.
|
|
14
|
+
*/
|
|
15
|
+
depths(): Float32Array;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A component that normalizes the values provided by a channel.
|
|
19
|
+
*/
|
|
20
|
+
export interface Signal<S extends Source> {
|
|
21
|
+
/**
|
|
22
|
+
* The channel this signal reads from.
|
|
23
|
+
*/
|
|
24
|
+
readonly channel: Channel<S>;
|
|
25
|
+
/**
|
|
26
|
+
* The `[low, high]` range mapped onto `0..1`. The endpoints must be
|
|
27
|
+
* finite and distinct. A reversed range is also valid and inverts the
|
|
28
|
+
* mapping.
|
|
29
|
+
*/
|
|
30
|
+
readonly domain: readonly [number, number];
|
|
31
|
+
/**
|
|
32
|
+
* Whether raw values outside `domain` are clamped to `0..1` or allowed to
|
|
33
|
+
* extrapolate beyond it.
|
|
34
|
+
*/
|
|
35
|
+
readonly clamp: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface SignalOptions {
|
|
38
|
+
/**
|
|
39
|
+
* The `[low, high]` range mapped onto `0..1`. The endpoints must be
|
|
40
|
+
* finite and distinct. A reversed range is also valid and inverts the
|
|
41
|
+
* mapping.
|
|
42
|
+
*/
|
|
43
|
+
readonly domain: readonly [number, number];
|
|
44
|
+
/**
|
|
45
|
+
* Whether raw values outside `domain` are clamped or extrapolated beyond it.
|
|
46
|
+
*/
|
|
47
|
+
readonly clamp?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function normalizeSignal({ domain: [low, high], clamp }: Signal<Source>, raw: number): number;
|
|
50
|
+
/**
|
|
51
|
+
* The signal factories exposed on `view.signal`.
|
|
52
|
+
*/
|
|
53
|
+
export declare namespace signal {
|
|
54
|
+
/**
|
|
55
|
+
* A signal over the specified channel.
|
|
56
|
+
*/
|
|
57
|
+
function of<S extends Source>(channel: Channel<S>, { domain, clamp }: SignalOptions): Signal<S>;
|
|
58
|
+
/**
|
|
59
|
+
* A particle's velocity magnitude, in scene units per second.
|
|
60
|
+
*/
|
|
61
|
+
function speed(options: SignalOptions): Signal<ParticleSpeedSource>;
|
|
62
|
+
/**
|
|
63
|
+
* A particle's distance into the scene along the view axis, in scene units.
|
|
64
|
+
*/
|
|
65
|
+
function depth(options: SignalOptions): Signal<ParticleDepthSource>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
import { channel as defineChannel } from "../render/channel.js";
|
|
3
|
+
/**
|
|
4
|
+
* Each particle's velocity magnitude, in scene units per second.
|
|
5
|
+
*/
|
|
6
|
+
const speedChannel = defineChannel({
|
|
7
|
+
name: "speed",
|
|
8
|
+
values: (source) => source.speeds(),
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Each particle's distance into the scene along the view axis, in scene units.
|
|
12
|
+
*/
|
|
13
|
+
const depthChannel = defineChannel({
|
|
14
|
+
name: "depth",
|
|
15
|
+
values: (source) => source.depths(),
|
|
16
|
+
});
|
|
17
|
+
export function normalizeSignal({ domain: [low, high], clamp }, raw) {
|
|
18
|
+
const t = (raw - low) / (high - low);
|
|
19
|
+
return clamp ? Math.max(0, Math.min(1, t)) : t;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The signal factories exposed on `view.signal`.
|
|
23
|
+
*/
|
|
24
|
+
export var signal;
|
|
25
|
+
(function (signal) {
|
|
26
|
+
/**
|
|
27
|
+
* A signal over the specified channel.
|
|
28
|
+
*/
|
|
29
|
+
function of(channel, { domain, clamp = true }) {
|
|
30
|
+
const [low, high] = domain;
|
|
31
|
+
assert(Number.isFinite(low) && Number.isFinite(high), `${channel.name} signal domain must be finite, but is [${low}, ${high}]`);
|
|
32
|
+
assert(low !== high, `${channel.name} signal domain endpoints must differ, but are both ${low}`);
|
|
33
|
+
return { channel, domain, clamp };
|
|
34
|
+
}
|
|
35
|
+
signal.of = of;
|
|
36
|
+
/**
|
|
37
|
+
* A particle's velocity magnitude, in scene units per second.
|
|
38
|
+
*/
|
|
39
|
+
function speed(options) {
|
|
40
|
+
return of(speedChannel, options);
|
|
41
|
+
}
|
|
42
|
+
signal.speed = speed;
|
|
43
|
+
/**
|
|
44
|
+
* A particle's distance into the scene along the view axis, in scene units.
|
|
45
|
+
*/
|
|
46
|
+
function depth(options) {
|
|
47
|
+
return of(depthChannel, options);
|
|
48
|
+
}
|
|
49
|
+
signal.depth = depth;
|
|
50
|
+
})(signal || (signal = {}));
|
|
51
|
+
//# sourceMappingURL=signal.js.map
|