-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathindex.js
260 lines (222 loc) · 8.14 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/**
* Represents octree data structure
*
* https://en.wikipedia.org/wiki/Octree
*/
var Bounds3 = require('./lib/bounds3.js');
var TreeNode = require('./lib/treeNode.js');
var EmptyRegion = new Bounds3();
var asyncFor = require('rafor');
module.exports = createTree;
function createTree(options) {
options = options || {};
var noPoints = [];
var root;
var originalArray;
var api = {
/**
* Initializes tree asynchronously. Very useful when you have millions
* of points and do not want to block rendering thread for too long.
*
* @param {number[]} points array of points for which we are building the
* tree. Flat sequence of (x, y, z) coordinates. Array length should be
* multiple of 3.
*
* @param {Function=} doneCallback called when tree is initialized. The
* callback will be called with single argument which represent current
* tree.
*/
initAsync: initAsync,
/**
* Synchronous version of `initAsync()`. Should only be used for small
* trees (less than 50-70k of points).
*
* @param {number[]} points array of points for which we are building the
* tree. Flat sequence of (x, y, z) coordinates. Array length should be
* multiple of 3.
*/
init: init,
/**
* Gets bounds of the root node. Bounds are represented by center of the
* node (x, y, z) and `half` attribute - distance from the center to an
* edge of the root node.
*/
bounds: getBounds,
/**
* Fires a ray from `rayOrigin` into `rayDirection` and collects all points
* that lie in the octants intersected by the ray.
*
* This method implements An Efficient Parametric Algorithm for Octree Traversal
* described in http://wscg.zcu.cz/wscg2000/Papers_2000/X31.pdf
*
* @param {Vector3} rayOrigin x,y,z coordinates where ray starts
* @param {Vector3} rayDirection normalized x,y,z direction where ray shoots.
* @param {number+} near minimum distance from the ray origin. 0 by default.
* @param {number+} far maximum length of the ray. POSITIVE_INFINITY by default
*
* @return {Array} of indices in the source array. Each index represnts a start
* of the x,y,z triplet of a point, that lies in the intersected octant.
*/
intersectRay: intersectRay,
/**
* Once you have collected points from the octants intersected by a ray
* (`intersectRay()` method), it may be worth to query points from the surrouning
* area.
*/
intersectSphere: intersectSphere,
/**
* Gets root node of the tree
*/
getRoot: getRoot
};
return api;
function getRoot() {
return root;
}
function intersectSphere(cx, cy, cz, r) {
if (!root) {
// Most likely we are not initialized yet
return noPoints;
}
var indices = [];
var r2 = r * r;
root.query(indices, originalArray, intersectCheck, preciseCheck);
return indices;
// http://stackoverflow.com/questions/4578967/cube-sphere-intersection-test
function intersectCheck(candidate) {
var dist = r2;
var half = candidate.half;
if (cx < candidate.x - half) dist -= sqr(cx - (candidate.x - half));
else if (cx > candidate.x + half) dist -= sqr(cx - (candidate.x + half));
if (cy < candidate.y - half) dist -= sqr(cy - (candidate.y - half));
else if (cy > candidate.y + half) dist -= sqr(cy - (candidate.y + half));
if (cz < candidate.z - half) dist -= sqr(cz - (candidate.z - half));
else if (cz > candidate.z + half) dist -= sqr(cz - (candidate.z + half));
return dist > 0;
}
function preciseCheck(x, y, z) {
return sqr(x - cx) + sqr(y - cy) + sqr(z - cz) < r2;
}
}
function sqr(x) {
return x * x;
}
function intersectRay(rayOrigin, rayDirection, near, far) {
if (!root) {
// Most likely we are not initialized yet
return noPoints;
}
if (near === undefined) near = 0;
if (far === undefined) far = Number.POSITIVE_INFINITY;
// we save as squar, to avoid expensive sqrt() operation
near *= near;
far *= far;
var indices = [];
root.query(indices, originalArray, intersectCheck, farEnough);
return indices.sort(byDistanceToCamera);
function intersectCheck(candidate) {
// using http://wscg.zcu.cz/wscg2000/Papers_2000/X31.pdf
var half = candidate.half;
var t1 = (candidate.x - half - rayOrigin.x) / rayDirection.x,
t2 = (candidate.x + half - rayOrigin.x) / rayDirection.x,
t3 = (candidate.y + half - rayOrigin.y) / rayDirection.y,
t4 = (candidate.y - half - rayOrigin.y) / rayDirection.y,
t5 = (candidate.z - half - rayOrigin.z) / rayDirection.z,
t6 = (candidate.z + half - rayOrigin.z) / rayDirection.z,
tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)),
tmin;
if (tmax < 0) return false;
tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
return tmin <= tmax && tmin <= far;
}
function farEnough(x, y, z) {
var dist = (x - rayOrigin.x) * (x - rayOrigin.x) +
(y - rayOrigin.y) * (y - rayOrigin.y) +
(z - rayOrigin.z) * (z - rayOrigin.z);
return near <= dist && dist <= far;
}
function byDistanceToCamera(idx0, idx1) {
var x0 = rayOrigin[idx0];
var y0 = rayOrigin[idx0 + 1];
var z0 = rayOrigin[idx0 + 2];
var dist0 = (x0 - rayOrigin.x) * (x0 - rayOrigin.x) +
(y0 - rayOrigin.y) * (y0 - rayOrigin.y) +
(z0 - rayOrigin.z) * (z0 - rayOrigin.z);
var x1 = rayOrigin[idx1];
var y1 = rayOrigin[idx1 + 1];
var z1 = rayOrigin[idx1 + 2];
var dist1 = (x1 - rayOrigin.x) * (x1 - rayOrigin.x) +
(y1 - rayOrigin.y) * (y1 - rayOrigin.y) +
(z1 - rayOrigin.z) * (z1 - rayOrigin.z);
return dist0 - dist1;
}
}
function init(points) {
verifyPointsInvariant(points);
originalArray = points;
root = createRootNode(points);
for (var i = 0; i < points.length; i += 3) {
root.insert(i, originalArray, 0);
}
}
function initAsync(points, doneCallback) {
verifyPointsInvariant(points);
var tempRoot = createRootNode(points);
asyncFor(points, insertToRoot, doneInternal, { step: 3 });
function insertToRoot(element, i) {
tempRoot.insert(i, points, 0);
}
function doneInternal() {
originalArray = points;
root = tempRoot;
if (typeof doneCallback === 'function') {
doneCallback(api);
}
}
}
function verifyPointsInvariant(points) {
if (!points) throw new Error('Points array is required for quadtree to work');
if (typeof points.length !== 'number') throw new Error('Points should be array-like object');
if (points.length % 3 !== 0) throw new Error('Points array should consist of series of x,y,z coordinates and be multiple of 3');
}
function getBounds() {
if (!root) return EmptyRegion;
return root.bounds;
}
function createRootNode(points) {
// Edge case deserves empty region:
if (points.length === 0) {
var empty = new Bounds3();
return new TreeNode(empty);
}
// Otherwise let's figure out how big should be the root region
var minX = Number.POSITIVE_INFINITY;
var minY = Number.POSITIVE_INFINITY;
var minZ = Number.POSITIVE_INFINITY;
var maxX = Number.NEGATIVE_INFINITY;
var maxY = Number.NEGATIVE_INFINITY;
var maxZ = Number.NEGATIVE_INFINITY;
for (var i = 0; i < points.length; i += 3) {
var x = points[i],
y = points[i + 1],
z = points[i + 2];
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
}
// Make bounds square:
var side = Math.max(Math.max(maxX - minX, maxY - minY), maxZ - minZ);
// since we need to have both sides inside the area, let's artificially
// grow the root region:
side += 2;
minX -= 1;
minY -= 1;
minZ -= 1;
var half = side / 2;
var bounds = new Bounds3(minX + half, minY + half, minZ + half, half);
return new TreeNode(bounds);
}
}