@es3d/es3d 1.0.0 → 1.0.2
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/LICENSE +201 -201
- package/README.md +95 -86
- package/docs/SPEC.md +426 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +390 -377
- package/src/cache/geometry-store.js +392 -392
- package/src/cache/mesh-bundle.js +123 -123
- package/src/cache/sha256.js +73 -73
- package/src/format/ast.js +238 -238
- package/src/format/colors.js +93 -93
- package/src/format/compact-parser.js +171 -171
- package/src/format/compact-tokenizer.js +165 -165
- package/src/format/errors.js +247 -247
- package/src/format/json.js +55 -55
- package/src/format/limits.js +14 -14
- package/src/format/parse.js +132 -92
- package/src/format/schema.js +617 -617
- package/src/format/serializer.js +333 -333
- package/src/format/validator.js +330 -330
- package/src/format/value-error.js +12 -12
- package/src/format/value-format.js +314 -314
- package/src/format/values.js +459 -459
- package/src/format/xml-parser.js +136 -136
- package/src/format/xml-tokenizer.js +237 -237
- package/src/geometry/contour.js +86 -86
- package/src/geometry/extrude.js +175 -175
- package/src/geometry/gear.js +85 -85
- package/src/geometry/geometry-cache.js +159 -159
- package/src/geometry/lod-error.js +178 -178
- package/src/geometry/mesh.js +458 -458
- package/src/geometry/primitives.js +299 -299
- package/src/geometry/registry.js +645 -645
- package/src/geometry/revolve.js +136 -136
- package/src/geometry/sweep.js +441 -441
- package/src/index.js +47 -47
- package/src/io/compression.js +67 -67
- package/src/io/convert.js +236 -236
- package/src/io/download.js +13 -13
- package/src/io/external-scene.js +127 -127
- package/src/io/file-loader.js +28 -28
- package/src/io/gltf-pack.js +1461 -0
- package/src/io/gltf.js +630 -630
- package/src/io/mesh-import.js +104 -104
- package/src/io/obj.js +352 -352
- package/src/io/step-brep.js +439 -439
- package/src/io/step-exact.js +1149 -1149
- package/src/io/step.js +1418 -1418
- package/src/io/stl.js +193 -193
- package/src/io/svg-import.js +787 -787
- package/src/material/materials.js +154 -154
- package/src/math/mat4.js +475 -475
- package/src/math/random.js +30 -30
- package/src/math/trig.js +65 -65
- package/src/math/vec3.js +81 -81
- package/src/optimize/optimizer.js +604 -579
- package/src/path/path-flatten.js +322 -322
- package/src/path/path-parser.js +332 -332
- package/src/path/shapes.js +129 -129
- package/src/path/triangulate.js +517 -517
- package/src/pipeline.js +74 -74
- package/src/render/camera.js +168 -168
- package/src/render/frame-builder.js +362 -362
- package/src/render/geometry-pool.js +149 -149
- package/src/render/lighting.js +149 -149
- package/src/render/orbit-controls.js +237 -237
- package/src/render/renderer.js +258 -249
- package/src/render/scroll-timeline.js +104 -104
- package/src/render/shaders/glsl.js +285 -285
- package/src/render/shaders/wgsl.js +252 -252
- package/src/render/stage.js +365 -352
- package/src/render/texture-cache.js +40 -40
- package/src/render/textures.js +223 -223
- package/src/render/webgl2/webgl2-backend.js +586 -532
- package/src/render/webgpu/webgpu-backend.js +482 -480
- package/src/scene/flatten.js +754 -754
- package/src/scene/picking.js +161 -161
- package/src/scene/scene-graph.js +320 -320
- package/src/transform/transform.js +211 -211
- package/src/wasm/kernels-binary.js +3 -3
- package/src/wasm/kernels.js +385 -385
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
// Shared vertex / index buffers.
|
|
2
|
-
//
|
|
3
|
-
// All meshes (every geometry, every level of detail, wireframe edge lists and
|
|
4
|
-
// normal-visualisation lines) are packed into ONE interleaved vertex array and
|
|
5
|
-
// ONE 32-bit index array. Indices are stored pre-offset (absolute), so a draw
|
|
6
|
-
// is just (firstIndex, indexCount). No per-draw buffer binding, and no base
|
|
7
|
-
// vertex. That keeps the WebGL2 and WebGPU paths the same.
|
|
8
|
-
//
|
|
9
|
-
// The pool is CPU-side; backends upload the ranges appended since their last
|
|
10
|
-
// sync (or everything after a reallocation).
|
|
11
|
-
|
|
12
|
-
export const VERTEX_FLOATS = 8; // position xyz, normal xyz, uv
|
|
13
|
-
export const VERTEX_BYTES = VERTEX_FLOATS * 4;
|
|
14
|
-
|
|
15
|
-
export class GeometryPool {
|
|
16
|
-
constructor(vertexCapacity = 1 << 16, indexCapacity = 1 << 17) {
|
|
17
|
-
this.vertices = new Float32Array(vertexCapacity * VERTEX_FLOATS);
|
|
18
|
-
this.indices = new Uint32Array(indexCapacity);
|
|
19
|
-
this.vertexCount = 0;
|
|
20
|
-
this.indexCount = 0;
|
|
21
|
-
this.ranges = new Map(); // key object -> {firstIndex, indexCount}
|
|
22
|
-
this.edgeRanges = new Map();
|
|
23
|
-
this.normalRanges = new Map();
|
|
24
|
-
// dirty tracking for incremental uploads
|
|
25
|
-
this.generation = 0; // bumps on reallocation
|
|
26
|
-
this.uploadedVertices = 0;
|
|
27
|
-
this.uploadedIndices = 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
reset() {
|
|
31
|
-
this.vertexCount = 0;
|
|
32
|
-
this.indexCount = 0;
|
|
33
|
-
this.ranges.clear();
|
|
34
|
-
this.edgeRanges.clear();
|
|
35
|
-
this.normalRanges.clear();
|
|
36
|
-
this.generation++;
|
|
37
|
-
this.uploadedVertices = 0;
|
|
38
|
-
this.uploadedIndices = 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get byteSize() {
|
|
42
|
-
return this.vertexCount * VERTEX_BYTES + this.indexCount * 4;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
ensure(extraV, extraI) {
|
|
46
|
-
let grew = false;
|
|
47
|
-
if ((this.vertexCount + extraV) * VERTEX_FLOATS > this.vertices.length) {
|
|
48
|
-
let cap = this.vertices.length / VERTEX_FLOATS;
|
|
49
|
-
while (cap < this.vertexCount + extraV) cap *= 2;
|
|
50
|
-
const v = new Float32Array(cap * VERTEX_FLOATS);
|
|
51
|
-
v.set(this.vertices.subarray(0, this.vertexCount * VERTEX_FLOATS));
|
|
52
|
-
this.vertices = v;
|
|
53
|
-
grew = true;
|
|
54
|
-
}
|
|
55
|
-
if (this.indexCount + extraI > this.indices.length) {
|
|
56
|
-
let cap = this.indices.length;
|
|
57
|
-
while (cap < this.indexCount + extraI) cap *= 2;
|
|
58
|
-
const i = new Uint32Array(cap);
|
|
59
|
-
i.set(this.indices.subarray(0, this.indexCount));
|
|
60
|
-
this.indices = i;
|
|
61
|
-
grew = true;
|
|
62
|
-
}
|
|
63
|
-
if (grew) {
|
|
64
|
-
this.generation++;
|
|
65
|
-
this.uploadedVertices = 0;
|
|
66
|
-
this.uploadedIndices = 0;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
appendVertices(mesh) {
|
|
71
|
-
const n = mesh.vertexCount;
|
|
72
|
-
const base = this.vertexCount;
|
|
73
|
-
const v = this.vertices;
|
|
74
|
-
const p = mesh.positions, nr = mesh.normals, t = mesh.uvs;
|
|
75
|
-
let o = base * VERTEX_FLOATS;
|
|
76
|
-
for (let i = 0; i < n; i++) {
|
|
77
|
-
v[o] = p[i * 3];
|
|
78
|
-
v[o + 1] = p[i * 3 + 1];
|
|
79
|
-
v[o + 2] = p[i * 3 + 2];
|
|
80
|
-
v[o + 3] = nr[i * 3];
|
|
81
|
-
v[o + 4] = nr[i * 3 + 1];
|
|
82
|
-
v[o + 5] = nr[i * 3 + 2];
|
|
83
|
-
v[o + 6] = t[i * 2];
|
|
84
|
-
v[o + 7] = t[i * 2 + 1];
|
|
85
|
-
o += VERTEX_FLOATS;
|
|
86
|
-
}
|
|
87
|
-
this.vertexCount += n;
|
|
88
|
-
return base;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
appendIndices(src, base) {
|
|
92
|
-
const first = this.indexCount;
|
|
93
|
-
const dst = this.indices;
|
|
94
|
-
for (let i = 0; i < src.length; i++) dst[first + i] = src[i] + base;
|
|
95
|
-
this.indexCount += src.length;
|
|
96
|
-
return first;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Range of a mesh's primitives (triangles or lines). */
|
|
100
|
-
meshRange(mesh) {
|
|
101
|
-
let r = this.ranges.get(mesh);
|
|
102
|
-
if (r) return r;
|
|
103
|
-
this.ensure(mesh.vertexCount, mesh.indices.length);
|
|
104
|
-
const base = this.appendVertices(mesh);
|
|
105
|
-
const first = this.appendIndices(mesh.indices, base);
|
|
106
|
-
r = { firstIndex: first, indexCount: mesh.indices.length, baseVertex: base, kind: mesh.kind };
|
|
107
|
-
this.ranges.set(mesh, r);
|
|
108
|
-
return r;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Line range with the unique edges of a mesh (wireframe). */
|
|
112
|
-
edgeRange(mesh) {
|
|
113
|
-
let r = this.edgeRanges.get(mesh);
|
|
114
|
-
if (r) return r;
|
|
115
|
-
const base = this.meshRange(mesh).baseVertex;
|
|
116
|
-
const edges = mesh.edges();
|
|
117
|
-
this.ensure(0, edges.length);
|
|
118
|
-
const first = this.appendIndices(edges, base);
|
|
119
|
-
r = { firstIndex: first, indexCount: edges.length, kind: 'lines' };
|
|
120
|
-
this.edgeRanges.set(mesh, r);
|
|
121
|
-
return r;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Line range visualising vertex normals (uv.x = 0 at the base, 1 at the tip). */
|
|
125
|
-
normalRange(mesh) {
|
|
126
|
-
let r = this.normalRanges.get(mesh);
|
|
127
|
-
if (r) return r;
|
|
128
|
-
const lines = mesh.normalLines();
|
|
129
|
-
r = this.meshRange(lines);
|
|
130
|
-
this.normalRanges.set(mesh, r);
|
|
131
|
-
return r;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Pending upload ranges since the last sync. */
|
|
135
|
-
pending() {
|
|
136
|
-
return {
|
|
137
|
-
full: this.uploadedVertices === 0 && this.uploadedIndices === 0,
|
|
138
|
-
vertexStart: this.uploadedVertices,
|
|
139
|
-
vertexEnd: this.vertexCount,
|
|
140
|
-
indexStart: this.uploadedIndices,
|
|
141
|
-
indexEnd: this.indexCount,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
markUploaded() {
|
|
146
|
-
this.uploadedVertices = this.vertexCount;
|
|
147
|
-
this.uploadedIndices = this.indexCount;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
1
|
+
// Shared vertex / index buffers.
|
|
2
|
+
//
|
|
3
|
+
// All meshes (every geometry, every level of detail, wireframe edge lists and
|
|
4
|
+
// normal-visualisation lines) are packed into ONE interleaved vertex array and
|
|
5
|
+
// ONE 32-bit index array. Indices are stored pre-offset (absolute), so a draw
|
|
6
|
+
// is just (firstIndex, indexCount). No per-draw buffer binding, and no base
|
|
7
|
+
// vertex. That keeps the WebGL2 and WebGPU paths the same.
|
|
8
|
+
//
|
|
9
|
+
// The pool is CPU-side; backends upload the ranges appended since their last
|
|
10
|
+
// sync (or everything after a reallocation).
|
|
11
|
+
|
|
12
|
+
export const VERTEX_FLOATS = 8; // position xyz, normal xyz, uv
|
|
13
|
+
export const VERTEX_BYTES = VERTEX_FLOATS * 4;
|
|
14
|
+
|
|
15
|
+
export class GeometryPool {
|
|
16
|
+
constructor(vertexCapacity = 1 << 16, indexCapacity = 1 << 17) {
|
|
17
|
+
this.vertices = new Float32Array(vertexCapacity * VERTEX_FLOATS);
|
|
18
|
+
this.indices = new Uint32Array(indexCapacity);
|
|
19
|
+
this.vertexCount = 0;
|
|
20
|
+
this.indexCount = 0;
|
|
21
|
+
this.ranges = new Map(); // key object -> {firstIndex, indexCount}
|
|
22
|
+
this.edgeRanges = new Map();
|
|
23
|
+
this.normalRanges = new Map();
|
|
24
|
+
// dirty tracking for incremental uploads
|
|
25
|
+
this.generation = 0; // bumps on reallocation
|
|
26
|
+
this.uploadedVertices = 0;
|
|
27
|
+
this.uploadedIndices = 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
reset() {
|
|
31
|
+
this.vertexCount = 0;
|
|
32
|
+
this.indexCount = 0;
|
|
33
|
+
this.ranges.clear();
|
|
34
|
+
this.edgeRanges.clear();
|
|
35
|
+
this.normalRanges.clear();
|
|
36
|
+
this.generation++;
|
|
37
|
+
this.uploadedVertices = 0;
|
|
38
|
+
this.uploadedIndices = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get byteSize() {
|
|
42
|
+
return this.vertexCount * VERTEX_BYTES + this.indexCount * 4;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ensure(extraV, extraI) {
|
|
46
|
+
let grew = false;
|
|
47
|
+
if ((this.vertexCount + extraV) * VERTEX_FLOATS > this.vertices.length) {
|
|
48
|
+
let cap = this.vertices.length / VERTEX_FLOATS;
|
|
49
|
+
while (cap < this.vertexCount + extraV) cap *= 2;
|
|
50
|
+
const v = new Float32Array(cap * VERTEX_FLOATS);
|
|
51
|
+
v.set(this.vertices.subarray(0, this.vertexCount * VERTEX_FLOATS));
|
|
52
|
+
this.vertices = v;
|
|
53
|
+
grew = true;
|
|
54
|
+
}
|
|
55
|
+
if (this.indexCount + extraI > this.indices.length) {
|
|
56
|
+
let cap = this.indices.length;
|
|
57
|
+
while (cap < this.indexCount + extraI) cap *= 2;
|
|
58
|
+
const i = new Uint32Array(cap);
|
|
59
|
+
i.set(this.indices.subarray(0, this.indexCount));
|
|
60
|
+
this.indices = i;
|
|
61
|
+
grew = true;
|
|
62
|
+
}
|
|
63
|
+
if (grew) {
|
|
64
|
+
this.generation++;
|
|
65
|
+
this.uploadedVertices = 0;
|
|
66
|
+
this.uploadedIndices = 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
appendVertices(mesh) {
|
|
71
|
+
const n = mesh.vertexCount;
|
|
72
|
+
const base = this.vertexCount;
|
|
73
|
+
const v = this.vertices;
|
|
74
|
+
const p = mesh.positions, nr = mesh.normals, t = mesh.uvs;
|
|
75
|
+
let o = base * VERTEX_FLOATS;
|
|
76
|
+
for (let i = 0; i < n; i++) {
|
|
77
|
+
v[o] = p[i * 3];
|
|
78
|
+
v[o + 1] = p[i * 3 + 1];
|
|
79
|
+
v[o + 2] = p[i * 3 + 2];
|
|
80
|
+
v[o + 3] = nr[i * 3];
|
|
81
|
+
v[o + 4] = nr[i * 3 + 1];
|
|
82
|
+
v[o + 5] = nr[i * 3 + 2];
|
|
83
|
+
v[o + 6] = t[i * 2];
|
|
84
|
+
v[o + 7] = t[i * 2 + 1];
|
|
85
|
+
o += VERTEX_FLOATS;
|
|
86
|
+
}
|
|
87
|
+
this.vertexCount += n;
|
|
88
|
+
return base;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
appendIndices(src, base) {
|
|
92
|
+
const first = this.indexCount;
|
|
93
|
+
const dst = this.indices;
|
|
94
|
+
for (let i = 0; i < src.length; i++) dst[first + i] = src[i] + base;
|
|
95
|
+
this.indexCount += src.length;
|
|
96
|
+
return first;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Range of a mesh's primitives (triangles or lines). */
|
|
100
|
+
meshRange(mesh) {
|
|
101
|
+
let r = this.ranges.get(mesh);
|
|
102
|
+
if (r) return r;
|
|
103
|
+
this.ensure(mesh.vertexCount, mesh.indices.length);
|
|
104
|
+
const base = this.appendVertices(mesh);
|
|
105
|
+
const first = this.appendIndices(mesh.indices, base);
|
|
106
|
+
r = { firstIndex: first, indexCount: mesh.indices.length, baseVertex: base, kind: mesh.kind };
|
|
107
|
+
this.ranges.set(mesh, r);
|
|
108
|
+
return r;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Line range with the unique edges of a mesh (wireframe). */
|
|
112
|
+
edgeRange(mesh) {
|
|
113
|
+
let r = this.edgeRanges.get(mesh);
|
|
114
|
+
if (r) return r;
|
|
115
|
+
const base = this.meshRange(mesh).baseVertex;
|
|
116
|
+
const edges = mesh.edges();
|
|
117
|
+
this.ensure(0, edges.length);
|
|
118
|
+
const first = this.appendIndices(edges, base);
|
|
119
|
+
r = { firstIndex: first, indexCount: edges.length, kind: 'lines' };
|
|
120
|
+
this.edgeRanges.set(mesh, r);
|
|
121
|
+
return r;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Line range visualising vertex normals (uv.x = 0 at the base, 1 at the tip). */
|
|
125
|
+
normalRange(mesh) {
|
|
126
|
+
let r = this.normalRanges.get(mesh);
|
|
127
|
+
if (r) return r;
|
|
128
|
+
const lines = mesh.normalLines();
|
|
129
|
+
r = this.meshRange(lines);
|
|
130
|
+
this.normalRanges.set(mesh, r);
|
|
131
|
+
return r;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Pending upload ranges since the last sync. */
|
|
135
|
+
pending() {
|
|
136
|
+
return {
|
|
137
|
+
full: this.uploadedVertices === 0 && this.uploadedIndices === 0,
|
|
138
|
+
vertexStart: this.uploadedVertices,
|
|
139
|
+
vertexEnd: this.vertexCount,
|
|
140
|
+
indexStart: this.uploadedIndices,
|
|
141
|
+
indexEnd: this.indexCount,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
markUploaded() {
|
|
146
|
+
this.uploadedVertices = this.vertexCount;
|
|
147
|
+
this.uploadedIndices = this.indexCount;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/render/lighting.js
CHANGED
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
// Packing of per-frame uniforms (camera, environment and lights) into the
|
|
2
|
-
// layout shared by the GLSL uniform block and the WGSL uniform struct:
|
|
3
|
-
//
|
|
4
|
-
// mat4 viewProj; mat4 view; vec4 cameraPos(time); vec4 sky(envIntensity);
|
|
5
|
-
// vec4 ground(exposure); vec4 params(lightCount, normalLength, lineBias, viewportH);
|
|
6
|
-
// vec4 lightPos[16] xyz = position, w = type (0 dir, 1 point, 2 spot, 3 ambient, 4 hemisphere)
|
|
7
|
-
// vec4 lightDir[16] xyz = direction, w = range
|
|
8
|
-
// vec4 lightColor[16] rgb = color * intensity, w = cos(outer cone)
|
|
9
|
-
// vec4 lightExtra[16] x = cos(inner cone), yzw = hemisphere ground color
|
|
10
|
-
|
|
11
|
-
import { colorToLinear } from '../format/colors.js';
|
|
12
|
-
|
|
13
|
-
export const MAX_LIGHTS = 16;
|
|
14
|
-
export const FRAME_FLOATS = 48 + MAX_LIGHTS * 16;
|
|
15
|
-
export const FRAME_BYTES = FRAME_FLOATS * 4;
|
|
16
|
-
|
|
17
|
-
const TYPE_CODE = { directional: 0, point: 1, spot: 2, ambient: 3, hemisphere: 4 };
|
|
18
|
-
|
|
19
|
-
/** Lights used when a document defines none: a key and a fill light. */
|
|
20
|
-
// Light intensity convention: a white directional light of intensity 1 lights a
|
|
21
|
-
// white diffuse surface it faces head-on to full brightness (radiance * PI in the shader).
|
|
22
|
-
export const DEFAULT_LIGHTS = [
|
|
23
|
-
{ type: 'directional', color: [1.1, 1.08, 1.04], direction: norm([-0.45, -0.85, -0.35]), position: [0, 0, 0], range: 0, outer: 0, inner: 0, ground: [0, 0, 0] },
|
|
24
|
-
{ type: 'directional', color: [0.22, 0.25, 0.32], direction: norm([0.6, -0.2, 0.75]), position: [0, 0, 0], range: 0, outer: 0, inner: 0, ground: [0, 0, 0] },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
function norm(v) {
|
|
28
|
-
const l = Math.hypot(v[0], v[1], v[2]) || 1;
|
|
29
|
-
return [v[0] / l, v[1] / l, v[2] / l];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @returns {{data: Float32Array, lightCount: number, truncated: number}}
|
|
34
|
-
*/
|
|
35
|
-
export function packFrameUniforms(out, { viewProj, view, cameraPos, time, environment, lights, normalLength = 0.1, lineBias = 0.0015, viewportHeight = 1 }) {
|
|
36
|
-
out.fill(0);
|
|
37
|
-
out.set(viewProj, 0);
|
|
38
|
-
out.set(view, 16);
|
|
39
|
-
out[32] = cameraPos[0];
|
|
40
|
-
out[33] = cameraPos[1];
|
|
41
|
-
out[34] = cameraPos[2];
|
|
42
|
-
out[35] = time || 0;
|
|
43
|
-
const sky = colorToLinear(environment.sky);
|
|
44
|
-
const ground = colorToLinear(environment.ground);
|
|
45
|
-
out[36] = sky[0];
|
|
46
|
-
out[37] = sky[1];
|
|
47
|
-
out[38] = sky[2];
|
|
48
|
-
out[39] = environment.intensity;
|
|
49
|
-
out[40] = ground[0];
|
|
50
|
-
out[41] = ground[1];
|
|
51
|
-
out[42] = ground[2];
|
|
52
|
-
out[43] = environment.exposure;
|
|
53
|
-
const list = lights && lights.length ? lights : DEFAULT_LIGHTS;
|
|
54
|
-
const count = Math.min(list.length, MAX_LIGHTS);
|
|
55
|
-
out[44] = count;
|
|
56
|
-
out[45] = normalLength;
|
|
57
|
-
out[46] = lineBias;
|
|
58
|
-
out[47] = viewportHeight;
|
|
59
|
-
for (let i = 0; i < count; i++) {
|
|
60
|
-
const l = list[i];
|
|
61
|
-
const p = 48 + i * 4;
|
|
62
|
-
const d = 48 + MAX_LIGHTS * 4 + i * 4;
|
|
63
|
-
const c = 48 + MAX_LIGHTS * 8 + i * 4;
|
|
64
|
-
const e = 48 + MAX_LIGHTS * 12 + i * 4;
|
|
65
|
-
out[p] = l.position[0];
|
|
66
|
-
out[p + 1] = l.position[1];
|
|
67
|
-
out[p + 2] = l.position[2];
|
|
68
|
-
out[p + 3] = TYPE_CODE[l.type] ?? 0;
|
|
69
|
-
out[d] = l.direction[0];
|
|
70
|
-
out[d + 1] = l.direction[1];
|
|
71
|
-
out[d + 2] = l.direction[2];
|
|
72
|
-
out[d + 3] = l.range || 0;
|
|
73
|
-
out[c] = l.color[0];
|
|
74
|
-
out[c + 1] = l.color[1];
|
|
75
|
-
out[c + 2] = l.color[2];
|
|
76
|
-
out[c + 3] = l.outer || 0;
|
|
77
|
-
// smoothstep needs edge0 < edge1: keep the inner cone strictly inside the outer one
|
|
78
|
-
out[e] = Math.max(l.inner || 0, (l.outer || 0) + 1e-4);
|
|
79
|
-
out[e + 1] = l.ground ? l.ground[0] : 0;
|
|
80
|
-
out[e + 2] = l.ground ? l.ground[1] : 0;
|
|
81
|
-
out[e + 3] = l.ground ? l.ground[2] : 0;
|
|
82
|
-
}
|
|
83
|
-
return { data: out, lightCount: count, truncated: Math.max(0, list.length - MAX_LIGHTS), usedDefaults: !(lights && lights.length) };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The light types of packed frame uniforms, in order, as a short key ("0,0",
|
|
88
|
-
* "0,1,2"): the light setup that mesh shaders are specialised for.
|
|
89
|
-
*/
|
|
90
|
-
export function lightTypes(frameData) {
|
|
91
|
-
const n = Math.round(frameData[44]);
|
|
92
|
-
let key = '';
|
|
93
|
-
for (let i = 0; i < n; i++) key += (i ? ',' : '') + Math.round(frameData[48 + i * 4 + 3]);
|
|
94
|
-
return key;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Material uniform block: baseColor, emissive(normalScale), params(metallic, roughness, flags), uvScale, overlay color. */
|
|
98
|
-
export const MATERIAL_FLOATS = 20;
|
|
99
|
-
|
|
100
|
-
export const MAT_FLAG_MAP = 1;
|
|
101
|
-
export const MAT_FLAG_NORMAL_MAP = 2;
|
|
102
|
-
export const MAT_FLAG_DOUBLE_SIDED = 4;
|
|
103
|
-
export const MAT_FLAG_UNLIT = 8;
|
|
104
|
-
export const MAT_FLAG_OVERLAY = 16;
|
|
105
|
-
export const MAT_FLAG_MR_MAP = 32;
|
|
106
|
-
export const MAT_FLAG_OCCLUSION = 64;
|
|
107
|
-
export const MAT_FLAG_EMISSIVE_MAP = 128;
|
|
108
|
-
export const MAT_FLAG_ALPHA_MASK = 256;
|
|
109
|
-
|
|
110
|
-
export function packMaterial(out, m, { hasMap = false, hasNormalMap = false, hasMrMap = false, hasOcclusionMap = false, hasEmissiveMap = false, doubleSided = false } = {}) {
|
|
111
|
-
out.fill(0);
|
|
112
|
-
out[0] = m.baseColor[0];
|
|
113
|
-
out[1] = m.baseColor[1];
|
|
114
|
-
out[2] = m.baseColor[2];
|
|
115
|
-
out[3] = m.alphaMode === 'opaque' ? 1 : m.opacity;
|
|
116
|
-
out[4] = m.emissive[0];
|
|
117
|
-
out[5] = m.emissive[1];
|
|
118
|
-
out[6] = m.emissive[2];
|
|
119
|
-
out[7] = m.normalScale;
|
|
120
|
-
out[8] = m.metallic;
|
|
121
|
-
out[9] = m.roughness;
|
|
122
|
-
out[10] =
|
|
123
|
-
(hasMap ? MAT_FLAG_MAP : 0) |
|
|
124
|
-
(hasNormalMap ? MAT_FLAG_NORMAL_MAP : 0) |
|
|
125
|
-
(doubleSided ? MAT_FLAG_DOUBLE_SIDED : 0) |
|
|
126
|
-
(m.unlit ? MAT_FLAG_UNLIT : 0) |
|
|
127
|
-
(hasMrMap ? MAT_FLAG_MR_MAP : 0) |
|
|
128
|
-
(hasOcclusionMap ? MAT_FLAG_OCCLUSION : 0) |
|
|
129
|
-
(hasEmissiveMap ? MAT_FLAG_EMISSIVE_MAP : 0) |
|
|
130
|
-
(m.alphaMode === 'mask' ? MAT_FLAG_ALPHA_MASK : 0);
|
|
131
|
-
out[11] = m.alphaCutoff ?? 0.5;
|
|
132
|
-
out[12] = m.uvScale[0];
|
|
133
|
-
out[13] = m.uvScale[1];
|
|
134
|
-
out[14] = m.occlusionStrength ?? 1;
|
|
135
|
-
return out;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Uniforms for debug line overlays (sRGB color, drawn unlit). */
|
|
139
|
-
export function packOverlay(out, rgba) {
|
|
140
|
-
out.fill(0);
|
|
141
|
-
out[16] = rgba[0];
|
|
142
|
-
out[17] = rgba[1];
|
|
143
|
-
out[18] = rgba[2];
|
|
144
|
-
out[19] = rgba[3];
|
|
145
|
-
out[10] = MAT_FLAG_OVERLAY;
|
|
146
|
-
out[12] = 1;
|
|
147
|
-
out[13] = 1;
|
|
148
|
-
return out;
|
|
149
|
-
}
|
|
1
|
+
// Packing of per-frame uniforms (camera, environment and lights) into the
|
|
2
|
+
// layout shared by the GLSL uniform block and the WGSL uniform struct:
|
|
3
|
+
//
|
|
4
|
+
// mat4 viewProj; mat4 view; vec4 cameraPos(time); vec4 sky(envIntensity);
|
|
5
|
+
// vec4 ground(exposure); vec4 params(lightCount, normalLength, lineBias, viewportH);
|
|
6
|
+
// vec4 lightPos[16] xyz = position, w = type (0 dir, 1 point, 2 spot, 3 ambient, 4 hemisphere)
|
|
7
|
+
// vec4 lightDir[16] xyz = direction, w = range
|
|
8
|
+
// vec4 lightColor[16] rgb = color * intensity, w = cos(outer cone)
|
|
9
|
+
// vec4 lightExtra[16] x = cos(inner cone), yzw = hemisphere ground color
|
|
10
|
+
|
|
11
|
+
import { colorToLinear } from '../format/colors.js';
|
|
12
|
+
|
|
13
|
+
export const MAX_LIGHTS = 16;
|
|
14
|
+
export const FRAME_FLOATS = 48 + MAX_LIGHTS * 16;
|
|
15
|
+
export const FRAME_BYTES = FRAME_FLOATS * 4;
|
|
16
|
+
|
|
17
|
+
const TYPE_CODE = { directional: 0, point: 1, spot: 2, ambient: 3, hemisphere: 4 };
|
|
18
|
+
|
|
19
|
+
/** Lights used when a document defines none: a key and a fill light. */
|
|
20
|
+
// Light intensity convention: a white directional light of intensity 1 lights a
|
|
21
|
+
// white diffuse surface it faces head-on to full brightness (radiance * PI in the shader).
|
|
22
|
+
export const DEFAULT_LIGHTS = [
|
|
23
|
+
{ type: 'directional', color: [1.1, 1.08, 1.04], direction: norm([-0.45, -0.85, -0.35]), position: [0, 0, 0], range: 0, outer: 0, inner: 0, ground: [0, 0, 0] },
|
|
24
|
+
{ type: 'directional', color: [0.22, 0.25, 0.32], direction: norm([0.6, -0.2, 0.75]), position: [0, 0, 0], range: 0, outer: 0, inner: 0, ground: [0, 0, 0] },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function norm(v) {
|
|
28
|
+
const l = Math.hypot(v[0], v[1], v[2]) || 1;
|
|
29
|
+
return [v[0] / l, v[1] / l, v[2] / l];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @returns {{data: Float32Array, lightCount: number, truncated: number}}
|
|
34
|
+
*/
|
|
35
|
+
export function packFrameUniforms(out, { viewProj, view, cameraPos, time, environment, lights, normalLength = 0.1, lineBias = 0.0015, viewportHeight = 1 }) {
|
|
36
|
+
out.fill(0);
|
|
37
|
+
out.set(viewProj, 0);
|
|
38
|
+
out.set(view, 16);
|
|
39
|
+
out[32] = cameraPos[0];
|
|
40
|
+
out[33] = cameraPos[1];
|
|
41
|
+
out[34] = cameraPos[2];
|
|
42
|
+
out[35] = time || 0;
|
|
43
|
+
const sky = colorToLinear(environment.sky);
|
|
44
|
+
const ground = colorToLinear(environment.ground);
|
|
45
|
+
out[36] = sky[0];
|
|
46
|
+
out[37] = sky[1];
|
|
47
|
+
out[38] = sky[2];
|
|
48
|
+
out[39] = environment.intensity;
|
|
49
|
+
out[40] = ground[0];
|
|
50
|
+
out[41] = ground[1];
|
|
51
|
+
out[42] = ground[2];
|
|
52
|
+
out[43] = environment.exposure;
|
|
53
|
+
const list = lights && lights.length ? lights : DEFAULT_LIGHTS;
|
|
54
|
+
const count = Math.min(list.length, MAX_LIGHTS);
|
|
55
|
+
out[44] = count;
|
|
56
|
+
out[45] = normalLength;
|
|
57
|
+
out[46] = lineBias;
|
|
58
|
+
out[47] = viewportHeight;
|
|
59
|
+
for (let i = 0; i < count; i++) {
|
|
60
|
+
const l = list[i];
|
|
61
|
+
const p = 48 + i * 4;
|
|
62
|
+
const d = 48 + MAX_LIGHTS * 4 + i * 4;
|
|
63
|
+
const c = 48 + MAX_LIGHTS * 8 + i * 4;
|
|
64
|
+
const e = 48 + MAX_LIGHTS * 12 + i * 4;
|
|
65
|
+
out[p] = l.position[0];
|
|
66
|
+
out[p + 1] = l.position[1];
|
|
67
|
+
out[p + 2] = l.position[2];
|
|
68
|
+
out[p + 3] = TYPE_CODE[l.type] ?? 0;
|
|
69
|
+
out[d] = l.direction[0];
|
|
70
|
+
out[d + 1] = l.direction[1];
|
|
71
|
+
out[d + 2] = l.direction[2];
|
|
72
|
+
out[d + 3] = l.range || 0;
|
|
73
|
+
out[c] = l.color[0];
|
|
74
|
+
out[c + 1] = l.color[1];
|
|
75
|
+
out[c + 2] = l.color[2];
|
|
76
|
+
out[c + 3] = l.outer || 0;
|
|
77
|
+
// smoothstep needs edge0 < edge1: keep the inner cone strictly inside the outer one
|
|
78
|
+
out[e] = Math.max(l.inner || 0, (l.outer || 0) + 1e-4);
|
|
79
|
+
out[e + 1] = l.ground ? l.ground[0] : 0;
|
|
80
|
+
out[e + 2] = l.ground ? l.ground[1] : 0;
|
|
81
|
+
out[e + 3] = l.ground ? l.ground[2] : 0;
|
|
82
|
+
}
|
|
83
|
+
return { data: out, lightCount: count, truncated: Math.max(0, list.length - MAX_LIGHTS), usedDefaults: !(lights && lights.length) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The light types of packed frame uniforms, in order, as a short key ("0,0",
|
|
88
|
+
* "0,1,2"): the light setup that mesh shaders are specialised for.
|
|
89
|
+
*/
|
|
90
|
+
export function lightTypes(frameData) {
|
|
91
|
+
const n = Math.round(frameData[44]);
|
|
92
|
+
let key = '';
|
|
93
|
+
for (let i = 0; i < n; i++) key += (i ? ',' : '') + Math.round(frameData[48 + i * 4 + 3]);
|
|
94
|
+
return key;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Material uniform block: baseColor, emissive(normalScale), params(metallic, roughness, flags), uvScale, overlay color. */
|
|
98
|
+
export const MATERIAL_FLOATS = 20;
|
|
99
|
+
|
|
100
|
+
export const MAT_FLAG_MAP = 1;
|
|
101
|
+
export const MAT_FLAG_NORMAL_MAP = 2;
|
|
102
|
+
export const MAT_FLAG_DOUBLE_SIDED = 4;
|
|
103
|
+
export const MAT_FLAG_UNLIT = 8;
|
|
104
|
+
export const MAT_FLAG_OVERLAY = 16;
|
|
105
|
+
export const MAT_FLAG_MR_MAP = 32;
|
|
106
|
+
export const MAT_FLAG_OCCLUSION = 64;
|
|
107
|
+
export const MAT_FLAG_EMISSIVE_MAP = 128;
|
|
108
|
+
export const MAT_FLAG_ALPHA_MASK = 256;
|
|
109
|
+
|
|
110
|
+
export function packMaterial(out, m, { hasMap = false, hasNormalMap = false, hasMrMap = false, hasOcclusionMap = false, hasEmissiveMap = false, doubleSided = false } = {}) {
|
|
111
|
+
out.fill(0);
|
|
112
|
+
out[0] = m.baseColor[0];
|
|
113
|
+
out[1] = m.baseColor[1];
|
|
114
|
+
out[2] = m.baseColor[2];
|
|
115
|
+
out[3] = m.alphaMode === 'opaque' ? 1 : m.opacity;
|
|
116
|
+
out[4] = m.emissive[0];
|
|
117
|
+
out[5] = m.emissive[1];
|
|
118
|
+
out[6] = m.emissive[2];
|
|
119
|
+
out[7] = m.normalScale;
|
|
120
|
+
out[8] = m.metallic;
|
|
121
|
+
out[9] = m.roughness;
|
|
122
|
+
out[10] =
|
|
123
|
+
(hasMap ? MAT_FLAG_MAP : 0) |
|
|
124
|
+
(hasNormalMap ? MAT_FLAG_NORMAL_MAP : 0) |
|
|
125
|
+
(doubleSided ? MAT_FLAG_DOUBLE_SIDED : 0) |
|
|
126
|
+
(m.unlit ? MAT_FLAG_UNLIT : 0) |
|
|
127
|
+
(hasMrMap ? MAT_FLAG_MR_MAP : 0) |
|
|
128
|
+
(hasOcclusionMap ? MAT_FLAG_OCCLUSION : 0) |
|
|
129
|
+
(hasEmissiveMap ? MAT_FLAG_EMISSIVE_MAP : 0) |
|
|
130
|
+
(m.alphaMode === 'mask' ? MAT_FLAG_ALPHA_MASK : 0);
|
|
131
|
+
out[11] = m.alphaCutoff ?? 0.5;
|
|
132
|
+
out[12] = m.uvScale[0];
|
|
133
|
+
out[13] = m.uvScale[1];
|
|
134
|
+
out[14] = m.occlusionStrength ?? 1;
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Uniforms for debug line overlays (sRGB color, drawn unlit). */
|
|
139
|
+
export function packOverlay(out, rgba) {
|
|
140
|
+
out.fill(0);
|
|
141
|
+
out[16] = rgba[0];
|
|
142
|
+
out[17] = rgba[1];
|
|
143
|
+
out[18] = rgba[2];
|
|
144
|
+
out[19] = rgba[3];
|
|
145
|
+
out[10] = MAT_FLAG_OVERLAY;
|
|
146
|
+
out[12] = 1;
|
|
147
|
+
out[13] = 1;
|
|
148
|
+
return out;
|
|
149
|
+
}
|