micro-gl 0.1.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.
Files changed (82) hide show
  1. package/README.md +556 -0
  2. package/package.json +37 -0
  3. package/src/2d/cameras/Camera2d.js +46 -0
  4. package/src/2d/constants.js +3 -0
  5. package/src/2d/controls/DragControls2d.js +174 -0
  6. package/src/2d/controls/PanZoomControls.js +64 -0
  7. package/src/2d/core/GpuResources2d.js +163 -0
  8. package/src/2d/core/InstancedShape2d.js +74 -0
  9. package/src/2d/core/Object2d.js +107 -0
  10. package/src/2d/core/Pipelines2d.js +110 -0
  11. package/src/2d/core/Renderer2d.js +353 -0
  12. package/src/2d/core/Scene2d.js +13 -0
  13. package/src/2d/core/Shape2d.js +13 -0
  14. package/src/2d/core/Uniforms2d.js +13 -0
  15. package/src/2d/geometries/CircleGeometry.js +27 -0
  16. package/src/2d/geometries/Geometry2d.js +108 -0
  17. package/src/2d/geometries/RectGeometry.js +23 -0
  18. package/src/2d/materials/BasicMaterial2d.js +12 -0
  19. package/src/2d/materials/Material2d.js +78 -0
  20. package/src/2d/materials/SpriteMaterial2d.js +28 -0
  21. package/src/2d/shaders/fragments.js +23 -0
  22. package/src/2d/shaders/shared.js +28 -0
  23. package/src/2d/shaders/vertexLayout.js +74 -0
  24. package/src/2d/shaders/vertexStages.js +59 -0
  25. package/src/3d/cameras/Camera.js +66 -0
  26. package/src/3d/cameras/OrthographicCamera.js +35 -0
  27. package/src/3d/cameras/PerspectiveCamera.js +25 -0
  28. package/src/3d/constants.js +18 -0
  29. package/src/3d/controls/DragControls.js +168 -0
  30. package/src/3d/controls/OrbitControls.js +125 -0
  31. package/src/3d/core/DirectionalShadowMap.js +283 -0
  32. package/src/3d/core/GpuResources.js +174 -0
  33. package/src/3d/core/InstanceMatrix.js +55 -0
  34. package/src/3d/core/InstancedBounds.js +114 -0
  35. package/src/3d/core/InstancedMesh.js +124 -0
  36. package/src/3d/core/Mesh.js +26 -0
  37. package/src/3d/core/Object3d.js +101 -0
  38. package/src/3d/core/Pipelines.js +125 -0
  39. package/src/3d/core/RayIntersection.js +184 -0
  40. package/src/3d/core/Raycaster.js +246 -0
  41. package/src/3d/core/Renderer.js +492 -0
  42. package/src/3d/core/Scene.js +13 -0
  43. package/src/3d/core/ShadowPipelines.js +64 -0
  44. package/src/3d/core/Uniforms.js +132 -0
  45. package/src/3d/geometries/BoxGeometry.js +56 -0
  46. package/src/3d/geometries/Geometry.js +97 -0
  47. package/src/3d/geometries/PlaneGeometry.js +24 -0
  48. package/src/3d/geometries/SphereGeometry.js +46 -0
  49. package/src/3d/geometries/WireframeGeometry.js +39 -0
  50. package/src/3d/helpers/GridHelper.js +43 -0
  51. package/src/3d/lights/AmbientLight.js +18 -0
  52. package/src/3d/lights/DirectionalLight.js +26 -0
  53. package/src/3d/lights/DirectionalShadow.js +29 -0
  54. package/src/3d/lights/PointLight.js +23 -0
  55. package/src/3d/materials/BasicMaterial.js +11 -0
  56. package/src/3d/materials/LambertMaterial.js +13 -0
  57. package/src/3d/materials/Material.js +87 -0
  58. package/src/3d/materials/TextureMaterial.js +24 -0
  59. package/src/3d/shaders/fragments.js +34 -0
  60. package/src/3d/shaders/shadows.js +52 -0
  61. package/src/3d/shaders/shared.js +129 -0
  62. package/src/3d/shaders/vertexLayout.js +85 -0
  63. package/src/3d/shaders/vertexStages.js +70 -0
  64. package/src/core/PointerControls.js +258 -0
  65. package/src/core/Texture.js +87 -0
  66. package/src/core/createMaterialPipelineLayouts.js +114 -0
  67. package/src/core/deviceLease.js +67 -0
  68. package/src/core/generateMipmaps.js +115 -0
  69. package/src/core/indexedTriangles.js +53 -0
  70. package/src/core/initWebGpu.js +65 -0
  71. package/src/core/materialResources.js +18 -0
  72. package/src/core/objectGpuResources.js +68 -0
  73. package/src/core/pipelineConstants.js +68 -0
  74. package/src/core/rendererConfig.js +66 -0
  75. package/src/core/uploadTexture.js +57 -0
  76. package/src/index.js +68 -0
  77. package/src/math/Frustum.js +143 -0
  78. package/src/math/Mat3.js +165 -0
  79. package/src/math/Mat4.js +359 -0
  80. package/src/math/Vec2.js +72 -0
  81. package/src/math/Vec3.js +98 -0
  82. package/src/math/color.js +20 -0
@@ -0,0 +1,246 @@
1
+ import { Vec3 } from '../../math/Vec3.js';
2
+ import { Mat4 } from '../../math/Mat4.js';
3
+ import {
4
+ DEFAULT_PRIMITIVE_TOPOLOGY,
5
+ isTriangleTopology,
6
+ } from '../../core/pipelineConstants.js';
7
+ import { INSTANCE_SIZE } from '../constants.js';
8
+ import { Mesh } from './Mesh.js';
9
+ import { copyAffineInstanceMatrix } from './InstanceMatrix.js';
10
+ import {
11
+ intersectIndexedGeometry,
12
+ intersectsBounds,
13
+ } from './RayIntersection.js';
14
+
15
+ const _invVP = new Mat4();
16
+ const _invWorld = new Mat4();
17
+ const _instanceMatrix = new Mat4();
18
+ const _combinedMatrix = new Mat4();
19
+ const _localOrigin = new Vec3();
20
+ const _localDirection = new Vec3();
21
+ const _farPoint = new Vec3();
22
+ const _worldDirection = new Vec3();
23
+
24
+ const DIRECTION_EPSILON = 1e-12;
25
+
26
+ function isVisibleInHierarchy(object) {
27
+ for (let current = object; current; current = current.parent) {
28
+ if (!current.visible) return false;
29
+ }
30
+ return true;
31
+ }
32
+
33
+ /**
34
+ * Casts a ray from the camera through a screen point and finds which
35
+ * indexed triangle surfaces it hits. Local bounds provide a fast broad
36
+ * phase; triangle tests determine the final hit. Instanced meshes are tested
37
+ * one transform at a time and report the matching `instanceId`.
38
+ *
39
+ * Matrices must be up to date, i.e. the scene must have been rendered
40
+ * (the renderer updates them every frame).
41
+ */
42
+ export class Raycaster {
43
+ constructor() {
44
+ this.origin = new Vec3();
45
+ this.direction = new Vec3();
46
+ /** Farthest distance accepted by intersectObjects(). */
47
+ this.maxDistance = Infinity;
48
+ }
49
+
50
+ /**
51
+ * Configures a world-space ray directly. `direction` may have any non-zero
52
+ * length; intersections normalize it without changing the public vector.
53
+ */
54
+ set(origin, direction, maxDistance = Infinity) {
55
+ this.origin.copy(origin);
56
+ this.direction.copy(direction);
57
+ this.maxDistance = maxDistance;
58
+ return this;
59
+ }
60
+
61
+ /**
62
+ * Builds the ray going through a screen point, for both perspective
63
+ * and orthographic cameras.
64
+ * @param {number} ndcX pointer x in normalized device coords (-1..1)
65
+ * @param {number} ndcY pointer y in normalized device coords (-1..1)
66
+ */
67
+ setFromCamera(ndcX, ndcY, camera) {
68
+ if (!_invVP.copy(camera.viewProjectionMatrix).tryInvert()) {
69
+ this.origin.set(0, 0, 0);
70
+ this.direction.set(0, 0, 0);
71
+ this.maxDistance = 0;
72
+ return this;
73
+ }
74
+
75
+ this.origin.set(ndcX, ndcY, 0).applyMat4(_invVP);
76
+ _farPoint.set(ndcX, ndcY, 1).applyMat4(_invVP);
77
+ this.direction.copy(_farPoint).sub(this.origin);
78
+ this.maxDistance = this.direction.length();
79
+ const valid =
80
+ Number.isFinite(this.origin.x) &&
81
+ Number.isFinite(this.origin.y) &&
82
+ Number.isFinite(this.origin.z) &&
83
+ Number.isFinite(this.maxDistance) &&
84
+ this.maxDistance > DIRECTION_EPSILON;
85
+ if (valid) {
86
+ this.direction.multiplyScalar(1 / this.maxDistance);
87
+ } else {
88
+ this.direction.set(0, 0, 0);
89
+ this.maxDistance = 0;
90
+ }
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Intersects the ray with an array of objects (meshes and/or scene-graph
96
+ * subtrees). Returns the nearest surface hit per mesh or per instance as
97
+ * `{ object, point, distance }`, globally sorted nearest first. Instanced
98
+ * hits additionally contain a zero-based `instanceId`.
99
+ */
100
+ intersectObjects(objects) {
101
+ const hits = [];
102
+ if (!prepareRay(this, _worldDirection)) return hits;
103
+ const visitedMeshes = new Set();
104
+
105
+ for (const root of objects) {
106
+ // Callers commonly pass pickable children directly, so account for
107
+ // invisible ancestors outside the traversed subtree as well.
108
+ if (!isVisibleInHierarchy(root)) continue;
109
+ root.traverseVisible((object) => {
110
+ if (
111
+ object instanceof Mesh &&
112
+ object.geometry &&
113
+ !visitedMeshes.has(object)
114
+ ) {
115
+ visitedMeshes.add(object);
116
+ this._appendMeshHits(object, hits);
117
+ }
118
+ });
119
+ }
120
+ hits.sort((a, b) => a.distance - b.distance);
121
+ return hits;
122
+ }
123
+
124
+ _appendMeshHits(mesh, hits) {
125
+ const topology =
126
+ mesh.material?.topology ?? DEFAULT_PRIMITIVE_TOPOLOGY;
127
+ if (!isTriangleTopology(topology)) return;
128
+
129
+ if (!mesh.isInstanced) {
130
+ const hit = this._intersectGeometry(mesh, mesh.worldMatrix, topology);
131
+ if (hit) hits.push(hit);
132
+ return;
133
+ }
134
+
135
+ const batchBounds = mesh.bounds;
136
+ if (
137
+ batchBounds &&
138
+ !this._intersectsTransformedBounds(batchBounds, mesh.worldMatrix)
139
+ ) {
140
+ return;
141
+ }
142
+
143
+ const availableInstances = Math.floor(
144
+ (mesh.instanceData?.length || 0) / INSTANCE_SIZE,
145
+ );
146
+ const instanceCount = Number.isInteger(mesh.count)
147
+ ? Math.min(Math.max(mesh.count, 0), availableInstances)
148
+ : 0;
149
+ for (let instanceId = 0; instanceId < instanceCount; instanceId++) {
150
+ if (
151
+ !copyAffineInstanceMatrix(
152
+ mesh.instanceData,
153
+ instanceId,
154
+ _instanceMatrix,
155
+ )
156
+ ) {
157
+ continue;
158
+ }
159
+ _combinedMatrix.multiplyMatrices(mesh.worldMatrix, _instanceMatrix);
160
+ const hit = this._intersectGeometry(mesh, _combinedMatrix, topology);
161
+ if (hit) {
162
+ hit.instanceId = instanceId;
163
+ hits.push(hit);
164
+ }
165
+ }
166
+ }
167
+
168
+ _intersectGeometry(mesh, transform, topology) {
169
+ if (!this._transformRay(transform)) return null;
170
+
171
+ const distance = intersectIndexedGeometry(
172
+ _localOrigin,
173
+ _localDirection,
174
+ mesh.geometry,
175
+ topology,
176
+ this.maxDistance,
177
+ );
178
+ if (distance === null) return null;
179
+
180
+ const point = new Vec3(
181
+ this.origin.x + _worldDirection.x * distance,
182
+ this.origin.y + _worldDirection.y * distance,
183
+ this.origin.z + _worldDirection.z * distance,
184
+ );
185
+ return { object: mesh, point, distance };
186
+ }
187
+
188
+ _intersectsTransformedBounds(bounds, transform) {
189
+ return (
190
+ this._transformRay(transform) &&
191
+ intersectsBounds(
192
+ _localOrigin,
193
+ _localDirection,
194
+ bounds,
195
+ this.maxDistance,
196
+ )
197
+ );
198
+ }
199
+
200
+ _transformRay(transform) {
201
+ if (!_invWorld.copy(transform).tryInvert()) return false;
202
+
203
+ // Transform a unit world-ray step into local space. Leaving the resulting
204
+ // direction unnormalized keeps intersection distances in world units,
205
+ // including under rotated and non-uniformly scaled transforms.
206
+ _localOrigin.copy(this.origin).applyMat4(_invWorld);
207
+ _localDirection
208
+ .set(
209
+ this.origin.x + _worldDirection.x,
210
+ this.origin.y + _worldDirection.y,
211
+ this.origin.z + _worldDirection.z,
212
+ )
213
+ .applyMat4(_invWorld)
214
+ .sub(_localOrigin);
215
+
216
+ return (
217
+ Number.isFinite(_localOrigin.x) &&
218
+ Number.isFinite(_localOrigin.y) &&
219
+ Number.isFinite(_localOrigin.z) &&
220
+ Number.isFinite(_localDirection.x) &&
221
+ Number.isFinite(_localDirection.y) &&
222
+ Number.isFinite(_localDirection.z)
223
+ );
224
+ }
225
+ }
226
+
227
+ function prepareRay(raycaster, targetDirection) {
228
+ const directionLength = raycaster.direction.length();
229
+ const validMaxDistance =
230
+ raycaster.maxDistance === Infinity ||
231
+ (Number.isFinite(raycaster.maxDistance) && raycaster.maxDistance >= 0);
232
+ if (
233
+ !Number.isFinite(raycaster.origin.x) ||
234
+ !Number.isFinite(raycaster.origin.y) ||
235
+ !Number.isFinite(raycaster.origin.z) ||
236
+ !Number.isFinite(directionLength) ||
237
+ directionLength <= DIRECTION_EPSILON ||
238
+ !validMaxDistance
239
+ ) {
240
+ return false;
241
+ }
242
+ targetDirection
243
+ .copy(raycaster.direction)
244
+ .multiplyScalar(1 / directionLength);
245
+ return true;
246
+ }