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,134 @@
|
|
|
1
|
+
import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
|
|
2
|
+
import { attribute, dot, float, max, normalize, normalView, positionLocal, vec3 } from "three/tsl";
|
|
3
|
+
import { CylinderGeometry, DynamicDrawUsage, InstancedBufferAttribute, InstancedBufferGeometry, Mesh, OctahedronGeometry, SphereGeometry, TorusGeometry, } from "three/webgpu";
|
|
4
|
+
import { assert } from "../../assert.js";
|
|
5
|
+
import { prepareGlyphReader } from "../../render/backend/cloud.js";
|
|
6
|
+
import { material as materialFactory } from "../../render/backend/material.js";
|
|
7
|
+
const SOLID_GLYPHS = new Set(["sphere", "octahedron", "axes", "gimbal"]);
|
|
8
|
+
const CENTER_ATTRIBUTE = "aCenter";
|
|
9
|
+
const SCALE_ATTRIBUTE = "aScale";
|
|
10
|
+
const COLOR_ATTRIBUTE = "aColor";
|
|
11
|
+
const FLOATS_PER_CENTER = 3;
|
|
12
|
+
const FLOATS_PER_COLOR = 4;
|
|
13
|
+
const SHADING = {
|
|
14
|
+
/**
|
|
15
|
+
* The direction of the light, in view space, toward the light.
|
|
16
|
+
*/
|
|
17
|
+
light: [0.35, 0.55, 0.75],
|
|
18
|
+
/**
|
|
19
|
+
* The brightness of a surface facing away from the light, as a fraction of
|
|
20
|
+
* one facing it.
|
|
21
|
+
*/
|
|
22
|
+
ambient: 0.4,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Whether `shape` is drawn as a solid.
|
|
26
|
+
*/
|
|
27
|
+
export function isSolid(shape) {
|
|
28
|
+
return SOLID_GLYPHS.has(shape);
|
|
29
|
+
}
|
|
30
|
+
// Each shape fits the unit cube that a flat glyph's quad spans, with the
|
|
31
|
+
// proportions of the flat glyph it corresponds to.
|
|
32
|
+
function solidGeometry(shape) {
|
|
33
|
+
switch (shape) {
|
|
34
|
+
case "sphere":
|
|
35
|
+
return new SphereGeometry(0.55, 12, 8);
|
|
36
|
+
case "octahedron":
|
|
37
|
+
return new OctahedronGeometry(0.75);
|
|
38
|
+
case "axes":
|
|
39
|
+
return merged([
|
|
40
|
+
new CylinderGeometry(0.11, 0.11, 1.7, 6).rotateZ(Math.PI / 2),
|
|
41
|
+
new CylinderGeometry(0.11, 0.11, 1.7, 6),
|
|
42
|
+
new CylinderGeometry(0.11, 0.11, 1.7, 6).rotateX(Math.PI / 2),
|
|
43
|
+
]);
|
|
44
|
+
case "gimbal":
|
|
45
|
+
return merged([
|
|
46
|
+
new TorusGeometry(0.8, 0.06, 6, 32),
|
|
47
|
+
new TorusGeometry(0.8, 0.06, 6, 32).rotateX(Math.PI / 2),
|
|
48
|
+
new TorusGeometry(0.8, 0.06, 6, 32).rotateY(Math.PI / 2),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function merged(parts) {
|
|
53
|
+
const result = mergeGeometries(parts);
|
|
54
|
+
assert(result !== null, "solid glyph parts have mismatched attributes");
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
part.dispose();
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function instancedCopy(base) {
|
|
61
|
+
const result = new InstancedBufferGeometry();
|
|
62
|
+
result.setIndex(base.getIndex());
|
|
63
|
+
for (const [name, values] of Object.entries(base.attributes)) {
|
|
64
|
+
result.setAttribute(name, values);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function createSolidMaterial() {
|
|
69
|
+
const color = attribute(COLOR_ATTRIBUTE, "vec4");
|
|
70
|
+
const facing = max(dot(normalView, normalize(vec3(...SHADING.light))), 0);
|
|
71
|
+
const shade = float(SHADING.ambient).add(facing.mul(1 - SHADING.ambient));
|
|
72
|
+
const result = materialFactory.solid({ color: color.xyz.mul(shade), opacity: color.w });
|
|
73
|
+
const center = attribute(CENTER_ATTRIBUTE, "vec3");
|
|
74
|
+
const scale = attribute(SCALE_ATTRIBUTE, "float");
|
|
75
|
+
result.positionNode = positionLocal.mul(scale).add(center);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function instancedAttribute(count, itemSize) {
|
|
79
|
+
const result = new InstancedBufferAttribute(new Float32Array(count * itemSize), itemSize);
|
|
80
|
+
result.setUsage(DynamicDrawUsage);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
function allocateSolidAttributes(geometry, count) {
|
|
84
|
+
const attributes = {
|
|
85
|
+
centers: instancedAttribute(count, FLOATS_PER_CENTER),
|
|
86
|
+
scales: instancedAttribute(count, 1),
|
|
87
|
+
colors: instancedAttribute(count, FLOATS_PER_COLOR),
|
|
88
|
+
};
|
|
89
|
+
geometry.setAttribute(CENTER_ATTRIBUTE, attributes.centers);
|
|
90
|
+
geometry.setAttribute(SCALE_ATTRIBUTE, attributes.scales);
|
|
91
|
+
geometry.setAttribute(COLOR_ATTRIBUTE, attributes.colors);
|
|
92
|
+
geometry.instanceCount = count;
|
|
93
|
+
return attributes;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Builds a glyph cloud that draws one shaded solid per particle.
|
|
97
|
+
*/
|
|
98
|
+
export function buildSolidCloud({ shape, style, sizeScale }) {
|
|
99
|
+
const geometry = instancedCopy(solidGeometry(shape));
|
|
100
|
+
const material = createSolidMaterial();
|
|
101
|
+
const mesh = new Mesh(geometry, material);
|
|
102
|
+
mesh.frustumCulled = false;
|
|
103
|
+
let count = -1;
|
|
104
|
+
let attributes = allocateSolidAttributes(geometry, 0);
|
|
105
|
+
return {
|
|
106
|
+
mesh,
|
|
107
|
+
update(frame, indices) {
|
|
108
|
+
if (indices.length !== count) {
|
|
109
|
+
count = indices.length;
|
|
110
|
+
attributes = allocateSolidAttributes(geometry, count);
|
|
111
|
+
}
|
|
112
|
+
const read = prepareGlyphReader(style, sizeScale, frame);
|
|
113
|
+
const centers = attributes.centers.array;
|
|
114
|
+
const scales = attributes.scales.array;
|
|
115
|
+
const colors = attributes.colors.array;
|
|
116
|
+
for (let slot = 0; slot < indices.length; slot++) {
|
|
117
|
+
const particle = indices[slot];
|
|
118
|
+
assert(particle !== undefined, `glyph slot ${slot} has no source particle`);
|
|
119
|
+
const { halfSize, center, color } = read(particle);
|
|
120
|
+
centers.set([center.x, center.y, center.z], slot * FLOATS_PER_CENTER);
|
|
121
|
+
scales[slot] = halfSize;
|
|
122
|
+
colors.set([color.r, color.g, color.b, color.a], slot * FLOATS_PER_COLOR);
|
|
123
|
+
}
|
|
124
|
+
attributes.centers.needsUpdate = true;
|
|
125
|
+
attributes.scales.needsUpdate = true;
|
|
126
|
+
attributes.colors.needsUpdate = true;
|
|
127
|
+
},
|
|
128
|
+
dispose() {
|
|
129
|
+
geometry.dispose();
|
|
130
|
+
material.dispose();
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=solids.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solids.js","sourceRoot":"","sources":["../../../src/render/backend/solids.ts"],"names":[],"mappings":"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","sourcesContent":["import { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { attribute, dot, float, max, normalize, normalView, positionLocal, vec3 } from \"three/tsl\";\nimport {\n type BufferGeometry,\n CylinderGeometry,\n DynamicDrawUsage,\n InstancedBufferAttribute,\n InstancedBufferGeometry,\n Mesh,\n type MeshBasicNodeMaterial,\n OctahedronGeometry,\n SphereGeometry,\n TorusGeometry,\n} from \"three/webgpu\";\nimport { assert } from \"@/assert.js\";\nimport { type GlyphCloud, type GlyphCloudOptions, prepareGlyphReader } from \"@/render/backend/cloud.js\";\nimport { material as materialFactory } from \"@/render/backend/material.js\";\nimport type { Frame } from \"@/render/frame.js\";\nimport type { ParticleGlyph, SolidGlyph } from \"@/render/layer.js\";\n\nconst SOLID_GLYPHS: ReadonlySet<ParticleGlyph> = new Set<SolidGlyph>([\"sphere\", \"octahedron\", \"axes\", \"gimbal\"]);\n\nconst CENTER_ATTRIBUTE = \"aCenter\";\nconst SCALE_ATTRIBUTE = \"aScale\";\nconst COLOR_ATTRIBUTE = \"aColor\";\n\nconst FLOATS_PER_CENTER = 3;\nconst FLOATS_PER_COLOR = 4;\n\nconst SHADING = {\n /**\n * The direction of the light, in view space, toward the light.\n */\n light: [0.35, 0.55, 0.75],\n\n /**\n * The brightness of a surface facing away from the light, as a fraction of\n * one facing it.\n */\n ambient: 0.4,\n} as const;\n\ninterface SolidAttributes {\n centers: InstancedBufferAttribute;\n scales: InstancedBufferAttribute;\n colors: InstancedBufferAttribute;\n}\n\n/**\n * Whether `shape` is drawn as a solid.\n */\nexport function isSolid(shape: ParticleGlyph): shape is SolidGlyph {\n return SOLID_GLYPHS.has(shape);\n}\n\n// Each shape fits the unit cube that a flat glyph's quad spans, with the\n// proportions of the flat glyph it corresponds to.\nfunction solidGeometry(shape: SolidGlyph): BufferGeometry {\n switch (shape) {\n case \"sphere\":\n return new SphereGeometry(0.55, 12, 8);\n case \"octahedron\":\n return new OctahedronGeometry(0.75);\n case \"axes\":\n return merged([\n new CylinderGeometry(0.11, 0.11, 1.7, 6).rotateZ(Math.PI / 2),\n new CylinderGeometry(0.11, 0.11, 1.7, 6),\n new CylinderGeometry(0.11, 0.11, 1.7, 6).rotateX(Math.PI / 2),\n ]);\n case \"gimbal\":\n return merged([\n new TorusGeometry(0.8, 0.06, 6, 32),\n new TorusGeometry(0.8, 0.06, 6, 32).rotateX(Math.PI / 2),\n new TorusGeometry(0.8, 0.06, 6, 32).rotateY(Math.PI / 2),\n ]);\n }\n}\n\nfunction merged(parts: BufferGeometry[]): BufferGeometry {\n const result = mergeGeometries(parts);\n assert(result !== null, \"solid glyph parts have mismatched attributes\");\n for (const part of parts) {\n part.dispose();\n }\n return result;\n}\n\nfunction instancedCopy(base: BufferGeometry): InstancedBufferGeometry {\n const result = new InstancedBufferGeometry();\n result.setIndex(base.getIndex());\n for (const [name, values] of Object.entries(base.attributes)) {\n result.setAttribute(name, values);\n }\n return result;\n}\n\nfunction createSolidMaterial(): MeshBasicNodeMaterial {\n const color = attribute<\"vec4\">(COLOR_ATTRIBUTE, \"vec4\");\n const facing = max(dot(normalView, normalize(vec3(...SHADING.light))), 0);\n const shade = float(SHADING.ambient).add(facing.mul(1 - SHADING.ambient));\n const result = materialFactory.solid({ color: color.xyz.mul(shade), opacity: color.w });\n const center = attribute<\"vec3\">(CENTER_ATTRIBUTE, \"vec3\");\n const scale = attribute<\"float\">(SCALE_ATTRIBUTE, \"float\");\n result.positionNode = positionLocal.mul(scale).add(center);\n return result;\n}\n\nfunction instancedAttribute(count: number, itemSize: number): InstancedBufferAttribute {\n const result = new InstancedBufferAttribute(new Float32Array(count * itemSize), itemSize);\n result.setUsage(DynamicDrawUsage);\n return result;\n}\n\nfunction allocateSolidAttributes(geometry: InstancedBufferGeometry, count: number): SolidAttributes {\n const attributes = {\n centers: instancedAttribute(count, FLOATS_PER_CENTER),\n scales: instancedAttribute(count, 1),\n colors: instancedAttribute(count, FLOATS_PER_COLOR),\n };\n geometry.setAttribute(CENTER_ATTRIBUTE, attributes.centers);\n geometry.setAttribute(SCALE_ATTRIBUTE, attributes.scales);\n geometry.setAttribute(COLOR_ATTRIBUTE, attributes.colors);\n geometry.instanceCount = count;\n return attributes;\n}\n\n/**\n * Builds a glyph cloud that draws one shaded solid per particle.\n */\nexport function buildSolidCloud({ shape, style, sizeScale }: GlyphCloudOptions<SolidGlyph>): GlyphCloud {\n const geometry = instancedCopy(solidGeometry(shape));\n const material = createSolidMaterial();\n const mesh = new Mesh(geometry, material);\n mesh.frustumCulled = false;\n\n let count = -1;\n let attributes = allocateSolidAttributes(geometry, 0);\n\n return {\n mesh,\n\n update(frame: Frame, indices: readonly number[]): void {\n if (indices.length !== count) {\n count = indices.length;\n attributes = allocateSolidAttributes(geometry, count);\n }\n\n const read = prepareGlyphReader(style, sizeScale, frame);\n const centers = attributes.centers.array as Float32Array;\n const scales = attributes.scales.array as Float32Array;\n const colors = attributes.colors.array as Float32Array;\n for (let slot = 0; slot < indices.length; slot++) {\n const particle = indices[slot];\n assert(particle !== undefined, `glyph slot ${slot} has no source particle`);\n const { halfSize, center, color } = read(particle);\n centers.set([center.x, center.y, center.z], slot * FLOATS_PER_CENTER);\n scales[slot] = halfSize;\n colors.set([color.r, color.g, color.b, color.a], slot * FLOATS_PER_COLOR);\n }\n\n attributes.centers.needsUpdate = true;\n attributes.scales.needsUpdate = true;\n attributes.colors.needsUpdate = true;\n },\n\n dispose(): void {\n geometry.dispose();\n material.dispose();\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A three-component position or direction in scene space.
|
|
3
|
+
*/
|
|
4
|
+
export type Vec3 = readonly [x: number, y: number, z: number];
|
|
5
|
+
/**
|
|
6
|
+
* The framing of a camera configuration.
|
|
7
|
+
*/
|
|
8
|
+
interface Framing {
|
|
9
|
+
/**
|
|
10
|
+
* The camera position in scene space.
|
|
11
|
+
*/
|
|
12
|
+
readonly position: Vec3;
|
|
13
|
+
/**
|
|
14
|
+
* The point the camera looks toward.
|
|
15
|
+
*/
|
|
16
|
+
readonly target: Vec3;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A camera configuration that uses a perspective projection, where objects
|
|
20
|
+
* farther from the camera appear smaller, the way the eye and a real lens see.
|
|
21
|
+
*/
|
|
22
|
+
export interface PerspectiveCamera extends Framing {
|
|
23
|
+
readonly kind: "perspective";
|
|
24
|
+
/**
|
|
25
|
+
* The vertical field of view, in degrees.
|
|
26
|
+
*/
|
|
27
|
+
readonly fieldOfView: number;
|
|
28
|
+
readonly near: number;
|
|
29
|
+
readonly far: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A camera configuration that uses an orthographic projection, where an object
|
|
33
|
+
* keeps the same size no matter its distance, so parallel lines stay parallel.
|
|
34
|
+
*/
|
|
35
|
+
export interface OrthographicCamera extends Framing {
|
|
36
|
+
readonly kind: "orthographic";
|
|
37
|
+
/**
|
|
38
|
+
* Half the vertical extent of the view volume, in scene units.
|
|
39
|
+
*/
|
|
40
|
+
readonly extent: number;
|
|
41
|
+
readonly near: number;
|
|
42
|
+
readonly far: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Any camera descriptor a stage accepts.
|
|
46
|
+
*/
|
|
47
|
+
export type Camera = PerspectiveCamera | OrthographicCamera;
|
|
48
|
+
/**
|
|
49
|
+
* The camera factories exposed on `view.camera`.
|
|
50
|
+
*/
|
|
51
|
+
export declare namespace camera {
|
|
52
|
+
/**
|
|
53
|
+
* A perspective camera.
|
|
54
|
+
*/
|
|
55
|
+
function perspective({ position, target, fieldOfView, near, far, }?: {
|
|
56
|
+
position?: Vec3;
|
|
57
|
+
target?: Vec3;
|
|
58
|
+
fieldOfView?: number;
|
|
59
|
+
near?: number;
|
|
60
|
+
far?: number;
|
|
61
|
+
}): PerspectiveCamera;
|
|
62
|
+
/**
|
|
63
|
+
* An orthographic camera.
|
|
64
|
+
*/
|
|
65
|
+
function orthographic({ position, target, extent, near, far, }?: {
|
|
66
|
+
position?: Vec3;
|
|
67
|
+
target?: Vec3;
|
|
68
|
+
extent?: number;
|
|
69
|
+
near?: number;
|
|
70
|
+
far?: number;
|
|
71
|
+
}): OrthographicCamera;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
const DEFAULT_POSITION = [0, 0, 3];
|
|
3
|
+
const DEFAULT_TARGET = [0, 0, 0];
|
|
4
|
+
function verifyClipRange({ near, far }) {
|
|
5
|
+
assert(near > 0, `camera near plane must be positive, but is ${near}`);
|
|
6
|
+
assert(far > near, `camera far plane ${far} must exceed the near plane ${near}`);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The camera factories exposed on `view.camera`.
|
|
10
|
+
*/
|
|
11
|
+
export var camera;
|
|
12
|
+
(function (camera) {
|
|
13
|
+
/**
|
|
14
|
+
* A perspective camera.
|
|
15
|
+
*/
|
|
16
|
+
function perspective({ position = DEFAULT_POSITION, target = DEFAULT_TARGET, fieldOfView = 45, near = 0.01, far = 100, } = {}) {
|
|
17
|
+
assert(fieldOfView > 0 && fieldOfView < 180, `field of view must be within 0..180, but is ${fieldOfView}`);
|
|
18
|
+
verifyClipRange({ near, far });
|
|
19
|
+
return { kind: "perspective", position, target, fieldOfView, near, far };
|
|
20
|
+
}
|
|
21
|
+
camera.perspective = perspective;
|
|
22
|
+
/**
|
|
23
|
+
* An orthographic camera.
|
|
24
|
+
*/
|
|
25
|
+
function orthographic({ position = DEFAULT_POSITION, target = DEFAULT_TARGET, extent = 1.2, near = 0.01, far = 100, } = {}) {
|
|
26
|
+
assert(extent > 0, `orthographic extent must be positive, but is ${extent}`);
|
|
27
|
+
verifyClipRange({ near, far });
|
|
28
|
+
return { kind: "orthographic", position, target, extent, near, far };
|
|
29
|
+
}
|
|
30
|
+
camera.orthographic = orthographic;
|
|
31
|
+
})(camera || (camera = {}));
|
|
32
|
+
//# sourceMappingURL=camera.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera.js","sourceRoot":"","sources":["../../src/render/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAuDrC,MAAM,gBAAgB,GAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzC,MAAM,cAAc,GAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAEvC,SAAS,eAAe,CAAC,EAAE,IAAI,EAAE,GAAG,EAAiC;IACnE,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,8CAA8C,IAAI,EAAE,CAAC,CAAC;IACvE,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,oBAAoB,GAAG,+BAA+B,IAAI,EAAE,CAAC,CAAC;AACnF,CAAC;AAED;;GAEG;AACH,MAAM,KAAW,MAAM,CA0CtB;AA1CD,WAAiB,MAAM;IACrB;;OAEG;IACH,SAAgB,WAAW,CAAC,EAC1B,QAAQ,GAAG,gBAAgB,EAC3B,MAAM,GAAG,cAAc,EACvB,WAAW,GAAG,EAAE,EAChB,IAAI,GAAG,IAAI,EACX,GAAG,GAAG,GAAG,MAOP,EAAE;QACJ,MAAM,CAAC,WAAW,GAAG,CAAC,IAAI,WAAW,GAAG,GAAG,EAAE,+CAA+C,WAAW,EAAE,CAAC,CAAC;QAC3G,eAAe,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAC3E,CAAC;IAhBe,kBAAW,cAgB1B,CAAA;IAED;;OAEG;IACH,SAAgB,YAAY,CAAC,EAC3B,QAAQ,GAAG,gBAAgB,EAC3B,MAAM,GAAG,cAAc,EACvB,MAAM,GAAG,GAAG,EACZ,IAAI,GAAG,IAAI,EACX,GAAG,GAAG,GAAG,MAOP,EAAE;QACJ,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,gDAAgD,MAAM,EAAE,CAAC,CAAC;QAC7E,eAAe,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACvE,CAAC;IAhBe,mBAAY,eAgB3B,CAAA;AACH,CAAC,EA1CgB,MAAM,KAAN,MAAM,QA0CtB","sourcesContent":["import { assert } from \"@/assert.js\";\n\n/**\n * A three-component position or direction in scene space.\n */\nexport type Vec3 = readonly [x: number, y: number, z: number];\n\n/**\n * The framing of a camera configuration.\n */\ninterface Framing {\n /**\n * The camera position in scene space.\n */\n readonly position: Vec3;\n\n /**\n * The point the camera looks toward.\n */\n readonly target: Vec3;\n}\n\n/**\n * A camera configuration that uses a perspective projection, where objects\n * farther from the camera appear smaller, the way the eye and a real lens see.\n */\nexport interface PerspectiveCamera extends Framing {\n readonly kind: \"perspective\";\n /**\n * The vertical field of view, in degrees.\n */\n readonly fieldOfView: number;\n readonly near: number;\n readonly far: number;\n}\n\n/**\n * A camera configuration that uses an orthographic projection, where an object\n * keeps the same size no matter its distance, so parallel lines stay parallel.\n */\nexport interface OrthographicCamera extends Framing {\n readonly kind: \"orthographic\";\n /**\n * Half the vertical extent of the view volume, in scene units.\n */\n readonly extent: number;\n readonly near: number;\n readonly far: number;\n}\n\n/**\n * Any camera descriptor a stage accepts.\n */\nexport type Camera = PerspectiveCamera | OrthographicCamera;\n\nconst DEFAULT_POSITION: Vec3 = [0, 0, 3];\nconst DEFAULT_TARGET: Vec3 = [0, 0, 0];\n\nfunction verifyClipRange({ near, far }: { near: number; far: number }): void {\n assert(near > 0, `camera near plane must be positive, but is ${near}`);\n assert(far > near, `camera far plane ${far} must exceed the near plane ${near}`);\n}\n\n/**\n * The camera factories exposed on `view.camera`.\n */\nexport namespace camera {\n /**\n * A perspective camera.\n */\n export function perspective({\n position = DEFAULT_POSITION,\n target = DEFAULT_TARGET,\n fieldOfView = 45,\n near = 0.01,\n far = 100,\n }: {\n position?: Vec3;\n target?: Vec3;\n fieldOfView?: number;\n near?: number;\n far?: number;\n } = {}): PerspectiveCamera {\n assert(fieldOfView > 0 && fieldOfView < 180, `field of view must be within 0..180, but is ${fieldOfView}`);\n verifyClipRange({ near, far });\n return { kind: \"perspective\", position, target, fieldOfView, near, far };\n }\n\n /**\n * An orthographic camera.\n */\n export function orthographic({\n position = DEFAULT_POSITION,\n target = DEFAULT_TARGET,\n extent = 1.2,\n near = 0.01,\n far = 100,\n }: {\n position?: Vec3;\n target?: Vec3;\n extent?: number;\n near?: number;\n far?: number;\n } = {}): OrthographicCamera {\n assert(extent > 0, `orthographic extent must be positive, but is ${extent}`);\n verifyClipRange({ near, far });\n return { kind: \"orthographic\", position, target, extent, near, far };\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Source } from "../render/sources.js";
|
|
2
|
+
/**
|
|
3
|
+
* A component extracting data for visualization from a source.
|
|
4
|
+
*/
|
|
5
|
+
export interface Channel<S extends Source> {
|
|
6
|
+
/**
|
|
7
|
+
* A human-readable label for data provided by this channel.
|
|
8
|
+
*/
|
|
9
|
+
readonly name: string;
|
|
10
|
+
/**
|
|
11
|
+
* The value of whatever this channel represents for each element in source.
|
|
12
|
+
*/
|
|
13
|
+
values(source: S): Float32Array;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for {@link channel}.
|
|
17
|
+
*/
|
|
18
|
+
export interface ChannelOptions<S extends Source> {
|
|
19
|
+
name: string;
|
|
20
|
+
values: (source: S) => Float32Array;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Defines a channel that will extract data from a source for visualization.
|
|
24
|
+
*/
|
|
25
|
+
export declare function channel<S extends Source>({ name, values }: ChannelOptions<S>): Channel<S>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
/**
|
|
3
|
+
* Defines a channel that will extract data from a source for visualization.
|
|
4
|
+
*/
|
|
5
|
+
export function channel({ name, values }) {
|
|
6
|
+
assert(/^[A-Za-z][A-Za-z0-9]*$/.test(name), `channel name must be alphanumeric and start with a letter, but is "${name}"`);
|
|
7
|
+
return { name, values };
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/render/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA0BrC;;GAEG;AACH,MAAM,UAAU,OAAO,CAAmB,EAAE,IAAI,EAAE,MAAM,EAAqB;IAC3E,MAAM,CACJ,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EACnC,sEAAsE,IAAI,GAAG,CAC9E,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["import { assert } from \"@/assert.js\";\nimport type { Source } from \"@/render/sources.js\";\n\n/**\n * A component extracting data for visualization from a source.\n */\nexport interface Channel<S extends Source> {\n /**\n * A human-readable label for data provided by this channel.\n */\n readonly name: string;\n\n /**\n * The value of whatever this channel represents for each element in source.\n */\n values(source: S): Float32Array;\n}\n\n/**\n * Options for {@link channel}.\n */\nexport interface ChannelOptions<S extends Source> {\n name: string;\n values: (source: S) => Float32Array;\n}\n\n/**\n * Defines a channel that will extract data from a source for visualization.\n */\nexport function channel<S extends Source>({ name, values }: ChannelOptions<S>): Channel<S> {\n assert(\n /^[A-Za-z][A-Za-z0-9]*$/.test(name),\n `channel name must be alphanumeric and start with a letter, but is \"${name}\"`,\n );\n return { name, values };\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A color at the API surface, written as an sRGB hex string: `#RGB`, `#RGBA`,
|
|
3
|
+
* `#RRGGBB`, or `#RRGGBBAA`.
|
|
4
|
+
*/
|
|
5
|
+
export type Color = string;
|
|
6
|
+
/**
|
|
7
|
+
* A parsed color with sRGB channels and alpha, each in the range `0..1`.
|
|
8
|
+
*/
|
|
9
|
+
export interface Rgba {
|
|
10
|
+
readonly r: number;
|
|
11
|
+
readonly g: number;
|
|
12
|
+
readonly b: number;
|
|
13
|
+
readonly a: number;
|
|
14
|
+
}
|
|
15
|
+
export declare namespace color {
|
|
16
|
+
/**
|
|
17
|
+
* Parses an sRGB hex color into channels in `0..1`.
|
|
18
|
+
*/
|
|
19
|
+
function parse(value: Color): Rgba;
|
|
20
|
+
/**
|
|
21
|
+
* Formats sRGB channels back into a hex string. The alpha channel is
|
|
22
|
+
* only emitted when it is not fully opaque.
|
|
23
|
+
*/
|
|
24
|
+
function format(rgba: Rgba): Color;
|
|
25
|
+
/**
|
|
26
|
+
* Linearly interpolates between two parsed colors in sRGB space by fraction
|
|
27
|
+
* `t`, clamped to `0..1`. `t = 0` returns `from`, `t = 1` returns `to`.
|
|
28
|
+
*/
|
|
29
|
+
function mix({ from, to, t }: {
|
|
30
|
+
from: Rgba;
|
|
31
|
+
to: Rgba;
|
|
32
|
+
t: number;
|
|
33
|
+
}): Rgba;
|
|
34
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
function expand(hex) {
|
|
3
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
4
|
+
return [...hex].map((digit) => digit + digit).join("");
|
|
5
|
+
}
|
|
6
|
+
return hex;
|
|
7
|
+
}
|
|
8
|
+
function channel(hex, offset) {
|
|
9
|
+
const pair = hex.slice(offset, offset + 2);
|
|
10
|
+
const value = Number.parseInt(pair, 16);
|
|
11
|
+
assert(Number.isInteger(value), `color "#${hex}" has an invalid channel "${pair}"`);
|
|
12
|
+
return value / 255;
|
|
13
|
+
}
|
|
14
|
+
function byte(value) {
|
|
15
|
+
const clamped = Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
16
|
+
return clamped.toString(16).padStart(2, "0");
|
|
17
|
+
}
|
|
18
|
+
export var color;
|
|
19
|
+
(function (color) {
|
|
20
|
+
/**
|
|
21
|
+
* Parses an sRGB hex color into channels in `0..1`.
|
|
22
|
+
*/
|
|
23
|
+
function parse(value) {
|
|
24
|
+
assert(value.startsWith("#"), `color "${value}" must start with "#"`);
|
|
25
|
+
const body = expand(value.slice(1));
|
|
26
|
+
assert(body.length === 6 || body.length === 8, `color "${value}" must have 3, 4, 6, or 8 hex digits, but has ${body.length}`);
|
|
27
|
+
assert(/^[0-9a-fA-F]+$/.test(body), `color "${value}" has a non-hexadecimal digit`);
|
|
28
|
+
return {
|
|
29
|
+
r: channel(body, 0),
|
|
30
|
+
g: channel(body, 2),
|
|
31
|
+
b: channel(body, 4),
|
|
32
|
+
a: body.length === 8 ? channel(body, 6) : 1,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
color.parse = parse;
|
|
36
|
+
/**
|
|
37
|
+
* Formats sRGB channels back into a hex string. The alpha channel is
|
|
38
|
+
* only emitted when it is not fully opaque.
|
|
39
|
+
*/
|
|
40
|
+
function format(rgba) {
|
|
41
|
+
const base = `#${byte(rgba.r)}${byte(rgba.g)}${byte(rgba.b)}`;
|
|
42
|
+
return rgba.a >= 1 ? base : `${base}${byte(rgba.a)}`;
|
|
43
|
+
}
|
|
44
|
+
color.format = format;
|
|
45
|
+
/**
|
|
46
|
+
* Linearly interpolates between two parsed colors in sRGB space by fraction
|
|
47
|
+
* `t`, clamped to `0..1`. `t = 0` returns `from`, `t = 1` returns `to`.
|
|
48
|
+
*/
|
|
49
|
+
function mix({ from, to, t }) {
|
|
50
|
+
const amount = Math.max(0, Math.min(1, t));
|
|
51
|
+
const lerp = (a, b) => a + (b - a) * amount;
|
|
52
|
+
return { r: lerp(from.r, to.r), g: lerp(from.g, to.g), b: lerp(from.b, to.b), a: lerp(from.a, to.a) };
|
|
53
|
+
}
|
|
54
|
+
color.mix = mix;
|
|
55
|
+
})(color || (color = {}));
|
|
56
|
+
//# sourceMappingURL=color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/render/color.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from \"@/assert.js\";\n\n/**\n * A color at the API surface, written as an sRGB hex string: `#RGB`, `#RGBA`,\n * `#RRGGBB`, or `#RRGGBBAA`.\n */\nexport type Color = string;\n\n/**\n * A parsed color with sRGB channels and alpha, each in the range `0..1`.\n */\nexport interface Rgba {\n readonly r: number;\n readonly g: number;\n readonly b: number;\n readonly a: number;\n}\n\nfunction expand(hex: string): string {\n if (hex.length === 3 || hex.length === 4) {\n return [...hex].map((digit) => digit + digit).join(\"\");\n }\n return hex;\n}\n\nfunction channel(hex: string, offset: number): number {\n const pair = hex.slice(offset, offset + 2);\n const value = Number.parseInt(pair, 16);\n assert(Number.isInteger(value), `color \"#${hex}\" has an invalid channel \"${pair}\"`);\n return value / 255;\n}\n\nfunction byte(value: number): string {\n const clamped = Math.max(0, Math.min(255, Math.round(value * 255)));\n return clamped.toString(16).padStart(2, \"0\");\n}\n\nexport namespace color {\n /**\n * Parses an sRGB hex color into channels in `0..1`.\n */\n export function parse(value: Color): Rgba {\n assert(value.startsWith(\"#\"), `color \"${value}\" must start with \"#\"`);\n const body = expand(value.slice(1));\n assert(\n body.length === 6 || body.length === 8,\n `color \"${value}\" must have 3, 4, 6, or 8 hex digits, but has ${body.length}`,\n );\n assert(/^[0-9a-fA-F]+$/.test(body), `color \"${value}\" has a non-hexadecimal digit`);\n\n return {\n r: channel(body, 0),\n g: channel(body, 2),\n b: channel(body, 4),\n a: body.length === 8 ? channel(body, 6) : 1,\n };\n }\n\n /**\n * Formats sRGB channels back into a hex string. The alpha channel is\n * only emitted when it is not fully opaque.\n */\n export function format(rgba: Rgba): Color {\n const base = `#${byte(rgba.r)}${byte(rgba.g)}${byte(rgba.b)}`;\n return rgba.a >= 1 ? base : `${base}${byte(rgba.a)}`;\n }\n\n /**\n * Linearly interpolates between two parsed colors in sRGB space by fraction\n * `t`, clamped to `0..1`. `t = 0` returns `from`, `t = 1` returns `to`.\n */\n export function mix({ from, to, t }: { from: Rgba; to: Rgba; t: number }): Rgba {\n const amount = Math.max(0, Math.min(1, t));\n const lerp = (a: number, b: number): number => a + (b - a) * amount;\n return { r: lerp(from.r, to.r), g: lerp(from.g, to.g), b: lerp(from.b, to.b), a: lerp(from.a, to.a) };\n }\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bloom effect that makes pixels brighter than `threshold` flare.
|
|
3
|
+
*/
|
|
4
|
+
export interface BloomEffect {
|
|
5
|
+
readonly type: "bloom";
|
|
6
|
+
/**
|
|
7
|
+
* The luminance, `0..1`, a pixel must exceed before it blooms.
|
|
8
|
+
*/
|
|
9
|
+
readonly threshold: number;
|
|
10
|
+
/**
|
|
11
|
+
* The gain bloomed pixels are added back with. Non-negative, unbounded
|
|
12
|
+
* above.
|
|
13
|
+
*/
|
|
14
|
+
readonly intensity: number;
|
|
15
|
+
/**
|
|
16
|
+
* The bloom spread, as a fraction of the viewport's smaller side.
|
|
17
|
+
*/
|
|
18
|
+
readonly radius: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A vignette pass darkening the frame edges.
|
|
22
|
+
*/
|
|
23
|
+
export interface VignetteEffect {
|
|
24
|
+
readonly type: "vignette";
|
|
25
|
+
/**
|
|
26
|
+
* How dark the corners become, `0..1`.
|
|
27
|
+
*/
|
|
28
|
+
readonly intensity: number;
|
|
29
|
+
/**
|
|
30
|
+
* How gradually the vignette falls off, `0..1`.
|
|
31
|
+
*/
|
|
32
|
+
readonly smoothness: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Any post-processing effect a stage accepts, applied in array order.
|
|
36
|
+
*/
|
|
37
|
+
export type Effect = BloomEffect | VignetteEffect;
|
|
38
|
+
/**
|
|
39
|
+
* Options for {@link effect.bloom}.
|
|
40
|
+
*/
|
|
41
|
+
export interface BloomOptions {
|
|
42
|
+
/**
|
|
43
|
+
* The luminance, `0..1`, a pixel must exceed before it blooms.
|
|
44
|
+
*/
|
|
45
|
+
readonly threshold?: number;
|
|
46
|
+
/**
|
|
47
|
+
* The gain bloomed pixels are added back with. Non-negative, unbounded
|
|
48
|
+
* above.
|
|
49
|
+
*/
|
|
50
|
+
readonly intensity?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The bloom spread, as a fraction of the viewport's smaller side.
|
|
53
|
+
*/
|
|
54
|
+
readonly radius?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Options for {@link effect.vignette}.
|
|
58
|
+
*/
|
|
59
|
+
export interface VignetteOptions {
|
|
60
|
+
/**
|
|
61
|
+
* How dark the corners become, `0..1`.
|
|
62
|
+
*/
|
|
63
|
+
readonly intensity?: number;
|
|
64
|
+
/**
|
|
65
|
+
* How gradually the vignette falls off, `0..1`.
|
|
66
|
+
*/
|
|
67
|
+
readonly smoothness?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The effect factories exposed on `view.effect`.
|
|
71
|
+
*/
|
|
72
|
+
export declare namespace effect {
|
|
73
|
+
/**
|
|
74
|
+
* A thresholded bloom pass.
|
|
75
|
+
*/
|
|
76
|
+
function bloom({ threshold, intensity, radius }?: BloomOptions): BloomEffect;
|
|
77
|
+
/**
|
|
78
|
+
* A vignette pass.
|
|
79
|
+
*/
|
|
80
|
+
function vignette({ intensity, smoothness }?: VignetteOptions): VignetteEffect;
|
|
81
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
function verifyIsUnit(value, name) {
|
|
3
|
+
assert(value >= 0 && value <= 1, `${name} must be within 0..1, but is ${value}`);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* The effect factories exposed on `view.effect`.
|
|
7
|
+
*/
|
|
8
|
+
export var effect;
|
|
9
|
+
(function (effect) {
|
|
10
|
+
/**
|
|
11
|
+
* A thresholded bloom pass.
|
|
12
|
+
*/
|
|
13
|
+
function bloom({ threshold = 0.85, intensity = 0.65, radius = 0.12 } = {}) {
|
|
14
|
+
verifyIsUnit(threshold, "bloom threshold");
|
|
15
|
+
assert(Number.isFinite(intensity) && intensity >= 0, `bloom intensity must be non-negative and finite, but is ${intensity}`);
|
|
16
|
+
assert(radius >= 0, `bloom radius must be non-negative, but is ${radius}`);
|
|
17
|
+
return { type: "bloom", threshold, intensity, radius };
|
|
18
|
+
}
|
|
19
|
+
effect.bloom = bloom;
|
|
20
|
+
/**
|
|
21
|
+
* A vignette pass.
|
|
22
|
+
*/
|
|
23
|
+
function vignette({ intensity = 0.45, smoothness = 0.85 } = {}) {
|
|
24
|
+
verifyIsUnit(intensity, "vignette intensity");
|
|
25
|
+
verifyIsUnit(smoothness, "vignette smoothness");
|
|
26
|
+
return { type: "vignette", intensity, smoothness };
|
|
27
|
+
}
|
|
28
|
+
effect.vignette = vignette;
|
|
29
|
+
})(effect || (effect = {}));
|
|
30
|
+
//# sourceMappingURL=effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/render/effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAmFrC,SAAS,YAAY,CAAC,KAAa,EAAE,IAAY;IAC/C,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,gCAAgC,KAAK,EAAE,CAAC,CAAC;AACnF,CAAC;AAED;;GAEG;AACH,MAAM,KAAW,MAAM,CAsBtB;AAtBD,WAAiB,MAAM;IACrB;;OAEG;IACH,SAAgB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,KAAmB,EAAE;QAC5F,YAAY,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC3C,MAAM,CACJ,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAC5C,2DAA2D,SAAS,EAAE,CACvE,CAAC;QACF,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,6CAA6C,MAAM,EAAE,CAAC,CAAC;QAC3E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACzD,CAAC;IARe,YAAK,QAQpB,CAAA;IAED;;OAEG;IACH,SAAgB,QAAQ,CAAC,EAAE,SAAS,GAAG,IAAI,EAAE,UAAU,GAAG,IAAI,KAAsB,EAAE;QACpF,YAAY,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;QAC9C,YAAY,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;QAChD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IACrD,CAAC;IAJe,eAAQ,WAIvB,CAAA;AACH,CAAC,EAtBgB,MAAM,KAAN,MAAM,QAsBtB","sourcesContent":["import { assert } from \"@/assert.js\";\n\n/**\n * A bloom effect that makes pixels brighter than `threshold` flare.\n */\nexport interface BloomEffect {\n readonly type: \"bloom\";\n\n /**\n * The luminance, `0..1`, a pixel must exceed before it blooms.\n */\n readonly threshold: number;\n\n /**\n * The gain bloomed pixels are added back with. Non-negative, unbounded\n * above.\n */\n readonly intensity: number;\n\n /**\n * The bloom spread, as a fraction of the viewport's smaller side.\n */\n readonly radius: number;\n}\n\n/**\n * A vignette pass darkening the frame edges.\n */\nexport interface VignetteEffect {\n readonly type: \"vignette\";\n\n /**\n * How dark the corners become, `0..1`.\n */\n readonly intensity: number;\n\n /**\n * How gradually the vignette falls off, `0..1`.\n */\n readonly smoothness: number;\n}\n\n/**\n * Any post-processing effect a stage accepts, applied in array order.\n */\nexport type Effect = BloomEffect | VignetteEffect;\n\n/**\n * Options for {@link effect.bloom}.\n */\nexport interface BloomOptions {\n /**\n * The luminance, `0..1`, a pixel must exceed before it blooms.\n */\n readonly threshold?: number;\n\n /**\n * The gain bloomed pixels are added back with. Non-negative, unbounded\n * above.\n */\n readonly intensity?: number;\n\n /**\n * The bloom spread, as a fraction of the viewport's smaller side.\n */\n readonly radius?: number;\n}\n\n/**\n * Options for {@link effect.vignette}.\n */\nexport interface VignetteOptions {\n /**\n * How dark the corners become, `0..1`.\n */\n readonly intensity?: number;\n\n /**\n * How gradually the vignette falls off, `0..1`.\n */\n readonly smoothness?: number;\n}\n\nfunction verifyIsUnit(value: number, name: string): void {\n assert(value >= 0 && value <= 1, `${name} must be within 0..1, but is ${value}`);\n}\n\n/**\n * The effect factories exposed on `view.effect`.\n */\nexport namespace effect {\n /**\n * A thresholded bloom pass.\n */\n export function bloom({ threshold = 0.85, intensity = 0.65, radius = 0.12 }: BloomOptions = {}): BloomEffect {\n verifyIsUnit(threshold, \"bloom threshold\");\n assert(\n Number.isFinite(intensity) && intensity >= 0,\n `bloom intensity must be non-negative and finite, but is ${intensity}`,\n );\n assert(radius >= 0, `bloom radius must be non-negative, but is ${radius}`);\n return { type: \"bloom\", threshold, intensity, radius };\n }\n\n /**\n * A vignette pass.\n */\n export function vignette({ intensity = 0.45, smoothness = 0.85 }: VignetteOptions = {}): VignetteEffect {\n verifyIsUnit(intensity, \"vignette intensity\");\n verifyIsUnit(smoothness, \"vignette smoothness\");\n return { type: \"vignette\", intensity, smoothness };\n }\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A view over a position buffer.
|
|
3
|
+
*/
|
|
4
|
+
export interface PackedPositions {
|
|
5
|
+
/**
|
|
6
|
+
* The number of particles the buffer holds.
|
|
7
|
+
*/
|
|
8
|
+
readonly count: number;
|
|
9
|
+
/**
|
|
10
|
+
* The x coordinate of a particle at the specified index.
|
|
11
|
+
*/
|
|
12
|
+
x(particle: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* The y coordinate of a particle at the specified index.
|
|
15
|
+
*/
|
|
16
|
+
y(particle: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* The z coordinate of a particle at the specified index.
|
|
19
|
+
*/
|
|
20
|
+
z(particle: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* A particle's coordinates as a tuple.
|
|
23
|
+
*/
|
|
24
|
+
at(particle: number): readonly [number, number, number];
|
|
25
|
+
/**
|
|
26
|
+
* Updates a particle's coordinates from an `[x, y, z]` tuple.
|
|
27
|
+
*/
|
|
28
|
+
set(particle: number, position: readonly [number, number, number]): void;
|
|
29
|
+
}
|
|
30
|
+
export declare namespace PackedPositions {
|
|
31
|
+
/**
|
|
32
|
+
* Wraps a packed position buffer so it can be read and written by particle.
|
|
33
|
+
*/
|
|
34
|
+
function of(buffer: Float32Array): PackedPositions;
|
|
35
|
+
/**
|
|
36
|
+
* Allocates a packed position buffer sized to hold `count` particles.
|
|
37
|
+
*/
|
|
38
|
+
function allocate(count: number): Float32Array<ArrayBuffer>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A snapshot of the simulation state at a given point in time.
|
|
42
|
+
*/
|
|
43
|
+
export interface Frame {
|
|
44
|
+
/**
|
|
45
|
+
* The simulated time of this frame, in seconds.
|
|
46
|
+
*/
|
|
47
|
+
readonly time: number;
|
|
48
|
+
/**
|
|
49
|
+
* Particle positions as a packed `x, y, z` buffer.
|
|
50
|
+
*/
|
|
51
|
+
readonly positions: Float32Array;
|
|
52
|
+
/**
|
|
53
|
+
* Per-particle speed (velocity magnitude).
|
|
54
|
+
*/
|
|
55
|
+
readonly speeds: Float32Array;
|
|
56
|
+
/**
|
|
57
|
+
* The number of particles in the swarm.
|
|
58
|
+
*/
|
|
59
|
+
readonly particleCount: number;
|
|
60
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
const STRIDE = 3;
|
|
3
|
+
class PackedView {
|
|
4
|
+
buffer;
|
|
5
|
+
constructor(buffer) {
|
|
6
|
+
this.buffer = buffer;
|
|
7
|
+
}
|
|
8
|
+
get count() {
|
|
9
|
+
return this.buffer.length / STRIDE;
|
|
10
|
+
}
|
|
11
|
+
x(particle) {
|
|
12
|
+
return this.read(particle, 0);
|
|
13
|
+
}
|
|
14
|
+
y(particle) {
|
|
15
|
+
return this.read(particle, 1);
|
|
16
|
+
}
|
|
17
|
+
z(particle) {
|
|
18
|
+
return this.read(particle, 2);
|
|
19
|
+
}
|
|
20
|
+
at(particle) {
|
|
21
|
+
return [this.x(particle), this.y(particle), this.z(particle)];
|
|
22
|
+
}
|
|
23
|
+
set(particle, position) {
|
|
24
|
+
const base = particle * STRIDE;
|
|
25
|
+
this.buffer[base] = position[0];
|
|
26
|
+
this.buffer[base + 1] = position[1];
|
|
27
|
+
this.buffer[base + 2] = position[2];
|
|
28
|
+
}
|
|
29
|
+
read(particle, axis) {
|
|
30
|
+
const value = this.buffer[particle * STRIDE + axis];
|
|
31
|
+
assert(value !== undefined, `particle ${particle} is out of range in a position buffer of ${this.buffer.length} floats`);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export var PackedPositions;
|
|
36
|
+
(function (PackedPositions) {
|
|
37
|
+
/**
|
|
38
|
+
* Wraps a packed position buffer so it can be read and written by particle.
|
|
39
|
+
*/
|
|
40
|
+
function of(buffer) {
|
|
41
|
+
return new PackedView(buffer);
|
|
42
|
+
}
|
|
43
|
+
PackedPositions.of = of;
|
|
44
|
+
/**
|
|
45
|
+
* Allocates a packed position buffer sized to hold `count` particles.
|
|
46
|
+
*/
|
|
47
|
+
function allocate(count) {
|
|
48
|
+
return new Float32Array(count * STRIDE);
|
|
49
|
+
}
|
|
50
|
+
PackedPositions.allocate = allocate;
|
|
51
|
+
})(PackedPositions || (PackedPositions = {}));
|
|
52
|
+
//# sourceMappingURL=frame.js.map
|