@es3d/es3d 0.0.0-stage → 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 (82) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +85 -2
  3. package/docs/REFERENCE.md +581 -0
  4. package/docs/SPEC.md +417 -0
  5. package/package.json +50 -4
  6. package/scripts/es3d-cli.mjs +377 -0
  7. package/src/cache/geometry-store.js +392 -0
  8. package/src/cache/mesh-bundle.js +123 -0
  9. package/src/cache/sha256.js +73 -0
  10. package/src/format/ast.js +238 -0
  11. package/src/format/colors.js +93 -0
  12. package/src/format/compact-parser.js +171 -0
  13. package/src/format/compact-tokenizer.js +165 -0
  14. package/src/format/errors.js +247 -0
  15. package/src/format/json.js +55 -0
  16. package/src/format/limits.js +14 -0
  17. package/src/format/parse.js +92 -0
  18. package/src/format/schema.js +617 -0
  19. package/src/format/serializer.js +333 -0
  20. package/src/format/validator.js +330 -0
  21. package/src/format/value-error.js +12 -0
  22. package/src/format/value-format.js +314 -0
  23. package/src/format/values.js +459 -0
  24. package/src/format/xml-parser.js +136 -0
  25. package/src/format/xml-tokenizer.js +237 -0
  26. package/src/geometry/contour.js +86 -0
  27. package/src/geometry/extrude.js +175 -0
  28. package/src/geometry/gear.js +85 -0
  29. package/src/geometry/geometry-cache.js +159 -0
  30. package/src/geometry/lod-error.js +178 -0
  31. package/src/geometry/mesh.js +458 -0
  32. package/src/geometry/primitives.js +299 -0
  33. package/src/geometry/registry.js +645 -0
  34. package/src/geometry/revolve.js +136 -0
  35. package/src/geometry/sweep.js +441 -0
  36. package/src/index.js +47 -0
  37. package/src/io/compression.js +67 -0
  38. package/src/io/convert.js +236 -0
  39. package/src/io/download.js +13 -0
  40. package/src/io/external-scene.js +127 -0
  41. package/src/io/file-loader.js +28 -0
  42. package/src/io/gltf.js +630 -0
  43. package/src/io/mesh-import.js +104 -0
  44. package/src/io/obj.js +352 -0
  45. package/src/io/step-brep.js +439 -0
  46. package/src/io/step-exact.js +1149 -0
  47. package/src/io/step.js +1418 -0
  48. package/src/io/stl.js +193 -0
  49. package/src/io/svg-import.js +787 -0
  50. package/src/material/materials.js +154 -0
  51. package/src/math/mat4.js +475 -0
  52. package/src/math/random.js +30 -0
  53. package/src/math/trig.js +65 -0
  54. package/src/math/vec3.js +81 -0
  55. package/src/optimize/optimizer.js +579 -0
  56. package/src/path/path-flatten.js +322 -0
  57. package/src/path/path-parser.js +332 -0
  58. package/src/path/shapes.js +129 -0
  59. package/src/path/triangulate.js +517 -0
  60. package/src/pipeline.js +74 -0
  61. package/src/render/camera.js +168 -0
  62. package/src/render/frame-builder.js +362 -0
  63. package/src/render/geometry-pool.js +149 -0
  64. package/src/render/lighting.js +149 -0
  65. package/src/render/orbit-controls.js +237 -0
  66. package/src/render/renderer.js +258 -0
  67. package/src/render/scroll-timeline.js +104 -0
  68. package/src/render/shaders/glsl.js +285 -0
  69. package/src/render/shaders/wgsl.js +252 -0
  70. package/src/render/stage.js +364 -0
  71. package/src/render/texture-cache.js +40 -0
  72. package/src/render/textures.js +223 -0
  73. package/src/render/webgl2/webgl2-backend.js +586 -0
  74. package/src/render/webgpu/webgpu-backend.js +482 -0
  75. package/src/scene/animation.js +171 -0
  76. package/src/scene/camera-constraints.js +117 -0
  77. package/src/scene/flatten.js +754 -0
  78. package/src/scene/picking.js +161 -0
  79. package/src/scene/scene-graph.js +320 -0
  80. package/src/transform/transform.js +211 -0
  81. package/src/wasm/kernels-binary.js +3 -0
  82. package/src/wasm/kernels.js +385 -0
@@ -0,0 +1,117 @@
1
+ // Camera limits and collision for interactive viewers (<controls>).
2
+ //
3
+ // Limits keep the orbit target in a box, the camera within a distance range
4
+ // and between two angles from straight up. Collision stops a moving camera in
5
+ // front of the first surface it would pass through: the move from the last
6
+ // camera position to the new one is ray cast against the scene, and only
7
+ // surfaces whose front side faces the camera block it. So a camera that starts
8
+ // inside an object can still leave it, an orbit target inside a product does
9
+ // not trap the camera, and a camera outside cannot enter. Instances under
10
+ // collide="false" are ignored.
11
+ //
12
+ // Nothing here touches the DOM; OrbitControls calls constrainCamera after
13
+ // every drag, pinch and wheel step.
14
+
15
+ import { raycast } from './picking.js';
16
+ import { FLAG_NO_COLLIDE } from './flatten.js';
17
+
18
+ const DEG = Math.PI / 180;
19
+ const POLE = 1e-3; // keeps the view direction off the up axis, where lookAt is undefined
20
+
21
+ /** Keep the target in its box and the camera within the distance and angle limits. */
22
+ export function applyLimits(camera, limits) {
23
+ if (!limits) return;
24
+ const p = camera.position, t = camera.target;
25
+ if (limits.targetMin && limits.targetMax) {
26
+ for (let k = 0; k < 3; k++) {
27
+ const c = Math.min(limits.targetMax[k], Math.max(limits.targetMin[k], t[k]));
28
+ p[k] += c - t[k];
29
+ t[k] = c;
30
+ }
31
+ }
32
+ const off = [p[0] - t[0], p[1] - t[1], p[2] - t[2]];
33
+ const r = Math.hypot(off[0], off[1], off[2]);
34
+ if (r < 1e-12) return;
35
+ let phi = Math.acos(Math.max(-1, Math.min(1, off[1] / r)));
36
+ const theta = Math.atan2(off[0], off[2]);
37
+ const minPolar = limits.minPolar ?? 0;
38
+ const maxPolar = limits.maxPolar ?? 180;
39
+ const angled = minPolar > 0 || maxPolar < 180;
40
+ if (angled) phi = Math.min(Math.min(maxPolar * DEG, Math.PI - POLE), Math.max(Math.max(minPolar * DEG, POLE), phi));
41
+ let d = r;
42
+ if (limits.minDistance != null) d = Math.max(d, limits.minDistance);
43
+ if (limits.maxDistance != null) d = Math.min(d, limits.maxDistance);
44
+ if (!angled && d === r) return;
45
+ const s = Math.sin(phi);
46
+ p[0] = t[0] + d * s * Math.sin(theta);
47
+ p[1] = t[1] + d * Math.cos(phi);
48
+ p[2] = t[2] + d * s * Math.cos(theta);
49
+ }
50
+
51
+ /** Distance kept between the camera and a surface: enough that the near plane does not cut into it. */
52
+ export function collisionMargin(camera, scene) {
53
+ const b = scene.bounds;
54
+ const R = Math.hypot(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]) / 2 || 1;
55
+ // the near plane's corners lie up to ~1.5 near distances from the eye at common fields of view
56
+ return Math.max((camera.currentNear || 0) * 2, R * 0.01);
57
+ }
58
+
59
+ /**
60
+ * Stop a camera move at the first surface it would pass through.
61
+ * @param {{position:number[], target:number[]}} prev camera before the move (assumed valid)
62
+ * @param {import('../render/camera.js').Camera} camera camera after the move; changed in place
63
+ * @param {{scene: object, meshOf: Function, isVisible?: Function, margin?: number}} world
64
+ * @returns {boolean} true when the move was shortened
65
+ */
66
+ export function sweepCamera(prev, camera, world) {
67
+ const { scene, meshOf } = world;
68
+ if (!scene || !scene.count) return false;
69
+ const margin = world.margin ?? collisionMargin(camera, scene);
70
+ const targetMove = [camera.target[0] - prev.target[0], camera.target[1] - prev.target[1], camera.target[2] - prev.target[2]];
71
+ let from = prev.position.slice();
72
+ let move = [camera.position[0] - from[0], camera.position[1] - from[1], camera.position[2] - from[2]];
73
+ const total = Math.hypot(move[0], move[1], move[2]);
74
+ if (total < 1e-12) return false;
75
+ let blocked = false;
76
+ let travelled = 0; // fraction of the requested move that was made, for the target
77
+ // the first stop leaves the rest of the move; its part along the surface is
78
+ // tried once more, so a drag that grazes a wall slides along it
79
+ for (let pass = 0; pass < 2; pass++) {
80
+ const len = Math.hypot(move[0], move[1], move[2]);
81
+ if (len < 1e-12) break;
82
+ const dir = [move[0] / len, move[1] / len, move[2] / len];
83
+ const hit = raycast(scene, meshOf, from, dir, { isVisible: world.isVisible, maxDistance: len + margin * 4, skipFlags: FLAG_NO_COLLIDE, facing: true });
84
+ // approaching at a grazing angle needs a longer stop along the ray for the same clearance
85
+ const cos = hit ? Math.abs(dir[0] * hit.normal[0] + dir[1] * hit.normal[1] + dir[2] * hit.normal[2]) : 0;
86
+ const allowed = hit ? Math.max(0, hit.distance - margin / Math.max(cos, 0.25)) : len;
87
+ const s = Math.min(1, allowed / len);
88
+ for (let k = 0; k < 3; k++) from[k] += move[k] * s;
89
+ if (pass === 0) travelled = s;
90
+ if (s >= 1) break;
91
+ blocked = true;
92
+ const rest = move.map((m) => m * (1 - s));
93
+ const into = rest[0] * hit.normal[0] + rest[1] * hit.normal[1] + rest[2] * hit.normal[2];
94
+ move = rest.map((m, k) => m - hit.normal[k] * into);
95
+ }
96
+ if (!blocked) return false;
97
+ // a pan moves target and camera together: keep doing so, so the view direction is unchanged
98
+ const pan = Math.hypot(targetMove[0] - (camera.position[0] - prev.position[0]), targetMove[1] - (camera.position[1] - prev.position[1]), targetMove[2] - (camera.position[2] - prev.position[2])) < total * 1e-6;
99
+ for (let k = 0; k < 3; k++) {
100
+ camera.target[k] = prev.target[k] + (pan ? from[k] - prev.position[k] : targetMove[k] * travelled);
101
+ camera.position[k] = from[k];
102
+ }
103
+ return true;
104
+ }
105
+
106
+ /**
107
+ * Apply <controls> limits, then collision when it is on.
108
+ * @param {{position:number[], target:number[]}} prev
109
+ * @param {import('../render/camera.js').Camera} camera
110
+ * @param {object|null} limits scene.controls
111
+ * @param {{scene: object, meshOf: Function, isVisible?: Function}|null} world
112
+ */
113
+ export function constrainCamera(prev, camera, limits, world) {
114
+ applyLimits(camera, limits);
115
+ if (limits && limits.collide && world) return sweepCamera(prev, camera, world);
116
+ return false;
117
+ }