@woosh/meep-engine 2.43.3 → 2.43.5
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/core/binary/BinaryBuffer.js +13 -1
- package/core/binary/BitSet.js +2 -2
- package/core/collection/array/array_range_equal_strict.js +22 -0
- package/core/collection/map/AsyncMapWrapper.js +13 -1
- package/core/collection/map/CachedAsyncMap.js +9 -2
- package/core/collection/map/CachedAsyncMap.spec.js +47 -0
- package/core/color/sRGB_to_linear.js +9 -4
- package/core/geom/3d/plane/orient3d_fast.js +3 -0
- package/core/geom/3d/plane/orient3d_robust.js +41 -0
- package/core/geom/3d/sphere/harmonics/README.md +15 -0
- package/core/geom/3d/sphere/harmonics/sh3_add.js +21 -0
- package/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +618 -0
- package/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +49 -0
- package/core/geom/3d/sphere/harmonics/sh3_sample_irradiance_by_direction.js +53 -0
- package/core/geom/3d/tetrahedra/TetrahedralMesh.js +251 -68
- package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +80 -3
- package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -0
- package/core/geom/3d/tetrahedra/delaunay/Cavity.js +5 -1
- package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +30 -31
- package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +54 -18
- package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +89 -0
- package/core/geom/3d/tetrahedra/delaunay/{tetrahedral_mesh_walk_toward_cavity.js → tetrahedral_mesh_walk_towards_containing_tetrahedron.js} +15 -12
- package/core/geom/3d/tetrahedra/delaunay/validate_cavity_boundary.js +60 -0
- package/core/geom/3d/tetrahedra/{point_in_tetrahedron_circumsphere.js → in_sphere_fast.js} +2 -4
- package/core/geom/3d/tetrahedra/in_sphere_robust.js +53 -0
- package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +44 -35
- package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +85 -38
- package/core/geom/3d/util/make_justified_point_grid.js +31 -0
- package/core/process/delay.js +5 -0
- package/editor/Editor.js +3 -0
- package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +195 -11
- package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.js +16 -0
- package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.js +4 -0
- package/engine/EngineHarness.js +11 -5
- package/engine/ecs/terrain/ecs/TerrainSystem.js +7 -1
- package/engine/ecs/transform/copy_three_transform.js +15 -0
- package/engine/graphics/ecs/light/Light.js +6 -1
- package/engine/graphics/ecs/light/LightSystem.d.ts +1 -1
- package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +2 -17
- package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
- package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +9 -1
- package/engine/graphics/sh3/LightProbeVolume.js +595 -0
- package/engine/graphics/sh3/SH3VisualisationMaterial.js +79 -0
- package/engine/graphics/sh3/prototypeSH3Probe.js +427 -0
- package/engine/graphics/sh3/visualise_probe.js +40 -0
- package/engine/graphics/texture/atlas/TextureAtlas.js +15 -3
- package/engine/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
- package/package.json +2 -1
- package/samples/terrain/from_image_2.js +127 -82
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +0 -224
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +0 -98
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { TetrahedralMesh } from "../../../core/geom/3d/tetrahedra/TetrahedralMesh.js";
|
|
2
|
+
import {
|
|
3
|
+
compute_delaunay_tetrahedral_mesh
|
|
4
|
+
} from "../../../core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js";
|
|
5
|
+
import { visualise_probe } from "./visualise_probe.js";
|
|
6
|
+
import {
|
|
7
|
+
build_tetrahedral_mesh_buffer_geometry
|
|
8
|
+
} from "../../../core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js";
|
|
9
|
+
import EntityBuilder from "../../ecs/EntityBuilder.js";
|
|
10
|
+
import { ShadedGeometry } from "../ecs/mesh-v2/ShadedGeometry.js";
|
|
11
|
+
import {
|
|
12
|
+
CubeCamera,
|
|
13
|
+
LinearEncoding,
|
|
14
|
+
LinearFilter,
|
|
15
|
+
LineBasicMaterial,
|
|
16
|
+
RGBAFormat,
|
|
17
|
+
Scene,
|
|
18
|
+
WebGLCubeRenderTarget
|
|
19
|
+
} from "three";
|
|
20
|
+
import { DrawMode } from "../ecs/mesh-v2/DrawMode.js";
|
|
21
|
+
import { Transform } from "../../ecs/transform/Transform.js";
|
|
22
|
+
import { array_copy } from "../../../core/collection/array/copyArray.js";
|
|
23
|
+
import { SurfacePoint3 } from "../../../core/geom/3d/SurfacePoint3.js";
|
|
24
|
+
import { Light } from "../ecs/light/Light.js";
|
|
25
|
+
import { threeMakeLight } from "../ecs/light/binding/three/threeMakeLight.js";
|
|
26
|
+
import { ThreeLightCache } from "../ecs/light/three/ThreeLightCache.js";
|
|
27
|
+
import { applyTransformToThreeObject } from "../ecs/mesh/applyTransformToThreeObject.js";
|
|
28
|
+
import { build_three_object } from "../ecs/mesh-v2/build_three_object.js";
|
|
29
|
+
import { ShadedGeometryFlags } from "../ecs/mesh-v2/ShadedGeometryFlags.js";
|
|
30
|
+
import { v3_length_sqr } from "../../../core/geom/v3_length_sqr.js";
|
|
31
|
+
|
|
32
|
+
const TEMP_CONTACT = new SurfacePoint3();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* evaluate the basis functions
|
|
36
|
+
* shBasis is an Array[ 9 ]
|
|
37
|
+
*
|
|
38
|
+
* @param {number} x
|
|
39
|
+
* @param {number} y
|
|
40
|
+
* @param {number} z
|
|
41
|
+
* @param {number[]|ArrayLike<number>|Float32Array|Float64Array} shBasis
|
|
42
|
+
*/
|
|
43
|
+
function getBasisAt(x, y, z, shBasis) {
|
|
44
|
+
|
|
45
|
+
// normal is assumed to be unit length
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// band 0
|
|
49
|
+
shBasis[0] = 0.282095;
|
|
50
|
+
|
|
51
|
+
// band 1
|
|
52
|
+
shBasis[1] = 0.488603 * y;
|
|
53
|
+
shBasis[2] = 0.488603 * z;
|
|
54
|
+
shBasis[3] = 0.488603 * x;
|
|
55
|
+
|
|
56
|
+
// band 2
|
|
57
|
+
shBasis[4] = 1.092548 * x * y;
|
|
58
|
+
shBasis[5] = 1.092548 * y * z;
|
|
59
|
+
shBasis[6] = 0.315392 * (3 * z * z - 1);
|
|
60
|
+
shBasis[7] = 1.092548 * x * z;
|
|
61
|
+
shBasis[8] = 0.546274 * (x * x - y * y);
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param {Uint8Array} data
|
|
68
|
+
* @param {THREE.WebGLRenderer} renderer
|
|
69
|
+
* @param {THREE.WebGLCubeRenderTarget} cubeRenderTarget
|
|
70
|
+
* @return {Float64Array}
|
|
71
|
+
*/
|
|
72
|
+
function fromCubeRenderTarget(data, renderer, cubeRenderTarget) {
|
|
73
|
+
|
|
74
|
+
// The renderTarget must be set to RGBA in order to make readRenderTargetPixels works
|
|
75
|
+
let total_weight = 0;
|
|
76
|
+
|
|
77
|
+
const __shared_buffer = new ArrayBuffer((27 + 9) * 8);
|
|
78
|
+
|
|
79
|
+
const sh_basis = new Float64Array(__shared_buffer, 0, 9);
|
|
80
|
+
const coefficients = new Float64Array(__shared_buffer, 9 * 8, 27);
|
|
81
|
+
|
|
82
|
+
const image_size = cubeRenderTarget.width;
|
|
83
|
+
|
|
84
|
+
const pixel_size = 2 / image_size;
|
|
85
|
+
|
|
86
|
+
let normal_x = 0, normal_y = 0, normal_z = 0;
|
|
87
|
+
|
|
88
|
+
for (let faceIndex = 0; faceIndex < 6; faceIndex++) {
|
|
89
|
+
|
|
90
|
+
renderer.readRenderTargetPixels(cubeRenderTarget, 0, 0, image_size, image_size, data, faceIndex);
|
|
91
|
+
|
|
92
|
+
// RGBA assumed
|
|
93
|
+
|
|
94
|
+
for (let i = 0, il = data.length; i < il; i += 4) {
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
// pixel coordinate on unit cube
|
|
98
|
+
|
|
99
|
+
const pixelIndex = i / 4;
|
|
100
|
+
|
|
101
|
+
const col = -1 + (pixelIndex % image_size + 0.5) * pixel_size;
|
|
102
|
+
|
|
103
|
+
const row = 1 - (Math.floor(pixelIndex / image_size) + 0.5) * pixel_size;
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
switch (faceIndex) {
|
|
107
|
+
|
|
108
|
+
case 0:
|
|
109
|
+
normal_x = 1;
|
|
110
|
+
normal_y = row;
|
|
111
|
+
normal_z = -col;
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 1:
|
|
115
|
+
normal_x = -1;
|
|
116
|
+
normal_y = row;
|
|
117
|
+
normal_z = col;
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 2:
|
|
121
|
+
normal_x = col;
|
|
122
|
+
normal_y = 1;
|
|
123
|
+
normal_z = -row;
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 3:
|
|
127
|
+
normal_x = col;
|
|
128
|
+
normal_y = -1;
|
|
129
|
+
normal_z = row;
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 4:
|
|
133
|
+
normal_x = col;
|
|
134
|
+
normal_y = row;
|
|
135
|
+
normal_z = 1;
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
case 5:
|
|
139
|
+
normal_x = -col;
|
|
140
|
+
normal_y = row;
|
|
141
|
+
normal_z = -1;
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// weight assigned to this pixel
|
|
147
|
+
|
|
148
|
+
const length_squared = v3_length_sqr(normal_x, normal_y, normal_z);
|
|
149
|
+
|
|
150
|
+
const length = Math.sqrt(length_squared);
|
|
151
|
+
|
|
152
|
+
const weight = 4 / (length * length_squared);
|
|
153
|
+
|
|
154
|
+
total_weight += weight;
|
|
155
|
+
|
|
156
|
+
// direction vector to this pixel
|
|
157
|
+
normal_x /= length;
|
|
158
|
+
normal_y /= length;
|
|
159
|
+
normal_z /= length;
|
|
160
|
+
|
|
161
|
+
// evaluate SH basis functions in direction dir
|
|
162
|
+
getBasisAt(normal_x, normal_y, normal_z, sh_basis);
|
|
163
|
+
|
|
164
|
+
// pixel color, already in linear space so no sRGB->Linear conversion necessary
|
|
165
|
+
|
|
166
|
+
// 0.003921 constant value is 1/255, a conversion value from UINT8 to normalized float
|
|
167
|
+
const weight_conv = weight * 0.00392156862745098;
|
|
168
|
+
|
|
169
|
+
const color_r = data[i] * weight_conv;
|
|
170
|
+
const color_g = data[i + 1] * weight_conv;
|
|
171
|
+
const color_b = data[i + 2] * weight_conv;
|
|
172
|
+
|
|
173
|
+
// accumulate
|
|
174
|
+
for (let j = 0; j < 27; j += 3) {
|
|
175
|
+
|
|
176
|
+
coefficients[j] += sh_basis[j] * color_r;
|
|
177
|
+
coefficients[j + 1] += sh_basis[j] * color_g;
|
|
178
|
+
coefficients[j + 2] += sh_basis[j] * color_b;
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// normalize
|
|
187
|
+
const norm = (4 * Math.PI) / total_weight;
|
|
188
|
+
|
|
189
|
+
for (let j = 0; j < 27; j++) {
|
|
190
|
+
|
|
191
|
+
coefficients[j] *= norm;
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return coefficients;
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class CubeRenderer {
|
|
201
|
+
constructor() {
|
|
202
|
+
/**
|
|
203
|
+
*
|
|
204
|
+
* @type {GraphicsEngine|null}
|
|
205
|
+
*/
|
|
206
|
+
this.ge = null;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
*
|
|
210
|
+
* @type {THREE.WebGLRenderer|null}
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
this.__renderer = null;
|
|
214
|
+
|
|
215
|
+
this.render_target = new WebGLCubeRenderTarget(32, {
|
|
216
|
+
format: RGBAFormat,
|
|
217
|
+
minFilter: LinearFilter,
|
|
218
|
+
magFilter: LinearFilter,
|
|
219
|
+
depthBuffer: true,
|
|
220
|
+
scissorTest: false,
|
|
221
|
+
stencilBuffer: false,
|
|
222
|
+
generateMipmaps: false,
|
|
223
|
+
encoding: LinearEncoding
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Used to read out GPU memory for each face
|
|
228
|
+
* @type {Uint8Array}
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
this.__cube_face_raw = new Uint8Array(this.render_target.width * this.render_target.width * 4);
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
this.__cube_camera = new CubeCamera(0.1, 100, this.render_target);
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
*
|
|
238
|
+
* @type {Scene}
|
|
239
|
+
* @private
|
|
240
|
+
*/
|
|
241
|
+
this.__scene = new Scene();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
*
|
|
246
|
+
* @param {EntityComponentDataset} ecd
|
|
247
|
+
*/
|
|
248
|
+
build_scene(ecd) {
|
|
249
|
+
this.__scene.children.splice(0, this.__scene.children.length);
|
|
250
|
+
|
|
251
|
+
ecd.traverseEntities([ShadedGeometry, Transform], (sg, t, entity) => {
|
|
252
|
+
const object3D = build_three_object(sg);
|
|
253
|
+
|
|
254
|
+
object3D.material = sg.material.clone();
|
|
255
|
+
|
|
256
|
+
applyTransformToThreeObject(object3D, t);
|
|
257
|
+
|
|
258
|
+
array_copy(t.matrix, 0, object3D.matrixWorld.elements, 0, 16);
|
|
259
|
+
|
|
260
|
+
this.__scene.add(object3D);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const lightCache = new ThreeLightCache();
|
|
264
|
+
|
|
265
|
+
ecd.traverseEntities([Light, Transform], (light, t) => {
|
|
266
|
+
|
|
267
|
+
const object = threeMakeLight(light, lightCache);
|
|
268
|
+
applyTransformToThreeObject(object, t);
|
|
269
|
+
|
|
270
|
+
this.__scene.add(object);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// this.__scene.add(this.__cube_camera);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
bake_start() {
|
|
277
|
+
// this.__renderer = WebGLRendererPool.global.get();
|
|
278
|
+
|
|
279
|
+
this.__renderer = this.ge.getRenderer();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bake_end() {
|
|
283
|
+
// WebGLRendererPool.global.release(this.__renderer);
|
|
284
|
+
|
|
285
|
+
this.__renderer = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
*
|
|
290
|
+
* @param {number[]} position
|
|
291
|
+
* @param {number} position_offset
|
|
292
|
+
* @param {number[]} output
|
|
293
|
+
* @param {number} output_offset
|
|
294
|
+
*/
|
|
295
|
+
bake(position, position_offset, output, output_offset) {
|
|
296
|
+
// console.warn(position_offset, output_offset);
|
|
297
|
+
|
|
298
|
+
// const ge = this.ge;
|
|
299
|
+
const renderer = this.__renderer;
|
|
300
|
+
|
|
301
|
+
const _rt = renderer.getRenderTarget();
|
|
302
|
+
const _rt_active_face = renderer.getActiveCubeFace();
|
|
303
|
+
const _acc = renderer.autoClearColor;
|
|
304
|
+
const _acd = renderer.autoClearDepth;
|
|
305
|
+
const _ac = renderer.autoClear;
|
|
306
|
+
const _outputEncoding = renderer.outputEncoding;
|
|
307
|
+
|
|
308
|
+
renderer.outputEncoding = LinearEncoding;
|
|
309
|
+
|
|
310
|
+
renderer.autoClearColor = true;
|
|
311
|
+
renderer.autoClearDepth = true;
|
|
312
|
+
renderer.autoClear = true;
|
|
313
|
+
|
|
314
|
+
this.__cube_camera.position.fromArray(position, position_offset);
|
|
315
|
+
this.__cube_camera.update(renderer, this.__scene);
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
// compute coefficients from renderer
|
|
319
|
+
const probe_coefficients = fromCubeRenderTarget(this.__cube_face_raw, renderer, this.render_target);
|
|
320
|
+
|
|
321
|
+
array_copy(probe_coefficients, 0, output, output_offset, 27);
|
|
322
|
+
|
|
323
|
+
// restore state
|
|
324
|
+
renderer.setRenderTarget(_rt, _rt_active_face);
|
|
325
|
+
renderer.autoClearColor = _acc;
|
|
326
|
+
renderer.autoClearDepth = _acd;
|
|
327
|
+
renderer.autoClear = _ac;
|
|
328
|
+
renderer.outputEncoding = _outputEncoding;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
class Baker {
|
|
333
|
+
constructor() {
|
|
334
|
+
/**
|
|
335
|
+
*
|
|
336
|
+
* @type {EntityComponentDataset|null}
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
this.__ecd = null;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
*
|
|
343
|
+
* @type {ExplicitBinaryBoundingVolumeHierarchy|null}
|
|
344
|
+
* @private
|
|
345
|
+
*/
|
|
346
|
+
this.__bvh = null;
|
|
347
|
+
|
|
348
|
+
this.__min_bounce_count = 3;
|
|
349
|
+
this.__max_bounce_count = 100;
|
|
350
|
+
|
|
351
|
+
this._ren = new CubeRenderer();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @param {ExplicitBinaryBoundingVolumeHierarchy} bvh
|
|
356
|
+
*/
|
|
357
|
+
set_bvh(bvh) {
|
|
358
|
+
this.__bvh = bvh;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
build_spatial() {
|
|
362
|
+
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
*
|
|
367
|
+
* @param {number[]} irradiance RGB irradiance result is written here
|
|
368
|
+
* @param {number} bounce_count number of reflections before this trace
|
|
369
|
+
* @param {number} origin_x
|
|
370
|
+
* @param {number} origin_y
|
|
371
|
+
* @param {number} origin_z
|
|
372
|
+
* @param {number} direction_x
|
|
373
|
+
* @param {number} direction_y
|
|
374
|
+
* @param {number} direction_z
|
|
375
|
+
*/
|
|
376
|
+
trace_ray(irradiance, bounce_count,
|
|
377
|
+
origin_x, origin_y, origin_z,
|
|
378
|
+
direction_x, direction_y, direction_z) {
|
|
379
|
+
|
|
380
|
+
//
|
|
381
|
+
|
|
382
|
+
if (!this.ray_cast(TEMP_CONTACT, origin_x, origin_y, origin_z, direction_x, direction_y, direction_z)) {
|
|
383
|
+
// no hit, assume irradiance is zeroed - black
|
|
384
|
+
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// record irradiance
|
|
389
|
+
|
|
390
|
+
// reflect ray around normal
|
|
391
|
+
// TODO apply BDRF to compute reflected ray
|
|
392
|
+
|
|
393
|
+
if (bounce_count > this.__max_bounce_count) {
|
|
394
|
+
// terminate
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
*
|
|
401
|
+
* @param contact
|
|
402
|
+
* @param origin_x
|
|
403
|
+
* @param origin_y
|
|
404
|
+
* @param origin_z
|
|
405
|
+
* @param direction_x
|
|
406
|
+
* @param direction_y
|
|
407
|
+
* @param direction_z
|
|
408
|
+
* @returns {boolean}
|
|
409
|
+
*/
|
|
410
|
+
ray_cast(contact,
|
|
411
|
+
origin_x, origin_y, origin_z,
|
|
412
|
+
direction_x, direction_y, direction_z
|
|
413
|
+
) {
|
|
414
|
+
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
prepare_scene() {
|
|
418
|
+
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
*
|
|
423
|
+
* @param {number[]} position
|
|
424
|
+
* @param {number} position_offset
|
|
425
|
+
* @param {number[]} output
|
|
426
|
+
* @param {number} output_offset
|
|
427
|
+
*/
|
|
428
|
+
bake_sh3_cube(
|
|
429
|
+
position, position_offset,
|
|
430
|
+
output, output_offset
|
|
431
|
+
) {
|
|
432
|
+
|
|
433
|
+
this._ren.bake(position, position_offset, output, output_offset);
|
|
434
|
+
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
*
|
|
439
|
+
* @param {Engine} engine
|
|
440
|
+
*/
|
|
441
|
+
prepare(engine) {
|
|
442
|
+
this._ren.ge = engine.graphics;
|
|
443
|
+
|
|
444
|
+
this._ren.build_scene(engine.entityManager.dataset);
|
|
445
|
+
|
|
446
|
+
this._ren.bake_start();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
finish() {
|
|
450
|
+
this._ren.bake_end();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export class LightProbeVolume {
|
|
455
|
+
constructor() {
|
|
456
|
+
this.__positions = [];
|
|
457
|
+
|
|
458
|
+
this.__mesh = new TetrahedralMesh();
|
|
459
|
+
|
|
460
|
+
this.__probe_data = [];
|
|
461
|
+
|
|
462
|
+
this.__length = 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
*
|
|
467
|
+
* @param {number} x
|
|
468
|
+
* @param {number} y
|
|
469
|
+
* @param {number} z
|
|
470
|
+
*/
|
|
471
|
+
add_point(x, y, z) {
|
|
472
|
+
const i = this.__length;
|
|
473
|
+
|
|
474
|
+
this.__positions[i * 3] = x;
|
|
475
|
+
this.__positions[i * 3 + 1] = y;
|
|
476
|
+
this.__positions[i * 3 + 2] = z;
|
|
477
|
+
|
|
478
|
+
for (let j = 0; j < 9 * 3; j++) {
|
|
479
|
+
this.__probe_data[i * 9 * 3 + j] = 1; // fill with white
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
this.__length++;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
*
|
|
487
|
+
* @param {number} index
|
|
488
|
+
* @param {number[]} source
|
|
489
|
+
* @param {number} source_offset
|
|
490
|
+
*/
|
|
491
|
+
white_probe(index, source, source_offset) {
|
|
492
|
+
array_copy(source, source_offset, this.__probe_data, index * 9 * 3, 9 * 3);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Build tetrahedral mesh
|
|
497
|
+
*/
|
|
498
|
+
build_mesh() {
|
|
499
|
+
|
|
500
|
+
const t0 = performance.now();
|
|
501
|
+
compute_delaunay_tetrahedral_mesh(this.__mesh, this.__positions, this.__length);
|
|
502
|
+
|
|
503
|
+
const t1 = performance.now();
|
|
504
|
+
|
|
505
|
+
// optional step to improve memory utilization
|
|
506
|
+
this.__mesh.compact();
|
|
507
|
+
|
|
508
|
+
const t2 = performance.now();
|
|
509
|
+
|
|
510
|
+
console.log(`Tetrahedral mesh (${this.__length} points, ${this.__mesh.size()} tets) build took ${t2 - t0}ms`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Bake light probes
|
|
516
|
+
* @param {Engine} engine
|
|
517
|
+
*/
|
|
518
|
+
bake(engine) {
|
|
519
|
+
const baker = new Baker();
|
|
520
|
+
// baker.set_bvh(entityManager.getSystem(ShadedGeometrySystem).__bvh_binary);
|
|
521
|
+
|
|
522
|
+
baker.prepare(engine);
|
|
523
|
+
|
|
524
|
+
const t0 = performance.now();
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
for (let i = 0; i < this.__length; i++) {
|
|
528
|
+
|
|
529
|
+
baker.bake_sh3_cube(
|
|
530
|
+
this.__positions, i * 3,
|
|
531
|
+
this.__probe_data, i * 9 * 3
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const t1 = performance.now();
|
|
537
|
+
|
|
538
|
+
console.log(`Baked ${this.__length} probes in ${(t1 - t0)}ms, ~${((t1 - t0) / this.__length).toFixed(2)}ms per probe`);
|
|
539
|
+
|
|
540
|
+
baker.finish();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
*
|
|
545
|
+
* @param {EntityComponentDataset} ecd
|
|
546
|
+
* @param {number} size
|
|
547
|
+
* @param {boolean} shadow
|
|
548
|
+
*/
|
|
549
|
+
visualize_probes({ ecd, size = 0.2, shadow = false }) {
|
|
550
|
+
for (let i = 0; i < this.__length; i++) {
|
|
551
|
+
visualise_probe({
|
|
552
|
+
size,
|
|
553
|
+
position: this.__positions,
|
|
554
|
+
position_offset: i * 3,
|
|
555
|
+
sh: this.__probe_data,
|
|
556
|
+
sh_offset: i * 3 * 9,
|
|
557
|
+
shadow: shadow
|
|
558
|
+
}).build(ecd);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
*
|
|
564
|
+
* @param {EntityComponentDataset} ecd
|
|
565
|
+
* @param {string|number} color CSS color definition
|
|
566
|
+
*/
|
|
567
|
+
visualize_mesh({ ecd, color = '#FF00FF' }) {
|
|
568
|
+
|
|
569
|
+
const geometry = build_tetrahedral_mesh_buffer_geometry(this.__mesh, this.__positions);
|
|
570
|
+
|
|
571
|
+
const sg = ShadedGeometry.from(geometry, new LineBasicMaterial({
|
|
572
|
+
color: color,
|
|
573
|
+
transparent: true,
|
|
574
|
+
opacity: 0.35,
|
|
575
|
+
depthWrite: false
|
|
576
|
+
}), DrawMode.LineSegments);
|
|
577
|
+
|
|
578
|
+
sg.clearFlag(ShadedGeometryFlags.CastShadow);
|
|
579
|
+
sg.clearFlag(ShadedGeometryFlags.ReceiveShadow);
|
|
580
|
+
|
|
581
|
+
new EntityBuilder()
|
|
582
|
+
.add(sg)
|
|
583
|
+
.add(new Transform())
|
|
584
|
+
.build(ecd);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
*
|
|
589
|
+
* @param {Engine} engine
|
|
590
|
+
*/
|
|
591
|
+
build(engine) {
|
|
592
|
+
this.build_mesh();
|
|
593
|
+
this.bake(engine);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ShaderMaterial, Vector3 } from "three";
|
|
2
|
+
|
|
3
|
+
const shader_vx = `
|
|
4
|
+
varying vec3 vNormal;
|
|
5
|
+
|
|
6
|
+
void main() {
|
|
7
|
+
|
|
8
|
+
vNormal = normalize( normalMatrix * normal );
|
|
9
|
+
|
|
10
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const shader_fg = `
|
|
16
|
+
#define RECIPROCAL_PI 0.318309886
|
|
17
|
+
|
|
18
|
+
vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {
|
|
19
|
+
// matrix is assumed to be orthogonal
|
|
20
|
+
return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf
|
|
24
|
+
vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {
|
|
25
|
+
// normal is assumed to have unit length
|
|
26
|
+
float x = normal.x, y = normal.y, z = normal.z;
|
|
27
|
+
// band 0
|
|
28
|
+
vec3 result = shCoefficients[ 0 ] * 0.886227;
|
|
29
|
+
// band 1
|
|
30
|
+
result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;
|
|
31
|
+
result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;
|
|
32
|
+
result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;
|
|
33
|
+
// band 2
|
|
34
|
+
result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;
|
|
35
|
+
result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;
|
|
36
|
+
result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );
|
|
37
|
+
result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;
|
|
38
|
+
result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
uniform vec3 sh[ 9 ]; // sh coefficients
|
|
44
|
+
uniform float intensity; // light probe intensity
|
|
45
|
+
varying vec3 vNormal;
|
|
46
|
+
|
|
47
|
+
void main() {
|
|
48
|
+
vec3 normal = normalize( vNormal );
|
|
49
|
+
vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
|
|
50
|
+
vec3 irradiance = shGetIrradianceAt( worldNormal, sh );
|
|
51
|
+
vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;
|
|
52
|
+
gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
export class SH3VisualisationMaterial extends ShaderMaterial {
|
|
57
|
+
|
|
58
|
+
constructor() {
|
|
59
|
+
const sh = [];
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < 9; i++) {
|
|
62
|
+
sh.push(new Vector3());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
super({
|
|
66
|
+
fragmentShader: shader_fg,
|
|
67
|
+
vertexShader: shader_vx,
|
|
68
|
+
uniforms: {
|
|
69
|
+
intensity: {
|
|
70
|
+
value: 1,
|
|
71
|
+
},
|
|
72
|
+
sh: {
|
|
73
|
+
value: sh
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|