Add minimal octree implementation.

This commit is contained in:
Viktor Kovacs 2021-05-14 16:58:28 +02:00
parent d6da882d9e
commit 8937cafdfd
5 changed files with 330 additions and 0 deletions

27
source/geometry/box3d.js Normal file
View File

@ -0,0 +1,27 @@
OV.Box3D = class
{
constructor (min, max)
{
this.min = min;
this.max = max;
}
GetMin ()
{
return this.min;
}
GetMax ()
{
return this.max;
}
GetCenter ()
{
return new OV.Coord3D (
(this.min.x + this.max.x) / 2.0,
(this.min.y + this.max.y) / 2.0,
(this.min.z + this.max.z) / 2.0
);
}
};

View File

@ -17,6 +17,16 @@ OV.IsGreater = function (a, b)
return a - b > OV.Eps;
};
OV.IsLowerOrEqual = function (a, b)
{
return b - a > -OV.Eps;
};
OV.IsGreaterOrEqual = function (a, b)
{
return a - b > -OV.Eps;
};
OV.IsEqual = function (a, b)
{
return Math.abs (b - a) < OV.Eps;

156
source/geometry/octree.js Normal file
View File

@ -0,0 +1,156 @@
OV.OctreeNode = class
{
constructor (boundingBox, level)
{
this.boundingBox = boundingBox;
this.level = level;
this.pointItems = [];
this.childNodes = [];
}
AddPoint (point, data, options)
{
let node = this.FindNodeForPoint (point);
if (node === null) {
return false;
}
if (node.FindPointDirectly (point) !== null) {
return false;
}
if (node.pointItems.length < options.maxPointsPerNode || node.level >= options.maxTreeDepth) {
node.AddPointDirectly (point, data);
return true;
} else {
node.CreateChildNodes ();
let oldPointItems = node.pointItems;
node.pointItems = [];
for (let i = 0; i < oldPointItems.length; i++) {
let pointItem = oldPointItems[i];
if (!node.AddPoint (pointItem.point, pointItem.data, options)) {
return false;
}
}
return node.AddPoint (point, data, options);
}
}
FindPoint (point)
{
let node = this.FindNodeForPoint (point);
if (node === null) {
return null;
}
return node.FindPointDirectly (point);
}
AddPointDirectly (point, data)
{
this.pointItems.push ({
point : point,
data : data
});
}
FindPointDirectly (point)
{
for (let i = 0; i < this.pointItems.length; i++) {
let pointItem = this.pointItems[i];
if (OV.CoordIsEqual3D (point, pointItem.point)) {
return pointItem.data;
}
}
return null;
}
FindNodeForPoint (point)
{
if (!this.IsPointInBounds (point)) {
return null;
}
if (this.childNodes.length === 0) {
return this;
}
for (let i = 0; i < this.childNodes.length; i++) {
let childNode = this.childNodes[i];
let foundNode = childNode.FindNodeForPoint (point);
if (foundNode !== null) {
return foundNode;
}
}
return null;
}
CreateChildNodes ()
{
function AddChildNode (node, minX, minY, minZ, sizeX, sizeY, sizeZ)
{
let box = new OV.Box3D (
new OV.Coord3D (minX, minY, minZ),
new OV.Coord3D (minX + sizeX, minY + sizeY, minZ + sizeZ)
);
node.childNodes.push (new OV.OctreeNode (box, node.level + 1, node.options));
}
let min = this.boundingBox.min;
let center = this.boundingBox.GetCenter ();
let sizeX = (this.boundingBox.max.x - this.boundingBox.min.x) / 2.0;
let sizeY = (this.boundingBox.max.y - this.boundingBox.min.y) / 2.0;
let sizeZ = (this.boundingBox.max.z - this.boundingBox.min.z) / 2.0;
AddChildNode (this, min.x, min.y, min.z, sizeX, sizeY, sizeZ);
AddChildNode (this, center.x, min.y, min.z, sizeX, sizeY, sizeZ);
AddChildNode (this, min.x, center.y, min.z, sizeX, sizeY, sizeZ);
AddChildNode (this, center.x, center.y, min.z, sizeX, sizeY, sizeZ);
AddChildNode (this, min.x, min.y, center.z, sizeX, sizeY, sizeZ);
AddChildNode (this, center.x, min.y, center.z, sizeX, sizeY, sizeZ);
AddChildNode (this, min.x, center.y, center.z, sizeX, sizeY, sizeZ);
AddChildNode (this, center.x, center.y, center.z, sizeX, sizeY, sizeZ);
}
IsPointInBounds (point)
{
let isEqual =
OV.IsGreaterOrEqual (point.x, this.boundingBox.min.x) &&
OV.IsGreaterOrEqual (point.y, this.boundingBox.min.y) &&
OV.IsGreaterOrEqual (point.z, this.boundingBox.min.z) &&
OV.IsLowerOrEqual (point.x, this.boundingBox.max.x) &&
OV.IsLowerOrEqual (point.y, this.boundingBox.max.y) &&
OV.IsLowerOrEqual (point.z, this.boundingBox.max.z);
return isEqual;
}
};
OV.Octree = class
{
constructor (boundingBox, options)
{
this.options = {
maxPointsPerNode : 10,
maxTreeDepth : 10
};
if (options !== undefined) {
if (options.maxPointsPerNode !== undefined) {
this.options.maxPointsPerNode = options.maxPointsPerNode;
}
if (options.maxTreeDepth !== undefined) {
this.options.maxTreeDepth = options.maxTreeDepth;
}
}
this.rootNode = new OV.OctreeNode (boundingBox, 0, this.options);
}
AddPoint (point, data)
{
return this.rootNode.AddPoint (point, data, this.options);
}
FindPoint (point)
{
return this.rootNode.FindPoint (point);
}
};

View File

@ -1,5 +1,11 @@
var assert = require ('assert');
function SeededRandom (from, to, seed)
{
var random = ((seed * 9301 + 49297) % 233280) / 233280;
return random * (to - from) + from;
}
function CreateYRot90Quaternion ()
{
let angle = Math.PI / 2.0;
@ -15,6 +21,37 @@ function CreateYRot90Quaternion ()
return quaternion;
}
describe ('Comparison', function () {
it ('IsGreater', function () {
assert (OV.IsEqual (1.0, 1.0));
assert (OV.IsEqual (1.0, 1.000000001));
assert (!OV.IsEqual (1.0, 1.0001));
});
it ('IsGreater', function () {
assert (OV.IsGreater (1.0, 0.0));
assert (OV.IsGreater (1.0001, 1.0));
assert (!OV.IsGreater (1.000000001, 1.0));
assert (OV.IsGreaterOrEqual (1.0001, 1.0));
assert (OV.IsGreaterOrEqual (1.000000001, 1.0));
assert (OV.IsGreaterOrEqual (0.999999999, 1.0));
assert (!OV.IsGreaterOrEqual (0.999, 1.0));
});
it ('IsLower', function () {
assert (OV.IsLower (0.0, 1.0));
assert (OV.IsLower (1.0, 1.0001));
assert (!OV.IsLower (1.0, 1.000000001));
assert (OV.IsLowerOrEqual (1.0, 1.0001));
assert (OV.IsLowerOrEqual (1.0, 1.000000001));
assert (OV.IsLowerOrEqual (1.0, 0.999999999));
assert (!OV.IsLowerOrEqual (1.0, 0.999));
});
});
describe ('Coord', function () {
it ('Length', function () {
var c = new OV.Coord3D (2.0, 0.0, 0.0);
@ -144,3 +181,101 @@ describe ('Tween', function() {
assert (OV.CoordIsEqual3D (segments[segments.length - 1], end));
});
});
describe ('Octree', function() {
it ('Add Point', function () {
let octree = new OV.Octree (new OV.Box3D (
new OV.Coord3D (-10.0, -10.0, -10.0),
new OV.Coord3D (10.0, 10.0, 10.0)
));
let p1 = new OV.Coord3D (0.0, 0.0, 0.0);
assert (octree.AddPoint (p1, 'p1'));
assert (!octree.AddPoint (p1, 'p2'));
assert.strictEqual (octree.FindPoint (p1), 'p1')
});
it ('Add Points', function () {
let octree = new OV.Octree (new OV.Box3D (
new OV.Coord3D (-10.0, -10.0, -10.0),
new OV.Coord3D (10.0, 10.0, 10.0)
));
let p1 = new OV.Coord3D (0.0, 0.0, 0.0);
let p2 = new OV.Coord3D (1.0, 1.0, 1.0);
let p3 = new OV.Coord3D (-1.0, 1.0, 1.0);
let p4 = new OV.Coord3D (-1.0, -1.0, 1.0);
let p5 = new OV.Coord3D (-1.0, -1.0, -1.0);
let p6 = new OV.Coord3D (2.0, 2.0, 2.0);
let p7 = new OV.Coord3D (-2.0, 2.0, 2.0);
let p8 = new OV.Coord3D (-2.0, -2.0, 2.0);
let p9 = new OV.Coord3D (-2.0, -2.0, -2.0);
assert (octree.AddPoint (p1, 'p1'));
assert (octree.AddPoint (p2, 'p2'));
assert (octree.AddPoint (p3, 'p3'));
assert (octree.AddPoint (p4, 'p4'));
assert (octree.AddPoint (p5, 'p5'));
assert (octree.AddPoint (p6, 'p6'));
assert (octree.AddPoint (p7, 'p7'));
assert (octree.AddPoint (p8, 'p8'));
assert (octree.AddPoint (p9, 'p9'));
assert.strictEqual (octree.FindPoint (p1), 'p1');
assert.strictEqual (octree.FindPoint (p2), 'p2');
assert.strictEqual (octree.FindPoint (p3), 'p3');
assert.strictEqual (octree.FindPoint (p4), 'p4');
assert.strictEqual (octree.FindPoint (p5), 'p5');
assert.strictEqual (octree.FindPoint (p6), 'p6');
assert.strictEqual (octree.FindPoint (p7), 'p7');
assert.strictEqual (octree.FindPoint (p8), 'p8');
assert.strictEqual (octree.FindPoint (p9), 'p9');
});
it ('Add Points On Boundaries', function () {
let octree = new OV.Octree (new OV.Box3D (
new OV.Coord3D (-10.0, -10.0, -10.0),
new OV.Coord3D (10.0, 10.0, 10.0)
));
let p1 = new OV.Coord3D (10.0, 10.0, 10.0);
let p2 = new OV.Coord3D (-10.0, -10.0, -10.0);
let p3 = new OV.Coord3D (20.0, 20.0, 20.0);
assert (octree.AddPoint (p1, 'p1'));
assert (octree.AddPoint (p2, 'p2'));
assert (!octree.AddPoint (p3, 'p3'));
assert.strictEqual (octree.FindPoint (p1), 'p1');
assert.strictEqual (octree.FindPoint (p2), 'p2');
assert.strictEqual (octree.FindPoint (p3), null);
});
it ('Stress Test', function () {
let box = new OV.Box3D (
new OV.Coord3D (-10.0, -10.0, -10.0),
new OV.Coord3D (10.0, 10.0, 10.0)
);
let octree = new OV.Octree (box);
let count = 1000;
let seed = 1;
let points = [];
for (let i = 0; i < count; i++) {
let x = SeededRandom (-10.0, 10.0, seed++);
let y = SeededRandom (-10.0, 10.0, seed++);
let z = SeededRandom (-10.0, 10.0, seed++);
let point = new OV.Coord3D (x, y, z);
points.push (point);
}
for (let i = 0; i < count; i++) {
let point = points[i];
assert (octree.AddPoint (point, i.toString ()));
}
for (let i = 0; i < count; i++) {
let point = points[i];
assert.strictEqual (octree.FindPoint (point), i.toString ());
}
});
});

View File

@ -10,6 +10,8 @@
"source/geometry/geometry.js",
"source/geometry/coord2d.js",
"source/geometry/coord3d.js",
"source/geometry/box3d.js",
"source/geometry/octree.js",
"source/geometry/matrix.js",
"source/geometry/transformation.js",
"source/geometry/tween.js",