import {Face3} from '../../../../runtime/graphic/core/Face3'; import {Vertex} from '../../../../runtime/graphic/core/Vertex'; import {Vector2} from '../../../../runtime/math/Vector2'; import {Vector3} from '../../../../runtime/math/Vector3'; import {Geometry} from '../../core/Geometry'; import {Path} from '../brep/Path'; /** * @author zz85 / http://www.lab4games.net/zz85/blog * * Creates extruded geometry from a path shape. * * parameters = { * * curveSegments: , // number of points on the curves * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too * amount: , // Depth to extrude the shape * * bevelEnabled: , // turn on bevel * bevelThickness: , // how deep into the original shape bevel goes * bevelSize: , // how far from shape outline is bevel * bevelSegments: , // number of bevel layers * * extrudePath: // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined) * frames: // containing arrays of tangents, normals, binormals * * uvGenerator: // object that provides UV generator functions * * } **/ export type ExtrudeGeometryParameters = { amount?: number//参与倒角的比例 bevelThickness?: number//倒角厚度 bevelSize?: number//倒角大小 bevelSegments?: number//倒角分段数 bevelEnabled?: boolean//是否开启倒角 curveSegments?: number//曲线分段 steps?: number//从曲线获得点数据的密度 extrudePath?: Path//拉伸路径 UVGenerator?: any lidEnabled?: boolean//是否生成上下面 //frames } export class ExtrudeGeometry extends Geometry { static WorldUVGenerator = { generateTopUV: function (geometry, indexA, indexB, indexC) { var positions = geometry.positions; var a = positions[indexA]; var b = positions[indexB]; var c = positions[indexC]; return [ new Vector2(a.x, a.y), new Vector2(b.x, b.y), new Vector2(c.x, c.y) ]; }, generateSideWallUV: function (geometry, indexA, indexB, indexC, indexD) { var positions = geometry.positions; var a = positions[indexA]; var b = positions[indexB]; var c = positions[indexC]; var d = positions[indexD]; if (Math.abs(a.y - b.y) < 0.01) { return [ new Vector2(a.x, 1 - a.z), new Vector2(b.x, 1 - b.z), new Vector2(c.x, 1 - c.z), new Vector2(d.x, 1 - d.z) ]; } else { return [ new Vector2(a.y, 1 - a.z), new Vector2(b.y, 1 - b.z), new Vector2(c.y, 1 - c.z), new Vector2(d.y, 1 - d.z) ]; } } }; type: string; public constructor(shapes, options?: ExtrudeGeometryParameters) { super(); this.type = 'ExtrudeGeometry'; shapes = Array.isArray(shapes) ? shapes : [shapes]; this.addShapeList(shapes, options); this.update(true); this.computeFaceNormals(); } public addShapeList(shapes, options?: ExtrudeGeometryParameters) { var sl = shapes.length; for (var s = 0; s < sl; s++) { var shape = shapes[s]; this.addShape(shape, options); } } public addShape(shape, options: ExtrudeGeometryParameters) { var amount = options.amount !== undefined ? options.amount : 100; var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10 var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8 var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false var lidEnabled = options.lidEnabled !== undefined ? options.lidEnabled : true; // false var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; var steps = options.steps !== undefined ? options.steps : 1; var extrudePath = options.extrudePath; var extrudePts, extrudeByPath = false; // Use default WorldUVGenerator if no UV generators are specified. var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : ExtrudeGeometry.WorldUVGenerator; var splineTube, binormal, normal, position2; if (extrudePath) { 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 = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames(extrudePath, steps, false); // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); binormal = new THREE.Vector3(); normal = new THREE.Vector3(); position2 = new THREE.Vector3(); } // Safeguards if bevels are not enabled if (!bevelEnabled) { bevelSegments = 0; bevelThickness = 0; bevelSize = 0; } // Variables initialization var ahole, h, hl; // looping of holes var scope = this; var shapesOffset = this.vertices.length; var shapePoints = shape.extractPoints(curveSegments); var vertices = shapePoints.shape; var holes = shapePoints.holes; var reverse = !THREE.ShapeUtils.isClockWise(vertices); if (reverse) { vertices = vertices.reverse(); // Maybe we should also check if holes are in the opposite direction, just to be safe ... for (h = 0, hl = holes.length; h < hl; h++) { ahole = holes[h]; if (THREE.ShapeUtils.isClockWise(ahole)) { holes[h] = ahole.reverse(); } } reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)! } var faces = THREE.ShapeUtils.triangulateShape(vertices, holes); /* Vertices */ var contour = vertices; // vertices has all points but contour has only points of circumference for (h = 0, hl = holes.length; h < hl; h++) { ahole = holes[h]; vertices = vertices.concat(ahole); } function scalePt2(pt, vec, size) { if (!vec) console.error("THREE.ExtrudeGeometry: vec does not exist"); return vec.clone().multiplyScalar(size).add(pt); } var b, bs, t, z, vert, vlen = vertices.length, face, flen = faces.length; // Find directions for point movement function getBevelVec(inPt, inPrev, inNext) { // computes for inPt the corresponding point inPt' on a new contour // shifted by 1 unit (length of normalized vector) to the left // if we walk along contour clockwise, this new contour is outside the old one // // inPt' is the intersection of the two lines parallel to the two // adjacent edges of inPt at a distance of 1 unit on the left side. var v_trans_x, v_trans_y, shrink_by = 1; // resulting translation vector for inPt // good reading for geometry algorithms (here: line-line intersection) // http://geomalgorithms.com/a05-_intersect-1.html var v_prev_x = inPt.x - inPrev.x, v_prev_y = inPt.y - inPrev.y; var v_next_x = inNext.x - inPt.x, v_next_y = inNext.y - inPt.y; var v_prev_lensq = (v_prev_x * v_prev_x + v_prev_y * v_prev_y); // check for collinear edges var collinear0 = (v_prev_x * v_next_y - v_prev_y * v_next_x); if (Math.abs(collinear0) > Number.EPSILON) { // not collinear // length of vectors for normalizing var v_prev_len = Math.sqrt(v_prev_lensq); var v_next_len = Math.sqrt(v_next_x * v_next_x + v_next_y * v_next_y); // shift adjacent points by unit vectors to the left var ptPrevShift_x = (inPrev.x - v_prev_y / v_prev_len); var ptPrevShift_y = (inPrev.y + v_prev_x / v_prev_len); var ptNextShift_x = (inNext.x - v_next_y / v_next_len); var ptNextShift_y = (inNext.y + v_next_x / v_next_len); // scaling factor for v_prev to intersection point var sf = ((ptNextShift_x - ptPrevShift_x) * v_next_y - (ptNextShift_y - ptPrevShift_y) * v_next_x) / (v_prev_x * v_next_y - v_prev_y * v_next_x); // vector from inPt to intersection point v_trans_x = (ptPrevShift_x + v_prev_x * sf - inPt.x); v_trans_y = (ptPrevShift_y + v_prev_y * sf - inPt.y); // Don't normalize!, otherwise sharp corners become ugly // but prevent crazy spikes var v_trans_lensq = (v_trans_x * v_trans_x + v_trans_y * v_trans_y); if (v_trans_lensq <= 2) { return new Vector2(v_trans_x, v_trans_y); } else { shrink_by = Math.sqrt(v_trans_lensq / 2); } } else { // handle special case of collinear edges var direction_eq = false; // assumes: opposite if (v_prev_x > Number.EPSILON) { if (v_next_x > Number.EPSILON) { direction_eq = true; } } else { if (v_prev_x < - Number.EPSILON) { if (v_next_x < - Number.EPSILON) { direction_eq = true; } } else { if (Math.sign(v_prev_y) === Math.sign(v_next_y)) { direction_eq = true; } } } if (direction_eq) { // console.log("Warning: lines are a straight sequence"); v_trans_x = - v_prev_y; v_trans_y = v_prev_x; shrink_by = Math.sqrt(v_prev_lensq); } else { // console.log("Warning: lines are a straight spike"); v_trans_x = v_prev_x; v_trans_y = v_prev_y; shrink_by = Math.sqrt(v_prev_lensq / 2); } } return new Vector2(v_trans_x / shrink_by, v_trans_y / shrink_by); } var contourMovements = []; for (var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i++ , j++ , k++) { if (j === il) j = 0; if (k === il) k = 0; // (j)---(i)---(k) // console.log('i,j,k', i, j , k) contourMovements[i] = getBevelVec(contour[i], contour[j], contour[k]); } var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat(); for (h = 0, hl = holes.length; h < hl; h++) { ahole = holes[h]; oneHoleMovements = []; for (i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i++ , j++ , k++) { if (j === il) j = 0; if (k === il) k = 0; // (j)---(i)---(k) oneHoleMovements[i] = getBevelVec(ahole[i], ahole[j], ahole[k]); } holesMovements.push(oneHoleMovements); verticesMovements = verticesMovements.concat(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, il = contour.length; i < il; i++) { vert = scalePt2(contour[i], contourMovements[i], bs); v(vert.x, vert.y, - z); } // expand holes for (h = 0, hl = holes.length; h < hl; h++) { ahole = holes[h]; oneHoleMovements = holesMovements[h]; for (i = 0, il = ahole.length; i < il; i++) { vert = scalePt2(ahole[i], oneHoleMovements[i], bs); 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); } else { // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); normal.copy(splineTube.normals[0]).multiplyScalar(vert.x); binormal.copy(splineTube.binormals[0]).multiplyScalar(vert.y); position2.copy(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 <= steps; 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 / steps * s); } else { // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); normal.copy(splineTube.normals[s]).multiplyScalar(vert.x); binormal.copy(splineTube.binormals[s]).multiplyScalar(vert.y); position2.copy(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, il = contour.length; i < il; i++) { vert = scalePt2(contour[i], contourMovements[i], bs); v(vert.x, vert.y, amount + z); } // expand holes for (h = 0, hl = holes.length; h < hl; h++) { ahole = holes[h]; oneHoleMovements = holesMovements[h]; for (i = 0, il = ahole.length; i < il; i++) { vert = scalePt2(ahole[i], oneHoleMovements[i], bs); if (!extrudeByPath) { v(vert.x, vert.y, amount + z); } else { v(vert.x, vert.y + extrudePts[steps - 1].y, extrudePts[steps - 1].x + z); } } } } /* Faces */ // Top and bottom faces if (lidEnabled) { buildLidFaces(); } // Sides faces buildSideFaces(); ///// Internal functions function 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); } layer = steps + 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); } } else { // Bottom faces for (i = 0; i < flen; i++) { face = faces[i]; f3(face[2], face[1], face[0]); } // Top faces for (i = 0; i < flen; i++) { face = faces[i]; f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps); } } } // Create faces for the z-sides of the shape function buildSideFaces() { var layeroffset = 0; sidewalls(contour, layeroffset); layeroffset += contour.length; for (h = 0, hl = holes.length; h < hl; h++) { ahole = holes[h]; sidewalls(ahole, layeroffset); //, true layeroffset += ahole.length; } } function sidewalls(contour, layeroffset) { var 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 = steps + 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, j, k); } } } function v(x, y, z) { scope.positions.push(new Vector3(x, y, z)); } function f3(a, b, c) { a += shapesOffset; b += shapesOffset; c += shapesOffset; scope.faces.push(new Face3(a, b, c, null, 0)); var uvs = uvgen.generateTopUV(scope, a, b, c); // scope.faceVertexUvs[0].push(uvs); } function f4(a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2) { a += shapesOffset; b += shapesOffset; c += shapesOffset; d += shapesOffset; scope.faces.push(new Face3(a, b, d, null, 1)); scope.faces.push(new Face3(b, c, d, null, 1)); var uvs = uvgen.generateSideWallUV(scope, a, b, c, d); //scope.faceVertexUvs[0].push([uvs[0], uvs[1], uvs[3]]); //scope.faceVertexUvs[0].push([uvs[1], uvs[2], uvs[3]]); } }; }