Browse Source

QuickHull3: Initial Commit

Mugen87 8 years ago
parent
commit
934000c11d

+ 3 - 0
src/constants.js

@@ -123,3 +123,6 @@ export var RGBM16Encoding = 3005;
 export var RGBDEncoding = 3006;
 export var BasicDepthPacking = 3200;
 export var RGBADepthPacking = 3201;
+export var Visible = 0;
+export var NonConvex = 1;
+export var Deleted = 2;

+ 130 - 0
src/math/convexhull/Face.js

@@ -0,0 +1,130 @@
+import { HalfEdge } from './HalfEdge';
+import { Vector3 } from '../Vector3';
+import { Triangle } from '../Triangle';
+import { Visible, NonConvex, Deleted } from '../../constants';
+
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+function Face() {
+
+  this.normal = new Vector3();
+  this.midpoint = new Vector3();
+  this.area = 0;
+
+  this.constant = 0; // signed distance from face to the origin
+  this.outside = null; // reference to the a vertex in a vertex list this face can see
+  this.mark = Visible;
+  this.edge = null;
+
+}
+
+Object.assign( Face, {
+
+  create: function( a, b, c ) {
+
+    var face = new Face();
+
+    var e0 = new HalfEdge( a, face );
+    var e1 = new HalfEdge( b, face );
+    var e2 = new HalfEdge( c, face );
+
+    // join edges
+
+    e0.next = e2.prev = e1;
+    e1.next = e0.prev = e2;
+    e2.next = e1.prev = e0;
+
+    // main half edge reference
+
+    face.edge = e0;
+
+    return face.compute();
+
+  }
+
+} );
+
+Object.assign( Face.prototype, {
+
+  getEdge: function ( i ) {
+
+    var edge = this.edge;
+
+    while ( i > 0 ) {
+
+      edge = edge.next;
+      i -= 1;
+
+    }
+
+    while ( i < 0 ) {
+
+      edge = edge.prev;
+      i += 1;
+
+    }
+
+    return edge;
+
+  },
+
+  compute: function () {
+
+    var triangle;
+
+    return function compute () {
+
+      if ( triangle === undefined ) triangle = new Triangle();
+
+      var a = this.edge.start();
+      var b = this.edge.end();
+      var c = this.edge.next.end();
+
+      triangle.set( a, b, c );
+
+      triangle.normal( this.normal );
+      triangle.midpoint( this.midpoint );
+      triangle.area( this.area );
+
+      this.constant = this.normal.dot( this.midpoint );
+
+      return this;
+
+    };
+
+  },
+
+  distanceToPlane: function ( point ) {
+
+    return this.normal.dot( point ) - this.constant;
+
+  },
+
+  clone: function () {
+
+    return new this.constructor().copy( this );
+
+  },
+
+	copy: function ( other ) {
+
+		this.normal.copy( other.normal );
+		this.midpoint.copy( other.midpoint );
+    this.area = other.area;
+
+    this.constant = other.constant;
+    this.outside = other.outside;
+    this.mark = other.mark;
+    this.edge = other.edge;
+
+		return this;
+
+	}
+
+} );
+
+
+export { Face };

+ 92 - 0
src/math/convexhull/HalfEdge.js

@@ -0,0 +1,92 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * Entity for a Doubly-Connected Edge List (DCEL).
+ *
+ */
+
+function HalfEdge( vertex, face ) {
+
+  this.vertex = vertex;
+  this.face = face;
+  this.next = null;
+  this.prev = null;
+  this.twin = null;
+
+}
+
+Object.assign( HalfEdge.prototype, {
+
+	start: function () {
+
+		return this.vertex;
+
+	},
+
+  end: function () {
+
+    return this.next ? this.next.vertex : null;
+
+  },
+
+  length: function () {
+
+    var start = this.end();
+    var end = this.end();
+
+    if ( end !== null ) {
+
+      return start.point.distanceTo( end.point );
+
+    }
+
+    return - 1;
+
+  },
+
+  lengthSquared: function () {
+
+    var start = this.end();
+    var end = this.end();
+
+    if ( end !== null ) {
+
+      return start.point.distanceToSquared( end.point );
+
+    }
+
+    return - 1;
+
+  },
+
+  setTwin: function ( edge ) {
+
+    this.twin = edge;
+    edge.twin = this;
+
+    return this;
+
+  },
+
+  clone: function () {
+
+    return new this.constructor().copy( this );
+
+  },
+
+	copy: function ( other ) {
+
+		this.vertex.copy( other.vertex );
+		this.face.copy( other.face );
+		this.next.copy( other.next );
+		this.prev.copy( other.prev );
+		this.twin.copy( other.twin );
+
+		return this;
+
+	}
+
+} );
+
+
+export { HalfEdge };

+ 241 - 0
src/math/convexhull/QuickHull3.js

@@ -0,0 +1,241 @@
+import { Vertex } from './Vertex';
+import { VertexList } from './VertexList';
+import { Visible, NonConvex, Deleted } from '../../constants';
+
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+function QuickHull3( points ) {
+
+	if ( Array.isArray( points ) !== true ) {
+
+		console.error( 'THREE.QuickHull3: Points parameter is not an array.' );
+
+	}
+
+	if ( points.length < 4 ) {
+
+		console.error( 'THREE.QuickHull3: The algorithm needs at least four points.' );
+
+	}
+
+	this.tolerance = - 1;
+
+	this.count = {
+		points: points.length,
+		faces: 0
+	};
+
+	this.faces = [];
+	this.newFaces = [];
+	this.discardedFaces = [];
+	this.vertexPointIndices = [];
+
+	this.claimed = new VertexList();
+	this.unclaimed = new VertexList();
+
+	this.vertices = []; // vertices of the hull (internal representation of points)
+
+	for ( var i = 0; i < this.count.points; i ++ ) {
+
+		this.vertices.push( new Vertex( points[ i ], i ) );
+
+	}
+
+}
+
+Object.assign( QuickHull3.prototype, {
+
+	addVertexToFace: function ( vertex, face ) {
+
+		vertex.face = face;
+
+		if ( face.outside === null ) {
+
+			this.claimed.append( vertex );
+
+		} else {
+
+			this.claimed.insertBefore( face.outside, vertex );
+
+		}
+
+		face.outside = vertex;
+
+		return this;
+
+	},
+
+	// Removes 'vertex' for the 'claimed' list of vertices.
+	// It also makes sure that the link from 'face' to the first vertex it sees in 'claimed' is linked correctly after the removal
+
+	removeVertexFromFace: function ( vertex, face ) {
+
+		if ( vertex === face.outside ) {
+
+			// fix face.outside link
+
+			if ( vertex.next !== null && vertex.next.face === face ) {
+
+				// face has at least 2 outside vertices, move the 'outside' reference
+
+				face.outside = vertex.next;
+
+			} else {
+
+				// vertex was the only outside vertex that face had
+
+				face.outside = null;
+
+			}
+
+		}
+
+		this.claimed.remove( vertex );
+
+	},
+
+	// Removes all the visible vertices that 'face' is able to see which are stored in the 'claimed' vertext list
+
+	removeAllVerticesFromFace: function ( face ) {
+
+		if ( face.outside !== null ) {
+
+			// reference to the first and last vertex of this face
+
+			var start = face.outside;
+			var end = face.outside;
+
+			while ( end.next !== null && end.next.face === face ) {
+
+				end = end.next;
+
+			}
+
+			this.claimed.removeSubList( start, end );
+
+			// fix references
+
+			start.prev = end.next = null;
+			face.outside = null;
+
+			return start;
+
+		}
+
+	},
+
+	deleteFaceVertices: function ( face, absorbingFace ) {
+
+		var faceVertices = this.removeAllVerticesFromFace( face );
+
+		if ( faceVertices !== undefined ) {
+
+			this.unclaimed.appendChain( faceVertices );
+
+			if ( absorbingFace === undefined ) {
+
+			} else {
+
+				// if there is an absorbing face assign vertices to it
+
+				var vertex = faceVertices;
+
+				do {
+
+					// we need to buffer the subsequent vertex at this point because the 'vertex.next' reference
+					// will be changed by upcoming method calls
+
+					var nextVertex = vertex.next;
+
+					var distance = absorbingFace.distanceToPlane( vertex.point );
+
+					// check if 'vertex' is able to see 'absorbingFace'
+
+					if ( distance > this.tolerance ) {
+
+						this.addVertexToFace( vertex, absorbingFace );
+
+					} else {
+
+						this.unclaimed.append( vertex );
+
+					}
+
+					// now assign next vertex
+
+					vertex = nextVertex;
+
+				} while ( vertex !== null );
+
+			}
+
+		}
+
+	},
+
+	// Reassigns as many vertices as possible from the unclaimed list to the new faces
+
+	resolveUnclaimedPoints: function ( newFaces ) {
+
+		var vertex = this.unclaimed.head;
+
+		do {
+
+			// buffer next reference, see .deleteFaceVertices()
+
+			var nextVertex = vertex.next;
+
+			var maxDistance = this.tolerance;
+
+			var maxFace = null;
+
+			for ( var i = 0; i < newFaces.length; i ++ ) {
+
+				var face = newFaces[ i ];
+
+				if ( face.mark === Visible ) {
+
+					var distance = face.distanceToPlane( vertex.point );
+
+					if ( distance > maxDistance ) {
+
+						maxDistance = distance;
+						maxFace = face;
+
+					}
+
+					if ( maxDistance > 1000 * this.tolerance ) break; // TODO: Why this?
+
+				}
+
+			}
+
+			if ( maxFace !== null ) {
+
+				this.addVertexToFace( vertex, maxFace );
+
+			}
+
+		} while ( vertex !== null );
+
+	},
+
+  clone: function () {
+
+    return new this.constructor().copy( this );
+
+  },
+
+	copy: function ( other ) {
+
+    return this;
+
+	}
+
+} );
+
+
+export { QuickHull3 };

+ 18 - 0
src/math/convexhull/Vertex.js

@@ -0,0 +1,18 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * A vertex is a double linked list node.
+ *
+ */
+
+function Vertex( point, index ) {
+
+  this.point = point;
+  this.index = index; // index in the input array of points
+  this.next = null;
+  this.prev = null;
+  this.face = null; // the face that is able to see this point
+
+}
+
+export { Vertex };

+ 202 - 0
src/math/convexhull/VertexList.js

@@ -0,0 +1,202 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+function VertexList() {
+
+  this.head = null;
+  this.tail = null;
+
+}
+
+Object.assign( VertexList.prototype, {
+
+	clear: function () {
+
+		this.head = this.tail = null;
+
+    return this;
+
+	},
+
+  // Inserts a vertex before a 'target' vertex
+
+	insertBefore: function ( target, vertex ) {
+
+		vertex.prev = target.prev;
+    vertex.next = target;
+
+    if ( vertex.prev === null ) {
+
+      this.head = vertex;
+
+    } else {
+
+      vertex.prev.next = vertex;
+
+    }
+
+    target.prev = vertex;
+
+    return this;
+
+	},
+
+  // Inserts a vertex after a 'target' vertex
+
+  insertAfter: function ( target, vertex ) {
+
+    vertex.prev = target;
+    vertex.next = target.next;
+
+    if ( vertex.next === null ) {
+
+      this.tail = vertex;
+
+    } else {
+
+      vertex.next.prev = vertex;
+
+    }
+
+    target.next = vertex;
+
+    return this;
+
+  },
+
+  // Appends a 'node' to the end of this doubly linked list
+
+  append: function ( vertex ) {
+
+    if ( this.head === null ) {
+
+      this.head = vertex;
+
+    } else {
+
+      this.tail.next = vertex;
+
+    }
+
+    vertex.prev = this.tail;
+    vertex.next = null; // the tail has no subsequent vertex
+
+    this.tail = vertex;
+
+    return this;
+
+  },
+
+  // Appends a chain of vertices where 'vertex' is the head.
+
+  appendChain: function ( vertex ) {
+
+    if ( this.head === null ) {
+
+      this.head = vertex;
+
+    } else {
+
+      this.tail.next = vertex;
+
+    }
+
+    vertex.prev = this.tail;
+
+    // ensure that the 'tail' reference points to the last vertex of the chain
+
+    while ( vertex.next !== null ) {
+
+      vertex = vertex.next;
+
+    }
+
+    this.tail = vertex;
+
+    return this;
+
+  },
+
+  // Deletes a vertex from this linked list
+
+  remove: function ( vertex ) {
+
+    if ( vertex.prev === null ) {
+
+      this.head = vertex.next;
+
+    } else {
+
+      vertex.prev.next = vertex.next;
+
+    }
+
+    if ( vertex.next === null ) {
+
+      this.tail = vertex.prev;
+
+    } else {
+
+      vertex.next.prev = vertex.prev;
+
+    }
+
+    return this;
+
+  },
+
+  // Removes a list of vertices whose 'head' is 'a' and whose 'tail' is b
+
+  removeSubList: function ( a, b ) {
+
+    if ( a.prev === null ) {
+
+      this.head = b.next;
+
+    } else {
+
+      a.prev.next = b.next;
+
+    }
+
+    if ( b.next === null ) {
+
+      this.tail = a.prev;
+
+    } else {
+
+      b.next.prev = a.prev;
+
+    }
+
+    return this;
+
+  },
+
+  isEmpty: function() {
+
+    return this.head === null;
+
+  },
+
+  clone: function () {
+
+    return new this.constructor().copy( this );
+
+  },
+
+	copy: function ( other ) {
+
+    this.head.copy( other.head );
+    this.tail.copy( other.tail );
+
+		return this;
+
+	}
+
+} );
+
+
+export { VertexList };