@1pizzateam/roostr 0.7.0 → 0.8.1

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.
Files changed (3) hide show
  1. package/dist/roostr.d.ts +275 -212
  2. package/dist/roostr.js +1449 -1121
  3. package/package.json +2 -2
package/dist/roostr.js CHANGED
@@ -24,8 +24,8 @@ SOFTWARE.
24
24
  https://github.com/1pizzateam/RoostR.js
25
25
  */
26
26
 
27
- import { Mat4x3, Vec3, Frustum, Vec2, Time, Utils, Mat4, Ray3, Rect, Trigo, Quat, Mat3, Box, Sphere as Sphere$1 } from '@1pizzateam/spock';
28
- export { Accumulator, Bezier, Box as BoundingBox, Sphere as BoundingSphere, Circ, Frustum, Grid, Mat3, Mat4, Mat4x3, NumArray, Quat, Rand, Ray3 as Ray, Rect, RollingAverage, Plane as SpatialPlane, Time, Trigo, Utils, Vec2, Vec3 } from '@1pizzateam/spock';
27
+ import { Mat4x3, Vec3, Frustum, Vec2, Utils, Time, Mat4, Ray3, Trigo, Quat, Mat3, Box, Sphere as Sphere$1, Rect } from '@1pizzateam/spock';
28
+ export { Accumulator, Bezier, Box as BoundingBox, Sphere as BoundingSphere, Circ, Frustum, Grid, Mat3, Mat4, Mat4x3, NumArray, Quat, Rand, Ray3 as Ray, Ray2, Rect, RollingAverage, Plane as SpatialPlane, Time, Trigo, Utils, Vec2, Vec3 } from '@1pizzateam/spock';
29
29
 
30
30
  class GLState {
31
31
  program = null;
@@ -42,6 +42,13 @@ class GLState {
42
42
  this.elementBuffer = null;
43
43
  this.attributeMask = null;
44
44
  }
45
+ reset() {
46
+ this.invalidate();
47
+ this.maxAttributes = 0;
48
+ this.frameOfProgram = new WeakMap();
49
+ this.materialOfProgram = new WeakMap();
50
+ this.materialVersionOfProgram = new WeakMap();
51
+ }
45
52
  }
46
53
  const states = new WeakMap();
47
54
  function getGLState(context) {
@@ -68,6 +75,13 @@ class SceneRenderer {
68
75
  this.blendEnabled = null;
69
76
  this.depthWrite = null;
70
77
  }
78
+ reset() {
79
+ getGLState(this.context).reset();
80
+ this.blendKey = '';
81
+ this.blendEnabled = null;
82
+ this.depthWrite = null;
83
+ this.defaultSettings();
84
+ }
71
85
  defaultSettings() {
72
86
  this.setDepthFunc('LEQUAL');
73
87
  this.enableDepthTest();
@@ -103,7 +117,7 @@ class SceneRenderer {
103
117
  this.context.depthMask(false);
104
118
  }
105
119
  enableBlendMode(equation, source, destination) {
106
- const key = equation + '|' + source + '|' + destination;
120
+ const key = `${equation}|${source}|${destination}`;
107
121
  if (key !== this.blendKey) {
108
122
  this.setBlendEquation(equation);
109
123
  this.setBlendFunction(source, destination);
@@ -154,9 +168,105 @@ class SceneGraph {
154
168
  }
155
169
  }
156
170
 
171
+ const scratchAmbient = new Vec3();
172
+ function clearFlatArrays(flatArrays) {
173
+ flatArrays.position.length = 0;
174
+ flatArrays.diffuse.length = 0;
175
+ flatArrays.specular.length = 0;
176
+ flatArrays.constantAttenuation.length = 0;
177
+ flatArrays.linearAttenuation.length = 0;
178
+ flatArrays.quadraticAttenuation.length = 0;
179
+ flatArrays.cutoff.length = 0;
180
+ flatArrays.exponent.length = 0;
181
+ flatArrays.direction.length = 0;
182
+ flatArrays.type.length = 0;
183
+ }
184
+ function ensureLightBuffers(flatArrays, count) {
185
+ if (flatArrays.floatPosition && flatArrays.floatPosition.length === count * 3)
186
+ return;
187
+ flatArrays.floatPosition = new Float32Array(count * 3);
188
+ flatArrays.floatDiffuse = new Float32Array(count * 3);
189
+ flatArrays.floatSpecular = new Float32Array(count * 3);
190
+ flatArrays.floatDirection = new Float32Array(count * 3);
191
+ flatArrays.floatAttenuation = new Float32Array(count * 3);
192
+ flatArrays.floatCutoff = new Float32Array(count);
193
+ flatArrays.floatExponent = new Float32Array(count);
194
+ flatArrays.floatType = new Float32Array(count);
195
+ flatArrays.packedPosition = new Float32Array(count * 4);
196
+ flatArrays.packedDiffuse = new Float32Array(count * 4);
197
+ flatArrays.packedSpecular = new Float32Array(count * 4);
198
+ }
199
+ function pushLight(flatArrays, index, kind, light) {
200
+ const o = index * 3;
201
+ const isSpot = kind === 'spot';
202
+ const isDirectional = kind === 'directional';
203
+ const att = isDirectional ? null : light;
204
+ light.position.toArray(flatArrays.floatPosition, o);
205
+ light.diffuse.toArray(flatArrays.floatDiffuse, o);
206
+ light.specular.toArray(flatArrays.floatSpecular, o);
207
+ if (isDirectional)
208
+ light.direction.toArray(flatArrays.floatDirection, o);
209
+ else if (isSpot)
210
+ light.direction.toArray(flatArrays.floatDirection, o);
211
+ else
212
+ flatArrays.floatDirection.fill(0, o, o + 3);
213
+ const constant = att ? att.constantAttenuation : 1;
214
+ const linear = att ? att.linearAttenuation : 0;
215
+ const quadratic = att ? att.quadraticAttenuation : 0;
216
+ flatArrays.floatAttenuation[o] = constant;
217
+ flatArrays.floatAttenuation[o + 1] = linear;
218
+ flatArrays.floatAttenuation[o + 2] = quadratic;
219
+ flatArrays.floatCutoff[index] = isSpot ? light.cutoff : 0;
220
+ flatArrays.floatExponent[index] = isSpot ? light.exponent : 0;
221
+ flatArrays.floatType[index] = isDirectional ? 0 : isSpot ? 2 : 1;
222
+ const q = index * 4;
223
+ light.position.toArray(flatArrays.packedPosition, q);
224
+ flatArrays.packedPosition[q + 3] = flatArrays.floatType[index];
225
+ light.diffuse.toArray(flatArrays.packedDiffuse, q);
226
+ flatArrays.packedDiffuse[q + 3] = flatArrays.floatCutoff[index];
227
+ light.specular.toArray(flatArrays.packedSpecular, q);
228
+ flatArrays.packedSpecular[q + 3] = flatArrays.floatExponent[index];
229
+ const dir = isDirectional || isSpot ? light.direction : null;
230
+ flatArrays.position.push(light.position.x, light.position.y, light.position.z);
231
+ flatArrays.diffuse.push(light.diffuse.x, light.diffuse.y, light.diffuse.z);
232
+ flatArrays.specular.push(light.specular.x, light.specular.y, light.specular.z);
233
+ if (dir)
234
+ flatArrays.direction.push(dir.x, dir.y, dir.z);
235
+ else
236
+ flatArrays.direction.push(0, 0, 0);
237
+ flatArrays.constantAttenuation.push(constant);
238
+ flatArrays.linearAttenuation.push(linear);
239
+ flatArrays.quadraticAttenuation.push(quadratic);
240
+ flatArrays.cutoff.push(isSpot ? light.cutoff : 0);
241
+ flatArrays.exponent.push(isSpot ? light.exponent : 0);
242
+ flatArrays.type.push(kind);
243
+ }
244
+ function packLights(directionals, points, spots, ambients, flatArrays, maxLights, extraAmbient) {
245
+ clearFlatArrays(flatArrays);
246
+ const total = spots.length + points.length + directionals.length;
247
+ const count = Math.min(total, maxLights);
248
+ ensureLightBuffers(flatArrays, count);
249
+ let index = 0;
250
+ for (let i = 0; i < spots.length && index < count; i++)
251
+ pushLight(flatArrays, index++, 'spot', spots[i]);
252
+ for (let i = 0; i < points.length && index < count; i++)
253
+ pushLight(flatArrays, index++, 'point', points[i]);
254
+ for (let i = 0; i < directionals.length && index < count; i++)
255
+ pushLight(flatArrays, index++, 'directional', directionals[i]);
256
+ const ambient = scratchAmbient.setScalar(0, 0, 0);
257
+ if (extraAmbient)
258
+ ambient.add(extraAmbient);
259
+ for (let i = 0; i < ambients.length; i++)
260
+ ambient.addScaledVector(ambients[i].diffuse, ambients[i].intensity);
261
+ if (!flatArrays.floatAmbient)
262
+ flatArrays.floatAmbient = new Float32Array(3);
263
+ ambient.toArray(flatArrays.floatAmbient);
264
+ flatArrays.ambient = [ambient.x, ambient.y, ambient.z];
265
+ return flatArrays;
266
+ }
267
+
157
268
  const MAX_LIGHTS = 8;
158
269
  class Lights {
159
- static _ambient = new Vec3();
160
270
  warnedOverflow = false;
161
271
  directionals;
162
272
  points;
@@ -249,106 +359,13 @@ class Lights {
249
359
  return false;
250
360
  }
251
361
  }
252
- ClearFlatArrays() {
253
- this.flatArrays.position.length = 0;
254
- this.flatArrays.diffuse.length = 0;
255
- this.flatArrays.specular.length = 0;
256
- this.flatArrays.constantAttenuation.length = 0;
257
- this.flatArrays.linearAttenuation.length = 0;
258
- this.flatArrays.quadraticAttenuation.length = 0;
259
- this.flatArrays.cutoff.length = 0;
260
- this.flatArrays.exponent.length = 0;
261
- this.flatArrays.direction.length = 0;
262
- this.flatArrays.type.length = 0;
263
- }
264
- ensureBuffers(count) {
265
- const f = this.flatArrays;
266
- if (f.floatPosition && f.floatPosition.length === count * 3)
267
- return;
268
- f.floatPosition = new Float32Array(count * 3);
269
- f.floatDiffuse = new Float32Array(count * 3);
270
- f.floatSpecular = new Float32Array(count * 3);
271
- f.floatDirection = new Float32Array(count * 3);
272
- f.floatAttenuation = new Float32Array(count * 3);
273
- f.floatCutoff = new Float32Array(count);
274
- f.floatExponent = new Float32Array(count);
275
- f.floatType = new Float32Array(count);
276
- f.packedPosition = new Float32Array(count * 4);
277
- f.packedDiffuse = new Float32Array(count * 4);
278
- f.packedSpecular = new Float32Array(count * 4);
279
- }
280
- push(index, kind, light) {
281
- const f = this.flatArrays;
282
- const o = index * 3;
283
- const isSpot = kind === 'spot';
284
- const isDirectional = kind === 'directional';
285
- const att = isDirectional ? null : light;
286
- light.position.toArray(f.floatPosition, o);
287
- light.diffuse.toArray(f.floatDiffuse, o);
288
- light.specular.toArray(f.floatSpecular, o);
289
- if (isDirectional)
290
- light.direction.toArray(f.floatDirection, o);
291
- else if (isSpot)
292
- light.direction.toArray(f.floatDirection, o);
293
- else
294
- f.floatDirection.fill(0, o, o + 3);
295
- const constant = att ? att.constantAttenuation : 1;
296
- const linear = att ? att.linearAttenuation : 0;
297
- const quadratic = att ? att.quadraticAttenuation : 0;
298
- f.floatAttenuation[o] = constant;
299
- f.floatAttenuation[o + 1] = linear;
300
- f.floatAttenuation[o + 2] = quadratic;
301
- f.floatCutoff[index] = isSpot ? light.cutoff : 0;
302
- f.floatExponent[index] = isSpot ? light.exponent : 0;
303
- f.floatType[index] = isDirectional ? 0 : isSpot ? 2 : 1;
304
- const q = index * 4;
305
- light.position.toArray(f.packedPosition, q);
306
- f.packedPosition[q + 3] = f.floatType[index];
307
- light.diffuse.toArray(f.packedDiffuse, q);
308
- f.packedDiffuse[q + 3] = f.floatCutoff[index];
309
- light.specular.toArray(f.packedSpecular, q);
310
- f.packedSpecular[q + 3] = f.floatExponent[index];
311
- const dir = isDirectional || isSpot ? light.direction : null;
312
- f.position.push(light.position.x, light.position.y, light.position.z);
313
- f.diffuse.push(light.diffuse.x, light.diffuse.y, light.diffuse.z);
314
- f.specular.push(light.specular.x, light.specular.y, light.specular.z);
315
- if (dir)
316
- f.direction.push(dir.x, dir.y, dir.z);
317
- else
318
- f.direction.push(0, 0, 0);
319
- f.constantAttenuation.push(constant);
320
- f.linearAttenuation.push(linear);
321
- f.quadraticAttenuation.push(quadratic);
322
- f.cutoff.push(isSpot ? light.cutoff : 0);
323
- f.exponent.push(isSpot ? light.exponent : 0);
324
- f.type.push(kind);
325
- }
326
362
  flatten(extraAmbient) {
327
- this.ClearFlatArrays();
328
363
  const total = this.nbSpots + this.nbPoints + this.nbDirectionals;
329
- const count = Math.min(total, MAX_LIGHTS);
330
364
  if (total > MAX_LIGHTS && !this.warnedOverflow) {
331
365
  this.warnedOverflow = true;
332
366
  console.warn(`Lights: ${total} lights added, only the first ${MAX_LIGHTS} (spots, points, then directionals) are used.`);
333
367
  }
334
- this.ensureBuffers(count);
335
- let index = 0;
336
- for (let i = 0; i < this.nbSpots && index < count; i++)
337
- this.push(index++, 'spot', this.spots[i]);
338
- for (let i = 0; i < this.nbPoints && index < count; i++)
339
- this.push(index++, 'point', this.points[i]);
340
- for (let i = 0; i < this.nbDirectionals && index < count; i++)
341
- this.push(index++, 'directional', this.directionals[i]);
342
- const ambient = Lights._ambient.setScalar(0, 0, 0);
343
- if (extraAmbient)
344
- ambient.add(extraAmbient);
345
- for (let i = 0; i < this.nbAmbients; i++)
346
- ambient.addScaledVector(this.ambients[i].diffuse, this.ambients[i].intensity);
347
- if (!this.flatArrays.floatAmbient)
348
- this.flatArrays.floatAmbient = new Float32Array(3);
349
- ambient.toArray(this.flatArrays.floatAmbient);
350
- this.flatArrays.ambient = [ambient.x, ambient.y, ambient.z];
351
- return this.flatArrays;
368
+ return packLights(this.directionals, this.points, this.spots, this.ambients, this.flatArrays, MAX_LIGHTS, extraAmbient);
352
369
  }
353
370
  clear() {
354
371
  this.directionals = [];
@@ -359,7 +376,7 @@ class Lights {
359
376
  this.nbPoints = 0;
360
377
  this.nbSpots = 0;
361
378
  this.nbAmbients = 0;
362
- this.ClearFlatArrays();
379
+ clearFlatArrays(this.flatArrays);
363
380
  }
364
381
  getFlatArray(property) {
365
382
  if (this.flatArrays.hasOwnProperty(property))
@@ -368,12 +385,70 @@ class Lights {
368
385
  }
369
386
  }
370
387
 
371
- function assignVec3(target, xOrVec, y, z) {
372
- if (xOrVec instanceof Vec3)
373
- return target.copy(xOrVec);
374
- if (typeof xOrVec === 'number')
375
- return target.setScalar(xOrVec, y, z);
376
- return target.setArray(xOrVec);
388
+ function raycastScene(meshes, traversalStamp, ray, precise = false) {
389
+ const hits = [];
390
+ const visit = (mesh) => {
391
+ if (!mesh.isActive())
392
+ return;
393
+ const hit = mesh.raycast(ray, precise);
394
+ if (hit)
395
+ hits.push({ mesh, distance: hit.distance, point: hit.point });
396
+ for (const child of mesh.children)
397
+ visit(child);
398
+ };
399
+ for (const mesh of meshes)
400
+ if (mesh.traversalStamp !== traversalStamp)
401
+ visit(mesh);
402
+ hits.sort((a, b) => a.distance - b.distance);
403
+ return hits;
404
+ }
405
+
406
+ class SceneCollector {
407
+ opaqueList = [];
408
+ blendedList = [];
409
+ scratchFrustum = new Frustum();
410
+ scratchVec = new Vec3();
411
+ collect(meshes, camera, frustum, opaque, blended, stamp) {
412
+ this.opaqueList.length = 0;
413
+ this.blendedList.length = 0;
414
+ const visit = (mesh) => {
415
+ if (!mesh.isActive())
416
+ return;
417
+ const wanted = mesh.blendMode ? blended : opaque;
418
+ if (wanted && !(frustum && !frustum.intersectsSphere(mesh.computeBoundingSphere()))) {
419
+ if (mesh.blendMode) {
420
+ mesh.sortKey = mesh.getWorldPosition(this.scratchVec).getDistance(camera.position, true);
421
+ this.blendedList.push(mesh);
422
+ }
423
+ else
424
+ this.opaqueList.push(mesh);
425
+ }
426
+ for (const child of mesh.children)
427
+ visit(child);
428
+ };
429
+ for (const mesh of meshes)
430
+ if (mesh.traversalStamp !== stamp)
431
+ visit(mesh);
432
+ }
433
+ drawBlended(scene, camera, time, flatLights, frame) {
434
+ const list = this.blendedList;
435
+ if (list.length === 0)
436
+ return 0;
437
+ if (list.length > 1)
438
+ list.sort((a, b) => b.sortKey - a.sortKey);
439
+ const projMatrix = camera.getProjectionMatrix();
440
+ const viewMatrix = camera.getViewMatrix();
441
+ const camPos = camera.position;
442
+ for (const mesh of list) {
443
+ const blend = mesh.blendOptions;
444
+ scene.enableBlendMode(blend.equation, blend.source, blend.destination);
445
+ scene.renderer.setDepthWrite(mesh.depthWrite);
446
+ mesh.renderSelf(projMatrix, viewMatrix, flatLights, time, true, camPos, scene.fog, frame);
447
+ }
448
+ scene.disableBlendMode();
449
+ scene.renderer.setDepthWrite(true);
450
+ return list.length;
451
+ }
377
452
  }
378
453
 
379
454
  class Scene {
@@ -386,14 +461,11 @@ class Scene {
386
461
  context;
387
462
  renderer;
388
463
  graph;
464
+ collector;
389
465
  resolution;
390
- scratchFrustum = new Frustum();
391
- scratchVec = new Vec3();
392
466
  frame = { id: 0 };
393
467
  frameCounter = 0;
394
468
  traversalStamp = 0;
395
- opaqueList = [];
396
- blendedList = [];
397
469
  constructor(contextOrOptions) {
398
470
  this.meshes = [];
399
471
  this.nbMeshes = 0;
@@ -401,6 +473,7 @@ class Scene {
401
473
  this.ambientColor = new Vec3(0, 0, 0);
402
474
  this.fog = null;
403
475
  this.enableFrustumCulling = false;
476
+ this.collector = new SceneCollector();
404
477
  let ctx = null;
405
478
  let opts = null;
406
479
  if (contextOrOptions && typeof contextOrOptions.createBuffer === 'function')
@@ -427,15 +500,14 @@ class Scene {
427
500
  this.renderer = new SceneRenderer(this.context);
428
501
  this.graph = new SceneGraph();
429
502
  if (opts && this.context) {
430
- if (opts.viewport)
431
- this.setViewport(opts.viewport.x || 0, opts.viewport.y || 0, opts.viewport.width, opts.viewport.height);
432
- if (opts.clearColor) {
433
- const c = opts.clearColor;
434
- if (Array.isArray(c))
435
- this.context.clearColor(c[0] ?? 0, c[1] ?? 0, c[2] ?? 0, c[3] ?? 1);
436
- else if ('x' in c && 'y' in c && 'z' in c)
437
- this.context.clearColor(c.x, c.y, c.z, 1.0);
503
+ if (opts.viewport) {
504
+ if (opts.viewport instanceof Vec2)
505
+ this.setViewport(opts.viewport);
506
+ else
507
+ this.setViewport(opts.viewport.size, opts.viewport.position);
438
508
  }
509
+ if (opts.clearColor)
510
+ this.setClearColor(opts.clearColor);
439
511
  if (opts.depthTest !== false && this.context.DEPTH_TEST) {
440
512
  this.context.enable(this.context.DEPTH_TEST);
441
513
  if (this.context.LEQUAL)
@@ -460,20 +532,32 @@ class Scene {
460
532
  return null;
461
533
  return (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
462
534
  }
463
- setViewport(x, y, width, height) {
464
- this.resolution.setScalar(width, height);
535
+ setViewport(size, position) {
536
+ const x = position?.x ?? 0;
537
+ const y = position?.y ?? 0;
538
+ const w = size.x;
539
+ const h = size.y;
540
+ this.resolution.x = w;
541
+ this.resolution.y = h;
465
542
  if (this.context && this.context.viewport)
466
- this.context.viewport(x, y, width, height);
543
+ this.context.viewport(x, y, w, h);
544
+ }
545
+ getSize(target = new Vec2()) {
546
+ target.copy(this.resolution);
547
+ return target;
548
+ }
549
+ setClearColor(color, alpha = 1.0) {
550
+ if (!this.context)
551
+ return;
552
+ this.context.clearColor(Utils.clamp(color.x, 0, 1), Utils.clamp(color.y, 0, 1), Utils.clamp(color.z, 0, 1), Utils.clamp(alpha, 0, 1));
467
553
  }
468
- setSize(sizeOrWidth, height) {
469
- const w = sizeOrWidth instanceof Vec2 ? sizeOrWidth.x : sizeOrWidth;
470
- const h = sizeOrWidth instanceof Vec2 ? sizeOrWidth.y : (height ?? 0);
554
+ setSize(size) {
471
555
  if (this.context && this.context.canvas) {
472
556
  const canvas = this.context.canvas;
473
- canvas.width = w;
474
- canvas.height = h;
557
+ canvas.width = size.x;
558
+ canvas.height = size.y;
475
559
  }
476
- this.setViewport(0, 0, w, h);
560
+ this.setViewport(size);
477
561
  }
478
562
  clear(color = true, depth = true) {
479
563
  if (!this.context)
@@ -507,26 +591,16 @@ class Scene {
507
591
  removeLight(light) {
508
592
  return this.lights.removeLight(light);
509
593
  }
510
- setAmbientColor(rOrVec, g, b) {
511
- if (typeof rOrVec === 'number')
512
- this.ambientColor.setScalar(rOrVec, g ?? 0, b ?? 0);
513
- else
514
- assignVec3(this.ambientColor, rOrVec);
594
+ setAmbientColor(color) {
595
+ this.ambientColor.copy(color);
515
596
  return this;
516
597
  }
517
- setFog(color, nearOrG, farOrB, near, far) {
518
- if (typeof color === 'number')
519
- this.fog = {
520
- color: new Vec3(color, nearOrG, farOrB ?? 0),
521
- near: near ?? 1,
522
- far: far ?? 1000
523
- };
524
- else
525
- this.fog = {
526
- color: assignVec3(new Vec3(), color),
527
- near: nearOrG,
528
- far: farOrB ?? 1000
529
- };
598
+ setFog(color, near, far = 1000) {
599
+ this.fog = {
600
+ color: new Vec3(color.x, color.y, color.z),
601
+ near,
602
+ far
603
+ };
530
604
  return this;
531
605
  }
532
606
  clearFog() {
@@ -540,6 +614,11 @@ class Scene {
540
614
  clearLights() {
541
615
  this.lights.clear();
542
616
  }
617
+ restore() {
618
+ this.renderer.reset();
619
+ for (const mesh of this.meshes)
620
+ mesh.restore();
621
+ }
543
622
  destroy(destroyMeshes = true) {
544
623
  if (destroyMeshes)
545
624
  for (const mesh of this.meshes)
@@ -562,41 +641,27 @@ class Scene {
562
641
  }
563
642
  raycast(ray, precise = false) {
564
643
  this.computeWorldMatrices();
565
- const hits = [];
566
- const visit = (mesh) => {
567
- if (!mesh.isActive())
568
- return;
569
- const hit = mesh.raycast(ray, precise);
570
- if (hit)
571
- hits.push({ mesh, distance: hit.distance, point: hit.point });
572
- for (const child of mesh.children)
573
- visit(child);
574
- };
575
- for (const mesh of this.meshes)
576
- if (mesh.traversalStamp !== this.traversalStamp)
577
- visit(mesh);
578
- hits.sort((a, b) => a.distance - b.distance);
579
- return hits;
644
+ return raycastScene(this.meshes, this.traversalStamp, ray, precise);
580
645
  }
581
646
  render(camera, time = Time.now()) {
582
647
  this.computeWorldMatrices();
583
648
  const flatLights = this.lights.flatten(this.ambientColor);
584
649
  const frustum = this.enableFrustumCulling
585
- ? this.scratchFrustum.setFromProjectionViewMatrix(camera.getProjectionViewMatrix())
650
+ ? this.collector.scratchFrustum.setFromProjectionViewMatrix(camera.getProjectionViewMatrix())
586
651
  : null;
587
- this.collect(camera, frustum, true, true, this.traversalStamp);
652
+ this.collector.collect(this.meshes, camera, frustum, true, true, this.traversalStamp);
588
653
  const frame = this.beginFrame();
589
654
  this.disableBlendMode();
590
655
  const projMatrix = camera.getProjectionMatrix();
591
656
  const viewMatrix = camera.getViewMatrix();
592
657
  const camPos = camera.position;
593
658
  let renderedCount = 0;
594
- for (const mesh of this.opaqueList) {
659
+ for (const mesh of this.collector.opaqueList) {
595
660
  this.renderer.setDepthWrite(mesh.depthWrite);
596
661
  mesh.renderSelf(projMatrix, viewMatrix, flatLights, time, false, camPos, this.fog, frame);
597
662
  renderedCount++;
598
663
  }
599
- renderedCount += this.drawBlended(camera, time, flatLights, frame);
664
+ renderedCount += this.collector.drawBlended(this, camera, time, flatLights, frame);
600
665
  this.renderer.setDepthWrite(true);
601
666
  return renderedCount;
602
667
  }
@@ -624,52 +689,105 @@ class Scene {
624
689
  this.frame.resolution = this.resolution;
625
690
  return this.frame;
626
691
  }
627
- collect(camera, frustum, opaque, blended, stamp) {
628
- this.opaqueList.length = 0;
629
- this.blendedList.length = 0;
630
- const visit = (mesh) => {
631
- if (!mesh.isActive())
632
- return;
633
- const wanted = mesh.blendMode ? blended : opaque;
634
- if (wanted && !(frustum && !frustum.intersectsSphere(mesh.computeBoundingSphere()))) {
635
- if (mesh.blendMode) {
636
- mesh.sortKey = mesh.getWorldPosition(this.scratchVec).getDistance(camera.position, true);
637
- this.blendedList.push(mesh);
638
- }
639
- else
640
- this.opaqueList.push(mesh);
641
- }
642
- for (const child of mesh.children)
643
- visit(child);
644
- };
645
- for (const mesh of this.meshes)
646
- if (mesh.traversalStamp !== stamp)
647
- visit(mesh);
692
+ renderBlended(camera, time = Time.now(), flatLights = this.lights.flatten(this.ambientColor), frustum = null) {
693
+ this.collector.collect(this.meshes, camera, frustum, false, true, this.markDescendants());
694
+ return this.collector.drawBlended(this, camera, time, flatLights, this.beginFrame());
648
695
  }
649
- drawBlended(camera, time, flatLights, frame) {
650
- const list = this.blendedList;
651
- if (list.length === 0)
652
- return 0;
653
- if (list.length > 1)
654
- list.sort((a, b) => b.sortKey - a.sortKey);
655
- const projMatrix = camera.getProjectionMatrix();
656
- const viewMatrix = camera.getViewMatrix();
657
- const camPos = camera.position;
658
- for (const mesh of list) {
659
- const blend = mesh.blendOptions;
660
- this.enableBlendMode(blend.equation, blend.source, blend.destination);
661
- this.renderer.setDepthWrite(mesh.depthWrite);
662
- mesh.renderSelf(projMatrix, viewMatrix, flatLights, time, true, camPos, this.fog, frame);
696
+ }
697
+
698
+ const Shader = {
699
+ create(context, str, type) {
700
+ const shader = context.createShader(context[type]);
701
+ if (!shader)
702
+ return null;
703
+ context.shaderSource(shader, str);
704
+ context.compileShader(shader);
705
+ if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) {
706
+ console.error(`${type} compilation failed:`, context.getShaderInfoLog(shader));
707
+ context.deleteShader(shader);
708
+ return null;
663
709
  }
664
- this.disableBlendMode();
665
- this.renderer.setDepthWrite(true);
666
- return list.length;
710
+ return shader;
667
711
  }
668
- renderBlended(camera, time = Time.now(), flatLights = this.lights.flatten(this.ambientColor), frustum = null) {
669
- this.collect(camera, frustum, false, true, this.markDescendants());
670
- return this.drawBlended(camera, time, flatLights, this.beginFrame());
712
+ };
713
+
714
+ const caches = new WeakMap();
715
+ const owners = new WeakMap();
716
+ const Program = {
717
+ create(context, vertexShader, fragmentShader) {
718
+ const program = context.createProgram();
719
+ if (program) {
720
+ const vshader = Shader.create(context, vertexShader, 'VERTEX_SHADER');
721
+ const fshader = Shader.create(context, fragmentShader, 'FRAGMENT_SHADER');
722
+ if (!vshader || !fshader) {
723
+ if (vshader)
724
+ context.deleteShader(vshader);
725
+ if (fshader)
726
+ context.deleteShader(fshader);
727
+ context.deleteProgram(program);
728
+ return null;
729
+ }
730
+ context.attachShader(program, vshader);
731
+ context.attachShader(program, fshader);
732
+ context.linkProgram(program);
733
+ if (!context.getProgramParameter(program, context.LINK_STATUS)) {
734
+ console.error('Program link failed:', context.getProgramInfoLog(program));
735
+ context.deleteShader(vshader);
736
+ context.deleteShader(fshader);
737
+ context.deleteProgram(program);
738
+ return null;
739
+ }
740
+ context.detachShader(program, vshader);
741
+ context.detachShader(program, fshader);
742
+ context.deleteShader(vshader);
743
+ context.deleteShader(fshader);
744
+ }
745
+ return program;
746
+ },
747
+ acquire(context, vertexShader, fragmentShader) {
748
+ let cache = caches.get(context);
749
+ if (!cache) {
750
+ cache = new Map();
751
+ caches.set(context, cache);
752
+ }
753
+ const key = `${vertexShader}\u0000${fragmentShader}`;
754
+ let entry = cache.get(key);
755
+ if (!entry) {
756
+ const program = Program.create(context, vertexShader, fragmentShader);
757
+ if (!program)
758
+ return null;
759
+ entry = { program, key, refs: 0 };
760
+ cache.set(key, entry);
761
+ owners.set(program, entry);
762
+ }
763
+ entry.refs++;
764
+ return entry.program;
765
+ },
766
+ release(context, program) {
767
+ const entry = owners.get(program);
768
+ if (!entry) {
769
+ context.deleteProgram(program);
770
+ return;
771
+ }
772
+ if (--entry.refs > 0)
773
+ return;
774
+ const cache = caches.get(context);
775
+ if (cache?.get(entry.key) === entry)
776
+ cache.delete(entry.key);
777
+ owners.delete(program);
778
+ context.deleteProgram(program);
779
+ },
780
+ sources(program) {
781
+ const entry = owners.get(program);
782
+ if (!entry)
783
+ return null;
784
+ const [vertex, fragment] = entry.key.split('\u0000');
785
+ return { vertex, fragment };
786
+ },
787
+ reset(context) {
788
+ caches.delete(context);
671
789
  }
672
- }
790
+ };
673
791
 
674
792
  class Renderer {
675
793
  canvas;
@@ -687,14 +805,19 @@ class Renderer {
687
805
  };
688
806
  this.onContextRestoredBound = () => {
689
807
  this.isContextLost = false;
808
+ if (this.context) {
809
+ Program.reset(this.context);
810
+ getGLState(this.context).reset();
811
+ }
690
812
  this.defaultSettings();
691
813
  this.options.onContextRestored?.();
692
814
  };
693
815
  this.canvas = typeof canvasID === 'string' ? this.findById(canvasID) : canvasID;
694
816
  this.context = null;
695
817
  if (this.canvas) {
696
- this.canvas.width = options.width ?? 1280;
697
- this.canvas.height = options.height ?? 720;
818
+ const size = options.size ?? new Vec2(1280, 720);
819
+ this.canvas.width = size.x;
820
+ this.canvas.height = size.y;
698
821
  const attributes = { alpha: false, ...options.contextAttributes };
699
822
  this.context = (this.canvas.getContext("webgl", attributes) ||
700
823
  this.canvas.getContext("experimental-webgl", attributes)) || null;
@@ -720,8 +843,8 @@ class Renderer {
720
843
  this.setFrontFace('CW');
721
844
  this.enable('CULL_FACE');
722
845
  this.setCullFace('BACK');
723
- this.setViewport(this.context.drawingBufferWidth, this.context.drawingBufferHeight);
724
- this.setClearColor(0.0, 0.0, 0.0, 1.0);
846
+ this.setViewport(new Vec2(this.context.drawingBufferWidth, this.context.drawingBufferHeight));
847
+ this.setClearColor(new Vec3(0.0, 0.0, 0.0), 1.0);
725
848
  }
726
849
  setFrontFace(mode) {
727
850
  if (this.context)
@@ -739,22 +862,14 @@ class Renderer {
739
862
  if (this.context)
740
863
  this.context.cullFace(this.context[mode]);
741
864
  }
742
- setViewport(width, height) {
865
+ setViewport(size) {
743
866
  if (this.context)
744
- this.context.viewport(0, 0, width, height);
867
+ this.context.viewport(0, 0, size.x, size.y);
745
868
  }
746
- setClearColor(rOrVec, g, b, a) {
747
- if (this.context)
748
- if (rOrVec instanceof Vec3) {
749
- const alpha = g !== undefined ? Utils.clamp(g, 0, 1) : 1.0;
750
- this.context.clearColor(Utils.clamp(rOrVec.x, 0, 1), Utils.clamp(rOrVec.y, 0, 1), Utils.clamp(rOrVec.z, 0, 1), alpha);
751
- }
752
- else {
753
- const green = g !== undefined ? Utils.clamp(g, 0, 1) : 0;
754
- const blue = b !== undefined ? Utils.clamp(b, 0, 1) : 0;
755
- const alpha = a !== undefined ? Utils.clamp(a, 0, 1) : 1.0;
756
- this.context.clearColor(Utils.clamp(rOrVec, 0, 1), green, blue, alpha);
757
- }
869
+ setClearColor(color, alpha = 1.0) {
870
+ if (!this.context)
871
+ return;
872
+ this.context.clearColor(Utils.clamp(color.x, 0, 1), Utils.clamp(color.y, 0, 1), Utils.clamp(color.z, 0, 1), Utils.clamp(alpha, 0, 1));
758
873
  }
759
874
  clearFrame() {
760
875
  if (this.context)
@@ -766,14 +881,19 @@ class Renderer {
766
881
  getCanvas() {
767
882
  return this.canvas;
768
883
  }
769
- setSize(sizeOrWidth, height) {
770
- const w = sizeOrWidth instanceof Vec2 ? sizeOrWidth.x : sizeOrWidth;
771
- const h = sizeOrWidth instanceof Vec2 ? sizeOrWidth.y : (height ?? 0);
884
+ setSize(size) {
885
+ if (this.canvas) {
886
+ this.canvas.width = size.x;
887
+ this.canvas.height = size.y;
888
+ }
889
+ this.setViewport(size);
890
+ }
891
+ getSize(target = new Vec2()) {
772
892
  if (this.canvas) {
773
- this.canvas.width = w;
774
- this.canvas.height = h;
893
+ target.x = this.canvas.width;
894
+ target.y = this.canvas.height;
775
895
  }
776
- this.setViewport(w, h);
896
+ return target;
777
897
  }
778
898
  findById(id) {
779
899
  return document.getElementById(id);
@@ -787,22 +907,18 @@ class RenderTarget {
787
907
  framebuffer;
788
908
  texture;
789
909
  depthBuffer;
790
- constructor(sizeOrWidth, heightOrContext, context) {
791
- if (sizeOrWidth instanceof Vec2) {
792
- this.width = sizeOrWidth.x;
793
- this.height = sizeOrWidth.y;
794
- this.context = heightOrContext;
795
- }
796
- else {
797
- this.width = sizeOrWidth;
798
- this.height = heightOrContext;
799
- this.context = context;
800
- }
910
+ constructor(size, context) {
911
+ this.width = size.x;
912
+ this.height = size.y;
913
+ this.context = context;
801
914
  this.framebuffer = null;
802
915
  this.texture = null;
803
916
  this.depthBuffer = null;
804
917
  this.init();
805
918
  }
919
+ restore() {
920
+ this.init();
921
+ }
806
922
  init() {
807
923
  const gl = this.context;
808
924
  this.framebuffer = gl.createFramebuffer();
@@ -828,20 +944,20 @@ class RenderTarget {
828
944
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
829
945
  gl.viewport(0, 0, this.width, this.height);
830
946
  }
831
- unbind(viewportWidthOrSize, viewportHeight) {
947
+ unbind(viewportSize) {
832
948
  const gl = this.context;
833
949
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
834
- if (viewportWidthOrSize instanceof Vec2)
835
- gl.viewport(0, 0, viewportWidthOrSize.x, viewportWidthOrSize.y);
836
- else if (viewportWidthOrSize !== undefined && viewportHeight !== undefined)
837
- gl.viewport(0, 0, viewportWidthOrSize, viewportHeight);
950
+ if (viewportSize)
951
+ gl.viewport(0, 0, viewportSize.x, viewportSize.y);
838
952
  }
839
953
  getSize(target = new Vec2()) {
840
- return target.setScalar(this.width, this.height);
954
+ target.x = this.width;
955
+ target.y = this.height;
956
+ return target;
841
957
  }
842
- setSize(sizeOrWidth, height) {
843
- const w = sizeOrWidth instanceof Vec2 ? sizeOrWidth.x : sizeOrWidth;
844
- const h = sizeOrWidth instanceof Vec2 ? sizeOrWidth.y : (height ?? 0);
958
+ setSize(size) {
959
+ const w = size.x;
960
+ const h = size.y;
845
961
  if (this.width === w && this.height === h)
846
962
  return;
847
963
  this.width = w;
@@ -922,7 +1038,7 @@ class Camera {
922
1038
  this.projectionViewMatrix.copy(this.projectionMatrix).multiplyAffine(this.viewMatrix);
923
1039
  this.isProjectionViewDirty = false;
924
1040
  }
925
- return this.projectionViewMatrix.toArray();
1041
+ return this.projectionViewMatrix;
926
1042
  }
927
1043
  getInverseProjectionViewMatrix() {
928
1044
  if (this.isInverseDirty) {
@@ -932,25 +1048,16 @@ class Camera {
932
1048
  }
933
1049
  return this.inverseProjectionViewMatrix;
934
1050
  }
935
- setPosition(xOrVec, y, z) {
936
- if (xOrVec instanceof Vec3)
937
- this.position.copy(xOrVec);
938
- else
939
- this.position.setScalar(xOrVec, y, z);
1051
+ setPosition(position) {
1052
+ this.position.copy(position);
940
1053
  this.setViewMatrix();
941
1054
  }
942
- setTarget(xOrVec, y, z) {
943
- if (xOrVec instanceof Vec3)
944
- this.target.copy(xOrVec);
945
- else
946
- this.target.setScalar(xOrVec, y, z);
1055
+ setTarget(target) {
1056
+ this.target.copy(target);
947
1057
  this.setViewMatrix();
948
1058
  }
949
- setUp(xOrVec, y, z) {
950
- if (xOrVec instanceof Vec3)
951
- this.up.copy(xOrVec);
952
- else
953
- this.up.setScalar(xOrVec, y, z);
1059
+ setUp(up) {
1060
+ this.up.copy(up);
954
1061
  this.setViewMatrix();
955
1062
  }
956
1063
  lookAt(target) {
@@ -978,26 +1085,13 @@ class Camera {
978
1085
  getProjectionMatrixObject() {
979
1086
  return this.projectionMatrix;
980
1087
  }
981
- getRay(ndcOrX, ndcYOrTarget, targetRay = new Ray3()) {
982
- let ndcX;
983
- let ndcY;
984
- let outRay = targetRay;
985
- if (ndcOrX instanceof Vec2) {
986
- ndcX = ndcOrX.x;
987
- ndcY = ndcOrX.y;
988
- if (ndcYOrTarget instanceof Ray3)
989
- outRay = ndcYOrTarget;
990
- }
991
- else {
992
- ndcX = ndcOrX;
993
- ndcY = typeof ndcYOrTarget === 'number' ? ndcYOrTarget : 0;
994
- }
1088
+ getRay(ndc, targetRay = new Ray3()) {
995
1089
  const inverse = this.getInverseProjectionViewMatrix();
996
- const near = inverse.transformPoint(Camera._scratchNdc.setScalar(ndcX, ndcY, -1), Camera._scratchNear);
997
- const far = inverse.transformPoint(Camera._scratchNdc.setScalar(ndcX, ndcY, 1.0), Camera._scratchFar);
1090
+ const near = inverse.transformPoint(Camera._scratchNdc.setScalar(ndc.x, ndc.y, -1), Camera._scratchNear);
1091
+ const far = inverse.transformPoint(Camera._scratchNdc.setScalar(ndc.x, ndc.y, 1.0), Camera._scratchFar);
998
1092
  const dir = Camera._scratchDir.subVectors(far, near).normalize();
999
- outRay.set(near, dir);
1000
- return outRay;
1093
+ targetRay.set(near, dir);
1094
+ return targetRay;
1001
1095
  }
1002
1096
  }
1003
1097
 
@@ -1006,35 +1100,24 @@ class PerspectiveCamera extends Camera {
1006
1100
  ratio;
1007
1101
  zNear;
1008
1102
  zFar;
1009
- constructor(fov, zNearOrContext, zFarOrNear, contextOrFar) {
1103
+ constructor(fov, nearFar = new Vec2(0.1, 1000), context) {
1010
1104
  super(new Vec3(), new Vec3(), new Vec3(0.0, 1.0, 0.0));
1011
- let zNear = 0.1;
1012
- let zFar = 1000;
1013
- let context = null;
1014
- if (zNearOrContext && typeof zNearOrContext !== 'number') {
1015
- context = zNearOrContext;
1016
- zNear = typeof zFarOrNear === 'number' ? zFarOrNear : 0.1;
1017
- zFar = typeof contextOrFar === 'number' ? contextOrFar : 1000;
1018
- }
1019
- else {
1020
- zNear = typeof zNearOrContext === 'number' ? zNearOrContext : 0.1;
1021
- zFar = typeof zFarOrNear === 'number' ? zFarOrNear : 1000;
1022
- if (contextOrFar && typeof contextOrFar !== 'number')
1023
- context = contextOrFar;
1024
- }
1025
1105
  this.fov = fov;
1026
1106
  this.ratio = 0;
1027
- this.zNear = zNear;
1028
- this.zFar = zFar;
1029
- const viewport = context && typeof context.getParameter === 'function' ? context.getParameter(context.VIEWPORT) : null;
1107
+ this.zNear = nearFar.x;
1108
+ this.zFar = nearFar.y;
1109
+ let viewport = null;
1110
+ if (context && typeof context.getParameter === 'function') {
1111
+ const vp = context.getParameter(context.VIEWPORT);
1112
+ if (vp && vp.length >= 4)
1113
+ viewport = new Vec2(vp[2], vp[3]);
1114
+ }
1030
1115
  this.setProjectionMatrix(viewport);
1031
1116
  super.setViewMatrix();
1032
1117
  }
1033
1118
  setProjectionMatrix(viewport) {
1034
- if (viewport instanceof Vec2)
1119
+ if (viewport)
1035
1120
  this.ratio = viewport.x / Math.max(1, viewport.y);
1036
- else if (viewport && viewport.length >= 4)
1037
- this.ratio = viewport[2] / Math.max(1, viewport[3]);
1038
1121
  else
1039
1122
  this.ratio = this.ratio || 1;
1040
1123
  this.projectionMatrix.perspective(this.fov, this.ratio, this.zNear, this.zFar);
@@ -1044,11 +1127,16 @@ class PerspectiveCamera extends Camera {
1044
1127
  this.fov = fov;
1045
1128
  this.setProjectionMatrix();
1046
1129
  }
1047
- setNearFar(zNear, zFar) {
1048
- this.zNear = zNear;
1049
- this.zFar = zFar;
1130
+ setNearFar(nearFar) {
1131
+ this.zNear = nearFar.x;
1132
+ this.zFar = nearFar.y;
1050
1133
  this.setProjectionMatrix();
1051
1134
  }
1135
+ getNearFar(target = new Vec2()) {
1136
+ target.x = this.zNear;
1137
+ target.y = this.zFar;
1138
+ return target;
1139
+ }
1052
1140
  }
1053
1141
 
1054
1142
  class OrthographicCamera extends Camera {
@@ -1058,24 +1146,14 @@ class OrthographicCamera extends Camera {
1058
1146
  bottom;
1059
1147
  near;
1060
1148
  far;
1061
- constructor(boundsOrLeft, rightOrNear, topOrFar, bottom, near, far) {
1149
+ constructor(bounds, nearFar = new Vec2(-1e3, 1000)) {
1062
1150
  super(new Vec3(), new Vec3(), new Vec3(0.0, 1.0, 0.0));
1063
- if (boundsOrLeft instanceof Rect) {
1064
- this.left = boundsOrLeft.topLeftCorner.x;
1065
- this.right = boundsOrLeft.bottomRightCorner.x;
1066
- this.bottom = boundsOrLeft.topLeftCorner.y;
1067
- this.top = boundsOrLeft.bottomRightCorner.y;
1068
- this.near = rightOrNear !== undefined ? rightOrNear : -1e3;
1069
- this.far = topOrFar !== undefined ? topOrFar : 1000;
1070
- }
1071
- else {
1072
- this.left = boundsOrLeft;
1073
- this.right = rightOrNear;
1074
- this.top = topOrFar;
1075
- this.bottom = bottom;
1076
- this.near = near !== undefined ? near : -1e3;
1077
- this.far = far !== undefined ? far : 1000;
1078
- }
1151
+ this.left = bounds.topLeftCorner.x;
1152
+ this.right = bounds.bottomRightCorner.x;
1153
+ this.bottom = bounds.topLeftCorner.y;
1154
+ this.top = bounds.bottomRightCorner.y;
1155
+ this.near = nearFar.x;
1156
+ this.far = nearFar.y;
1079
1157
  this.setProjectionMatrix();
1080
1158
  super.setViewMatrix();
1081
1159
  }
@@ -1083,29 +1161,22 @@ class OrthographicCamera extends Camera {
1083
1161
  this.projectionMatrix.orthographic(this.left, this.right, this.top, this.bottom, this.near, this.far);
1084
1162
  super.setProjectionMatrix();
1085
1163
  }
1086
- setBounds(boundsOrLeft, rightOrNear, topOrFar, bottom, near, far) {
1087
- if (boundsOrLeft instanceof Rect) {
1088
- this.left = boundsOrLeft.topLeftCorner.x;
1089
- this.right = boundsOrLeft.bottomRightCorner.x;
1090
- this.bottom = boundsOrLeft.topLeftCorner.y;
1091
- this.top = boundsOrLeft.bottomRightCorner.y;
1092
- if (rightOrNear !== undefined)
1093
- this.near = rightOrNear;
1094
- if (topOrFar !== undefined)
1095
- this.far = topOrFar;
1096
- }
1097
- else {
1098
- this.left = boundsOrLeft;
1099
- this.right = rightOrNear;
1100
- this.top = topOrFar;
1101
- this.bottom = bottom;
1102
- if (near !== undefined)
1103
- this.near = near;
1104
- if (far !== undefined)
1105
- this.far = far;
1164
+ setBounds(bounds, nearFar) {
1165
+ this.left = bounds.topLeftCorner.x;
1166
+ this.right = bounds.bottomRightCorner.x;
1167
+ this.bottom = bounds.topLeftCorner.y;
1168
+ this.top = bounds.bottomRightCorner.y;
1169
+ if (nearFar) {
1170
+ this.near = nearFar.x;
1171
+ this.far = nearFar.y;
1106
1172
  }
1107
1173
  this.setProjectionMatrix();
1108
1174
  }
1175
+ getNearFar(target = new Vec2()) {
1176
+ target.x = this.near;
1177
+ target.y = this.far;
1178
+ return target;
1179
+ }
1109
1180
  }
1110
1181
 
1111
1182
  class OrbitControls {
@@ -1326,88 +1397,273 @@ class OrbitControls {
1326
1397
  }
1327
1398
  }
1328
1399
 
1329
- const Shader = {
1330
- create(context, str, type) {
1331
- let shader = context.createShader(context[type]);
1332
- if (!shader)
1333
- return null;
1334
- context.shaderSource(shader, str);
1335
- context.compileShader(shader);
1336
- if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) {
1337
- console.error(`${type} compilation failed:`, context.getShaderInfoLog(shader));
1338
- context.deleteShader(shader);
1339
- return null;
1340
- }
1341
- return shader;
1342
- }
1343
- };
1344
-
1345
- const caches = new WeakMap();
1346
- const owners = new WeakMap();
1347
- class Program {
1348
- static create(context, vertexShader, fragmentShader) {
1349
- let program = context.createProgram();
1350
- if (program) {
1351
- let vshader = Shader.create(context, vertexShader, 'VERTEX_SHADER');
1352
- let fshader = Shader.create(context, fragmentShader, 'FRAGMENT_SHADER');
1353
- if (!vshader || !fshader) {
1354
- if (vshader)
1355
- context.deleteShader(vshader);
1356
- if (fshader)
1357
- context.deleteShader(fshader);
1358
- context.deleteProgram(program);
1359
- return null;
1360
- }
1361
- context.attachShader(program, vshader);
1362
- context.attachShader(program, fshader);
1363
- context.linkProgram(program);
1364
- if (!context.getProgramParameter(program, context.LINK_STATUS)) {
1365
- console.error('Program link failed:', context.getProgramInfoLog(program));
1366
- context.deleteShader(vshader);
1367
- context.deleteShader(fshader);
1368
- context.deleteProgram(program);
1369
- return null;
1370
- }
1371
- context.detachShader(program, vshader);
1372
- context.detachShader(program, fshader);
1373
- context.deleteShader(vshader);
1374
- context.deleteShader(fshader);
1400
+ class Node {
1401
+ position;
1402
+ scale;
1403
+ quaternion;
1404
+ modelMatrix;
1405
+ worldMatrix;
1406
+ normalMatrix;
1407
+ boundingBox;
1408
+ boundingSphere;
1409
+ localBoundingBox;
1410
+ localBoundingSphere;
1411
+ parent = null;
1412
+ children = [];
1413
+ traversalStamp = 0;
1414
+ isBoundsDirty = true;
1415
+ isNormalMatrixDirty = true;
1416
+ isWorldDirty = true;
1417
+ _rotation;
1418
+ isRotationStale = false;
1419
+ isTransformDirty = false;
1420
+ static UP = new Vec3(0, 1, 0);
1421
+ static scratchQuat = new Quat();
1422
+ static scratchVec = new Vec3();
1423
+ constructor() {
1424
+ this.position = new Vec3();
1425
+ this._rotation = new Vec3();
1426
+ this.scale = new Vec3(1.0, 1.0, 1.0);
1427
+ this.quaternion = new Quat();
1428
+ this.modelMatrix = new Mat4x3();
1429
+ this.worldMatrix = new Mat4x3();
1430
+ this.normalMatrix = new Mat3();
1431
+ this.modelMatrix.identity();
1432
+ this.worldMatrix.identity();
1433
+ this.normalMatrix.identity();
1434
+ this.localBoundingBox = new Box();
1435
+ this.localBoundingSphere = new Sphere$1();
1436
+ this.boundingBox = new Box();
1437
+ this.boundingSphere = new Sphere$1();
1438
+ }
1439
+ addChild(node) {
1440
+ if (this.children.indexOf(node) === -1) {
1441
+ if (node.parent && node.parent !== this)
1442
+ node.parent.removeChild(node);
1443
+ this.children.push(node);
1444
+ node.parent = this;
1445
+ node.isWorldDirty = true;
1375
1446
  }
1376
- return program;
1377
1447
  }
1378
- static acquire(context, vertexShader, fragmentShader) {
1379
- let cache = caches.get(context);
1380
- if (!cache) {
1381
- cache = new Map();
1382
- caches.set(context, cache);
1448
+ removeChild(node) {
1449
+ const index = this.children.indexOf(node);
1450
+ if (index !== -1) {
1451
+ this.children.splice(index, 1);
1452
+ node.parent = null;
1453
+ node.isWorldDirty = true;
1454
+ return true;
1383
1455
  }
1384
- const key = vertexShader + '\u0000' + fragmentShader;
1385
- let entry = cache.get(key);
1386
- if (!entry) {
1387
- const program = Program.create(context, vertexShader, fragmentShader);
1388
- if (!program)
1389
- return null;
1390
- entry = { program, key, refs: 0 };
1391
- cache.set(key, entry);
1392
- owners.set(program, entry);
1456
+ return false;
1457
+ }
1458
+ markDirty() {
1459
+ this.isTransformDirty = true;
1460
+ this.isBoundsDirty = true;
1461
+ this.isNormalMatrixDirty = true;
1462
+ }
1463
+ get rotation() {
1464
+ if (this.isRotationStale) {
1465
+ this.quaternion.getEuler(this._rotation);
1466
+ this.isRotationStale = false;
1393
1467
  }
1394
- entry.refs++;
1395
- return entry.program;
1468
+ return this._rotation;
1396
1469
  }
1397
- static release(context, program) {
1398
- const entry = owners.get(program);
1399
- if (!entry) {
1400
- context.deleteProgram(program);
1470
+ set rotation(euler) {
1471
+ this.setRotation(euler);
1472
+ }
1473
+ rotationChanged() {
1474
+ this.isRotationStale = true;
1475
+ this.markDirty();
1476
+ }
1477
+ setPosition(position) {
1478
+ this.position.copy(position);
1479
+ this.markDirty();
1480
+ return this;
1481
+ }
1482
+ setRotation(rotation) {
1483
+ this._rotation.copy(rotation);
1484
+ this.quaternion.setFromEuler(this._rotation);
1485
+ this.isRotationStale = false;
1486
+ this.markDirty();
1487
+ return this;
1488
+ }
1489
+ setScale(scale) {
1490
+ this.scale.copy(scale);
1491
+ this.markDirty();
1492
+ return this;
1493
+ }
1494
+ scaleBy(factor) {
1495
+ this.scale.multiply(factor);
1496
+ this.markDirty();
1497
+ return this;
1498
+ }
1499
+ setQuaternion(q) {
1500
+ this.quaternion.copy(q);
1501
+ this.rotationChanged();
1502
+ return this;
1503
+ }
1504
+ slerp(targetQuat, t) {
1505
+ this.quaternion.slerp(targetQuat, t);
1506
+ this.rotationChanged();
1507
+ return this;
1508
+ }
1509
+ rotateX(angle) {
1510
+ this.quaternion.rotateX(angle);
1511
+ this.rotationChanged();
1512
+ return this;
1513
+ }
1514
+ rotateY(angle) {
1515
+ this.quaternion.rotateY(angle);
1516
+ this.rotationChanged();
1517
+ return this;
1518
+ }
1519
+ rotateZ(angle) {
1520
+ this.quaternion.rotateZ(angle);
1521
+ this.rotationChanged();
1522
+ return this;
1523
+ }
1524
+ rotateOnAxis(axis, angle) {
1525
+ this.quaternion.multiply(Node.scratchQuat.setAxisAngle(axis, angle));
1526
+ this.rotationChanged();
1527
+ return this;
1528
+ }
1529
+ translate(translation) {
1530
+ this.position.add(translation);
1531
+ this.markDirty();
1532
+ return this;
1533
+ }
1534
+ lookAt(target, up = Node.UP) {
1535
+ if (this.position.equals(target))
1536
+ return this;
1537
+ this.quaternion.lookAt(this.position, target, up);
1538
+ this.rotationChanged();
1539
+ return this;
1540
+ }
1541
+ updateModelMatrix() {
1542
+ if (!this.isTransformDirty)
1543
+ return false;
1544
+ this.modelMatrix.compose(this.position, this.quaternion, this.scale);
1545
+ this.isTransformDirty = false;
1546
+ this.isBoundsDirty = true;
1547
+ this.isNormalMatrixDirty = true;
1548
+ return true;
1549
+ }
1550
+ computeNormalMatrix() {
1551
+ if (!this.isNormalMatrixDirty)
1552
+ return this.normalMatrix;
1553
+ this.worldMatrix.toNormalMatrix(this.normalMatrix.toArray());
1554
+ this.isNormalMatrixDirty = false;
1555
+ return this.normalMatrix;
1556
+ }
1557
+ getWorldPosition(target = new Vec3()) {
1558
+ return this.worldMatrix.getTranslation(target);
1559
+ }
1560
+ getWorldDirection(target = new Vec3()) {
1561
+ return this.worldMatrix.getColumn(2, target).opposite().normalize();
1562
+ }
1563
+ distanceTo(target) {
1564
+ const targetPos = target instanceof Vec3 ? target : target.getWorldPosition();
1565
+ return this.getWorldPosition(Node.scratchVec).getDistance(targetPos);
1566
+ }
1567
+ isInsideBounds(box) {
1568
+ return box.containsPoint(this.getWorldPosition(Node.scratchVec));
1569
+ }
1570
+ setLocalBounds(box) {
1571
+ this.localBoundingBox.copy(box);
1572
+ this.localBoundingBox.getCenter(Node.scratchVec);
1573
+ this.localBoundingSphere.set(Node.scratchVec, box.min.getDistance(box.max) / 2);
1574
+ this.isBoundsDirty = true;
1575
+ return this;
1576
+ }
1577
+ computeBounds() {
1578
+ if (!this.isBoundsDirty)
1401
1579
  return;
1580
+ if (!this.localBoundingBox.isEmpty()) {
1581
+ this.boundingBox.copy(this.localBoundingBox).applyMatrix4(this.worldMatrix);
1582
+ this.boundingSphere.copy(this.localBoundingSphere).applyMatrix4(this.worldMatrix);
1402
1583
  }
1403
- if (--entry.refs > 0)
1404
- return;
1405
- caches.get(context)?.delete(entry.key);
1406
- owners.delete(program);
1407
- context.deleteProgram(program);
1584
+ else {
1585
+ const pos = this.getWorldPosition(Node.scratchVec);
1586
+ this.boundingBox.set(pos, pos);
1587
+ this.boundingSphere.set(pos, 0);
1588
+ }
1589
+ this.isBoundsDirty = false;
1590
+ }
1591
+ computeBoundingBox() {
1592
+ this.computeBounds();
1593
+ return this.boundingBox;
1594
+ }
1595
+ computeBoundingSphere() {
1596
+ this.computeBounds();
1597
+ return this.boundingSphere;
1598
+ }
1599
+ setWorldMatrix(worldMatrix) {
1600
+ this.worldMatrix.copy(worldMatrix).multiply(this.modelMatrix);
1601
+ this.isBoundsDirty = true;
1602
+ this.isNormalMatrixDirty = true;
1603
+ this.isWorldDirty = false;
1604
+ }
1605
+ computeWorldMatrix(graph) {
1606
+ const modelChanged = this.updateModelMatrix();
1607
+ const changed = this.isWorldDirty || modelChanged;
1608
+ if (changed)
1609
+ this.setWorldMatrix(graph.getWorldMatrix());
1610
+ graph.pushModelMatrix(this.worldMatrix);
1611
+ for (const child of this.children) {
1612
+ if (changed)
1613
+ child.isWorldDirty = true;
1614
+ child.computeWorldMatrix(graph);
1615
+ }
1616
+ graph.popModelMatrix();
1408
1617
  }
1409
1618
  }
1410
1619
 
1620
+ const scratchInverse = new Mat4x3();
1621
+ const scratchRay = new Ray3();
1622
+ const scratchVec = new Vec3();
1623
+ const scratchDir = new Vec3();
1624
+ const scratchA = new Vec3();
1625
+ const scratchB = new Vec3();
1626
+ const scratchC = new Vec3();
1627
+ const scratchHit = new Vec3();
1628
+ const scratchWorldHit = new Vec3();
1629
+ function getVertex(targetMesh, index, target) {
1630
+ const vertices = targetMesh.vertices;
1631
+ const size = targetMesh.itemSize || 3;
1632
+ const offset = (targetMesh.indices ? targetMesh.indices[index] : index) * size;
1633
+ return target.setScalar(vertices[offset], vertices[offset + 1], size > 2 ? vertices[offset + 2] : 0);
1634
+ }
1635
+ function raycastTriangles(mesh, ray) {
1636
+ const inverse = scratchInverse.copy(mesh.worldMatrix).invertAffine();
1637
+ const localRay = scratchRay.set(inverse.transformPoint(ray.origin, scratchVec), inverse.transformDirection(ray.direction, scratchDir));
1638
+ const strip = mesh.primitive === 'TRIANGLE_STRIP';
1639
+ let best = null;
1640
+ let bestDistance = Infinity;
1641
+ for (const subMesh of mesh.subMeshes) {
1642
+ const end = subMesh.start + subMesh.count;
1643
+ for (let i = subMesh.start; i + 2 < end; i += strip ? 1 : 3) {
1644
+ getVertex(mesh, i, scratchA);
1645
+ if (strip && (i - subMesh.start) % 2 === 1) {
1646
+ getVertex(mesh, i + 2, scratchB);
1647
+ getVertex(mesh, i + 1, scratchC);
1648
+ }
1649
+ else {
1650
+ getVertex(mesh, i + 1, scratchB);
1651
+ getVertex(mesh, i + 2, scratchC);
1652
+ }
1653
+ const local = localRay.intersectTriangle(scratchA, scratchB, scratchC, false, scratchHit);
1654
+ if (!local)
1655
+ continue;
1656
+ const world = mesh.worldMatrix.transformPoint(local, scratchWorldHit);
1657
+ const distance = ray.origin.getDistance(world);
1658
+ if (distance < bestDistance) {
1659
+ bestDistance = distance;
1660
+ best = (best ?? new Vec3()).copy(world);
1661
+ }
1662
+ }
1663
+ }
1664
+ return best ? { distance: bestDistance, point: best } : null;
1665
+ }
1666
+
1411
1667
  const isPowerOfTwo = (n) => (n & (n - 1)) === 0;
1412
1668
  function canGenerateMipmaps(context, width, height) {
1413
1669
  if (typeof WebGL2RenderingContext !== 'undefined' && context instanceof WebGL2RenderingContext)
@@ -1416,7 +1672,7 @@ function canGenerateMipmaps(context, width, height) {
1416
1672
  }
1417
1673
  const Texture = {
1418
1674
  create(source, context, options) {
1419
- let webGLTexture = context.createTexture();
1675
+ const webGLTexture = context.createTexture();
1420
1676
  if (!webGLTexture)
1421
1677
  return null;
1422
1678
  const flipY = options?.flipY !== undefined ? options.flipY : true;
@@ -1430,13 +1686,16 @@ const Texture = {
1430
1686
  context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, minFilter);
1431
1687
  context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, wrapS);
1432
1688
  context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, wrapT);
1433
- if (source instanceof Uint8Array)
1434
- context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, options?.width || 1, options?.height || 1, 0, context.RGBA, context.UNSIGNED_BYTE, source);
1689
+ if (source instanceof Uint8Array) {
1690
+ const width = options?.size ? options.size.x : 1;
1691
+ const height = options?.size ? options.size.y : 1;
1692
+ context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, width, height, 0, context.RGBA, context.UNSIGNED_BYTE, source);
1693
+ }
1435
1694
  else
1436
1695
  context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, source);
1437
1696
  if (options?.generateMipmaps) {
1438
- const width = source instanceof Uint8Array ? options.width || 1 : source.width;
1439
- const height = source instanceof Uint8Array ? options.height || 1 : source.height;
1697
+ const width = source instanceof Uint8Array ? (options?.size ? options.size.x : 1) : source.width;
1698
+ const height = source instanceof Uint8Array ? (options?.size ? options.size.y : 1) : source.height;
1440
1699
  if (canGenerateMipmaps(context, width, height))
1441
1700
  context.generateMipmap(context.TEXTURE_2D);
1442
1701
  else
@@ -1454,11 +1713,15 @@ const Texture = {
1454
1713
  img.src = url;
1455
1714
  });
1456
1715
  },
1457
- update(texture, source, context, xOffset = 0, yOffset = 0, width, height, flipY = true) {
1716
+ update(texture, source, context, offset = new Vec2(), size, flipY = true) {
1458
1717
  context.bindTexture(context.TEXTURE_2D, texture);
1459
1718
  context.pixelStorei(context.UNPACK_FLIP_Y_WEBGL, flipY ? 1 : 0);
1719
+ const xOffset = offset.x;
1720
+ const yOffset = offset.y;
1721
+ const width = size ? size.x : 1;
1722
+ const height = size ? size.y : 1;
1460
1723
  if (source instanceof Uint8Array)
1461
- context.texSubImage2D(context.TEXTURE_2D, 0, xOffset, yOffset, width || 1, height || 1, context.RGBA, context.UNSIGNED_BYTE, source);
1724
+ context.texSubImage2D(context.TEXTURE_2D, 0, xOffset, yOffset, width, height, context.RGBA, context.UNSIGNED_BYTE, source);
1462
1725
  else
1463
1726
  context.texSubImage2D(context.TEXTURE_2D, 0, xOffset, yOffset, context.RGBA, context.UNSIGNED_BYTE, source);
1464
1727
  context.bindTexture(context.TEXTURE_2D, null);
@@ -1494,6 +1757,95 @@ function uploadUniform(context, location, uniform) {
1494
1757
  context[type](location, value);
1495
1758
  }
1496
1759
 
1760
+ const scratchView = new Mat4x3();
1761
+ const scratchCamera = new Vec3();
1762
+ function lightTypeValue(type) {
1763
+ return type === 'directional' ? 0 : type === 'point' ? 1 : 2;
1764
+ }
1765
+ function packVec4(xyz, w) {
1766
+ const count = Math.floor(xyz.length / 3);
1767
+ const packed = new Float32Array(count * 4);
1768
+ for (let i = 0; i < count; i++) {
1769
+ packed[i * 4] = xyz[i * 3];
1770
+ packed[i * 4 + 1] = xyz[i * 3 + 1];
1771
+ packed[i * 4 + 2] = xyz[i * 3 + 2];
1772
+ packed[i * 4 + 3] = w[i] || 0;
1773
+ }
1774
+ return packed;
1775
+ }
1776
+ function uploadFrameUniforms(context, program, projectionMatrix, viewMatrix, lights, time, cameraPosition, fog, frame) {
1777
+ if (program.projectionMatrix)
1778
+ context.uniformMatrix4fv(program.projectionMatrix, false, projectionMatrix);
1779
+ if (program.viewMatrix)
1780
+ context.uniformMatrix4fv(program.viewMatrix, false, viewMatrix);
1781
+ if (program.time)
1782
+ context.uniform1f(program.time, time);
1783
+ if (frame && frame.resolution && program.screenResolution)
1784
+ context.uniform2f(program.screenResolution, frame.resolution.x, frame.resolution.y);
1785
+ if (lights) {
1786
+ if (program.nbLights && lights.type)
1787
+ context.uniform1i(program.nbLights, lights.type.length);
1788
+ if (program.lightPosition && lights.position && lights.position.length > 0)
1789
+ context.uniform3fv(program.lightPosition, lights.floatPosition || new Float32Array(lights.position));
1790
+ if (program.lightDiffuse && lights.diffuse && lights.diffuse.length > 0)
1791
+ context.uniform3fv(program.lightDiffuse, lights.floatDiffuse || new Float32Array(lights.diffuse));
1792
+ if (program.lightSpecular && lights.specular && lights.specular.length > 0)
1793
+ context.uniform3fv(program.lightSpecular, lights.floatSpecular || new Float32Array(lights.specular));
1794
+ if (program.lightDirection && lights.direction && lights.direction.length > 0)
1795
+ context.uniform3fv(program.lightDirection, lights.floatDirection || new Float32Array(lights.direction));
1796
+ if (program.lightAttenuation && (lights.floatAttenuation || lights.constantAttenuation?.length)) {
1797
+ let attArr = lights.floatAttenuation;
1798
+ if (!attArr) {
1799
+ const att = [];
1800
+ for (let k = 0; k < lights.constantAttenuation.length; k++)
1801
+ att.push(lights.constantAttenuation[k] || 0, lights.linearAttenuation[k] || 0, lights.quadraticAttenuation[k] || 0);
1802
+ attArr = new Float32Array(att);
1803
+ }
1804
+ context.uniform3fv(program.lightAttenuation, attArr);
1805
+ }
1806
+ if (program.lightCutoff && lights.cutoff && lights.cutoff.length > 0)
1807
+ context.uniform1fv(program.lightCutoff, lights.floatCutoff || new Float32Array(lights.cutoff));
1808
+ if (program.lightExponent && lights.exponent && lights.exponent.length > 0)
1809
+ context.uniform1fv(program.lightExponent, lights.floatExponent || new Float32Array(lights.exponent));
1810
+ if (program.lightType && (lights.floatType || lights.type?.length)) {
1811
+ let typeArr = lights.floatType;
1812
+ if (!typeArr)
1813
+ typeArr = new Float32Array(lights.type.map(t => t === 'directional' ? 0 : t === 'point' ? 1 : 2));
1814
+ context.uniform1fv(program.lightType, typeArr);
1815
+ }
1816
+ if (program.lightPositionType && lights.position && lights.position.length > 0)
1817
+ context.uniform4fv(program.lightPositionType, lights.packedPosition || packVec4(lights.position, (lights.type || []).map(lightTypeValue)));
1818
+ if (program.lightDiffuseCutoff && lights.diffuse && lights.diffuse.length > 0)
1819
+ context.uniform4fv(program.lightDiffuseCutoff, lights.packedDiffuse || packVec4(lights.diffuse, lights.cutoff || []));
1820
+ if (program.lightSpecularExponent && lights.specular && lights.specular.length > 0)
1821
+ context.uniform4fv(program.lightSpecularExponent, lights.packedSpecular || packVec4(lights.specular, lights.exponent || []));
1822
+ if (program.ambientLight && lights.ambient)
1823
+ context.uniform3fv(program.ambientLight, lights.floatAmbient || new Float32Array(lights.ambient));
1824
+ }
1825
+ if (program.cameraPosition) {
1826
+ let camera = cameraPosition;
1827
+ if (!camera && viewMatrix) {
1828
+ scratchView.toArray().set(viewMatrix);
1829
+ camera = scratchView.invertAffine().getTranslation(scratchCamera);
1830
+ }
1831
+ if (camera)
1832
+ context.uniform3f(program.cameraPosition, camera.x, camera.y, camera.z);
1833
+ }
1834
+ if (program.hasFog) {
1835
+ if (fog) {
1836
+ context.uniform1i(program.hasFog, 1);
1837
+ if (program.fogColor)
1838
+ context.uniform3f(program.fogColor, fog.color.x, fog.color.y, fog.color.z);
1839
+ if (program.fogNear)
1840
+ context.uniform1f(program.fogNear, fog.near);
1841
+ if (program.fogFar)
1842
+ context.uniform1f(program.fogFar, fog.far);
1843
+ }
1844
+ else
1845
+ context.uniform1i(program.hasFog, 0);
1846
+ }
1847
+ }
1848
+
1497
1849
  class MeshRenderer {
1498
1850
  context;
1499
1851
  state;
@@ -1505,7 +1857,7 @@ class MeshRenderer {
1505
1857
  defaultSettings() {
1506
1858
  }
1507
1859
  createBuffer(target, size, drawMethod) {
1508
- let buffer = this.context.createBuffer();
1860
+ const buffer = this.context.createBuffer();
1509
1861
  this.bindBuffer(target, buffer);
1510
1862
  this.context.bufferData(this.context[target], size, this.context[drawMethod]);
1511
1863
  return buffer;
@@ -1564,6 +1916,70 @@ class MeshRenderer {
1564
1916
  const mode = typeof primitive === 'number' ? primitive : (this.context[primitive] ?? this.context.TRIANGLES);
1565
1917
  this.context.drawArrays(mode, subMesh.start, subMesh.count);
1566
1918
  }
1919
+ drawMesh(mesh, projectionMatrix, viewMatrix, lights, time, cameraPosition, fog, frame) {
1920
+ const context = this.context;
1921
+ const state = this.state;
1922
+ for (let i = 0; i < mesh.nbSubMeshes; i++) {
1923
+ const program = mesh.programs[i] || mesh.programs[mesh.nbPrograms - 1];
1924
+ this.useProgram(program);
1925
+ if (!frame || state.frameOfProgram.get(program) !== frame.id) {
1926
+ if (frame)
1927
+ state.frameOfProgram.set(program, frame.id);
1928
+ uploadFrameUniforms(context, program, projectionMatrix, viewMatrix, lights, time, cameraPosition, fog, frame);
1929
+ }
1930
+ const material = mesh.materials[i] || mesh.materials[mesh.materials.length - 1];
1931
+ if (material) {
1932
+ material.updateUniforms();
1933
+ if (state.materialOfProgram.get(program) !== material || state.materialVersionOfProgram.get(program) !== material.version) {
1934
+ state.materialOfProgram.set(program, material);
1935
+ state.materialVersionOfProgram.set(program, material.version);
1936
+ const materialUniforms = material.uniforms;
1937
+ for (const property in materialUniforms)
1938
+ if (materialUniforms.hasOwnProperty(property) && program[property])
1939
+ uploadUniform(context, program[property], materialUniforms[property]);
1940
+ }
1941
+ }
1942
+ let attributeMask = 0;
1943
+ if (mesh.indices)
1944
+ this.bindBuffer('ELEMENT_ARRAY_BUFFER', mesh.indexBuffer);
1945
+ if (program.vertexPosition !== -1 && program.vertexPosition !== undefined && mesh.vertexBuffer) {
1946
+ this.bindBuffer('ARRAY_BUFFER', mesh.vertexBuffer);
1947
+ this.vertexAttribPointer(program.vertexPosition, mesh.itemSize, 'FLOAT', false, 0, 0);
1948
+ attributeMask |= 1 << program.vertexPosition;
1949
+ }
1950
+ if (mesh.normals && program.vertexNormal !== -1 && program.vertexNormal !== undefined) {
1951
+ this.bindBuffer('ARRAY_BUFFER', mesh.normalBuffer);
1952
+ this.vertexAttribPointer(program.vertexNormal, 3, 'FLOAT', false, 0, 0);
1953
+ attributeMask |= 1 << program.vertexNormal;
1954
+ }
1955
+ if (mesh.uvs && program.textureCoord !== -1 && program.textureCoord !== undefined) {
1956
+ this.bindBuffer('ARRAY_BUFFER', mesh.texCoordBuffer);
1957
+ this.vertexAttribPointer(program.textureCoord, 2, 'FLOAT', false, 0, 0);
1958
+ attributeMask |= 1 << program.textureCoord;
1959
+ }
1960
+ this.setAttributeMask(attributeMask);
1961
+ if (mesh.WebGLTexture) {
1962
+ context.activeTexture(context.TEXTURE0);
1963
+ context.bindTexture(context.TEXTURE_2D, mesh.WebGLTexture);
1964
+ if (program.sampler)
1965
+ context.uniform1i(program.sampler, 0);
1966
+ if (program.hasTexture)
1967
+ context.uniform1i(program.hasTexture, 1);
1968
+ }
1969
+ else if (program.hasTexture)
1970
+ context.uniform1i(program.hasTexture, 0);
1971
+ if (program.modelMatrix)
1972
+ context.uniformMatrix4fv(program.modelMatrix, false, mesh.worldMatrix.toArray());
1973
+ if (program.normalMatrix) {
1974
+ mesh.computeNormalMatrix();
1975
+ context.uniformMatrix3fv(program.normalMatrix, false, mesh.normalMatrix.toArray());
1976
+ }
1977
+ for (const property in mesh.customUniforms)
1978
+ if (mesh.customUniforms.hasOwnProperty(property) && program[property])
1979
+ uploadUniform(context, program[property], mesh.customUniforms[property]);
1980
+ this[mesh.drawMethod](mesh.primitive, mesh.subMeshes[i], mesh.indexType);
1981
+ }
1982
+ }
1567
1983
  }
1568
1984
 
1569
1985
  class SubMesh {
@@ -1604,16 +2020,16 @@ class Material {
1604
2020
  this.setShininess(options.shininess);
1605
2021
  }
1606
2022
  }
1607
- setAmbient(xOrVec, y, z) {
1608
- assignVec3(this.ambient, xOrVec, y, z).toArray(this.uniforms.materialAmbient.value);
2023
+ setAmbient(ambient) {
2024
+ this.ambient.copy(ambient).toArray(this.uniforms.materialAmbient.value);
1609
2025
  this.version++;
1610
2026
  }
1611
- setDiffuse(xOrVec, y, z) {
1612
- assignVec3(this.diffuse, xOrVec, y, z).toArray(this.uniforms.materialDiffuse.value);
2027
+ setDiffuse(diffuse) {
2028
+ this.diffuse.copy(diffuse).toArray(this.uniforms.materialDiffuse.value);
1613
2029
  this.version++;
1614
2030
  }
1615
- setSpecular(xOrVec, y, z) {
1616
- assignVec3(this.specular, xOrVec, y, z).toArray(this.uniforms.materialSpecular.value);
2031
+ setSpecular(specular) {
2032
+ this.specular.copy(specular).toArray(this.uniforms.materialSpecular.value);
1617
2033
  this.version++;
1618
2034
  }
1619
2035
  setShininess(shininess) {
@@ -1661,54 +2077,25 @@ void main(void) {
1661
2077
  fragment: `
1662
2078
  precision mediump float;
1663
2079
 
1664
- varying vec2 vTextureCoord;
1665
-
1666
- uniform sampler2D uSampler;
1667
- uniform vec3 uMaterialDiffuse;
1668
- uniform int uHasTexture;
1669
-
1670
- void main(void) {
1671
- vec4 color = vec4(uMaterialDiffuse, 1.0);
1672
- if (uHasTexture == 1) {
1673
- color *= texture2D(uSampler, vTextureCoord);
1674
- }
1675
- gl_FragColor = color;
1676
- }
1677
- `,
1678
- create(context) {
1679
- return Program.create(context, BasicShader.vertex, BasicShader.fragment);
1680
- }
1681
- };
1682
- const NormalShader = {
1683
- vertex: `
1684
- attribute vec3 aVertexPosition;
1685
- attribute vec3 aVertexNormal;
1686
-
1687
- uniform mat4 uModelMatrix;
1688
- uniform mat4 uViewMatrix;
1689
- uniform mat4 uProjectionMatrix;
1690
- uniform mat3 uNormalMatrix;
1691
-
1692
- varying vec3 vNormal;
1693
-
1694
- void main(void) {
1695
- vNormal = normalize(uNormalMatrix * aVertexNormal);
1696
- gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
1697
- }
1698
- `,
1699
- fragment: `
1700
- precision mediump float;
1701
-
1702
- varying vec3 vNormal;
2080
+ varying vec2 vTextureCoord;
2081
+
2082
+ uniform sampler2D uSampler;
2083
+ uniform vec3 uMaterialDiffuse;
2084
+ uniform int uHasTexture;
1703
2085
 
1704
2086
  void main(void) {
1705
- gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
2087
+ vec4 color = vec4(uMaterialDiffuse, 1.0);
2088
+ if (uHasTexture == 1) {
2089
+ color *= texture2D(uSampler, vTextureCoord);
2090
+ }
2091
+ gl_FragColor = color;
1706
2092
  }
1707
2093
  `,
1708
2094
  create(context) {
1709
- return Program.create(context, NormalShader.vertex, NormalShader.fragment);
2095
+ return Program.create(context, BasicShader.vertex, BasicShader.fragment);
1710
2096
  }
1711
2097
  };
2098
+
1712
2099
  const PhongShader = {
1713
2100
  vertex: `
1714
2101
  attribute vec3 aVertexPosition;
@@ -1846,14 +2233,124 @@ void main(void) {
1846
2233
  }
1847
2234
  };
1848
2235
 
2236
+ const NormalShader = {
2237
+ vertex: `
2238
+ attribute vec3 aVertexPosition;
2239
+ attribute vec3 aVertexNormal;
2240
+
2241
+ uniform mat4 uModelMatrix;
2242
+ uniform mat4 uViewMatrix;
2243
+ uniform mat4 uProjectionMatrix;
2244
+ uniform mat3 uNormalMatrix;
2245
+
2246
+ varying vec3 vNormal;
2247
+
2248
+ void main(void) {
2249
+ vNormal = normalize(uNormalMatrix * aVertexNormal);
2250
+ gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
2251
+ }
2252
+ `,
2253
+ fragment: `
2254
+ precision mediump float;
2255
+
2256
+ varying vec3 vNormal;
2257
+
2258
+ void main(void) {
2259
+ gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
2260
+ }
2261
+ `,
2262
+ create(context) {
2263
+ return Program.create(context, NormalShader.vertex, NormalShader.fragment);
2264
+ }
2265
+ };
2266
+
2267
+ const initializedPrograms = new WeakSet();
2268
+ function ucfirst(string) {
2269
+ return string.charAt(0).toUpperCase() + string.slice(1);
2270
+ }
2271
+ function addProgramAttribute(context, program, name) {
2272
+ program[name] = context.getAttribLocation(program, `a${ucfirst(name)}`);
2273
+ }
2274
+ function addProgramUniform(context, program, name) {
2275
+ const ucName = `u${ucfirst(name)}`;
2276
+ let loc = context.getUniformLocation(program, ucName);
2277
+ if (loc === null)
2278
+ loc = context.getUniformLocation(program, `${ucName}[0]`);
2279
+ program[name] = loc;
2280
+ }
2281
+ function initProgramLocations(context, program, customUniforms, materials) {
2282
+ if (!initializedPrograms.has(program)) {
2283
+ initializedPrograms.add(program);
2284
+ addProgramAttribute(context, program, 'vertexPosition');
2285
+ addProgramAttribute(context, program, 'vertexNormal');
2286
+ addProgramAttribute(context, program, 'textureCoord');
2287
+ addProgramUniform(context, program, 'modelMatrix');
2288
+ addProgramUniform(context, program, 'normalMatrix');
2289
+ addProgramUniform(context, program, 'viewMatrix');
2290
+ addProgramUniform(context, program, 'projectionMatrix');
2291
+ addProgramUniform(context, program, 'time');
2292
+ addProgramUniform(context, program, 'screenResolution');
2293
+ addProgramUniform(context, program, 'sampler');
2294
+ addProgramUniform(context, program, 'hasTexture');
2295
+ addProgramUniform(context, program, 'nbLights');
2296
+ addProgramUniform(context, program, 'lightPosition');
2297
+ addProgramUniform(context, program, 'lightDiffuse');
2298
+ addProgramUniform(context, program, 'lightSpecular');
2299
+ addProgramUniform(context, program, 'lightDirection');
2300
+ addProgramUniform(context, program, 'lightAttenuation');
2301
+ addProgramUniform(context, program, 'lightCutoff');
2302
+ addProgramUniform(context, program, 'lightExponent');
2303
+ addProgramUniform(context, program, 'lightType');
2304
+ addProgramUniform(context, program, 'lightPositionType');
2305
+ addProgramUniform(context, program, 'lightDiffuseCutoff');
2306
+ addProgramUniform(context, program, 'lightSpecularExponent');
2307
+ addProgramUniform(context, program, 'ambientLight');
2308
+ addProgramUniform(context, program, 'ambientColor');
2309
+ addProgramUniform(context, program, 'cameraPosition');
2310
+ addProgramUniform(context, program, 'hasFog');
2311
+ addProgramUniform(context, program, 'fogColor');
2312
+ addProgramUniform(context, program, 'fogNear');
2313
+ addProgramUniform(context, program, 'fogFar');
2314
+ if (program.screenResolution) {
2315
+ context.useProgram(program);
2316
+ const viewport = context.getParameter(context.VIEWPORT);
2317
+ if (viewport && viewport.length >= 4)
2318
+ context.uniform2f(program.screenResolution, viewport[2], viewport[3]);
2319
+ }
2320
+ }
2321
+ for (const property in customUniforms)
2322
+ if (customUniforms.hasOwnProperty(property) && program[property] === undefined)
2323
+ addProgramUniform(context, program, property);
2324
+ for (const mat of materials)
2325
+ for (const property in mat.uniforms)
2326
+ if (mat.uniforms.hasOwnProperty(property) && program[property] === undefined)
2327
+ addProgramUniform(context, program, property);
2328
+ }
2329
+ function syncMaterialUniforms(context, programs, material) {
2330
+ const materialUniforms = material.uniforms;
2331
+ for (const property in materialUniforms)
2332
+ if (materialUniforms.hasOwnProperty(property))
2333
+ for (const program of programs)
2334
+ if (program[property] === undefined)
2335
+ program[property] = context.getUniformLocation(program, `u${ucfirst(property)}`);
2336
+ }
2337
+ function syncCustomUniform(context, programs, name) {
2338
+ for (const program of programs)
2339
+ if (program[name] === undefined)
2340
+ program[name] = context.getUniformLocation(program, `u${ucfirst(name)}`);
2341
+ }
2342
+ function syncTextureSampler(context, programs) {
2343
+ for (const program of programs)
2344
+ if (program.sampler === undefined)
2345
+ program.sampler = context.getUniformLocation(program, 'uSampler');
2346
+ }
2347
+
1849
2348
  var eDrawMethod;
1850
2349
  (function (eDrawMethod) {
1851
2350
  eDrawMethod["drawElements"] = "drawElements";
1852
2351
  eDrawMethod["drawArrays"] = "drawArrays";
1853
2352
  })(eDrawMethod || (eDrawMethod = {}));
1854
- class Mesh {
1855
- boundingBox;
1856
- boundingSphere;
2353
+ class Mesh extends Node {
1857
2354
  vertices;
1858
2355
  indices;
1859
2356
  normals;
@@ -1870,49 +2367,23 @@ class Mesh {
1870
2367
  indexBuffer;
1871
2368
  normalBuffer;
1872
2369
  texCoordBuffer;
1873
- position;
1874
- scale;
1875
- quaternion;
1876
- modelMatrix;
1877
- worldMatrix;
1878
- normalMatrix;
1879
- parent = null;
1880
- traversalStamp = 0;
1881
2370
  sortKey = 0;
1882
- _rotation;
1883
- isRotationStale;
1884
- isTransformDirty;
1885
- isWorldDirty = true;
1886
2371
  ownsTexture = false;
2372
+ textureSource = null;
2373
+ textureOptions;
1887
2374
  indexType;
1888
- static UP = new Vec3(0, 1, 0);
1889
- static scratchQuat = new Quat();
1890
- static scratchView = new Mat4x3();
1891
- static scratchVec = new Vec3();
1892
- static scratchCamera = new Vec3();
1893
- static scratchDir = new Vec3();
1894
- static scratchA = new Vec3();
1895
- static scratchB = new Vec3();
1896
- static scratchC = new Vec3();
1897
2375
  static scratchHit = new Vec3();
1898
- static scratchWorldHit = new Vec3();
1899
- static scratchInverse = new Mat4x3();
1900
- static scratchRay = new Ray3();
1901
- isBoundsDirty = true;
1902
- isNormalMatrixDirty = true;
1903
- localBoundingBox;
1904
- localBoundingSphere;
1905
2376
  active;
1906
2377
  drawMethod;
1907
2378
  programs;
1908
2379
  nbPrograms;
1909
2380
  materials;
1910
- children;
1911
2381
  blendMode;
1912
2382
  blendOptions = { equation: 'FUNC_ADD', source: 'SRC_ALPHA', destination: 'ONE' };
1913
2383
  depthWrite = true;
1914
2384
  zOrder;
1915
2385
  constructor(meshOrContext, contextOrMesh) {
2386
+ super();
1916
2387
  let mesh;
1917
2388
  let context;
1918
2389
  if (meshOrContext && ('drawingBufferWidth' in meshOrContext || typeof meshOrContext.createBuffer === 'function')) {
@@ -1937,37 +2408,12 @@ class Mesh {
1937
2408
  this.context = context;
1938
2409
  this.renderer = new MeshRenderer(this.context);
1939
2410
  this.WebGLTexture = null;
1940
- this.vertexBuffer = this.vertices
1941
- ? this.renderer.createBuffer('ARRAY_BUFFER', this.vertices instanceof Float32Array ? this.vertices : new Float32Array(this.vertices), 'STATIC_DRAW')
1942
- : null;
2411
+ this.vertexBuffer = null;
2412
+ this.indexBuffer = null;
2413
+ this.normalBuffer = null;
2414
+ this.texCoordBuffer = null;
1943
2415
  this.indexType = this.context.UNSIGNED_SHORT;
1944
- this.indexBuffer = this.indices
1945
- ? this.renderer.createBuffer('ELEMENT_ARRAY_BUFFER', this.createIndexData(this.indices), 'STATIC_DRAW')
1946
- : null;
1947
- this.normalBuffer = this.normals
1948
- ? this.renderer.createBuffer('ARRAY_BUFFER', this.normals instanceof Float32Array ? this.normals : new Float32Array(this.normals), 'STATIC_DRAW')
1949
- : null;
1950
- this.texCoordBuffer = this.uvs
1951
- ? this.renderer.createBuffer('ARRAY_BUFFER', this.uvs instanceof Float32Array ? this.uvs : new Float32Array(this.uvs), 'STATIC_DRAW')
1952
- : null;
1953
- this.position = new Vec3();
1954
- this._rotation = new Vec3();
1955
- this.scale = new Vec3(1.0, 1.0, 1.0);
1956
- this.quaternion = new Quat();
1957
- this.modelMatrix = new Mat4x3();
1958
- this.worldMatrix = new Mat4x3();
1959
- this.normalMatrix = new Mat3();
1960
- this.isRotationStale = false;
1961
- this.isTransformDirty = false;
1962
- this.modelMatrix.identity();
1963
- this.worldMatrix.identity();
1964
- this.normalMatrix.identity();
1965
- this.isBoundsDirty = true;
1966
- this.isNormalMatrixDirty = true;
1967
- this.localBoundingBox = new Box();
1968
- this.localBoundingSphere = new Sphere$1();
1969
- this.boundingBox = new Box();
1970
- this.boundingSphere = new Sphere$1();
2416
+ this.createBuffers();
1971
2417
  if (this.vertices && this.vertices.length > 0) {
1972
2418
  this.localBoundingBox.setFromPoints(this.vertices, this.itemSize || 3);
1973
2419
  this.localBoundingSphere.setFromPoints(this.vertices, undefined, this.itemSize || 3);
@@ -1977,7 +2423,6 @@ class Mesh {
1977
2423
  this.programs = [];
1978
2424
  this.nbPrograms = 0;
1979
2425
  this.materials = [];
1980
- this.children = [];
1981
2426
  this.blendMode = false;
1982
2427
  this.zOrder = 0;
1983
2428
  }
@@ -1994,256 +2439,52 @@ class Mesh {
1994
2439
  isActive() {
1995
2440
  return this.active;
1996
2441
  }
1997
- addChild(mesh) {
1998
- if (this.children.indexOf(mesh) === -1) {
1999
- if (mesh.parent && mesh.parent !== this)
2000
- mesh.parent.removeChild(mesh);
2001
- this.children.push(mesh);
2002
- mesh.parent = this;
2003
- mesh.isWorldDirty = true;
2004
- }
2005
- }
2006
- removeChild(mesh) {
2007
- const index = this.children.indexOf(mesh);
2008
- if (index !== -1) {
2009
- this.children.splice(index, 1);
2010
- mesh.parent = null;
2011
- mesh.isWorldDirty = true;
2012
- return true;
2013
- }
2014
- return false;
2015
- }
2016
2442
  addProgram(vertexShaderOrProgram, fragmentShaderOrMaterial, material) {
2017
- if (this.nbPrograms < this.nbSubMeshes) {
2018
- let program;
2019
- let mat;
2020
- if (typeof vertexShaderOrProgram !== 'string') {
2021
- program = vertexShaderOrProgram;
2022
- mat = (fragmentShaderOrMaterial instanceof Material) ? fragmentShaderOrMaterial : new Material();
2023
- }
2024
- else {
2025
- program = Program.acquire(this.context, vertexShaderOrProgram, fragmentShaderOrMaterial);
2026
- mat = (material instanceof Material) ? material : (fragmentShaderOrMaterial instanceof Material ? fragmentShaderOrMaterial : new Material());
2027
- }
2028
- if (!program)
2029
- return false;
2030
- this.programs.push(program);
2031
- this.addMaterial(mat);
2032
- this.createProgram();
2033
- return true;
2034
- }
2035
- return false;
2036
- }
2037
- clearPrograms() {
2038
- for (let program of this.programs)
2039
- Program.release(this.context, program);
2040
- this.programs = [];
2041
- this.nbPrograms = 0;
2042
- this.materials = [];
2043
- }
2044
- addMaterial(material) {
2045
- if (material && this.materials.length < this.nbSubMeshes) {
2046
- this.materials.push(material);
2047
- let materialUniforms = material.uniforms;
2048
- for (let property in materialUniforms)
2049
- if (materialUniforms.hasOwnProperty(property))
2050
- for (let program of this.programs)
2051
- if (program[property] === undefined)
2052
- program[property] = this.context.getUniformLocation(program, 'u' + this.ucfirst(property));
2053
- return true;
2054
- }
2055
- return false;
2056
- }
2057
- markDirty() {
2058
- this.isTransformDirty = true;
2059
- this.isBoundsDirty = true;
2060
- this.isNormalMatrixDirty = true;
2061
- }
2062
- get rotation() {
2063
- if (this.isRotationStale) {
2064
- this.quaternion.getEuler(this._rotation);
2065
- this.isRotationStale = false;
2066
- }
2067
- return this._rotation;
2068
- }
2069
- set rotation(euler) {
2070
- this.setRotation(euler);
2071
- }
2072
- rotationChanged() {
2073
- this.isRotationStale = true;
2074
- this.markDirty();
2075
- }
2076
- setPosition(xOrVec, y, z) {
2077
- assignVec3(this.position, xOrVec, y, z);
2078
- this.markDirty();
2079
- return this;
2080
- }
2081
- setRotation(xOrVec, y, z) {
2082
- assignVec3(this._rotation, xOrVec, y, z);
2083
- this.quaternion.setFromEuler(this._rotation);
2084
- this.isRotationStale = false;
2085
- this.markDirty();
2086
- return this;
2087
- }
2088
- setScale(xOrVec, y, z) {
2089
- if (typeof xOrVec === 'number' && y === undefined && z === undefined)
2090
- this.scale.setScalar(xOrVec, xOrVec, xOrVec);
2091
- else
2092
- assignVec3(this.scale, xOrVec, y, z);
2093
- this.markDirty();
2094
- return this;
2095
- }
2096
- scaleBy(factorOrVec) {
2097
- if (factorOrVec instanceof Vec3)
2098
- this.scale.multiply(factorOrVec);
2099
- else
2100
- this.scale.scale(factorOrVec);
2101
- this.markDirty();
2102
- return this;
2103
- }
2104
- setQuaternion(q) {
2105
- this.quaternion.copy(q);
2106
- this.rotationChanged();
2107
- return this;
2108
- }
2109
- slerp(targetQuat, t) {
2110
- this.quaternion.slerp(targetQuat, t);
2111
- this.rotationChanged();
2112
- return this;
2113
- }
2114
- rotateX(angle) {
2115
- this.quaternion.rotateX(angle);
2116
- this.rotationChanged();
2117
- return this;
2118
- }
2119
- rotateY(angle) {
2120
- this.quaternion.rotateY(angle);
2121
- this.rotationChanged();
2122
- return this;
2123
- }
2124
- rotateZ(angle) {
2125
- this.quaternion.rotateZ(angle);
2126
- this.rotationChanged();
2127
- return this;
2128
- }
2129
- rotateOnAxis(axis, angle) {
2130
- this.quaternion.multiply(Mesh.scratchQuat.setAxisAngle(axis, angle));
2131
- this.rotationChanged();
2132
- return this;
2133
- }
2134
- translate(xOrVec, y, z) {
2135
- if (typeof xOrVec === 'number')
2136
- this.position.add(Mesh.scratchVec.setScalar(xOrVec, y ?? 0, z ?? 0));
2137
- else
2138
- this.position.add(xOrVec instanceof Vec3 ? xOrVec : Mesh.scratchVec.setArray(xOrVec));
2139
- this.markDirty();
2140
- return this;
2141
- }
2142
- lookAt(target, up = Mesh.UP) {
2143
- if (this.position.equals(target))
2144
- return this;
2145
- this.quaternion.lookAt(this.position, target, up);
2146
- this.rotationChanged();
2147
- return this;
2148
- }
2149
- updateModelMatrix() {
2150
- if (!this.isTransformDirty)
2151
- return false;
2152
- this.modelMatrix.compose(this.position, this.quaternion, this.scale);
2153
- this.isTransformDirty = false;
2154
- this.isBoundsDirty = true;
2155
- this.isNormalMatrixDirty = true;
2156
- return true;
2157
- }
2158
- computeNormalMatrix() {
2159
- if (!this.isNormalMatrixDirty)
2160
- return this.normalMatrix;
2161
- this.worldMatrix.toNormalMatrix(this.normalMatrix.toArray());
2162
- this.isNormalMatrixDirty = false;
2163
- return this.normalMatrix;
2164
- }
2165
- getWorldPosition(target = new Vec3()) {
2166
- return this.worldMatrix.getTranslation(target);
2167
- }
2168
- getWorldDirection(target = new Vec3()) {
2169
- return this.worldMatrix.getColumn(2, target).opposite().normalize();
2170
- }
2171
- distanceTo(target) {
2172
- const targetPos = target instanceof Vec3 ? target : target.getWorldPosition();
2173
- return this.getWorldPosition(Mesh.scratchVec).getDistance(targetPos);
2174
- }
2175
- isInsideBounds(min, max) {
2176
- return this.getWorldPosition(Mesh.scratchVec).isInBounds(min, max);
2177
- }
2178
- computeBounds() {
2179
- if (!this.isBoundsDirty)
2180
- return;
2181
- if (this.vertices && this.vertices.length > 0) {
2182
- this.boundingBox.copy(this.localBoundingBox).applyMatrix4(this.worldMatrix);
2183
- this.boundingSphere.copy(this.localBoundingSphere).applyMatrix4(this.worldMatrix);
2184
- }
2185
- else {
2186
- const pos = this.getWorldPosition(Mesh.scratchVec);
2187
- this.boundingBox.set(pos, pos);
2188
- this.boundingSphere.set(pos, 0);
2443
+ if (this.nbPrograms < this.nbSubMeshes) {
2444
+ let program;
2445
+ let mat;
2446
+ if (typeof vertexShaderOrProgram !== 'string') {
2447
+ program = vertexShaderOrProgram;
2448
+ mat = (fragmentShaderOrMaterial instanceof Material) ? fragmentShaderOrMaterial : new Material();
2449
+ }
2450
+ else {
2451
+ program = Program.acquire(this.context, vertexShaderOrProgram, fragmentShaderOrMaterial);
2452
+ mat = (material instanceof Material) ? material : (fragmentShaderOrMaterial instanceof Material ? fragmentShaderOrMaterial : new Material());
2453
+ }
2454
+ if (!program)
2455
+ return false;
2456
+ this.programs.push(program);
2457
+ this.addMaterial(mat);
2458
+ this.createProgram();
2459
+ return true;
2189
2460
  }
2190
- this.isBoundsDirty = false;
2461
+ return false;
2191
2462
  }
2192
- computeBoundingBox() {
2193
- this.computeBounds();
2194
- return this.boundingBox;
2463
+ clearPrograms() {
2464
+ for (const program of this.programs)
2465
+ Program.release(this.context, program);
2466
+ this.programs = [];
2467
+ this.nbPrograms = 0;
2468
+ this.materials = [];
2195
2469
  }
2196
- computeBoundingSphere() {
2197
- this.computeBounds();
2198
- return this.boundingSphere;
2470
+ addMaterial(material) {
2471
+ if (material && this.materials.length < this.nbSubMeshes) {
2472
+ this.materials.push(material);
2473
+ syncMaterialUniforms(this.context, this.programs, material);
2474
+ return true;
2475
+ }
2476
+ return false;
2199
2477
  }
2200
2478
  raycast(ray, precise = false) {
2201
2479
  if (!this.isActive())
2202
2480
  return null;
2203
2481
  const sphere = this.computeBoundingSphere();
2204
- const hitPoint = ray.intersectSphere(sphere);
2482
+ const hitPoint = ray.intersectSphere(sphere, Mesh.scratchHit);
2205
2483
  if (!hitPoint)
2206
2484
  return null;
2207
2485
  if (precise && this.vertices && (this.primitive === 'TRIANGLES' || this.primitive === 'TRIANGLE_STRIP'))
2208
- return this.raycastTriangles(ray);
2209
- return { distance: ray.origin.getDistance(hitPoint), point: hitPoint };
2210
- }
2211
- raycastTriangles(ray) {
2212
- const inverse = Mesh.scratchInverse.copy(this.worldMatrix).invertAffine();
2213
- const localRay = Mesh.scratchRay.set(inverse.transformPoint(ray.origin, Mesh.scratchVec), inverse.transformDirection(ray.direction, Mesh.scratchDir));
2214
- const strip = this.primitive === 'TRIANGLE_STRIP';
2215
- let best = null;
2216
- let bestDistance = Infinity;
2217
- for (const subMesh of this.subMeshes) {
2218
- const end = subMesh.start + subMesh.count;
2219
- for (let i = subMesh.start; i + 2 < end; i += strip ? 1 : 3) {
2220
- this.getVertex(i, Mesh.scratchA);
2221
- if (strip && (i - subMesh.start) % 2 === 1) {
2222
- this.getVertex(i + 2, Mesh.scratchB);
2223
- this.getVertex(i + 1, Mesh.scratchC);
2224
- }
2225
- else {
2226
- this.getVertex(i + 1, Mesh.scratchB);
2227
- this.getVertex(i + 2, Mesh.scratchC);
2228
- }
2229
- const local = localRay.intersectTriangle(Mesh.scratchA, Mesh.scratchB, Mesh.scratchC, false, Mesh.scratchHit);
2230
- if (!local)
2231
- continue;
2232
- const world = this.worldMatrix.transformPoint(local, Mesh.scratchWorldHit);
2233
- const distance = ray.origin.getDistance(world);
2234
- if (distance < bestDistance) {
2235
- bestDistance = distance;
2236
- best = (best ?? new Vec3()).copy(world);
2237
- }
2238
- }
2239
- }
2240
- return best ? { distance: bestDistance, point: best } : null;
2241
- }
2242
- getVertex(index, target) {
2243
- const vertices = this.vertices;
2244
- const size = this.itemSize || 3;
2245
- const offset = (this.indices ? this.indices[index] : index) * size;
2246
- return target.setScalar(vertices[offset], vertices[offset + 1], size > 2 ? vertices[offset + 2] : 0);
2486
+ return raycastTriangles(this, ray);
2487
+ return { distance: ray.origin.getDistance(hitPoint), point: new Vec3().copy(hitPoint) };
2247
2488
  }
2248
2489
  useBasicMaterial(options) {
2249
2490
  const mat = new Material();
@@ -2287,12 +2528,8 @@ class Mesh {
2287
2528
  this.context.deleteTexture(this.WebGLTexture);
2288
2529
  this.WebGLTexture = null;
2289
2530
  this.ownsTexture = false;
2290
- }
2291
- setWorldMatrix(worldMatrix) {
2292
- this.worldMatrix.copy(worldMatrix).multiply(this.modelMatrix);
2293
- this.isBoundsDirty = true;
2294
- this.isNormalMatrixDirty = true;
2295
- this.isWorldDirty = false;
2531
+ this.textureSource = null;
2532
+ this.textureOptions = undefined;
2296
2533
  }
2297
2534
  setTexture(img, options) {
2298
2535
  const texture = Texture.create(img, this.context, options);
@@ -2301,16 +2538,14 @@ class Mesh {
2301
2538
  this.releaseTexture();
2302
2539
  this.WebGLTexture = texture;
2303
2540
  this.ownsTexture = true;
2304
- for (let program of this.programs)
2305
- if (program.sampler === undefined)
2306
- program.sampler = this.context.getUniformLocation(program, 'uSampler');
2541
+ this.textureSource = img;
2542
+ this.textureOptions = options;
2543
+ syncTextureSampler(this.context, this.programs);
2307
2544
  }
2308
2545
  addCustomUniform(name, type, value) {
2309
2546
  if (!this.customUniforms.hasOwnProperty(name)) {
2310
2547
  this.customUniforms[name] = new Uniform(type, value);
2311
- for (let program of this.programs)
2312
- if (program[name] === undefined)
2313
- program[name] = this.context.getUniformLocation(program, 'u' + this.ucfirst(name));
2548
+ syncCustomUniform(this.context, this.programs, name);
2314
2549
  }
2315
2550
  }
2316
2551
  setCustomUniform(name, value) {
@@ -2331,66 +2566,48 @@ class Mesh {
2331
2566
  }
2332
2567
  return indices instanceof Uint16Array ? indices : new Uint16Array(indices);
2333
2568
  }
2334
- static initializedPrograms = new WeakSet();
2335
- createProgram() {
2336
- let program = this.programs[this.nbPrograms];
2337
- if (!Mesh.initializedPrograms.has(program)) {
2338
- Mesh.initializedPrograms.add(program);
2339
- this.addProgramAttribute('vertexPosition');
2340
- this.addProgramAttribute('vertexNormal');
2341
- this.addProgramAttribute('textureCoord');
2342
- this.addProgramUniform('modelMatrix');
2343
- this.addProgramUniform('normalMatrix');
2344
- this.addProgramUniform('viewMatrix');
2345
- this.addProgramUniform('projectionMatrix');
2346
- this.addProgramUniform('time');
2347
- this.addProgramUniform('screenResolution');
2348
- this.addProgramUniform('sampler');
2349
- this.addProgramUniform('hasTexture');
2350
- this.addProgramUniform('nbLights');
2351
- this.addProgramUniform('lightPosition');
2352
- this.addProgramUniform('lightDiffuse');
2353
- this.addProgramUniform('lightSpecular');
2354
- this.addProgramUniform('lightDirection');
2355
- this.addProgramUniform('lightAttenuation');
2356
- this.addProgramUniform('lightCutoff');
2357
- this.addProgramUniform('lightExponent');
2358
- this.addProgramUniform('lightType');
2359
- this.addProgramUniform('lightPositionType');
2360
- this.addProgramUniform('lightDiffuseCutoff');
2361
- this.addProgramUniform('lightSpecularExponent');
2362
- this.addProgramUniform('ambientLight');
2363
- this.addProgramUniform('ambientColor');
2364
- this.addProgramUniform('cameraPosition');
2365
- this.addProgramUniform('hasFog');
2366
- this.addProgramUniform('fogColor');
2367
- this.addProgramUniform('fogNear');
2368
- this.addProgramUniform('fogFar');
2369
- if (program.screenResolution) {
2370
- this.renderer.useProgram(program);
2371
- let viewport = this.context.getParameter(this.context.VIEWPORT);
2372
- if (viewport && viewport.length >= 4)
2373
- this.context.uniform2f(program.screenResolution, viewport[2], viewport[3]);
2374
- }
2375
- }
2376
- for (let property in this.customUniforms)
2377
- if (this.customUniforms.hasOwnProperty(property) && program[property] === undefined)
2378
- this.addProgramUniform(property);
2379
- for (let mat of this.materials)
2380
- for (let property in mat.uniforms)
2381
- if (mat.uniforms.hasOwnProperty(property) && program[property] === undefined)
2382
- this.addProgramUniform(property);
2383
- this.nbPrograms++;
2569
+ createBuffers() {
2570
+ this.vertexBuffer = this.vertices
2571
+ ? this.renderer.createBuffer('ARRAY_BUFFER', this.vertices instanceof Float32Array ? this.vertices : new Float32Array(this.vertices), 'STATIC_DRAW')
2572
+ : null;
2573
+ this.indexType = this.context.UNSIGNED_SHORT;
2574
+ this.indexBuffer = this.indices
2575
+ ? this.renderer.createBuffer('ELEMENT_ARRAY_BUFFER', this.createIndexData(this.indices), 'STATIC_DRAW')
2576
+ : null;
2577
+ this.normalBuffer = this.normals
2578
+ ? this.renderer.createBuffer('ARRAY_BUFFER', this.normals instanceof Float32Array ? this.normals : new Float32Array(this.normals), 'STATIC_DRAW')
2579
+ : null;
2580
+ this.texCoordBuffer = this.uvs
2581
+ ? this.renderer.createBuffer('ARRAY_BUFFER', this.uvs instanceof Float32Array ? this.uvs : new Float32Array(this.uvs), 'STATIC_DRAW')
2582
+ : null;
2384
2583
  }
2385
- addProgramAttribute(name) {
2386
- this.programs[this.nbPrograms][name] = this.context.getAttribLocation(this.programs[this.nbPrograms], 'a' + this.ucfirst(name));
2584
+ restore() {
2585
+ this.createBuffers();
2586
+ const sources = this.programs.map((program) => Program.sources(program));
2587
+ this.programs = [];
2588
+ this.nbPrograms = 0;
2589
+ sources.forEach((source) => {
2590
+ if (!source)
2591
+ return;
2592
+ const program = Program.acquire(this.context, source.vertex, source.fragment);
2593
+ if (!program)
2594
+ return;
2595
+ this.programs.push(program);
2596
+ this.createProgram();
2597
+ });
2598
+ if (this.programs.length !== sources.length)
2599
+ this.setInactive();
2600
+ if (this.ownsTexture && this.textureSource) {
2601
+ const texture = Texture.create(this.textureSource, this.context, this.textureOptions);
2602
+ this.WebGLTexture = texture;
2603
+ this.ownsTexture = texture !== null;
2604
+ }
2605
+ for (const child of this.children)
2606
+ child.restore();
2387
2607
  }
2388
- addProgramUniform(name) {
2389
- const ucName = 'u' + this.ucfirst(name);
2390
- let loc = this.context.getUniformLocation(this.programs[this.nbPrograms], ucName);
2391
- if (loc === null)
2392
- loc = this.context.getUniformLocation(this.programs[this.nbPrograms], ucName + '[0]');
2393
- this.programs[this.nbPrograms][name] = loc;
2608
+ createProgram() {
2609
+ initProgramLocations(this.context, this.programs[this.nbPrograms], this.customUniforms, this.materials);
2610
+ this.nbPrograms++;
2394
2611
  }
2395
2612
  activateBlendMode() {
2396
2613
  this.blendMode = true;
@@ -2409,178 +2626,17 @@ class Mesh {
2409
2626
  this.depthWrite = enabled;
2410
2627
  return this;
2411
2628
  }
2412
- computeWorldMatrix(graph) {
2413
- const modelChanged = this.updateModelMatrix();
2414
- const changed = this.isWorldDirty || modelChanged;
2415
- if (changed)
2416
- this.setWorldMatrix(graph.getWorldMatrix());
2417
- graph.pushModelMatrix(this.worldMatrix);
2418
- for (let child of this.children) {
2419
- if (changed)
2420
- child.isWorldDirty = true;
2421
- child.computeWorldMatrix(graph);
2422
- }
2423
- graph.popModelMatrix();
2424
- }
2425
2629
  render(projectionMatrix, viewMatrix, lights, time, blendMode, cameraPosition, fog, frame) {
2426
2630
  if (!this.isActive())
2427
2631
  return;
2428
2632
  this.renderSelf(projectionMatrix, viewMatrix, lights, time, blendMode, cameraPosition, fog, frame);
2429
- for (let child of this.children)
2633
+ for (const child of this.children)
2430
2634
  child.render(projectionMatrix, viewMatrix, lights, time, blendMode, cameraPosition, fog, frame);
2431
2635
  }
2432
2636
  renderSelf(projectionMatrix, viewMatrix, lights, time, blendMode, cameraPosition, fog, frame) {
2433
2637
  if (!this.isActive() || this.nbPrograms === 0 || this.blendMode !== blendMode)
2434
2638
  return;
2435
- const context = this.context;
2436
- const renderer = this.renderer;
2437
- const state = renderer.state;
2438
- for (let i = 0; i < this.nbSubMeshes; i++) {
2439
- const program = this.programs[i] || this.programs[this.nbPrograms - 1];
2440
- renderer.useProgram(program);
2441
- if (!frame || state.frameOfProgram.get(program) !== frame.id) {
2442
- if (frame)
2443
- state.frameOfProgram.set(program, frame.id);
2444
- this.uploadFrameUniforms(program, projectionMatrix, viewMatrix, lights, time, cameraPosition, fog, frame);
2445
- }
2446
- const material = this.materials[i] || this.materials[this.materials.length - 1];
2447
- if (material) {
2448
- material.updateUniforms();
2449
- if (state.materialOfProgram.get(program) !== material || state.materialVersionOfProgram.get(program) !== material.version) {
2450
- state.materialOfProgram.set(program, material);
2451
- state.materialVersionOfProgram.set(program, material.version);
2452
- const materialUniforms = material.uniforms;
2453
- for (let property in materialUniforms)
2454
- if (materialUniforms.hasOwnProperty(property) && program[property])
2455
- uploadUniform(context, program[property], materialUniforms[property]);
2456
- }
2457
- }
2458
- let attributeMask = 0;
2459
- if (this.indices)
2460
- renderer.bindBuffer('ELEMENT_ARRAY_BUFFER', this.indexBuffer);
2461
- if (program.vertexPosition !== -1 && program.vertexPosition !== undefined && this.vertexBuffer) {
2462
- renderer.bindBuffer('ARRAY_BUFFER', this.vertexBuffer);
2463
- renderer.vertexAttribPointer(program.vertexPosition, this.itemSize, 'FLOAT', false, 0, 0);
2464
- attributeMask |= 1 << program.vertexPosition;
2465
- }
2466
- if (this.normals && program.vertexNormal !== -1 && program.vertexNormal !== undefined) {
2467
- renderer.bindBuffer('ARRAY_BUFFER', this.normalBuffer);
2468
- renderer.vertexAttribPointer(program.vertexNormal, 3, 'FLOAT', false, 0, 0);
2469
- attributeMask |= 1 << program.vertexNormal;
2470
- }
2471
- if (this.uvs && program.textureCoord !== -1 && program.textureCoord !== undefined) {
2472
- renderer.bindBuffer('ARRAY_BUFFER', this.texCoordBuffer);
2473
- renderer.vertexAttribPointer(program.textureCoord, 2, 'FLOAT', false, 0, 0);
2474
- attributeMask |= 1 << program.textureCoord;
2475
- }
2476
- renderer.setAttributeMask(attributeMask);
2477
- if (this.WebGLTexture) {
2478
- context.activeTexture(context.TEXTURE0);
2479
- context.bindTexture(context.TEXTURE_2D, this.WebGLTexture);
2480
- if (program.sampler)
2481
- context.uniform1i(program.sampler, 0);
2482
- if (program.hasTexture)
2483
- context.uniform1i(program.hasTexture, 1);
2484
- }
2485
- else if (program.hasTexture)
2486
- context.uniform1i(program.hasTexture, 0);
2487
- if (program.modelMatrix)
2488
- context.uniformMatrix4fv(program.modelMatrix, false, this.worldMatrix.toArray());
2489
- if (program.normalMatrix) {
2490
- this.computeNormalMatrix();
2491
- context.uniformMatrix3fv(program.normalMatrix, false, this.normalMatrix.toArray());
2492
- }
2493
- for (let property in this.customUniforms)
2494
- if (this.customUniforms.hasOwnProperty(property) && program[property])
2495
- uploadUniform(context, program[property], this.customUniforms[property]);
2496
- renderer[this.drawMethod](this.primitive, this.subMeshes[i], this.indexType);
2497
- }
2498
- }
2499
- static lightTypeValue(type) {
2500
- return type === 'directional' ? 0 : type === 'point' ? 1 : 2;
2501
- }
2502
- static packVec4(xyz, w) {
2503
- const count = Math.floor(xyz.length / 3);
2504
- const packed = new Float32Array(count * 4);
2505
- for (let i = 0; i < count; i++) {
2506
- packed[i * 4] = xyz[i * 3];
2507
- packed[i * 4 + 1] = xyz[i * 3 + 1];
2508
- packed[i * 4 + 2] = xyz[i * 3 + 2];
2509
- packed[i * 4 + 3] = w[i] || 0;
2510
- }
2511
- return packed;
2512
- }
2513
- uploadFrameUniforms(program, projectionMatrix, viewMatrix, lights, time, cameraPosition, fog, frame) {
2514
- const context = this.context;
2515
- if (program.projectionMatrix)
2516
- context.uniformMatrix4fv(program.projectionMatrix, false, projectionMatrix);
2517
- if (program.viewMatrix)
2518
- context.uniformMatrix4fv(program.viewMatrix, false, viewMatrix);
2519
- if (program.time)
2520
- context.uniform1f(program.time, time);
2521
- if (frame && frame.resolution && program.screenResolution)
2522
- context.uniform2f(program.screenResolution, frame.resolution.x, frame.resolution.y);
2523
- if (lights) {
2524
- if (program.nbLights && lights.type)
2525
- context.uniform1i(program.nbLights, lights.type.length);
2526
- if (program.lightPosition && lights.position && lights.position.length > 0)
2527
- context.uniform3fv(program.lightPosition, lights.floatPosition || new Float32Array(lights.position));
2528
- if (program.lightDiffuse && lights.diffuse && lights.diffuse.length > 0)
2529
- context.uniform3fv(program.lightDiffuse, lights.floatDiffuse || new Float32Array(lights.diffuse));
2530
- if (program.lightSpecular && lights.specular && lights.specular.length > 0)
2531
- context.uniform3fv(program.lightSpecular, lights.floatSpecular || new Float32Array(lights.specular));
2532
- if (program.lightDirection && lights.direction && lights.direction.length > 0)
2533
- context.uniform3fv(program.lightDirection, lights.floatDirection || new Float32Array(lights.direction));
2534
- if (program.lightAttenuation && (lights.floatAttenuation || lights.constantAttenuation?.length)) {
2535
- let attArr = lights.floatAttenuation;
2536
- if (!attArr) {
2537
- const att = [];
2538
- for (let k = 0; k < lights.constantAttenuation.length; k++)
2539
- att.push(lights.constantAttenuation[k] || 0, lights.linearAttenuation[k] || 0, lights.quadraticAttenuation[k] || 0);
2540
- attArr = new Float32Array(att);
2541
- }
2542
- context.uniform3fv(program.lightAttenuation, attArr);
2543
- }
2544
- if (program.lightCutoff && lights.cutoff && lights.cutoff.length > 0)
2545
- context.uniform1fv(program.lightCutoff, lights.floatCutoff || new Float32Array(lights.cutoff));
2546
- if (program.lightExponent && lights.exponent && lights.exponent.length > 0)
2547
- context.uniform1fv(program.lightExponent, lights.floatExponent || new Float32Array(lights.exponent));
2548
- if (program.lightType && (lights.floatType || lights.type?.length)) {
2549
- let typeArr = lights.floatType;
2550
- if (!typeArr)
2551
- typeArr = new Float32Array(lights.type.map(t => t === 'directional' ? 0 : t === 'point' ? 1 : 2));
2552
- context.uniform1fv(program.lightType, typeArr);
2553
- }
2554
- if (program.lightPositionType && lights.position && lights.position.length > 0)
2555
- context.uniform4fv(program.lightPositionType, lights.packedPosition || Mesh.packVec4(lights.position, (lights.type || []).map(Mesh.lightTypeValue)));
2556
- if (program.lightDiffuseCutoff && lights.diffuse && lights.diffuse.length > 0)
2557
- context.uniform4fv(program.lightDiffuseCutoff, lights.packedDiffuse || Mesh.packVec4(lights.diffuse, lights.cutoff || []));
2558
- if (program.lightSpecularExponent && lights.specular && lights.specular.length > 0)
2559
- context.uniform4fv(program.lightSpecularExponent, lights.packedSpecular || Mesh.packVec4(lights.specular, lights.exponent || []));
2560
- if (program.ambientLight && lights.ambient)
2561
- context.uniform3fv(program.ambientLight, lights.floatAmbient || new Float32Array(lights.ambient));
2562
- }
2563
- if (program.cameraPosition) {
2564
- let camera = cameraPosition;
2565
- if (!camera && viewMatrix) {
2566
- Mesh.scratchView.toArray().set(viewMatrix);
2567
- camera = Mesh.scratchView.invertAffine().getTranslation(Mesh.scratchCamera);
2568
- }
2569
- if (camera)
2570
- context.uniform3f(program.cameraPosition, camera.x, camera.y, camera.z);
2571
- }
2572
- if (program.hasFog)
2573
- if (fog) {
2574
- context.uniform1i(program.hasFog, 1);
2575
- if (program.fogColor)
2576
- context.uniform3f(program.fogColor, fog.color.x, fog.color.y, fog.color.z);
2577
- if (program.fogNear)
2578
- context.uniform1f(program.fogNear, fog.near);
2579
- if (program.fogFar)
2580
- context.uniform1f(program.fogFar, fog.far);
2581
- }
2582
- else
2583
- context.uniform1i(program.hasFog, 0);
2639
+ this.renderer.drawMesh(this, projectionMatrix, viewMatrix, lights, time, cameraPosition, fog, frame);
2584
2640
  }
2585
2641
  destroy() {
2586
2642
  this.clearPrograms();
@@ -2593,15 +2649,12 @@ class Mesh {
2593
2649
  if (this.texCoordBuffer)
2594
2650
  this.renderer.deleteBuffer(this.texCoordBuffer), this.texCoordBuffer = null;
2595
2651
  this.releaseTexture();
2596
- for (let child of this.children) {
2652
+ for (const child of this.children) {
2597
2653
  child.parent = null;
2598
2654
  child.destroy();
2599
2655
  }
2600
2656
  this.children = [];
2601
2657
  }
2602
- ucfirst(string) {
2603
- return string.charAt(0).toUpperCase() + string.slice(1);
2604
- }
2605
2658
  }
2606
2659
 
2607
2660
  class BasicMesh {
@@ -2625,22 +2678,23 @@ class BasicMesh {
2625
2678
 
2626
2679
  class Line extends BasicMesh {
2627
2680
  thickness;
2628
- constructor(vertices, thickness) {
2681
+ constructor(vertices = [], thickness = 2.0) {
2629
2682
  super();
2630
- this.thickness = thickness ? thickness * 0.5 : 1.0;
2631
- if (vertices && vertices.length > 0 && vertices[0] instanceof Vec3) {
2632
- const flat = [];
2683
+ this.thickness = (thickness || 2.0) * 0.5;
2684
+ this.itemSize = 3;
2685
+ this.primitive = 'LINE_STRIP';
2686
+ this.setPoints(vertices);
2687
+ }
2688
+ setPoints(vertices) {
2689
+ const flat = [];
2690
+ if (vertices)
2633
2691
  for (const v of vertices)
2634
2692
  flat.push(v.x, v.y, v.z);
2635
- this.vertices = flat;
2636
- }
2637
- else
2638
- this.vertices = vertices || [];
2639
- this.itemSize = 3;
2693
+ this.vertices = flat;
2640
2694
  this.subMeshes = [
2641
2695
  new SubMesh(0, this.vertices.length / this.itemSize)
2642
2696
  ];
2643
- this.primitive = 'LINE_STRIP';
2697
+ return this;
2644
2698
  }
2645
2699
  }
2646
2700
 
@@ -2666,21 +2720,17 @@ class FullscreenQuad extends BasicMesh {
2666
2720
  }
2667
2721
 
2668
2722
  class Quad extends BasicMesh {
2669
- constructor(sizeOrWidth, height) {
2723
+ constructor(size = new Vec2(2.0, 2.0)) {
2670
2724
  super();
2671
2725
  let width;
2672
2726
  let h;
2673
- if (sizeOrWidth instanceof Vec2) {
2674
- width = sizeOrWidth.x * 0.5;
2675
- h = sizeOrWidth.y * 0.5;
2676
- }
2677
- else if (sizeOrWidth instanceof Rect) {
2678
- width = sizeOrWidth.size.x * 0.5;
2679
- h = sizeOrWidth.size.y * 0.5;
2727
+ if (size instanceof Rect) {
2728
+ width = size.size.x * 0.5;
2729
+ h = size.size.y * 0.5;
2680
2730
  }
2681
2731
  else {
2682
- width = sizeOrWidth ? sizeOrWidth * 0.5 : 1.0;
2683
- h = height ? height * 0.5 : 1.0;
2732
+ width = size.x * 0.5;
2733
+ h = size.y * 0.5;
2684
2734
  }
2685
2735
  this.vertices = [width, -h, 0.0,
2686
2736
  -width, -h, 0.0,
@@ -2702,21 +2752,17 @@ class Quad extends BasicMesh {
2702
2752
 
2703
2753
  class MultiQuad extends BasicMesh {
2704
2754
  quad;
2705
- constructor(sizeOrWidth, height) {
2755
+ constructor(size = new Vec2(2.0, 2.0)) {
2706
2756
  super();
2707
2757
  let width;
2708
2758
  let h;
2709
- if (sizeOrWidth instanceof Vec2) {
2710
- width = sizeOrWidth.x * 0.5;
2711
- h = sizeOrWidth.y * 0.5;
2712
- }
2713
- else if (sizeOrWidth instanceof Rect) {
2714
- width = sizeOrWidth.size.x * 0.5;
2715
- h = sizeOrWidth.size.y * 0.5;
2759
+ if (size instanceof Rect) {
2760
+ width = size.size.x * 0.5;
2761
+ h = size.size.y * 0.5;
2716
2762
  }
2717
2763
  else {
2718
- width = sizeOrWidth ? sizeOrWidth * 0.5 : 1.0;
2719
- h = height ? height * 0.5 : 1.0;
2764
+ width = size.x * 0.5;
2765
+ h = size.y * 0.5;
2720
2766
  }
2721
2767
  this.quad = {
2722
2768
  vertices: new Float32Array([width, -h, 0.0,
@@ -2758,21 +2804,14 @@ class MultiQuad extends BasicMesh {
2758
2804
  }
2759
2805
 
2760
2806
  class Cube extends BasicMesh {
2761
- constructor(sizeOrDimensions = 1.0, flat = false) {
2807
+ constructor(size = new Vec3(2.0, 2.0, 2.0), flat = false) {
2762
2808
  super();
2763
- let sx, sy, sz;
2764
- if (sizeOrDimensions instanceof Vec3) {
2765
- const half = sizeOrDimensions.clone().halve();
2766
- sx = half.x || 0.5;
2767
- sy = half.y || 0.5;
2768
- sz = half.z || 0.5;
2769
- }
2770
- else {
2771
- const s = sizeOrDimensions ? sizeOrDimensions * 0.5 : 1.0;
2772
- sx = s;
2773
- sy = s;
2774
- sz = s;
2775
- }
2809
+ const halfX = (size ? size.x : 2.0) * 0.5;
2810
+ const halfY = (size ? size.y : 2.0) * 0.5;
2811
+ const halfZ = (size ? size.z : 2.0) * 0.5;
2812
+ const sx = halfX || 1.0;
2813
+ const sy = halfY || 1.0;
2814
+ const sz = halfZ || 1.0;
2776
2815
  if (flat) {
2777
2816
  this.vertices = [
2778
2817
  -sx, -sy, sz, sx, -sy, sz, sx, sy, sz, -sx, sy, sz,
@@ -2835,7 +2874,7 @@ class Cube extends BasicMesh {
2835
2874
  }
2836
2875
 
2837
2876
  class Sphere extends BasicMesh {
2838
- constructor() {
2877
+ constructor(scale = new Vec3(1.0, 1.0, 1.0)) {
2839
2878
  super();
2840
2879
  this.vertices = [-0.7071, -0.7071, 0, -0.5449, -0.8315, 0.1084, -0.5556, -0.8315, 0, -0.8315, 0.5556, 0, -0.9061, 0.3827, 0.1802, -0.9239, 0.3827, 0, -0.3753, -0.9239, 0.0747, -0.3827, -0.9239, 0, -0.9619, 0.1951, 0.1913, -0.9808, 0.1951, 0, -0.1913, -0.9808, 0.0381, -0.1951, -0.9808, 0, -0.9808, 0, 0.1951, -1, 0, 0, -0.1951, 0.9808, 0, 0, 1, 0, -0.1913, 0.9808, 0.0381, 0, -1, 0, -0.9619, -0.1951, 0.1913, -0.9808, -0.1951, 0, -0.3753, 0.9239, 0.0747, -0.3827, 0.9239, 0, -0.9239, -0.3827, 0, -0.9061, -0.3827, 0.1802, -0.5556, 0.8315, 0, -0.5449, 0.8315, 0.1084, -0.8155, -0.5556, 0.1622, -0.8315, -0.5556, 0, -0.6935, 0.7071, 0.1379, -0.7071, 0.7071, 0, -0.6935, -0.7071, 0.1379, -0.8155, 0.5556, 0.1622, -0.7682, -0.5556, 0.3182, -0.6533, -0.7071, 0.2706, -0.7682, 0.5556, 0.3182, -0.5133, -0.8315, 0.2126, -0.8536, 0.3827, 0.3536, -0.3536, -0.9239, 0.1464, -0.9061, 0.1951, 0.3753, -0.1802, -0.9808, 0.0747, -0.9239, 0, 0.3827, -0.1802, 0.9808, 0.0747, -0.9061, -0.1951, 0.3753, -0.3536, 0.9239, 0.1464, -0.8536, -0.3827, 0.3536, -0.5133, 0.8315, 0.2126, -0.6533, 0.7071, 0.2706, -0.1622, 0.9808, 0.1084, -0.1622, -0.9808, 0.1084, -0.8155, -0.1951, 0.5449, -0.3182, 0.9239, 0.2126, -0.7682, -0.3827, 0.5133, -0.4619, 0.8315, 0.3087, -0.6913, -0.5556, 0.4619, -0.5879, 0.7071, 0.3928, -0.5879, -0.7071, 0.3928, -0.6913, 0.5556, 0.4619, -0.4619, -0.8315, 0.3087, -0.7682, 0.3827, 0.5133, -0.3182, -0.9239, 0.2126, -0.8155, 0.1951, 0.5449, -0.8315, 0, 0.5556, -0.5879, 0.5556, 0.5879, -0.3928, -0.8315, 0.3928, -0.6533, 0.3827, 0.6533, -0.2706, -0.9239, 0.2706, -0.6935, 0.1951, 0.6935, -0.1379, -0.9808, 0.1379, -0.7071, 0, 0.7071, -0.1379, 0.9808, 0.1379, -0.6935, -0.1951, 0.6935, -0.2706, 0.9239, 0.2706, -0.6533, -0.3827, 0.6533, -0.3928, 0.8315, 0.3928, -0.5879, -0.5556, 0.5879, -0.5, 0.7071, 0.5, -0.5, -0.7071, 0.5, -0.5449, -0.1951, 0.8155, -0.2126, 0.9239, 0.3182, -0.5133, -0.3827, 0.7682, -0.3087, 0.8315, 0.4619, -0.4619, -0.5556, 0.6913, -0.3928, 0.7071, 0.5879, -0.3928, -0.7071, 0.5879, -0.4619, 0.5556, 0.6913, -0.3087, -0.8315, 0.4619, -0.5133, 0.3827, 0.7682, -0.2126, -0.9239, 0.3182, -0.5449, 0.1951, 0.8155, -0.1084, -0.9808, 0.1622, -0.5556, 0, 0.8315, -0.1084, 0.9808, 0.1622, -0.2126, -0.8315, 0.5133, -0.3536, 0.3827, 0.8536, -0.1464, -0.9239, 0.3536, -0.3753, 0.1951, 0.9061, -0.0747, -0.9808, 0.1802, -0.3827, 0, 0.9239, -0.0747, 0.9808, 0.1802, -0.3753, -0.1951, 0.9061, -0.1464, 0.9239, 0.3536, -0.3536, -0.3827, 0.8536, -0.2126, 0.8315, 0.5133, -0.3182, -0.5556, 0.7682, -0.2706, 0.7071, 0.6533, -0.2706, -0.7071, 0.6533, -0.3182, 0.5556, 0.7682, -0.1802, -0.3827, 0.9061, -0.1084, 0.8315, 0.5449, -0.1622, -0.5556, 0.8155, -0.1379, 0.7071, 0.6935, -0.1379, -0.7071, 0.6935, -0.1622, 0.5556, 0.8155, -0.1084, -0.8315, 0.5449, -0.1802, 0.3827, 0.9061, -0.0747, -0.9239, 0.3753, -0.1913, 0.1951, 0.9619, -0.0381, -0.9808, 0.1913, -0.1951, 0, 0.9808, -0.0381, 0.9808, 0.1913, -0.1913, -0.1951, 0.9619, -0.0747, 0.9239, 0.3753, 0, -0.8315, 0.5556, 0, -0.9239, 0.3827, 0, 0.3827, 0.9239, 0, 0.1951, 0.9808, 0, -0.9808, 0.1951, 0, 0, 1, 0, 0.9808, 0.1951, 0, -0.1951, 0.9808, 0, 0.9239, 0.3827, 0, -0.3827, 0.9239, 0, 0.8315, 0.5556, 0, -0.5556, 0.8315, 0, 0.7071, 0.7071, 0, -0.7071, 0.7071, 0, 0.5556, 0.8315, 0.1084, 0.8315, 0.5449, 0.1802, -0.3827, 0.9061, 0.1622, -0.5556, 0.8155, 0.138, 0.7071, 0.6935, 0.138, -0.7071, 0.6935, 0.1622, 0.5556, 0.8155, 0.1084, -0.8315, 0.5449, 0.1802, 0.3827, 0.9061, 0.0747, -0.9239, 0.3753, 0.1913, 0.1951, 0.9619, 0.0381, -0.9808, 0.1913, 0.1951, 0, 0.9808, 0.0381, 0.9808, 0.1913, 0.1913, -0.1951, 0.9619, 0.0747, 0.9239, 0.3753, 0.3536, 0.3827, 0.8536, 0.3753, 0.1951, 0.9061, 0.0747, -0.9808, 0.1802, 0.3827, 0, 0.9239, 0.0747, 0.9808, 0.1802, 0.3753, -0.1951, 0.9061, 0.1464, 0.9239, 0.3536, 0.3536, -0.3827, 0.8536, 0.2126, 0.8315, 0.5133, 0.3182, -0.5556, 0.7682, 0.2706, 0.7071, 0.6533, 0.2706, -0.7071, 0.6533, 0.3182, 0.5556, 0.7682, 0.2126, -0.8315, 0.5133, 0.1464, -0.9239, 0.3536, 0.5133, -0.3827, 0.7682, 0.4619, -0.5556, 0.6913, 0.3928, 0.7071, 0.5879, 0.3928, -0.7071, 0.5879, 0.4619, 0.5556, 0.6913, 0.3087, -0.8315, 0.4619, 0.5133, 0.3827, 0.7682, 0.2126, -0.9239, 0.3182, 0.5449, 0.1951, 0.8155, 0.1084, -0.9808, 0.1622, 0.5556, 0, 0.8315, 0.1084, 0.9808, 0.1622, 0.5449, -0.1951, 0.8155, 0.2126, 0.9239, 0.3182, 0.3087, 0.8315, 0.4619, 0.2706, -0.9239, 0.2706, 0.138, -0.9808, 0.1379, 0.7071, 0, 0.7071, 0.138, 0.9808, 0.1379, 0.6935, -0.1951, 0.6935, 0.2706, 0.9239, 0.2706, 0.6533, -0.3827, 0.6533, 0.3928, 0.8315, 0.3928, 0.5879, -0.5556, 0.5879, 0.5, 0.7071, 0.5, 0.5, -0.7071, 0.5, 0.5879, 0.5556, 0.5879, 0.3928, -0.8315, 0.3928, 0.6533, 0.3827, 0.6533, 0.6935, 0.1951, 0.6935, 0.4619, 0.8315, 0.3087, 0.5879, 0.7071, 0.3928, 0.5879, -0.7071, 0.3928, 0.6913, 0.5556, 0.4619, 0.4619, -0.8315, 0.3087, 0.7682, 0.3827, 0.5133, 0.3182, -0.9239, 0.2126, 0.8155, 0.1951, 0.5449, 0.1622, -0.9808, 0.1084, 0.8315, 0, 0.5556, 0.1622, 0.9808, 0.1084, 0.8155, -0.1951, 0.5449, 0.3182, 0.9239, 0.2126, 0.7682, -0.3827, 0.5133, 0.6913, -0.5556, 0.4619, 0.9239, 0, 0.3827, 0.1802, 0.9808, 0.0747, 0.1802, -0.9808, 0.0747, 0.9061, -0.1951, 0.3753, 0.3536, 0.9239, 0.1464, 0.8536, -0.3827, 0.3536, 0.5133, 0.8315, 0.2126, 0.7682, -0.5556, 0.3182, 0.6533, 0.7071, 0.2706, 0.6533, -0.7071, 0.2706, 0.7682, 0.5556, 0.3182, 0.5133, -0.8315, 0.2126, 0.8536, 0.3827, 0.3536, 0.3536, -0.9239, 0.1464, 0.9061, 0.1951, 0.3753, 0.6935, -0.7071, 0.1379, 0.8155, 0.5556, 0.1622, 0.5449, -0.8315, 0.1084, 0.9061, 0.3827, 0.1802, 0.3753, -0.9239, 0.0747, 0.9619, 0.1951, 0.1913, 0.1913, -0.9808, 0.0381, 0.9808, 0, 0.1951, 0.1913, 0.9808, 0.0381, 0.9619, -0.1951, 0.1913, 0.3753, 0.9239, 0.0747, 0.9061, -0.3827, 0.1802, 0.5449, 0.8315, 0.1084, 0.8155, -0.5556, 0.1622, 0.6935, 0.7071, 0.1379, 0.1951, -0.9808, 0, 0.9808, -0.1951, 0, 0.3827, 0.9239, 0, 0.9239, -0.3827, 0, 0.5556, 0.8315, 0, 0.8315, -0.5556, 0, 0.7071, 0.7071, 0, 0.7071, -0.7071, 0, 0.8315, 0.5556, 0, 0.5556, -0.8315, 0, 0.9239, 0.3827, 0, 0.3827, -0.9239, 0, 0.9808, 0.1951, 0, 1, 0, 0, 0.1951, 0.9808, 0, 0.5449, -0.8315, -0.1084, 0.9061, 0.3827, -0.1802, 0.3753, -0.9239, -0.0747, 0.9619, 0.1951, -0.1913, 0.1913, -0.9808, -0.0381, 0.9808, 0, -0.1951, 0.1913, 0.9808, -0.0381, 0.9619, -0.1951, -0.1913, 0.3753, 0.9239, -0.0747, 0.9061, -0.3827, -0.1802, 0.5449, 0.8315, -0.1084, 0.8155, -0.5556, -0.1622, 0.6935, 0.7071, -0.1379, 0.6935, -0.7071, -0.1379, 0.8155, 0.5556, -0.1622, 0.3536, 0.9239, -0.1464, 0.8536, -0.3827, -0.3536, 0.5133, 0.8315, -0.2126, 0.7682, -0.5556, -0.3182, 0.6533, 0.7071, -0.2706, 0.6533, -0.7071, -0.2706, 0.7682, 0.5556, -0.3182, 0.5133, -0.8315, -0.2126, 0.8536, 0.3827, -0.3536, 0.3536, -0.9239, -0.1464, 0.9061, 0.1951, -0.3753, 0.1802, -0.9808, -0.0747, 0.9239, 0, -0.3827, 0.1802, 0.9808, -0.0747, 0.9061, -0.1951, -0.3753, 0.7682, 0.3827, -0.5133, 0.3182, -0.9239, -0.2126, 0.8155, 0.1951, -0.5449, 0.1622, -0.9808, -0.1084, 0.8315, 0, -0.5556, 0.1622, 0.9808, -0.1084, 0.8155, -0.1951, -0.5449, 0.3182, 0.9239, -0.2126, 0.7682, -0.3827, -0.5133, 0.4619, 0.8315, -0.3087, 0.6913, -0.5556, -0.4619, 0.5879, 0.7071, -0.3928, 0.5879, -0.7071, -0.3928, 0.6913, 0.5556, -0.4619, 0.4619, -0.8315, -0.3087, 0.6533, -0.3827, -0.6533, 0.3928, 0.8315, -0.3928, 0.5879, -0.5556, -0.5879, 0.5, 0.7071, -0.5, 0.5, -0.7071, -0.5, 0.5879, 0.5556, -0.5879, 0.3928, -0.8315, -0.3928, 0.6533, 0.3827, -0.6533, 0.2706, -0.9239, -0.2706, 0.6935, 0.1951, -0.6935, 0.1379, -0.9808, -0.1379, 0.7071, 0, -0.7071, 0.138, 0.9808, -0.1379, 0.6935, -0.1951, -0.6935, 0.2706, 0.9239, -0.2706, 0.3087, -0.8315, -0.4619, 0.2126, -0.9239, -0.3182, 0.5133, 0.3827, -0.7682, 0.5449, 0.1951, -0.8155, 0.1084, -0.9808, -0.1622, 0.5556, 0, -0.8315, 0.1084, 0.9808, -0.1622, 0.5449, -0.1951, -0.8155, 0.2126, 0.9239, -0.3182, 0.5133, -0.3827, -0.7682, 0.3087, 0.8315, -0.4619, 0.4619, -0.5556, -0.6913, 0.3928, 0.7071, -0.5879, 0.3928, -0.7071, -0.5879, 0.4619, 0.5556, -0.6913, 0.2126, 0.8315, -0.5133, 0.3536, -0.3827, -0.8536, 0.3182, -0.5556, -0.7682, 0.2706, 0.7071, -0.6533, 0.2706, -0.7071, -0.6533, 0.3182, 0.5556, -0.7682, 0.2126, -0.8315, -0.5133, 0.3536, 0.3827, -0.8536, 0.1464, -0.9239, -0.3536, 0.3753, 0.1951, -0.9061, 0.0747, -0.9808, -0.1802, 0.3827, 0, -0.9239, 0.0747, 0.9808, -0.1802, 0.3753, -0.1951, -0.9061, 0.1464, 0.9239, -0.3536, 0.1802, 0.3827, -0.9061, 0.1913, 0.1951, -0.9619, 0.0381, -0.9808, -0.1913, 0.1951, 0, -0.9808, 0.0381, 0.9808, -0.1913, 0.1913, -0.1951, -0.9619, 0.0747, 0.9239, -0.3753, 0.1802, -0.3827, -0.9061, 0.1084, 0.8315, -0.5449, 0.1622, -0.5556, -0.8155, 0.1379, 0.7071, -0.6935, 0.1379, -0.7071, -0.6935, 0.1622, 0.5556, -0.8155, 0.1084, -0.8315, -0.5449, 0.0747, -0.9239, -0.3753, 0, -0.3827, -0.9239, 0, -0.5556, -0.8315, 0, 0.8315, -0.5556, 0, 0.7071, -0.7071, 0, -0.7071, -0.7071, 0, 0.5556, -0.8315, 0, -0.8315, -0.5556, 0, 0.3827, -0.9239, 0, -0.9239, -0.3827, 0, 0.1951, -0.9808, 0, -0.9808, -0.1951, 0, 0, -1, 0, 0.9808, -0.1951, 0, -0.1951, -0.9808, 0, 0.9239, -0.3827, -0.0381, -0.9808, -0.1913, -0.1951, 0, -0.9808, -0.0381, 0.9808, -0.1913, -0.1913, -0.1951, -0.9619, -0.0747, 0.9239, -0.3753, -0.1802, -0.3827, -0.9061, -0.1084, 0.8315, -0.5449, -0.1622, -0.5556, -0.8155, -0.1379, 0.7071, -0.6935, -0.1379, -0.7071, -0.6935, -0.1622, 0.5556, -0.8155, -0.1084, -0.8315, -0.5449, -0.1802, 0.3827, -0.9061, -0.0747, -0.9239, -0.3753, -0.1913, 0.1951, -0.9619, -0.2706, -0.7071, -0.6533, -0.3182, 0.5556, -0.7682, -0.2126, -0.8315, -0.5133, -0.3536, 0.3827, -0.8536, -0.1464, -0.9239, -0.3536, -0.3753, 0.1951, -0.9061, -0.0747, -0.9808, -0.1802, -0.3827, 0, -0.9239, -0.0747, 0.9808, -0.1802, -0.3753, -0.1951, -0.9061, -0.1464, 0.9239, -0.3536, -0.3536, -0.3827, -0.8536, -0.2126, 0.8315, -0.5133, -0.3182, -0.5556, -0.7682, -0.2706, 0.7071, -0.6533, -0.1084, 0.9808, -0.1622, -0.1084, -0.9808, -0.1622, -0.5556, 0, -0.8315, -0.5449, -0.1951, -0.8155, -0.2126, 0.9239, -0.3182, -0.5133, -0.3827, -0.7682, -0.3087, 0.8315, -0.4619, -0.4619, -0.5556, -0.6913, -0.3928, 0.7071, -0.5879, -0.3928, -0.7071, -0.5879, -0.4619, 0.5556, -0.6913, -0.3087, -0.8315, -0.4619, -0.5133, 0.3827, -0.7682, -0.2126, -0.9239, -0.3182, -0.5449, 0.1951, -0.8155, -0.5, 0.7071, -0.5, -0.5879, 0.5556, -0.5879, -0.3928, -0.8315, -0.3928, -0.6533, 0.3827, -0.6533, -0.2706, -0.9239, -0.2706, -0.6935, 0.1951, -0.6935, -0.1379, -0.9808, -0.1379, -0.7071, 0, -0.7071, -0.1379, 0.9808, -0.1379, -0.6935, -0.1951, -0.6935, -0.2706, 0.9239, -0.2706, -0.6533, -0.3827, -0.6533, -0.3928, 0.8315, -0.3928, -0.5879, -0.5556, -0.5879, -0.5, -0.7071, -0.5, -0.8155, -0.1951, -0.5449, -0.3182, 0.9239, -0.2126, -0.7682, -0.3827, -0.5133, -0.4619, 0.8315, -0.3087, -0.6913, -0.5556, -0.4619, -0.5879, 0.7071, -0.3928, -0.5879, -0.7071, -0.3928, -0.6913, 0.5556, -0.4619, -0.4619, -0.8315, -0.3087, -0.7682, 0.3827, -0.5133, -0.3182, -0.9239, -0.2126, -0.8155, 0.1951, -0.5449, -0.1622, -0.9808, -0.1084, -0.8315, 0, -0.5556, -0.1622, 0.9808, -0.1084, -0.5133, -0.8315, -0.2126, -0.8536, 0.3827, -0.3536, -0.3536, -0.9239, -0.1464, -0.9061, 0.1951, -0.3753, -0.1802, -0.9808, -0.0747, -0.9239, 0, -0.3827, -0.1802, 0.9808, -0.0747, -0.9061, -0.1951, -0.3753, -0.3536, 0.9239, -0.1464, -0.8536, -0.3827, -0.3536, -0.5133, 0.8315, -0.2126, -0.7682, -0.5556, -0.3182, -0.6533, 0.7071, -0.2706, -0.6533, -0.7071, -0.2706, -0.7682, 0.5556, -0.3182, -0.3753, 0.9239, -0.0747, -0.9061, -0.3827, -0.1802, -0.5449, 0.8315, -0.1084, -0.8155, -0.5556, -0.1622, -0.6935, 0.7071, -0.1379, -0.6935, -0.7071, -0.1379, -0.8155, 0.5556, -0.1622, -0.5449, -0.8315, -0.1084, -0.9061, 0.3827, -0.1802, -0.3753, -0.9239, -0.0747, -0.9619, 0.1951, -0.1913, -0.1913, -0.9808, -0.0381, -0.9808, 0, -0.1951, -0.1913, 0.9808, -0.0381, -0.9619, -0.1951, -0.1913
2841
2880
  ];
@@ -2846,6 +2885,28 @@ class Sphere extends BasicMesh {
2846
2885
  this.subMeshes = [
2847
2886
  new SubMesh(0, 2880)
2848
2887
  ];
2888
+ if (scale !== undefined) {
2889
+ const sx = scale.x;
2890
+ const sy = scale.y;
2891
+ const sz = scale.z;
2892
+ if (sx !== 1 || sy !== 1 || sz !== 1) {
2893
+ for (let i = 0; i < this.vertices.length; i += 3) {
2894
+ this.vertices[i] *= sx;
2895
+ this.vertices[i + 1] *= sy;
2896
+ this.vertices[i + 2] *= sz;
2897
+ }
2898
+ if (sx !== sy || sy !== sz)
2899
+ for (let i = 0; i < this.normals.length; i += 3) {
2900
+ const nx = this.normals[i] / sx;
2901
+ const ny = this.normals[i + 1] / sy;
2902
+ const nz = this.normals[i + 2] / sz;
2903
+ const len = Math.hypot(nx, ny, nz) || 1.0;
2904
+ this.normals[i] = nx / len;
2905
+ this.normals[i + 1] = ny / len;
2906
+ this.normals[i + 2] = nz / len;
2907
+ }
2908
+ }
2909
+ }
2849
2910
  }
2850
2911
  }
2851
2912
 
@@ -2865,27 +2926,13 @@ class Hemisphere extends BasicMesh {
2865
2926
  }
2866
2927
 
2867
2928
  class Plane extends BasicMesh {
2868
- constructor(sizeOrWidth = 1.0, heightOrWidthSegments = 1.0, widthOrHeightSegments = 1, heightSegmentsOrOrientation, orientation = 'xz') {
2929
+ constructor(size = new Vec2(1.0, 1.0), segments = new Vec2(1, 1), orientation = 'xz') {
2869
2930
  super();
2870
- let w;
2871
- let h;
2872
- let gridX;
2873
- let gridY;
2874
- let orient;
2875
- if (sizeOrWidth instanceof Vec2) {
2876
- w = sizeOrWidth.x || 1.0;
2877
- h = sizeOrWidth.y || 1.0;
2878
- gridX = Utils.clamp(Utils.floor(heightOrWidthSegments || 1, 0), 1, Infinity);
2879
- gridY = Utils.clamp(Utils.floor(widthOrHeightSegments || 1, 0), 1, Infinity);
2880
- orient = (typeof heightSegmentsOrOrientation === 'string' ? heightSegmentsOrOrientation : orientation) || 'xz';
2881
- }
2882
- else {
2883
- w = sizeOrWidth || 1.0;
2884
- h = heightOrWidthSegments || 1.0;
2885
- gridX = Utils.clamp(Utils.floor(widthOrHeightSegments || 1, 0), 1, Infinity);
2886
- gridY = Utils.clamp(Utils.floor((typeof heightSegmentsOrOrientation === 'number' ? heightSegmentsOrOrientation : 1) || 1, 0), 1, Infinity);
2887
- orient = orientation || 'xz';
2888
- }
2931
+ const w = size.x || 1.0;
2932
+ const h = size.y || 1.0;
2933
+ const gridX = Utils.clamp(Utils.floor(segments.x || 1, 0), 1, Infinity);
2934
+ const gridY = Utils.clamp(Utils.floor(segments.y || 1, 0), 1, Infinity);
2935
+ const orient = orientation || 'xz';
2889
2936
  const gridX1 = gridX + 1;
2890
2937
  const gridY1 = gridY + 1;
2891
2938
  const segmentWidth = w / gridX;
@@ -2931,13 +2978,14 @@ class Plane extends BasicMesh {
2931
2978
  }
2932
2979
 
2933
2980
  class Cylinder extends BasicMesh {
2934
- constructor(radiusTop = 1.0, radiusBottom = 1.0, height = 1.0, radialSegments = 16, heightSegments = 1, openEnded = false) {
2981
+ constructor(radii = new Vec2(1.0, 1.0), height = 1.0, radialSegments = 16, heightSegments = 1, openEnded = false) {
2935
2982
  super();
2936
- const rTop = radiusTop !== undefined ? radiusTop : 1.0;
2937
- const rBottom = radiusBottom !== undefined ? radiusBottom : 1.0;
2938
- const h = height || 1.0;
2939
- const radSeg = Utils.clamp(Utils.floor(radialSegments || 16, 0), 3, Infinity);
2940
- const hSeg = Utils.clamp(Utils.floor(heightSegments || 1, 0), 1, Infinity);
2983
+ const rTop = radii.x;
2984
+ const rBottom = radii.y;
2985
+ const h = height;
2986
+ const radSeg = Utils.clamp(Utils.floor(radialSegments, 0), 3, Infinity);
2987
+ const hSeg = Utils.clamp(Utils.floor(heightSegments, 0), 1, Infinity);
2988
+ const open = openEnded;
2941
2989
  const vertices = [];
2942
2990
  const normals = [];
2943
2991
  const uvs = [];
@@ -2978,7 +3026,7 @@ class Cylinder extends BasicMesh {
2978
3026
  indices.push(a, b, d);
2979
3027
  indices.push(b, c, d);
2980
3028
  }
2981
- if (!openEnded) {
3029
+ if (!open) {
2982
3030
  if (rTop > 0) {
2983
3031
  const centerIndexTop = index++;
2984
3032
  vertices.push(0, halfH, 0);
@@ -3086,13 +3134,10 @@ class CustomMesh {
3086
3134
  ];
3087
3135
  }
3088
3136
  static toFloat32(array) {
3089
- if (array.length > 0 && array[0] instanceof Vec3) {
3090
- const flat = new Float32Array(array.length * 3);
3091
- for (let i = 0; i < array.length; i++)
3092
- array[i].toArray(flat, i * 3);
3093
- return flat;
3094
- }
3095
- return array instanceof Float32Array ? array.slice(0) : new Float32Array(array);
3137
+ const flat = new Float32Array(array.length * 3);
3138
+ for (let i = 0; i < array.length; i++)
3139
+ array[i].toArray(flat, i * 3);
3140
+ return flat;
3096
3141
  }
3097
3142
  setVertices(array) {
3098
3143
  this.vertices = CustomMesh.toFloat32(array);
@@ -3172,6 +3217,284 @@ class CustomMesh {
3172
3217
  }
3173
3218
  }
3174
3219
 
3220
+ const SpriteShader = {
3221
+ vertex: `
3222
+ attribute vec3 aVertexPosition;
3223
+ attribute vec2 aTextureCoord;
3224
+
3225
+ uniform mat4 uModelMatrix;
3226
+ uniform mat4 uViewMatrix;
3227
+ uniform mat4 uProjectionMatrix;
3228
+ uniform float uFrame;
3229
+ uniform float uFrames;
3230
+ uniform float uBillboard;
3231
+ uniform float uDepthBias;
3232
+
3233
+ varying vec2 vTextureCoord;
3234
+ varying float vDepth;
3235
+
3236
+ void main(void) {
3237
+ vTextureCoord = vec2((uFrame + aTextureCoord.x) / uFrames, aTextureCoord.y);
3238
+ vec3 view;
3239
+ if (uBillboard < 0.5) {
3240
+ view = (uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0)).xyz;
3241
+ } else {
3242
+ vec3 origin = (uViewMatrix * uModelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
3243
+ vec3 up = vec3(0.0, 1.0, 0.0);
3244
+ vec3 right = vec3(1.0, 0.0, 0.0);
3245
+ if (uBillboard > 1.5) {
3246
+ up = normalize((uViewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz);
3247
+ vec3 across = cross(origin, up);
3248
+ if (length(across) > 0.0001)
3249
+ right = normalize(across);
3250
+ }
3251
+ float scaleX = length(uModelMatrix[0].xyz);
3252
+ float scaleY = length(uModelMatrix[1].xyz);
3253
+ view = origin + right * aVertexPosition.x * scaleX + up * aVertexPosition.y * scaleY;
3254
+ }
3255
+ vDepth = -view.z;
3256
+ vec4 clip = uProjectionMatrix * vec4(view, 1.0);
3257
+ // only the depth moves: the picture stays where it is and as big as it is
3258
+ float bias = min(uDepthBias, max(0.0, vDepth - 1.0));
3259
+ if (bias > 0.0) {
3260
+ vec4 nearer = uProjectionMatrix * vec4(view.xy, view.z + bias, 1.0);
3261
+ clip.z = nearer.z / nearer.w * clip.w;
3262
+ }
3263
+ gl_Position = clip;
3264
+ }
3265
+ `,
3266
+ fragment: `
3267
+ precision mediump float;
3268
+
3269
+ uniform sampler2D uSampler;
3270
+ uniform vec3 uMaterialDiffuse;
3271
+ uniform float uAlphaCutoff;
3272
+ uniform int uHasFog;
3273
+ uniform vec3 uFogColor;
3274
+ uniform float uFogNear;
3275
+ uniform float uFogFar;
3276
+
3277
+ varying vec2 vTextureCoord;
3278
+ varying float vDepth;
3279
+
3280
+ void main(void) {
3281
+ vec4 texel = texture2D(uSampler, vTextureCoord);
3282
+ if (texel.a < uAlphaCutoff)
3283
+ discard;
3284
+ vec3 color = texel.rgb * uMaterialDiffuse;
3285
+ if (uHasFog == 1) {
3286
+ float fade = clamp((vDepth - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0);
3287
+ color = mix(color, uFogColor, fade);
3288
+ }
3289
+ gl_FragColor = vec4(color, texel.a);
3290
+ }
3291
+ `,
3292
+ create(context) {
3293
+ return Program.create(context, SpriteShader.vertex, SpriteShader.fragment);
3294
+ }
3295
+ };
3296
+
3297
+ const scratchSpritePos = new Vec3();
3298
+ const scratchSpriteScale = new Vec3();
3299
+ const scratchSpriteNormal = new Vec3();
3300
+ const scratchSpriteUp = new Vec3();
3301
+ const scratchSpriteRight = new Vec3();
3302
+ const scratchSpriteHit = new Vec3();
3303
+ const scratchSpriteC0 = new Vec3();
3304
+ const scratchSpriteC1 = new Vec3();
3305
+ const scratchSpriteC2 = new Vec3();
3306
+ const scratchSpriteC3 = new Vec3();
3307
+ function raycastSprite(sprite, ray) {
3308
+ const worldPos = sprite.getWorldPosition(scratchSpritePos);
3309
+ const scale = sprite.worldMatrix.getScale(scratchSpriteScale);
3310
+ const normal = scratchSpriteNormal;
3311
+ const up = scratchSpriteUp;
3312
+ const right = scratchSpriteRight;
3313
+ if (sprite.billboard === 'spherical') {
3314
+ normal.copy(ray.direction).opposite().normalize();
3315
+ up.setScalar(0, 1, 0);
3316
+ const dotUp = up.dotProduct(normal);
3317
+ up.subtractScaledVector(normal, dotUp);
3318
+ if (up.getMagnitude(true) < 0.0001) {
3319
+ up.setScalar(0, 0, 1);
3320
+ const dotZ = up.dotProduct(normal);
3321
+ up.subtractScaledVector(normal, dotZ);
3322
+ }
3323
+ up.normalize();
3324
+ right.crossVectors(up, normal).normalize();
3325
+ }
3326
+ else {
3327
+ up.setScalar(0, 1, 0);
3328
+ normal.setScalar(-ray.direction.x, 0, -ray.direction.z);
3329
+ if (normal.getMagnitude(true) < 0.0001)
3330
+ normal.setScalar(ray.origin.x - worldPos.x, 0, ray.origin.z - worldPos.z);
3331
+ if (normal.getMagnitude(true) < 0.0001)
3332
+ normal.setScalar(0, 0, 1);
3333
+ else
3334
+ normal.normalize();
3335
+ right.crossVectors(up, normal).normalize();
3336
+ }
3337
+ const hw = (sprite.width / 2) * scale.x;
3338
+ const y0 = (sprite.anchor === 'bottom' ? 0 : -sprite.height / 2) * scale.y;
3339
+ const y1 = (sprite.anchor === 'bottom' ? sprite.height : sprite.height / 2) * scale.y;
3340
+ const c0 = scratchSpriteC0.setScalar(worldPos.x - right.x * hw + up.x * y0, worldPos.y - right.y * hw + up.y * y0, worldPos.z - right.z * hw + up.z * y0);
3341
+ const c1 = scratchSpriteC1.setScalar(worldPos.x + right.x * hw + up.x * y0, worldPos.y + right.y * hw + up.y * y0, worldPos.z + right.z * hw + up.z * y0);
3342
+ const c2 = scratchSpriteC2.setScalar(worldPos.x + right.x * hw + up.x * y1, worldPos.y + right.y * hw + up.y * y1, worldPos.z + right.z * hw + up.z * y1);
3343
+ const c3 = scratchSpriteC3.setScalar(worldPos.x - right.x * hw + up.x * y1, worldPos.y - right.y * hw + up.y * y1, worldPos.z - right.z * hw + up.z * y1);
3344
+ let hit = ray.intersectTriangle(c0, c1, c2, false, scratchSpriteHit);
3345
+ if (!hit)
3346
+ hit = ray.intersectTriangle(c0, c2, c3, false, scratchSpriteHit);
3347
+ if (!hit)
3348
+ return null;
3349
+ return { distance: ray.origin.getDistance(hit), point: new Vec3().copy(hit) };
3350
+ }
3351
+
3352
+ const BILLBOARD_VALUES = { none: 0, spherical: 1, cylindrical: 2 };
3353
+ class Sprite extends Mesh {
3354
+ width;
3355
+ height;
3356
+ frames;
3357
+ anchor;
3358
+ billboard;
3359
+ currentFrame = 0;
3360
+ alphaCutoff = 0.5;
3361
+ depthBias = 0;
3362
+ static scratchSpriteMin = new Vec3();
3363
+ static scratchSpriteMax = new Vec3();
3364
+ static scratchSpriteBox = new Box();
3365
+ static scratchSpriteHit = new Vec3();
3366
+ constructor(context, options) {
3367
+ const size = options.size ?? new Vec2(1, 1);
3368
+ const width = size.x;
3369
+ const height = size.y;
3370
+ const anchor = options.anchor ?? 'center';
3371
+ const billboard = options.billboard ?? 'spherical';
3372
+ const frames = Math.max(1, Math.floor(options.frames ?? 1));
3373
+ const x = width / 2;
3374
+ const y0 = anchor === 'bottom' ? 0 : -height / 2;
3375
+ const y1 = anchor === 'bottom' ? height : height / 2;
3376
+ const geometry = {
3377
+ vertices: [-x, y0, 0, x, y0, 0, x, y1, 0, -x, y0, 0, x, y1, 0, -x, y1, 0],
3378
+ uvs: [0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1],
3379
+ indices: null,
3380
+ normals: null,
3381
+ subMeshes: [],
3382
+ itemSize: 3,
3383
+ primitive: 'TRIANGLES'
3384
+ };
3385
+ super(geometry, context);
3386
+ this.width = width;
3387
+ this.height = height;
3388
+ this.frames = frames;
3389
+ this.anchor = anchor;
3390
+ this.billboard = billboard;
3391
+ this.alphaCutoff = options.alphaCutoff ?? 0.5;
3392
+ this.depthBias = options.depthBias ?? 0;
3393
+ const material = options.material ?? new Material({ diffuse: new Vec3(1, 1, 1) });
3394
+ this.addProgram(SpriteShader.vertex, options.fragment ?? SpriteShader.fragment, material);
3395
+ this.addCustomUniform('frame', 'uniform1f', 0);
3396
+ this.addCustomUniform('frames', 'uniform1f', frames);
3397
+ this.addCustomUniform('billboard', 'uniform1f', BILLBOARD_VALUES[billboard]);
3398
+ this.addCustomUniform('alphaCutoff', 'uniform1f', this.alphaCutoff);
3399
+ this.addCustomUniform('depthBias', 'uniform1f', this.depthBias);
3400
+ this.updateLocalBounds();
3401
+ if (options.texture)
3402
+ this.setTexture(options.texture, options.textureOptions);
3403
+ }
3404
+ updateLocalBounds() {
3405
+ const x = this.width / 2;
3406
+ const y0 = this.anchor === 'bottom' ? 0 : -this.height / 2;
3407
+ const y1 = this.anchor === 'bottom' ? this.height : this.height / 2;
3408
+ if (this.billboard !== 'none') {
3409
+ const reach = Math.hypot(x, Math.max(Math.abs(y0), Math.abs(y1)));
3410
+ if (this.billboard === 'spherical') {
3411
+ Sprite.scratchSpriteMin.setScalar(-reach, -reach, -reach);
3412
+ Sprite.scratchSpriteMax.setScalar(reach, reach, reach);
3413
+ }
3414
+ else {
3415
+ Sprite.scratchSpriteMin.setScalar(-x, y0, -x);
3416
+ Sprite.scratchSpriteMax.setScalar(x, y1, x);
3417
+ }
3418
+ }
3419
+ else {
3420
+ Sprite.scratchSpriteMin.setScalar(-x, y0, 0);
3421
+ Sprite.scratchSpriteMax.setScalar(x, y1, 0);
3422
+ }
3423
+ Sprite.scratchSpriteBox.set(Sprite.scratchSpriteMin, Sprite.scratchSpriteMax);
3424
+ this.setLocalBounds(Sprite.scratchSpriteBox);
3425
+ }
3426
+ setFrame(frame) {
3427
+ this.currentFrame = ((Math.floor(frame) % this.frames) + this.frames) % this.frames;
3428
+ this.setCustomUniform('frame', this.currentFrame);
3429
+ return this;
3430
+ }
3431
+ getFrame() {
3432
+ return this.currentFrame;
3433
+ }
3434
+ setFrames(frames) {
3435
+ this.frames = Math.max(1, Math.floor(frames));
3436
+ this.setCustomUniform('frames', this.frames);
3437
+ return this.setFrame(this.currentFrame);
3438
+ }
3439
+ getFrames() {
3440
+ return this.frames;
3441
+ }
3442
+ setBillboard(billboard) {
3443
+ if (this.billboard === billboard)
3444
+ return this;
3445
+ this.billboard = billboard;
3446
+ this.setCustomUniform('billboard', BILLBOARD_VALUES[billboard]);
3447
+ this.updateLocalBounds();
3448
+ return this;
3449
+ }
3450
+ getBillboard() {
3451
+ return this.billboard;
3452
+ }
3453
+ setAlphaCutoff(cutoff) {
3454
+ this.alphaCutoff = cutoff;
3455
+ this.setCustomUniform('alphaCutoff', cutoff);
3456
+ return this;
3457
+ }
3458
+ getAlphaCutoff() {
3459
+ return this.alphaCutoff;
3460
+ }
3461
+ setDepthBias(depthBias) {
3462
+ this.depthBias = depthBias;
3463
+ this.setCustomUniform('depthBias', depthBias);
3464
+ return this;
3465
+ }
3466
+ getDepthBias() {
3467
+ return this.depthBias;
3468
+ }
3469
+ getWidth() {
3470
+ return this.width;
3471
+ }
3472
+ getHeight() {
3473
+ return this.height;
3474
+ }
3475
+ getSize(target = new Vec2()) {
3476
+ target.x = this.width;
3477
+ target.y = this.height;
3478
+ return target;
3479
+ }
3480
+ getAnchor() {
3481
+ return this.anchor;
3482
+ }
3483
+ raycast(ray, precise = false) {
3484
+ if (!this.isActive())
3485
+ return null;
3486
+ if (this.billboard === 'none')
3487
+ return super.raycast(ray, precise);
3488
+ const sphere = this.computeBoundingSphere();
3489
+ const hitSphere = ray.intersectSphere(sphere, Sprite.scratchSpriteHit);
3490
+ if (!hitSphere)
3491
+ return null;
3492
+ if (!precise)
3493
+ return { distance: ray.origin.getDistance(hitSphere), point: new Vec3().copy(hitSphere) };
3494
+ return raycastSprite(this, ray);
3495
+ }
3496
+ }
3497
+
3175
3498
  class DirectionalLight {
3176
3499
  position;
3177
3500
  diffuse;
@@ -3185,9 +3508,9 @@ class DirectionalLight {
3185
3508
  this.specular = new Vec3(0.8, 0.8, 0.8);
3186
3509
  this.type = 'directional';
3187
3510
  if (directionOrOptions)
3188
- if (directionOrOptions instanceof Vec3 || Array.isArray(directionOrOptions))
3511
+ if (directionOrOptions instanceof Vec3)
3189
3512
  this.setDirection(directionOrOptions);
3190
- else if (typeof directionOrOptions === 'object') {
3513
+ else {
3191
3514
  if (directionOrOptions.direction)
3192
3515
  this.setDirection(directionOrOptions.direction);
3193
3516
  if (directionOrOptions.diffuse)
@@ -3198,17 +3521,17 @@ class DirectionalLight {
3198
3521
  this.setPosition(directionOrOptions.position);
3199
3522
  }
3200
3523
  }
3201
- setPosition(xOrVec, y, z) {
3202
- assignVec3(this.position, xOrVec, y, z);
3524
+ setPosition(position) {
3525
+ this.position.copy(position);
3203
3526
  }
3204
- setDirection(xOrVec, y, z) {
3205
- assignVec3(this.direction, xOrVec, y, z).normalize();
3527
+ setDirection(direction) {
3528
+ this.direction.copy(direction).normalize();
3206
3529
  }
3207
- setDiffuse(xOrVec, y, z) {
3208
- assignVec3(this.diffuse, xOrVec, y, z);
3530
+ setDiffuse(diffuse) {
3531
+ this.diffuse.copy(diffuse);
3209
3532
  }
3210
- setSpecular(xOrVec, y, z) {
3211
- assignVec3(this.specular, xOrVec, y, z);
3533
+ setSpecular(specular) {
3534
+ this.specular.copy(specular);
3212
3535
  }
3213
3536
  }
3214
3537
 
@@ -3223,9 +3546,9 @@ class PointLight extends DirectionalLight {
3223
3546
  this.quadraticAttenuation = 0;
3224
3547
  this.type = 'point';
3225
3548
  if (positionOrOptions)
3226
- if (positionOrOptions instanceof Vec3 || Array.isArray(positionOrOptions))
3549
+ if (positionOrOptions instanceof Vec3)
3227
3550
  this.setPosition(positionOrOptions);
3228
- else if (typeof positionOrOptions === 'object') {
3551
+ else {
3229
3552
  if (positionOrOptions.position)
3230
3553
  this.setPosition(positionOrOptions.position);
3231
3554
  if (positionOrOptions.diffuse)
@@ -3261,7 +3584,7 @@ class SpotLight extends PointLight {
3261
3584
  this.exponent = 0;
3262
3585
  this.direction = new Vec3(1.0, 0.0, 0.0);
3263
3586
  this.type = 'spot';
3264
- if (positionOrOptions && typeof positionOrOptions === 'object' && !(positionOrOptions instanceof Vec3) && !Array.isArray(positionOrOptions)) {
3587
+ if (positionOrOptions && !(positionOrOptions instanceof Vec3)) {
3265
3588
  if (positionOrOptions.direction)
3266
3589
  this.setDirection(positionOrOptions.direction);
3267
3590
  if (positionOrOptions.cutoff !== undefined)
@@ -3276,8 +3599,8 @@ class SpotLight extends PointLight {
3276
3599
  setExponent(value) {
3277
3600
  this.exponent = value;
3278
3601
  }
3279
- setDirection(xOrVec, y, z) {
3280
- assignVec3(this.direction, xOrVec, y, z);
3602
+ setDirection(direction) {
3603
+ this.direction.copy(direction);
3281
3604
  }
3282
3605
  }
3283
3606
 
@@ -3285,31 +3608,21 @@ class AmbientLight {
3285
3608
  diffuse;
3286
3609
  intensity;
3287
3610
  type;
3288
- constructor(rOrVec = 1.0, g = 1.0, b = 1.0, intensity = 1.0) {
3289
- this.diffuse = new Vec3();
3290
- if (rOrVec instanceof Vec3) {
3291
- this.diffuse.copy(rOrVec);
3292
- this.intensity = g !== undefined ? g : 1.0;
3293
- }
3294
- else {
3295
- this.diffuse.setScalar(rOrVec, g, b);
3296
- this.intensity = intensity;
3297
- }
3611
+ constructor(diffuse = new Vec3(1.0, 1.0, 1.0), intensity = 1.0) {
3612
+ this.diffuse = new Vec3().copy(diffuse);
3613
+ this.intensity = intensity;
3298
3614
  this.type = 'ambient';
3299
3615
  }
3300
- setColor(xOrVec, y, z) {
3301
- if (typeof xOrVec === 'number')
3302
- this.diffuse.setScalar(xOrVec, y ?? 0, z ?? 0);
3303
- else
3304
- assignVec3(this.diffuse, xOrVec);
3616
+ setColor(diffuse) {
3617
+ this.diffuse.copy(diffuse);
3305
3618
  return this;
3306
3619
  }
3307
3620
  setIntensity(intensity) {
3308
3621
  this.intensity = intensity;
3309
3622
  return this;
3310
3623
  }
3311
- getColor() {
3312
- return this.diffuse;
3624
+ getColor(target = new Vec3()) {
3625
+ return target.copy(this.diffuse);
3313
3626
  }
3314
3627
  get color() {
3315
3628
  return this.diffuse;
@@ -3319,4 +3632,19 @@ class AmbientLight {
3319
3632
  }
3320
3633
  }
3321
3634
 
3322
- export { AmbientLight, BasicMesh, BasicShader, Camera, Cannon, Cube, CustomMesh, Cylinder, DirectionalLight, FullscreenQuad, Hemisphere, Lights, Line, MAX_LIGHTS, Material, Mesh, MultiQuad, NormalShader, OrbitControls, OrthographicCamera, PerspectiveCamera, PhongShader, Plane, PointLight, Program, Quad, RenderTarget, Renderer, Scene, Shader, Sphere, SpotLight, SubMesh, Texture, Uniform, VWing, eDrawMethod };
3635
+ function assignVec3(target, xOrVec, y, z) {
3636
+ if (xOrVec instanceof Vec3)
3637
+ return target.copy(xOrVec);
3638
+ if (typeof xOrVec === 'number')
3639
+ return target.setScalar(xOrVec, y, z);
3640
+ return target.setArray(xOrVec);
3641
+ }
3642
+ function assignVec2(target, xOrVec, y) {
3643
+ if (xOrVec instanceof Vec2)
3644
+ return target.copy(xOrVec);
3645
+ if (typeof xOrVec === 'number')
3646
+ return target.setScalar(xOrVec, y);
3647
+ return target.setArray(xOrVec);
3648
+ }
3649
+
3650
+ export { AmbientLight, BasicMesh, BasicShader, Camera, Cannon, Cube, CustomMesh, Cylinder, DirectionalLight, FullscreenQuad, Hemisphere, Lights, Line, MAX_LIGHTS, Material, Mesh, MultiQuad, Node, NormalShader, OrbitControls, OrthographicCamera, PerspectiveCamera, PhongShader, Plane, PointLight, Program, Quad, RenderTarget, Renderer, Scene, Shader, Sphere, SpotLight, Sprite, SpriteShader, SubMesh, Texture, Uniform, VWing, assignVec2, assignVec3, eDrawMethod };