three-ilda 0.1.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/CREDITS.md +6 -0
- package/LICENSE +21 -0
- package/README.md +354 -0
- package/package.json +93 -0
- package/src/core/LaserScanner.js +97 -0
- package/src/core/LaserShowBase.js +224 -0
- package/src/index.js +10 -0
- package/src/loaders/ILDALoader.js +160 -0
- package/src/objects/LaserShow.js +25 -0
- package/src/postprocessing/LaserShowPipeline.js +119 -0
- package/src/webgl/LaserShow.js +85 -0
- package/src/webgl/LaserShowPipeline.js +168 -0
- package/src/webgl.js +10 -0
- package/types/core/LaserScanner.d.ts +85 -0
- package/types/core/LaserShowBase.d.ts +166 -0
- package/types/index.d.ts +10 -0
- package/types/loaders/ILDALoader.d.ts +124 -0
- package/types/objects/LaserShow.d.ts +19 -0
- package/types/postprocessing/LaserShowPipeline.d.ts +89 -0
- package/types/webgl/LaserShow.d.ts +28 -0
- package/types/webgl/LaserShowPipeline.d.ts +99 -0
- package/types/webgl.d.ts +10 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Color, DepthTexture, HalfFloatType, Layers, NoBlending, ShaderMaterial, Vector2, WebGLRenderTarget } from 'three'
|
|
2
|
+
import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'
|
|
3
|
+
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
|
|
4
|
+
|
|
5
|
+
const QUAD_VERTEX = /* glsl */`
|
|
6
|
+
varying vec2 vUv;
|
|
7
|
+
void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`
|
|
8
|
+
|
|
9
|
+
// Separable Gaussian with sigma 3 texels, the GaussianBlurNode settings of the node pipeline.
|
|
10
|
+
function blurFragment(sigma = 3, radius = 8) {
|
|
11
|
+
const weights = []; let sum = 0
|
|
12
|
+
for (let i = 0; i <= radius; i++) { const w = Math.exp(-(i * i) / (2 * sigma * sigma)); weights.push(w); sum += i ? 2 * w : w }
|
|
13
|
+
const taps = weights.map((w, i) => i
|
|
14
|
+
? `sum += (texture2D(tDiffuse, vUv + direction * ${i}.0) + texture2D(tDiffuse, vUv - direction * ${i}.0)) * ${(w / sum).toFixed(6)};`
|
|
15
|
+
: `vec4 sum = texture2D(tDiffuse, vUv) * ${(w / sum).toFixed(6)};`)
|
|
16
|
+
return /* glsl */`
|
|
17
|
+
uniform sampler2D tDiffuse;
|
|
18
|
+
uniform vec2 direction;
|
|
19
|
+
varying vec2 vUv;
|
|
20
|
+
void main() {
|
|
21
|
+
${taps.join('\n ')}
|
|
22
|
+
gl_FragColor = sum;
|
|
23
|
+
}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const COMPOSITE_FRAGMENT = /* glsl */`
|
|
27
|
+
uniform sampler2D tScene, tBeams;
|
|
28
|
+
uniform float haze;
|
|
29
|
+
varying vec2 vUv;
|
|
30
|
+
void main() {
|
|
31
|
+
vec4 scene = texture2D(tScene, vUv);
|
|
32
|
+
vec3 beams = texture2D(tBeams, vUv).rgb;
|
|
33
|
+
float protect = 1.0 - smoothstep(0.025, 0.3, max(scene.r, max(scene.g, scene.b)));
|
|
34
|
+
gl_FragColor = vec4(scene.rgb + (1.0 - exp(-beams)) * 0.14 * protect * haze, scene.a);
|
|
35
|
+
#include <tonemapping_fragment>
|
|
36
|
+
#include <colorspace_fragment>
|
|
37
|
+
}`
|
|
38
|
+
|
|
39
|
+
/** @typedef {import('../core/LaserShowBase.js').LaserShowBase} LaserShowBase */
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} LaserShowPipelineOptions
|
|
42
|
+
* @property {number} [beamLayer] Layer reserved for beam meshes, 1 to 31 (default 31).
|
|
43
|
+
* @property {number} [strength] Bloom strength (default 15).
|
|
44
|
+
* @property {number} [radius] Bloom radius (default 1).
|
|
45
|
+
* @property {number} [threshold] Bloom luminance threshold (default 0).
|
|
46
|
+
* @property {number} [samples] MSAA samples of the scene pass (default 4).
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Post-processing for THREE.WebGLRenderer (the WebGL 2 version) with the same structure as the node pipeline:
|
|
51
|
+
* scene pass with a depth texture, bloom, a half-resolution beam pass faded by scene depth, blur and haze compositing.
|
|
52
|
+
* Uses the host's scene and camera; reserves beamLayer (default 31) for registered beam meshes.
|
|
53
|
+
*/
|
|
54
|
+
export class LaserShowPipeline {
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('three').WebGLRenderer} renderer
|
|
57
|
+
* @param {import('three').Scene} scene
|
|
58
|
+
* @param {import('three').Camera} camera
|
|
59
|
+
* @param {LaserShowPipelineOptions} [options]
|
|
60
|
+
*/
|
|
61
|
+
constructor(renderer, scene, camera, { beamLayer = 31, strength = 15, radius = 1, threshold = 0, samples = 4 } = {}) {
|
|
62
|
+
if (!Number.isInteger(beamLayer) || beamLayer < 1 || beamLayer > 31) throw new RangeError('beamLayer must be an integer from 1 to 31.')
|
|
63
|
+
this.renderer = renderer; this.scene = scene; this.camera = camera; this.beamLayer = beamLayer
|
|
64
|
+
/** @type {Map<LaserShowBase, { mask: number, uniforms: Record<string, { value: unknown }>, onDispose: () => void }>} */
|
|
65
|
+
this.shows = new Map()
|
|
66
|
+
this.disposed = false
|
|
67
|
+
this._size = new Vector2(); this._clear = new Color(); this._beamLayers = new Layers(); this._beamLayers.set(beamLayer)
|
|
68
|
+
this.sceneTarget = new WebGLRenderTarget(1, 1, { type: HalfFloatType, samples, depthTexture: new DepthTexture(1, 1) })
|
|
69
|
+
this.beamTarget = new WebGLRenderTarget(1, 1, { type: HalfFloatType, depthBuffer: false })
|
|
70
|
+
this.blurTarget = new WebGLRenderTarget(1, 1, { type: HalfFloatType, depthBuffer: false })
|
|
71
|
+
this.glow = new UnrealBloomPass(new Vector2(1, 1), strength, radius, threshold)
|
|
72
|
+
// UnrealBloomPass scales its composite by 3 "for backwards compatibility"; the TSL BloomNode does not.
|
|
73
|
+
// Drop the factor so glow.strength means the same in both versions (measured before this: WebGL 2 at 5 matched TSL at 15).
|
|
74
|
+
const composite = this.glow.compositeMaterial
|
|
75
|
+
if (composite.fragmentShader.includes('3.0 * bloomStrength')) { composite.fragmentShader = composite.fragmentShader.replace('3.0 * bloomStrength', 'bloomStrength'); composite.needsUpdate = true }
|
|
76
|
+
this._blur = new FullScreenQuad(new ShaderMaterial({
|
|
77
|
+
uniforms: { tDiffuse: { value: null }, direction: { value: new Vector2() } },
|
|
78
|
+
vertexShader: QUAD_VERTEX, fragmentShader: blurFragment(), depthTest: false, depthWrite: false, blending: NoBlending,
|
|
79
|
+
}))
|
|
80
|
+
this._composite = new FullScreenQuad(new ShaderMaterial({
|
|
81
|
+
uniforms: { tScene: { value: this.sceneTarget.texture }, tBeams: { value: this.beamTarget.texture }, haze: { value: 0 } },
|
|
82
|
+
vertexShader: QUAD_VERTEX, fragmentShader: COMPOSITE_FRAGMENT, depthTest: false, depthWrite: false, blending: NoBlending,
|
|
83
|
+
}))
|
|
84
|
+
// Shared by every registered beam material and refreshed on each render.
|
|
85
|
+
this._depth = {
|
|
86
|
+
tDepth: { value: this.sceneTarget.depthTexture }, resolution: { value: new Vector2(1, 1) },
|
|
87
|
+
cameraNear: { value: camera.near }, cameraFar: { value: camera.far }, orthographic: { value: camera.isOrthographicCamera === true },
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @param {LaserShowBase} show @returns {this} */
|
|
92
|
+
add(show) {
|
|
93
|
+
if (this.shows.has(show)) return this
|
|
94
|
+
if (!show.isLaserShow || show.disposed) throw new TypeError('Expected a live LaserShow.')
|
|
95
|
+
const material = show.beams.material
|
|
96
|
+
if (!material.isShaderMaterial) throw new TypeError('Expected a LaserShow from the three-ilda/webgl entry.')
|
|
97
|
+
if (show.beams.userData.laserPipeline) throw new Error('This LaserShow already belongs to another LaserShowPipeline.')
|
|
98
|
+
const saved = { mask: show.beams.layers.mask, uniforms: {}, onDispose: () => this.remove(show) }
|
|
99
|
+
for (const key of Object.keys(this._depth)) { saved.uniforms[key] = material.uniforms[key]; material.uniforms[key] = this._depth[key] }
|
|
100
|
+
material.defines.DEPTH_FADE = ''; material.needsUpdate = true
|
|
101
|
+
show.beams.layers.set(this.beamLayer); show.beams.userData.laserPipeline = this
|
|
102
|
+
show.addEventListener('dispose', saved.onDispose); this.shows.set(show, saved)
|
|
103
|
+
return this
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @param {LaserShowBase} show @returns {this} */
|
|
107
|
+
remove(show) {
|
|
108
|
+
const saved = this.shows.get(show)
|
|
109
|
+
if (!saved) return this
|
|
110
|
+
const material = show.beams.material
|
|
111
|
+
for (const key of Object.keys(saved.uniforms)) material.uniforms[key] = saved.uniforms[key]
|
|
112
|
+
delete material.defines.DEPTH_FADE; material.needsUpdate = true
|
|
113
|
+
show.beams.layers.mask = saved.mask; delete show.beams.userData.laserPipeline
|
|
114
|
+
show.removeEventListener('dispose', saved.onDispose); this.shows.delete(show)
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_resize(width, height) {
|
|
119
|
+
this.sceneTarget.setSize(width, height)
|
|
120
|
+
const w = Math.max(1, Math.round(width / 2)), h = Math.max(1, Math.round(height / 2))
|
|
121
|
+
this.beamTarget.setSize(w, h); this.blurTarget.setSize(w, h)
|
|
122
|
+
this.glow.setSize(width, height)
|
|
123
|
+
this._depth.resolution.value.set(w, h)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
render() {
|
|
127
|
+
if (this.disposed) return
|
|
128
|
+
const { renderer, scene, camera } = this
|
|
129
|
+
renderer.getDrawingBufferSize(this._size)
|
|
130
|
+
const width = Math.max(1, Math.floor(this._size.x)), height = Math.max(1, Math.floor(this._size.y))
|
|
131
|
+
if (width !== this.sceneTarget.width || height !== this.sceneTarget.height) this._resize(width, height)
|
|
132
|
+
let beams = false
|
|
133
|
+
for (const show of this.shows.keys()) {
|
|
134
|
+
let visible = show.beams.visible && show.beamIntensity > 0
|
|
135
|
+
for (let parent = show; parent; parent = parent.parent) visible &&= parent.visible
|
|
136
|
+
if (visible) { beams = true; break }
|
|
137
|
+
}
|
|
138
|
+
const target = renderer.getRenderTarget(), mask = camera.layers.mask, autoClear = renderer.autoClear
|
|
139
|
+
renderer.autoClear = true
|
|
140
|
+
camera.layers.disable(this.beamLayer)
|
|
141
|
+
renderer.setRenderTarget(this.sceneTarget); renderer.render(scene, camera)
|
|
142
|
+
this.glow.render(renderer, null, this.sceneTarget, 0, false) // adds bloom in place
|
|
143
|
+
if (beams) {
|
|
144
|
+
this._depth.cameraNear.value = camera.near; this._depth.cameraFar.value = camera.far; this._depth.orthographic.value = camera.isOrthographicCamera === true
|
|
145
|
+
const background = scene.background, alpha = renderer.getClearAlpha(); renderer.getClearColor(this._clear)
|
|
146
|
+
scene.background = null; renderer.setClearColor(0x000000, 0); camera.layers.mask = this._beamLayers.mask
|
|
147
|
+
renderer.setRenderTarget(this.beamTarget); renderer.render(scene, camera)
|
|
148
|
+
scene.background = background; renderer.setClearColor(this._clear, alpha)
|
|
149
|
+
const blur = this._blur.material.uniforms
|
|
150
|
+
blur.tDiffuse.value = this.beamTarget.texture; blur.direction.value.set(1 / this.beamTarget.width, 0)
|
|
151
|
+
renderer.setRenderTarget(this.blurTarget); this._blur.render(renderer)
|
|
152
|
+
blur.tDiffuse.value = this.blurTarget.texture; blur.direction.value.set(0, 1 / this.beamTarget.height)
|
|
153
|
+
renderer.setRenderTarget(this.beamTarget); this._blur.render(renderer)
|
|
154
|
+
}
|
|
155
|
+
camera.layers.mask = mask
|
|
156
|
+
this._composite.material.uniforms.haze.value = beams ? 1 : 0
|
|
157
|
+
renderer.setRenderTarget(target); this._composite.render(renderer)
|
|
158
|
+
renderer.autoClear = autoClear
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
dispose() {
|
|
162
|
+
if (this.disposed) return
|
|
163
|
+
for (const show of [...this.shows.keys()]) this.remove(show)
|
|
164
|
+
this.sceneTarget.depthTexture.dispose(); this.sceneTarget.dispose(); this.beamTarget.dispose(); this.blurTarget.dispose()
|
|
165
|
+
this.glow.dispose(); this._blur.material.dispose(); this._blur.dispose(); this._composite.material.dispose(); this._composite.dispose()
|
|
166
|
+
this.disposed = true
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/webgl.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @typedef {import('./loaders/ILDALoader.js').ILDAAsset} ILDAAsset */
|
|
2
|
+
/** @typedef {import('./loaders/ILDALoader.js').ILDAFrame} ILDAFrame */
|
|
3
|
+
/** @typedef {import('./core/LaserScanner.js').ScannerParams} ScannerParams */
|
|
4
|
+
/** @typedef {import('./core/LaserShowBase.js').LaserShowOptions} LaserShowOptions */
|
|
5
|
+
/** @typedef {import('./core/LaserShowBase.js').BeamMode} BeamMode */
|
|
6
|
+
/** @typedef {import('./webgl/LaserShowPipeline.js').LaserShowPipelineOptions} LaserShowPipelineOptions */
|
|
7
|
+
export { ILDALoader, ILDA_DEFAULT_PALETTE } from './loaders/ILDALoader.js'
|
|
8
|
+
export { LaserScanner, SCANNER_DEFAULTS, TRAIL_POINTS } from './core/LaserScanner.js'
|
|
9
|
+
export { LaserShow } from './webgl/LaserShow.js'
|
|
10
|
+
export { LaserShowPipeline } from './webgl/LaserShowPipeline.js'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ScannerParams
|
|
3
|
+
* @property {number} rate Samples per second.
|
|
4
|
+
* @property {number} gain Mirror spring constant.
|
|
5
|
+
* @property {number} dampening Fraction of velocity kept per sample (larger = less damping).
|
|
6
|
+
* @property {number} blankingOffset Blank-flag offset in points.
|
|
7
|
+
* @property {number} lightDecay Afterglow factor per 1/60 s.
|
|
8
|
+
*/
|
|
9
|
+
export const TRAIL_POINTS: 2048;
|
|
10
|
+
/** @type {Readonly<ScannerParams>} */
|
|
11
|
+
export const SCANNER_DEFAULTS: Readonly<ScannerParams>;
|
|
12
|
+
/** The demo's per-sample galvo recurrence, with a fixed-size ring instead of shifting/allocating arrays each frame. */
|
|
13
|
+
export class LaserScanner {
|
|
14
|
+
/**
|
|
15
|
+
* @param {import('../loaders/ILDALoader.js').ILDAFrame[]} frames Frames of one projector track.
|
|
16
|
+
* @param {number} [capacity] Ring size in samples.
|
|
17
|
+
*/
|
|
18
|
+
constructor(frames: import("../loaders/ILDALoader.js").ILDAFrame[], capacity?: number);
|
|
19
|
+
frames: import("../loaders/ILDALoader.js").ILDAFrame[];
|
|
20
|
+
capacity: number;
|
|
21
|
+
starts: number[];
|
|
22
|
+
total: number;
|
|
23
|
+
position: Float32Array<ArrayBuffer>;
|
|
24
|
+
color: Float32Array<ArrayBuffer>;
|
|
25
|
+
blank: Uint8Array<ArrayBuffer>;
|
|
26
|
+
born: Float64Array<ArrayBuffer>;
|
|
27
|
+
output: {
|
|
28
|
+
position: Float32Array<ArrayBuffer>;
|
|
29
|
+
color: Float32Array<ArrayBuffer>;
|
|
30
|
+
blank: Uint8Array<ArrayBuffer>;
|
|
31
|
+
count: number;
|
|
32
|
+
};
|
|
33
|
+
/** @param {number} [frame] */
|
|
34
|
+
reset(frame?: number): void;
|
|
35
|
+
frame: any;
|
|
36
|
+
index: number;
|
|
37
|
+
lastFrame: any;
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
vx: any;
|
|
41
|
+
vy: any;
|
|
42
|
+
time: number;
|
|
43
|
+
fraction: number;
|
|
44
|
+
write: any;
|
|
45
|
+
count: any;
|
|
46
|
+
samples: number;
|
|
47
|
+
get point(): number;
|
|
48
|
+
/** @param {number} offset @returns {number} */
|
|
49
|
+
blankAtOffset(offset: number): number;
|
|
50
|
+
/** @param {ScannerParams} params @param {number} born */
|
|
51
|
+
sample(params: ScannerParams, born: number): void;
|
|
52
|
+
/** @param {number} dt Seconds. @param {ScannerParams} params @returns {number} Samples produced. */
|
|
53
|
+
advance(dt: number, params: ScannerParams): number;
|
|
54
|
+
/** @param {number} frame @param {ScannerParams} params */
|
|
55
|
+
seekFrame(frame: number, params: ScannerParams): void;
|
|
56
|
+
/** @param {number} lightDecay */
|
|
57
|
+
snapshot(lightDecay: number): {
|
|
58
|
+
position: Float32Array<ArrayBuffer>;
|
|
59
|
+
color: Float32Array<ArrayBuffer>;
|
|
60
|
+
blank: Uint8Array<ArrayBuffer>;
|
|
61
|
+
count: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export type ScannerParams = {
|
|
65
|
+
/**
|
|
66
|
+
* Samples per second.
|
|
67
|
+
*/
|
|
68
|
+
rate: number;
|
|
69
|
+
/**
|
|
70
|
+
* Mirror spring constant.
|
|
71
|
+
*/
|
|
72
|
+
gain: number;
|
|
73
|
+
/**
|
|
74
|
+
* Fraction of velocity kept per sample (larger = less damping).
|
|
75
|
+
*/
|
|
76
|
+
dampening: number;
|
|
77
|
+
/**
|
|
78
|
+
* Blank-flag offset in points.
|
|
79
|
+
*/
|
|
80
|
+
blankingOffset: number;
|
|
81
|
+
/**
|
|
82
|
+
* Afterglow factor per 1/60 s.
|
|
83
|
+
*/
|
|
84
|
+
lightDecay: number;
|
|
85
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** @typedef {import('../loaders/ILDALoader.js').ILDAAsset} ILDAAsset */
|
|
2
|
+
/** @typedef {'Disabled' | 'Front' | 'Back'} BeamMode */
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} LaserShowOptions
|
|
5
|
+
* @property {number} [track] Projector track to play; defaults to the first track of the asset.
|
|
6
|
+
* @property {number} [capacity] Trail length in samples, 2 to 65536 (default 2048).
|
|
7
|
+
* @property {number} [width] Projection width in local units at zoom 1 (default 4.8).
|
|
8
|
+
* @property {number} [height] Projection height in local units at zoom 1 (default 3.2).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Renderer-independent part of a laser show: scene graph, scanner playback, segment compaction and buffer uploads.
|
|
12
|
+
* The image lies in local XY at Z=0; Front is +Z, Back is -Z, regardless of the camera.
|
|
13
|
+
* One instance plays one ILDA projector track. Assets may be shared; playback/GPU buffers are independent.
|
|
14
|
+
* A subclass supplies the two materials through _createMaterials(); the uniform handles it returns only need a `.value`.
|
|
15
|
+
*/
|
|
16
|
+
export class LaserShowBase extends Group<import("three").Object3DEventMap> {
|
|
17
|
+
/**
|
|
18
|
+
* @param {ILDAAsset | null} [asset] Parsed ILDA file; call setData() later when omitted.
|
|
19
|
+
* @param {LaserShowOptions} [options]
|
|
20
|
+
*/
|
|
21
|
+
constructor(asset?: ILDAAsset | null, { track, capacity, width, height }?: LaserShowOptions);
|
|
22
|
+
type: string;
|
|
23
|
+
isLaserShow: boolean;
|
|
24
|
+
capacity: number;
|
|
25
|
+
playing: boolean;
|
|
26
|
+
disposed: boolean;
|
|
27
|
+
/** @type {import('./LaserScanner.js').ScannerParams} */
|
|
28
|
+
_params: import("./LaserScanner.js").ScannerParams;
|
|
29
|
+
/** @type {BeamMode} */
|
|
30
|
+
_beamMode: BeamMode;
|
|
31
|
+
_zoom: number;
|
|
32
|
+
_dirty: boolean;
|
|
33
|
+
/** @type {ILDAAsset | null} */ asset: ILDAAsset | null;
|
|
34
|
+
/** @type {number | undefined} */ track: number | undefined;
|
|
35
|
+
/** @type {LaserScanner | null} */ scanner: LaserScanner | null;
|
|
36
|
+
_projectionSize: Vector2;
|
|
37
|
+
_scale: {
|
|
38
|
+
value: import("three").Vector3;
|
|
39
|
+
};
|
|
40
|
+
_origin: {
|
|
41
|
+
value: import("three").Vector3;
|
|
42
|
+
};
|
|
43
|
+
_clock: {
|
|
44
|
+
value: number;
|
|
45
|
+
};
|
|
46
|
+
_intensity: {
|
|
47
|
+
value: number;
|
|
48
|
+
};
|
|
49
|
+
_beamIntensity: {
|
|
50
|
+
value: number;
|
|
51
|
+
};
|
|
52
|
+
projector: Object3D<import("three").Object3DEventMap>;
|
|
53
|
+
_linePosition: BufferAttribute<import("three").BufferAttributeEventMap>;
|
|
54
|
+
_lineColor: BufferAttribute<import("three").BufferAttributeEventMap>;
|
|
55
|
+
projection: LineSegments<BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>, import("three").Material<import("three").MaterialEventMap>, import("three").Object3DEventMap>;
|
|
56
|
+
_beamPosition: BufferAttribute<import("three").BufferAttributeEventMap>;
|
|
57
|
+
_beamColor: BufferAttribute<import("three").BufferAttributeEventMap>;
|
|
58
|
+
beams: Mesh<BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>, import("three").Material<import("three").MaterialEventMap>, import("three").Object3DEventMap>;
|
|
59
|
+
/**
|
|
60
|
+
* Creates the line and beam materials for one renderer family.
|
|
61
|
+
* @returns {{ line: import('three').Material, beam: import('three').Material, scale: { value: import('three').Vector3 }, origin: { value: import('three').Vector3 }, clock: { value: number }, intensity: { value: number }, beamIntensity: { value: number } }}
|
|
62
|
+
*/
|
|
63
|
+
_createMaterials(): {
|
|
64
|
+
line: import("three").Material;
|
|
65
|
+
beam: import("three").Material;
|
|
66
|
+
scale: {
|
|
67
|
+
value: import("three").Vector3;
|
|
68
|
+
};
|
|
69
|
+
origin: {
|
|
70
|
+
value: import("three").Vector3;
|
|
71
|
+
};
|
|
72
|
+
clock: {
|
|
73
|
+
value: number;
|
|
74
|
+
};
|
|
75
|
+
intensity: {
|
|
76
|
+
value: number;
|
|
77
|
+
};
|
|
78
|
+
beamIntensity: {
|
|
79
|
+
value: number;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
set pointRate(v: number);
|
|
83
|
+
get pointRate(): number;
|
|
84
|
+
set gain(v: number);
|
|
85
|
+
get gain(): number;
|
|
86
|
+
set dampening(v: number);
|
|
87
|
+
get dampening(): number;
|
|
88
|
+
set blankingOffset(v: number);
|
|
89
|
+
get blankingOffset(): number;
|
|
90
|
+
set lightDecay(v: number);
|
|
91
|
+
get lightDecay(): number;
|
|
92
|
+
set zoom(v: number);
|
|
93
|
+
get zoom(): number;
|
|
94
|
+
set intensity(v: number);
|
|
95
|
+
get intensity(): number;
|
|
96
|
+
set beamIntensity(v: number);
|
|
97
|
+
get beamIntensity(): number;
|
|
98
|
+
/** @param {BeamMode} mode */
|
|
99
|
+
set beamMode(mode: BeamMode);
|
|
100
|
+
get beamMode(): BeamMode;
|
|
101
|
+
get frame(): any;
|
|
102
|
+
get frameCount(): number;
|
|
103
|
+
get time(): number;
|
|
104
|
+
get trackIds(): number[];
|
|
105
|
+
get projectionSize(): Vector2;
|
|
106
|
+
/** @param {number} width @param {number} height @returns {this} */
|
|
107
|
+
setProjectionSize(width: number, height: number): this;
|
|
108
|
+
_updateScale(): void;
|
|
109
|
+
_retune(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Replaces the played file, building a fresh scanner for one projector track.
|
|
112
|
+
* @param {ILDAAsset} asset
|
|
113
|
+
* @param {number} [track] Defaults to the first track of the asset.
|
|
114
|
+
* @returns {this}
|
|
115
|
+
*/
|
|
116
|
+
setData(asset: ILDAAsset, track?: number): this;
|
|
117
|
+
/** @param {number} track @returns {this} */
|
|
118
|
+
setTrack(track: number): this;
|
|
119
|
+
/** @param {number} frame @returns {this} */
|
|
120
|
+
seekFrame(frame: number): this;
|
|
121
|
+
reset(): this;
|
|
122
|
+
/**
|
|
123
|
+
* Advances playback and uploads the trail. Call once per application frame, including while paused to apply parameter changes.
|
|
124
|
+
* @param {number} [delta] Seconds since the previous call.
|
|
125
|
+
* @returns {this}
|
|
126
|
+
*/
|
|
127
|
+
update(delta?: number): this;
|
|
128
|
+
/**
|
|
129
|
+
* Clone shares the immutable asset, but copies scanner history and allocates independent GPU resources.
|
|
130
|
+
* @param {boolean} [recursive]
|
|
131
|
+
* @returns {this}
|
|
132
|
+
*/
|
|
133
|
+
clone(recursive?: boolean): this;
|
|
134
|
+
/** @param {LaserShowBase} source @param {boolean} [recursive] @returns {this} */
|
|
135
|
+
copy(source: LaserShowBase, recursive?: boolean): this;
|
|
136
|
+
/** Releases only this instance's GPU resources; shared ILDA data stays usable. */
|
|
137
|
+
dispose(): void;
|
|
138
|
+
}
|
|
139
|
+
export type ILDAAsset = import("../loaders/ILDALoader.js").ILDAAsset;
|
|
140
|
+
export type BeamMode = "Disabled" | "Front" | "Back";
|
|
141
|
+
export type LaserShowOptions = {
|
|
142
|
+
/**
|
|
143
|
+
* Projector track to play; defaults to the first track of the asset.
|
|
144
|
+
*/
|
|
145
|
+
track?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Trail length in samples, 2 to 65536 (default 2048).
|
|
148
|
+
*/
|
|
149
|
+
capacity?: number;
|
|
150
|
+
/**
|
|
151
|
+
* Projection width in local units at zoom 1 (default 4.8).
|
|
152
|
+
*/
|
|
153
|
+
width?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Projection height in local units at zoom 1 (default 3.2).
|
|
156
|
+
*/
|
|
157
|
+
height?: number;
|
|
158
|
+
};
|
|
159
|
+
import { Group } from 'three';
|
|
160
|
+
import { LaserScanner } from './LaserScanner.js';
|
|
161
|
+
import { Vector2 } from 'three';
|
|
162
|
+
import { Object3D } from 'three';
|
|
163
|
+
import { BufferAttribute } from 'three';
|
|
164
|
+
import { BufferGeometry } from 'three';
|
|
165
|
+
import { LineSegments } from 'three';
|
|
166
|
+
import { Mesh } from 'three';
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { LaserShow } from "./objects/LaserShow.js";
|
|
2
|
+
export { LaserShowPipeline } from "./postprocessing/LaserShowPipeline.js";
|
|
3
|
+
export type ILDAAsset = import("./loaders/ILDALoader.js").ILDAAsset;
|
|
4
|
+
export type ILDAFrame = import("./loaders/ILDALoader.js").ILDAFrame;
|
|
5
|
+
export type ScannerParams = import("./core/LaserScanner.js").ScannerParams;
|
|
6
|
+
export type LaserShowOptions = import("./core/LaserShowBase.js").LaserShowOptions;
|
|
7
|
+
export type BeamMode = import("./core/LaserShowBase.js").BeamMode;
|
|
8
|
+
export type LaserShowPipelineOptions = import("./postprocessing/LaserShowPipeline.js").LaserShowPipelineOptions;
|
|
9
|
+
export { ILDALoader, ILDA_DEFAULT_PALETTE } from "./loaders/ILDALoader.js";
|
|
10
|
+
export { LaserScanner, SCANNER_DEFAULTS, TRAIL_POINTS } from "./core/LaserScanner.js";
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ILDAFrame
|
|
3
|
+
* @property {number} number Frame number from the file.
|
|
4
|
+
* @property {string} name Frame name from the file.
|
|
5
|
+
* @property {number} projector Projector ID from the file (zero-based).
|
|
6
|
+
* @property {number} count Number of points.
|
|
7
|
+
* @property {Float32Array} position Interleaved XYZ, normalized by 32768. Z is zero for 2D formats.
|
|
8
|
+
* @property {Float32Array} color Interleaved RGB in [0, 1], including colors of blanked points.
|
|
9
|
+
* @property {Uint8Array} blank One flag per point: 1 means laser off.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} ILDAAsset
|
|
13
|
+
* @property {ILDAFrame[]} frames Point frames in file order; palette records are not frames.
|
|
14
|
+
* @property {Map<number, ILDAFrame[]>} tracks Frames grouped by projector ID, sharing objects with frames.
|
|
15
|
+
* @property {number} totalPoints Total number of points in all tracks.
|
|
16
|
+
* @property {number} blanked Number of blanked points.
|
|
17
|
+
* @property {number} paletteFallbacks Number of unknown palette indices rendered in white.
|
|
18
|
+
* @property {boolean} eof Whether an explicit end-of-file record was found.
|
|
19
|
+
* @property {number} bytes Input size in bytes.
|
|
20
|
+
* @property {number} parseTimeMs CPU parsing time; excludes downloading and local file reading.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* ILDA IDTF loader for formats 0, 1, 2, 4 and 5. No renderer, DOM or player dependency.
|
|
24
|
+
* Uses the standard Three.js LoadingManager, FileLoader and inherited loadAsync().
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const loader = new ILDALoader(manager);
|
|
28
|
+
* const show = await loader.loadAsync('/shows/example.ild');
|
|
29
|
+
* const localShow = loader.parse(await file.arrayBuffer());
|
|
30
|
+
*
|
|
31
|
+
* @extends Loader
|
|
32
|
+
*/
|
|
33
|
+
export class ILDALoader extends Loader<any, string> {
|
|
34
|
+
/** @param {import('three').LoadingManager} [manager] */
|
|
35
|
+
constructor(manager?: import("three").LoadingManager);
|
|
36
|
+
fallbackPalette: readonly (readonly number[])[];
|
|
37
|
+
/**
|
|
38
|
+
* Sets colors for indexed files without a format-2 palette. Embedded palettes always win.
|
|
39
|
+
* Copies the array; pass null to restore the standard ILDA palette.
|
|
40
|
+
* @param {ReadonlyArray<ReadonlyArray<number>> | null} palette Between 1 and 256 RGB triplets of integer bytes.
|
|
41
|
+
* @returns {ILDALoader}
|
|
42
|
+
*/
|
|
43
|
+
setFallbackPalette(palette: ReadonlyArray<ReadonlyArray<number>> | null): ILDALoader;
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} url URL or path, resolved by the loading manager.
|
|
46
|
+
* @param {(asset: ILDAAsset) => void} onLoad
|
|
47
|
+
* @param {(event: ProgressEvent) => void} [onProgress]
|
|
48
|
+
* @param {(error: Error) => void} [onError] Receives download and parsing errors.
|
|
49
|
+
*/
|
|
50
|
+
load(url: string, onLoad: (asset: ILDAAsset) => void, onProgress?: (event: ProgressEvent) => void, onError?: (error: Error) => void): void;
|
|
51
|
+
/**
|
|
52
|
+
* Parses already-loaded bytes synchronously. Does not create geometry or apply scanner settings.
|
|
53
|
+
* @param {ArrayBuffer} buffer
|
|
54
|
+
* @returns {ILDAAsset}
|
|
55
|
+
* @throws {Error} On invalid data, unsupported formats or the 32 MiB / 2M point limits.
|
|
56
|
+
*/
|
|
57
|
+
parse(buffer: ArrayBuffer): ILDAAsset;
|
|
58
|
+
}
|
|
59
|
+
export const ILDA_DEFAULT_PALETTE: readonly (readonly number[])[];
|
|
60
|
+
export type ILDAFrame = {
|
|
61
|
+
/**
|
|
62
|
+
* Frame number from the file.
|
|
63
|
+
*/
|
|
64
|
+
number: number;
|
|
65
|
+
/**
|
|
66
|
+
* Frame name from the file.
|
|
67
|
+
*/
|
|
68
|
+
name: string;
|
|
69
|
+
/**
|
|
70
|
+
* Projector ID from the file (zero-based).
|
|
71
|
+
*/
|
|
72
|
+
projector: number;
|
|
73
|
+
/**
|
|
74
|
+
* Number of points.
|
|
75
|
+
*/
|
|
76
|
+
count: number;
|
|
77
|
+
/**
|
|
78
|
+
* Interleaved XYZ, normalized by 32768. Z is zero for 2D formats.
|
|
79
|
+
*/
|
|
80
|
+
position: Float32Array;
|
|
81
|
+
/**
|
|
82
|
+
* Interleaved RGB in [0, 1], including colors of blanked points.
|
|
83
|
+
*/
|
|
84
|
+
color: Float32Array;
|
|
85
|
+
/**
|
|
86
|
+
* One flag per point: 1 means laser off.
|
|
87
|
+
*/
|
|
88
|
+
blank: Uint8Array;
|
|
89
|
+
};
|
|
90
|
+
export type ILDAAsset = {
|
|
91
|
+
/**
|
|
92
|
+
* Point frames in file order; palette records are not frames.
|
|
93
|
+
*/
|
|
94
|
+
frames: ILDAFrame[];
|
|
95
|
+
/**
|
|
96
|
+
* Frames grouped by projector ID, sharing objects with frames.
|
|
97
|
+
*/
|
|
98
|
+
tracks: Map<number, ILDAFrame[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Total number of points in all tracks.
|
|
101
|
+
*/
|
|
102
|
+
totalPoints: number;
|
|
103
|
+
/**
|
|
104
|
+
* Number of blanked points.
|
|
105
|
+
*/
|
|
106
|
+
blanked: number;
|
|
107
|
+
/**
|
|
108
|
+
* Number of unknown palette indices rendered in white.
|
|
109
|
+
*/
|
|
110
|
+
paletteFallbacks: number;
|
|
111
|
+
/**
|
|
112
|
+
* Whether an explicit end-of-file record was found.
|
|
113
|
+
*/
|
|
114
|
+
eof: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Input size in bytes.
|
|
117
|
+
*/
|
|
118
|
+
bytes: number;
|
|
119
|
+
/**
|
|
120
|
+
* CPU parsing time; excludes downloading and local file reading.
|
|
121
|
+
*/
|
|
122
|
+
parseTimeMs: number;
|
|
123
|
+
};
|
|
124
|
+
import { Loader } from 'three';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laser show for THREE.WebGPURenderer (the WebGPU / TSL version): node materials with the projection and beam math in TSL.
|
|
3
|
+
* Playback, buffers and the public API live in LaserShowBase; ../webgl/LaserShow.js is the WebGL 2 version for WebGLRenderer.
|
|
4
|
+
*/
|
|
5
|
+
export class LaserShow extends LaserShowBase {
|
|
6
|
+
_createMaterials(): {
|
|
7
|
+
line: LineBasicNodeMaterial;
|
|
8
|
+
beam: MeshBasicNodeMaterial;
|
|
9
|
+
scale: import("three/webgpu").UniformNode<"vec3", Vector3>;
|
|
10
|
+
origin: import("three/webgpu").UniformNode<"vec3", Vector3>;
|
|
11
|
+
clock: import("three/webgpu").UniformNode<"float", number>;
|
|
12
|
+
intensity: import("three/webgpu").UniformNode<"float", number>;
|
|
13
|
+
beamIntensity: import("three/webgpu").UniformNode<"float", number>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
import { LaserShowBase } from '../core/LaserShowBase.js';
|
|
17
|
+
import { LineBasicNodeMaterial } from 'three/webgpu';
|
|
18
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
19
|
+
import { Vector3 } from 'three/webgpu';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** @typedef {import('../core/LaserShowBase.js').LaserShowBase} LaserShowBase */
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} LaserShowPipelineOptions
|
|
4
|
+
* @property {number} [beamLayer] Layer reserved for beam meshes, 1 to 31 (default 31).
|
|
5
|
+
* @property {number} [strength] Bloom strength (default 15).
|
|
6
|
+
* @property {number} [radius] Bloom radius (default 1).
|
|
7
|
+
* @property {number} [threshold] Bloom luminance threshold (default 0).
|
|
8
|
+
* @property {number} [samples] MSAA samples of the scene pass (default 4).
|
|
9
|
+
* @property {boolean} [inspect] Label the passes for the r185 Inspector (default false).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Optional post-processing owned by the host application. Uses its scene, camera and renderer.
|
|
13
|
+
* Reserves beamLayer (default 31); all other objects keep their normal camera layers.
|
|
14
|
+
* One pipeline can serve multiple registered LaserShow objects in the same scene.
|
|
15
|
+
*/
|
|
16
|
+
export class LaserShowPipeline {
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('three/webgpu').WebGPURenderer} renderer
|
|
19
|
+
* @param {import('three').Scene} scene
|
|
20
|
+
* @param {import('three').Camera} camera
|
|
21
|
+
* @param {LaserShowPipelineOptions} [options]
|
|
22
|
+
*/
|
|
23
|
+
constructor(renderer: import("three/webgpu").WebGPURenderer, scene: import("three").Scene, camera: import("three").Camera, { beamLayer, strength, radius, threshold, samples, inspect }?: LaserShowPipelineOptions);
|
|
24
|
+
renderer: import("three/webgpu").WebGPURenderer;
|
|
25
|
+
camera: import("three").Camera;
|
|
26
|
+
beamLayer: number;
|
|
27
|
+
/** @type {Map<LaserShowBase, { mask: number, opacity: unknown, onDispose: () => void }>} */
|
|
28
|
+
shows: Map<LaserShowBase, {
|
|
29
|
+
mask: number;
|
|
30
|
+
opacity: unknown;
|
|
31
|
+
onDispose: () => void;
|
|
32
|
+
}>;
|
|
33
|
+
disposed: boolean;
|
|
34
|
+
_withBeams: boolean;
|
|
35
|
+
_baseLayers: Layers;
|
|
36
|
+
scenePass: PassNode;
|
|
37
|
+
_near: any;
|
|
38
|
+
_far: any;
|
|
39
|
+
_sceneViewZ: import("three/webgpu").Node<"float">;
|
|
40
|
+
beamPass: BeamPass;
|
|
41
|
+
glow: import("three/addons/tsl/display/BloomNode.js").default;
|
|
42
|
+
blur: import("three/addons/tsl/display/GaussianBlurNode.js").default;
|
|
43
|
+
projectionOutput: any;
|
|
44
|
+
beamOutput: import("three/webgpu").VarNode<"vec4", import("three/webgpu").ConstNode<"vec4", import("three").Vector4>>;
|
|
45
|
+
pipeline: RenderPipeline;
|
|
46
|
+
/** @param {LaserShowBase} show @returns {this} */
|
|
47
|
+
add(show: LaserShowBase): this;
|
|
48
|
+
/** @param {LaserShowBase} show @returns {this} */
|
|
49
|
+
remove(show: LaserShowBase): this;
|
|
50
|
+
render(): void;
|
|
51
|
+
dispose(): void;
|
|
52
|
+
}
|
|
53
|
+
export type LaserShowBase = import("../core/LaserShowBase.js").LaserShowBase;
|
|
54
|
+
export type LaserShowPipelineOptions = {
|
|
55
|
+
/**
|
|
56
|
+
* Layer reserved for beam meshes, 1 to 31 (default 31).
|
|
57
|
+
*/
|
|
58
|
+
beamLayer?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Bloom strength (default 15).
|
|
61
|
+
*/
|
|
62
|
+
strength?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Bloom radius (default 1).
|
|
65
|
+
*/
|
|
66
|
+
radius?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Bloom luminance threshold (default 0).
|
|
69
|
+
*/
|
|
70
|
+
threshold?: number;
|
|
71
|
+
/**
|
|
72
|
+
* MSAA samples of the scene pass (default 4).
|
|
73
|
+
*/
|
|
74
|
+
samples?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Label the passes for the r185 Inspector (default false).
|
|
77
|
+
*/
|
|
78
|
+
inspect?: boolean;
|
|
79
|
+
};
|
|
80
|
+
import { Layers } from 'three/webgpu';
|
|
81
|
+
import { PassNode } from 'three/webgpu';
|
|
82
|
+
declare class BeamPass extends PassNode {
|
|
83
|
+
constructor(scene: any, camera: any);
|
|
84
|
+
_clear: Color;
|
|
85
|
+
updateBefore(frame: any): void;
|
|
86
|
+
}
|
|
87
|
+
import { RenderPipeline } from 'three/webgpu';
|
|
88
|
+
import { Color } from 'three/webgpu';
|
|
89
|
+
export {};
|