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.
- package/README.md +556 -0
- package/package.json +37 -0
- package/src/2d/cameras/Camera2d.js +46 -0
- package/src/2d/constants.js +3 -0
- package/src/2d/controls/DragControls2d.js +174 -0
- package/src/2d/controls/PanZoomControls.js +64 -0
- package/src/2d/core/GpuResources2d.js +163 -0
- package/src/2d/core/InstancedShape2d.js +74 -0
- package/src/2d/core/Object2d.js +107 -0
- package/src/2d/core/Pipelines2d.js +110 -0
- package/src/2d/core/Renderer2d.js +353 -0
- package/src/2d/core/Scene2d.js +13 -0
- package/src/2d/core/Shape2d.js +13 -0
- package/src/2d/core/Uniforms2d.js +13 -0
- package/src/2d/geometries/CircleGeometry.js +27 -0
- package/src/2d/geometries/Geometry2d.js +108 -0
- package/src/2d/geometries/RectGeometry.js +23 -0
- package/src/2d/materials/BasicMaterial2d.js +12 -0
- package/src/2d/materials/Material2d.js +78 -0
- package/src/2d/materials/SpriteMaterial2d.js +28 -0
- package/src/2d/shaders/fragments.js +23 -0
- package/src/2d/shaders/shared.js +28 -0
- package/src/2d/shaders/vertexLayout.js +74 -0
- package/src/2d/shaders/vertexStages.js +59 -0
- package/src/3d/cameras/Camera.js +66 -0
- package/src/3d/cameras/OrthographicCamera.js +35 -0
- package/src/3d/cameras/PerspectiveCamera.js +25 -0
- package/src/3d/constants.js +18 -0
- package/src/3d/controls/DragControls.js +168 -0
- package/src/3d/controls/OrbitControls.js +125 -0
- package/src/3d/core/DirectionalShadowMap.js +283 -0
- package/src/3d/core/GpuResources.js +174 -0
- package/src/3d/core/InstanceMatrix.js +55 -0
- package/src/3d/core/InstancedBounds.js +114 -0
- package/src/3d/core/InstancedMesh.js +124 -0
- package/src/3d/core/Mesh.js +26 -0
- package/src/3d/core/Object3d.js +101 -0
- package/src/3d/core/Pipelines.js +125 -0
- package/src/3d/core/RayIntersection.js +184 -0
- package/src/3d/core/Raycaster.js +246 -0
- package/src/3d/core/Renderer.js +492 -0
- package/src/3d/core/Scene.js +13 -0
- package/src/3d/core/ShadowPipelines.js +64 -0
- package/src/3d/core/Uniforms.js +132 -0
- package/src/3d/geometries/BoxGeometry.js +56 -0
- package/src/3d/geometries/Geometry.js +97 -0
- package/src/3d/geometries/PlaneGeometry.js +24 -0
- package/src/3d/geometries/SphereGeometry.js +46 -0
- package/src/3d/geometries/WireframeGeometry.js +39 -0
- package/src/3d/helpers/GridHelper.js +43 -0
- package/src/3d/lights/AmbientLight.js +18 -0
- package/src/3d/lights/DirectionalLight.js +26 -0
- package/src/3d/lights/DirectionalShadow.js +29 -0
- package/src/3d/lights/PointLight.js +23 -0
- package/src/3d/materials/BasicMaterial.js +11 -0
- package/src/3d/materials/LambertMaterial.js +13 -0
- package/src/3d/materials/Material.js +87 -0
- package/src/3d/materials/TextureMaterial.js +24 -0
- package/src/3d/shaders/fragments.js +34 -0
- package/src/3d/shaders/shadows.js +52 -0
- package/src/3d/shaders/shared.js +129 -0
- package/src/3d/shaders/vertexLayout.js +85 -0
- package/src/3d/shaders/vertexStages.js +70 -0
- package/src/core/PointerControls.js +258 -0
- package/src/core/Texture.js +87 -0
- package/src/core/createMaterialPipelineLayouts.js +114 -0
- package/src/core/deviceLease.js +67 -0
- package/src/core/generateMipmaps.js +115 -0
- package/src/core/indexedTriangles.js +53 -0
- package/src/core/initWebGpu.js +65 -0
- package/src/core/materialResources.js +18 -0
- package/src/core/objectGpuResources.js +68 -0
- package/src/core/pipelineConstants.js +68 -0
- package/src/core/rendererConfig.js +66 -0
- package/src/core/uploadTexture.js +57 -0
- package/src/index.js +68 -0
- package/src/math/Frustum.js +143 -0
- package/src/math/Mat3.js +165 -0
- package/src/math/Mat4.js +359 -0
- package/src/math/Vec2.js +72 -0
- package/src/math/Vec3.js +98 -0
- 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
|
+
}
|