ExtrudeGeometry class
@author zz85 / http://www.lab4games.net/zz85/blog
Creates extruded geometry from a path shape.
parameters = {
size: <float>, // size of the text height: <float>, // thickness to extrude text curveSegments: <int>, // number of points on the curves steps: <int> | <List>, // number of points or list of U positions for z-side extrusions / used for subdividing segements of extrude spline too amount: <int>, // Amount
bevelEnabled: <bool>, // turn on bevel bevelThickness: <float>, // how deep into text bevel goes bevelSize: <float>, // how far from text outline is bevel bevelSegments: <int>, // number of bevel layers
extrudePath: <THREE.CurvePath> // 2d/3d spline path to extrude shape orthogonality to frames: <THREE.TubeGeometry.FrenetFrames> // containing arrays of tangents, normals, binormals
bendPath: <THREE.CurvePath> // 2d path for bend the shape around x/y plane
material: <int> // material index for front and back faces extrudeMaterial: <int> // material index for extrusion and beveled faces
}
class ExtrudeGeometry extends Geometry {
List shapes;
var shapebb;
ExtrudeGeometry( this.shapes,
{
amount: 100,
bevelThickness: 6.0,
bevelSize: null,
bevelSegments: 3,
bevelEnabled: true,
curveSegments: 12,
steps: 1, //can assume a number of steps or a List with all U's of steps
bendPath,
extrudePath,
frames,
material,
extrudeMaterial } ) : super() {
if (bevelSize == null) bevelSize = bevelThickness - 2.0;
if (shapes == null) {
shapes = [];
return;
}
shapebb = shapes.last.getBoundingBox();
addShapeList( shapes,
amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );
computeCentroids();
computeFaceNormals();
// can't really use automatic vertex normals
// as then front and back sides get smoothed too
// should do separate smoothing just for sides
//this.computeVertexNormals();
//console.log( "took", ( Date.now() - startTime ) );
}
addShapeList(shapes, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
curveSegments,steps,bendPath,extrudePath,frames,material, extrudeMaterial) {
var sl = shapes.length;
for ( var s = 0; s < sl; s ++ ) {
var shape = shapes[ s ];
addShape( shape, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );
}
}
// addShape Helpers
_scalePt2 ( Vector2 pt, Vector2 vec, num size ) {
if ( vec == null ) print( "die" );
return vec.clone().scale( size ).add( pt );
}
_getBevelVec2( Vector2 pt_i, Vector2 pt_j, Vector2 pt_k ) {
var a = ExtrudeGeometry.__v1,
b = ExtrudeGeometry.__v2,
v_hat = ExtrudeGeometry.__v3,
w_hat = ExtrudeGeometry.__v4,
p = ExtrudeGeometry.__v5,
q = ExtrudeGeometry.__v6,
v, w,
v_dot_w_hat, q_sub_p_dot_w_hat,
s, intersection;
// good reading for line-line intersection
// http://sputsoft.com/blog/2010/03/line-line-intersection.html
// define a as vector j->i
// define b as vectot k->i
a.setValues( pt_i.x - pt_j.x, pt_i.y - pt_j.y );
b.setValues( pt_i.x - pt_k.x, pt_i.y - pt_k.y );
// get unit vectors
v = a.normalize();
w = b.normalize();
// normals from pt i
v_hat.setValues( -v.y, v.x );
w_hat.setValues( w.y, -w.x );
// pts from i
p.setFrom( pt_i ).add( v_hat );
q.setFrom( pt_i ).add( w_hat );
if ( p.x == q.x && p.y == q.y) { // TODO add vector_math ".equals(p, q)"
//console.log("Warning: lines are straight");
return w_hat.clone();
}
// Points from j, k. helps prevents points cross overover most of the time
p.setFrom( pt_j ).add( v_hat );
q.setFrom( pt_k ).add( w_hat );
v_dot_w_hat = v.dot( w_hat );
q_sub_p_dot_w_hat = q.sub( p ).dot( w_hat );
// We should not reach these conditions
if ( v_dot_w_hat == 0 ) {
print( "Either infinite or no solutions!" );
if ( q_sub_p_dot_w_hat == 0 ) {
print( "Its finite solutions." );
} else {
print( "Too bad, no solutions." );
}
}
s = q_sub_p_dot_w_hat / v_dot_w_hat;
if ( s < 0 ) {
// in case of emergecy, revert to algorithm 1.
return _getBevelVec1( pt_i, pt_j, pt_k );
}
intersection = v.scale( s ).add( p );
return intersection.sub( pt_i ).clone(); // Don't normalize!, otherwise sharp corners become ugly
}
static var RAD_TO_DEGREES = 180 / Math.PI;
// Algorithm 2
_getBevelVec( pt_i, pt_j, pt_k ) => _getBevelVec2( pt_i, pt_j, pt_k );
_getBevelVec1( pt_i, pt_j, pt_k ) {
var anglea = Math.atan2( pt_j.y - pt_i.y, pt_j.x - pt_i.x );
var angleb = Math.atan2( pt_k.y - pt_i.y, pt_k.x - pt_i.x );
if ( anglea > angleb ) {
angleb += Math.PI * 2;
}
var anglec = ( anglea + angleb ) / 2;
//console.log('angle1', anglea * RAD_TO_DEGREES,'angle2', angleb * RAD_TO_DEGREES, 'anglec', anglec *RAD_TO_DEGREES);
var x = - Math.cos( anglec );
var y = - Math.sin( anglec );
var vec = new Vector2( x, y ); //.normalize();
return vec;
}
_v( x, y, z ) {
vertices.add( new Vector3( x, y, z ) );
}
// TODO - This is a helper function to reverse a list added by nelsonsilva
List _reverse(List list) {
List reversed = [];
var i = list.length;
while (i > 0) {
reversed.add(list[--i]);
}
return reversed;
}
addShape( Shape shape, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled,
curveSegments, steps, bendPath, extrudePath, TubeGeometry frames, material, extrudeMaterial,
{ExtrudeGeometryWorldUVGenerator UVGenerator }) {
var extrudePts, extrudeByPath = false;
//shapebb = shape.getBoundingBox();
// set UV generator
var uvgen = (UVGenerator!= null) ? UVGenerator : new ExtrudeGeometryWorldUVGenerator();
TubeGeometry splineTube;
Vector3 binormal, normal, position2;
var nSteps = (steps is List)? steps.length : steps;
if ( extrudePath != null ) {
if(steps is List){
List divisions = [0];
divisions.addAll(steps);
extrudePts = extrudePath.getUPoints(divisions);
} else{
extrudePts = extrudePath.getSpacedPoints( steps );
}
extrudeByPath = true;
bevelEnabled = false; // bevels not supported for path extrusion
// SETUP TNB variables
// Reuse TNB from TubeGeomtry for now.
// TODO1 - have a .isClosed in spline?
splineTube = (frames != null) ? frames : new TubeGeometry.FrenetFrames(extrudePath, steps, false);
// console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
binormal = new Vector3.zero();
normal = new Vector3.zero();
position2 = new Vector3.zero();
}
// Safeguards if bevels are not enabled
if ( ! bevelEnabled ) {
bevelSegments = 0;
bevelThickness = 0;
bevelSize = 0;
}
// Variables initalization
List<Vector2> ahole;
var h, hl; // looping of holes
var bevelPoints = [];
var shapesOffset = this.vertices.length;
if ( bendPath != null ) {
shape.addWrapPath( bendPath );
}
var shapePoints = shape.extractPoints();
List vertices = shapePoints["shape"];
List<List<Vector2>> holes = shapePoints["holes"];
var reverse = !ShapeUtils.isClockWise( vertices ) ;
if ( reverse ) {
vertices = _reverse(vertices);
// Maybe we should also check if holes are in the opposite direction, just to be safe ...
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
if ( ShapeUtils.isClockWise( ahole ) ) {
holes[ h ] = _reverse(ahole);
}
}
reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)!
}
var faces = ShapeUtils.triangulateShape ( vertices, holes );
//var faces = THREE.Shape.Utils.triangulate2( vertices, holes );
// Would it be better to move points after triangulation?
// shapePoints = shape.extractAllPointsWithBend( curveSegments, bendPath );
// vertices = shapePoints.shape;
// holes = shapePoints.holes;
//console.log(faces);
////
/// Handle Vertices
////
var contour = vertices; // vertices has all points but contour has only points of circumference
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
vertices = new List.from(vertices);
vertices.addAll( ahole );
}
var b, bs, t, z,
vert, vlen = vertices.length,
face, flen = faces.length,
cont, clen = contour.length;
//------
// Find directions for point movement
//
var contourMovements = new List(contour.length);
num i = 0,
il = contour.length,
j = il - 1,
k = i + 1;
for ( i = 0; i < il; i ++ ) {
if ( j == il ) j = 0;
if ( k == il ) k = 0;
// (j)---(i)---(k)
// console.log('i,j,k', i, j , k)
var pt_i = contour[ i ];
var pt_j = contour[ j ];
var pt_k = contour[ k ];
contourMovements[ i ]= _getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
j ++; k ++;
}
List holesMovements = [],
oneHoleMovements,
verticesMovements = new List.from(contourMovements);
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
oneHoleMovements = new List(ahole.length);
i = 0;
il = ahole.length;
j = il - 1;
k = i + 1;
for ( i = 0; i < il; i++) {
if ( j == il ) j = 0;
if ( k == il ) k = 0;
// (j)---(i)---(k)
oneHoleMovements[ i ]= _getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
j++;
k++;
}
holesMovements.add( oneHoleMovements );
verticesMovements.addAll( oneHoleMovements );
}
// Loop bevelSegments, 1 for the front, 1 for the back
for ( b = 0; b < bevelSegments; b ++ ) {
//for ( b = bevelSegments; b > 0; b -- ) {
t = b / bevelSegments;
z = bevelThickness * ( 1 - t );
//z = bevelThickness * t;
bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved
//bs = bevelSize * t ; // linear
// contract shape
for ( i = 0; i < contour.length; i ++ ) {
vert = _scalePt2( contour[ i ], contourMovements[ i ], bs );
//vert = scalePt( contour[ i ], contourCentroid, bs, false );
_v( vert.x, vert.y, - z );
}
// expand holes
for ( h = 0; h < holes.length; h++ ) {
ahole = holes[ h ];
oneHoleMovements = holesMovements[ h ];
for ( i = 0; i < ahole.length; i++ ) {
vert = _scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
//vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true );
_v( vert.x, vert.y, -z );
}
}
}
bs = bevelSize;
// Back facing vertices
for ( i = 0; i < vlen; i ++ ) {
vert = bevelEnabled ? _scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
if ( !extrudeByPath ) {
_v( vert.x, vert.y, 0.0 );
} else {
// v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
normal.setFrom(splineTube.normals[0]).scale(vert.x);
binormal.setFrom(splineTube.binormals[0]).scale(vert.y);
position2.setFrom(extrudePts[0]).add(normal).add(binormal);
_v(position2.x, position2.y, position2.z);
}
}
// Add stepped vertices...
// Including front facing vertices
var s;
for ( s = 1; s <= nSteps; s ++ ) {
for ( i = 0; i < vlen; i ++ ) {
vert = bevelEnabled ? _scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
if ( !extrudeByPath ) {
_v( vert.x, vert.y, amount / nSteps * s );
} else {
// v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
normal.setFrom(splineTube.normals[s]).scale(vert.x);
binormal.setFrom(splineTube.binormals[s]).scale(vert.y);
position2.setFrom(extrudePts[s]).add(normal).add(binormal);
_v(position2.x, position2.y, position2.z );
}
}
}
// Add bevel segments planes
//for ( b = 1; b <= bevelSegments; b ++ ) {
for ( b = bevelSegments - 1; b >= 0; b -- ) {
t = b / bevelSegments;
z = bevelThickness * ( 1 - t );
//bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) );
bs = bevelSize * Math.sin ( t * Math.PI/2 ) ;
// contract shape
for ( i = 0; i < contour.length; i ++ ) {
vert = _scalePt2( contour[ i ], contourMovements[ i ], bs );
_v( vert.x, vert.y, amount + z );
}
// expand holes
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
oneHoleMovements = holesMovements[ h ];
for ( i = 0; i < ahole.length; i++ ) {
vert = _scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
if ( !extrudeByPath ) {
_v( vert.x, vert.y, amount + z );
} else {
_v( vert.x, vert.y + extrudePts[ nSteps - 1 ].y, extrudePts[ nSteps - 1 ].x + z );
}
}
}
}
////
/// Handle Faces
////
///// Internal functions
f3( a, b, c, isBottom ) {
a += shapesOffset;
b += shapesOffset;
c += shapesOffset;
// normal, color, material
this.faces.add( new Face3( a, b, c, null, null, material ) );
var uvs = isBottom ? uvgen.generateBottomUV( this, shape, null, a, b, c)
: uvgen.generateTopUV( this, shape, null, a, b, c);
this.faceVertexUvs[ 0 ].add(uvs);
}
f4( a, b, c, d, wallContour, stepIndex, stepsLength ) {
a += shapesOffset;
b += shapesOffset;
c += shapesOffset;
d += shapesOffset;
this.faces.add( new Face4( a, b, c, d, null, null, extrudeMaterial ) );
var uvs = uvgen.generateSideWallUV( this, shape, wallContour, null, a, b, c, d, stepIndex, stepsLength);
this.faceVertexUvs[ 0 ].add(uvs);
}
// Top and bottom faces
//buildLidFaces() {
if ( bevelEnabled ) {
var layer = 0 ; // steps + 1
var offset = vlen * layer;
// Bottom faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true );
}
layer = nSteps + bevelSegments * 2;
offset = vlen * layer;
// Top faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false );
}
} else {
// Bottom faces
for ( i = 0; i < flen; i++ ) {
face = faces[ i ];
f3( face[ 2 ], face[ 1 ], face[ 0 ], true );
}
// Top faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 0 ] + vlen * nSteps, face[ 1 ] + vlen * nSteps, face[ 2 ] + vlen * nSteps, false );
}
}
sidewalls( contour, layeroffset ) {
var i, j, k;
i = contour.length;
while ( --i >= 0 ) {
j = i;
k = i - 1;
if ( k < 0 ) k = contour.length - 1;
//console.log('b', i,j, i-1, k,vertices.length);
var s = 0, sl = nSteps + bevelSegments * 2;
for ( s = 0; s < sl; s ++ ) {
var slen1 = vlen * s;
var slen2 = vlen * ( s + 1 );
var a = layeroffset + j + slen1,
b = layeroffset + k + slen1,
c = layeroffset + k + slen2,
d = layeroffset + j + slen2;
f4( a, b, c, d, contour, s, sl );
}
}
}
// Sides faces
//buildSideFaces() {
// Create faces for the z-sides of the shape
var layeroffset = 0;
sidewalls( contour, layeroffset );
layeroffset += contour.length;
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
sidewalls( ahole, layeroffset );
//, true
layeroffset += ahole.length;
}
}
static Vector2 ___v1 = null; static get __v1 => (___v1 == null)? ___v1 = new Vector2.zero() : ___v1;
static Vector2 ___v2 = null; static get __v2 => (___v2 == null)? ___v2 = new Vector2.zero() : ___v2;
static Vector2 ___v3 = null; static get __v3 => (___v3 == null)? ___v3 = new Vector2.zero() : ___v3;
static Vector2 ___v4 = null; static get __v4 => (___v4 == null)? ___v4 = new Vector2.zero() : ___v4;
static Vector2 ___v5 = null; static get __v5 => (___v5 == null)? ___v5 = new Vector2.zero() : ___v5;
static Vector2 ___v6 = null; static get __v6 => (___v6 == null)? ___v6 = new Vector2.zero() : ___v6;
}
Extends
Geometry > ExtrudeGeometry
Subclasses
Static Properties
var RAD_TO_DEGREES #
static var RAD_TO_DEGREES = 180 / Math.PI
Constructors
new ExtrudeGeometry(List shapes, {amount: 100, bevelThickness: 6.0, bevelSize: null, bevelSegments: 3, bevelEnabled: true, curveSegments: 12, steps: 1, bendPath, extrudePath, frames, material, extrudeMaterial}) #
Creates a new Object instance.
Object instances have no meaningful state, and are only useful through their identity. An Object instance is equal to itself only.
ExtrudeGeometry( this.shapes,
{
amount: 100,
bevelThickness: 6.0,
bevelSize: null,
bevelSegments: 3,
bevelEnabled: true,
curveSegments: 12,
steps: 1, //can assume a number of steps or a List with all U's of steps
bendPath,
extrudePath,
frames,
material,
extrudeMaterial } ) : super() {
if (bevelSize == null) bevelSize = bevelThickness - 2.0;
if (shapes == null) {
shapes = [];
return;
}
shapebb = shapes.last.getBoundingBox();
addShapeList( shapes,
amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );
computeCentroids();
computeFaceNormals();
// can't really use automatic vertex normals
// as then front and back sides get smoothed too
// should do separate smoothing just for sides
//this.computeVertexNormals();
//console.log( "took", ( Date.now() - startTime ) );
}
Properties
bool isDynamic #
bool get isDynamic => _dynamic;
set isDynamic(bool value) => _dynamic = value;
var shapebb #
var shapebb
Operators
Methods
dynamic addShape(Shape shape, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled, curveSegments, steps, bendPath, extrudePath, TubeGeometry frames, material, extrudeMaterial, {ExtrudeGeometryWorldUVGenerator UVGenerator}) #
addShape( Shape shape, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled,
curveSegments, steps, bendPath, extrudePath, TubeGeometry frames, material, extrudeMaterial,
{ExtrudeGeometryWorldUVGenerator UVGenerator }) {
var extrudePts, extrudeByPath = false;
//shapebb = shape.getBoundingBox();
// set UV generator
var uvgen = (UVGenerator!= null) ? UVGenerator : new ExtrudeGeometryWorldUVGenerator();
TubeGeometry splineTube;
Vector3 binormal, normal, position2;
var nSteps = (steps is List)? steps.length : steps;
if ( extrudePath != null ) {
if(steps is List){
List divisions = [0];
divisions.addAll(steps);
extrudePts = extrudePath.getUPoints(divisions);
} else{
extrudePts = extrudePath.getSpacedPoints( steps );
}
extrudeByPath = true;
bevelEnabled = false; // bevels not supported for path extrusion
// SETUP TNB variables
// Reuse TNB from TubeGeomtry for now.
// TODO1 - have a .isClosed in spline?
splineTube = (frames != null) ? frames : new TubeGeometry.FrenetFrames(extrudePath, steps, false);
// console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
binormal = new Vector3.zero();
normal = new Vector3.zero();
position2 = new Vector3.zero();
}
// Safeguards if bevels are not enabled
if ( ! bevelEnabled ) {
bevelSegments = 0;
bevelThickness = 0;
bevelSize = 0;
}
// Variables initalization
List<Vector2> ahole;
var h, hl; // looping of holes
var bevelPoints = [];
var shapesOffset = this.vertices.length;
if ( bendPath != null ) {
shape.addWrapPath( bendPath );
}
var shapePoints = shape.extractPoints();
List vertices = shapePoints["shape"];
List<List<Vector2>> holes = shapePoints["holes"];
var reverse = !ShapeUtils.isClockWise( vertices ) ;
if ( reverse ) {
vertices = _reverse(vertices);
// Maybe we should also check if holes are in the opposite direction, just to be safe ...
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
if ( ShapeUtils.isClockWise( ahole ) ) {
holes[ h ] = _reverse(ahole);
}
}
reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)!
}
var faces = ShapeUtils.triangulateShape ( vertices, holes );
//var faces = THREE.Shape.Utils.triangulate2( vertices, holes );
// Would it be better to move points after triangulation?
// shapePoints = shape.extractAllPointsWithBend( curveSegments, bendPath );
// vertices = shapePoints.shape;
// holes = shapePoints.holes;
//console.log(faces);
////
/// Handle Vertices
////
var contour = vertices; // vertices has all points but contour has only points of circumference
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
vertices = new List.from(vertices);
vertices.addAll( ahole );
}
var b, bs, t, z,
vert, vlen = vertices.length,
face, flen = faces.length,
cont, clen = contour.length;
//------
// Find directions for point movement
//
var contourMovements = new List(contour.length);
num i = 0,
il = contour.length,
j = il - 1,
k = i + 1;
for ( i = 0; i < il; i ++ ) {
if ( j == il ) j = 0;
if ( k == il ) k = 0;
// (j)---(i)---(k)
// console.log('i,j,k', i, j , k)
var pt_i = contour[ i ];
var pt_j = contour[ j ];
var pt_k = contour[ k ];
contourMovements[ i ]= _getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
j ++; k ++;
}
List holesMovements = [],
oneHoleMovements,
verticesMovements = new List.from(contourMovements);
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
oneHoleMovements = new List(ahole.length);
i = 0;
il = ahole.length;
j = il - 1;
k = i + 1;
for ( i = 0; i < il; i++) {
if ( j == il ) j = 0;
if ( k == il ) k = 0;
// (j)---(i)---(k)
oneHoleMovements[ i ]= _getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
j++;
k++;
}
holesMovements.add( oneHoleMovements );
verticesMovements.addAll( oneHoleMovements );
}
// Loop bevelSegments, 1 for the front, 1 for the back
for ( b = 0; b < bevelSegments; b ++ ) {
//for ( b = bevelSegments; b > 0; b -- ) {
t = b / bevelSegments;
z = bevelThickness * ( 1 - t );
//z = bevelThickness * t;
bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved
//bs = bevelSize * t ; // linear
// contract shape
for ( i = 0; i < contour.length; i ++ ) {
vert = _scalePt2( contour[ i ], contourMovements[ i ], bs );
//vert = scalePt( contour[ i ], contourCentroid, bs, false );
_v( vert.x, vert.y, - z );
}
// expand holes
for ( h = 0; h < holes.length; h++ ) {
ahole = holes[ h ];
oneHoleMovements = holesMovements[ h ];
for ( i = 0; i < ahole.length; i++ ) {
vert = _scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
//vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true );
_v( vert.x, vert.y, -z );
}
}
}
bs = bevelSize;
// Back facing vertices
for ( i = 0; i < vlen; i ++ ) {
vert = bevelEnabled ? _scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
if ( !extrudeByPath ) {
_v( vert.x, vert.y, 0.0 );
} else {
// v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
normal.setFrom(splineTube.normals[0]).scale(vert.x);
binormal.setFrom(splineTube.binormals[0]).scale(vert.y);
position2.setFrom(extrudePts[0]).add(normal).add(binormal);
_v(position2.x, position2.y, position2.z);
}
}
// Add stepped vertices...
// Including front facing vertices
var s;
for ( s = 1; s <= nSteps; s ++ ) {
for ( i = 0; i < vlen; i ++ ) {
vert = bevelEnabled ? _scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
if ( !extrudeByPath ) {
_v( vert.x, vert.y, amount / nSteps * s );
} else {
// v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
normal.setFrom(splineTube.normals[s]).scale(vert.x);
binormal.setFrom(splineTube.binormals[s]).scale(vert.y);
position2.setFrom(extrudePts[s]).add(normal).add(binormal);
_v(position2.x, position2.y, position2.z );
}
}
}
// Add bevel segments planes
//for ( b = 1; b <= bevelSegments; b ++ ) {
for ( b = bevelSegments - 1; b >= 0; b -- ) {
t = b / bevelSegments;
z = bevelThickness * ( 1 - t );
//bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) );
bs = bevelSize * Math.sin ( t * Math.PI/2 ) ;
// contract shape
for ( i = 0; i < contour.length; i ++ ) {
vert = _scalePt2( contour[ i ], contourMovements[ i ], bs );
_v( vert.x, vert.y, amount + z );
}
// expand holes
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
oneHoleMovements = holesMovements[ h ];
for ( i = 0; i < ahole.length; i++ ) {
vert = _scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
if ( !extrudeByPath ) {
_v( vert.x, vert.y, amount + z );
} else {
_v( vert.x, vert.y + extrudePts[ nSteps - 1 ].y, extrudePts[ nSteps - 1 ].x + z );
}
}
}
}
////
/// Handle Faces
////
///// Internal functions
f3( a, b, c, isBottom ) {
a += shapesOffset;
b += shapesOffset;
c += shapesOffset;
// normal, color, material
this.faces.add( new Face3( a, b, c, null, null, material ) );
var uvs = isBottom ? uvgen.generateBottomUV( this, shape, null, a, b, c)
: uvgen.generateTopUV( this, shape, null, a, b, c);
this.faceVertexUvs[ 0 ].add(uvs);
}
f4( a, b, c, d, wallContour, stepIndex, stepsLength ) {
a += shapesOffset;
b += shapesOffset;
c += shapesOffset;
d += shapesOffset;
this.faces.add( new Face4( a, b, c, d, null, null, extrudeMaterial ) );
var uvs = uvgen.generateSideWallUV( this, shape, wallContour, null, a, b, c, d, stepIndex, stepsLength);
this.faceVertexUvs[ 0 ].add(uvs);
}
// Top and bottom faces
//buildLidFaces() {
if ( bevelEnabled ) {
var layer = 0 ; // steps + 1
var offset = vlen * layer;
// Bottom faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true );
}
layer = nSteps + bevelSegments * 2;
offset = vlen * layer;
// Top faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false );
}
} else {
// Bottom faces
for ( i = 0; i < flen; i++ ) {
face = faces[ i ];
f3( face[ 2 ], face[ 1 ], face[ 0 ], true );
}
// Top faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 0 ] + vlen * nSteps, face[ 1 ] + vlen * nSteps, face[ 2 ] + vlen * nSteps, false );
}
}
sidewalls( contour, layeroffset ) {
var i, j, k;
i = contour.length;
while ( --i >= 0 ) {
j = i;
k = i - 1;
if ( k < 0 ) k = contour.length - 1;
//console.log('b', i,j, i-1, k,vertices.length);
var s = 0, sl = nSteps + bevelSegments * 2;
for ( s = 0; s < sl; s ++ ) {
var slen1 = vlen * s;
var slen2 = vlen * ( s + 1 );
var a = layeroffset + j + slen1,
b = layeroffset + k + slen1,
c = layeroffset + k + slen2,
d = layeroffset + j + slen2;
f4( a, b, c, d, contour, s, sl );
}
}
}
// Sides faces
//buildSideFaces() {
// Create faces for the z-sides of the shape
var layeroffset = 0;
sidewalls( contour, layeroffset );
layeroffset += contour.length;
for ( h = 0; h < holes.length; h ++ ) {
ahole = holes[ h ];
sidewalls( ahole, layeroffset );
//, true
layeroffset += ahole.length;
}
}
dynamic addShapeList(shapes, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled, curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial) #
addShapeList(shapes, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
curveSegments,steps,bendPath,extrudePath,frames,material, extrudeMaterial) {
var sl = shapes.length;
for ( var s = 0; s < sl; s ++ ) {
var shape = shapes[ s ];
addShape( shape, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );
}
}
void applyMatrix(Matrix4 matrix) #
void applyMatrix( Matrix4 matrix ) {
Matrix4 matrixRotation = new Matrix4.identity();
extractRotation( matrixRotation, matrix);
vertices.forEach((vertex) => vertex.applyProjection(matrix));
faces.forEach((face) {
face.normal.applyProjection(matrixRotation);
face.vertexNormals.forEach((normal) => normal.applyProjection(matrixRotation));
face.centroid.applyProjection(matrix);
});
}
void computeBoundingBox() #
void computeBoundingBox() {
if ( boundingBox == null ) {
boundingBox = new BoundingBox( min: new Vector3.zero(), max: new Vector3.zero() );
}
if ( vertices.length > 0 ) {
Vector3 position, firstPosition = vertices[ 0 ];
boundingBox.min.setFrom( firstPosition );
boundingBox.max.setFrom( firstPosition );
Vector3 min = boundingBox.min,
max = boundingBox.max;
num vl = vertices.length;
for ( int v = 1; v < vl; v ++ ) {
position = vertices[ v ];
if ( position.x < min.x ) {
min.x = position.x;
} else if ( position.x > max.x ) {
max.x = position.x;
}
if ( position.y < min.y ) {
min.y = position.y;
} else if ( position.y > max.y ) {
max.y = position.y;
}
if ( position.z < min.z ) {
min.z = position.z;
} else if ( position.z > max.z ) {
max.z = position.z;
}
}
}
}
void computeBoundingSphere() #
void computeBoundingSphere() {
num radiusSq;
var maxRadiusSq = vertices.fold(0, (num curMaxRadiusSq, Vector3 vertex) {
radiusSq = vertex.length2;
return ( radiusSq > curMaxRadiusSq ) ? radiusSq : curMaxRadiusSq;
});
boundingSphere = new BoundingSphere(radius: Math.sqrt(maxRadiusSq) );
}
void computeCentroids() #
void computeCentroids() {
faces.forEach((Face face) {
face.centroid.setValues( 0.0, 0.0, 0.0 );
face.indices.forEach((idx) {
face.centroid.add( vertices[ idx ] );
});
face.centroid /= face.size.toDouble();
});
}
void computeFaceNormals() #
void computeFaceNormals() {
faces.forEach((face) {
var vA = vertices[ face.a ],
vB = vertices[ face.b ],
vC = vertices[ face.c ];
Vector3 cb = vC - vB;
Vector3 ab = vA - vB;
cb = cb.cross( ab );
cb.normalize();
face.normal = cb;
});
}
void computeTangents() #
void computeTangents() {
// based on http://www.terathon.com/code/tangent.html
// tangents go to vertices
var f, fl, face;
num i, il, vertexIndex, test, w;
Vector3 vA, vB, vC;
UV uvA, uvB, uvC;
List uv;
num x1, x2, y1, y2, z1, z2, s1, s2, t1, t2, r;
Vector3 sdir = new Vector3.zero(),
tdir = new Vector3.zero(),
tmp = new Vector3.zero(),
tmp2 = new Vector3.zero(),
n = new Vector3.zero(),
t;
List<Vector3> tan1 = vertices.map((_) => new Vector3.zero()).toList(),
tan2 = vertices.map((_) => new Vector3.zero()).toList();
var handleTriangle = ( context, a, b, c, ua, ub, uc ) {
vA = context.vertices[ a ];
vB = context.vertices[ b ];
vC = context.vertices[ c ];
uvA = uv[ ua ];
uvB = uv[ ub ];
uvC = uv[ uc ];
x1 = vB.x - vA.x;
x2 = vC.x - vA.x;
y1 = vB.y - vA.y;
y2 = vC.y - vA.y;
z1 = vB.z - vA.z;
z2 = vC.z - vA.z;
s1 = uvB.u - uvA.u;
s2 = uvC.u - uvA.u;
t1 = uvB.v - uvA.v;
t2 = uvC.v - uvA.v;
r = 1.0 / ( s1 * t2 - s2 * t1 );
sdir.setValues( ( t2 * x1 - t1 * x2 ) * r,
( t2 * y1 - t1 * y2 ) * r,
( t2 * z1 - t1 * z2 ) * r );
tdir.setValues( ( s1 * x2 - s2 * x1 ) * r,
( s1 * y2 - s2 * y1 ) * r,
( s1 * z2 - s2 * z1 ) * r );
tan1[ a ].add( sdir );
tan1[ b ].add( sdir );
tan1[ c ].add( sdir );
tan2[ a ].add( tdir );
tan2[ b ].add( tdir );
tan2[ c ].add( tdir );
};
fl = this.faces.length;
for ( f = 0; f < fl; f ++ ) {
face = this.faces[ f ];
UV uv = faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents
// TODO - Come up with a way to handle an arbitrary number of vertexes
var triangles = [];
if ( face.size == 3 ) {
triangles.add([0, 1, 2]);
} else if ( face.size == 4 ) {
triangles.add([0, 1, 3]);
triangles.add([1, 2, 3]);
}
triangles.forEach((t) {
handleTriangle( this, face.indices[t[0]], face.indices[t[1]], face.indices[t[2]], t[0], t[1], t[2] );
});
}
faces.forEach((face) {
il = face.vertexNormals.length;
for ( i = 0; i < il; i++ ) {
n.setFrom( face.vertexNormals[ i ] );
vertexIndex = face.indices[i];
t = tan1[ vertexIndex ];
// Gram-Schmidt orthogonalize
tmp.setFrom( t );
tmp.sub( n.scale( n.dot( t ) ) ).normalize();
// Calculate handedness
tmp2 = face.vertexNormals[i].cross(t);
test = tmp2.dot( tan2[ vertexIndex ] );
w = (test < 0.0) ? -1.0 : 1.0;
face.vertexTangents[ i ] = new Vector4( tmp.x, tmp.y, tmp.z, w );
}
});
hasTangents = true;
}
void computeVertexNormals() #
void computeVertexNormals() {
List<Vector3> vertices;
// create internal buffers for reuse when calling this method repeatedly
// (otherwise memory allocation / deallocation every frame is big resource hog)
if ( __tmpVertices == null ) {
__tmpVertices = [];
this.vertices.forEach((_) => __tmpVertices.add(new Vector3.zero()));
vertices = __tmpVertices;
faces.forEach((face) {
face.vertexNormals = new List.generate(face.size, (_) => new Vector3.zero(), growable: false);
});
} else {
vertices = __tmpVertices;
var vl = this.vertices.length;
for ( var v = 0; v < vl; v ++ ) {
vertices[ v ].setValues( 0.0, 0.0, 0.0 );
}
}
faces.forEach((Face face) {
face.indices.forEach((idx) {
vertices[ idx ].add( face.normal );
});
});
vertices.forEach((v) => v.normalize());
faces.forEach((Face face) {
var i = 0;
face.indices.forEach((idx) {
face.vertexNormals[ i++ ].setFrom( vertices[ idx ] );
});
});
}
int mergeVertices() #
int mergeVertices() {
Map verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique)
List<Vector3> unique = [];
List<int> changes = [];
String key;
int precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
num precision = Math.pow( 10, precisionPoints );
int i, il;
var abcd = 'abcd', o, k, j, jl, u;
Vector3 v;
il = this.vertices.length;
for( i = 0; i < il; i++) {
v = this.vertices[i];
key = [ ( v.x * precision ).round().toStringAsFixed(0),
( v.y * precision ).round().toStringAsFixed(0),
( v.z * precision ).round().toStringAsFixed(0) ].join('_' );
if ( verticesMap[ key ] == null ) {
verticesMap[ key ] = i;
unique.add( v );
//TODO: pretty sure this is an acceptable change in syntax here:
//changes[ i ] = unique.length - 1;
changes.add( unique.length - 1);
} else {
//print('Duplicate vertex found. $i could be using ${verticesMap[key]}');
//print('changes len ${changes.length} add at i = $i');
//changes[ i ] = changes[ verticesMap[ key ] ];
changes.add( changes[ verticesMap[ key ] ] );
}
}
// Start to patch face indices
faces.forEach((Face face) {
for (var i = 0; i < face.size; i++) {
face.indices[i] = changes[ face.indices[i] ];
/* TODO
// check dups in (a, b, c, d) and convert to -> face3
var o = [ face.a, face.b, face.c, face.d ];
for ( var k = 3; k > 0; k -- ) {
if ( o.indexOf( face[ abcd[ k ] ] ) != k ) {
// console.log('faces', face.a, face.b, face.c, face.d, 'dup at', k);
o.removeAt( k );
this.faces[ i ] = new THREE.Face3( o[0], o[1], o[2], face.normal, face.color, face.materialIndex );
for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
u = this.faceVertexUvs[ j ][ i ];
if ( u ) u.removeAt( k );
}
this.faces[ i ].vertexColors = face.vertexColors;
break;
}
}*/
}
});
// Use unique set of vertices
var diff = vertices.length - unique.length;
vertices = unique;
return diff;
}