Commit 57219920 authored by Dmitry's avatar Dmitry

Working BSP tree

parent af595692
...@@ -4,9 +4,7 @@ ...@@ -4,9 +4,7 @@
* @author julianwa / https://github.com/julianwa * @author julianwa / https://github.com/julianwa
*/ */
define(function(require, exports, module){ define(['three'], function(THREE, BSPTree){
var THREE = require("three");
var Projector = {}; var Projector = {};
var i, l, o; // counters var i, l, o; // counters
...@@ -42,6 +40,22 @@ define(function(require, exports, module){ ...@@ -42,6 +40,22 @@ define(function(require, exports, module){
}; };
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 () { Projector.RenderableVertex = function () {
this.position = new THREE.Vector3(); this.position = new THREE.Vector3();
...@@ -73,6 +87,14 @@ define(function(require, exports, module){ ...@@ -73,6 +87,14 @@ define(function(require, exports, module){
}; };
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 () { Projector.RenderableSprite = function () {
this.id = 0; this.id = 0;
...@@ -656,6 +678,7 @@ define(function(require, exports, module){ ...@@ -656,6 +678,7 @@ define(function(require, exports, module){
if ( vertices.length === 0 ) continue; if ( vertices.length === 0 ) continue;
var v1 = getNextVertexInPool(); var v1 = getNextVertexInPool();
v1.positionWorld.copy( vertices[ 0 ] );
v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix ); v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix );
// Handle LineStrip and LinePieces // Handle LineStrip and LinePieces
...@@ -664,6 +687,7 @@ define(function(require, exports, module){ ...@@ -664,6 +687,7 @@ define(function(require, exports, module){
for ( var v = 1, vl = vertices.length; v < vl; v ++ ) { for ( var v = 1, vl = vertices.length; v < vl; v ++ ) {
v1 = getNextVertexInPool(); v1 = getNextVertexInPool();
v1.positionWorld.copy( vertices[ v ] );
v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix ); v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix );
if ( ( v + 1 ) % step > 0 ) continue; if ( ( v + 1 ) % step > 0 ) continue;
...@@ -683,7 +707,10 @@ define(function(require, exports, module){ ...@@ -683,7 +707,10 @@ define(function(require, exports, module){
_line.id = object.id; _line.id = object.id;
_line.v1.positionScreen.copy( _clippedVertex1PositionScreen ); _line.v1.positionScreen.copy( _clippedVertex1PositionScreen );
_line.v1.positionWorld.copy( v1.positionWorld );
_line.v2.positionScreen.copy( _clippedVertex2PositionScreen ); _line.v2.positionScreen.copy( _clippedVertex2PositionScreen );
_line.v2.positionWorld.copy( v2.positionWorld );
_line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z ); _line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z );
...@@ -741,20 +768,106 @@ define(function(require, exports, module){ ...@@ -741,20 +768,106 @@ define(function(require, exports, module){
if ( sortElements === true ) { if ( sortElements === true ) {
_renderData.elements.sort( customSort ); //_renderData.elements.sort( customSort );
} }
// Build the BSP tree // 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; return _renderData;
}; };
// Pools
function getNextObjectInPool() {
if ( _objectCount === _objectPoolLength ) {
var object = new Projector.RenderableObject();
_objectPool.push( object );
_objectPoolLength ++;
_objectCount ++;
return object;
}
return _objectPool[ _objectCount ++ ];
}
function getNextVertexInPool() {
if ( _vertexCount === _vertexPoolLength ) {
var vertex = new Projector.RenderableVertex();
_vertexPool.push( vertex );
_vertexPoolLength ++;
_vertexCount ++;
return vertex;
}
return _vertexPool[ _vertexCount ++ ];
}
function getNextFaceInPool() {
if ( _faceCount === _facePoolLength ) {
var face = new Projector.RenderableFace();
_facePool.push( face );
_facePoolLength ++;
_faceCount ++;
return face;
}
return _facePool[ _faceCount ++ ];
}
function getNextLineInPool() {
if ( _lineCount === _linePoolLength ) {
var line = new Projector.RenderableLine();
_linePool.push( line );
_linePoolLength ++;
_lineCount ++;
return line;
}
return _linePool[ _lineCount ++ ];
}
function getNextSpriteInPool() {
if ( _spriteCount === _spritePoolLength ) {
var sprite = new Projector.RenderableSprite();
_spritePool.push( sprite );
_spritePoolLength ++;
_spriteCount ++;
return sprite;
}
return _spritePool[ _spriteCount ++ ];
}
function BSPTree(data){ function BSPTree(data){
if(data.length){ if(data.length){
this.root = BSPTree.createNode(data[0]); this.root = BSPTree.utils.createNode(data[0]);
for(var i = 1; i < data.length; i++){ for(var i = 1; i < data.length; i++){
this.insert(data[i]); this.insert(data[i]);
} }
...@@ -765,29 +878,35 @@ define(function(require, exports, module){ ...@@ -765,29 +878,35 @@ define(function(require, exports, module){
compareWith = compareWith || this.root; compareWith = compareWith || this.root;
var node = (element instanceof BSPTree.Node) ? var node = (element instanceof BSPTree.Node) ?
element : element :
BSPTree.createNode(element); BSPTree.utils.createNode(element);
var comparison = compareWith.isBehind(node); var comparison = node.isBehind(compareWith);
if(comparison === undefined){ if(comparison === undefined){
var fragments = node.separate(compareWith); var fragments = node.separate(
compareWith.getNormal(),
compareWith.getPointOnPlane()
);
var self = this;
fragments.forEach(function(f){ fragments.forEach(function(f){
this.insert(f, compareWith); self.insert(f, compareWith);
}); });
return; return;
} }
if(comparison){ if(comparison){
if(!compareWith.front){
compareWith.front = node;
} else {
this.insert(node, compareWith.front);
}
} else {
if(!compareWith.back){ if(!compareWith.back){
compareWith.back = node; compareWith.back = node;
} else { } else {
this.insert(node, compareWith.back); this.insert(node, compareWith.back);
} }
} else {
if(!compareWith.front){
compareWith.front = node;
} else {
this.insert(node, compareWith.front);
}
} }
}; };
...@@ -796,7 +915,7 @@ define(function(require, exports, module){ ...@@ -796,7 +915,7 @@ define(function(require, exports, module){
function traverse(node){ function traverse(node){
if(node){ if(node){
traverse(node.back); traverse(node.back);
output.push(node); output.push(node.element);
traverse(node.front); traverse(node.front);
} }
} }
...@@ -804,29 +923,70 @@ define(function(require, exports, module){ ...@@ -804,29 +923,70 @@ define(function(require, exports, module){
return output; return output;
} }
BSPTree.createNode = function(element){ BSPTree.utils = {
return new (element.v3 ? BSPTree.LineNode : BSPTree.TriangleNode)(element); 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);
BSPTree.getPointSign = function(normal, point, pointOnPlane){ var l = p2.clone().sub(p1);
return BSPTree.sign(normal.dot(point.clone().sub(pointOnPlane))); var lower = l.dot(normal);
}
BSPTree.isZero = function(x){ if(lower === 0) return undefined;
return Math.abs(x) < BSPTree.SIGN_EPSILON;
}
BSPTree.sign = function(x){ var d = upper / lower;
if(Math.abs(x) < BSPTree.SIGN_EPSILON){
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; return 0;
} else if(x > 0) { } else if(x > 0) {
return 1; return 1;
} else { } else {
return -1; return -1;
} }
} },
BSPTree.SIGN_EPSILON = 1e-6; EPSILON: 1e-6,
IDENTITY_MATRIX: new THREE.Matrix4()
}
BSPTree.Node = function(){ BSPTree.Node = function(){
...@@ -835,16 +995,16 @@ define(function(require, exports, module){ ...@@ -835,16 +995,16 @@ define(function(require, exports, module){
} }
BSPTree.Node.prototype.isBehind = function(node){ BSPTree.Node.prototype.isBehind = function(node){
var normal = this.getNormal(); var normal = node.getNormal();
var point = this.getPointOnPlane(); var point = node.getPointOnPlane();
var viewer = _camera.position; var viewer = _camera.position;
var viewerSign = BSPTree.getPointSign(normal, viewer, point); var viewerSign = BSPTree.utils.getPointSign(normal, viewer, point);
var nodeSign = node.getSign(normal, point); var thisSign = this.getSign(normal, point);
if(nodeSign === undefined) return undefined; if(thisSign === undefined) return undefined;
return (viewerSign !== nodeSign); return (viewerSign !== thisSign);
} }
...@@ -854,7 +1014,7 @@ define(function(require, exports, module){ ...@@ -854,7 +1014,7 @@ define(function(require, exports, module){
}; };
BSPTree.LineNode.prototype = Object.create(BSPTree.Node.prototype); BSPTree.LineNode.prototype = Object.create(BSPTree.Node.prototype);
BSPTree.LineNode.getNormal = function(){ BSPTree.LineNode.prototype.getNormal = function(){
var l = new THREE.Line3( var l = new THREE.Line3(
this.element.v1.positionWorld, this.element.v1.positionWorld,
this.element.v2.positionWorld this.element.v2.positionWorld
...@@ -868,42 +1028,49 @@ define(function(require, exports, module){ ...@@ -868,42 +1028,49 @@ define(function(require, exports, module){
} }
BSPTree.LineNode.prototype.getSign = function(normal, pointOnPlane){ BSPTree.LineNode.prototype.getSign = function(normal, pointOnPlane){
var sign = BSPTree.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane); var s1 = BSPTree.utils.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane);
var signTmp = BSPTree.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane); var s2 = BSPTree.utils.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane);
if(signTmp !== sign) return undefined;
return sign; 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!');
} }
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){ BSPTree.LineNode.prototype.separate = function(normal, point){
var upper = point.clone().sub(this.element.v1.positionWorld); var intersectionPoint = BSPTree.utils.linePlaneIntersection(
upper = upper.dot(normal); normal, point,
this.element.v1.positionWorld,
var l0 = this.element.v2.positionWorld; this.element.v2.positionWorld
);
var l = l0.clone().sub(this.element.v1.positionWorld);
var lower = l.dot(normal);
if(lower === 0) return this; if(intersectionPoint){
var newLine = getNextLineInPool();
newLine.copy(this.element);
var d = upper / lower; var vertex = getNextVertexInPool();
vertex.position.copy(intersectionPoint);
BSPTree.utils.projectVertex(vertex);
var intersectionPoint = l.multiplyScalar(d).add(l0); newLine.v1.copy(vertex);
newLine.v2 = this.element.v2;
this.element.v2 = vertex;
return intersectionPoint; return [this, BSPTree.utils.createNode(newLine)];
} else {
return [this];
} }
}
BSPTree.TriangleNode = function(element){ BSPTree.TriangleNode = function(element){
this.element = element; this.element = element;
...@@ -911,7 +1078,7 @@ define(function(require, exports, module){ ...@@ -911,7 +1078,7 @@ define(function(require, exports, module){
}; };
BSPTree.TriangleNode.prototype = Object.create(BSPTree.Node.prototype); BSPTree.TriangleNode.prototype = Object.create(BSPTree.Node.prototype);
BSPTree.TriangleNode.getNormal = function(){ BSPTree.TriangleNode.prototype.getNormal = function(){
return this.element.normalModel; return this.element.normalModel;
} }
...@@ -920,182 +1087,123 @@ define(function(require, exports, module){ ...@@ -920,182 +1087,123 @@ define(function(require, exports, module){
} }
BSPTree.TriangleNode.prototype.getSign = function(normal, pointOnPlane){ BSPTree.TriangleNode.prototype.getSign = function(normal, pointOnPlane){
var sign = BSPTree.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane); var s1 = BSPTree.utils.getPointSign(normal, this.element.v1.positionWorld, pointOnPlane);
var signTmp = BSPTree.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane); var s2 = BSPTree.utils.getPointSign(normal, this.element.v2.positionWorld, pointOnPlane);
if(signTmp !== sign) return undefined; var s3 = BSPTree.utils.getPointSign(normal, this.element.v3.positionWorld, pointOnPlane);
var signTmp = BSPTree.getPointSign(normal, this.element.v3.positionWorld, pointOnPlane);
if(signTmp !== sign) return undefined; var sMax = Math.max(s1, s2, s3);
return sign; var sMin = Math.min(s1, s2, s3);
}
switch(Math.abs(sMax-sMin)){
// Pools case 0:
return sMax;
function getNextObjectInPool() { case 1:
return sMax || sMin;
if ( _objectCount === _objectPoolLength ) { case 2:
return undefined;
var object = new Projector.RenderableObject(); default:
_objectPool.push( object ); throw new Error('It looks like some unexpected FP error!');
_objectPoolLength ++;
_objectCount ++;
return object;
}
return _objectPool[ _objectCount ++ ];
}
function getNextVertexInPool() {
if ( _vertexCount === _vertexPoolLength ) {
var vertex = new Projector.RenderableVertex();
_vertexPool.push( vertex );
_vertexPoolLength ++;
_vertexCount ++;
return vertex;
}
return _vertexPool[ _vertexCount ++ ];
}
function getNextFaceInPool() {
if ( _faceCount === _facePoolLength ) {
var face = new Projector.RenderableFace();
_facePool.push( face );
_facePoolLength ++;
_faceCount ++;
return face;
} }
return _facePool[ _faceCount ++ ];
} }
function getNextLineInPool() { 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;
if ( _lineCount === _linePoolLength ) { // 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
);
var line = new Projector.RenderableLine(); if(i12 && i23 && i31){
_linePool.push( line ); // Special case, one split point is a vertex
_linePoolLength ++; // In this case we split triangle into two
_lineCount ++; var iVertex, iSide;
return line;
} var vertex = getNextVertexInPool();
var newTriangle = getNextFaceInPool();
newTriangle.copy(this.element);
return _linePool[ _lineCount ++ ]; 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);
function getNextSpriteInPool() { this.element.v1.copy(vertex);
newTriangle.v2 = vertex;
if ( _spriteCount === _spritePoolLength ) { } else {
vertex.position = i23;
var sprite = new Projector.RenderableSprite(); BSPTree.utils.projectVertex(vertex);
_spritePool.push( sprite );
_spritePoolLength ++;
_spriteCount ++;
return sprite;
this.element.v2.copy(vertex);
newTriangle.v3 = vertex;
} }
return _spritePool[ _spriteCount ++ ]; return [this, BSPTree.utils.createNode(newTriangle)];
} else {
var t1 = getNextFaceInPool();
var t2 = getNextFaceInPool();
} t1.copy(this.element);
t2.copy(this.element);
// ---------- var v1 = getNextVertexInPool();
// This sorting algorithm uses a combination of 'painter sort' and var v2 = getNextVertexInPool();
// 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 // Split triangle into three triangles
// on another side if(!i12){
function getPointSign(planeNormal, point, pointOnPlane){ this.element.v1.position = i31;
return planeNormal.dot(point.clone().sub(pointOnPlane)) > 0; this.element.v2.position = i23;
} BSPTree.utils.projectVertex(this.element.v1);
BSPTree.utils.projectVertex(this.element.v2);
function compareFaces(a, b){ t1.v2.copy(this.element.v2);
var normal = b.normalModel; t1.v3.copy(this.element.v1);
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; t2.v3.copy(this.element.v2);
if(!point) return undefined; } 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);
var viewer = _camera.position; t1.v1.copy(this.element.v2);
t1.v3.copy(this.element.v3);
var viewerSign = getPointSign(normal, viewer, point); t2.v1.copy(this.element.v3);
var aSign, signTmp; } else {
this.element.v1.position = i12;
this.element.v3.position = i23;
BSPTree.utils.projectVertex(this.element.v1);
BSPTree.utils.projectVertex(this.element.v3);
try { t1.v1.copy(this.element.v1);
var aSign = getPointSign(normal, a.v1.positionWorld, point); t1.v2.copy(this.element.v3);
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;
}
return (viewerSign === aSign); t2.v2.copy(this.element.v1);
} catch(e) {
return undefined;
}
} }
return [
// Output: this,
// - positive means a is in front of b; BSPTree.utils.createNode(t1),
// - negative means b is in front of a; BSPTree.utils.createNode(t2)
// - 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 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);
}
function painterSort(a, b){ function painterSort(a, b){
if ( a.z !== b.z ) { if ( a.z !== b.z ) {
...@@ -1187,6 +1295,6 @@ define(function(require, exports, module){ ...@@ -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