phoria2 1.1.0 → 1.3.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 +70 -13
- package/dist/bloom.js +4 -4
- package/dist/bloom.js.map +1 -1
- package/dist/charts.js +538 -508
- package/dist/charts.js.map +1 -1
- package/dist/core.js +4270 -3652
- package/dist/core.js.map +1 -1
- package/dist/edit.js +65 -65
- package/dist/edit.js.map +1 -1
- package/dist/explain.js +6 -2
- package/dist/explain.js.map +1 -1
- package/dist/gltf.js +116 -116
- 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 +188 -115
- package/dist/particles.js.map +1 -1
- package/dist/passes.js +29 -29
- package/dist/passes.js.map +1 -1
- package/dist/phoria2.js +1 -1
- package/dist/picking.js +32 -16
- package/dist/picking.js.map +1 -1
- package/dist/snapshot.js +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/types/OrbitControl.d.ts +6 -2
- package/dist/types/Raycaster.d.ts +10 -0
- package/dist/types/View.d.ts +70 -1
- package/dist/types/area.d.ts +18 -0
- package/dist/types/charts/Chart.d.ts +7 -0
- package/dist/types/explain.d.ts +3 -0
- package/dist/types/frame.d.ts +7 -0
- package/dist/types/geometry/Geometry.d.ts +24 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/keyboard.d.ts +48 -0
- package/dist/types/labels.d.ts +4 -0
- package/dist/types/material/Material.d.ts +15 -0
- package/dist/types/material/Texture.d.ts +18 -2
- package/dist/types/particles/Emitter.d.ts +39 -2
- package/dist/types/scene/Node.d.ts +19 -14
- package/dist/types/scene/drawables.d.ts +54 -15
- package/dist/types/scene/lights.d.ts +16 -2
- package/dist/types/shadow.d.ts +2 -1
- package/dist/types/types.d.ts +14 -0
- package/dist/types/value/Color.d.ts +0 -4
- package/dist/types/value/Quat.d.ts +4 -2
- package/dist/types/webgl/resources.d.ts +12 -2
- package/dist/types/webgl/shaders.d.ts +7 -2
- package/llms-full.txt +106 -10
- package/package.json +4 -1
- package/schema/scene.schema.json +67 -1
- package/src/OrbitControl.js +7 -3
- package/src/Raycaster.js +58 -0
- package/src/View.js +220 -41
- package/src/area.js +131 -0
- package/src/charts/Chart.js +20 -2
- package/src/charts/marks.js +29 -2
- package/src/explain.js +6 -2
- package/src/frame.js +29 -9
- package/src/geometry/Geometry.js +32 -4
- package/src/index.js +1 -0
- package/src/keyboard.js +298 -0
- package/src/labels.js +4 -1
- package/src/material/Material.js +9 -2
- package/src/material/Texture.js +55 -2
- package/src/particles/Emitter.js +121 -10
- package/src/scene/Node.js +20 -9
- package/src/scene/Scene.js +2 -0
- package/src/scene/cameras.js +4 -1
- package/src/scene/drawables.js +115 -15
- package/src/scene/lights.js +40 -6
- package/src/shadow.js +27 -4
- package/src/types.js +6 -0
- package/src/value/Color.js +21 -0
- package/src/value/Quat.js +15 -6
- package/src/watch.js +14 -2
- package/src/webgl/WebGLRenderer.js +36 -5
- package/src/webgl/picking.js +22 -1
- package/src/webgl/resources.js +168 -14
- package/src/webgl/shaders.js +110 -11
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)
|
|
@@ -415,7 +461,7 @@ export class Resources {
|
|
|
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
|
+
}
|
package/src/webgl/shaders.js
CHANGED
|
@@ -12,12 +12,18 @@ export const ATTRIBUTES = {
|
|
|
12
12
|
instanceColor: 8,
|
|
13
13
|
tangent: 9, // brushed metal's direction: along the UVs' u, and which way their v runs
|
|
14
14
|
uv2: 10, // a second UV set, for baked occlusion and light maps
|
|
15
|
+
// points drawn as squares (sprites): each one's size, opacity and angle, and which point it is
|
|
16
|
+
pointSize: 11,
|
|
17
|
+
pointOpacity: 12,
|
|
18
|
+
pointAngle: 13,
|
|
19
|
+
pointIndex: 14,
|
|
15
20
|
// lines: per-segment endpoints and colours, and the corner of the quad being drawn
|
|
16
21
|
segmentStart: 0,
|
|
17
22
|
segmentEnd: 1,
|
|
18
23
|
segmentColorStart: 2,
|
|
19
24
|
segmentColorEnd: 3,
|
|
20
25
|
corner: 4,
|
|
26
|
+
segmentInfo: 5, // how far along its chain the segment starts, and whether it starts or ends one
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
// a section plane: fragments on its far side are cut away
|
|
@@ -957,6 +963,17 @@ void main() {
|
|
|
957
963
|
export const POINTS_VERTEX = /* glsl */ `
|
|
958
964
|
layout(location = 0) in vec3 position;
|
|
959
965
|
layout(location = 3) in vec3 color;
|
|
966
|
+
#ifdef SPRITES
|
|
967
|
+
// each point a square: one instance a point, drawn as a strip over the corners (along 0-1, across -1-1)
|
|
968
|
+
layout(location = 4) in vec2 corner;
|
|
969
|
+
layout(location = 11) in float pointSize;
|
|
970
|
+
layout(location = 12) in float pointOpacity;
|
|
971
|
+
layout(location = 13) in float pointAngle;
|
|
972
|
+
layout(location = 14) in float pointIndex;
|
|
973
|
+
uniform vec2 uResolution; // drawing buffer size in pixels
|
|
974
|
+
out vec2 vUv;
|
|
975
|
+
out float vOpacity;
|
|
976
|
+
#endif
|
|
960
977
|
|
|
961
978
|
uniform mat4 uModel;
|
|
962
979
|
uniform mat4 uView;
|
|
@@ -978,11 +995,34 @@ ${COMMON}
|
|
|
978
995
|
|
|
979
996
|
void main() {
|
|
980
997
|
vec4 viewPosition = uView * (uModel * vec4(position, 1.0));
|
|
998
|
+
#ifdef SPRITES
|
|
999
|
+
// the square's corner, turned by the point's angle: the texture turns with it
|
|
1000
|
+
vec2 c = vec2(corner.x * 2.0 - 1.0, corner.y);
|
|
1001
|
+
float s = sin(pointAngle), k = cos(pointAngle);
|
|
1002
|
+
vec2 turned = vec2(c.x * k - c.y * s, c.x * s + c.y * k);
|
|
1003
|
+
float size = uSize * pointSize;
|
|
1004
|
+
#ifdef SIZE_ATTENUATION
|
|
1005
|
+
// world units: the square sits in the view, facing the camera
|
|
1006
|
+
viewPosition.xy += turned * size * 0.5;
|
|
1007
|
+
gl_Position = uProjection * viewPosition;
|
|
1008
|
+
#else
|
|
1009
|
+
// pixels: the square is laid out on the screen
|
|
981
1010
|
gl_Position = uProjection * viewPosition;
|
|
1011
|
+
gl_Position.xy += turned * size / uResolution * gl_Position.w;
|
|
1012
|
+
#endif
|
|
1013
|
+
// the image upright on the square, its top at the top, as a point's is
|
|
1014
|
+
vUv = vec2(c.x * 0.5 + 0.5, 0.5 - c.y * 0.5);
|
|
1015
|
+
vOpacity = pointOpacity;
|
|
1016
|
+
int self = int(pointIndex + 0.5);
|
|
1017
|
+
#else
|
|
1018
|
+
gl_Position = uProjection * viewPosition;
|
|
1019
|
+
int self = gl_VertexID;
|
|
1020
|
+
#endif
|
|
982
1021
|
#ifdef FOG
|
|
983
1022
|
vFogDepth = -viewPosition.z;
|
|
984
1023
|
#endif
|
|
985
|
-
#ifdef
|
|
1024
|
+
#ifdef SPRITES
|
|
1025
|
+
#elif defined(SIZE_ATTENUATION)
|
|
986
1026
|
// perspective shrinks with distance; an orthographic projection (w row 0, 0, 0, 1) doesn't
|
|
987
1027
|
float depth = uProjection[3][3] == 1.0 ? 1.0 : max(-viewPosition.z, 1e-4);
|
|
988
1028
|
gl_PointSize = uSize * uScale / depth;
|
|
@@ -993,9 +1033,9 @@ void main() {
|
|
|
993
1033
|
#ifdef VERTEX_COLORS
|
|
994
1034
|
vColor = srgbToLinear(color);
|
|
995
1035
|
#endif
|
|
996
|
-
vHighlight = uHighlightIndex == -1 ||
|
|
1036
|
+
vHighlight = uHighlightIndex == -1 || self == uHighlightIndex ? 1.0 : 0.0;
|
|
997
1037
|
#ifdef PICK
|
|
998
|
-
vPickElement = uint(
|
|
1038
|
+
vPickElement = uint(self);
|
|
999
1039
|
#endif
|
|
1000
1040
|
}
|
|
1001
1041
|
`
|
|
@@ -1004,6 +1044,10 @@ const POINTS_FRAGMENT = /* glsl */ `
|
|
|
1004
1044
|
precision highp float;
|
|
1005
1045
|
in vec3 vColor;
|
|
1006
1046
|
flat in float vHighlight;
|
|
1047
|
+
#ifdef SPRITES
|
|
1048
|
+
in vec2 vUv;
|
|
1049
|
+
in float vOpacity;
|
|
1050
|
+
#endif
|
|
1007
1051
|
uniform vec4 uHighlight;
|
|
1008
1052
|
uniform vec3 uColor;
|
|
1009
1053
|
uniform float uOpacity;
|
|
@@ -1016,12 +1060,18 @@ ${COMMON}
|
|
|
1016
1060
|
${FOG}
|
|
1017
1061
|
|
|
1018
1062
|
void main() {
|
|
1063
|
+
#ifdef SPRITES
|
|
1064
|
+
vec2 at = vUv;
|
|
1065
|
+
vec4 base = vec4(uColor * vColor, uOpacity * vOpacity);
|
|
1066
|
+
#else
|
|
1067
|
+
vec2 at = gl_PointCoord;
|
|
1019
1068
|
vec4 base = vec4(uColor * vColor, uOpacity);
|
|
1069
|
+
#endif
|
|
1020
1070
|
#ifdef MAP
|
|
1021
|
-
base *= texture(uMap,
|
|
1071
|
+
base *= texture(uMap, at);
|
|
1022
1072
|
#else
|
|
1023
1073
|
// round points with a one pixel soft edge
|
|
1024
|
-
vec2 p =
|
|
1074
|
+
vec2 p = at * 2.0 - 1.0;
|
|
1025
1075
|
float r = dot(p, p);
|
|
1026
1076
|
float edge = fwidth(r);
|
|
1027
1077
|
base.a *= 1.0 - smoothstep(1.0 - edge, 1.0, r);
|
|
@@ -1037,7 +1087,9 @@ layout(location = 0) in vec3 segmentStart;
|
|
|
1037
1087
|
layout(location = 1) in vec3 segmentEnd;
|
|
1038
1088
|
layout(location = 2) in vec3 segmentColorStart;
|
|
1039
1089
|
layout(location = 3) in vec3 segmentColorEnd;
|
|
1040
|
-
|
|
1090
|
+
// the distance along its chain (segments joined end to start) where it starts, in the model's
|
|
1091
|
+
// units; 1 if it starts a chain; 1 if it ends one
|
|
1092
|
+
layout(location = 5) in vec3 segmentInfo;
|
|
1041
1093
|
|
|
1042
1094
|
uniform mat4 uModel;
|
|
1043
1095
|
uniform mat4 uView;
|
|
@@ -1045,8 +1097,18 @@ uniform mat4 uProjection;
|
|
|
1045
1097
|
uniform vec2 uResolution; // drawing buffer size in pixels
|
|
1046
1098
|
uniform float uWidth; // pixels, already scaled by the pixel ratio
|
|
1047
1099
|
uniform float uNear;
|
|
1100
|
+
uniform int uArrows; // arrowheads: 1 at the start of each chain, 2 at its end, 3 at both
|
|
1101
|
+
uniform float uArrowSize; // their length in pixels, scaled by the pixel ratio
|
|
1102
|
+
|
|
1103
|
+
// six vertices an instance make the segment's quad, as two triangles - x: 0 at its start, 1 at its
|
|
1104
|
+
// end; y: -1 or 1 across - and six more its two arrowheads, drawn only when asked for
|
|
1105
|
+
const vec2 QUAD[6] = vec2[6](vec2(0.0, -1.0), vec2(0.0, 1.0), vec2(1.0, -1.0), vec2(1.0, -1.0), vec2(0.0, 1.0), vec2(1.0, 1.0));
|
|
1048
1106
|
|
|
1049
1107
|
out vec3 vColor;
|
|
1108
|
+
#ifdef DASHED
|
|
1109
|
+
out float vDistance; // along the chain, in the model's units
|
|
1110
|
+
flat out float vArrow; // 1 on an arrowhead, which is never dashed
|
|
1111
|
+
#endif
|
|
1050
1112
|
#ifdef PICK
|
|
1051
1113
|
flat out uint vPickElement; // which instance, point or segment, for GPU picking
|
|
1052
1114
|
#endif
|
|
@@ -1077,21 +1139,48 @@ void main() {
|
|
|
1077
1139
|
dir = len > 0.0 ? dir / len : vec2(1.0, 0.0);
|
|
1078
1140
|
vec2 normal = vec2(-dir.y, dir.x);
|
|
1079
1141
|
|
|
1080
|
-
|
|
1142
|
+
bool arrowStart = (uArrows & 1) != 0 && segmentInfo.y > 0.5;
|
|
1143
|
+
bool arrowEnd = (uArrows & 2) != 0 && segmentInfo.z > 0.5;
|
|
1144
|
+
float along, arrow = 0.0;
|
|
1145
|
+
vec2 offset;
|
|
1146
|
+
if (gl_VertexID < 6) {
|
|
1147
|
+
vec2 c = QUAD[gl_VertexID];
|
|
1148
|
+
along = c.x;
|
|
1149
|
+
// past each end by half the width so joins look continuous - or short of it, under an arrowhead
|
|
1150
|
+
float startOut = arrowStart ? -uArrowSize * 0.8 : uWidth * 0.5;
|
|
1151
|
+
float endOut = arrowEnd ? -uArrowSize * 0.8 : uWidth * 0.5;
|
|
1152
|
+
offset = normal * c.y * uWidth * 0.5 + dir * (along > 0.5 ? endOut : -startOut);
|
|
1153
|
+
} else {
|
|
1154
|
+
// an arrowhead: its tip on the chain's end, pointing out along the segment
|
|
1155
|
+
int which = (gl_VertexID - 6) / 3, k = gl_VertexID - 6 - which * 3;
|
|
1156
|
+
if (which == 0 ? !arrowStart : !arrowEnd) {
|
|
1157
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
along = float(which);
|
|
1161
|
+
vec2 d = which == 1 ? dir : -dir;
|
|
1162
|
+
float wing = uArrowSize * 0.45;
|
|
1163
|
+
offset = k == 0 ? vec2(0.0) : -d * uArrowSize + normal * (k == 1 ? wing : -wing);
|
|
1164
|
+
arrow = 1.0;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
vec4 clip = mix(ca, cb, along);
|
|
1081
1168
|
#ifdef FOG
|
|
1082
|
-
vFogDepth = -mix(a.z, b.z,
|
|
1169
|
+
vFogDepth = -mix(a.z, b.z, along);
|
|
1083
1170
|
#endif
|
|
1084
|
-
// extend past each end by half the width so joins look continuous
|
|
1085
|
-
vec2 offset = normal * corner.y * uWidth * 0.5 + dir * (corner.x * 2.0 - 1.0) * uWidth * 0.5;
|
|
1086
1171
|
clip.xy += offset / (uResolution * 0.5) * clip.w;
|
|
1087
1172
|
gl_Position = clip;
|
|
1173
|
+
#ifdef DASHED
|
|
1174
|
+
vDistance = segmentInfo.x + length(segmentEnd - segmentStart) * along;
|
|
1175
|
+
vArrow = arrow;
|
|
1176
|
+
#endif
|
|
1088
1177
|
#ifdef PICK
|
|
1089
1178
|
vPickElement = uint(gl_InstanceID);
|
|
1090
1179
|
#endif
|
|
1091
1180
|
|
|
1092
1181
|
vColor = vec3(1.0);
|
|
1093
1182
|
#ifdef VERTEX_COLORS
|
|
1094
|
-
vColor = srgbToLinear(mix(segmentColorStart, segmentColorEnd,
|
|
1183
|
+
vColor = srgbToLinear(mix(segmentColorStart, segmentColorEnd, along));
|
|
1095
1184
|
#endif
|
|
1096
1185
|
}
|
|
1097
1186
|
`
|
|
@@ -1101,12 +1190,22 @@ precision highp float;
|
|
|
1101
1190
|
in vec3 vColor;
|
|
1102
1191
|
uniform vec3 uColor;
|
|
1103
1192
|
uniform float uOpacity;
|
|
1193
|
+
#ifdef DASHED
|
|
1194
|
+
in float vDistance;
|
|
1195
|
+
flat in float vArrow;
|
|
1196
|
+
uniform float uDash; // dash and gap lengths, in the model's units
|
|
1197
|
+
uniform float uGap;
|
|
1198
|
+
uniform float uDashOffset; // moves the dashes along: animate it for flow
|
|
1199
|
+
#endif
|
|
1104
1200
|
out vec4 fragColor;
|
|
1105
1201
|
|
|
1106
1202
|
${COMMON}
|
|
1107
1203
|
${FOG}
|
|
1108
1204
|
|
|
1109
1205
|
void main() {
|
|
1206
|
+
#ifdef DASHED
|
|
1207
|
+
if (vArrow < 0.5 && mod(vDistance - uDashOffset, uDash + uGap) >= uDash) discard;
|
|
1208
|
+
#endif
|
|
1110
1209
|
vec3 color = fogged(uColor * vColor);
|
|
1111
1210
|
fragColor = vec4(linearToSrgb(color) * uOpacity, uOpacity);
|
|
1112
1211
|
}
|