@es3d/es3d 1.0.0 → 1.0.1

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.
Files changed (79) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +86 -86
  3. package/docs/SPEC.md +417 -417
  4. package/package.json +1 -1
  5. package/scripts/es3d-cli.mjs +377 -377
  6. package/src/cache/geometry-store.js +392 -392
  7. package/src/cache/mesh-bundle.js +123 -123
  8. package/src/cache/sha256.js +73 -73
  9. package/src/format/ast.js +238 -238
  10. package/src/format/colors.js +93 -93
  11. package/src/format/compact-parser.js +171 -171
  12. package/src/format/compact-tokenizer.js +165 -165
  13. package/src/format/errors.js +247 -247
  14. package/src/format/json.js +55 -55
  15. package/src/format/limits.js +14 -14
  16. package/src/format/parse.js +92 -92
  17. package/src/format/schema.js +617 -617
  18. package/src/format/serializer.js +333 -333
  19. package/src/format/validator.js +330 -330
  20. package/src/format/value-error.js +12 -12
  21. package/src/format/value-format.js +314 -314
  22. package/src/format/values.js +459 -459
  23. package/src/format/xml-parser.js +136 -136
  24. package/src/format/xml-tokenizer.js +237 -237
  25. package/src/geometry/contour.js +86 -86
  26. package/src/geometry/extrude.js +175 -175
  27. package/src/geometry/gear.js +85 -85
  28. package/src/geometry/geometry-cache.js +159 -159
  29. package/src/geometry/lod-error.js +178 -178
  30. package/src/geometry/mesh.js +458 -458
  31. package/src/geometry/primitives.js +299 -299
  32. package/src/geometry/registry.js +645 -645
  33. package/src/geometry/revolve.js +136 -136
  34. package/src/geometry/sweep.js +441 -441
  35. package/src/index.js +47 -47
  36. package/src/io/compression.js +67 -67
  37. package/src/io/convert.js +236 -236
  38. package/src/io/download.js +13 -13
  39. package/src/io/external-scene.js +127 -127
  40. package/src/io/file-loader.js +28 -28
  41. package/src/io/gltf.js +630 -630
  42. package/src/io/mesh-import.js +104 -104
  43. package/src/io/obj.js +352 -352
  44. package/src/io/step-brep.js +439 -439
  45. package/src/io/step-exact.js +1149 -1149
  46. package/src/io/step.js +1418 -1418
  47. package/src/io/stl.js +193 -193
  48. package/src/io/svg-import.js +787 -787
  49. package/src/material/materials.js +154 -154
  50. package/src/math/mat4.js +475 -475
  51. package/src/math/random.js +30 -30
  52. package/src/math/trig.js +65 -65
  53. package/src/math/vec3.js +81 -81
  54. package/src/optimize/optimizer.js +579 -579
  55. package/src/path/path-flatten.js +322 -322
  56. package/src/path/path-parser.js +332 -332
  57. package/src/path/shapes.js +129 -129
  58. package/src/path/triangulate.js +517 -517
  59. package/src/pipeline.js +74 -74
  60. package/src/render/camera.js +168 -168
  61. package/src/render/frame-builder.js +362 -362
  62. package/src/render/geometry-pool.js +149 -149
  63. package/src/render/lighting.js +149 -149
  64. package/src/render/orbit-controls.js +237 -237
  65. package/src/render/renderer.js +258 -249
  66. package/src/render/scroll-timeline.js +104 -104
  67. package/src/render/shaders/glsl.js +285 -285
  68. package/src/render/shaders/wgsl.js +252 -252
  69. package/src/render/stage.js +364 -352
  70. package/src/render/texture-cache.js +40 -40
  71. package/src/render/textures.js +223 -223
  72. package/src/render/webgl2/webgl2-backend.js +586 -532
  73. package/src/render/webgpu/webgpu-backend.js +482 -480
  74. package/src/scene/flatten.js +754 -754
  75. package/src/scene/picking.js +161 -161
  76. package/src/scene/scene-graph.js +320 -320
  77. package/src/transform/transform.js +211 -211
  78. package/src/wasm/kernels-binary.js +3 -3
  79. package/src/wasm/kernels.js +385 -385
@@ -1,178 +1,178 @@
1
- // Chordal error of each generator, in the same units as its canonical mesh.
2
- //
3
- // The frame builder keeps a level only while this distance, scaled by the
4
- // instance and projected, stays within one pixel. Bounds are the maximum
5
- // distance from the mesh to the true surface. Analytic primitives are measured
6
- // from the same vertex grid the tessellator builds. Curves use the flatten
7
- // tolerance, which the flattener treats as a hard chordal limit.
8
-
9
- import { sphereDeviationWasm } from '../wasm/kernels.js';
10
-
11
- /** Sagitta of a unit circle drawn with n equal chords, as a fraction of the radius. */
12
- export function circleSagitta(n) {
13
- const s = Math.max(3, n);
14
- return 1 - Math.cos(Math.PI / s);
15
- }
16
-
17
- function closestDistanceToOrigin(ax, ay, az, bx, by, bz, cx, cy, cz) {
18
- const abx = bx - ax, aby = by - ay, abz = bz - az;
19
- const acx = cx - ax, acy = cy - ay, acz = cz - az;
20
- let nx = aby * acz - abz * acy;
21
- let ny = abz * acx - abx * acz;
22
- let nz = abx * acy - aby * acx;
23
- const nl = Math.hypot(nx, ny, nz);
24
- if (nl < 1e-14) return 1;
25
- nx /= nl;
26
- ny /= nl;
27
- nz /= nl;
28
- const d = ax * nx + ay * ny + az * nz;
29
- const fx = d * nx, fy = d * ny, fz = d * nz;
30
- const v0x = acx, v0y = acy, v0z = acz;
31
- const v1x = abx, v1y = aby, v1z = abz;
32
- const v2x = fx - ax, v2y = fy - ay, v2z = fz - az;
33
- const d00 = v0x * v0x + v0y * v0y + v0z * v0z;
34
- const d01 = v0x * v1x + v0y * v1y + v0z * v1z;
35
- const d11 = v1x * v1x + v1y * v1y + v1z * v1z;
36
- const d02 = v0x * v2x + v0y * v2y + v0z * v2z;
37
- const d12 = v1x * v2x + v1y * v2y + v1z * v2z;
38
- const det = d00 * d11 - d01 * d01;
39
- if (Math.abs(det) > 1e-18) {
40
- const inv = 1 / det;
41
- const u = (d11 * d02 - d01 * d12) * inv;
42
- const v = (d00 * d12 - d01 * d02) * inv;
43
- if (u >= -1e-5 && v >= -1e-5 && u + v <= 1 + 1e-5) return Math.abs(d);
44
- }
45
- return Math.min(edgeDistance(ax, ay, az, bx, by, bz), edgeDistance(bx, by, bz, cx, cy, cz), edgeDistance(cx, cy, cz, ax, ay, az));
46
- }
47
-
48
- function edgeDistance(ax, ay, az, bx, by, bz) {
49
- const abx = bx - ax, aby = by - ay, abz = bz - az;
50
- const ab2 = abx * abx + aby * aby + abz * abz;
51
- let t = ab2 > 0 ? -(ax * abx + ay * aby + az * abz) / ab2 : 0;
52
- if (t < 0) t = 0;
53
- else if (t > 1) t = 1;
54
- return Math.hypot(ax + abx * t, ay + aby * t, az + abz * t);
55
- }
56
-
57
- function radialError(a, b, c) {
58
- return 1 - closestDistanceToOrigin(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
59
- }
60
-
61
- const sphereDeviations = new Map();
62
-
63
- /** Maximum radial error of the reference UV sphere at this segment count. */
64
- export function sphereDeviation(segments) {
65
- // every sphere element asks for the same few segment counts: measure each once
66
- const w = Math.max(3, segments | 0);
67
- let max = sphereDeviations.get(w);
68
- if (max === undefined) {
69
- max = measureSphereDeviation(w);
70
- sphereDeviations.set(w, max);
71
- }
72
- return max;
73
- }
74
-
75
- function measureSphereDeviation(w) {
76
- const v = sphereDeviationWasm(w);
77
- if (v !== null) return v;
78
- const hRings = Math.max(2, Math.round(w / 2));
79
- const TAU = Math.PI * 2;
80
- // the grid's sines and cosines, once per ring and per column
81
- const st = new Float64Array(hRings + 1), ct = new Float64Array(hRings + 1);
82
- for (let j = 0; j <= hRings; j++) {
83
- const th = (j / hRings) * Math.PI;
84
- st[j] = Math.sin(th);
85
- ct[j] = Math.cos(th);
86
- }
87
- const cp = new Float64Array(w + 1), sp = new Float64Array(w + 1);
88
- for (let i = 0; i <= w; i++) {
89
- const ph = ((i % w) / w) * TAU;
90
- cp[i] = Math.cos(ph);
91
- sp[i] = Math.sin(ph);
92
- }
93
- let max = 0;
94
- for (let j = 0; j < hRings; j++) {
95
- for (let i = 0; i < w; i++) {
96
- // quad corners a (i, j), b (i, j + 1), c (i + 1, j + 1), d (i + 1, j)
97
- const ax = st[j] * cp[i], ay = ct[j], az = -st[j] * sp[i];
98
- const bx = st[j + 1] * cp[i], by = ct[j + 1], bz = -st[j + 1] * sp[i];
99
- const cx = st[j + 1] * cp[i + 1], cy = ct[j + 1], cz = -st[j + 1] * sp[i + 1];
100
- const dx = st[j] * cp[i + 1], dy = ct[j], dz = -st[j] * sp[i + 1];
101
- if (j !== hRings - 1) {
102
- const e = 1 - closestDistanceToOrigin(ax, ay, az, bx, by, bz, cx, cy, cz);
103
- if (e > max) max = e;
104
- }
105
- if (j !== 0) {
106
- const e = 1 - closestDistanceToOrigin(ax, ay, az, cx, cy, cz, dx, dy, dz);
107
- if (e > max) max = e;
108
- }
109
- }
110
- }
111
- return max;
112
- }
113
-
114
- /** Radial error of a stored unit sphere (binary32 vertex positions). */
115
- export function sphereMeshError(positions, indices) {
116
- let max = 0;
117
- for (let t = 0; t < indices.length; t += 3) {
118
- const at = (i) => [positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]];
119
- const e = radialError(at(indices[t]), at(indices[t + 1]), at(indices[t + 2]));
120
- if (e > max) max = e;
121
- }
122
- return max;
123
- }
124
-
125
- /** Cylinder or cone in canonical space (bottom radius 1, top radius `top`). */
126
- export function cylinderDeviation(top, segments) {
127
- return Math.max(1, top, 0) * circleSagitta(segments);
128
- }
129
-
130
- /** Capsule of tube radius 1. The straight section does not add radial error. */
131
- export function capsuleDeviation(segments) {
132
- const w = Math.max(3, segments | 0);
133
- const hr = Math.max(1, Math.round(w / 4));
134
- // latitude step over a quarter circle, and the equatorial chord
135
- return Math.max(circleSagitta(w), 1 - Math.cos(Math.PI / (2 * hr)));
136
- }
137
-
138
- /** Torus of major radius 1 and tube radius `tube`, in major-radius units. */
139
- export function torusDeviation(tube, arcDeg, segments, tubeSegments) {
140
- const full = arcDeg >= 360 - 1e-9;
141
- const ts = Math.max(3, Math.round(segments * (full ? 1 : arcDeg / 360)) || 3);
142
- const rs = Math.max(3, tubeSegments);
143
- // The quad sags along the tube and along the sweep. Adding the two sagittas
144
- // bounds the distance to the true torus.
145
- return tube * circleSagitta(rs) + (1 + tube) * circleSagitta(ts);
146
- }
147
-
148
- /** Rounded box: the corner is a sphere of radius r, stepped by π/4 per segment. */
149
- export function roundedBoxDeviation(radius, seg) {
150
- const s = Math.max(1, seg);
151
- return radius * (1 - Math.cos(Math.PI / (4 * s)));
152
- }
153
-
154
- /**
155
- * Gear outline in module units. The flank and the tip arc are polylines;
156
- * the bound is the sagitta of the tip circle at the flank's step.
157
- */
158
- export function gearDeviation(teeth, flank) {
159
- const ra = teeth / 2 + 1;
160
- return ra * circleSagitta(Math.max(3, flank));
161
- }
162
-
163
- /** Flatten tolerances as a fraction of the path's control-box diagonal, per level. */
164
- export const PATH_TOL = [0.0006, 0.0025, 0.01, 0.035];
165
-
166
- /** Absolute flatten error for a path whose control box has this diagonal. */
167
- export function pathFlattenDeviation(diag, lod) {
168
- const d = diag > 0 && Number.isFinite(diag) ? diag : 1;
169
- const i = Math.min(PATH_TOL.length - 1, Math.max(0, lod));
170
- return d * PATH_TOL[i];
171
- }
172
-
173
- /** Quarter-round bevel of radius r with n segments. */
174
- export function bevelDeviation(radius, segments) {
175
- if (!(radius > 0)) return 0;
176
- const n = Math.max(1, segments);
177
- return radius * (1 - Math.cos(Math.PI / (2 * n)));
178
- }
1
+ // Chordal error of each generator, in the same units as its canonical mesh.
2
+ //
3
+ // The frame builder keeps a level only while this distance, scaled by the
4
+ // instance and projected, stays within one pixel. Bounds are the maximum
5
+ // distance from the mesh to the true surface. Analytic primitives are measured
6
+ // from the same vertex grid the tessellator builds. Curves use the flatten
7
+ // tolerance, which the flattener treats as a hard chordal limit.
8
+
9
+ import { sphereDeviationWasm } from '../wasm/kernels.js';
10
+
11
+ /** Sagitta of a unit circle drawn with n equal chords, as a fraction of the radius. */
12
+ export function circleSagitta(n) {
13
+ const s = Math.max(3, n);
14
+ return 1 - Math.cos(Math.PI / s);
15
+ }
16
+
17
+ function closestDistanceToOrigin(ax, ay, az, bx, by, bz, cx, cy, cz) {
18
+ const abx = bx - ax, aby = by - ay, abz = bz - az;
19
+ const acx = cx - ax, acy = cy - ay, acz = cz - az;
20
+ let nx = aby * acz - abz * acy;
21
+ let ny = abz * acx - abx * acz;
22
+ let nz = abx * acy - aby * acx;
23
+ const nl = Math.hypot(nx, ny, nz);
24
+ if (nl < 1e-14) return 1;
25
+ nx /= nl;
26
+ ny /= nl;
27
+ nz /= nl;
28
+ const d = ax * nx + ay * ny + az * nz;
29
+ const fx = d * nx, fy = d * ny, fz = d * nz;
30
+ const v0x = acx, v0y = acy, v0z = acz;
31
+ const v1x = abx, v1y = aby, v1z = abz;
32
+ const v2x = fx - ax, v2y = fy - ay, v2z = fz - az;
33
+ const d00 = v0x * v0x + v0y * v0y + v0z * v0z;
34
+ const d01 = v0x * v1x + v0y * v1y + v0z * v1z;
35
+ const d11 = v1x * v1x + v1y * v1y + v1z * v1z;
36
+ const d02 = v0x * v2x + v0y * v2y + v0z * v2z;
37
+ const d12 = v1x * v2x + v1y * v2y + v1z * v2z;
38
+ const det = d00 * d11 - d01 * d01;
39
+ if (Math.abs(det) > 1e-18) {
40
+ const inv = 1 / det;
41
+ const u = (d11 * d02 - d01 * d12) * inv;
42
+ const v = (d00 * d12 - d01 * d02) * inv;
43
+ if (u >= -1e-5 && v >= -1e-5 && u + v <= 1 + 1e-5) return Math.abs(d);
44
+ }
45
+ return Math.min(edgeDistance(ax, ay, az, bx, by, bz), edgeDistance(bx, by, bz, cx, cy, cz), edgeDistance(cx, cy, cz, ax, ay, az));
46
+ }
47
+
48
+ function edgeDistance(ax, ay, az, bx, by, bz) {
49
+ const abx = bx - ax, aby = by - ay, abz = bz - az;
50
+ const ab2 = abx * abx + aby * aby + abz * abz;
51
+ let t = ab2 > 0 ? -(ax * abx + ay * aby + az * abz) / ab2 : 0;
52
+ if (t < 0) t = 0;
53
+ else if (t > 1) t = 1;
54
+ return Math.hypot(ax + abx * t, ay + aby * t, az + abz * t);
55
+ }
56
+
57
+ function radialError(a, b, c) {
58
+ return 1 - closestDistanceToOrigin(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
59
+ }
60
+
61
+ const sphereDeviations = new Map();
62
+
63
+ /** Maximum radial error of the reference UV sphere at this segment count. */
64
+ export function sphereDeviation(segments) {
65
+ // every sphere element asks for the same few segment counts: measure each once
66
+ const w = Math.max(3, segments | 0);
67
+ let max = sphereDeviations.get(w);
68
+ if (max === undefined) {
69
+ max = measureSphereDeviation(w);
70
+ sphereDeviations.set(w, max);
71
+ }
72
+ return max;
73
+ }
74
+
75
+ function measureSphereDeviation(w) {
76
+ const v = sphereDeviationWasm(w);
77
+ if (v !== null) return v;
78
+ const hRings = Math.max(2, Math.round(w / 2));
79
+ const TAU = Math.PI * 2;
80
+ // the grid's sines and cosines, once per ring and per column
81
+ const st = new Float64Array(hRings + 1), ct = new Float64Array(hRings + 1);
82
+ for (let j = 0; j <= hRings; j++) {
83
+ const th = (j / hRings) * Math.PI;
84
+ st[j] = Math.sin(th);
85
+ ct[j] = Math.cos(th);
86
+ }
87
+ const cp = new Float64Array(w + 1), sp = new Float64Array(w + 1);
88
+ for (let i = 0; i <= w; i++) {
89
+ const ph = ((i % w) / w) * TAU;
90
+ cp[i] = Math.cos(ph);
91
+ sp[i] = Math.sin(ph);
92
+ }
93
+ let max = 0;
94
+ for (let j = 0; j < hRings; j++) {
95
+ for (let i = 0; i < w; i++) {
96
+ // quad corners a (i, j), b (i, j + 1), c (i + 1, j + 1), d (i + 1, j)
97
+ const ax = st[j] * cp[i], ay = ct[j], az = -st[j] * sp[i];
98
+ const bx = st[j + 1] * cp[i], by = ct[j + 1], bz = -st[j + 1] * sp[i];
99
+ const cx = st[j + 1] * cp[i + 1], cy = ct[j + 1], cz = -st[j + 1] * sp[i + 1];
100
+ const dx = st[j] * cp[i + 1], dy = ct[j], dz = -st[j] * sp[i + 1];
101
+ if (j !== hRings - 1) {
102
+ const e = 1 - closestDistanceToOrigin(ax, ay, az, bx, by, bz, cx, cy, cz);
103
+ if (e > max) max = e;
104
+ }
105
+ if (j !== 0) {
106
+ const e = 1 - closestDistanceToOrigin(ax, ay, az, cx, cy, cz, dx, dy, dz);
107
+ if (e > max) max = e;
108
+ }
109
+ }
110
+ }
111
+ return max;
112
+ }
113
+
114
+ /** Radial error of a stored unit sphere (binary32 vertex positions). */
115
+ export function sphereMeshError(positions, indices) {
116
+ let max = 0;
117
+ for (let t = 0; t < indices.length; t += 3) {
118
+ const at = (i) => [positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]];
119
+ const e = radialError(at(indices[t]), at(indices[t + 1]), at(indices[t + 2]));
120
+ if (e > max) max = e;
121
+ }
122
+ return max;
123
+ }
124
+
125
+ /** Cylinder or cone in canonical space (bottom radius 1, top radius `top`). */
126
+ export function cylinderDeviation(top, segments) {
127
+ return Math.max(1, top, 0) * circleSagitta(segments);
128
+ }
129
+
130
+ /** Capsule of tube radius 1. The straight section does not add radial error. */
131
+ export function capsuleDeviation(segments) {
132
+ const w = Math.max(3, segments | 0);
133
+ const hr = Math.max(1, Math.round(w / 4));
134
+ // latitude step over a quarter circle, and the equatorial chord
135
+ return Math.max(circleSagitta(w), 1 - Math.cos(Math.PI / (2 * hr)));
136
+ }
137
+
138
+ /** Torus of major radius 1 and tube radius `tube`, in major-radius units. */
139
+ export function torusDeviation(tube, arcDeg, segments, tubeSegments) {
140
+ const full = arcDeg >= 360 - 1e-9;
141
+ const ts = Math.max(3, Math.round(segments * (full ? 1 : arcDeg / 360)) || 3);
142
+ const rs = Math.max(3, tubeSegments);
143
+ // The quad sags along the tube and along the sweep. Adding the two sagittas
144
+ // bounds the distance to the true torus.
145
+ return tube * circleSagitta(rs) + (1 + tube) * circleSagitta(ts);
146
+ }
147
+
148
+ /** Rounded box: the corner is a sphere of radius r, stepped by π/4 per segment. */
149
+ export function roundedBoxDeviation(radius, seg) {
150
+ const s = Math.max(1, seg);
151
+ return radius * (1 - Math.cos(Math.PI / (4 * s)));
152
+ }
153
+
154
+ /**
155
+ * Gear outline in module units. The flank and the tip arc are polylines;
156
+ * the bound is the sagitta of the tip circle at the flank's step.
157
+ */
158
+ export function gearDeviation(teeth, flank) {
159
+ const ra = teeth / 2 + 1;
160
+ return ra * circleSagitta(Math.max(3, flank));
161
+ }
162
+
163
+ /** Flatten tolerances as a fraction of the path's control-box diagonal, per level. */
164
+ export const PATH_TOL = [0.0006, 0.0025, 0.01, 0.035];
165
+
166
+ /** Absolute flatten error for a path whose control box has this diagonal. */
167
+ export function pathFlattenDeviation(diag, lod) {
168
+ const d = diag > 0 && Number.isFinite(diag) ? diag : 1;
169
+ const i = Math.min(PATH_TOL.length - 1, Math.max(0, lod));
170
+ return d * PATH_TOL[i];
171
+ }
172
+
173
+ /** Quarter-round bevel of radius r with n segments. */
174
+ export function bevelDeviation(radius, segments) {
175
+ if (!(radius > 0)) return 0;
176
+ const n = Math.max(1, segments);
177
+ return radius * (1 - Math.cos(Math.PI / (2 * n)));
178
+ }