/** if (neighbours[j] == locCode) {
// console.log(locCode);
}
* @class
* An octree constructed using the point cloud.
* @property {number} threshold - A threshold indicating whether or not a further subdivision is needed based on the number of data points in the current node.
* @property {number} maxDepth - A maximum depth of the octree.
* @property {Object} points - An object storing the points belonging to each node indexed by the location id of the node.
* @property {Object} aabbs - An object storing the axis-aligned bounding boxes belonging to each node indexed by the location id of the node.
* @constructor
* @param {number} threshold - A threshold indicating whether or not a further subdivision is needed based on the number of data points in the current node.
* @param {number} maxDepth - A maximum depth of the octree.
*/
Lore.Octree = class Octree {
constructor(threshold, maxDepth) {
this.threshold = threshold || 500;
this.maxDepth = maxDepth || 8;
this.points = {};
this.aabbs = {};
this.offsets = [
[-0.5, -0.5, -0.5],
[-0.5, -0.5, +0.5],
[-0.5, +0.5, -0.5],
[-0.5, +0.5, +0.5],
[+0.5, -0.5, -0.5],
[+0.5, -0.5, +0.5],
[+0.5, +0.5, -0.5],
[+0.5, +0.5, +0.5]
];
}
/**
* Builds the octree by assigning the indices of data points and axis-aligned bounding boxes to assoziative arrays indexed by the location code.
* @param {Uint32Array} pointIndices - An set of points that are either sub-divided into sub nodes or assigned to the current node.
* @param {Float32Array} vertices - An array containing the positions of all the vertices.
* @param {PLOTTER.AABB} aabb - The bounding box of the current node.
* @param {number} locCode - A binary code encoding the id and the level of the current node.
*/
build(pointIndices, vertices, aabb, locCode) {
locCode = locCode || 1;
// Set the location code of the axis-aligned bounding box
aabb.setLocCode(locCode);
// Store the axis aligned bounding box of this node
// and set the points belonging to the node to null
this.points[locCode] = null;
this.aabbs[locCode] = aabb;
// Check if this node reaches the maximum depth or the threshold
let depth = this.getDepth(locCode);
if (pointIndices.length <= this.threshold || depth >= this.maxDepth) {
this.points[locCode] = new Uint32Array(pointIndices.length);
for (var i = 0; i < pointIndices.length; i++) {
this.points[locCode][i] = pointIndices[i];
}
return true;
}
let childPointCounts = new Uint32Array(8);
let codes = new Float32Array(pointIndices.length);
for (var i = 0; i < pointIndices.length; i++) {
// Points are indices to the vertices array
// which stores x,y,z coordinates linear
let k = pointIndices[i] * 3;
// Assign point to subtree, this gives a code
// 000, 001, 010, 011, 100, 101, 110, 111
// (-> 8 possible subtrees)
if (vertices[k + 0] >= aabb.center.components[0]) codes[i] |= 4;
if (vertices[k + 1] >= aabb.center.components[1]) codes[i] |= 2;
if (vertices[k + 2] >= aabb.center.components[2]) codes[i] |= 1;
childPointCounts[codes[i]]++;
}
let nextPoints = new Array(8);
let nextAabb = new Array(8);
for (var i = 0; i < 8; i++) {
if (childPointCounts[i] == 0) continue;
nextPoints[i] = new Uint32Array(childPointCounts[i]);
for (var j = 0, k = 0; j < pointIndices.length; j++) {
if (codes[j] == i) {
nextPoints[i][k++] = pointIndices[j];
}
}
let o = this.offsets[i];
let offset = new Lore.Vector3f(o[0], o[1], o[2]);
offset.multiplyScalar(aabb.radius);
nextAabb[i] = new Lore.AABB(aabb.center.clone().add(offset), 0.5 * aabb.radius);
}
for (var i = 0; i < 8; i++) {
if (childPointCounts[i] == 0) {
continue;
}
let nextLocCode = this.generateLocCode(locCode, i);
this.build(nextPoints[i], vertices, nextAabb[i], nextLocCode);
}
return this;
}
/**
* Returns an array containing the location codes of all the axis-aligned
* bounding boxes inside this octree.
*/
getLocCodes() {
return Object.keys(this.aabbs);
}
/**
* Calculates the depth of the node from its location code.
* @param {number} locCode - A binary code encoding the id and the level of the current node.
* @returns {number} The depth of the node with the provided location code.
*/
getDepth(locCode) {
// If the msb is at position 6 (e.g. 1000000) the
// depth is 2, since the locCode contains two nodes (2 x 3 bits)
return Lore.Utils.msb(locCode) / 3;
}
/**
* Generates a location code for a node based on the full code of the parent and the code of the current node.
* @param {number} The full location code of the parent node.
* @param {number} The 3 bit code of the current node.
* @returns {number} The full location code for the current node.
*/
generateLocCode(parentCode, nodeCode) {
// Insert the code of this new node, just before the msb (that is set to 1)
// of the parents code
let msb = Lore.Utils.msb(parentCode);
if (msb == -1) {
return nodeCode | 8;
} else {
// Left-shift the parent code by msb
parentCode = parentCode <<= 3;
// OR parent code with node code
return parentCode | nodeCode;
}
}
/**
* The callback that is called when a node of the octree is visited.
* @callback PLOTTER.Octree~traverseCallback
* @param {Uint32Array} points - The points associated with the node.
* @param {PLOTTER.AABB} aabb - The axis-aligned bounding box associated with the node.
* @param {number} locCode - The location code of the node.
*/
/**
* Traverses the octree depth-first.
* @param {PLOTTER.Octree~traverseCallback} traverseCallback - Is called for each node where a axis-aligned bounding box exists.
* @param {number} locCode - The location code of the node that serves as the starting node for the traversion.
*/
traverse(traverseCallback, locCode) {
locCode = locCode || 1;
for (var i = 0; i < 8; i++) {
let next = locCode << 3 | i;
// If it has an aabb, it exists
if (this.aabbs[next]) {
traverseCallback(this.points[next], this.aabbs[next], next);
this.traverse(traverseCallback, next);
}
}
}
/**
* The callback that is called when a node of the octree is visited which meets the condition.
* @callback PLOTTER.Octree~traverseIfCallback
* @param {Uint32Array} points - The points associated with the node.
* @param {PLOTTER.AABB} aabb - The axis-aligned bounding box associated with the node.
* @param {number} locCode - The location code of the node.
*/
/**
* The callback that is called to test a node.
* @callback PLOTTER.Octree~conditionCallback
* @param {PLOTTER.AABB} aabb - The axis-aligned bounding box associated with the node.
* @param {number} locCode - The location code of the node.
*/
/**
* Traverses the octree depth-first, does not visit nodes / subtrees if a condition is not met.
* @param {PLOTTER.Octree~traverseIfCallback} traverseIfCallback - Is called for each node where a axis-aligned bounding box exists and returns either true or false, with false stopping further exploration of the subtree.
* @param {PLOTTER.Octree~conditionCallback} conditionCallback - Is called to test whether or not a subtree should be explored.
* @param {number} locCode - The location code of the node that serves as the starting node for the traversion.
*/
traverseIf(traverseIfCallback, conditionCallback, locCode) {
locCode = locCode || 1;
for (var i = 0; i < 8; i++) {
let next = locCode << 3 | i;
// If it has an aabb, it exists
if (this.aabbs[next]) {
if (!conditionCallback(this.aabbs[next], next)) {
continue;
}
traverseIfCallback(this.points[next], this.aabbs[next], next);
this.traverseIf(traverseIfCallback, conditionCallback, next);
}
}
}
/**
* Searches for octree nodes that are intersected by the ray and returns all the points associated with those nodes.
* @param {Lore.Raycaster} raycaster - The raycaster used for checking for intersects.
* @returns {Array} A set of points which are associated with octree nodes intersected by the ray.
*/
raySearch(raycaster) {
let result = [];
// Info: shouldn't be necessary any more
// Always add the points from the root
// The root has the location code 1
// ... looks like it's still necessary
if (this.points[1]) {
for (var i = 0; i < this.points[1].length; i++) {
result.push({
index: this.points[1][i],
locCode: 1
});
}
}
// Calculate the direction, and the percentage
// of the direction, of the ray
let dir = raycaster.ray.direction.clone();
dir.normalize();
let inverseDir = new Lore.Vector3f(1, 1, 1);
inverseDir.divide(dir);
this.traverseIf(function (points, aabb, locCode) {
// If there is an aabb, that contains no points but only
// nodes, skip here
if (!points) {
return;
}
for (var i = 0; i < points.length; i++) {
result.push({
index: points[i],
locCode: locCode
});
}
}, function (aabb, locCode) {
return aabb.cylinderTest(raycaster.ray.source, inverseDir,
raycaster.far, raycaster.threshold);
});
return result;
}
/**
* Returns an array containing all the centers of the axis-aligned bounding boxes
* in this octree that have points associated with them.
* @returns {Array} An array containing the centers as Lore.Vector3f objects.
*/
getCenters(threshold) {
threshold = threshold || 0;
let centers = new Array();
this.traverse(function (points, aabb, next) {
if (points && points.length > threshold) {
centers.push(aabb.center);
}
});
return centers;
}
/**
* This function returns the closest box in the octree to the point given as an argument.
* @param {Lore.Vector3f} point - The point.
* @param {number} threshold - The minimum number of points an axis-aligned bounding box should contain to count as a hit.
* @param {number} locCode - The starting locCode, if not set, starts at the root.
* @returns {Lore.AABB} The closest axis-aligned bounding box to the input point.
*/
getClosestBox(point, threshold, locCode) {
locCode = locCode || 1;
let closest = -1;
let minDist = Number.MAX_VALUE;
for (var i = 0; i < 8; i++) {
let next = locCode << 3 | i;
// If it has an aabb, it exists
if (this.aabbs[next]) {
// Continue if under threshold
if (this.points[next] && this.points[next].length < threshold) {
continue;
}
let dist = this.aabbs[next].distanceToPointSq(point.components[0], point.components[1], point.components[2]);
if (dist < minDist) {
minDist = dist;
closest = next;
}
}
}
if (closest < 0) {
return this.aabbs[locCode];
} else {
return this.getClosestBox(point, threshold, closest);
}
}
/**
* This function returns the closest box in the octree to the point given as an argument. The distance measured is to the
* box center.
* @param {Lore.Vector3f} point - The point.
* @param {number} threshold - The minimum number of points an axis-aligned bounding box should contain to count as a hit.
* @param {number} locCode - The starting locCode, if not set, starts at the root.
* @returns {Lore.AABB} The closest axis-aligned bounding box to the input point.
*/
getClosestBoxFromCenter(point, threshold, locCode) {
locCode = locCode || 1;
let closest = -1;
let minDist = Number.MAX_VALUE;
for (var i = 0; i < 8; i++) {
let next = locCode << 3 | i;
// If it has an aabb, it exists
if (this.aabbs[next]) {
// Continue if under threshold
if (this.points[next] && this.points[next].length < threshold) {
continue;
}
let dist = this.aabbs[next].distanceFromCenterToPointSq(point.components[0], point.components[1], point.components[2]);
if (dist < minDist) {
minDist = dist;
closest = next;
}
}
}
if (closest < 0) {
return this.aabbs[locCode];
} else {
return this.getClosestBox(point, threshold, closest);
}
}
/**
* This function returns the farthest box in the octree to the point given as an argument.
* @param {Lore.Vector3f} point - The point.
* @param {number} threshold - The minimum number of points an axis-aligned bounding box should contain to count as a hit.
* @param {number} locCode - The starting locCode, if not set, starts at the root.
* @returns {Lore.AABB} The farthest axis-aligned bounding box to the input point.
*/
getFarthestBox(point, threshold, locCode) {
locCode = locCode || 1;
let farthest = -1;
let maxDist = Number.MIN_VALUE;
for (var i = 0; i < 8; i++) {
let next = locCode << 3 | i;
// If it has an aabb, it exists
if (this.aabbs[next]) {
// Continue if under threshold
if (this.points[next] && this.points[next].length < threshold) {
continue;
}
let dist = this.aabbs[next].distanceToPointSq(point.components[0], point.components[1], point.components[2]);
if (dist > maxDist) {
maxDist = dist;
farthest = next;
}
}
}
if (farthest < 0) {
return this.aabbs[locCode];
} else {
return this.getFarthestBox(point, threshold, farthest);
}
}
/**
* Finds the closest point inside the octree to the point provided as an argument.
* @param {Lore.Vector3f} point - The point.
* @param {Float32Array} - An array containing the positions of the points.
* @param {number} threshold - Only consider points inside a axis-aligned bounding box with a minimum of [threshold] points.
* @param {number} locCode - If specified, the axis-aligned bounding box in which the point is searched for. If not set, all boxes are searched.
* @returns {Lore.Vector3f} The position of the closest point.
*/
getClosestPoint(point, positions, threshold, locCode) {
threshold = threshold || 0;
let minDist = Number.MAX_VALUE;
let result = null;
let box = null;
if (locCode) {
box = this.aabbs[locCode];
} else {
box = this.getClosestBox(point, threshold);
}
let boxPoints = this.points[box.getLocCode()];
// If the box does not contain any points
if (!boxPoints) {
return null;
}
for (var i = 0; i < boxPoints.length; i++) {
let index = boxPoints[i];
index *= 3;
let x = positions[index];
let y = positions[index + 1];
let z = positions[index + 2];
let pc = point.components;
let distSq = Math.pow(pc[0] - x, 2) + Math.pow(pc[1] - y, 2) + Math.pow(pc[2] - z, 2);
if (distSq < minDist) {
minDist = distSq;
result = {
x: x,
y: y,
z: z
};
}
}
if (!result) {
return null;
}
return new Lore.Vector3f(result.x, result.y, result.z);
}
/**
* Finds the farthest point inside the octree to the point provided as an argument.
* @param {Lore.Vector3f} point - The point.
* @param {Float32Array} - An array containing the positions of the points.
* @param {number} threshold - Only consider points inside a axis-aligned bounding box with a minimum of [threshold] points.
* @param {number} locCode - If specified, the axis-aligned bounding box in which the point is searched for. If not set, all boxes are searched.
* @returns {Lore.Vector3f} The position of the farthest point.
*/
getFarthestPoint(point, positions, threshold, locCode) {
threshold = threshold || 0;
let maxDist = Number.MIN_VALUE;
let result = null;
// Get farthest box
let box = null;
if (locCode) {
box = this.aabbs[locCode];
} else {
box = this.getFArthestBox(point, threshold);
}
let boxPoints = this.points[box.getLocCode()];
// If the box does not contain any points
if (!boxPoints) {
return null;
}
for (var i = 0; i < boxPoints.length; i++) {
let index = boxPoints[i];
index *= 3;
let x = positions[index];
let y = positions[index + 1];
let z = positions[index + 2];
let pc = point.components;
let distSq = Math.pow(pc[0] - x, 2) + Math.pow(pc[1] - y, 2) + Math.pow(pc[2] - z, 2);
if (distSq > maxDist) {
maxDist = distSq;
result = {
x: x,
y: y,
z: z
};
}
}
if (!result) {
return null;
}
return new Lore.Vector3f(result.x, result.y, result.z);
}
/**
* Returns the parent of a given location code by simply shifting it to the right by tree, removing the current code.
* @param {number} locCode - The location code of a node.
*/
getParent(locCode) {
return locCode >>> 3;
}
/**
* Find neighbouring axis-aligned bounding boxes.
* @param {number} locCode - The location code of the axis-aligned bounding box whose neighbours will be returned
* @returns {Array} An array of location codes of the neighbouring axis-aligned bounding boxes.
*/
getNeighbours(locCode) {
let self = this;
let locCodes = new Array();
this.traverseIf(function (points, aabbs, code) {
if (points && points.length > 0 && code != locCode) {
locCodes.push(code);
}
}, function (aabb, code) {
// Exit branch if this node is not a neighbour
return aabb.testAABB(self.aabbs[locCode]);
});
return locCodes;
}
/**
* The callback that is called when the nearest neighbours have been found.
* @callback PLOTTER.Plot~kNNCallback
* @param {Uint32Array} e - An array containing containing the k-nearest neighbours ordered by distance (ascending).
*/
/**
* Returns the k-nearest neighbours of a vertex.
* @param {number} k - The number of nearest neighbours to return.
* @param {number} point - The index of a vertex or a vertex.
* @param {number} locCode - The location code of the axis-aligned bounding box containing the vertex. If not set, the box is searched for.
* @param {Float32Array} positions - The position information for the points indexed in this octree.
* @param {PLOTTER.Plot~kNNCallback} kNNCallback - The callback that is called after the k-nearest neighbour search has finished.
*/
kNearestNeighbours(k, point, locCode, positions, kNNCallback) {
k += 1; // Account for the fact, that the point itself should be returned as well.
let length = positions / 3;
let p = point;
if (!isNaN(parseFloat(point))) {
let p = {
x: positions[p * 3],
y: positions[p * 3 + 1],
z: positions[p * 3 + 2]
};
}
if (locCode === null) {
locCode = this.getClosestBoxFromCenter(new Lore.Vector3f(p.x, p.y, p.z), 0).locCode;
}
// Calculte the distances to the other cells
let cellDistances = this.getCellDistancesToPoint(p.x, p.y, p.z, locCode);
// Calculte the distances to the other points in the same cell
let pointDistances = this.pointDistancesSq(p.x, p.y, p.z, locCode, positions)
// Sort the indices according to distance
let radixSort = new Lore.RadixSort();
let sortedPointDistances = radixSort.sort(pointDistances.distancesSq, true);
// Sort the neighbours according to distance
let sortedCellDistances = radixSort.sort(cellDistances.distancesSq, true);
// Since the closest points always stay the closest points event when adding
// the points of another cell, instead of resizing the array, just define
// an offset
let pointOffset = 0;
// Get all the neighbours from this cell that are closer than the nereast box
let indexCount = 0;
let indices = new Uint32Array(k);
for (var i = 0; indexCount < k && i < sortedPointDistances.array.length; i++) {
// Break if closest neighbouring cell is closer than the closest remaining point
if (sortedPointDistances.array[i] > sortedCellDistances.array[0]) {
// Set the offset to the most distant closest member
pointOffset = i;
break;
}
indices[i] = pointDistances.indices[sortedPointDistances.indices[i]];
indexCount++;
}
// If enough neighbours have been found in the same cell, no need to continue
if (indexCount == k) {
return indices;
}
for (var i = 0; i < sortedCellDistances.array.length; i++) {
// Get the points from the cell and merge them with the already found ones
let locCode = cellDistances.locCodes[sortedCellDistances.indices[i]];
let newPointDistances = this.pointDistancesSq(p.x, p.y, p.z, locCode, positions);
pointDistances = Lore.Octree.mergePointDistances(pointDistances, newPointDistances);
// Sort the merged points
let sortedNewPointDistances = radixSort.sort(pointDistances.distancesSq, true);
for (var j = pointOffset; indexCount < k && j < sortedNewPointDistances.array.length; j++) {
if (sortedNewPointDistances.array[j] > sortedCellDistances.array[i + 1]) {
pointOffset = j;
break;
}
indices[j] = pointDistances.indices[sortedNewPointDistances.indices[j]];
indexCount++;
}
if (indexCount == k || indexCount >= length - 1) {
// kNNCallback(indices);
return indices;
}
}
//kNNCallback(indices);
return indices;
}
/**
* Calculates the distances from a given point to all of the cells containing points
* @param {number} x - The x-value of the coordinate.
* @param {number} y - The y-value of the coordinate.
* @param {number} z - The z-value of the coordinate.
* @param {number} locCode - The location code of the cell containing the point.
* @returns {Object} An object containing arrays for the locCodes and the squred distances.
*/
getCellDistancesToPoint(x, y, z, locCode) {
let locCodes = new Array();
this.traverse(function (points, aabb, code) {
if (points && points.length > 0 && code != locCode) {
locCodes.push(code);
}
});
let dists = new Float32Array(locCodes.length);
for (var i = 0; i < locCodes.length; i++) {
dists[i] = this.aabbs[locCodes[i]].distanceToPointSq(x, y, z);
}
return {
locCodes: locCodes,
distancesSq: dists
};
}
/**
* Expands the current neighbourhood around the cell where the point specified by x, y, z is in.
* @param {number} x - The x-value of the coordinate.
* @param {number} y - The y-value of the coordinate.
* @param {number} z - The z-value of the coordinate.
* @param {number} locCode - The location code of the cell containing the point.
* @param {Object} cellDistances - The object containing location codes and distances.
* @returns {number} The number of added location codes.
*/
expandNeighbourhood(x, y, z, locCode, cellDistances) {
let locCodes = cellDistances.locCodes;
let distancesSq = cellDistances.distancesSq;
let length = locCodes.length;
for (var i = length - 1; i >= 0; i--) {
let neighbours = this.getNeighbours(locCodes[i]);
for (var j = 0; j < neighbours.length; j++) {
if (neighbours[j] !== locCode && !Lore.Utils.arrayContains(locCodes, neighbours[j])) {
locCodes.push(neighbours[j]);
}
}
}
// Update the distances
let l1 = locCodes.length;
let l2 = distancesSq.length;
if (l1 === l2) {
return;
}
let dists = new Float32Array(l1 - l2);
for (var i = l2, c = 0; i < l1; i++, c++) {
dists[c] = this.aabbs[locCodes[i]].distanceToPointSq(x, y, z);
}
cellDistances.distancesSq = Lore.Utils.concatTypedArrays(distancesSq, dists);
return locCodes.length - length;
}
/**
* Returns a list of the cells neighbouring the cell with the provided locCode and the point specified by x, y and z.
* @param {number} x - The x-value of the coordinate.
* @param {number} y - The y-value of the coordinate.
* @param {number} z - The z-value of the coordinate.
* @param {number} locCode - The number of the axis-aligned bounding box.
* @returns {Object} An object containing arrays for the locCodes and the squred distances.
*/
cellDistancesSq(x, y, z, locCode) {
let locCodes = this.getNeighbours(locCode);
let dists = new Float32Array(locCodes.length);
for (var i = 0; i < locCodes.length; i++) {
dists[i] = this.aabbs[locCodes[i]].distanceToPointSq(x, y, z);
}
return {
locCodes: locCodes,
distancesSq: dists
};
}
/**
* Returns a list of the the squared distances of the points contained in the axis-aligned bounding box to the provided coordinates.
* @param {number} x - The x-value of the coordinate.
* @param {number} y - The y-value of the coordinate.
* @param {number} z - The z-value of the coordinate.
* @param {number} locCode - The number of the axis-aligned bounding box.
* @param {Float32Array} positions - The array containing the vertex coordinates.
* @returns {Object} An object containing arrays for the indices and distances.
*/
pointDistancesSq(x, y, z, locCode, positions) {
let points = this.points[locCode];
let indices = new Uint32Array(points.length);
let dists = new Float32Array(points.length);
for (var i = 0; i < points.length; i++) {
let index = points[i] * 3;
let x2 = positions[index];
let y2 = positions[index + 1];
let z2 = positions[index + 2];
indices[i] = points[i];
dists[i] = Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2) + Math.pow(z2 - z, 2);
}
return {
indices: indices,
distancesSq: dists
};
}
/**
* Concatenates the two typed arrays a and b and returns a new array. The two arrays have to be of the same type.
* Due to performance reasons, there is no check whether the types match.
* @param {Array} a - The first array.
* @param {Array} b - The second array.
* @returns {Array} The concatenated array.
*/
static concatTypedArrays(a, b) {
let c = new a.constructor(a.length + b.length);
c.set(a);
c.set(b, a.length);
return c;
}
/**
* Merges the two arrays (indices and distancesSq) in the point distances object.
* @param {Object} a - The first point distances object.
* @param {Object} b - The second point distances object.
* @returns {Object} The concatenated point distances object.
*/
static mergePointDistances(a, b) {
let newObj = {};
newObj.indices = Lore.Octree.concatTypedArrays(a.indices, b.indices);
newObj.distancesSq = Lore.Octree.concatTypedArrays(a.distancesSq, b.distancesSq);
return newObj;
}
/**
* Merges the two arrays (locCodes and distancesSq) in the cell distances object.
* @param {Object} a - The first cell distances object.
* @param {Object} b - The second cell distances object.
* @returns {Object} The concatenated cell distances object.
*/
static mergeCellDistances(a, b) {
let newObj = {};
newObj.locCodes = Lore.Octree.concatTypedArrays(a.locCodes, b.locCodes);
newObj.distancesSq = Lore.Octree.concatTypedArrays(a.distancesSq, b.distancesSq);
return newObj;
}
/**
* Clones an octree.
* @param {Lore.Octree} original - The octree to be cloned.
* @returns {Lore.Octree} The cloned octree.
*/
static clone(original) {
let clone = new Lore.Octree();
clone.threshold = original.threshold;
clone.maxDepth = original.maxDepth;
clone.points = original.points;
for (var property in original.aabbs) {
if (original.aabbs.hasOwnProperty(property)) {
clone.aabbs[property] = Lore.AABB.clone(original.aabbs[property]);
}
}
return clone;
}
}