123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- /*
- * Extruder.js
- *
- * @author realor
- */
- import { ObjectBuilder } from './ObjectBuilder.js'
- import { SweptSolidBuilder } from './SweptSolidBuilder.js'
- import { Solid } from '../core/Solid.js'
- import { Profile } from '../core/Profile.js'
- import { Cord } from '../core/Cord.js'
- import { SolidGeometry } from '../core/SolidGeometry.js'
- import { ProfileGeometry } from '../core/ProfileGeometry.js'
- import * as THREE from '../lib/three.module.js'
- class Extruder extends SweptSolidBuilder {
- depth = 1
- direction = new THREE.Vector3(0, 0, 1)
- smoothAngle = 0
- constructor(depth, direction) {
- super()
- if (typeof depth === 'number') {
- this.depth = depth
- }
- if (direction instanceof THREE.Vector3) {
- this.direction.copy(direction)
- }
- }
- performBuild(solid) {
- const direction = this.direction
- const depth = this.depth
- let profile = this.findClosedProfile(solid)
- if (profile === undefined) return true
- let [outerRing, innerRings, stepVertexCount] = this.prepareRings(profile)
- const sign = Math.sign(depth) * Math.sign(direction.z)
- let cordPoints
- let extrudeVector = null
- let cord = this.findCord(solid)
- if (cord && cord.geometry) {
- cord.visible = false
- cordPoints = cord.geometry.points
- //remove duplicated vertices
- const array = []
- array.push(cordPoints[0])
- for (let i = 1; i < cordPoints.length; i++) {
- if (cordPoints[i - 1].distanceTo(cordPoints[i]) >= this.minPointDistance) {
- array.push(cordPoints[i].clone())
- }
- }
- if (array.length < 2) return
- cordPoints = array
- for (let point of cordPoints) {
- point.applyMatrix4(cord.matrix)
- }
- } else {
- // extrude in direction vector
- extrudeVector = new THREE.Vector3()
- extrudeVector.copy(direction).normalize()
- if (depth !== 0) {
- extrudeVector.multiplyScalar(Math.abs(depth) * Math.sign(direction.z))
- }
- // create cordPoints from direction
- cordPoints = [] // Point3D[]
- cordPoints.push(new THREE.Vector3(0, 0, 0))
- cordPoints.push(new THREE.Vector3(0, 0, extrudeVector.z))
- }
- const p1 = new THREE.Vector3()
- const p2 = new THREE.Vector3()
- const p3 = new THREE.Vector3()
- const vs = new THREE.Vector3()
- const vx = new THREE.Vector3()
- const vy = new THREE.Vector3()
- const vz = new THREE.Vector3()
- const v1 = new THREE.Vector3()
- const v2 = new THREE.Vector3()
- const ray = new THREE.Ray()
- // add fake point to generate last ring
- let length = cordPoints.length
- vs.subVectors(cordPoints[length - 1], cordPoints[length - 2])
- let last = new THREE.Vector3()
- last.copy(cordPoints[length - 1]).add(vs)
- cordPoints.push(last)
- // create matrix from initial segment of cordPoints
- p1.copy(cordPoints[0])
- p2.copy(cordPoints[1])
- vz.subVectors(p2, p1)
- vz.normalize()
- if (vz.y !== 0) vx.set(-vz.y, vz.x, 0).normalize()
- else if (vz.x !== 0) vx.set(vz.y, -vz.x, 0).normalize()
- else vx.set(1, 0, 0)
- vy.crossVectors(vz, vx)
- let matrix = new THREE.Matrix4()
- matrix.set(vx.x, vy.x, vz.x, p1.x, vx.y, vy.y, vz.y, p1.y, vx.z, vy.z, vz.z, p1.z, 0, 0, 0, 1)
- matrix.multiply(profile.matrix)
- const geometry = new SolidGeometry()
- const getPlane = (v1, v2, point) => {
- let normal = new THREE.Vector3()
- if (Math.abs(v1.dot(v2)) > 0.9999) {
- normal = v1
- } else {
- let s = new THREE.Vector3()
- s.subVectors(v2, v1).normalize()
- let v = new THREE.Vector3()
- v.crossVectors(s, v1).normalize()
- normal.crossVectors(s, v).normalize()
- }
- let plane = new THREE.Plane()
- plane.setFromNormalAndCoplanarPoint(normal, point)
- return plane
- }
- // add all ring vertices
- this.addStepVertices(outerRing, innerRings, matrix, geometry)
- if (depth === 0) {
- // special case
- // add top face
- this.addProfileFace(0, outerRing, innerRings, false, geometry)
- geometry.isManifold = false
- geometry.smoothAngle = this.smoothAngle
- solid.updateGeometry(geometry)
- return true
- }
- // add bottom face
- this.addProfileFace(0, outerRing, innerRings, true, geometry)
- let offset1 = 0
- let offset2 = stepVertexCount
- // create side faces
- for (let i = 1; i < cordPoints.length - 1; i++) {
- // add new step vertices
- p1.copy(cordPoints[i - 1])
- p2.copy(cordPoints[i])
- p3.copy(cordPoints[i + 1])
- v1.subVectors(p2, p1).normalize()
- v2.subVectors(p3, p2).normalize()
- let plane = getPlane(v1, v2, p2)
- for (let i = 0; i < stepVertexCount; i++) {
- ray.set(geometry.vertices[offset1 + i], v1)
- let vertex = new THREE.Vector3()
- vertex = ray.intersectPlane(plane, vertex)
- if (vertex === null) throw "Can't extrude this profile for the given directrix"
- geometry.vertices.push(vertex)
- }
- this.addLateralFaces(offset1, offset2, outerRing, innerRings, false, geometry)
- offset1 = offset2
- offset2 += stepVertexCount
- }
- // add top face
- this.addProfileFace(offset1, outerRing, innerRings, false, geometry)
- if (extrudeVector) {
- /* shear */
- const a = extrudeVector.x / extrudeVector.z
- const b = extrudeVector.y / extrudeVector.z
- const shearMatrix = new THREE.Matrix4()
- shearMatrix.elements[8] = a
- shearMatrix.elements[9] = b
- if (sign < 0) {
- const reverseMatrix = new THREE.Matrix4()
- extrudeVector.multiplyScalar(-1)
- reverseMatrix.makeTranslation(extrudeVector.x, extrudeVector.y, extrudeVector.z)
- shearMatrix.multiplyMatrices(reverseMatrix, shearMatrix)
- }
- geometry.applyMatrix4(shearMatrix)
- }
- geometry.isManifold = true
- geometry.smoothAngle = this.smoothAngle
- solid.updateGeometry(geometry)
- return true
- }
- findClosedProfile(solid) {
- for (let child of solid.children) {
- if (child instanceof Profile) {
- if (child.geometry && child.geometry.isClosed()) return child
- }
- }
- return undefined
- }
- findCord(solid) {
- for (let child of solid.children) {
- if (child instanceof Cord) {
- if (child.geometry) return child
- }
- }
- return undefined
- }
- copy(source) {
- this.depth = source.depth
- this.direction.copy(source.direction)
- this.smoothAngle = source.smothAngle // degrees
- this.minPointDistance = source.minPointDistance
- return this
- }
- }
- ObjectBuilder.addClass(Extruder)
- export { Extruder }
|