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/scene/lights.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Color } from '../value/Color.js'
|
|
2
2
|
import { Vec3 } from '../value/Vec3.js'
|
|
3
|
-
import {
|
|
3
|
+
import { Box3 } from '../value/Box3.js'
|
|
4
|
+
import { checkNumber, describe, fail } from '../validate.js'
|
|
4
5
|
import { NODE_OPTIONS, Node, inParentSpace, setPoint } from './Node.js'
|
|
5
6
|
|
|
6
7
|
/** @import { NodeOptions } from './Node.js' */
|
|
7
|
-
/** @import { ColorInput, Vec3Input } from '../types.js' */
|
|
8
|
+
/** @import { Box3Input, ColorInput, Vec3Input } from '../types.js' */
|
|
8
9
|
|
|
9
10
|
/** @typedef {NodeOptions & { color?: ColorInput, intensity?: number }} LightOptions */
|
|
10
11
|
/** @typedef {'ambient' | 'hemisphere' | 'directional' | 'point' | 'spot'} LightKind */
|
|
@@ -189,24 +190,57 @@ export class DirectionalLight extends AimedLight {
|
|
|
189
190
|
static type = 'directional-light'
|
|
190
191
|
/** @internal @type {LightKind} */
|
|
191
192
|
static kind = 'directional'
|
|
192
|
-
static get OPTIONS() { return [...LIGHT_OPTIONS, 'direction', 'target', 'castShadow'] }
|
|
193
|
+
static get OPTIONS() { return [...LIGHT_OPTIONS, 'direction', 'target', 'castShadow', 'shadowBounds'] }
|
|
193
194
|
|
|
194
195
|
castShadow = false
|
|
196
|
+
/** @type {Box3 | null} */
|
|
197
|
+
#shadowBounds = null
|
|
195
198
|
|
|
196
|
-
/** @param {AimedLightOptions & { castShadow?: boolean }} [options] */
|
|
199
|
+
/** @param {AimedLightOptions & { castShadow?: boolean, shadowBounds?: Box3Input | null }} [options] */
|
|
197
200
|
constructor(options = {}) {
|
|
198
201
|
super(options)
|
|
199
202
|
if (options.castShadow !== undefined) this.castShadow = !!options.castShadow
|
|
203
|
+
if (options.shadowBounds !== undefined) this.#setShadowBounds(options.shadowBounds)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The part of the world, in world units, that has shadows - or null (the default) to follow
|
|
208
|
+
* whatever casts them. The shadow map is fitted to what casts every frame, so one caster flung far
|
|
209
|
+
* off spreads its texels over everything and every shadow blurs; set the area where things happen
|
|
210
|
+
* and its shadows stay sharp. Casters outside it still shadow it, from wherever they stand.
|
|
211
|
+
* Assigning copies.
|
|
212
|
+
* @returns {Box3 | null}
|
|
213
|
+
*/
|
|
214
|
+
get shadowBounds() { return this.#shadowBounds }
|
|
215
|
+
|
|
216
|
+
/** @param {Box3Input | null} v */
|
|
217
|
+
set shadowBounds(v) { this.#setShadowBounds(v) }
|
|
218
|
+
|
|
219
|
+
/** @param {Box3Input | null} v */
|
|
220
|
+
#setShadowBounds(v) {
|
|
221
|
+
if (v === null) {
|
|
222
|
+
this.#shadowBounds = null
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
const b = /** @type {any} */ (v)
|
|
226
|
+
if (!b || typeof b !== 'object' || !b.min || !b.max) {
|
|
227
|
+
fail(this.constructor.name, `'shadowBounds' is a box - { min: [x, y, z], max: [x, y, z] } or a Box3 - or null, got ${describe(v)}`)
|
|
228
|
+
}
|
|
229
|
+
const box = new Box3(Vec3.fromInput(b.min, this.constructor.name), Vec3.fromInput(b.max, this.constructor.name))
|
|
230
|
+
if (box.isEmpty()) fail(this.constructor.name, `'shadowBounds' min must not be above max on any axis, got ${describe(v)}`)
|
|
231
|
+
this.#shadowBounds = box
|
|
200
232
|
}
|
|
201
233
|
|
|
202
234
|
/** @internal @returns {Record<string, unknown>} */
|
|
203
235
|
cloneOptions() {
|
|
204
|
-
return { ...super.cloneOptions(), castShadow: this.castShadow }
|
|
236
|
+
return { ...super.cloneOptions(), castShadow: this.castShadow, shadowBounds: this.#shadowBounds }
|
|
205
237
|
}
|
|
206
238
|
|
|
207
239
|
/** @internal @returns {Record<string, unknown>} */
|
|
208
240
|
jsonOptions() {
|
|
209
|
-
|
|
241
|
+
const b = this.#shadowBounds
|
|
242
|
+
return { ...super.jsonOptions(), ...(this.castShadow ? { castShadow: true } : {}),
|
|
243
|
+
...(b ? { shadowBounds: { min: Array.from(b.min), max: Array.from(b.max) } } : {}) }
|
|
210
244
|
}
|
|
211
245
|
}
|
|
212
246
|
|
package/src/shadow.js
CHANGED
|
@@ -23,9 +23,10 @@ export const SHADOW_MAP_SIZE = 2048
|
|
|
23
23
|
* @param {Box3[]} casters world bounds
|
|
24
24
|
* @param {Box3[]} receivers world bounds
|
|
25
25
|
* @param {number} [size] map size in texels
|
|
26
|
+
* @param {Box3 | null} [area] the light's shadowBounds: the map covers this across, whatever casts
|
|
26
27
|
* @returns {ShadowFit | null}
|
|
27
28
|
*/
|
|
28
|
-
export function fitShadow(direction, casters, receivers, size = SHADOW_MAP_SIZE) {
|
|
29
|
+
export function fitShadow(direction, casters, receivers, size = SHADOW_MAP_SIZE, area = null) {
|
|
29
30
|
const boxes = casters.filter((b) => !b.isEmpty())
|
|
30
31
|
if (!boxes.length) return null
|
|
31
32
|
const d = normalized(direction)
|
|
@@ -50,7 +51,10 @@ export function fitShadow(direction, casters, receivers, size = SHADOW_MAP_SIZE)
|
|
|
50
51
|
if (z > z1) z1 = z
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
+
// across the light's view: the area it was given, or else the casters; in depth, everything that
|
|
55
|
+
// casts - so a caster outside the area, between it and the light, still shadows it
|
|
56
|
+
if (area) corners(area, true)
|
|
57
|
+
for (const b of boxes) corners(b, !area)
|
|
54
58
|
// receivers only stretch the depth (view z is negative in front, so the far end is the minimum)
|
|
55
59
|
for (const b of receivers) if (!b.isEmpty()) corners(b, false)
|
|
56
60
|
|
|
@@ -58,10 +62,29 @@ export function fitShadow(direction, casters, receivers, size = SHADOW_MAP_SIZE)
|
|
|
58
62
|
const margin = Math.max(x1 - x0, y1 - y0, 1e-3) * 0.02
|
|
59
63
|
x0 -= margin; x1 += margin; y0 -= margin; y1 += margin
|
|
60
64
|
const depthMargin = Math.max(z1 - z0, 1e-3) * 0.01
|
|
61
|
-
|
|
65
|
+
z0 -= depthMargin; z1 += depthMargin
|
|
66
|
+
|
|
67
|
+
// steady while things move: the map grows and shrinks in small steps rather than with every change
|
|
68
|
+
// in the casters' extent, and its edges sit on the texel grid, so a shadow that isn't moving falls
|
|
69
|
+
// on the same texels every frame, instead of shimmering as the fit slides about under it
|
|
70
|
+
const w = steps(x1 - x0), h = steps(y1 - y0)
|
|
71
|
+
const tx = w / size, ty = h / size
|
|
72
|
+
x0 = Math.floor((x0 + x1 - w) / 2 / tx) * tx
|
|
73
|
+
y0 = Math.floor((y0 + y1 - h) / 2 / ty) * ty
|
|
74
|
+
// the depth range too, which sets what each stored depth means
|
|
75
|
+
const dz = steps(z1 - z0) / 64
|
|
76
|
+
z0 = Math.floor(z0 / dz) * dz
|
|
77
|
+
z1 = Math.ceil(z1 / dz) * dz
|
|
78
|
+
|
|
79
|
+
const projection = mat4.ortho(mat4.create(), x0, x0 + w, y0, y0 + h, -z1, -z0)
|
|
62
80
|
const viewProjection = new Mat4()
|
|
63
81
|
mat4.multiply(viewProjection, projection, view)
|
|
64
|
-
return { viewProjection, texelSize: Math.max(
|
|
82
|
+
return { viewProjection, texelSize: Math.max(w, h) / size }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A length rounded up to the next of a ladder of sizes, sixteen to each doubling. @param {number} v */
|
|
86
|
+
function steps(v) {
|
|
87
|
+
return 2 ** (Math.ceil(Math.log2(Math.max(v, 1e-3)) * 16) / 16)
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
/** Spot shadows' map: four tiles of half its size, one a light. */
|
package/src/types.js
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
/** A vector as accepted on input: [x, y, z], { x, y, z } or a Vec3. @typedef {Vec3Like | XYZ} Vec3Input */
|
|
11
11
|
|
|
12
|
+
/** A quaternion as an object literal - as physics engines such as Rapier return them. @typedef {{ x: number, y: number, z: number, w: number }} XYZW */
|
|
13
|
+
|
|
14
|
+
/** A rotation quaternion as accepted on input: [x, y, z, w], { x, y, z, w } or a Quat. @typedef {ArrayLike<number> | XYZW} QuatInput */
|
|
15
|
+
|
|
16
|
+
/** A box as accepted on input: a Box3, or its lowest and highest corners. @typedef {import('./value/Box3.js').Box3 | { min: Vec3Input, max: Vec3Input }} Box3Input */
|
|
17
|
+
|
|
12
18
|
/**
|
|
13
19
|
* A colour as accepted on input, in sRGB: '#ff8800', '#f80', 0xff8800, 'rgb(255 136 0)',
|
|
14
20
|
* 'hsl(32 100% 50%)', a CSS colour name, or [r, g, b] with components 0-1.
|
package/src/value/Color.js
CHANGED
|
@@ -16,6 +16,11 @@ const HELP = "use '#rrggbb', a CSS name, 'rgb(...)', 'hsl(...)', 0xrrggbb or [r,
|
|
|
16
16
|
* An RGB colour. Input is sRGB, as everywhere in phoria2; the components are stored **linear**,
|
|
17
17
|
* ready for lighting maths. `toJSON()` gives sRGB '#rrggbb' back.
|
|
18
18
|
*/
|
|
19
|
+
// colour strings already worked out, as linear rgb - emptied when full, as a palette never fills it
|
|
20
|
+
/** @type {Map<string, number[]>} */
|
|
21
|
+
const PARSED = new Map()
|
|
22
|
+
const PARSED_MAX = 1024
|
|
23
|
+
|
|
19
24
|
export class Color extends Float32Array {
|
|
20
25
|
static get [Symbol.species]() { return Float32Array }
|
|
21
26
|
|
|
@@ -154,6 +159,22 @@ export class Color extends Float32Array {
|
|
|
154
159
|
|
|
155
160
|
/** @param {string} s @param {string} owner */
|
|
156
161
|
#setString(s, owner) {
|
|
162
|
+
// the same few strings come again and again - an instance's colour set every frame - so each is
|
|
163
|
+
// worked out once
|
|
164
|
+
const known = PARSED.get(s)
|
|
165
|
+
if (known) {
|
|
166
|
+
this[0] = known[0]
|
|
167
|
+
this[1] = known[1]
|
|
168
|
+
this[2] = known[2]
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
this.#parse(s, owner)
|
|
172
|
+
if (PARSED.size >= PARSED_MAX) PARSED.clear()
|
|
173
|
+
PARSED.set(s, [this[0], this[1], this[2]])
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @param {string} s @param {string} owner */
|
|
177
|
+
#parse(s, owner) {
|
|
157
178
|
const lower = s.toLowerCase()
|
|
158
179
|
if (lower.startsWith('#')) {
|
|
159
180
|
let hex = lower.slice(1)
|
package/src/value/Quat.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as quat from '../math/quat.js'
|
|
2
2
|
import { describe, fail } from '../validate.js'
|
|
3
3
|
|
|
4
|
+
/** @import { QuatInput } from '../types.js' */
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* A rotation quaternion [x, y, z, w]: a Float32Array(4) with accessors and chainable methods.
|
|
6
8
|
* A node's quaternion and its Euler `rotation` keep each other in sync through these setters;
|
|
@@ -61,13 +63,20 @@ export class Quat extends Float32Array {
|
|
|
61
63
|
return this.changed()
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
/** @param {
|
|
66
|
+
/** @param {QuatInput} q [x, y, z, w], or { x, y, z, w } as physics engines give them @param {string} [owner] @returns {this} */
|
|
65
67
|
copy(q, owner = 'Quat') {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const o = /** @type {any} */ (q)
|
|
69
|
+
if (o && typeof o === 'object' && o.length >= 4) {
|
|
70
|
+
this[0] = o[0]
|
|
71
|
+
this[1] = o[1]
|
|
72
|
+
this[2] = o[2]
|
|
73
|
+
this[3] = o[3]
|
|
74
|
+
} else if (o && typeof o === 'object' && 'x' in o && 'y' in o && 'z' in o && 'w' in o) {
|
|
75
|
+
this[0] = o.x
|
|
76
|
+
this[1] = o.y
|
|
77
|
+
this[2] = o.z
|
|
78
|
+
this[3] = o.w
|
|
79
|
+
} else fail(owner, `expected a quaternion [x, y, z, w] or { x, y, z, w }, got ${describe(q)}`)
|
|
71
80
|
return this.changed()
|
|
72
81
|
}
|
|
73
82
|
|
package/src/watch.js
CHANGED
|
@@ -123,7 +123,7 @@ export class ChangeWatch {
|
|
|
123
123
|
this.#push((node.handlersFor('click').length ? 1 : 0) | (node.handlersFor('hover').length ? 2 : 0) | (node.tooltip ? 4 : 0) | (node.onDrag ? 8 : 0) | (node.highlightWhole ? 16 : 0))
|
|
124
124
|
// what hovering tints it with - a different colour shows straight away on what is hovered
|
|
125
125
|
if (node.highlight) this.#pushAll(node.highlight)
|
|
126
|
-
else this.#push(-4)
|
|
126
|
+
else this.#push(node.highlight === false ? -5 : -4)
|
|
127
127
|
const outline = node.outline
|
|
128
128
|
if (outline) {
|
|
129
129
|
this.#pushAll(outline.color)
|
|
@@ -147,7 +147,11 @@ export class ChangeWatch {
|
|
|
147
147
|
this.#push(g.version)
|
|
148
148
|
this.#push(g.drawCount ?? -1)
|
|
149
149
|
this.#push(node.renderOrder)
|
|
150
|
-
this.#push((node.castShadow ? 1 : 0) | (node.receiveShadow ? 2 : 0))
|
|
150
|
+
this.#push((node.castShadow ? 1 : 0) | (node.receiveShadow ? 2 : 0) | (node.sort ? 4 : 0))
|
|
151
|
+
if ('arrowBits' in node) {
|
|
152
|
+
this.#push(node.arrowBits)
|
|
153
|
+
this.#push(node.arrowSize)
|
|
154
|
+
}
|
|
151
155
|
// per-instance labels come from these, so swapping either redraws
|
|
152
156
|
this.#push(node.labels ? idOf(node.labels) : 0)
|
|
153
157
|
this.#push(node.data && typeof node.data === 'object' ? idOf(node.data) : 0)
|
|
@@ -164,6 +168,9 @@ export class ChangeWatch {
|
|
|
164
168
|
this.#push(m.metalness)
|
|
165
169
|
this.#push(m.size)
|
|
166
170
|
this.#push(m.width)
|
|
171
|
+
this.#push(m.dash)
|
|
172
|
+
this.#push(m.gap)
|
|
173
|
+
this.#push(m.dashOffset)
|
|
167
174
|
this.#push(m.alphaTest)
|
|
168
175
|
this.#push(m.normalScale)
|
|
169
176
|
this.#push(m.aoMapIntensity)
|
|
@@ -208,6 +215,7 @@ export class ChangeWatch {
|
|
|
208
215
|
this.#push(label.priority)
|
|
209
216
|
this.#push(label.occlude ? 1 : 0)
|
|
210
217
|
this.#push(label.leader ? 1 : 0)
|
|
218
|
+
this.#push(label.minScale)
|
|
211
219
|
}
|
|
212
220
|
|
|
213
221
|
/** @param {any} light */
|
|
@@ -218,6 +226,10 @@ export class ChangeWatch {
|
|
|
218
226
|
if (light.direction) this.#pushAll(light.direction)
|
|
219
227
|
if (light.target) this.#pushAll(light.target)
|
|
220
228
|
if (light.castShadow) this.#push(1)
|
|
229
|
+
if (light.shadowBounds) {
|
|
230
|
+
this.#pushAll(light.shadowBounds.min)
|
|
231
|
+
this.#pushAll(light.shadowBounds.max)
|
|
232
|
+
}
|
|
221
233
|
if ('range' in light) this.#push(light.range)
|
|
222
234
|
if ('angle' in light) {
|
|
223
235
|
this.#push(light.angle)
|
|
@@ -117,6 +117,8 @@ export class WebGLRenderer {
|
|
|
117
117
|
/** This frame's prefiltered environment, when it is an HDR image. @type {Environment | null} */ #environment = null
|
|
118
118
|
/** This frame's shadows - directional, spot, point - for the meshes that receive them. @type {import('./passes.js').ShadowMap[]} */ #shadows = []
|
|
119
119
|
/** @type {Float32Array} */ #normalMatrix = new Float32Array(9)
|
|
120
|
+
/** Model-view for sorting points back to front. */
|
|
121
|
+
#modelView = new Float32Array(16)
|
|
120
122
|
/** @type {Record<string, any>} */ #lights = {}
|
|
121
123
|
/** This frame waits for shaders to compile rather than leaving things out. */
|
|
122
124
|
#wait = false
|
|
@@ -407,6 +409,7 @@ export class WebGLRenderer {
|
|
|
407
409
|
|
|
408
410
|
const clip = kind === 'mesh' ? item.clip : null
|
|
409
411
|
const defines = this.#defines(kind, material, geometry.colors !== null, !!instances, geometry.uvs2 !== null)
|
|
412
|
+
if (kind === 'points' && /** @type {any} */ (node).drawsSprites) defines.push('SPRITES')
|
|
410
413
|
for (const shadow of shadows) defines.push(shadow.define)
|
|
411
414
|
const fog = material.fog ? frame.fog : null
|
|
412
415
|
if (fog) {
|
|
@@ -531,9 +534,18 @@ export class WebGLRenderer {
|
|
|
531
534
|
if (kind === 'lines') {
|
|
532
535
|
gl.disable(gl.CULL_FACE)
|
|
533
536
|
program.set('uWidth', material.width * this.pixelRatio)
|
|
537
|
+
const arrows = /** @type {any} */ (node).arrowBits ?? 0
|
|
538
|
+
program.set('uArrows', arrows)
|
|
539
|
+
program.set('uArrowSize', /** @type {any} */ (node).arrowPixels?.(material.width) * this.pixelRatio || 0)
|
|
540
|
+
if (material.dash > 0) {
|
|
541
|
+
program.set('uDash', material.dash)
|
|
542
|
+
program.set('uGap', material.gap || material.dash)
|
|
543
|
+
program.set('uDashOffset', material.dashOffset)
|
|
544
|
+
}
|
|
534
545
|
const buffers = this.#gpu.resources.lines(geometry)
|
|
535
546
|
gl.bindVertexArray(buffers.vao)
|
|
536
|
-
|
|
547
|
+
// a quad a segment, and two arrowheads where there are any
|
|
548
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, arrows ? 12 : 6, Math.min(buffers.count, Math.floor(geometry.drawnCount / 2)))
|
|
537
549
|
this.info.drawCalls++
|
|
538
550
|
return
|
|
539
551
|
}
|
|
@@ -541,9 +553,27 @@ export class WebGLRenderer {
|
|
|
541
553
|
if (kind === 'points') {
|
|
542
554
|
gl.disable(gl.CULL_FACE)
|
|
543
555
|
program.set('uSize', material.sizeAttenuation ? material.size : material.size * this.pixelRatio)
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
556
|
+
// round points are solid, but their edges are soft: blended over what is behind even in the
|
|
557
|
+
// opaque pass, or the edge fades to black - a dark ring round every dot
|
|
558
|
+
const round = !transparent && !material.map
|
|
559
|
+
if (round) {
|
|
560
|
+
gl.enable(gl.BLEND)
|
|
561
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
|
562
|
+
}
|
|
563
|
+
if (/** @type {any} */ (node).drawsSprites) {
|
|
564
|
+
// each point a square, any size and turned - drawn back to front when the points ask, for
|
|
565
|
+
// smoke and other soft things that hide what is behind them
|
|
566
|
+
program.set('uResolution', [this.canvas.width, this.canvas.height])
|
|
567
|
+
const modelView = /** @type {any} */ (node).sort ? mat4.multiply(this.#modelView, frame.camera.view, worldMatrix) : null
|
|
568
|
+
const buffers = this.#gpu.resources.sprites(geometry, modelView)
|
|
569
|
+
gl.bindVertexArray(buffers.vao)
|
|
570
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, buffers.count)
|
|
571
|
+
} else {
|
|
572
|
+
const buffers = this.#gpu.resources.mesh(geometry, null, false)
|
|
573
|
+
gl.bindVertexArray(buffers.vao)
|
|
574
|
+
gl.drawArrays(gl.POINTS, 0, geometry.drawnVertices)
|
|
575
|
+
}
|
|
576
|
+
if (round) gl.disable(gl.BLEND)
|
|
547
577
|
this.info.drawCalls++
|
|
548
578
|
return
|
|
549
579
|
}
|
|
@@ -676,7 +706,8 @@ export class WebGLRenderer {
|
|
|
676
706
|
} else if (kind === 'points') {
|
|
677
707
|
if (material.map) d.push('MAP')
|
|
678
708
|
if (material.sizeAttenuation) d.push('SIZE_ATTENUATION')
|
|
679
|
-
}
|
|
709
|
+
} else if (material.dash > 0) d.push('DASHED')
|
|
710
|
+
// (points drawn as squares add SPRITES - see #draw, which knows the node)
|
|
680
711
|
return d
|
|
681
712
|
}
|
|
682
713
|
}
|
package/src/webgl/picking.js
CHANGED
|
@@ -18,6 +18,9 @@ const PICK_FRAGMENT = /* glsl */ `
|
|
|
18
18
|
precision highp float;
|
|
19
19
|
precision highp int;
|
|
20
20
|
flat in uint vPickElement;
|
|
21
|
+
#ifdef SPRITES
|
|
22
|
+
in vec2 vUv;
|
|
23
|
+
#endif
|
|
21
24
|
#ifdef CLIP
|
|
22
25
|
in vec3 vWorldPosition;
|
|
23
26
|
#endif
|
|
@@ -30,7 +33,11 @@ void main() {
|
|
|
30
33
|
clipAway(vWorldPosition);
|
|
31
34
|
#endif
|
|
32
35
|
#ifdef ROUND
|
|
36
|
+
#ifdef SPRITES
|
|
37
|
+
vec2 p = vUv * 2.0 - 1.0;
|
|
38
|
+
#else
|
|
33
39
|
vec2 p = gl_PointCoord * 2.0 - 1.0;
|
|
40
|
+
#endif
|
|
34
41
|
if (dot(p, p) > 1.0) discard;
|
|
35
42
|
#endif
|
|
36
43
|
pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
|
|
@@ -219,9 +226,11 @@ export class Picker {
|
|
|
219
226
|
const kind = mode === 'points' ? 'points' : mode === 'lines' ? 'lines' : 'mesh'
|
|
220
227
|
const defines = ['PICK']
|
|
221
228
|
if (instances) defines.push('INSTANCED')
|
|
229
|
+
const sprites = kind === 'points' && /** @type {any} */ (node).drawsSprites
|
|
222
230
|
if (kind === 'points') {
|
|
223
231
|
if (material.sizeAttenuation) defines.push('SIZE_ATTENUATION')
|
|
224
232
|
if (!material.map) defines.push('ROUND')
|
|
233
|
+
if (sprites) defines.push('SPRITES')
|
|
225
234
|
}
|
|
226
235
|
if (kind === 'mesh' && item.clip) defines.push('CLIP')
|
|
227
236
|
const program = this.programs.ready(`pick-${kind}`, defines, SOURCES[kind])
|
|
@@ -240,9 +249,13 @@ export class Picker {
|
|
|
240
249
|
program.set('uResolution', [REGION, REGION])
|
|
241
250
|
program.set('uWidth', material.width * pixelRatio)
|
|
242
251
|
program.set('uNear', frame.camera.near)
|
|
252
|
+
// arrowheads pick as their segment; dashes' gaps do too, so a dashed link is as easy to hit
|
|
253
|
+
const arrows = /** @type {any} */ (node).arrowBits ?? 0
|
|
254
|
+
program.set('uArrows', arrows)
|
|
255
|
+
program.set('uArrowSize', /** @type {any} */ (node).arrowPixels?.(material.width) * pixelRatio || 0)
|
|
243
256
|
const buffers = this.resources.lines(geometry)
|
|
244
257
|
gl.bindVertexArray(buffers.vao)
|
|
245
|
-
gl.drawArraysInstanced(gl.
|
|
258
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, arrows ? 12 : 6, Math.min(buffers.count, Math.floor(geometry.drawnCount / 2)))
|
|
246
259
|
return
|
|
247
260
|
}
|
|
248
261
|
if (kind === 'points') {
|
|
@@ -250,6 +263,14 @@ export class Picker {
|
|
|
250
263
|
// sizes as drawn: world sizes scale by the real projection, not the zoomed one
|
|
251
264
|
program.set('uSize', material.sizeAttenuation ? material.size : material.size * pixelRatio)
|
|
252
265
|
program.set('uScale', frame.camera.projection[5] * height / 2)
|
|
266
|
+
if (sprites) {
|
|
267
|
+
// squares, in whatever order they were last drawn - each says which point it is
|
|
268
|
+
program.set('uResolution', [REGION, REGION])
|
|
269
|
+
const buffers = this.resources.sprites(geometry)
|
|
270
|
+
gl.bindVertexArray(buffers.vao)
|
|
271
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, buffers.count)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
253
274
|
const buffers = this.resources.mesh(geometry, null, false)
|
|
254
275
|
gl.bindVertexArray(buffers.vao)
|
|
255
276
|
gl.drawArrays(gl.POINTS, 0, geometry.drawnVertices)
|