@solidrt/3d 0.0.48 → 0.0.50
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/AGENTS.md +162 -13
- package/README.md +19 -5
- package/examples/README.md +8 -0
- package/examples/pick.tsx +98 -0
- package/examples/scene-background.tsx +43 -0
- package/package.json +3 -3
- package/src/bvh.ts +258 -0
- package/src/components.tsx +66 -5
- package/src/geometry.ts +29 -0
- package/src/index.ts +5 -5
- package/src/material.ts +163 -55
- package/src/math.ts +42 -0
- package/src/order.ts +51 -0
- package/src/scene.ts +516 -28
package/src/material.ts
CHANGED
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Colors are straight [r, g, b, a?] 0..1 at the API and premultiplied here
|
|
9
9
|
// once, at the boundary (the engine's pixel contract). An alpha below 1
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// with
|
|
13
|
-
//
|
|
10
|
+
// blends only on a `transparent: true` material (Three's rule: the flag is
|
|
11
|
+
// explicit, alpha alone still draws opaque). Transparent materials build
|
|
12
|
+
// their pipeline with blend "alpha" and depthWrite off, and the scene draws
|
|
13
|
+
// their meshes after the opaque ones, sorted back-to-front per mesh.
|
|
14
14
|
//
|
|
15
|
-
// Custom looks
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
15
|
+
// Custom looks get the same split through shaderMaterialClass (one
|
|
16
|
+
// program, instance() per parameterisation); shaderMaterial is a class with
|
|
17
|
+
// a single instance. The raw layer (compileShader / createRenderPipeline in
|
|
18
|
+
// @solidrt/core/gpu) stays first-class beneath both.
|
|
19
19
|
|
|
20
20
|
import {
|
|
21
21
|
compileShader,
|
|
@@ -55,6 +55,10 @@ export type Material = {
|
|
|
55
55
|
* mesh whose geometry layout differs is rejected at add() - the strides
|
|
56
56
|
* disagree, so a mismatch would render garbage, not just miss a channel. */
|
|
57
57
|
layout?: VertexLayout
|
|
58
|
+
/** True when the pipeline blends over (blend "alpha", depthWrite off):
|
|
59
|
+
* the scene draws this material's meshes after every opaque one, sorted
|
|
60
|
+
* back-to-front by mesh origin, and re-sorts them when the camera moves. */
|
|
61
|
+
transparent?: boolean
|
|
58
62
|
/** Present on materials that own their pipeline (shaderMaterial). */
|
|
59
63
|
dispose?(): void
|
|
60
64
|
}
|
|
@@ -96,23 +100,30 @@ const FRAGMENT_MAP_SRC = glsl`
|
|
|
96
100
|
`
|
|
97
101
|
|
|
98
102
|
let sharedVertex: ShaderStageId | undefined
|
|
99
|
-
let pipelines:
|
|
103
|
+
let pipelines: Partial<Record<UnlitClass, RenderPipelineId>> = {}
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
// One pipeline per unlit CLASS: fragment kind x transparency, since blend
|
|
106
|
+
// state is pipeline state.
|
|
107
|
+
type UnlitClass = "color" | "map" | "color-transparent" | "map-transparent"
|
|
108
|
+
|
|
109
|
+
function pipelineFor(cls: UnlitClass): RenderPipelineId {
|
|
110
|
+
let existing = pipelines[cls]
|
|
103
111
|
if (existing !== undefined) return existing
|
|
104
112
|
if (sharedVertex === undefined) sharedVertex = compileShader("vertex", VERTEX_SRC, { header: true })
|
|
105
|
-
let
|
|
113
|
+
let transparent = cls.endsWith("-transparent")
|
|
114
|
+
let fragment = compileShader("fragment", cls.startsWith("color") ? FRAGMENT_COLOR_SRC : FRAGMENT_MAP_SRC, {
|
|
106
115
|
header: true,
|
|
107
116
|
})
|
|
108
|
-
let program = linkProgram(sharedVertex, fragment, { label: "scene-unlit-" +
|
|
117
|
+
let program = linkProgram(sharedVertex, fragment, { label: "scene-unlit-" + cls })
|
|
109
118
|
let pipeline = createRenderPipeline(program, {
|
|
110
119
|
attributes: VERTEX_LAYOUTS.standard,
|
|
111
120
|
depth: true,
|
|
121
|
+
depthWrite: transparent ? false : undefined,
|
|
122
|
+
blend: transparent ? "alpha" : undefined,
|
|
112
123
|
cull: "back",
|
|
113
|
-
label: "scene-unlit-" +
|
|
124
|
+
label: "scene-unlit-" + cls,
|
|
114
125
|
})
|
|
115
|
-
pipelines[
|
|
126
|
+
pipelines[cls] = pipeline
|
|
116
127
|
return pipeline
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -121,6 +132,9 @@ export type UnlitOptions = {
|
|
|
121
132
|
color?: [number, number, number] | [number, number, number, number]
|
|
122
133
|
/** A texture id to sample (tinted by `color` when both are given). */
|
|
123
134
|
map?: TextureId
|
|
135
|
+
/** Blend over what is behind (color alpha and map alpha both count).
|
|
136
|
+
* Without it an alpha below 1 still draws opaque. See Material.transparent. */
|
|
137
|
+
transparent?: boolean
|
|
124
138
|
}
|
|
125
139
|
|
|
126
140
|
/**
|
|
@@ -132,10 +146,16 @@ export function unlit(opts: UnlitOptions = {}): Material {
|
|
|
132
146
|
let color = opts.color ?? [1, 1, 1]
|
|
133
147
|
let a = color.length === 4 ? color[3] : 1
|
|
134
148
|
let uColor = [color[0] * a, color[1] * a, color[2] * a, a]
|
|
149
|
+
let transparent = opts.transparent === true
|
|
135
150
|
if (opts.map !== undefined) {
|
|
136
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
pipeline: () => pipelineFor(transparent ? "map-transparent" : "map"),
|
|
153
|
+
params: { uColor },
|
|
154
|
+
textures: { uMap: opts.map },
|
|
155
|
+
transparent,
|
|
156
|
+
}
|
|
137
157
|
}
|
|
138
|
-
return { pipeline: () => pipelineFor("color"), params: { uColor } }
|
|
158
|
+
return { pipeline: () => pipelineFor(transparent ? "color-transparent" : "color"), params: { uColor }, transparent }
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
// Mirrors the engine's own preamble rule: a source carrying its own
|
|
@@ -144,7 +164,46 @@ function needsHeader(source: string): boolean {
|
|
|
144
164
|
return !source.trimStart().startsWith("#version")
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
|
|
167
|
+
// The scene-background pass (scene.setBackground). The vertex stage is the
|
|
168
|
+
// engine's own attributeless fullscreen triangle (gl_VertexID, no vertex
|
|
169
|
+
// buffer), emitting the SAME vUV the shader-target contract provides: 0..1
|
|
170
|
+
// with origin at the displayed top-left - so a backdrop fragment written
|
|
171
|
+
// for createShaderTexture ports verbatim.
|
|
172
|
+
const BACKGROUND_VERTEX = glsl`
|
|
173
|
+
out vec2 vUV;
|
|
174
|
+
void main() {
|
|
175
|
+
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
176
|
+
vUV = p;
|
|
177
|
+
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
|
178
|
+
}
|
|
179
|
+
`
|
|
180
|
+
|
|
181
|
+
// Pipeline fragments get no vUV from the engine preamble (a pipeline's
|
|
182
|
+
// varyings are its own), so the background slot injects the full
|
|
183
|
+
// shader-target fragment contract itself: vUV, fragColor, iResolution.
|
|
184
|
+
const BACKGROUND_FRAGMENT_PREAMBLE =
|
|
185
|
+
"#version 300 es\nprecision highp float;\nin vec2 vUV;\nout vec4 fragColor;\nuniform vec2 iResolution;\n"
|
|
186
|
+
|
|
187
|
+
/** The scene's background pipeline (internal - reached via
|
|
188
|
+
* scene.setBackground): depth-free, attributeless, drawn as entry zero of
|
|
189
|
+
* the scene pass. */
|
|
190
|
+
export function backgroundPipeline(fragment: string, label: string): { pipeline: RenderPipelineId; program: ProgramId } {
|
|
191
|
+
let vs = compileShader("vertex", BACKGROUND_VERTEX, { header: true })
|
|
192
|
+
let fs = compileShader(
|
|
193
|
+
"fragment",
|
|
194
|
+
needsHeader(fragment) ? BACKGROUND_FRAGMENT_PREAMBLE + fragment : fragment,
|
|
195
|
+
{ header: false },
|
|
196
|
+
)
|
|
197
|
+
let program = linkProgram(vs, fs, { label })
|
|
198
|
+
destroyShader(vs)
|
|
199
|
+
destroyShader(fs)
|
|
200
|
+
let pipeline = createRenderPipeline(program, { label })
|
|
201
|
+
return { pipeline, program }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The class half of a shader material: sources and pipeline state, the
|
|
205
|
+
* things one compiled program fixes. */
|
|
206
|
+
export type ShaderMaterialClassOptions = {
|
|
148
207
|
/**
|
|
149
208
|
* Vertex stage GLSL. MUST declare and use `uniform mat4 uModel` (the
|
|
150
209
|
* mesh's world matrix, written per entry whenever the mesh moves) and
|
|
@@ -167,11 +226,16 @@ export type ShaderMaterialOptions = {
|
|
|
167
226
|
*/
|
|
168
227
|
vertex: string
|
|
169
228
|
fragment: string
|
|
170
|
-
/**
|
|
171
|
-
*
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
229
|
+
/** Blend over what is behind, with the scene sorting this material's
|
|
230
|
+
* meshes back-to-front after the opaque ones (see Material.transparent).
|
|
231
|
+
* Sets the pipeline defaults blend "alpha" and depthWrite false; the
|
|
232
|
+
* fragment must write premultiplied output (`vec4(rgb * a, a)`). Defaults
|
|
233
|
+
* to true whenever `blend` is set to anything but "none": every blended
|
|
234
|
+
* draw belongs after the opaques so it depth-tests against them, and
|
|
235
|
+
* back-to-front is harmless for the order-independent modes. */
|
|
236
|
+
transparent?: boolean
|
|
237
|
+
/** Pipeline state; defaults match unlit: depth: true, cull: "back",
|
|
238
|
+
* and for transparent materials blend "alpha", depthWrite: false. */
|
|
175
239
|
depth?: boolean
|
|
176
240
|
depthWrite?: boolean
|
|
177
241
|
blend?: BlendMode
|
|
@@ -180,18 +244,41 @@ export type ShaderMaterialOptions = {
|
|
|
180
244
|
label?: string
|
|
181
245
|
}
|
|
182
246
|
|
|
247
|
+
/** The instance half of a shader material: uniform seeds and sampler
|
|
248
|
+
* bindings for one parameterisation of a class's program. */
|
|
249
|
+
export type ShaderMaterialInstanceOptions = {
|
|
250
|
+
/** Uniform seeds beyond the standard set; update per mesh later with
|
|
251
|
+
* setMeshParams. */
|
|
252
|
+
params?: ShaderParams
|
|
253
|
+
textures?: Record<string, TextureId>
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export type ShaderMaterialOptions = ShaderMaterialClassOptions & ShaderMaterialInstanceOptions
|
|
257
|
+
|
|
183
258
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* pipeline
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* compile two pipelines - there is no dedupe by source value (a hidden
|
|
190
|
-
* cache keyed by content is the anti-pattern the GPU layer avoids
|
|
191
|
-
* throughout). Create one per look at app scope, share it across meshes,
|
|
192
|
-
* and `dispose()` it if the app is done with the look for good.
|
|
259
|
+
* One program and pipeline, many parameterisations: the class/instance
|
|
260
|
+
* split unlit has internally, for your own GLSL. `instance()` returns a
|
|
261
|
+
* Material sharing the class's pipeline with its own params/textures - the
|
|
262
|
+
* class compiles once, and dispose() is on the class alone (instances hold
|
|
263
|
+
* nothing of their own).
|
|
193
264
|
*/
|
|
194
|
-
export
|
|
265
|
+
export type ShaderMaterialClass = {
|
|
266
|
+
instance(opts?: ShaderMaterialInstanceOptions): Material
|
|
267
|
+
/** Destroy the shared program and pipeline. Instances still in use draw
|
|
268
|
+
* nothing valid afterwards. */
|
|
269
|
+
dispose(): void
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* A material class from your own GLSL: sources without a `#version` line
|
|
274
|
+
* get the standard pipeline preamble (`fragColor`, `iResolution`). Two
|
|
275
|
+
* calls with identical sources compile two programs - there is no dedupe by
|
|
276
|
+
* source value (a hidden cache keyed by content is the anti-pattern the GPU
|
|
277
|
+
* layer avoids throughout); the class IS the app-owned split. Create one
|
|
278
|
+
* per program at app scope, `instance()` per look, and `dispose()` the class
|
|
279
|
+
* when the app is done with the look for good.
|
|
280
|
+
*/
|
|
281
|
+
export function shaderMaterialClass(opts: ShaderMaterialClassOptions): ShaderMaterialClass {
|
|
195
282
|
// The standard-set contract, checked where the mistake is made: a vertex
|
|
196
283
|
// stage that never mentions the matrices cannot place meshes, and with
|
|
197
284
|
// shared params skipping undeclared names the omission would otherwise
|
|
@@ -208,30 +295,34 @@ export function shaderMaterial(opts: ShaderMaterialOptions): Material {
|
|
|
208
295
|
// Attributes live in the vertex stage only, so unlike the uNormal scan
|
|
209
296
|
// there is nothing to look for in the fragment source.
|
|
210
297
|
let layout: VertexLayout = /\baColor\b/.test(opts.vertex) ? "colored" : "standard"
|
|
298
|
+
let normalMatrix = /\buNormal\b/.test(opts.vertex) || /\buNormal\b/.test(opts.fragment)
|
|
299
|
+
let transparent = opts.transparent ?? (opts.blend !== undefined && opts.blend !== "none")
|
|
300
|
+
let depth = opts.depth ?? true
|
|
301
|
+
let pipelineFor = (): RenderPipelineId => {
|
|
302
|
+
if (pipeline === undefined) {
|
|
303
|
+
let vs = compileShader("vertex", opts.vertex, { header: needsHeader(opts.vertex) })
|
|
304
|
+
let fs = compileShader("fragment", opts.fragment, { header: needsHeader(opts.fragment) })
|
|
305
|
+
program = linkProgram(vs, fs, { label: opts.label })
|
|
306
|
+
destroyShader(vs)
|
|
307
|
+
destroyShader(fs)
|
|
308
|
+
pipeline = createRenderPipeline(program, {
|
|
309
|
+
attributes: VERTEX_LAYOUTS[layout],
|
|
310
|
+
depth,
|
|
311
|
+
// depthWrite needs a depth buffer, so the transparent default
|
|
312
|
+
// only applies when there is one.
|
|
313
|
+
depthWrite: opts.depthWrite ?? (transparent && depth ? false : undefined),
|
|
314
|
+
blend: opts.blend ?? (transparent ? "alpha" : undefined),
|
|
315
|
+
cull: opts.cull ?? "back",
|
|
316
|
+
topology: opts.topology,
|
|
317
|
+
label: opts.label,
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
return pipeline
|
|
321
|
+
}
|
|
211
322
|
return {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
pipeline() {
|
|
215
|
-
if (pipeline === undefined) {
|
|
216
|
-
let vs = compileShader("vertex", opts.vertex, { header: needsHeader(opts.vertex) })
|
|
217
|
-
let fs = compileShader("fragment", opts.fragment, { header: needsHeader(opts.fragment) })
|
|
218
|
-
program = linkProgram(vs, fs, { label: opts.label })
|
|
219
|
-
destroyShader(vs)
|
|
220
|
-
destroyShader(fs)
|
|
221
|
-
pipeline = createRenderPipeline(program, {
|
|
222
|
-
attributes: VERTEX_LAYOUTS[layout],
|
|
223
|
-
depth: opts.depth ?? true,
|
|
224
|
-
depthWrite: opts.depthWrite,
|
|
225
|
-
blend: opts.blend,
|
|
226
|
-
cull: opts.cull ?? "back",
|
|
227
|
-
topology: opts.topology,
|
|
228
|
-
label: opts.label,
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
return pipeline
|
|
323
|
+
instance(inst = {}) {
|
|
324
|
+
return { normalMatrix, layout, transparent, pipeline: pipelineFor, params: inst.params ?? {}, textures: inst.textures }
|
|
232
325
|
},
|
|
233
|
-
params: opts.params ?? {},
|
|
234
|
-
textures: opts.textures,
|
|
235
326
|
dispose() {
|
|
236
327
|
if (pipeline !== undefined) {
|
|
237
328
|
destroyRenderPipeline(pipeline)
|
|
@@ -244,3 +335,20 @@ export function shaderMaterial(opts: ShaderMaterialOptions): Material {
|
|
|
244
335
|
},
|
|
245
336
|
}
|
|
246
337
|
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* A material from your own GLSL: the custom-look escape hatch, first-class
|
|
341
|
+
* next to unlit. A class with a single instance - `shaderMaterialClass()`
|
|
342
|
+
* is the form for one program with many parameterisations.
|
|
343
|
+
*
|
|
344
|
+
* The INSTANCE is the pipeline handle: two calls with identical sources
|
|
345
|
+
* compile two pipelines - there is no dedupe by source value. Create one
|
|
346
|
+
* per look at app scope, share it across meshes, and `dispose()` it if the
|
|
347
|
+
* app is done with the look for good.
|
|
348
|
+
*/
|
|
349
|
+
export function shaderMaterial(opts: ShaderMaterialOptions): Material {
|
|
350
|
+
let cls = shaderMaterialClass(opts)
|
|
351
|
+
let material = cls.instance(opts)
|
|
352
|
+
material.dispose = cls.dispose
|
|
353
|
+
return material
|
|
354
|
+
}
|
package/src/math.ts
CHANGED
|
@@ -57,6 +57,48 @@ export function transformPoint(out: Vec4, m: Mat4, p: Vec3): Vec4 {
|
|
|
57
57
|
return out
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Transform a DIRECTION by m's upper 3x3 (w = 0: rotation and scale apply,
|
|
62
|
+
* translation does not). The ray-direction counterpart of transformPoint.
|
|
63
|
+
*/
|
|
64
|
+
export function transformVector(out: Vec3, m: Mat4, v: Vec3): Vec3 {
|
|
65
|
+
let x = v[0], y = v[1], z = v[2]
|
|
66
|
+
out[0] = m[0] * x + m[4] * y + m[8] * z
|
|
67
|
+
out[1] = m[1] * x + m[5] * y + m[9] * z
|
|
68
|
+
out[2] = m[2] * x + m[6] * y + m[10] * z
|
|
69
|
+
return out
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Invert an AFFINE matrix - a world or local matrix whose bottom row is
|
|
74
|
+
* 0,0,0,1, NOT a projection: the upper 3x3 inverts by cofactors and the
|
|
75
|
+
* translation is pulled back through it. Picking's world-to-local step.
|
|
76
|
+
* `out` may alias `m`. A degenerate (zero-scale) matrix yields the raw
|
|
77
|
+
* cofactors instead of NaNs, the same policy as normalMatrix.
|
|
78
|
+
*/
|
|
79
|
+
export function invertAffine(out: Mat4, m: Mat4): Mat4 {
|
|
80
|
+
let a = m[0], b = m[4], c = m[8]
|
|
81
|
+
let d = m[1], e = m[5], f = m[9]
|
|
82
|
+
let g = m[2], h = m[6], i = m[10]
|
|
83
|
+
let tx = m[12], ty = m[13], tz = m[14]
|
|
84
|
+
let c00 = e * i - f * h
|
|
85
|
+
let c01 = f * g - d * i
|
|
86
|
+
let c02 = d * h - e * g
|
|
87
|
+
let det = a * c00 + b * c01 + c * c02
|
|
88
|
+
let s = 1 / (det || 1)
|
|
89
|
+
let r00 = c00 * s, r01 = (c * h - b * i) * s, r02 = (b * f - c * e) * s
|
|
90
|
+
let r10 = c01 * s, r11 = (a * i - c * g) * s, r12 = (c * d - a * f) * s
|
|
91
|
+
let r20 = c02 * s, r21 = (b * g - a * h) * s, r22 = (a * e - b * d) * s
|
|
92
|
+
out[0] = r00; out[1] = r10; out[2] = r20; out[3] = 0
|
|
93
|
+
out[4] = r01; out[5] = r11; out[6] = r21; out[7] = 0
|
|
94
|
+
out[8] = r02; out[9] = r12; out[10] = r22; out[11] = 0
|
|
95
|
+
out[12] = -(r00 * tx + r01 * ty + r02 * tz)
|
|
96
|
+
out[13] = -(r10 * tx + r11 * ty + r12 * tz)
|
|
97
|
+
out[14] = -(r20 * tx + r21 * ty + r22 * tz)
|
|
98
|
+
out[15] = 1
|
|
99
|
+
return out
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
/** out = a * b (column vectors: b applies first). out may alias a or b. */
|
|
61
103
|
export function multiply(out: Mat4, a: Mat4, b: Mat4): Mat4 {
|
|
62
104
|
let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]
|
package/src/order.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Draw-list ordering for a scene: a pure function of the live meshes and the
|
|
2
|
+
// camera's view matrix, with no GUI import, so the check rig
|
|
3
|
+
// (checks/order-check.ts) runs it headless on flux against a linear oracle.
|
|
4
|
+
// The scene calls it whenever the order is dirty and hands the result to
|
|
5
|
+
// setDrawOrder.
|
|
6
|
+
|
|
7
|
+
import type { Mat4, Vec3 } from "./math.ts"
|
|
8
|
+
|
|
9
|
+
/** The slice of a Mesh the sort reads (field names match Mesh so the
|
|
10
|
+
* scene passes its meshes straight through). */
|
|
11
|
+
export type Orderable<T> = {
|
|
12
|
+
_entry: T | null
|
|
13
|
+
_transparent: boolean
|
|
14
|
+
renderOrder: number
|
|
15
|
+
_center: Vec3
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Draw order: `first` (the background entry, if any), then opaque meshes by
|
|
20
|
+
* renderOrder with add order within a key, then transparent meshes by
|
|
21
|
+
* renderOrder then back-to-front by the view-space depth of the world-bounds
|
|
22
|
+
* center. The center, not the origin (Three's key), so geometry built
|
|
23
|
+
* off-origin sorts by where it is; and not the nearest bounds point, which
|
|
24
|
+
* would draw a large translucent ground plane over the small translucents
|
|
25
|
+
* resting on it. Per-mesh only: no per-triangle sort, no OIT.
|
|
26
|
+
*/
|
|
27
|
+
export function orderEntries<T>(meshes: readonly Orderable<T>[], view: Mat4, first?: T): T[] {
|
|
28
|
+
let opaque: Orderable<T>[] = []
|
|
29
|
+
let transparent: Orderable<T>[] = []
|
|
30
|
+
for (let m of meshes) {
|
|
31
|
+
if (m._entry === null) continue
|
|
32
|
+
;(m._transparent ? transparent : opaque).push(m)
|
|
33
|
+
}
|
|
34
|
+
// Array sort is stable, so equal keys keep add order.
|
|
35
|
+
opaque.sort((a, b) => a.renderOrder - b.renderOrder)
|
|
36
|
+
if (transparent.length > 1) {
|
|
37
|
+
// The camera looks down -z in view space, so farther is more negative
|
|
38
|
+
// and ascending depth is back-to-front.
|
|
39
|
+
let depth = new Map<Orderable<T>, number>()
|
|
40
|
+
for (let m of transparent) {
|
|
41
|
+
let c = m._center
|
|
42
|
+
depth.set(m, view[2] * c[0] + view[6] * c[1] + view[10] * c[2] + view[14])
|
|
43
|
+
}
|
|
44
|
+
transparent.sort((a, b) => a.renderOrder - b.renderOrder || depth.get(a)! - depth.get(b)!)
|
|
45
|
+
}
|
|
46
|
+
let order: T[] = []
|
|
47
|
+
if (first !== undefined) order.push(first)
|
|
48
|
+
for (let m of opaque) order.push(m._entry!)
|
|
49
|
+
for (let m of transparent) order.push(m._entry!)
|
|
50
|
+
return order
|
|
51
|
+
}
|