@solidrt/3d 0.0.50 → 0.0.52
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/AGENTS.md +393 -87
- package/README.md +20 -10
- package/demos/README.md +15 -0
- package/demos/assets/icon.svg +23 -0
- package/demos/package.json +9 -0
- package/demos/src/the-third-dimension.tsx +866 -0
- package/demos/tsconfig.json +15 -0
- package/examples/README.md +34 -2
- package/examples/aim.tsx +5 -5
- package/examples/instanced.tsx +158 -0
- package/examples/lit.tsx +66 -0
- package/examples/model.glb +0 -0
- package/examples/model.tsx +51 -0
- package/examples/pick.tsx +5 -5
- package/examples/scene-background.tsx +3 -3
- package/examples/scene-basic.tsx +3 -3
- package/examples/scene-post-effect.tsx +3 -3
- package/examples/scene-views.tsx +95 -0
- package/examples/shadows.tsx +86 -0
- package/examples/sprites.tsx +95 -0
- package/examples/sweep-paths.tsx +11 -27
- package/package.json +5 -3
- package/src/components.tsx +171 -3
- package/src/geometry-gpu.ts +97 -0
- package/src/geometry.ts +413 -162
- package/src/glsl.ts +83 -3
- package/src/gltf.ts +437 -0
- package/src/index.ts +18 -11
- package/src/material.ts +373 -47
- package/src/math.ts +114 -0
- package/src/model-file.ts +122 -0
- package/src/model.ts +105 -0
- package/src/orbit.ts +13 -9
- package/src/order.ts +12 -5
- package/src/profile.ts +4 -8
- package/src/scene.ts +1270 -281
- package/src/sweep.ts +21 -36
- package/src/bvh.ts +0 -258
package/src/sweep.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// Uint32Array by vertex count - filleted profiles times many path points
|
|
9
9
|
// make dense outputs routine here.
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import type { Geometry } from "./geometry.ts"
|
|
11
|
+
import { STANDARD_FLOATS, packGeometry } from "./geometry.ts"
|
|
12
|
+
import type { Geometry, GeometryOptions } from "./geometry.ts"
|
|
13
13
|
import { add, cross, dot, normalize, scale, sub } from "./math.ts"
|
|
14
14
|
import type { Vec3 } from "./math.ts"
|
|
15
15
|
import { earClip, normalizeProfile, profileBounds, profileRing } from "./profile.ts"
|
|
@@ -58,7 +58,7 @@ function emitCap(
|
|
|
58
58
|
mirrorU: boolean,
|
|
59
59
|
flip: boolean,
|
|
60
60
|
): void {
|
|
61
|
-
let base = verts.length /
|
|
61
|
+
let base = verts.length / STANDARD_FLOATS
|
|
62
62
|
for (let i = 0; i < px.length; i++) {
|
|
63
63
|
let p = place(px[i]!, py[i]!)
|
|
64
64
|
let u = mirrorU ? (b.maxX - px[i]!) / b.w : (px[i]! - b.minX) / b.w
|
|
@@ -81,13 +81,10 @@ function emitCap(
|
|
|
81
81
|
* unit square like plane(); the -z cap mirrors u so its texture reads
|
|
82
82
|
* unmirrored from outside.
|
|
83
83
|
*/
|
|
84
|
-
export
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
bevel = 0,
|
|
88
|
-
bevelSegments = 4,
|
|
89
|
-
label?: string,
|
|
90
|
-
): Geometry {
|
|
84
|
+
export type ExtrudeOptions = GeometryOptions & { depth?: number; bevel?: number; bevelSegments?: number }
|
|
85
|
+
|
|
86
|
+
export function extrude(profile: Profile, options: ExtrudeOptions = {}): Geometry {
|
|
87
|
+
let { depth = 1, bevel = 0, bevelSegments = 4 } = options
|
|
91
88
|
let pts = normalizeProfile(profile)
|
|
92
89
|
let { entries, miterX, miterY } = profileRing(pts)
|
|
93
90
|
let h = depth / 2
|
|
@@ -155,11 +152,7 @@ export function extrude(
|
|
|
155
152
|
emitCap(verts, indices, cx, cy, tris, bounds, (x, y) => [x, y, h], [0, 0, 1], false, false)
|
|
156
153
|
emitCap(verts, indices, cx, cy, tris, bounds, (x, y) => [x, y, -h], [0, 0, -1], true, true)
|
|
157
154
|
|
|
158
|
-
return
|
|
159
|
-
vertices: new Float32Array(verts),
|
|
160
|
-
indices: packIndices(indices, verts.length / FLOATS_PER_VERTEX),
|
|
161
|
-
label,
|
|
162
|
-
}
|
|
155
|
+
return packGeometry(verts, indices, options)
|
|
163
156
|
}
|
|
164
157
|
|
|
165
158
|
/**
|
|
@@ -172,13 +165,10 @@ export function extrude(
|
|
|
172
165
|
* duplicated like torus), v = normalized distance around the profile;
|
|
173
166
|
* caps map the profile's bounding box, the end cap mirrored in u.
|
|
174
167
|
*/
|
|
175
|
-
export
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
angle = Math.PI * 2,
|
|
179
|
-
start = 0,
|
|
180
|
-
label?: string,
|
|
181
|
-
): Geometry {
|
|
168
|
+
export type LatheOptions = GeometryOptions & { segments?: number; angle?: number; start?: number }
|
|
169
|
+
|
|
170
|
+
export function lathe(profile: Profile, options: LatheOptions = {}): Geometry {
|
|
171
|
+
let { segments = 32, angle = Math.PI * 2, start = 0 } = options
|
|
182
172
|
if (!(angle > 0) || angle > Math.PI * 2 + 1e-9) throw new Error("Lathe angle must be in (0, 2*PI]")
|
|
183
173
|
let pts = normalizeProfile(profile)
|
|
184
174
|
let { entries } = profileRing(pts)
|
|
@@ -215,11 +205,7 @@ export function lathe(
|
|
|
215
205
|
}
|
|
216
206
|
}
|
|
217
207
|
|
|
218
|
-
return
|
|
219
|
-
vertices: new Float32Array(verts),
|
|
220
|
-
indices: packIndices(indices, verts.length / FLOATS_PER_VERTEX),
|
|
221
|
-
label,
|
|
222
|
-
}
|
|
208
|
+
return packGeometry(verts, indices, options)
|
|
223
209
|
}
|
|
224
210
|
|
|
225
211
|
/** A sweep-path point: `p` in world space, `smooth` to share one ring
|
|
@@ -351,7 +337,7 @@ export function pathFrames(path: SweepPath): PathFrames {
|
|
|
351
337
|
* mirrored in u. A near-reversal joint clamps its mitre stretch (4x,
|
|
352
338
|
* like the profile's own miter clamp) instead of flinging vertices.
|
|
353
339
|
*/
|
|
354
|
-
export function sweep(profile: Profile, path: SweepPath,
|
|
340
|
+
export function sweep(profile: Profile, path: SweepPath, options: GeometryOptions = {}): Geometry {
|
|
355
341
|
let pts = normalizeProfile(profile)
|
|
356
342
|
let { entries } = profileRing(pts)
|
|
357
343
|
let p = normalizePath(path)
|
|
@@ -362,7 +348,7 @@ export function sweep(profile: Profile, path: SweepPath, label?: string): Geomet
|
|
|
362
348
|
let verts: number[] = []
|
|
363
349
|
let indices: number[] = []
|
|
364
350
|
let emitRing = (pos: Vec3[], normals: Vec3[], v: number): number => {
|
|
365
|
-
let base = verts.length /
|
|
351
|
+
let base = verts.length / STANDARD_FLOATS
|
|
366
352
|
for (let k = 0; k < entries.length; k++) {
|
|
367
353
|
let q = pos[k]!
|
|
368
354
|
let m = normals[k]!
|
|
@@ -436,11 +422,7 @@ export function sweep(profile: Profile, path: SweepPath, label?: string): Geomet
|
|
|
436
422
|
emitCap(verts, indices, px, py, tris, bounds, capPlace(p[0]!.p, 0), scale(tangents[0]!, -1), false, false)
|
|
437
423
|
emitCap(verts, indices, px, py, tris, bounds, capPlace(p[n - 1]!.p, n - 2), tangents[n - 2]!, true, true)
|
|
438
424
|
|
|
439
|
-
return
|
|
440
|
-
vertices: new Float32Array(verts),
|
|
441
|
-
indices: packIndices(indices, verts.length / FLOATS_PER_VERTEX),
|
|
442
|
-
label,
|
|
443
|
-
}
|
|
425
|
+
return packGeometry(verts, indices, options)
|
|
444
426
|
}
|
|
445
427
|
|
|
446
428
|
/**
|
|
@@ -450,11 +432,14 @@ export function sweep(profile: Profile, path: SweepPath, label?: string): Geomet
|
|
|
450
432
|
* bend, smooth = continuous), and both ends get flat caps. UVs: u around
|
|
451
433
|
* the tube, v along the path.
|
|
452
434
|
*/
|
|
453
|
-
export
|
|
435
|
+
export type TubeOptions = GeometryOptions & { radius?: number; radialSegments?: number }
|
|
436
|
+
|
|
437
|
+
export function tube(path: SweepPath, options: TubeOptions = {}): Geometry {
|
|
438
|
+
let { radius = 0.5, radialSegments = 12, ...rest } = options
|
|
454
439
|
let profile: Profile = []
|
|
455
440
|
for (let i = 0; i < radialSegments; i++) {
|
|
456
441
|
let a = (i / radialSegments) * Math.PI * 2
|
|
457
442
|
profile.push({ p: [Math.cos(a) * radius, Math.sin(a) * radius], smooth: true })
|
|
458
443
|
}
|
|
459
|
-
return sweep(profile, path,
|
|
444
|
+
return sweep(profile, path, rest)
|
|
460
445
|
}
|
package/src/bvh.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
// A dynamic bounding-volume hierarchy - the physics-broadphase AABB tree
|
|
2
|
-
// (Box2D/Bullet lineage): a binary tree over the items' world boxes, NOT a
|
|
3
|
-
// subdivision of space. Leaves store FAT boxes (a margin around the tight
|
|
4
|
-
// bounds) so an item moving a little refits nothing; an item escaping its
|
|
5
|
-
// fat box is removed and re-inserted along the cheapest path (least area
|
|
6
|
-
// growth). A ray query walks O(log n) nodes instead of testing every item,
|
|
7
|
-
// which is what keeps per-pointer-move picking off the O(meshes) path.
|
|
8
|
-
//
|
|
9
|
-
// The scene keeps the tree current from its own sync walk: transforms that
|
|
10
|
-
// changed are exactly the leaves to update, so maintenance is O(changed)
|
|
11
|
-
// per frame - the same delta discipline as rendering, and a static scene
|
|
12
|
-
// pays nothing. Storage is flat parallel arrays indexed by node id (no
|
|
13
|
-
// per-node objects, no allocation at steady state past tree growth).
|
|
14
|
-
//
|
|
15
|
-
// Pure module by design: no GUI imports, so the differential check rig
|
|
16
|
-
// (checks/pick-check.ts) runs it headless on flux against a linear oracle.
|
|
17
|
-
|
|
18
|
-
/** Fat-margin fraction of a leaf's largest extent. Bigger = fewer
|
|
19
|
-
* re-inserts while moving, worse query pruning; 5% is the usual trade. */
|
|
20
|
-
const MARGIN = 0.05
|
|
21
|
-
|
|
22
|
-
type Visit<T> = (item: T) => void
|
|
23
|
-
|
|
24
|
-
export type Bvh<T> = {
|
|
25
|
-
/** Insert an item with its tight world box; returns the leaf handle. */
|
|
26
|
-
insert(item: T, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number
|
|
27
|
-
/** Update a leaf's tight box. Free while it stays inside the fat box;
|
|
28
|
-
* otherwise the leaf re-inserts. Returns true when it moved. */
|
|
29
|
-
update(leaf: number, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): boolean
|
|
30
|
-
/** Remove a leaf (the handle is dead afterwards). */
|
|
31
|
-
remove(leaf: number): void
|
|
32
|
-
/** Visit every item whose FAT box the ray hits (t >= 0). Broadphase
|
|
33
|
-
* only: the caller narrowphases against its own tight volumes. */
|
|
34
|
-
raycast(ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, visit: Visit<T>): void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Entry distance of a ray against a box: the smallest t >= 0 with
|
|
39
|
-
* origin + t * direction inside [min, max] (0 when the origin starts
|
|
40
|
-
* inside), or -1 for a miss. The direction need not be normalized - t is
|
|
41
|
-
* in units of its length, which is what keeps a ray transformed into a
|
|
42
|
-
* mesh's local space reporting world distances. Shared by the tree's
|
|
43
|
-
* broadphase and the scene's narrowphase.
|
|
44
|
-
*/
|
|
45
|
-
export function rayBoxDistance(
|
|
46
|
-
ox: number, oy: number, oz: number,
|
|
47
|
-
dx: number, dy: number, dz: number,
|
|
48
|
-
minX: number, minY: number, minZ: number,
|
|
49
|
-
maxX: number, maxY: number, maxZ: number,
|
|
50
|
-
): number {
|
|
51
|
-
let tNear = 0
|
|
52
|
-
let tFar = Infinity
|
|
53
|
-
// Per axis: a zero direction component never crosses the slab, so the
|
|
54
|
-
// origin must already be inside it (the multiply-by-inverse shortcut
|
|
55
|
-
// turns that case into NaN, hence the explicit branch).
|
|
56
|
-
if (dx === 0) {
|
|
57
|
-
if (ox < minX || ox > maxX) return -1
|
|
58
|
-
} else {
|
|
59
|
-
let inv = 1 / dx
|
|
60
|
-
let t1 = (minX - ox) * inv
|
|
61
|
-
let t2 = (maxX - ox) * inv
|
|
62
|
-
if (t1 > t2) { let t = t1; t1 = t2; t2 = t }
|
|
63
|
-
if (t1 > tNear) tNear = t1
|
|
64
|
-
if (t2 < tFar) tFar = t2
|
|
65
|
-
}
|
|
66
|
-
if (dy === 0) {
|
|
67
|
-
if (oy < minY || oy > maxY) return -1
|
|
68
|
-
} else {
|
|
69
|
-
let inv = 1 / dy
|
|
70
|
-
let t1 = (minY - oy) * inv
|
|
71
|
-
let t2 = (maxY - oy) * inv
|
|
72
|
-
if (t1 > t2) { let t = t1; t1 = t2; t2 = t }
|
|
73
|
-
if (t1 > tNear) tNear = t1
|
|
74
|
-
if (t2 < tFar) tFar = t2
|
|
75
|
-
}
|
|
76
|
-
if (dz === 0) {
|
|
77
|
-
if (oz < minZ || oz > maxZ) return -1
|
|
78
|
-
} else {
|
|
79
|
-
let inv = 1 / dz
|
|
80
|
-
let t1 = (minZ - oz) * inv
|
|
81
|
-
let t2 = (maxZ - oz) * inv
|
|
82
|
-
if (t1 > t2) { let t = t1; t1 = t2; t2 = t }
|
|
83
|
-
if (t1 > tNear) tNear = t1
|
|
84
|
-
if (t2 < tFar) tFar = t2
|
|
85
|
-
}
|
|
86
|
-
return tFar >= tNear ? tNear : -1
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function createBvh<T>(): Bvh<T> {
|
|
90
|
-
// Node storage, 6 bounds floats per node; child1 === -1 marks a leaf.
|
|
91
|
-
let bounds: number[] = []
|
|
92
|
-
let parent: number[] = []
|
|
93
|
-
let child1: number[] = []
|
|
94
|
-
let child2: number[] = []
|
|
95
|
-
let items: (T | undefined)[] = []
|
|
96
|
-
let free: number[] = []
|
|
97
|
-
let root = -1
|
|
98
|
-
// Traversal stack, reused across queries.
|
|
99
|
-
let stack: number[] = []
|
|
100
|
-
|
|
101
|
-
let allocate = (): number => {
|
|
102
|
-
let node = free.pop()
|
|
103
|
-
if (node === undefined) {
|
|
104
|
-
node = parent.length
|
|
105
|
-
bounds.push(0, 0, 0, 0, 0, 0)
|
|
106
|
-
parent.push(-1)
|
|
107
|
-
child1.push(-1)
|
|
108
|
-
child2.push(-1)
|
|
109
|
-
items.push(undefined)
|
|
110
|
-
}
|
|
111
|
-
return node
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Half the surface area - the SAH cost of a node's box. Degenerate
|
|
115
|
-
// (flat) boxes still cost via their other faces, which is what makes
|
|
116
|
-
// the heuristic sane for planes.
|
|
117
|
-
let area = (n: number): number => {
|
|
118
|
-
let i = n * 6
|
|
119
|
-
let w = bounds[i + 3]! - bounds[i]!
|
|
120
|
-
let h = bounds[i + 4]! - bounds[i + 1]!
|
|
121
|
-
let d = bounds[i + 5]! - bounds[i + 2]!
|
|
122
|
-
return w * (h + d) + h * d
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
let unionArea = (n: number, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number => {
|
|
126
|
-
let i = n * 6
|
|
127
|
-
let w = Math.max(bounds[i + 3]!, maxX) - Math.min(bounds[i]!, minX)
|
|
128
|
-
let h = Math.max(bounds[i + 4]!, maxY) - Math.min(bounds[i + 1]!, minY)
|
|
129
|
-
let d = Math.max(bounds[i + 5]!, maxZ) - Math.min(bounds[i + 2]!, minZ)
|
|
130
|
-
return w * (h + d) + h * d
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Recompute an internal node's box as the union of its children.
|
|
134
|
-
let refit = (n: number): void => {
|
|
135
|
-
let a = child1[n]! * 6
|
|
136
|
-
let b = child2[n]! * 6
|
|
137
|
-
let i = n * 6
|
|
138
|
-
bounds[i] = Math.min(bounds[a]!, bounds[b]!)
|
|
139
|
-
bounds[i + 1] = Math.min(bounds[a + 1]!, bounds[b + 1]!)
|
|
140
|
-
bounds[i + 2] = Math.min(bounds[a + 2]!, bounds[b + 2]!)
|
|
141
|
-
bounds[i + 3] = Math.max(bounds[a + 3]!, bounds[b + 3]!)
|
|
142
|
-
bounds[i + 4] = Math.max(bounds[a + 4]!, bounds[b + 4]!)
|
|
143
|
-
bounds[i + 5] = Math.max(bounds[a + 5]!, bounds[b + 5]!)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let insertLeaf = (leaf: number): void => {
|
|
147
|
-
if (root === -1) {
|
|
148
|
-
root = leaf
|
|
149
|
-
parent[leaf] = -1
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
let i = leaf * 6
|
|
153
|
-
let minX = bounds[i]!, minY = bounds[i + 1]!, minZ = bounds[i + 2]!
|
|
154
|
-
let maxX = bounds[i + 3]!, maxY = bounds[i + 4]!, maxZ = bounds[i + 5]!
|
|
155
|
-
// Descend toward the sibling that grows the least (the classic
|
|
156
|
-
// incremental surface-area heuristic).
|
|
157
|
-
let sibling = root
|
|
158
|
-
while (child1[sibling] !== -1) {
|
|
159
|
-
let a = child1[sibling]!
|
|
160
|
-
let b = child2[sibling]!
|
|
161
|
-
let costA = unionArea(a, minX, minY, minZ, maxX, maxY, maxZ) - area(a)
|
|
162
|
-
let costB = unionArea(b, minX, minY, minZ, maxX, maxY, maxZ) - area(b)
|
|
163
|
-
sibling = costA < costB ? a : b
|
|
164
|
-
}
|
|
165
|
-
let oldParent = parent[sibling]!
|
|
166
|
-
let newParent = allocate()
|
|
167
|
-
parent[newParent] = oldParent
|
|
168
|
-
child1[newParent] = sibling
|
|
169
|
-
child2[newParent] = leaf
|
|
170
|
-
items[newParent] = undefined
|
|
171
|
-
parent[sibling] = newParent
|
|
172
|
-
parent[leaf] = newParent
|
|
173
|
-
if (oldParent === -1) {
|
|
174
|
-
root = newParent
|
|
175
|
-
} else if (child1[oldParent] === sibling) {
|
|
176
|
-
child1[oldParent] = newParent
|
|
177
|
-
} else {
|
|
178
|
-
child2[oldParent] = newParent
|
|
179
|
-
}
|
|
180
|
-
for (let n = newParent; n !== -1; n = parent[n]!) refit(n)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let removeLeaf = (leaf: number): void => {
|
|
184
|
-
if (leaf === root) {
|
|
185
|
-
root = -1
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
let p = parent[leaf]!
|
|
189
|
-
let sibling = child1[p] === leaf ? child2[p]! : child1[p]!
|
|
190
|
-
let grand = parent[p]!
|
|
191
|
-
parent[sibling] = grand
|
|
192
|
-
if (grand === -1) {
|
|
193
|
-
root = sibling
|
|
194
|
-
} else {
|
|
195
|
-
if (child1[grand] === p) child1[grand] = sibling
|
|
196
|
-
else child2[grand] = sibling
|
|
197
|
-
for (let n = grand; n !== -1; n = parent[n]!) refit(n)
|
|
198
|
-
}
|
|
199
|
-
child1[p] = -1
|
|
200
|
-
items[p] = undefined
|
|
201
|
-
free.push(p)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let setFat = (leaf: number, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): void => {
|
|
205
|
-
let m = MARGIN * Math.max(maxX - minX, maxY - minY, maxZ - minZ)
|
|
206
|
-
let i = leaf * 6
|
|
207
|
-
bounds[i] = minX - m
|
|
208
|
-
bounds[i + 1] = minY - m
|
|
209
|
-
bounds[i + 2] = minZ - m
|
|
210
|
-
bounds[i + 3] = maxX + m
|
|
211
|
-
bounds[i + 4] = maxY + m
|
|
212
|
-
bounds[i + 5] = maxZ + m
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
insert(item, minX, minY, minZ, maxX, maxY, maxZ) {
|
|
217
|
-
let leaf = allocate()
|
|
218
|
-
items[leaf] = item
|
|
219
|
-
setFat(leaf, minX, minY, minZ, maxX, maxY, maxZ)
|
|
220
|
-
insertLeaf(leaf)
|
|
221
|
-
return leaf
|
|
222
|
-
},
|
|
223
|
-
update(leaf, minX, minY, minZ, maxX, maxY, maxZ) {
|
|
224
|
-
let i = leaf * 6
|
|
225
|
-
if (
|
|
226
|
-
bounds[i]! <= minX && bounds[i + 1]! <= minY && bounds[i + 2]! <= minZ &&
|
|
227
|
-
bounds[i + 3]! >= maxX && bounds[i + 4]! >= maxY && bounds[i + 5]! >= maxZ
|
|
228
|
-
) {
|
|
229
|
-
return false
|
|
230
|
-
}
|
|
231
|
-
removeLeaf(leaf)
|
|
232
|
-
setFat(leaf, minX, minY, minZ, maxX, maxY, maxZ)
|
|
233
|
-
insertLeaf(leaf)
|
|
234
|
-
return true
|
|
235
|
-
},
|
|
236
|
-
remove(leaf) {
|
|
237
|
-
removeLeaf(leaf)
|
|
238
|
-
items[leaf] = undefined
|
|
239
|
-
free.push(leaf)
|
|
240
|
-
},
|
|
241
|
-
raycast(ox, oy, oz, dx, dy, dz, visit) {
|
|
242
|
-
if (root === -1) return
|
|
243
|
-
stack.length = 0
|
|
244
|
-
stack.push(root)
|
|
245
|
-
while (stack.length > 0) {
|
|
246
|
-
let n = stack.pop()!
|
|
247
|
-
let i = n * 6
|
|
248
|
-
let t = rayBoxDistance(ox, oy, oz, dx, dy, dz, bounds[i]!, bounds[i + 1]!, bounds[i + 2]!, bounds[i + 3]!, bounds[i + 4]!, bounds[i + 5]!)
|
|
249
|
-
if (t < 0) continue
|
|
250
|
-
if (child1[n] === -1) {
|
|
251
|
-
visit(items[n] as T)
|
|
252
|
-
} else {
|
|
253
|
-
stack.push(child1[n]!, child2[n]!)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
}
|
|
258
|
-
}
|