phoria2 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/bloom.js +4 -4
- package/dist/bloom.js.map +1 -1
- package/dist/charts.js +527 -510
- package/dist/charts.js.map +1 -1
- package/dist/core.js +1760 -1170
- package/dist/core.js.map +1 -1
- package/dist/debug.js +297 -0
- package/dist/debug.js.map +1 -0
- package/dist/edit.js +60 -60
- package/dist/edit.js.map +1 -1
- package/dist/explain.js +32 -13
- package/dist/explain.js.map +1 -1
- package/dist/gltf.js +155 -155
- package/dist/gltf.js.map +1 -1
- package/dist/keyboard.js +188 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/motion.js +35 -35
- package/dist/motion.js.map +1 -1
- package/dist/particles.js +187 -114
- package/dist/particles.js.map +1 -1
- package/dist/passes.js +116 -104
- package/dist/passes.js.map +1 -1
- package/dist/phoria2.js +1 -1
- package/dist/picking.js +95 -62
- package/dist/picking.js.map +1 -1
- package/dist/snapshot.js +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/types/View.d.ts +27 -0
- package/dist/types/area.d.ts +18 -0
- package/dist/types/debug.d.ts +74 -0
- package/dist/types/explain.d.ts +4 -0
- package/dist/types/frame.d.ts +5 -0
- package/dist/types/geometry/Geometry.d.ts +16 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/material/Material.d.ts +67 -0
- package/dist/types/material/Texture.d.ts +18 -2
- package/dist/types/particles/Emitter.d.ts +39 -2
- package/dist/types/scene/drawables.d.ts +23 -2
- package/dist/types/watch.d.ts +8 -1
- package/dist/types/webgl/hooks.d.ts +48 -0
- package/dist/types/webgl/passes.d.ts +4 -0
- package/dist/types/webgl/programs.d.ts +15 -5
- package/dist/types/webgl/resources.d.ts +12 -2
- package/dist/types/webgl/shaders.d.ts +41 -5
- package/llms-full.txt +275 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/schema/scene.schema.json +75 -0
- package/src/View.js +121 -7
- package/src/area.js +131 -0
- package/src/charts/marks.js +29 -2
- package/src/debug.js +283 -0
- package/src/explain.js +19 -0
- package/src/frame.js +7 -2
- package/src/geometry/Geometry.js +16 -1
- package/src/index.js +1 -0
- package/src/material/Material.js +130 -4
- package/src/material/Texture.js +55 -2
- package/src/particles/Emitter.js +121 -10
- package/src/scene/drawables.js +89 -8
- package/src/watch.js +26 -3
- package/src/webgl/WebGLRenderer.js +55 -7
- package/src/webgl/hooks.js +121 -0
- package/src/webgl/passes.js +17 -2
- package/src/webgl/picking.js +45 -3
- package/src/webgl/programs.js +20 -9
- package/src/webgl/resources.js +169 -15
- package/src/webgl/shaders.js +545 -38
package/src/webgl/resources.js
CHANGED
|
@@ -10,6 +10,9 @@ import { isVideo } from '../material/Texture.js'
|
|
|
10
10
|
|
|
11
11
|
// uploads bind textures here, out of the way of the units programs sample from
|
|
12
12
|
const UPLOAD_UNIT = 15
|
|
13
|
+
// past this many changed regions since the last upload, those in each cell of a GRID x GRID split of
|
|
14
|
+
// the image go as one rectangle round them - nearby changes together, far ones still apart
|
|
15
|
+
const MOST_REGIONS = 16, GRID = 4
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* @typedef {object} MeshBuffers
|
|
@@ -109,6 +112,11 @@ export function uvMatrix(t) {
|
|
|
109
112
|
|
|
110
113
|
// a quad as a triangle strip of [along, across] corners, shared by every Lines draw
|
|
111
114
|
const LINE_CORNERS = new Float32Array([0, -1, 0, 1, 1, -1, 1, 1])
|
|
115
|
+
// a point drawn as a square: position, colour, size, opacity, angle and index, as [location, floats,
|
|
116
|
+
// offset in floats]
|
|
117
|
+
const SPRITE_LAYOUT = [[ATTRIBUTES.position, 3, 0], [ATTRIBUTES.color, 3, 3], [ATTRIBUTES.pointSize, 1, 6],
|
|
118
|
+
[ATTRIBUTES.pointOpacity, 1, 7], [ATTRIBUTES.pointAngle, 1, 8], [ATTRIBUTES.pointIndex, 1, 9]]
|
|
119
|
+
const SPRITE_STRIDE = 10
|
|
112
120
|
|
|
113
121
|
export class Resources {
|
|
114
122
|
/** @type {WebGL2RenderingContext} */ gl
|
|
@@ -116,7 +124,8 @@ export class Resources {
|
|
|
116
124
|
/** @type {WeakMap<object, MeshBuffers>} */ #triangles = new WeakMap()
|
|
117
125
|
/** @type {WeakMap<object, MeshBuffers>} */ #points = new WeakMap()
|
|
118
126
|
/** @type {WeakMap<object, MeshBuffers>} */ #lines = new WeakMap()
|
|
119
|
-
/** @type {WeakMap<
|
|
127
|
+
/** Points drawn as squares: an instance a point. @type {WeakMap<object, MeshBuffers & { capacity: number, sorted: boolean, data: Float32Array }>} */ #sprites = new WeakMap()
|
|
128
|
+
/** The GPU copy of each texture: the version it holds, and the storage it was made with. @type {WeakMap<Texture, { texture: WebGLTexture, version: number, width: number, height: number, internal: number }>} */ #textures = new WeakMap()
|
|
120
129
|
/** @type {WebGLTexture | null} */ #white = null
|
|
121
130
|
/** @type {WebGLBuffer | null} */ #corners = null
|
|
122
131
|
/** Counts for renderer.info. */
|
|
@@ -284,8 +293,8 @@ export class Resources {
|
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
/**
|
|
287
|
-
* Vertex array for Lines: one instance per segment with its two endpoints (and colours)
|
|
288
|
-
* expanded to a screen-space quad in the vertex shader.
|
|
296
|
+
* Vertex array for Lines: one instance per segment with its two endpoints (and colours) and where
|
|
297
|
+
* it is along its chain, expanded to a screen-space quad - and arrowheads - in the vertex shader.
|
|
289
298
|
* @param {Geometry} geometry
|
|
290
299
|
* @returns {MeshBuffers}
|
|
291
300
|
*/
|
|
@@ -301,6 +310,9 @@ export class Resources {
|
|
|
301
310
|
const segments = geometry.elementCount / 2
|
|
302
311
|
const ends = new Float32Array(segments * 6)
|
|
303
312
|
const colors = new Float32Array(segments * 6).fill(1)
|
|
313
|
+
// chains: runs of segments each starting where the one before ended - a polyline. Dashes run
|
|
314
|
+
// on along a chain, and arrowheads go at its ends
|
|
315
|
+
const info = new Float32Array(segments * 3)
|
|
304
316
|
for (let s = 0; s < segments; s++) {
|
|
305
317
|
const i0 = idx ? idx[s * 2] : s * 2, i1 = idx ? idx[s * 2 + 1] : s * 2 + 1
|
|
306
318
|
for (let k = 0; k < 3; k++) {
|
|
@@ -311,6 +323,12 @@ export class Resources {
|
|
|
311
323
|
colors[s * 6 + 3 + k] = c[i1 * 3 + k]
|
|
312
324
|
}
|
|
313
325
|
}
|
|
326
|
+
const o = s * 6, joined = s > 0 && ends[o - 3] === ends[o] && ends[o - 2] === ends[o + 1] && ends[o - 1] === ends[o + 2]
|
|
327
|
+
if (joined) {
|
|
328
|
+
info[s * 3] = info[s * 3 - 3] + Math.hypot(ends[o - 3] - ends[o - 6], ends[o - 2] - ends[o - 5], ends[o - 1] - ends[o - 4])
|
|
329
|
+
info[s * 3 - 1] = 0
|
|
330
|
+
} else info[s * 3 + 1] = 1
|
|
331
|
+
info[s * 3 + 2] = 1
|
|
314
332
|
}
|
|
315
333
|
|
|
316
334
|
const vao = /** @type {WebGLVertexArrayObject} */ (gl.createVertexArray())
|
|
@@ -330,6 +348,13 @@ export class Resources {
|
|
|
330
348
|
}
|
|
331
349
|
interleavedPair(ends, ATTRIBUTES.segmentStart, ATTRIBUTES.segmentEnd)
|
|
332
350
|
interleavedPair(colors, ATTRIBUTES.segmentColorStart, ATTRIBUTES.segmentColorEnd)
|
|
351
|
+
const chains = /** @type {WebGLBuffer} */ (gl.createBuffer())
|
|
352
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, chains)
|
|
353
|
+
gl.bufferData(gl.ARRAY_BUFFER, info, gl.STATIC_DRAW)
|
|
354
|
+
gl.enableVertexAttribArray(ATTRIBUTES.segmentInfo)
|
|
355
|
+
gl.vertexAttribPointer(ATTRIBUTES.segmentInfo, 3, gl.FLOAT, false, 0, 0)
|
|
356
|
+
gl.vertexAttribDivisor(ATTRIBUTES.segmentInfo, 1)
|
|
357
|
+
buffers.push(chains)
|
|
333
358
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.#cornerBuffer())
|
|
334
359
|
gl.enableVertexAttribArray(ATTRIBUTES.corner)
|
|
335
360
|
gl.vertexAttribPointer(ATTRIBUTES.corner, 2, gl.FLOAT, false, 0, 0)
|
|
@@ -354,7 +379,7 @@ export class Resources {
|
|
|
354
379
|
// a video with no frame yet: what it showed before, or white - and try again next frame
|
|
355
380
|
if (isVideo(texture.image) && /** @type {HTMLVideoElement} */ (texture.image).readyState < 2) return entry?.texture ?? this.#whiteTexture()
|
|
356
381
|
if (!entry) {
|
|
357
|
-
entry = { texture: /** @type {WebGLTexture} */ (gl.createTexture()), version: -1 }
|
|
382
|
+
entry = { texture: /** @type {WebGLTexture} */ (gl.createTexture()), version: -1, width: 0, height: 0, internal: 0 }
|
|
358
383
|
this.#textures.set(texture, entry)
|
|
359
384
|
this.counts.textures++
|
|
360
385
|
texture.onDispose(() => this.#freeTexture(texture))
|
|
@@ -364,16 +389,37 @@ export class Resources {
|
|
|
364
389
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, texture.flipY)
|
|
365
390
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false)
|
|
366
391
|
const image = texture.image
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
392
|
+
const data = 'data' in image ? /** @type {DataImage} */ (image) : null
|
|
393
|
+
// raw pixels: bytes as usual, floats (HDR) as half floats, which filter everywhere; sRGB textures
|
|
394
|
+
// are stored as sRGB so sampling returns linear values, filtered correctly
|
|
395
|
+
const float = !!data && data.data instanceof Float32Array
|
|
396
|
+
const internal = float ? gl.RGBA16F : texture.colorSpace === 'srgb' ? gl.SRGB8_ALPHA8 : gl.RGBA8
|
|
397
|
+
const type = float ? gl.FLOAT : gl.UNSIGNED_BYTE
|
|
398
|
+
const [width, height] = sizeOf(image)
|
|
399
|
+
if (entry.version < 0 || entry.width !== width || entry.height !== height || entry.internal !== internal) {
|
|
400
|
+
// new storage: the first upload, or the image changed size or kind
|
|
401
|
+
if (data) gl.texImage2D(gl.TEXTURE_2D, 0, internal, width, height, 0, gl.RGBA, type, data.data)
|
|
402
|
+
else gl.texImage2D(gl.TEXTURE_2D, 0, internal, gl.RGBA, type, /** @type {TexImageSource} */ (image))
|
|
403
|
+
Object.assign(entry, { width, height, internal })
|
|
373
404
|
} else {
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
405
|
+
// the same storage, refilled: just the parts that changed, when the updates since said - a
|
|
406
|
+
// canvas drawn on a little at a time - or the whole image (a video's next frame). A flipped
|
|
407
|
+
// image's rows run the other way, so it goes whole
|
|
408
|
+
let regions = texture.flipY ? null : texture.changedSince(entry.version)
|
|
409
|
+
if (regions && regions.length > MOST_REGIONS) regions = byCell(regions, width, height)
|
|
410
|
+
for (const r of regions ?? [{ x: 0, y: 0, width, height }]) {
|
|
411
|
+
// inside the image: a region over its edge is cut there, one off it sends nothing
|
|
412
|
+
const x = Math.max(0, r.x), y = Math.max(0, r.y)
|
|
413
|
+
const w = Math.min(width, r.x + r.width) - x, h = Math.min(height, r.y + r.height) - y
|
|
414
|
+
if (!(w > 0 && h > 0)) continue
|
|
415
|
+
gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, x)
|
|
416
|
+
gl.pixelStorei(gl.UNPACK_SKIP_ROWS, y)
|
|
417
|
+
if (data) gl.pixelStorei(gl.UNPACK_ROW_LENGTH, width)
|
|
418
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, w, h, gl.RGBA, type, /** @type {any} */ (data ? data.data : image))
|
|
419
|
+
}
|
|
420
|
+
gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0)
|
|
421
|
+
gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0)
|
|
422
|
+
gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
|
377
423
|
}
|
|
378
424
|
const wrap = { repeat: gl.REPEAT, clamp: gl.CLAMP_TO_EDGE, mirror: gl.MIRRORED_REPEAT }[texture.wrap]
|
|
379
425
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap)
|
|
@@ -409,13 +455,13 @@ export class Resources {
|
|
|
409
455
|
if (!d.isDrawable) return
|
|
410
456
|
keys.add(d.geometry)
|
|
411
457
|
if ('instanceMatrices' in d) keys.add(d)
|
|
412
|
-
for (const [, t] of d.material.
|
|
458
|
+
for (const [, t] of d.material.allTextures) keys.add(t)
|
|
413
459
|
})
|
|
414
460
|
for (const key of keys) {
|
|
415
461
|
const users = this.#usedBy.get(key)
|
|
416
462
|
if (!users?.delete(owner) || users.size) continue
|
|
417
463
|
this.#usedBy.delete(key)
|
|
418
|
-
for (const cache of [this.#triangles, this.#points, this.#lines]) {
|
|
464
|
+
for (const cache of [this.#triangles, this.#points, this.#lines, this.#sprites]) {
|
|
419
465
|
const entry = cache.get(key)
|
|
420
466
|
if (!entry) continue
|
|
421
467
|
this.#free(entry)
|
|
@@ -434,6 +480,64 @@ export class Resources {
|
|
|
434
480
|
users.add(this.owner)
|
|
435
481
|
}
|
|
436
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Vertex array for points drawn as squares: one instance a point - its position, colour, size,
|
|
485
|
+
* opacity, angle and index - over the corner strip lines use. Given a model-view matrix, the points
|
|
486
|
+
* go back to front (furthest first) - re-sorted every call, as the camera may have moved; without
|
|
487
|
+
* one, in their own order; with undefined, in whatever order they were last sent (picking, which
|
|
488
|
+
* reads each point's index and doesn't mind).
|
|
489
|
+
* @param {Geometry} geometry @param {ArrayLike<number> | null} [modelView]
|
|
490
|
+
* @returns {MeshBuffers}
|
|
491
|
+
*/
|
|
492
|
+
sprites(geometry, modelView) {
|
|
493
|
+
this.#mark(geometry)
|
|
494
|
+
const gl = this.gl
|
|
495
|
+
let entry = this.#sprites.get(geometry)
|
|
496
|
+
const sorting = !!modelView
|
|
497
|
+
if (entry && entry.version === geometry.version && !sorting && (modelView === undefined || !entry.sorted)) return entry
|
|
498
|
+
const n = geometry.drawnVertices, count = geometry.vertexCount
|
|
499
|
+
if (!entry || entry.capacity < count) {
|
|
500
|
+
if (entry) this.#free(entry)
|
|
501
|
+
else this.#watch(geometry, geometry, this.#sprites)
|
|
502
|
+
const vao = /** @type {WebGLVertexArrayObject} */ (gl.createVertexArray())
|
|
503
|
+
gl.bindVertexArray(vao)
|
|
504
|
+
const buffer = /** @type {WebGLBuffer} */ (gl.createBuffer())
|
|
505
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
506
|
+
gl.bufferData(gl.ARRAY_BUFFER, count * SPRITE_STRIDE * 4, gl.DYNAMIC_DRAW)
|
|
507
|
+
for (const [location, size, offset] of SPRITE_LAYOUT) {
|
|
508
|
+
gl.enableVertexAttribArray(location)
|
|
509
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, SPRITE_STRIDE * 4, offset * 4)
|
|
510
|
+
gl.vertexAttribDivisor(location, 1)
|
|
511
|
+
}
|
|
512
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.#cornerBuffer())
|
|
513
|
+
gl.enableVertexAttribArray(ATTRIBUTES.corner)
|
|
514
|
+
gl.vertexAttribPointer(ATTRIBUTES.corner, 2, gl.FLOAT, false, 0, 0)
|
|
515
|
+
gl.bindVertexArray(null)
|
|
516
|
+
entry = { vao, buffers: [buffer], count: 0, indexType: 0, geometry, version: -1, instanceVersion: 0, capacity: count, sorted: false, data: new Float32Array(count * SPRITE_STRIDE) }
|
|
517
|
+
this.#sprites.set(geometry, entry)
|
|
518
|
+
}
|
|
519
|
+
const order = sorting ? backToFront(geometry.positions, n, /** @type {ArrayLike<number>} */ (modelView)) : null
|
|
520
|
+
const p = geometry.positions, c = geometry.colors, sizes = geometry.sizes, opacities = geometry.opacities, angles = geometry.angles
|
|
521
|
+
const data = entry.data
|
|
522
|
+
for (let j = 0; j < n; j++) {
|
|
523
|
+
const i = order ? order[j] : j, o = j * SPRITE_STRIDE
|
|
524
|
+
data[o] = p[i * 3]
|
|
525
|
+
data[o + 1] = p[i * 3 + 1]
|
|
526
|
+
data[o + 2] = p[i * 3 + 2]
|
|
527
|
+
data[o + 3] = c ? c[i * 3] : 1
|
|
528
|
+
data[o + 4] = c ? c[i * 3 + 1] : 1
|
|
529
|
+
data[o + 5] = c ? c[i * 3 + 2] : 1
|
|
530
|
+
data[o + 6] = sizes ? sizes[i] : 1
|
|
531
|
+
data[o + 7] = opacities ? opacities[i] : 1
|
|
532
|
+
data[o + 8] = angles ? angles[i] : 0
|
|
533
|
+
data[o + 9] = i
|
|
534
|
+
}
|
|
535
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffers[0])
|
|
536
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, data, 0, n * SPRITE_STRIDE)
|
|
537
|
+
Object.assign(entry, { count: n, version: geometry.version, sorted: sorting })
|
|
538
|
+
return entry
|
|
539
|
+
}
|
|
540
|
+
|
|
437
541
|
/** @param {Texture} texture */
|
|
438
542
|
#freeTexture(texture) {
|
|
439
543
|
const entry = this.#textures.get(texture)
|
|
@@ -448,6 +552,7 @@ export class Resources {
|
|
|
448
552
|
this.#triangles = new WeakMap()
|
|
449
553
|
this.#points = new WeakMap()
|
|
450
554
|
this.#lines = new WeakMap()
|
|
555
|
+
this.#sprites = new WeakMap()
|
|
451
556
|
this.#textures = new WeakMap()
|
|
452
557
|
this.#usedBy = new WeakMap()
|
|
453
558
|
this.#white = null
|
|
@@ -497,3 +602,52 @@ export class Resources {
|
|
|
497
602
|
return this.#corners
|
|
498
603
|
}
|
|
499
604
|
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* An image's size in pixels - a video's frames, an <img>'s own pixels rather than its size on the page.
|
|
608
|
+
* @param {any} image @returns {[number, number]}
|
|
609
|
+
*/
|
|
610
|
+
function sizeOf(image) {
|
|
611
|
+
if (isVideo(image)) return [image.videoWidth, image.videoHeight]
|
|
612
|
+
if ('naturalWidth' in image) return [image.naturalWidth, image.naturalHeight]
|
|
613
|
+
return [image.width, image.height]
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Many regions as a few: those whose middles fall in the same cell of a GRID x GRID split of the
|
|
618
|
+
* image, as one rectangle round them.
|
|
619
|
+
* @param {import('../material/Texture.js').TextureRegion[]} regions @param {number} width @param {number} height
|
|
620
|
+
*/
|
|
621
|
+
function byCell(regions, width, height) {
|
|
622
|
+
/** @type {Map<number, number[]>} x0, y0, x1, y1 round each cell's regions */
|
|
623
|
+
const cells = new Map()
|
|
624
|
+
for (const r of regions) {
|
|
625
|
+
const cx = Math.min(GRID - 1, Math.max(0, Math.floor((r.x + r.width / 2) / width * GRID)))
|
|
626
|
+
const cy = Math.min(GRID - 1, Math.max(0, Math.floor((r.y + r.height / 2) / height * GRID)))
|
|
627
|
+
const key = cy * GRID + cx, box = cells.get(key)
|
|
628
|
+
if (!box) cells.set(key, [r.x, r.y, r.x + r.width, r.y + r.height])
|
|
629
|
+
else {
|
|
630
|
+
box[0] = Math.min(box[0], r.x)
|
|
631
|
+
box[1] = Math.min(box[1], r.y)
|
|
632
|
+
box[2] = Math.max(box[2], r.x + r.width)
|
|
633
|
+
box[3] = Math.max(box[3], r.y + r.height)
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return [...cells.values()].map(([x0, y0, x1, y1]) => ({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 }))
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* The first n points' order furthest from the camera first: by their depth in view, given the
|
|
641
|
+
* model-view matrix.
|
|
642
|
+
* @param {Float32Array} p @param {number} n @param {ArrayLike<number>} m
|
|
643
|
+
* @returns {Uint32Array}
|
|
644
|
+
*/
|
|
645
|
+
function backToFront(p, n, m) {
|
|
646
|
+
const depth = new Float32Array(n), order = new Uint32Array(n)
|
|
647
|
+
for (let i = 0; i < n; i++) {
|
|
648
|
+
// view space z: more negative is further away
|
|
649
|
+
depth[i] = m[2] * p[i * 3] + m[6] * p[i * 3 + 1] + m[10] * p[i * 3 + 2] + m[14]
|
|
650
|
+
order[i] = i
|
|
651
|
+
}
|
|
652
|
+
return order.sort((a, b) => depth[a] - depth[b])
|
|
653
|
+
}
|