initial commit
This commit is contained in:
810
lib/extras/Earcut.js
Normal file
810
lib/extras/Earcut.js
Normal file
@ -0,0 +1,810 @@
|
||||
/**
|
||||
* @author Mugen87 / https://github.com/Mugen87
|
||||
* Port from https://github.com/mapbox/earcut (v2.1.2)
|
||||
*/
|
||||
|
||||
var Earcut = {
|
||||
|
||||
triangulate: function ( data, holeIndices, dim ) {
|
||||
|
||||
dim = dim || 2;
|
||||
|
||||
var hasHoles = holeIndices && holeIndices.length,
|
||||
outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length,
|
||||
outerNode = linkedList( data, 0, outerLen, dim, true ),
|
||||
triangles = [];
|
||||
|
||||
if ( ! outerNode ) return triangles;
|
||||
|
||||
var minX, minY, maxX, maxY, x, y, invSize;
|
||||
|
||||
if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );
|
||||
|
||||
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
||||
|
||||
if ( data.length > 80 * dim ) {
|
||||
|
||||
minX = maxX = data[ 0 ];
|
||||
minY = maxY = data[ 1 ];
|
||||
|
||||
for ( var i = dim; i < outerLen; i += dim ) {
|
||||
|
||||
x = data[ i ];
|
||||
y = data[ i + 1 ];
|
||||
if ( x < minX ) minX = x;
|
||||
if ( y < minY ) minY = y;
|
||||
if ( x > maxX ) maxX = x;
|
||||
if ( y > maxY ) maxY = y;
|
||||
|
||||
}
|
||||
|
||||
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
|
||||
|
||||
invSize = Math.max( maxX - minX, maxY - minY );
|
||||
invSize = invSize !== 0 ? 1 / invSize : 0;
|
||||
|
||||
}
|
||||
|
||||
earcutLinked( outerNode, triangles, dim, minX, minY, invSize );
|
||||
|
||||
return triangles;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// create a circular doubly linked list from polygon points in the specified winding order
|
||||
|
||||
function linkedList( data, start, end, dim, clockwise ) {
|
||||
|
||||
var i, last;
|
||||
|
||||
if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) {
|
||||
|
||||
for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );
|
||||
|
||||
} else {
|
||||
|
||||
for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );
|
||||
|
||||
}
|
||||
|
||||
if ( last && equals( last, last.next ) ) {
|
||||
|
||||
removeNode( last );
|
||||
last = last.next;
|
||||
|
||||
}
|
||||
|
||||
return last;
|
||||
|
||||
}
|
||||
|
||||
// eliminate colinear or duplicate points
|
||||
|
||||
function filterPoints( start, end ) {
|
||||
|
||||
if ( ! start ) return start;
|
||||
if ( ! end ) end = start;
|
||||
|
||||
var p = start, again;
|
||||
|
||||
do {
|
||||
|
||||
again = false;
|
||||
|
||||
if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) {
|
||||
|
||||
removeNode( p );
|
||||
p = end = p.prev;
|
||||
if ( p === p.next ) break;
|
||||
again = true;
|
||||
|
||||
} else {
|
||||
|
||||
p = p.next;
|
||||
|
||||
}
|
||||
|
||||
} while ( again || p !== end );
|
||||
|
||||
return end;
|
||||
|
||||
}
|
||||
|
||||
// main ear slicing loop which triangulates a polygon (given as a linked list)
|
||||
|
||||
function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
|
||||
|
||||
if ( ! ear ) return;
|
||||
|
||||
// interlink polygon nodes in z-order
|
||||
|
||||
if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize );
|
||||
|
||||
var stop = ear, prev, next;
|
||||
|
||||
// iterate through ears, slicing them one by one
|
||||
|
||||
while ( ear.prev !== ear.next ) {
|
||||
|
||||
prev = ear.prev;
|
||||
next = ear.next;
|
||||
|
||||
if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) {
|
||||
|
||||
// cut off the triangle
|
||||
triangles.push( prev.i / dim );
|
||||
triangles.push( ear.i / dim );
|
||||
triangles.push( next.i / dim );
|
||||
|
||||
removeNode( ear );
|
||||
|
||||
// skipping the next vertice leads to less sliver triangles
|
||||
ear = next.next;
|
||||
stop = next.next;
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
ear = next;
|
||||
|
||||
// if we looped through the whole remaining polygon and can't find any more ears
|
||||
|
||||
if ( ear === stop ) {
|
||||
|
||||
// try filtering points and slicing again
|
||||
|
||||
if ( ! pass ) {
|
||||
|
||||
earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );
|
||||
|
||||
// if this didn't work, try curing all small self-intersections locally
|
||||
|
||||
} else if ( pass === 1 ) {
|
||||
|
||||
ear = cureLocalIntersections( ear, triangles, dim );
|
||||
earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );
|
||||
|
||||
// as a last resort, try splitting the remaining polygon into two
|
||||
|
||||
} else if ( pass === 2 ) {
|
||||
|
||||
splitEarcut( ear, triangles, dim, minX, minY, invSize );
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check whether a polygon node forms a valid ear with adjacent nodes
|
||||
|
||||
function isEar( ear ) {
|
||||
|
||||
var a = ear.prev,
|
||||
b = ear,
|
||||
c = ear.next;
|
||||
|
||||
if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
|
||||
|
||||
// now make sure we don't have other points inside the potential ear
|
||||
var p = ear.next.next;
|
||||
|
||||
while ( p !== ear.prev ) {
|
||||
|
||||
if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
p = p.next;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
function isEarHashed( ear, minX, minY, invSize ) {
|
||||
|
||||
var a = ear.prev,
|
||||
b = ear,
|
||||
c = ear.next;
|
||||
|
||||
if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
|
||||
|
||||
// triangle bbox; min & max are calculated like this for speed
|
||||
|
||||
var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),
|
||||
minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),
|
||||
maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),
|
||||
maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );
|
||||
|
||||
// z-order range for the current triangle bbox;
|
||||
|
||||
var minZ = zOrder( minTX, minTY, minX, minY, invSize ),
|
||||
maxZ = zOrder( maxTX, maxTY, minX, minY, invSize );
|
||||
|
||||
// first look for points inside the triangle in increasing z-order
|
||||
|
||||
var p = ear.nextZ;
|
||||
|
||||
while ( p && p.z <= maxZ ) {
|
||||
|
||||
if ( p !== ear.prev && p !== ear.next &&
|
||||
pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
||||
area( p.prev, p, p.next ) >= 0 ) return false;
|
||||
p = p.nextZ;
|
||||
|
||||
}
|
||||
|
||||
// then look for points in decreasing z-order
|
||||
|
||||
p = ear.prevZ;
|
||||
|
||||
while ( p && p.z >= minZ ) {
|
||||
|
||||
if ( p !== ear.prev && p !== ear.next &&
|
||||
pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
||||
area( p.prev, p, p.next ) >= 0 ) return false;
|
||||
|
||||
p = p.prevZ;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// go through all polygon nodes and cure small local self-intersections
|
||||
|
||||
function cureLocalIntersections( start, triangles, dim ) {
|
||||
|
||||
var p = start;
|
||||
|
||||
do {
|
||||
|
||||
var a = p.prev, b = p.next.next;
|
||||
|
||||
if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {
|
||||
|
||||
triangles.push( a.i / dim );
|
||||
triangles.push( p.i / dim );
|
||||
triangles.push( b.i / dim );
|
||||
|
||||
// remove two nodes involved
|
||||
|
||||
removeNode( p );
|
||||
removeNode( p.next );
|
||||
|
||||
p = start = b;
|
||||
|
||||
}
|
||||
|
||||
p = p.next;
|
||||
|
||||
} while ( p !== start );
|
||||
|
||||
return p;
|
||||
|
||||
}
|
||||
|
||||
// try splitting polygon into two and triangulate them independently
|
||||
|
||||
function splitEarcut( start, triangles, dim, minX, minY, invSize ) {
|
||||
|
||||
// look for a valid diagonal that divides the polygon into two
|
||||
|
||||
var a = start;
|
||||
|
||||
do {
|
||||
|
||||
var b = a.next.next;
|
||||
|
||||
while ( b !== a.prev ) {
|
||||
|
||||
if ( a.i !== b.i && isValidDiagonal( a, b ) ) {
|
||||
|
||||
// split the polygon in two by the diagonal
|
||||
|
||||
var c = splitPolygon( a, b );
|
||||
|
||||
// filter colinear points around the cuts
|
||||
|
||||
a = filterPoints( a, a.next );
|
||||
c = filterPoints( c, c.next );
|
||||
|
||||
// run earcut on each half
|
||||
|
||||
earcutLinked( a, triangles, dim, minX, minY, invSize );
|
||||
earcutLinked( c, triangles, dim, minX, minY, invSize );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
b = b.next;
|
||||
|
||||
}
|
||||
|
||||
a = a.next;
|
||||
|
||||
} while ( a !== start );
|
||||
|
||||
}
|
||||
|
||||
// link every hole into the outer loop, producing a single-ring polygon without holes
|
||||
|
||||
function eliminateHoles( data, holeIndices, outerNode, dim ) {
|
||||
|
||||
var queue = [], i, len, start, end, list;
|
||||
|
||||
for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
|
||||
|
||||
start = holeIndices[ i ] * dim;
|
||||
end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;
|
||||
list = linkedList( data, start, end, dim, false );
|
||||
if ( list === list.next ) list.steiner = true;
|
||||
queue.push( getLeftmost( list ) );
|
||||
|
||||
}
|
||||
|
||||
queue.sort( compareX );
|
||||
|
||||
// process holes from left to right
|
||||
|
||||
for ( i = 0; i < queue.length; i ++ ) {
|
||||
|
||||
eliminateHole( queue[ i ], outerNode );
|
||||
outerNode = filterPoints( outerNode, outerNode.next );
|
||||
|
||||
}
|
||||
|
||||
return outerNode;
|
||||
|
||||
}
|
||||
|
||||
function compareX( a, b ) {
|
||||
|
||||
return a.x - b.x;
|
||||
|
||||
}
|
||||
|
||||
// find a bridge between vertices that connects hole with an outer ring and and link it
|
||||
|
||||
function eliminateHole( hole, outerNode ) {
|
||||
|
||||
outerNode = findHoleBridge( hole, outerNode );
|
||||
|
||||
if ( outerNode ) {
|
||||
|
||||
var b = splitPolygon( outerNode, hole );
|
||||
|
||||
filterPoints( b, b.next );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
||||
|
||||
function findHoleBridge( hole, outerNode ) {
|
||||
|
||||
var p = outerNode,
|
||||
hx = hole.x,
|
||||
hy = hole.y,
|
||||
qx = - Infinity,
|
||||
m;
|
||||
|
||||
// find a segment intersected by a ray from the hole's leftmost point to the left;
|
||||
// segment's endpoint with lesser x will be potential connection point
|
||||
|
||||
do {
|
||||
|
||||
if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
|
||||
|
||||
var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
|
||||
|
||||
if ( x <= hx && x > qx ) {
|
||||
|
||||
qx = x;
|
||||
|
||||
if ( x === hx ) {
|
||||
|
||||
if ( hy === p.y ) return p;
|
||||
if ( hy === p.next.y ) return p.next;
|
||||
|
||||
}
|
||||
|
||||
m = p.x < p.next.x ? p : p.next;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p = p.next;
|
||||
|
||||
} while ( p !== outerNode );
|
||||
|
||||
if ( ! m ) return null;
|
||||
|
||||
if ( hx === qx ) return m.prev; // hole touches outer segment; pick lower endpoint
|
||||
|
||||
// look for points inside the triangle of hole point, segment intersection and endpoint;
|
||||
// if there are no points found, we have a valid connection;
|
||||
// otherwise choose the point of the minimum angle with the ray as connection point
|
||||
|
||||
var stop = m,
|
||||
mx = m.x,
|
||||
my = m.y,
|
||||
tanMin = Infinity,
|
||||
tan;
|
||||
|
||||
p = m.next;
|
||||
|
||||
while ( p !== stop ) {
|
||||
|
||||
if ( hx >= p.x && p.x >= mx && hx !== p.x &&
|
||||
pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
|
||||
|
||||
tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
|
||||
|
||||
if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) {
|
||||
|
||||
m = p;
|
||||
tanMin = tan;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p = p.next;
|
||||
|
||||
}
|
||||
|
||||
return m;
|
||||
|
||||
}
|
||||
|
||||
// interlink polygon nodes in z-order
|
||||
|
||||
function indexCurve( start, minX, minY, invSize ) {
|
||||
|
||||
var p = start;
|
||||
|
||||
do {
|
||||
|
||||
if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize );
|
||||
p.prevZ = p.prev;
|
||||
p.nextZ = p.next;
|
||||
p = p.next;
|
||||
|
||||
} while ( p !== start );
|
||||
|
||||
p.prevZ.nextZ = null;
|
||||
p.prevZ = null;
|
||||
|
||||
sortLinked( p );
|
||||
|
||||
}
|
||||
|
||||
// Simon Tatham's linked list merge sort algorithm
|
||||
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
||||
|
||||
function sortLinked( list ) {
|
||||
|
||||
var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;
|
||||
|
||||
do {
|
||||
|
||||
p = list;
|
||||
list = null;
|
||||
tail = null;
|
||||
numMerges = 0;
|
||||
|
||||
while ( p ) {
|
||||
|
||||
numMerges ++;
|
||||
q = p;
|
||||
pSize = 0;
|
||||
|
||||
for ( i = 0; i < inSize; i ++ ) {
|
||||
|
||||
pSize ++;
|
||||
q = q.nextZ;
|
||||
if ( ! q ) break;
|
||||
|
||||
}
|
||||
|
||||
qSize = inSize;
|
||||
|
||||
while ( pSize > 0 || ( qSize > 0 && q ) ) {
|
||||
|
||||
if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
|
||||
|
||||
e = p;
|
||||
p = p.nextZ;
|
||||
pSize --;
|
||||
|
||||
} else {
|
||||
|
||||
e = q;
|
||||
q = q.nextZ;
|
||||
qSize --;
|
||||
|
||||
}
|
||||
|
||||
if ( tail ) tail.nextZ = e;
|
||||
else list = e;
|
||||
|
||||
e.prevZ = tail;
|
||||
tail = e;
|
||||
|
||||
}
|
||||
|
||||
p = q;
|
||||
|
||||
}
|
||||
|
||||
tail.nextZ = null;
|
||||
inSize *= 2;
|
||||
|
||||
} while ( numMerges > 1 );
|
||||
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
// z-order of a point given coords and inverse of the longer side of data bbox
|
||||
|
||||
function zOrder( x, y, minX, minY, invSize ) {
|
||||
|
||||
// coords are transformed into non-negative 15-bit integer range
|
||||
|
||||
x = 32767 * ( x - minX ) * invSize;
|
||||
y = 32767 * ( y - minY ) * invSize;
|
||||
|
||||
x = ( x | ( x << 8 ) ) & 0x00FF00FF;
|
||||
x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
|
||||
x = ( x | ( x << 2 ) ) & 0x33333333;
|
||||
x = ( x | ( x << 1 ) ) & 0x55555555;
|
||||
|
||||
y = ( y | ( y << 8 ) ) & 0x00FF00FF;
|
||||
y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
|
||||
y = ( y | ( y << 2 ) ) & 0x33333333;
|
||||
y = ( y | ( y << 1 ) ) & 0x55555555;
|
||||
|
||||
return x | ( y << 1 );
|
||||
|
||||
}
|
||||
|
||||
// find the leftmost node of a polygon ring
|
||||
|
||||
function getLeftmost( start ) {
|
||||
|
||||
var p = start, leftmost = start;
|
||||
|
||||
do {
|
||||
|
||||
if ( p.x < leftmost.x ) leftmost = p;
|
||||
p = p.next;
|
||||
|
||||
} while ( p !== start );
|
||||
|
||||
return leftmost;
|
||||
|
||||
}
|
||||
|
||||
// check if a point lies within a convex triangle
|
||||
|
||||
function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) {
|
||||
|
||||
return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&
|
||||
( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
|
||||
( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
|
||||
|
||||
}
|
||||
|
||||
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
||||
|
||||
function isValidDiagonal( a, b ) {
|
||||
|
||||
return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) &&
|
||||
locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
|
||||
|
||||
}
|
||||
|
||||
// signed area of a triangle
|
||||
|
||||
function area( p, q, r ) {
|
||||
|
||||
return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
|
||||
|
||||
}
|
||||
|
||||
// check if two points are equal
|
||||
|
||||
function equals( p1, p2 ) {
|
||||
|
||||
return p1.x === p2.x && p1.y === p2.y;
|
||||
|
||||
}
|
||||
|
||||
// check if two segments intersect
|
||||
|
||||
function intersects( p1, q1, p2, q2 ) {
|
||||
|
||||
if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) ||
|
||||
( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true;
|
||||
|
||||
return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 &&
|
||||
area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;
|
||||
|
||||
}
|
||||
|
||||
// check if a polygon diagonal intersects any polygon segments
|
||||
|
||||
function intersectsPolygon( a, b ) {
|
||||
|
||||
var p = a;
|
||||
|
||||
do {
|
||||
|
||||
if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
||||
intersects( p, p.next, a, b ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
p = p.next;
|
||||
|
||||
} while ( p !== a );
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// check if a polygon diagonal is locally inside the polygon
|
||||
|
||||
function locallyInside( a, b ) {
|
||||
|
||||
return area( a.prev, a, a.next ) < 0 ?
|
||||
area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :
|
||||
area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;
|
||||
|
||||
}
|
||||
|
||||
// check if the middle point of a polygon diagonal is inside the polygon
|
||||
|
||||
function middleInside( a, b ) {
|
||||
|
||||
var p = a,
|
||||
inside = false,
|
||||
px = ( a.x + b.x ) / 2,
|
||||
py = ( a.y + b.y ) / 2;
|
||||
|
||||
do {
|
||||
|
||||
if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&
|
||||
( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) {
|
||||
|
||||
inside = ! inside;
|
||||
|
||||
}
|
||||
|
||||
p = p.next;
|
||||
|
||||
} while ( p !== a );
|
||||
|
||||
return inside;
|
||||
|
||||
}
|
||||
|
||||
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
||||
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
||||
|
||||
function splitPolygon( a, b ) {
|
||||
|
||||
var a2 = new Node( a.i, a.x, a.y ),
|
||||
b2 = new Node( b.i, b.x, b.y ),
|
||||
an = a.next,
|
||||
bp = b.prev;
|
||||
|
||||
a.next = b;
|
||||
b.prev = a;
|
||||
|
||||
a2.next = an;
|
||||
an.prev = a2;
|
||||
|
||||
b2.next = a2;
|
||||
a2.prev = b2;
|
||||
|
||||
bp.next = b2;
|
||||
b2.prev = bp;
|
||||
|
||||
return b2;
|
||||
|
||||
}
|
||||
|
||||
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
||||
|
||||
function insertNode( i, x, y, last ) {
|
||||
|
||||
var p = new Node( i, x, y );
|
||||
|
||||
if ( ! last ) {
|
||||
|
||||
p.prev = p;
|
||||
p.next = p;
|
||||
|
||||
} else {
|
||||
|
||||
p.next = last.next;
|
||||
p.prev = last;
|
||||
last.next.prev = p;
|
||||
last.next = p;
|
||||
|
||||
}
|
||||
|
||||
return p;
|
||||
|
||||
}
|
||||
|
||||
function removeNode( p ) {
|
||||
|
||||
p.next.prev = p.prev;
|
||||
p.prev.next = p.next;
|
||||
|
||||
if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
|
||||
if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
|
||||
|
||||
}
|
||||
|
||||
function Node( i, x, y ) {
|
||||
|
||||
// vertice index in coordinates array
|
||||
this.i = i;
|
||||
|
||||
// vertex coordinates
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
// previous and next vertice nodes in a polygon ring
|
||||
this.prev = null;
|
||||
this.next = null;
|
||||
|
||||
// z-order curve value
|
||||
this.z = null;
|
||||
|
||||
// previous and next nodes in z-order
|
||||
this.prevZ = null;
|
||||
this.nextZ = null;
|
||||
|
||||
// indicates whether this is a steiner point
|
||||
this.steiner = false;
|
||||
|
||||
}
|
||||
|
||||
function signedArea( data, start, end, dim ) {
|
||||
|
||||
var sum = 0;
|
||||
|
||||
for ( var i = start, j = end - dim; i < end; i += dim ) {
|
||||
|
||||
sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
|
||||
j = i;
|
||||
|
||||
}
|
||||
|
||||
return sum;
|
||||
|
||||
}
|
||||
|
||||
export { Earcut };
|
55
lib/extras/ImageUtils.js
Normal file
55
lib/extras/ImageUtils.js
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @author mrdoob / http://mrdoob.com/
|
||||
* @author alteredq / http://alteredqualia.com/
|
||||
* @author szimek / https://github.com/szimek/
|
||||
*/
|
||||
|
||||
var ImageUtils = {
|
||||
|
||||
getDataURL: function ( image ) {
|
||||
|
||||
var canvas;
|
||||
|
||||
if ( typeof HTMLCanvasElement == 'undefined' ) {
|
||||
|
||||
return image.src;
|
||||
|
||||
} else if ( image instanceof HTMLCanvasElement ) {
|
||||
|
||||
canvas = image;
|
||||
|
||||
} else {
|
||||
|
||||
canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
|
||||
var context = canvas.getContext( '2d' );
|
||||
|
||||
if ( image instanceof ImageData ) {
|
||||
|
||||
context.putImageData( image, 0, 0 );
|
||||
|
||||
} else {
|
||||
|
||||
context.drawImage( image, 0, 0, image.width, image.height );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( canvas.width > 2048 || canvas.height > 2048 ) {
|
||||
|
||||
return canvas.toDataURL( 'image/jpeg', 0.6 );
|
||||
|
||||
} else {
|
||||
|
||||
return canvas.toDataURL( 'image/png' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { ImageUtils };
|
96
lib/extras/ShapeUtils.js
Normal file
96
lib/extras/ShapeUtils.js
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
*/
|
||||
|
||||
import { Earcut } from './Earcut.js';
|
||||
|
||||
var ShapeUtils = {
|
||||
|
||||
// calculate area of the contour polygon
|
||||
|
||||
area: function ( contour ) {
|
||||
|
||||
var n = contour.length;
|
||||
var a = 0.0;
|
||||
|
||||
for ( var p = n - 1, q = 0; q < n; p = q ++ ) {
|
||||
|
||||
a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
|
||||
|
||||
}
|
||||
|
||||
return a * 0.5;
|
||||
|
||||
},
|
||||
|
||||
isClockWise: function ( pts ) {
|
||||
|
||||
return ShapeUtils.area( pts ) < 0;
|
||||
|
||||
},
|
||||
|
||||
triangulateShape: function ( contour, holes ) {
|
||||
|
||||
var vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
|
||||
var holeIndices = []; // array of hole indices
|
||||
var faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
|
||||
|
||||
removeDupEndPts( contour );
|
||||
addContour( vertices, contour );
|
||||
|
||||
//
|
||||
|
||||
var holeIndex = contour.length;
|
||||
|
||||
holes.forEach( removeDupEndPts );
|
||||
|
||||
for ( var i = 0; i < holes.length; i ++ ) {
|
||||
|
||||
holeIndices.push( holeIndex );
|
||||
holeIndex += holes[ i ].length;
|
||||
addContour( vertices, holes[ i ] );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var triangles = Earcut.triangulate( vertices, holeIndices );
|
||||
|
||||
//
|
||||
|
||||
for ( var i = 0; i < triangles.length; i += 3 ) {
|
||||
|
||||
faces.push( triangles.slice( i, i + 3 ) );
|
||||
|
||||
}
|
||||
|
||||
return faces;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function removeDupEndPts( points ) {
|
||||
|
||||
var l = points.length;
|
||||
|
||||
if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
|
||||
|
||||
points.pop();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function addContour( vertices, contour ) {
|
||||
|
||||
for ( var i = 0; i < contour.length; i ++ ) {
|
||||
|
||||
vertices.push( contour[ i ].x );
|
||||
vertices.push( contour[ i ].y );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { ShapeUtils };
|
425
lib/extras/core/Curve.js
Normal file
425
lib/extras/core/Curve.js
Normal file
@ -0,0 +1,425 @@
|
||||
import { _Math } from '../../math/Math.js';
|
||||
import { Vector3 } from '../../math/Vector3.js';
|
||||
import { Matrix4 } from '../../math/Matrix4.js';
|
||||
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
* Extensible curve object
|
||||
*
|
||||
* Some common of curve methods:
|
||||
* .getPoint( t, optionalTarget ), .getTangent( t )
|
||||
* .getPointAt( u, optionalTarget ), .getTangentAt( u )
|
||||
* .getPoints(), .getSpacedPoints()
|
||||
* .getLength()
|
||||
* .updateArcLengths()
|
||||
*
|
||||
* This following curves inherit from THREE.Curve:
|
||||
*
|
||||
* -- 2D curves --
|
||||
* THREE.ArcCurve
|
||||
* THREE.CubicBezierCurve
|
||||
* THREE.EllipseCurve
|
||||
* THREE.LineCurve
|
||||
* THREE.QuadraticBezierCurve
|
||||
* THREE.SplineCurve
|
||||
*
|
||||
* -- 3D curves --
|
||||
* THREE.CatmullRomCurve3
|
||||
* THREE.CubicBezierCurve3
|
||||
* THREE.LineCurve3
|
||||
* THREE.QuadraticBezierCurve3
|
||||
*
|
||||
* A series of curves can be represented as a THREE.CurvePath.
|
||||
*
|
||||
**/
|
||||
|
||||
/**************************************************************
|
||||
* Abstract Curve base class
|
||||
**************************************************************/
|
||||
|
||||
function Curve() {
|
||||
|
||||
this.type = 'Curve';
|
||||
|
||||
this.arcLengthDivisions = 200;
|
||||
|
||||
}
|
||||
|
||||
Object.assign( Curve.prototype, {
|
||||
|
||||
// Virtual base class method to overwrite and implement in subclasses
|
||||
// - t [0 .. 1]
|
||||
|
||||
getPoint: function ( /* t, optionalTarget */ ) {
|
||||
|
||||
console.warn( 'THREE.Curve: .getPoint() not implemented.' );
|
||||
return null;
|
||||
|
||||
},
|
||||
|
||||
// Get point at relative position in curve according to arc length
|
||||
// - u [0 .. 1]
|
||||
|
||||
getPointAt: function ( u, optionalTarget ) {
|
||||
|
||||
var t = this.getUtoTmapping( u );
|
||||
return this.getPoint( t, optionalTarget );
|
||||
|
||||
},
|
||||
|
||||
// Get sequence of points using getPoint( t )
|
||||
|
||||
getPoints: function ( divisions ) {
|
||||
|
||||
if ( divisions === undefined ) divisions = 5;
|
||||
|
||||
var points = [];
|
||||
|
||||
for ( var d = 0; d <= divisions; d ++ ) {
|
||||
|
||||
points.push( this.getPoint( d / divisions ) );
|
||||
|
||||
}
|
||||
|
||||
return points;
|
||||
|
||||
},
|
||||
|
||||
// Get sequence of points using getPointAt( u )
|
||||
|
||||
getSpacedPoints: function ( divisions ) {
|
||||
|
||||
if ( divisions === undefined ) divisions = 5;
|
||||
|
||||
var points = [];
|
||||
|
||||
for ( var d = 0; d <= divisions; d ++ ) {
|
||||
|
||||
points.push( this.getPointAt( d / divisions ) );
|
||||
|
||||
}
|
||||
|
||||
return points;
|
||||
|
||||
},
|
||||
|
||||
// Get total curve arc length
|
||||
|
||||
getLength: function () {
|
||||
|
||||
var lengths = this.getLengths();
|
||||
return lengths[ lengths.length - 1 ];
|
||||
|
||||
},
|
||||
|
||||
// Get list of cumulative segment lengths
|
||||
|
||||
getLengths: function ( divisions ) {
|
||||
|
||||
if ( divisions === undefined ) divisions = this.arcLengthDivisions;
|
||||
|
||||
if ( this.cacheArcLengths &&
|
||||
( this.cacheArcLengths.length === divisions + 1 ) &&
|
||||
! this.needsUpdate ) {
|
||||
|
||||
return this.cacheArcLengths;
|
||||
|
||||
}
|
||||
|
||||
this.needsUpdate = false;
|
||||
|
||||
var cache = [];
|
||||
var current, last = this.getPoint( 0 );
|
||||
var p, sum = 0;
|
||||
|
||||
cache.push( 0 );
|
||||
|
||||
for ( p = 1; p <= divisions; p ++ ) {
|
||||
|
||||
current = this.getPoint( p / divisions );
|
||||
sum += current.distanceTo( last );
|
||||
cache.push( sum );
|
||||
last = current;
|
||||
|
||||
}
|
||||
|
||||
this.cacheArcLengths = cache;
|
||||
|
||||
return cache; // { sums: cache, sum: sum }; Sum is in the last element.
|
||||
|
||||
},
|
||||
|
||||
updateArcLengths: function () {
|
||||
|
||||
this.needsUpdate = true;
|
||||
this.getLengths();
|
||||
|
||||
},
|
||||
|
||||
// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
|
||||
|
||||
getUtoTmapping: function ( u, distance ) {
|
||||
|
||||
var arcLengths = this.getLengths();
|
||||
|
||||
var i = 0, il = arcLengths.length;
|
||||
|
||||
var targetArcLength; // The targeted u distance value to get
|
||||
|
||||
if ( distance ) {
|
||||
|
||||
targetArcLength = distance;
|
||||
|
||||
} else {
|
||||
|
||||
targetArcLength = u * arcLengths[ il - 1 ];
|
||||
|
||||
}
|
||||
|
||||
// binary search for the index with largest value smaller than target u distance
|
||||
|
||||
var low = 0, high = il - 1, comparison;
|
||||
|
||||
while ( low <= high ) {
|
||||
|
||||
i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
|
||||
|
||||
comparison = arcLengths[ i ] - targetArcLength;
|
||||
|
||||
if ( comparison < 0 ) {
|
||||
|
||||
low = i + 1;
|
||||
|
||||
} else if ( comparison > 0 ) {
|
||||
|
||||
high = i - 1;
|
||||
|
||||
} else {
|
||||
|
||||
high = i;
|
||||
break;
|
||||
|
||||
// DONE
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
i = high;
|
||||
|
||||
if ( arcLengths[ i ] === targetArcLength ) {
|
||||
|
||||
return i / ( il - 1 );
|
||||
|
||||
}
|
||||
|
||||
// we could get finer grain at lengths, or use simple interpolation between two points
|
||||
|
||||
var lengthBefore = arcLengths[ i ];
|
||||
var lengthAfter = arcLengths[ i + 1 ];
|
||||
|
||||
var segmentLength = lengthAfter - lengthBefore;
|
||||
|
||||
// determine where we are between the 'before' and 'after' points
|
||||
|
||||
var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
|
||||
|
||||
// add that fractional amount to t
|
||||
|
||||
var t = ( i + segmentFraction ) / ( il - 1 );
|
||||
|
||||
return t;
|
||||
|
||||
},
|
||||
|
||||
// Returns a unit vector tangent at t
|
||||
// In case any sub curve does not implement its tangent derivation,
|
||||
// 2 points a small delta apart will be used to find its gradient
|
||||
// which seems to give a reasonable approximation
|
||||
|
||||
getTangent: function ( t ) {
|
||||
|
||||
var delta = 0.0001;
|
||||
var t1 = t - delta;
|
||||
var t2 = t + delta;
|
||||
|
||||
// Capping in case of danger
|
||||
|
||||
if ( t1 < 0 ) t1 = 0;
|
||||
if ( t2 > 1 ) t2 = 1;
|
||||
|
||||
var pt1 = this.getPoint( t1 );
|
||||
var pt2 = this.getPoint( t2 );
|
||||
|
||||
var vec = pt2.clone().sub( pt1 );
|
||||
return vec.normalize();
|
||||
|
||||
},
|
||||
|
||||
getTangentAt: function ( u ) {
|
||||
|
||||
var t = this.getUtoTmapping( u );
|
||||
return this.getTangent( t );
|
||||
|
||||
},
|
||||
|
||||
computeFrenetFrames: function ( segments, closed ) {
|
||||
|
||||
// see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
|
||||
|
||||
var normal = new Vector3();
|
||||
|
||||
var tangents = [];
|
||||
var normals = [];
|
||||
var binormals = [];
|
||||
|
||||
var vec = new Vector3();
|
||||
var mat = new Matrix4();
|
||||
|
||||
var i, u, theta;
|
||||
|
||||
// compute the tangent vectors for each segment on the curve
|
||||
|
||||
for ( i = 0; i <= segments; i ++ ) {
|
||||
|
||||
u = i / segments;
|
||||
|
||||
tangents[ i ] = this.getTangentAt( u );
|
||||
tangents[ i ].normalize();
|
||||
|
||||
}
|
||||
|
||||
// select an initial normal vector perpendicular to the first tangent vector,
|
||||
// and in the direction of the minimum tangent xyz component
|
||||
|
||||
normals[ 0 ] = new Vector3();
|
||||
binormals[ 0 ] = new Vector3();
|
||||
var min = Number.MAX_VALUE;
|
||||
var tx = Math.abs( tangents[ 0 ].x );
|
||||
var ty = Math.abs( tangents[ 0 ].y );
|
||||
var tz = Math.abs( tangents[ 0 ].z );
|
||||
|
||||
if ( tx <= min ) {
|
||||
|
||||
min = tx;
|
||||
normal.set( 1, 0, 0 );
|
||||
|
||||
}
|
||||
|
||||
if ( ty <= min ) {
|
||||
|
||||
min = ty;
|
||||
normal.set( 0, 1, 0 );
|
||||
|
||||
}
|
||||
|
||||
if ( tz <= min ) {
|
||||
|
||||
normal.set( 0, 0, 1 );
|
||||
|
||||
}
|
||||
|
||||
vec.crossVectors( tangents[ 0 ], normal ).normalize();
|
||||
|
||||
normals[ 0 ].crossVectors( tangents[ 0 ], vec );
|
||||
binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
|
||||
|
||||
|
||||
// compute the slowly-varying normal and binormal vectors for each segment on the curve
|
||||
|
||||
for ( i = 1; i <= segments; i ++ ) {
|
||||
|
||||
normals[ i ] = normals[ i - 1 ].clone();
|
||||
|
||||
binormals[ i ] = binormals[ i - 1 ].clone();
|
||||
|
||||
vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
|
||||
|
||||
if ( vec.length() > Number.EPSILON ) {
|
||||
|
||||
vec.normalize();
|
||||
|
||||
theta = Math.acos( _Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
|
||||
|
||||
normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
|
||||
|
||||
}
|
||||
|
||||
binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
|
||||
|
||||
}
|
||||
|
||||
// if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
|
||||
|
||||
if ( closed === true ) {
|
||||
|
||||
theta = Math.acos( _Math.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
|
||||
theta /= segments;
|
||||
|
||||
if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
|
||||
|
||||
theta = - theta;
|
||||
|
||||
}
|
||||
|
||||
for ( i = 1; i <= segments; i ++ ) {
|
||||
|
||||
// twist a little...
|
||||
normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
|
||||
binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
tangents: tangents,
|
||||
normals: normals,
|
||||
binormals: binormals
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
clone: function () {
|
||||
|
||||
return new this.constructor().copy( this );
|
||||
|
||||
},
|
||||
|
||||
copy: function ( source ) {
|
||||
|
||||
this.arcLengthDivisions = source.arcLengthDivisions;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
|
||||
var data = {
|
||||
metadata: {
|
||||
version: 4.5,
|
||||
type: 'Curve',
|
||||
generator: 'Curve.toJSON'
|
||||
}
|
||||
};
|
||||
|
||||
data.arcLengthDivisions = this.arcLengthDivisions;
|
||||
data.type = this.type;
|
||||
|
||||
return data;
|
||||
|
||||
},
|
||||
|
||||
fromJSON: function ( json ) {
|
||||
|
||||
this.arcLengthDivisions = json.arcLengthDivisions;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { Curve };
|
261
lib/extras/core/CurvePath.js
Normal file
261
lib/extras/core/CurvePath.js
Normal file
@ -0,0 +1,261 @@
|
||||
import { Curve } from './Curve.js';
|
||||
import * as Curves from '../curves/Curves.js';
|
||||
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
*
|
||||
**/
|
||||
|
||||
/**************************************************************
|
||||
* Curved Path - a curve path is simply a array of connected
|
||||
* curves, but retains the api of a curve
|
||||
**************************************************************/
|
||||
|
||||
function CurvePath() {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'CurvePath';
|
||||
|
||||
this.curves = [];
|
||||
this.autoClose = false; // Automatically closes the path
|
||||
|
||||
}
|
||||
|
||||
CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {
|
||||
|
||||
constructor: CurvePath,
|
||||
|
||||
add: function ( curve ) {
|
||||
|
||||
this.curves.push( curve );
|
||||
|
||||
},
|
||||
|
||||
closePath: function () {
|
||||
|
||||
// Add a line curve if start and end of lines are not connected
|
||||
var startPoint = this.curves[ 0 ].getPoint( 0 );
|
||||
var endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );
|
||||
|
||||
if ( ! startPoint.equals( endPoint ) ) {
|
||||
|
||||
this.curves.push( new Curves[ 'LineCurve' ]( endPoint, startPoint ) );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// To get accurate point with reference to
|
||||
// entire path distance at time t,
|
||||
// following has to be done:
|
||||
|
||||
// 1. Length of each sub path have to be known
|
||||
// 2. Locate and identify type of curve
|
||||
// 3. Get t for the curve
|
||||
// 4. Return curve.getPointAt(t')
|
||||
|
||||
getPoint: function ( t ) {
|
||||
|
||||
var d = t * this.getLength();
|
||||
var curveLengths = this.getCurveLengths();
|
||||
var i = 0;
|
||||
|
||||
// To think about boundaries points.
|
||||
|
||||
while ( i < curveLengths.length ) {
|
||||
|
||||
if ( curveLengths[ i ] >= d ) {
|
||||
|
||||
var diff = curveLengths[ i ] - d;
|
||||
var curve = this.curves[ i ];
|
||||
|
||||
var segmentLength = curve.getLength();
|
||||
var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
|
||||
|
||||
return curve.getPointAt( u );
|
||||
|
||||
}
|
||||
|
||||
i ++;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
// loop where sum != 0, sum > d , sum+1 <d
|
||||
|
||||
},
|
||||
|
||||
// We cannot use the default THREE.Curve getPoint() with getLength() because in
|
||||
// THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
|
||||
// getPoint() depends on getLength
|
||||
|
||||
getLength: function () {
|
||||
|
||||
var lens = this.getCurveLengths();
|
||||
return lens[ lens.length - 1 ];
|
||||
|
||||
},
|
||||
|
||||
// cacheLengths must be recalculated.
|
||||
updateArcLengths: function () {
|
||||
|
||||
this.needsUpdate = true;
|
||||
this.cacheLengths = null;
|
||||
this.getCurveLengths();
|
||||
|
||||
},
|
||||
|
||||
// Compute lengths and cache them
|
||||
// We cannot overwrite getLengths() because UtoT mapping uses it.
|
||||
|
||||
getCurveLengths: function () {
|
||||
|
||||
// We use cache values if curves and cache array are same length
|
||||
|
||||
if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
|
||||
|
||||
return this.cacheLengths;
|
||||
|
||||
}
|
||||
|
||||
// Get length of sub-curve
|
||||
// Push sums into cached array
|
||||
|
||||
var lengths = [], sums = 0;
|
||||
|
||||
for ( var i = 0, l = this.curves.length; i < l; i ++ ) {
|
||||
|
||||
sums += this.curves[ i ].getLength();
|
||||
lengths.push( sums );
|
||||
|
||||
}
|
||||
|
||||
this.cacheLengths = lengths;
|
||||
|
||||
return lengths;
|
||||
|
||||
},
|
||||
|
||||
getSpacedPoints: function ( divisions ) {
|
||||
|
||||
if ( divisions === undefined ) divisions = 40;
|
||||
|
||||
var points = [];
|
||||
|
||||
for ( var i = 0; i <= divisions; i ++ ) {
|
||||
|
||||
points.push( this.getPoint( i / divisions ) );
|
||||
|
||||
}
|
||||
|
||||
if ( this.autoClose ) {
|
||||
|
||||
points.push( points[ 0 ] );
|
||||
|
||||
}
|
||||
|
||||
return points;
|
||||
|
||||
},
|
||||
|
||||
getPoints: function ( divisions ) {
|
||||
|
||||
divisions = divisions || 12;
|
||||
|
||||
var points = [], last;
|
||||
|
||||
for ( var i = 0, curves = this.curves; i < curves.length; i ++ ) {
|
||||
|
||||
var curve = curves[ i ];
|
||||
var resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2
|
||||
: ( curve && ( curve.isLineCurve || curve.isLineCurve3 ) ) ? 1
|
||||
: ( curve && curve.isSplineCurve ) ? divisions * curve.points.length
|
||||
: divisions;
|
||||
|
||||
var pts = curve.getPoints( resolution );
|
||||
|
||||
for ( var j = 0; j < pts.length; j ++ ) {
|
||||
|
||||
var point = pts[ j ];
|
||||
|
||||
if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
|
||||
|
||||
points.push( point );
|
||||
last = point;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
|
||||
|
||||
points.push( points[ 0 ] );
|
||||
|
||||
}
|
||||
|
||||
return points;
|
||||
|
||||
},
|
||||
|
||||
copy: function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.curves = [];
|
||||
|
||||
for ( var i = 0, l = source.curves.length; i < l; i ++ ) {
|
||||
|
||||
var curve = source.curves[ i ];
|
||||
|
||||
this.curves.push( curve.clone() );
|
||||
|
||||
}
|
||||
|
||||
this.autoClose = source.autoClose;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.autoClose = this.autoClose;
|
||||
data.curves = [];
|
||||
|
||||
for ( var i = 0, l = this.curves.length; i < l; i ++ ) {
|
||||
|
||||
var curve = this.curves[ i ];
|
||||
data.curves.push( curve.toJSON() );
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
},
|
||||
|
||||
fromJSON: function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.autoClose = json.autoClose;
|
||||
this.curves = [];
|
||||
|
||||
for ( var i = 0, l = json.curves.length; i < l; i ++ ) {
|
||||
|
||||
var curve = json.curves[ i ];
|
||||
this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { CurvePath };
|
145
lib/extras/core/Font.js
Normal file
145
lib/extras/core/Font.js
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
* @author mrdoob / http://mrdoob.com/
|
||||
*/
|
||||
|
||||
import { ShapePath } from './ShapePath.js';
|
||||
|
||||
|
||||
function Font( data ) {
|
||||
|
||||
this.type = 'Font';
|
||||
|
||||
this.data = data;
|
||||
|
||||
}
|
||||
|
||||
Object.assign( Font.prototype, {
|
||||
|
||||
isFont: true,
|
||||
|
||||
generateShapes: function ( text, size ) {
|
||||
|
||||
if ( size === undefined ) size = 100;
|
||||
|
||||
var shapes = [];
|
||||
var paths = createPaths( text, size, this.data );
|
||||
|
||||
for ( var p = 0, pl = paths.length; p < pl; p ++ ) {
|
||||
|
||||
Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
|
||||
|
||||
}
|
||||
|
||||
return shapes;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
function createPaths( text, size, data ) {
|
||||
|
||||
var chars = Array.from ? Array.from( text ) : String( text ).split( '' ); // see #13988
|
||||
var scale = size / data.resolution;
|
||||
var line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;
|
||||
|
||||
var paths = [];
|
||||
|
||||
var offsetX = 0, offsetY = 0;
|
||||
|
||||
for ( var i = 0; i < chars.length; i ++ ) {
|
||||
|
||||
var char = chars[ i ];
|
||||
|
||||
if ( char === '\n' ) {
|
||||
|
||||
offsetX = 0;
|
||||
offsetY -= line_height;
|
||||
|
||||
} else {
|
||||
|
||||
var ret = createPath( char, scale, offsetX, offsetY, data );
|
||||
offsetX += ret.offsetX;
|
||||
paths.push( ret.path );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return paths;
|
||||
|
||||
}
|
||||
|
||||
function createPath( char, scale, offsetX, offsetY, data ) {
|
||||
|
||||
var glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
|
||||
|
||||
if ( ! glyph ) return;
|
||||
|
||||
var path = new ShapePath();
|
||||
|
||||
var x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
|
||||
|
||||
if ( glyph.o ) {
|
||||
|
||||
var outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
|
||||
|
||||
for ( var i = 0, l = outline.length; i < l; ) {
|
||||
|
||||
var action = outline[ i ++ ];
|
||||
|
||||
switch ( action ) {
|
||||
|
||||
case 'm': // moveTo
|
||||
|
||||
x = outline[ i ++ ] * scale + offsetX;
|
||||
y = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.moveTo( x, y );
|
||||
|
||||
break;
|
||||
|
||||
case 'l': // lineTo
|
||||
|
||||
x = outline[ i ++ ] * scale + offsetX;
|
||||
y = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.lineTo( x, y );
|
||||
|
||||
break;
|
||||
|
||||
case 'q': // quadraticCurveTo
|
||||
|
||||
cpx = outline[ i ++ ] * scale + offsetX;
|
||||
cpy = outline[ i ++ ] * scale + offsetY;
|
||||
cpx1 = outline[ i ++ ] * scale + offsetX;
|
||||
cpy1 = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
|
||||
|
||||
break;
|
||||
|
||||
case 'b': // bezierCurveTo
|
||||
|
||||
cpx = outline[ i ++ ] * scale + offsetX;
|
||||
cpy = outline[ i ++ ] * scale + offsetY;
|
||||
cpx1 = outline[ i ++ ] * scale + offsetX;
|
||||
cpy1 = outline[ i ++ ] * scale + offsetY;
|
||||
cpx2 = outline[ i ++ ] * scale + offsetX;
|
||||
cpy2 = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return { offsetX: glyph.ha * scale, path: path };
|
||||
|
||||
}
|
||||
|
||||
export { Font };
|
81
lib/extras/core/Interpolations.js
Normal file
81
lib/extras/core/Interpolations.js
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
*
|
||||
* Bezier Curves formulas obtained from
|
||||
* http://en.wikipedia.org/wiki/Bézier_curve
|
||||
*/
|
||||
|
||||
function CatmullRom( t, p0, p1, p2, p3 ) {
|
||||
|
||||
var v0 = ( p2 - p0 ) * 0.5;
|
||||
var v1 = ( p3 - p1 ) * 0.5;
|
||||
var t2 = t * t;
|
||||
var t3 = t * t2;
|
||||
return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function QuadraticBezierP0( t, p ) {
|
||||
|
||||
var k = 1 - t;
|
||||
return k * k * p;
|
||||
|
||||
}
|
||||
|
||||
function QuadraticBezierP1( t, p ) {
|
||||
|
||||
return 2 * ( 1 - t ) * t * p;
|
||||
|
||||
}
|
||||
|
||||
function QuadraticBezierP2( t, p ) {
|
||||
|
||||
return t * t * p;
|
||||
|
||||
}
|
||||
|
||||
function QuadraticBezier( t, p0, p1, p2 ) {
|
||||
|
||||
return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
|
||||
QuadraticBezierP2( t, p2 );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function CubicBezierP0( t, p ) {
|
||||
|
||||
var k = 1 - t;
|
||||
return k * k * k * p;
|
||||
|
||||
}
|
||||
|
||||
function CubicBezierP1( t, p ) {
|
||||
|
||||
var k = 1 - t;
|
||||
return 3 * k * k * t * p;
|
||||
|
||||
}
|
||||
|
||||
function CubicBezierP2( t, p ) {
|
||||
|
||||
return 3 * ( 1 - t ) * t * t * p;
|
||||
|
||||
}
|
||||
|
||||
function CubicBezierP3( t, p ) {
|
||||
|
||||
return t * t * t * p;
|
||||
|
||||
}
|
||||
|
||||
function CubicBezier( t, p0, p1, p2, p3 ) {
|
||||
|
||||
return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
|
||||
CubicBezierP3( t, p3 );
|
||||
|
||||
}
|
||||
|
||||
export { CatmullRom, QuadraticBezier, CubicBezier };
|
183
lib/extras/core/Path.js
Normal file
183
lib/extras/core/Path.js
Normal file
@ -0,0 +1,183 @@
|
||||
import { Vector2 } from '../../math/Vector2.js';
|
||||
import { CurvePath } from './CurvePath.js';
|
||||
import { EllipseCurve } from '../curves/EllipseCurve.js';
|
||||
import { SplineCurve } from '../curves/SplineCurve.js';
|
||||
import { CubicBezierCurve } from '../curves/CubicBezierCurve.js';
|
||||
import { QuadraticBezierCurve } from '../curves/QuadraticBezierCurve.js';
|
||||
import { LineCurve } from '../curves/LineCurve.js';
|
||||
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
* Creates free form 2d path using series of points, lines or curves.
|
||||
**/
|
||||
|
||||
function Path( points ) {
|
||||
|
||||
CurvePath.call( this );
|
||||
|
||||
this.type = 'Path';
|
||||
|
||||
this.currentPoint = new Vector2();
|
||||
|
||||
if ( points ) {
|
||||
|
||||
this.setFromPoints( points );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {
|
||||
|
||||
constructor: Path,
|
||||
|
||||
setFromPoints: function ( points ) {
|
||||
|
||||
this.moveTo( points[ 0 ].x, points[ 0 ].y );
|
||||
|
||||
for ( var i = 1, l = points.length; i < l; i ++ ) {
|
||||
|
||||
this.lineTo( points[ i ].x, points[ i ].y );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
moveTo: function ( x, y ) {
|
||||
|
||||
this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
|
||||
|
||||
},
|
||||
|
||||
lineTo: function ( x, y ) {
|
||||
|
||||
var curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
|
||||
this.curves.push( curve );
|
||||
|
||||
this.currentPoint.set( x, y );
|
||||
|
||||
},
|
||||
|
||||
quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
|
||||
|
||||
var curve = new QuadraticBezierCurve(
|
||||
this.currentPoint.clone(),
|
||||
new Vector2( aCPx, aCPy ),
|
||||
new Vector2( aX, aY )
|
||||
);
|
||||
|
||||
this.curves.push( curve );
|
||||
|
||||
this.currentPoint.set( aX, aY );
|
||||
|
||||
},
|
||||
|
||||
bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
|
||||
|
||||
var curve = new CubicBezierCurve(
|
||||
this.currentPoint.clone(),
|
||||
new Vector2( aCP1x, aCP1y ),
|
||||
new Vector2( aCP2x, aCP2y ),
|
||||
new Vector2( aX, aY )
|
||||
);
|
||||
|
||||
this.curves.push( curve );
|
||||
|
||||
this.currentPoint.set( aX, aY );
|
||||
|
||||
},
|
||||
|
||||
splineThru: function ( pts /*Array of Vector*/ ) {
|
||||
|
||||
var npts = [ this.currentPoint.clone() ].concat( pts );
|
||||
|
||||
var curve = new SplineCurve( npts );
|
||||
this.curves.push( curve );
|
||||
|
||||
this.currentPoint.copy( pts[ pts.length - 1 ] );
|
||||
|
||||
},
|
||||
|
||||
arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
|
||||
|
||||
var x0 = this.currentPoint.x;
|
||||
var y0 = this.currentPoint.y;
|
||||
|
||||
this.absarc( aX + x0, aY + y0, aRadius,
|
||||
aStartAngle, aEndAngle, aClockwise );
|
||||
|
||||
},
|
||||
|
||||
absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
|
||||
|
||||
this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
|
||||
|
||||
},
|
||||
|
||||
ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
|
||||
|
||||
var x0 = this.currentPoint.x;
|
||||
var y0 = this.currentPoint.y;
|
||||
|
||||
this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
|
||||
|
||||
},
|
||||
|
||||
absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
|
||||
|
||||
var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
|
||||
|
||||
if ( this.curves.length > 0 ) {
|
||||
|
||||
// if a previous curve is present, attempt to join
|
||||
var firstPoint = curve.getPoint( 0 );
|
||||
|
||||
if ( ! firstPoint.equals( this.currentPoint ) ) {
|
||||
|
||||
this.lineTo( firstPoint.x, firstPoint.y );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.curves.push( curve );
|
||||
|
||||
var lastPoint = curve.getPoint( 1 );
|
||||
this.currentPoint.copy( lastPoint );
|
||||
|
||||
},
|
||||
|
||||
copy: function ( source ) {
|
||||
|
||||
CurvePath.prototype.copy.call( this, source );
|
||||
|
||||
this.currentPoint.copy( source.currentPoint );
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
|
||||
var data = CurvePath.prototype.toJSON.call( this );
|
||||
|
||||
data.currentPoint = this.currentPoint.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
},
|
||||
|
||||
fromJSON: function ( json ) {
|
||||
|
||||
CurvePath.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.currentPoint.fromArray( json.currentPoint );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { Path };
|
115
lib/extras/core/Shape.js
Normal file
115
lib/extras/core/Shape.js
Normal file
@ -0,0 +1,115 @@
|
||||
import { Path } from './Path.js';
|
||||
import { _Math } from '../../math/Math.js';
|
||||
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
* Defines a 2d shape plane using paths.
|
||||
**/
|
||||
|
||||
// STEP 1 Create a path.
|
||||
// STEP 2 Turn path into shape.
|
||||
// STEP 3 ExtrudeGeometry takes in Shape/Shapes
|
||||
// STEP 3a - Extract points from each shape, turn to vertices
|
||||
// STEP 3b - Triangulate each shape, add faces.
|
||||
|
||||
function Shape( points ) {
|
||||
|
||||
Path.call( this, points );
|
||||
|
||||
this.uuid = _Math.generateUUID();
|
||||
|
||||
this.type = 'Shape';
|
||||
|
||||
this.holes = [];
|
||||
|
||||
}
|
||||
|
||||
Shape.prototype = Object.assign( Object.create( Path.prototype ), {
|
||||
|
||||
constructor: Shape,
|
||||
|
||||
getPointsHoles: function ( divisions ) {
|
||||
|
||||
var holesPts = [];
|
||||
|
||||
for ( var i = 0, l = this.holes.length; i < l; i ++ ) {
|
||||
|
||||
holesPts[ i ] = this.holes[ i ].getPoints( divisions );
|
||||
|
||||
}
|
||||
|
||||
return holesPts;
|
||||
|
||||
},
|
||||
|
||||
// get points of shape and holes (keypoints based on segments parameter)
|
||||
|
||||
extractPoints: function ( divisions ) {
|
||||
|
||||
return {
|
||||
|
||||
shape: this.getPoints( divisions ),
|
||||
holes: this.getPointsHoles( divisions )
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
copy: function ( source ) {
|
||||
|
||||
Path.prototype.copy.call( this, source );
|
||||
|
||||
this.holes = [];
|
||||
|
||||
for ( var i = 0, l = source.holes.length; i < l; i ++ ) {
|
||||
|
||||
var hole = source.holes[ i ];
|
||||
|
||||
this.holes.push( hole.clone() );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
|
||||
var data = Path.prototype.toJSON.call( this );
|
||||
|
||||
data.uuid = this.uuid;
|
||||
data.holes = [];
|
||||
|
||||
for ( var i = 0, l = this.holes.length; i < l; i ++ ) {
|
||||
|
||||
var hole = this.holes[ i ];
|
||||
data.holes.push( hole.toJSON() );
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
},
|
||||
|
||||
fromJSON: function ( json ) {
|
||||
|
||||
Path.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.uuid = json.uuid;
|
||||
this.holes = [];
|
||||
|
||||
for ( var i = 0, l = json.holes.length; i < l; i ++ ) {
|
||||
|
||||
var hole = json.holes[ i ];
|
||||
this.holes.push( new Path().fromJSON( hole ) );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { Shape };
|
286
lib/extras/core/ShapePath.js
Normal file
286
lib/extras/core/ShapePath.js
Normal file
@ -0,0 +1,286 @@
|
||||
/**
|
||||
* @author zz85 / http://www.lab4games.net/zz85/blog
|
||||
* minimal class for proxing functions to Path. Replaces old "extractSubpaths()"
|
||||
**/
|
||||
|
||||
import { Color } from '../../math/Color.js';
|
||||
import { Path } from './Path.js';
|
||||
import { Shape } from './Shape.js';
|
||||
import { ShapeUtils } from '../ShapeUtils.js';
|
||||
|
||||
function ShapePath() {
|
||||
|
||||
this.type = 'ShapePath';
|
||||
|
||||
this.color = new Color();
|
||||
|
||||
this.subPaths = [];
|
||||
this.currentPath = null;
|
||||
|
||||
}
|
||||
|
||||
Object.assign( ShapePath.prototype, {
|
||||
|
||||
moveTo: function ( x, y ) {
|
||||
|
||||
this.currentPath = new Path();
|
||||
this.subPaths.push( this.currentPath );
|
||||
this.currentPath.moveTo( x, y );
|
||||
|
||||
},
|
||||
|
||||
lineTo: function ( x, y ) {
|
||||
|
||||
this.currentPath.lineTo( x, y );
|
||||
|
||||
},
|
||||
|
||||
quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
|
||||
|
||||
this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
|
||||
|
||||
},
|
||||
|
||||
bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
|
||||
|
||||
this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
|
||||
|
||||
},
|
||||
|
||||
splineThru: function ( pts ) {
|
||||
|
||||
this.currentPath.splineThru( pts );
|
||||
|
||||
},
|
||||
|
||||
toShapes: function ( isCCW, noHoles ) {
|
||||
|
||||
function toShapesNoHoles( inSubpaths ) {
|
||||
|
||||
var shapes = [];
|
||||
|
||||
for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) {
|
||||
|
||||
var tmpPath = inSubpaths[ i ];
|
||||
|
||||
var tmpShape = new Shape();
|
||||
tmpShape.curves = tmpPath.curves;
|
||||
|
||||
shapes.push( tmpShape );
|
||||
|
||||
}
|
||||
|
||||
return shapes;
|
||||
|
||||
}
|
||||
|
||||
function isPointInsidePolygon( inPt, inPolygon ) {
|
||||
|
||||
var polyLen = inPolygon.length;
|
||||
|
||||
// inPt on polygon contour => immediate success or
|
||||
// toggling of inside/outside at every single! intersection point of an edge
|
||||
// with the horizontal line through inPt, left of inPt
|
||||
// not counting lowerY endpoints of edges and whole edges on that line
|
||||
var inside = false;
|
||||
for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
|
||||
|
||||
var edgeLowPt = inPolygon[ p ];
|
||||
var edgeHighPt = inPolygon[ q ];
|
||||
|
||||
var edgeDx = edgeHighPt.x - edgeLowPt.x;
|
||||
var edgeDy = edgeHighPt.y - edgeLowPt.y;
|
||||
|
||||
if ( Math.abs( edgeDy ) > Number.EPSILON ) {
|
||||
|
||||
// not parallel
|
||||
if ( edgeDy < 0 ) {
|
||||
|
||||
edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
|
||||
edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
|
||||
|
||||
}
|
||||
if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue;
|
||||
|
||||
if ( inPt.y === edgeLowPt.y ) {
|
||||
|
||||
if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ?
|
||||
// continue; // no intersection or edgeLowPt => doesn't count !!!
|
||||
|
||||
} else {
|
||||
|
||||
var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );
|
||||
if ( perpEdge === 0 ) return true; // inPt is on contour ?
|
||||
if ( perpEdge < 0 ) continue;
|
||||
inside = ! inside; // true intersection left of inPt
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// parallel or collinear
|
||||
if ( inPt.y !== edgeLowPt.y ) continue; // parallel
|
||||
// edge lies on the same horizontal line as inPt
|
||||
if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
|
||||
( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour !
|
||||
// continue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return inside;
|
||||
|
||||
}
|
||||
|
||||
var isClockWise = ShapeUtils.isClockWise;
|
||||
|
||||
var subPaths = this.subPaths;
|
||||
if ( subPaths.length === 0 ) return [];
|
||||
|
||||
if ( noHoles === true ) return toShapesNoHoles( subPaths );
|
||||
|
||||
|
||||
var solid, tmpPath, tmpShape, shapes = [];
|
||||
|
||||
if ( subPaths.length === 1 ) {
|
||||
|
||||
tmpPath = subPaths[ 0 ];
|
||||
tmpShape = new Shape();
|
||||
tmpShape.curves = tmpPath.curves;
|
||||
shapes.push( tmpShape );
|
||||
return shapes;
|
||||
|
||||
}
|
||||
|
||||
var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
|
||||
holesFirst = isCCW ? ! holesFirst : holesFirst;
|
||||
|
||||
// console.log("Holes first", holesFirst);
|
||||
|
||||
var betterShapeHoles = [];
|
||||
var newShapes = [];
|
||||
var newShapeHoles = [];
|
||||
var mainIdx = 0;
|
||||
var tmpPoints;
|
||||
|
||||
newShapes[ mainIdx ] = undefined;
|
||||
newShapeHoles[ mainIdx ] = [];
|
||||
|
||||
for ( var i = 0, l = subPaths.length; i < l; i ++ ) {
|
||||
|
||||
tmpPath = subPaths[ i ];
|
||||
tmpPoints = tmpPath.getPoints();
|
||||
solid = isClockWise( tmpPoints );
|
||||
solid = isCCW ? ! solid : solid;
|
||||
|
||||
if ( solid ) {
|
||||
|
||||
if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++;
|
||||
|
||||
newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };
|
||||
newShapes[ mainIdx ].s.curves = tmpPath.curves;
|
||||
|
||||
if ( holesFirst ) mainIdx ++;
|
||||
newShapeHoles[ mainIdx ] = [];
|
||||
|
||||
//console.log('cw', i);
|
||||
|
||||
} else {
|
||||
|
||||
newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
|
||||
|
||||
//console.log('ccw', i);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// only Holes? -> probably all Shapes with wrong orientation
|
||||
if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths );
|
||||
|
||||
|
||||
if ( newShapes.length > 1 ) {
|
||||
|
||||
var ambiguous = false;
|
||||
var toChange = [];
|
||||
|
||||
for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
|
||||
|
||||
betterShapeHoles[ sIdx ] = [];
|
||||
|
||||
}
|
||||
|
||||
for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
|
||||
|
||||
var sho = newShapeHoles[ sIdx ];
|
||||
|
||||
for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) {
|
||||
|
||||
var ho = sho[ hIdx ];
|
||||
var hole_unassigned = true;
|
||||
|
||||
for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
|
||||
|
||||
if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
|
||||
|
||||
if ( sIdx !== s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
|
||||
if ( hole_unassigned ) {
|
||||
|
||||
hole_unassigned = false;
|
||||
betterShapeHoles[ s2Idx ].push( ho );
|
||||
|
||||
} else {
|
||||
|
||||
ambiguous = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if ( hole_unassigned ) {
|
||||
|
||||
betterShapeHoles[ sIdx ].push( ho );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// console.log("ambiguous: ", ambiguous);
|
||||
if ( toChange.length > 0 ) {
|
||||
|
||||
// console.log("to change: ", toChange);
|
||||
if ( ! ambiguous ) newShapeHoles = betterShapeHoles;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var tmpHoles;
|
||||
|
||||
for ( var i = 0, il = newShapes.length; i < il; i ++ ) {
|
||||
|
||||
tmpShape = newShapes[ i ].s;
|
||||
shapes.push( tmpShape );
|
||||
tmpHoles = newShapeHoles[ i ];
|
||||
|
||||
for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
|
||||
|
||||
tmpShape.holes.push( tmpHoles[ j ].h );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//console.log("shape", shapes);
|
||||
|
||||
return shapes;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { ShapePath };
|
18
lib/extras/curves/ArcCurve.js
Normal file
18
lib/extras/curves/ArcCurve.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { EllipseCurve } from './EllipseCurve.js';
|
||||
|
||||
|
||||
function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
|
||||
|
||||
EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
|
||||
|
||||
this.type = 'ArcCurve';
|
||||
|
||||
}
|
||||
|
||||
ArcCurve.prototype = Object.create( EllipseCurve.prototype );
|
||||
ArcCurve.prototype.constructor = ArcCurve;
|
||||
|
||||
ArcCurve.prototype.isArcCurve = true;
|
||||
|
||||
|
||||
export { ArcCurve };
|
255
lib/extras/curves/CatmullRomCurve3.js
Normal file
255
lib/extras/curves/CatmullRomCurve3.js
Normal file
@ -0,0 +1,255 @@
|
||||
import { Vector3 } from '../../math/Vector3.js';
|
||||
import { Curve } from '../core/Curve.js';
|
||||
|
||||
/**
|
||||
* @author zz85 https://github.com/zz85
|
||||
*
|
||||
* Centripetal CatmullRom Curve - which is useful for avoiding
|
||||
* cusps and self-intersections in non-uniform catmull rom curves.
|
||||
* http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
|
||||
*
|
||||
* curve.type accepts centripetal(default), chordal and catmullrom
|
||||
* curve.tension is used for catmullrom which defaults to 0.5
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Based on an optimized c++ solution in
|
||||
- http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
|
||||
- http://ideone.com/NoEbVM
|
||||
|
||||
This CubicPoly class could be used for reusing some variables and calculations,
|
||||
but for three.js curve use, it could be possible inlined and flatten into a single function call
|
||||
which can be placed in CurveUtils.
|
||||
*/
|
||||
|
||||
function CubicPoly() {
|
||||
|
||||
var c0 = 0, c1 = 0, c2 = 0, c3 = 0;
|
||||
|
||||
/*
|
||||
* Compute coefficients for a cubic polynomial
|
||||
* p(s) = c0 + c1*s + c2*s^2 + c3*s^3
|
||||
* such that
|
||||
* p(0) = x0, p(1) = x1
|
||||
* and
|
||||
* p'(0) = t0, p'(1) = t1.
|
||||
*/
|
||||
function init( x0, x1, t0, t1 ) {
|
||||
|
||||
c0 = x0;
|
||||
c1 = t0;
|
||||
c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
|
||||
c3 = 2 * x0 - 2 * x1 + t0 + t1;
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
initCatmullRom: function ( x0, x1, x2, x3, tension ) {
|
||||
|
||||
init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
|
||||
|
||||
},
|
||||
|
||||
initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
|
||||
|
||||
// compute tangents when parameterized in [t1,t2]
|
||||
var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
|
||||
var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;
|
||||
|
||||
// rescale tangents for parametrization in [0,1]
|
||||
t1 *= dt1;
|
||||
t2 *= dt1;
|
||||
|
||||
init( x1, x2, t1, t2 );
|
||||
|
||||
},
|
||||
|
||||
calc: function ( t ) {
|
||||
|
||||
var t2 = t * t;
|
||||
var t3 = t2 * t;
|
||||
return c0 + c1 * t + c2 * t2 + c3 * t3;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var tmp = new Vector3();
|
||||
var px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
|
||||
|
||||
function CatmullRomCurve3( points, closed, curveType, tension ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'CatmullRomCurve3';
|
||||
|
||||
this.points = points || [];
|
||||
this.closed = closed || false;
|
||||
this.curveType = curveType || 'centripetal';
|
||||
this.tension = tension || 0.5;
|
||||
|
||||
}
|
||||
|
||||
CatmullRomCurve3.prototype = Object.create( Curve.prototype );
|
||||
CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;
|
||||
|
||||
CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
|
||||
|
||||
CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector3();
|
||||
|
||||
var points = this.points;
|
||||
var l = points.length;
|
||||
|
||||
var p = ( l - ( this.closed ? 0 : 1 ) ) * t;
|
||||
var intPoint = Math.floor( p );
|
||||
var weight = p - intPoint;
|
||||
|
||||
if ( this.closed ) {
|
||||
|
||||
intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
|
||||
|
||||
} else if ( weight === 0 && intPoint === l - 1 ) {
|
||||
|
||||
intPoint = l - 2;
|
||||
weight = 1;
|
||||
|
||||
}
|
||||
|
||||
var p0, p1, p2, p3; // 4 points
|
||||
|
||||
if ( this.closed || intPoint > 0 ) {
|
||||
|
||||
p0 = points[ ( intPoint - 1 ) % l ];
|
||||
|
||||
} else {
|
||||
|
||||
// extrapolate first point
|
||||
tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
|
||||
p0 = tmp;
|
||||
|
||||
}
|
||||
|
||||
p1 = points[ intPoint % l ];
|
||||
p2 = points[ ( intPoint + 1 ) % l ];
|
||||
|
||||
if ( this.closed || intPoint + 2 < l ) {
|
||||
|
||||
p3 = points[ ( intPoint + 2 ) % l ];
|
||||
|
||||
} else {
|
||||
|
||||
// extrapolate last point
|
||||
tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
|
||||
p3 = tmp;
|
||||
|
||||
}
|
||||
|
||||
if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
|
||||
|
||||
// init Centripetal / Chordal Catmull-Rom
|
||||
var pow = this.curveType === 'chordal' ? 0.5 : 0.25;
|
||||
var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );
|
||||
var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );
|
||||
var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );
|
||||
|
||||
// safety check for repeated points
|
||||
if ( dt1 < 1e-4 ) dt1 = 1.0;
|
||||
if ( dt0 < 1e-4 ) dt0 = dt1;
|
||||
if ( dt2 < 1e-4 ) dt2 = dt1;
|
||||
|
||||
px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
|
||||
py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
|
||||
pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );
|
||||
|
||||
} else if ( this.curveType === 'catmullrom' ) {
|
||||
|
||||
px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );
|
||||
py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );
|
||||
pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );
|
||||
|
||||
}
|
||||
|
||||
point.set(
|
||||
px.calc( weight ),
|
||||
py.calc( weight ),
|
||||
pz.calc( weight )
|
||||
);
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
CatmullRomCurve3.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.points = [];
|
||||
|
||||
for ( var i = 0, l = source.points.length; i < l; i ++ ) {
|
||||
|
||||
var point = source.points[ i ];
|
||||
|
||||
this.points.push( point.clone() );
|
||||
|
||||
}
|
||||
|
||||
this.closed = source.closed;
|
||||
this.curveType = source.curveType;
|
||||
this.tension = source.tension;
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
CatmullRomCurve3.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.points = [];
|
||||
|
||||
for ( var i = 0, l = this.points.length; i < l; i ++ ) {
|
||||
|
||||
var point = this.points[ i ];
|
||||
data.points.push( point.toArray() );
|
||||
|
||||
}
|
||||
|
||||
data.closed = this.closed;
|
||||
data.curveType = this.curveType;
|
||||
data.tension = this.tension;
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
CatmullRomCurve3.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.points = [];
|
||||
|
||||
for ( var i = 0, l = json.points.length; i < l; i ++ ) {
|
||||
|
||||
var point = json.points[ i ];
|
||||
this.points.push( new Vector3().fromArray( point ) );
|
||||
|
||||
}
|
||||
|
||||
this.closed = json.closed;
|
||||
this.curveType = json.curveType;
|
||||
this.tension = json.tension;
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { CatmullRomCurve3 };
|
79
lib/extras/curves/CubicBezierCurve.js
Normal file
79
lib/extras/curves/CubicBezierCurve.js
Normal file
@ -0,0 +1,79 @@
|
||||
import { Curve } from '../core/Curve.js';
|
||||
import { CubicBezier } from '../core/Interpolations.js';
|
||||
import { Vector2 } from '../../math/Vector2.js';
|
||||
|
||||
|
||||
function CubicBezierCurve( v0, v1, v2, v3 ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'CubicBezierCurve';
|
||||
|
||||
this.v0 = v0 || new Vector2();
|
||||
this.v1 = v1 || new Vector2();
|
||||
this.v2 = v2 || new Vector2();
|
||||
this.v3 = v3 || new Vector2();
|
||||
|
||||
}
|
||||
|
||||
CubicBezierCurve.prototype = Object.create( Curve.prototype );
|
||||
CubicBezierCurve.prototype.constructor = CubicBezierCurve;
|
||||
|
||||
CubicBezierCurve.prototype.isCubicBezierCurve = true;
|
||||
|
||||
CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector2();
|
||||
|
||||
var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
|
||||
|
||||
point.set(
|
||||
CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
|
||||
CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
|
||||
);
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
CubicBezierCurve.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.v0.copy( source.v0 );
|
||||
this.v1.copy( source.v1 );
|
||||
this.v2.copy( source.v2 );
|
||||
this.v3.copy( source.v3 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
CubicBezierCurve.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.v0 = this.v0.toArray();
|
||||
data.v1 = this.v1.toArray();
|
||||
data.v2 = this.v2.toArray();
|
||||
data.v3 = this.v3.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
CubicBezierCurve.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.v0.fromArray( json.v0 );
|
||||
this.v1.fromArray( json.v1 );
|
||||
this.v2.fromArray( json.v2 );
|
||||
this.v3.fromArray( json.v3 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { CubicBezierCurve };
|
80
lib/extras/curves/CubicBezierCurve3.js
Normal file
80
lib/extras/curves/CubicBezierCurve3.js
Normal file
@ -0,0 +1,80 @@
|
||||
import { Curve } from '../core/Curve.js';
|
||||
import { CubicBezier } from '../core/Interpolations.js';
|
||||
import { Vector3 } from '../../math/Vector3.js';
|
||||
|
||||
|
||||
function CubicBezierCurve3( v0, v1, v2, v3 ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'CubicBezierCurve3';
|
||||
|
||||
this.v0 = v0 || new Vector3();
|
||||
this.v1 = v1 || new Vector3();
|
||||
this.v2 = v2 || new Vector3();
|
||||
this.v3 = v3 || new Vector3();
|
||||
|
||||
}
|
||||
|
||||
CubicBezierCurve3.prototype = Object.create( Curve.prototype );
|
||||
CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;
|
||||
|
||||
CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
|
||||
|
||||
CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector3();
|
||||
|
||||
var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
|
||||
|
||||
point.set(
|
||||
CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
|
||||
CubicBezier( t, v0.y, v1.y, v2.y, v3.y ),
|
||||
CubicBezier( t, v0.z, v1.z, v2.z, v3.z )
|
||||
);
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
CubicBezierCurve3.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.v0.copy( source.v0 );
|
||||
this.v1.copy( source.v1 );
|
||||
this.v2.copy( source.v2 );
|
||||
this.v3.copy( source.v3 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
CubicBezierCurve3.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.v0 = this.v0.toArray();
|
||||
data.v1 = this.v1.toArray();
|
||||
data.v2 = this.v2.toArray();
|
||||
data.v3 = this.v3.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
CubicBezierCurve3.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.v0.fromArray( json.v0 );
|
||||
this.v1.fromArray( json.v1 );
|
||||
this.v2.fromArray( json.v2 );
|
||||
this.v3.fromArray( json.v3 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { CubicBezierCurve3 };
|
10
lib/extras/curves/Curves.js
Normal file
10
lib/extras/curves/Curves.js
Normal file
@ -0,0 +1,10 @@
|
||||
export { ArcCurve } from './ArcCurve.js';
|
||||
export { CatmullRomCurve3 } from './CatmullRomCurve3.js';
|
||||
export { CubicBezierCurve } from './CubicBezierCurve.js';
|
||||
export { CubicBezierCurve3 } from './CubicBezierCurve3.js';
|
||||
export { EllipseCurve } from './EllipseCurve.js';
|
||||
export { LineCurve } from './LineCurve.js';
|
||||
export { LineCurve3 } from './LineCurve3.js';
|
||||
export { QuadraticBezierCurve } from './QuadraticBezierCurve.js';
|
||||
export { QuadraticBezierCurve3 } from './QuadraticBezierCurve3.js';
|
||||
export { SplineCurve } from './SplineCurve.js';
|
158
lib/extras/curves/EllipseCurve.js
Normal file
158
lib/extras/curves/EllipseCurve.js
Normal file
@ -0,0 +1,158 @@
|
||||
import { Curve } from '../core/Curve.js';
|
||||
import { Vector2 } from '../../math/Vector2.js';
|
||||
|
||||
|
||||
function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'EllipseCurve';
|
||||
|
||||
this.aX = aX || 0;
|
||||
this.aY = aY || 0;
|
||||
|
||||
this.xRadius = xRadius || 1;
|
||||
this.yRadius = yRadius || 1;
|
||||
|
||||
this.aStartAngle = aStartAngle || 0;
|
||||
this.aEndAngle = aEndAngle || 2 * Math.PI;
|
||||
|
||||
this.aClockwise = aClockwise || false;
|
||||
|
||||
this.aRotation = aRotation || 0;
|
||||
|
||||
}
|
||||
|
||||
EllipseCurve.prototype = Object.create( Curve.prototype );
|
||||
EllipseCurve.prototype.constructor = EllipseCurve;
|
||||
|
||||
EllipseCurve.prototype.isEllipseCurve = true;
|
||||
|
||||
EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector2();
|
||||
|
||||
var twoPi = Math.PI * 2;
|
||||
var deltaAngle = this.aEndAngle - this.aStartAngle;
|
||||
var samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
|
||||
|
||||
// ensures that deltaAngle is 0 .. 2 PI
|
||||
while ( deltaAngle < 0 ) deltaAngle += twoPi;
|
||||
while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
|
||||
|
||||
if ( deltaAngle < Number.EPSILON ) {
|
||||
|
||||
if ( samePoints ) {
|
||||
|
||||
deltaAngle = 0;
|
||||
|
||||
} else {
|
||||
|
||||
deltaAngle = twoPi;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( this.aClockwise === true && ! samePoints ) {
|
||||
|
||||
if ( deltaAngle === twoPi ) {
|
||||
|
||||
deltaAngle = - twoPi;
|
||||
|
||||
} else {
|
||||
|
||||
deltaAngle = deltaAngle - twoPi;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var angle = this.aStartAngle + t * deltaAngle;
|
||||
var x = this.aX + this.xRadius * Math.cos( angle );
|
||||
var y = this.aY + this.yRadius * Math.sin( angle );
|
||||
|
||||
if ( this.aRotation !== 0 ) {
|
||||
|
||||
var cos = Math.cos( this.aRotation );
|
||||
var sin = Math.sin( this.aRotation );
|
||||
|
||||
var tx = x - this.aX;
|
||||
var ty = y - this.aY;
|
||||
|
||||
// Rotate the point about the center of the ellipse.
|
||||
x = tx * cos - ty * sin + this.aX;
|
||||
y = tx * sin + ty * cos + this.aY;
|
||||
|
||||
}
|
||||
|
||||
return point.set( x, y );
|
||||
|
||||
};
|
||||
|
||||
EllipseCurve.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.aX = source.aX;
|
||||
this.aY = source.aY;
|
||||
|
||||
this.xRadius = source.xRadius;
|
||||
this.yRadius = source.yRadius;
|
||||
|
||||
this.aStartAngle = source.aStartAngle;
|
||||
this.aEndAngle = source.aEndAngle;
|
||||
|
||||
this.aClockwise = source.aClockwise;
|
||||
|
||||
this.aRotation = source.aRotation;
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
EllipseCurve.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.aX = this.aX;
|
||||
data.aY = this.aY;
|
||||
|
||||
data.xRadius = this.xRadius;
|
||||
data.yRadius = this.yRadius;
|
||||
|
||||
data.aStartAngle = this.aStartAngle;
|
||||
data.aEndAngle = this.aEndAngle;
|
||||
|
||||
data.aClockwise = this.aClockwise;
|
||||
|
||||
data.aRotation = this.aRotation;
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
EllipseCurve.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.aX = json.aX;
|
||||
this.aY = json.aY;
|
||||
|
||||
this.xRadius = json.xRadius;
|
||||
this.yRadius = json.yRadius;
|
||||
|
||||
this.aStartAngle = json.aStartAngle;
|
||||
this.aEndAngle = json.aEndAngle;
|
||||
|
||||
this.aClockwise = json.aClockwise;
|
||||
|
||||
this.aRotation = json.aRotation;
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { EllipseCurve };
|
90
lib/extras/curves/LineCurve.js
Normal file
90
lib/extras/curves/LineCurve.js
Normal file
@ -0,0 +1,90 @@
|
||||
import { Vector2 } from '../../math/Vector2.js';
|
||||
import { Curve } from '../core/Curve.js';
|
||||
|
||||
|
||||
function LineCurve( v1, v2 ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'LineCurve';
|
||||
|
||||
this.v1 = v1 || new Vector2();
|
||||
this.v2 = v2 || new Vector2();
|
||||
|
||||
}
|
||||
|
||||
LineCurve.prototype = Object.create( Curve.prototype );
|
||||
LineCurve.prototype.constructor = LineCurve;
|
||||
|
||||
LineCurve.prototype.isLineCurve = true;
|
||||
|
||||
LineCurve.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector2();
|
||||
|
||||
if ( t === 1 ) {
|
||||
|
||||
point.copy( this.v2 );
|
||||
|
||||
} else {
|
||||
|
||||
point.copy( this.v2 ).sub( this.v1 );
|
||||
point.multiplyScalar( t ).add( this.v1 );
|
||||
|
||||
}
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
// Line curve is linear, so we can overwrite default getPointAt
|
||||
|
||||
LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {
|
||||
|
||||
return this.getPoint( u, optionalTarget );
|
||||
|
||||
};
|
||||
|
||||
LineCurve.prototype.getTangent = function ( /* t */ ) {
|
||||
|
||||
var tangent = this.v2.clone().sub( this.v1 );
|
||||
|
||||
return tangent.normalize();
|
||||
|
||||
};
|
||||
|
||||
LineCurve.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.v1.copy( source.v1 );
|
||||
this.v2.copy( source.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
LineCurve.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.v1 = this.v1.toArray();
|
||||
data.v2 = this.v2.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
LineCurve.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.v1.fromArray( json.v1 );
|
||||
this.v2.fromArray( json.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { LineCurve };
|
82
lib/extras/curves/LineCurve3.js
Normal file
82
lib/extras/curves/LineCurve3.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { Vector3 } from '../../math/Vector3.js';
|
||||
import { Curve } from '../core/Curve.js';
|
||||
|
||||
|
||||
function LineCurve3( v1, v2 ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'LineCurve3';
|
||||
|
||||
this.v1 = v1 || new Vector3();
|
||||
this.v2 = v2 || new Vector3();
|
||||
|
||||
}
|
||||
|
||||
LineCurve3.prototype = Object.create( Curve.prototype );
|
||||
LineCurve3.prototype.constructor = LineCurve3;
|
||||
|
||||
LineCurve3.prototype.isLineCurve3 = true;
|
||||
|
||||
LineCurve3.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector3();
|
||||
|
||||
if ( t === 1 ) {
|
||||
|
||||
point.copy( this.v2 );
|
||||
|
||||
} else {
|
||||
|
||||
point.copy( this.v2 ).sub( this.v1 );
|
||||
point.multiplyScalar( t ).add( this.v1 );
|
||||
|
||||
}
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
// Line curve is linear, so we can overwrite default getPointAt
|
||||
|
||||
LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {
|
||||
|
||||
return this.getPoint( u, optionalTarget );
|
||||
|
||||
};
|
||||
|
||||
LineCurve3.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.v1.copy( source.v1 );
|
||||
this.v2.copy( source.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
LineCurve3.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.v1 = this.v1.toArray();
|
||||
data.v2 = this.v2.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
LineCurve3.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.v1.fromArray( json.v1 );
|
||||
this.v2.fromArray( json.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { LineCurve3 };
|
75
lib/extras/curves/QuadraticBezierCurve.js
Normal file
75
lib/extras/curves/QuadraticBezierCurve.js
Normal file
@ -0,0 +1,75 @@
|
||||
import { Curve } from '../core/Curve.js';
|
||||
import { QuadraticBezier } from '../core/Interpolations.js';
|
||||
import { Vector2 } from '../../math/Vector2.js';
|
||||
|
||||
|
||||
function QuadraticBezierCurve( v0, v1, v2 ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'QuadraticBezierCurve';
|
||||
|
||||
this.v0 = v0 || new Vector2();
|
||||
this.v1 = v1 || new Vector2();
|
||||
this.v2 = v2 || new Vector2();
|
||||
|
||||
}
|
||||
|
||||
QuadraticBezierCurve.prototype = Object.create( Curve.prototype );
|
||||
QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;
|
||||
|
||||
QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
|
||||
|
||||
QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector2();
|
||||
|
||||
var v0 = this.v0, v1 = this.v1, v2 = this.v2;
|
||||
|
||||
point.set(
|
||||
QuadraticBezier( t, v0.x, v1.x, v2.x ),
|
||||
QuadraticBezier( t, v0.y, v1.y, v2.y )
|
||||
);
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
QuadraticBezierCurve.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.v0.copy( source.v0 );
|
||||
this.v1.copy( source.v1 );
|
||||
this.v2.copy( source.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
QuadraticBezierCurve.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.v0 = this.v0.toArray();
|
||||
data.v1 = this.v1.toArray();
|
||||
data.v2 = this.v2.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
QuadraticBezierCurve.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.v0.fromArray( json.v0 );
|
||||
this.v1.fromArray( json.v1 );
|
||||
this.v2.fromArray( json.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { QuadraticBezierCurve };
|
76
lib/extras/curves/QuadraticBezierCurve3.js
Normal file
76
lib/extras/curves/QuadraticBezierCurve3.js
Normal file
@ -0,0 +1,76 @@
|
||||
import { Curve } from '../core/Curve.js';
|
||||
import { QuadraticBezier } from '../core/Interpolations.js';
|
||||
import { Vector3 } from '../../math/Vector3.js';
|
||||
|
||||
|
||||
function QuadraticBezierCurve3( v0, v1, v2 ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'QuadraticBezierCurve3';
|
||||
|
||||
this.v0 = v0 || new Vector3();
|
||||
this.v1 = v1 || new Vector3();
|
||||
this.v2 = v2 || new Vector3();
|
||||
|
||||
}
|
||||
|
||||
QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );
|
||||
QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;
|
||||
|
||||
QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
|
||||
|
||||
QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector3();
|
||||
|
||||
var v0 = this.v0, v1 = this.v1, v2 = this.v2;
|
||||
|
||||
point.set(
|
||||
QuadraticBezier( t, v0.x, v1.x, v2.x ),
|
||||
QuadraticBezier( t, v0.y, v1.y, v2.y ),
|
||||
QuadraticBezier( t, v0.z, v1.z, v2.z )
|
||||
);
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
QuadraticBezierCurve3.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.v0.copy( source.v0 );
|
||||
this.v1.copy( source.v1 );
|
||||
this.v2.copy( source.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
QuadraticBezierCurve3.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.v0 = this.v0.toArray();
|
||||
data.v1 = this.v1.toArray();
|
||||
data.v2 = this.v2.toArray();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
QuadraticBezierCurve3.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.v0.fromArray( json.v0 );
|
||||
this.v1.fromArray( json.v1 );
|
||||
this.v2.fromArray( json.v2 );
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { QuadraticBezierCurve3 };
|
98
lib/extras/curves/SplineCurve.js
Normal file
98
lib/extras/curves/SplineCurve.js
Normal file
@ -0,0 +1,98 @@
|
||||
import { Curve } from '../core/Curve.js';
|
||||
import { CatmullRom } from '../core/Interpolations.js';
|
||||
import { Vector2 } from '../../math/Vector2.js';
|
||||
|
||||
|
||||
function SplineCurve( points /* array of Vector2 */ ) {
|
||||
|
||||
Curve.call( this );
|
||||
|
||||
this.type = 'SplineCurve';
|
||||
|
||||
this.points = points || [];
|
||||
|
||||
}
|
||||
|
||||
SplineCurve.prototype = Object.create( Curve.prototype );
|
||||
SplineCurve.prototype.constructor = SplineCurve;
|
||||
|
||||
SplineCurve.prototype.isSplineCurve = true;
|
||||
|
||||
SplineCurve.prototype.getPoint = function ( t, optionalTarget ) {
|
||||
|
||||
var point = optionalTarget || new Vector2();
|
||||
|
||||
var points = this.points;
|
||||
var p = ( points.length - 1 ) * t;
|
||||
|
||||
var intPoint = Math.floor( p );
|
||||
var weight = p - intPoint;
|
||||
|
||||
var p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ];
|
||||
var p1 = points[ intPoint ];
|
||||
var p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ];
|
||||
var p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ];
|
||||
|
||||
point.set(
|
||||
CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
|
||||
CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
|
||||
);
|
||||
|
||||
return point;
|
||||
|
||||
};
|
||||
|
||||
SplineCurve.prototype.copy = function ( source ) {
|
||||
|
||||
Curve.prototype.copy.call( this, source );
|
||||
|
||||
this.points = [];
|
||||
|
||||
for ( var i = 0, l = source.points.length; i < l; i ++ ) {
|
||||
|
||||
var point = source.points[ i ];
|
||||
|
||||
this.points.push( point.clone() );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
SplineCurve.prototype.toJSON = function () {
|
||||
|
||||
var data = Curve.prototype.toJSON.call( this );
|
||||
|
||||
data.points = [];
|
||||
|
||||
for ( var i = 0, l = this.points.length; i < l; i ++ ) {
|
||||
|
||||
var point = this.points[ i ];
|
||||
data.points.push( point.toArray() );
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
SplineCurve.prototype.fromJSON = function ( json ) {
|
||||
|
||||
Curve.prototype.fromJSON.call( this, json );
|
||||
|
||||
this.points = [];
|
||||
|
||||
for ( var i = 0, l = json.points.length; i < l; i ++ ) {
|
||||
|
||||
var point = json.points[ i ];
|
||||
this.points.push( new Vector2().fromArray( point ) );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { SplineCurve };
|
22
lib/extras/objects/ImmediateRenderObject.js
Normal file
22
lib/extras/objects/ImmediateRenderObject.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { Object3D } from '../../core/Object3D.js';
|
||||
|
||||
/**
|
||||
* @author alteredq / http://alteredqualia.com/
|
||||
*/
|
||||
|
||||
function ImmediateRenderObject( material ) {
|
||||
|
||||
Object3D.call( this );
|
||||
|
||||
this.material = material;
|
||||
this.render = function ( /* renderCallback */ ) {};
|
||||
|
||||
}
|
||||
|
||||
ImmediateRenderObject.prototype = Object.create( Object3D.prototype );
|
||||
ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;
|
||||
|
||||
ImmediateRenderObject.prototype.isImmediateRenderObject = true;
|
||||
|
||||
|
||||
export { ImmediateRenderObject };
|
Reference in New Issue
Block a user