Commit 57219920 authored by Dmitry's avatar Dmitry

Working BSP tree

parent af595692
......@@ -4,9 +4,7 @@
* @author julianwa / https://github.com/julianwa
*/
define(function(require, exports, module){
var THREE = require("three");
define(['three'], function(THREE, BSPTree){
var Projector = {};
var i, l, o; // counters
......@@ -41,6 +39,22 @@ define(function(require, exports, module){
this.z = 0;
};
Projector.RenderableFace.prototype.copy = function ( face ){
this.v1.copy(face.v1);
this.v2.copy(face.v2);
this.v3.copy(face.v3);
this.normalModel.copy(face.normalModel);
this.vertexNormalsModel = face.vertexNormalsModel;
this.vertexNormalsLength = face.vertexNormalsLength;
this.color = face.color;
this.material = face.material;
this.uvs = face.uvs;
this.priority = face.priority;
};
Projector.RenderableVertex = function () {
......@@ -72,6 +86,14 @@ define(function(require, exports, module){
this.z = 0;
};
Projector.RenderableLine.prototype.copy = function( line ){
this.v1.copy(line.v1);
this.v2.copy(line.v2);
this.vertexColors = line.vertexColors;
this.material = line.material;
};
Projector.RenderableSprite = function () {
......@@ -656,6 +678,7 @@ define(function(require, exports, module){
if ( vertices.length === 0 ) continue;
var v1 = getNextVertexInPool();
v1.positionWorld.copy( vertices[ 0 ] );
v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix );
// Handle LineStrip and LinePieces
......@@ -664,6 +687,7 @@ define(function(require, exports, module){
for ( var v = 1, vl = vertices.length; v < vl; v ++ ) {
v1 = getNextVertexInPool();
v1.positionWorld.copy( vertices[ v ] );
v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix );
if ( ( v + 1 ) % step > 0 ) continue;
......@@ -683,7 +707,10 @@ define(function(require, exports, module){
_line.id = object.id;
_line.v1.positionScreen.copy( _clippedVertex1PositionScreen );
_line.v1.positionWorld.copy( v1.positionWorld );
_line.v2.positionScreen.copy( _clippedVertex2PositionScreen );
_line.v2.positionWorld.copy( v2.positionWorld );
_line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z );
......@@ -741,192 +768,19 @@ define(function(require, exports, module){
if ( sortElements === true ) {
_renderData.elements.sort( customSort );
//_renderData.elements.sort( customSort );
}
// Build the BSP tree
//var btree = buildBSPTree(_renderData.elements);
var btree = new BSPTree(_renderData.elements);
_renderData.elements = btree.toArray();
//_renderData.elements.length = 15;
return _renderData;
};
function BSPTree(data){
if(data.length){
this.root = BSPTree.createNode(data[0]);
for(var i = 1; i < data.length; i++){
this.insert(data[i]);
}
}
}
BSPTree.prototype.insert = function(element, compareWith){
compareWith = compareWith || this.root;
var node = (element instanceof BSPTree.Node) ?
element :
BSPTree.createNode(element);
var comparison = compareWith.isBehind(node);
if(comparison === undefined){
var fragments = node.separate(compareWith);
fragments.forEach(function(f){
this.insert(f, compareWith);
});
return;
}
if(comparison){
if(!compareWith.front){
compareWith.front = node;
} else {
this.insert(node, compareWith.front);
}
} else {
if(!compareWith.back){
compareWith.back = node;
} else {
this.insert(node, compareWith.back);
}
}
};
BSPTree.prototype.toArray = function(){
var output = [];
function traverse(node){
if(node){
traverse(node.back);
output.push(node);
traverse(node.front);
}
}
traverse(this.root);
return output;
}
BSPTree.createNode = function(element){
return new (element.v3 ? BSPTree.LineNode : BSPTree.TriangleNode)(element);
};
BSPTree.getPointSign = function(normal, point, pointOnPlane){
return BSPTree.sign(normal.dot(point.clone().sub(pointOnPlane)));
}
BSPTree.isZero = function(x){
return Math.abs(x) < BSPTree.SIGN_EPSILON;
}
BSPTree.sign = function(x){
if(Math.abs(x) < BSPTree.SIGN_EPSILON){
return 0;
} else if(x > 0) {
return 1;
} else {
return -1;
}
}
BSPTree.SIGN_EPSILON = 1e-6;
BSPTree.Node = function(){
this.back = null;
this.front = null;
}
BSPTree.Node.prototype.isBehind = function(node){
var normal = this.getNormal();
var point = this.getPointOnPlane();
var viewer = _camera.position;
var viewerSign = BSPTree.getPointSign(normal, viewer, point);
var nodeSign = node.getSign(normal, point);
if(nodeSign === undefined) return undefined;
return (viewerSign !== nodeSign);
}
BSPTree.LineNode = function(element){
this.element = element;
this.isTriangle = false;
};
BSPTree.LineNode.prototype = Object.create(BSPTree.Node.prototype);
BSPTree.LineNode.getNormal = function(){
var l = new THREE.Line3(
this.element.v1.positionWorld,
this.element.v2.positionWorld
);
var pt = l.closestPointToPoint(_camera.position, false);
return pt.sub(_camera.position);
}
BSPTree.LineNode.prototype.getPointOnPlane = function(){
return this.element.v1.positionWorld;
}
BSPTree.LineNode.prototype.getSign = function(normal, pointOnPlane){
var sign = BSPTree.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane);
var signTmp = BSPTree.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane);
if(signTmp !== sign) return undefined;
return sign;
}
BSPTree.LineNode.prototype.isBehind = function(node){
var normal = this.getNormal();
var point = this.element.v1.positionWorld;
var viewer = _camera.position;
var viewerSign = BSPTree.getPointSign(normal, viewer, point);
var nodeSign = node.getSign(normal, point);
if(nodeSign === undefined) return undefined;
return (viewerSign !== nodeSign);
}
BSPTree.LineNode.prototype.separate = function(normal, point){
var upper = point.clone().sub(this.element.v1.positionWorld);
upper = upper.dot(normal);
var l0 = this.element.v2.positionWorld;
var l = l0.clone().sub(this.element.v1.positionWorld);
var lower = l.dot(normal);
if(lower === 0) return this;
var d = upper / lower;
var intersectionPoint = l.multiplyScalar(d).add(l0);
return intersectionPoint;
}
BSPTree.TriangleNode = function(element){
this.element = element;
this.isTriangle = true;
};
BSPTree.TriangleNode.prototype = Object.create(BSPTree.Node.prototype);
BSPTree.TriangleNode.getNormal = function(){
return this.element.normalModel;
}
BSPTree.TriangleNode.prototype.getPointOnPlane = function(){
return this.element.v1.positionWorld;
}
BSPTree.TriangleNode.prototype.getSign = function(normal, pointOnPlane){
var sign = BSPTree.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane);
var signTmp = BSPTree.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane);
if(signTmp !== sign) return undefined;
var signTmp = BSPTree.getPointSign(normal, this.element.v3.positionWorld, pointOnPlane);
if(signTmp !== sign) return undefined;
return sign;
}
// Pools
......@@ -1011,91 +865,345 @@ define(function(require, exports, module){
}
// ----------
// This sorting algorithm uses a combination of 'painter sort' and
// binary space partition methods
// See https://users.cs.jmu.edu/bernstdh/web/common/lectures/slides_hidden-visible-3d.php
// on slide 12 for specific BSP formulas used
// ----------
// Returns true for points on one side of plane and false for points
// on another side
function getPointSign(planeNormal, point, pointOnPlane){
return planeNormal.dot(point.clone().sub(pointOnPlane)) > 0;
}
function compareFaces(a, b){
var normal = b.normalModel;
if(!normal) {
var l = new THREE.Line3(b.v1.positionWorld, b.v2.positionWorld);
var pt = l.closestPointToPoint(_camera.position, false);
normal = pt.sub(_camera.position);
}
var point = b.v1.positionWorld;
if(!point) return undefined;
var viewer = _camera.position;
var viewerSign = getPointSign(normal, viewer, point);
var aSign, signTmp;
try {
var aSign = getPointSign(normal, a.v1.positionWorld, point);
var signTmp = getPointSign(normal, a.v2.positionWorld, point);
if(signTmp !== aSign) return undefined;
// For lines that don't have .v3 we do not check it
if(a.v3){
var signTmp = getPointSign(normal, a.v3.positionWorld, point);
if(signTmp !== aSign) return undefined;
function BSPTree(data){
if(data.length){
this.root = BSPTree.utils.createNode(data[0]);
for(var i = 1; i < data.length; i++){
this.insert(data[i]);
}
}
}
BSPTree.prototype.insert = function(element, compareWith){
compareWith = compareWith || this.root;
var node = (element instanceof BSPTree.Node) ?
element :
BSPTree.utils.createNode(element);
var comparison = node.isBehind(compareWith);
if(comparison === undefined){
var fragments = node.separate(
compareWith.getNormal(),
compareWith.getPointOnPlane()
);
var self = this;
fragments.forEach(function(f){
self.insert(f, compareWith);
});
return;
}
if(comparison){
if(!compareWith.back){
compareWith.back = node;
} else {
this.insert(node, compareWith.back);
}
return (viewerSign === aSign);
} catch(e) {
return undefined;
}
}
// Output:
// - positive means a is in front of b;
// - negative means b is in front of a;
// - 0 means 'leave as is'
function customSort( a, b ) {
function priority(x){
if(typeof x.priority !== 'number') return -Infinity;
return x.priority;
}
if (priority(a) !== priority(b)){
var r = priority(a) - priority(b);
if(r > 1) return 1;
if(r < 1) return -1;
return r;
}
// If this is true, a is fully in front of b, if this is false,
// a is fully behind b. If this is undefined, some points of a
// are in front of b and some are behind.
var isAInFrontOfB = compareFaces(a, b);
// If not undefined, we have got a defined result that we'll use
if ( isAInFrontOfB !== undefined ) {
return isAInFrontOfB ? 1 : -1;
} else {
// Try to compare b with a, it may give defined result when
// compareFaces(a, b) failed
var isBInFrontOfA = compareFaces(b, a);
if ( isBInFrontOfA !== undefined ) {
return isBInFrontOfA ? -1 : 1;
}
if(!compareWith.front){
compareWith.front = node;
} else {
this.insert(node, compareWith.front);
}
}
};
BSPTree.prototype.toArray = function(){
var output = [];
function traverse(node){
if(node){
traverse(node.back);
output.push(node.element);
traverse(node.front);
}
}
traverse(this.root);
return output;
}
BSPTree.utils = {
createNode: function(element){
return new (element.v3 ? BSPTree.TriangleNode : BSPTree.LineNode)(element);
},
getPointSign: function(normal, point, pointOnPlane){
return this.sign(normal.dot(point.clone().sub(pointOnPlane)));
},
isPointInSegment: function(point, p1, p2){
// This function assumes that point lies on the line
// and determines whether it is in line _segment_
var minX = Math.min(p1.x, p2.x);
var minY = Math.min(p1.y, p2.y);
var minZ = Math.min(p1.z, p2.z);
var maxX = Math.max(p1.x, p2.x);
var maxY = Math.max(p1.y, p2.y);
var maxZ = Math.max(p1.z, p2.z);
return (point.x >= minX && point.x <= maxX) &&
(point.y >= minY && point.y <= maxY) &&
(point.z >= minZ && point.z <= maxZ);
},
isZero: function(x){
return Math.abs(x) < this.EPSILON;
},
linePlaneIntersection: function(normal, pointOnPlane, p1, p2){
var upper = pointOnPlane.clone().sub(p1);
upper = upper.dot(normal);
var l = p2.clone().sub(p1);
var lower = l.dot(normal);
if(lower === 0) return undefined;
var d = upper / lower;
var intersectionPoint = l.multiplyScalar(d).add(p1);
return BSPTree.utils.isPointInSegment(
intersectionPoint, p1, p2
) ? intersectionPoint : undefined;
},
pointsEqual: function(p1, p2){
return this.isZero(p2.distanceTo(p1));
},
projectVertex: function(vertex){
var oldModelMatrix = _modelMatrix;
_modelMatrix = this.IDENTITY_MATRIX;
renderList.projectVertex(vertex);
vertex.positionScreen.w = 1;
_modelMatrix = oldModelMatrix;
},
sign: function(x){
if(this.isZero(x)){
return 0;
} else if(x > 0) {
return 1;
} else {
return -1;
}
},
EPSILON: 1e-6,
IDENTITY_MATRIX: new THREE.Matrix4()
}
BSPTree.Node = function(){
this.back = null;
this.front = null;
}
BSPTree.Node.prototype.isBehind = function(node){
var normal = node.getNormal();
var point = node.getPointOnPlane();
var viewer = _camera.position;
var viewerSign = BSPTree.utils.getPointSign(normal, viewer, point);
var thisSign = this.getSign(normal, point);
if(thisSign === undefined) return undefined;
return (viewerSign !== thisSign);
}
BSPTree.LineNode = function(element){
this.element = element;
this.isTriangle = false;
};
BSPTree.LineNode.prototype = Object.create(BSPTree.Node.prototype);
BSPTree.LineNode.prototype.getNormal = function(){
var l = new THREE.Line3(
this.element.v1.positionWorld,
this.element.v2.positionWorld
);
var pt = l.closestPointToPoint(_camera.position, false);
return pt.sub(_camera.position);
}
BSPTree.LineNode.prototype.getPointOnPlane = function(){
return this.element.v1.positionWorld;
}
BSPTree.LineNode.prototype.getSign = function(normal, pointOnPlane){
var s1 = BSPTree.utils.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane);
var s2 = BSPTree.utils.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane);
var sMax = Math.max(s1, s2);
var sMin = Math.min(s1, s2);
switch(Math.abs(sMax-sMin)){
case 0:
return sMax;
case 1:
return sMax || sMin;
case 2:
return undefined;
default:
throw new Error('It looks like some unexpected FP error!');
}
// If we are here, it means that BSP without splitting failed.
// Trying 'painter sort' algorithm, based on average
// z-coordinate of triangle points.
return painterSort(a, b);
}
}
BSPTree.LineNode.prototype.separate = function(normal, point){
var intersectionPoint = BSPTree.utils.linePlaneIntersection(
normal, point,
this.element.v1.positionWorld,
this.element.v2.positionWorld
);
if(intersectionPoint){
var newLine = getNextLineInPool();
newLine.copy(this.element);
var vertex = getNextVertexInPool();
vertex.position.copy(intersectionPoint);
BSPTree.utils.projectVertex(vertex);
newLine.v1.copy(vertex);
newLine.v2 = this.element.v2;
this.element.v2 = vertex;
return [this, BSPTree.utils.createNode(newLine)];
} else {
return [this];
}
}
BSPTree.TriangleNode = function(element){
this.element = element;
this.isTriangle = true;
};
BSPTree.TriangleNode.prototype = Object.create(BSPTree.Node.prototype);
BSPTree.TriangleNode.prototype.getNormal = function(){
return this.element.normalModel;
}
BSPTree.TriangleNode.prototype.getPointOnPlane = function(){
return this.element.v1.positionWorld;
}
BSPTree.TriangleNode.prototype.getSign = function(normal, pointOnPlane){
var s1 = BSPTree.utils.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane);
var s2 = BSPTree.utils.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane);
var s3 = BSPTree.utils.getPointSign(normal, this.element.v3.positionWorld, pointOnPlane);
var sMax = Math.max(s1, s2, s3);
var sMin = Math.min(s1, s2, s3);
switch(Math.abs(sMax-sMin)){
case 0:
return sMax;
case 1:
return sMax || sMin;
case 2:
return undefined;
default:
throw new Error('It looks like some unexpected FP error!');
}
}
BSPTree.TriangleNode.prototype.separate = function(normal, pointOnPlane){
var p1 = this.element.v1.positionWorld;
var p2 = this.element.v2.positionWorld;
var p3 = this.element.v3.positionWorld;
// Intersection points
var i12 = BSPTree.utils.linePlaneIntersection(
normal, pointOnPlane, p1, p2
);
var i23 = BSPTree.utils.linePlaneIntersection(
normal, pointOnPlane, p2, p3
);
var i31 = BSPTree.utils.linePlaneIntersection(
normal, pointOnPlane, p3, p1
);
if(i12 && i23 && i31){
// Special case, one split point is a vertex
// In this case we split triangle into two
var iVertex, iSide;
var vertex = getNextVertexInPool();
var newTriangle = getNextFaceInPool();
newTriangle.copy(this.element);
if(BSPTree.utils.pointsEqual(i12, i23)){
vertex.position = i31;
BSPTree.utils.projectVertex(vertex);
this.element.v3.copy(vertex);
newTriangle.v1 = vertex;
} else if(BSPTree.utils.pointsEqual(i23, i31)){
vertex.position = i12;
BSPTree.utils.projectVertex(vertex);
this.element.v1.copy(vertex);
newTriangle.v2 = vertex;
} else {
vertex.position = i23;
BSPTree.utils.projectVertex(vertex);
this.element.v2.copy(vertex);
newTriangle.v3 = vertex;
}
return [this, BSPTree.utils.createNode(newTriangle)];
} else {
var t1 = getNextFaceInPool();
var t2 = getNextFaceInPool();
t1.copy(this.element);
t2.copy(this.element);
var v1 = getNextVertexInPool();
var v2 = getNextVertexInPool();
// Split triangle into three triangles
if(!i12){
this.element.v1.position = i31;
this.element.v2.position = i23;
BSPTree.utils.projectVertex(this.element.v1);
BSPTree.utils.projectVertex(this.element.v2);
t1.v2.copy(this.element.v2);
t1.v3.copy(this.element.v1);
t2.v3.copy(this.element.v2);
} else if(!i23){
this.element.v2.position = i12;
this.element.v3.position = i31;
BSPTree.utils.projectVertex(this.element.v2);
BSPTree.utils.projectVertex(this.element.v3);
t1.v1.copy(this.element.v2);
t1.v3.copy(this.element.v3);
t2.v1.copy(this.element.v3);
} else {
this.element.v1.position = i12;
this.element.v3.position = i23;
BSPTree.utils.projectVertex(this.element.v1);
BSPTree.utils.projectVertex(this.element.v3);
t1.v1.copy(this.element.v1);
t1.v2.copy(this.element.v3);
t2.v2.copy(this.element.v1);
}
return [
this,
BSPTree.utils.createNode(t1),
BSPTree.utils.createNode(t2)
]
}
}
function painterSort(a, b){
if ( a.z !== b.z ) {
......@@ -1187,6 +1295,6 @@ define(function(require, exports, module){
};
module.exports = Projector;
return Projector;
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment