diff --git a/src/Scene.js b/src/Scene.js index 1b3c4ce..0fc85b3 100644 --- a/src/Scene.js +++ b/src/Scene.js @@ -10,9 +10,11 @@ import Stats from './utils/stats.module.js'; import { add3DPoint } from './datums' import { extrude } from './extrude' import { onHover, onPick } from './utils/mouseEvents'; -import { _vec2, _vec3 } from './utils/static' +import { _vec2, _vec3, color } from './utils/static' import { Vector3 } from 'three/src/Three'; +import CSG from "./utils/three-csg.js" + const eq = (a1, a2) => { if (a1.length != a2.length) return false for (let i = 0; i < a1.length; i++) { @@ -51,10 +53,11 @@ export class Scene extends THREE.Scene { const axesHelper = new THREE.AxesHelper(0.4); helpersGroup.add(axesHelper); + // console.log(color) const pxy = new THREE.Mesh( new THREE.PlaneGeometry(5, 5), new THREE.MeshBasicMaterial({ - color: 0xff0000, + color: color.Plane, opacity: 0.2, side: THREE.DoubleSide, transparent: true, @@ -77,16 +80,15 @@ export class Scene extends THREE.Scene { - const color = 0xFFFFFF; const intensity = 1; - const light1 = new THREE.DirectionalLight(color, intensity); + const light1 = new THREE.DirectionalLight(color.lighting, intensity); light1.position.set(10, 10, 10); this.add(light1); - const light2 = new THREE.DirectionalLight(color, intensity); + const light2 = new THREE.DirectionalLight(color.lighting, intensity); light2.position.set(-10, -10, -5); this.add(light2); - const ambient = new THREE.AmbientLight(color, intensity); + const ambient = new THREE.AmbientLight(color.lighting, intensity); this.add(ambient); @@ -198,4 +200,31 @@ async function addSketch() { } window.sc = new Scene(store); +window.loader = new THREE.ObjectLoader(); + + +// const mm = [] +// for (let i = 1; i <= 3; i++) { +// const obj = loader.parse(JSON.parse(localStorage.getItem(i.toString()))) +// mm.push(obj) +// sc.add(mm[mm.length - 1]) +// obj.visible = false +// } + + + +// //Create a bsp tree from each of the meshes + +// let bspA = CSG.fromMesh( mm[0] ) +// let bspB = CSG.fromMesh( mm[2] ) + +// // Subtract one bsp from the other via .subtract... other supported modes are .union and .intersect + +// let bspResult = bspA.subtract(bspB) + +// //Get the resulting mesh from the result bsp, and assign meshA.material to the resulting mesh + +// let meshResult = CSG.toMesh( bspResult, mm[0].matrix, mm[0].material ) + +// sc.add(meshResult) diff --git a/src/extrude.js b/src/extrude.js index ffc4b25..17dc7d4 100644 --- a/src/extrude.js +++ b/src/extrude.js @@ -12,7 +12,7 @@ export function extrude(sketch) { function findPair(node) { visited.add(node) - let linkedObj = linkedObjs.get(node.l_id) + let linkedObj = linkedObjs.get(node.userData.l_id) let arr; if (linkedObj[0] == 'line') { arr = children[objIdx.get(linkedObj[1][2])].geometry.attributes.position.array @@ -40,7 +40,7 @@ export function extrude(sketch) { function findTouching(node) { - for (let t of node.constraints) { + for (let t of node.userData.constraints) { if (constraints.get(t)[0] != 'coincident') continue for (let c of constraints.get(t)[2]) { if (c == -1) continue; @@ -64,11 +64,12 @@ export function extrude(sketch) { const extrudeSettings = { depth: 8, bevelEnabled: false }; const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); const phong = new THREE.MeshPhongMaterial({ - color: color.extrude, + color: color.Extrude, emissive: color.emissive, flatShading: true }); const mesh = new THREE.Mesh(geometry, phong) + mesh.name = "Extrude" for (let i = 0; i < offSetPts.length; i += 2) { if ( diff --git a/src/sketcher/Sketcher.js b/src/sketcher/Sketcher.js index 87299da..88ce765 100644 --- a/src/sketcher/Sketcher.js +++ b/src/sketcher/Sketcher.js @@ -172,7 +172,7 @@ class Sketcher extends THREE.Group { } delete(obj) { - let link = this.linkedObjs.get(obj.l_id) + let link = this.linkedObjs.get(obj.userData.l_id) if (!link) return; link = link[1] @@ -183,14 +183,14 @@ class Sketcher extends THREE.Group { obj.geometry.dispose() obj.material.dispose() - for (let c_id of obj.constraints) { + for (let c_id of obj.userData.constraints) { this.deleteConstraints(c_id) } } this.children.splice(i, link.length) - this.linkedObjs.delete(obj.l_id) + this.linkedObjs.delete(obj.userData.l_id) return i } @@ -201,7 +201,8 @@ class Sketcher extends THREE.Group { if (idx == -1) continue const ob = this.children[this.objIdx.get(idx)] if (ob) { - ob.constraints.delete(c_id) + // ob.constraints.delete(c_id) + ob.userData.constraints.splice(ob.userData.constraints.indexOf(c_id), 1) } } this.constraints.delete(c_id) diff --git a/src/sketcher/constraintEvents.js b/src/sketcher/constraintEvents.js index 0c6f16e..80c8fc5 100644 --- a/src/sketcher/constraintEvents.js +++ b/src/sketcher/constraintEvents.js @@ -12,9 +12,8 @@ export function addDimension(ent1, ent2, distance) { ] ) - ent1.constraints.add(this.c_id) - ent2.constraints.add(this.c_id) - + ent1.userData.constraints.push(this.c_id) + ent2.userData.constraints.push(this.c_id) } @@ -36,8 +35,8 @@ export function setCoincident() { [toComb[i - 1].id, toComb[i].id, -1, -1] /////// ] ) - toComb[i].constraints.add(this.c_id) - toComb[i - 1].constraints.add(this.c_id) + toComb[i].userData.constraints.push(this.c_id) + toComb[i - 1].userData.constraints.push(this.c_id) } this.updateOtherBuffers() diff --git a/src/sketcher/drawEvents.js b/src/sketcher/drawEvents.js index 5c18e35..43744e8 100644 --- a/src/sketcher/drawEvents.js +++ b/src/sketcher/drawEvents.js @@ -18,7 +18,7 @@ export function drawOnClick1(e) { this.linkedObjs.set(this.l_id, [this.mode, this.toPush.map(e=>e.id)]) for (let obj of this.toPush) { - obj.l_id = this.l_id + obj.userData.l_id = this.l_id } this.l_id += 1 diff --git a/src/sketcher/sketchArc.js b/src/sketcher/sketchArc.js index 6cea6ba..274b905 100644 --- a/src/sketcher/sketchArc.js +++ b/src/sketcher/sketchArc.js @@ -10,16 +10,18 @@ export function sketchArc(mouseLoc) { const p1 = ptObj(mouseLoc) p1.matrixAutoUpdate = false; - p1.constraints = new Set() + p1.userData.constraints = [] const p2 = ptObj() p2.matrixAutoUpdate = false; - p2.constraints = new Set() + p2.userData.constraints = [] const arc = lineObj(n) arc.frustumCulled = false; const p3 = ptObj() + p3.matrixAutoUpdate = false; + p3.userData.constraints = [] return [p1, p2, p3, arc] } diff --git a/src/sketcher/sketchLine.js b/src/sketcher/sketchLine.js index f66cd0c..ace13d9 100644 --- a/src/sketcher/sketchLine.js +++ b/src/sketcher/sketchLine.js @@ -6,16 +6,16 @@ export function sketchLine(mouseLoc) { const p1 = ptObj() p1.matrixAutoUpdate = false; - p1.constraints = new Set() + p1.userData.constraints = [] const p2 = ptObj() p2.matrixAutoUpdate = false; - p2.constraints = new Set() + p2.userData.constraints = [] const line = lineObj() line.matrixAutoUpdate = false; line.frustumCulled = false; - line.constraints = new Set() + line.userData.constraints = [] line.geometry.attributes.position.set(mouseLoc) @@ -30,8 +30,9 @@ export function sketchLine(mouseLoc) { ] ) - p1.constraints.add(this.c_id) - this.children[this.children.length - 2].constraints.add(this.c_id) + p1.userData.constraints.push(this.c_id) + console.log(this.children[this.children.length - 2].userData.constraints,'here') + this.children[this.children.length - 2].userData.constraints.push(this.c_id) } diff --git a/src/utils/csg-lib.js b/src/utils/csg-lib.js new file mode 100644 index 0000000..5da250c --- /dev/null +++ b/src/utils/csg-lib.js @@ -0,0 +1,483 @@ + +// ## License +// +// Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the MIT license. +// THREE.js rework by thrax + +// # class CSG +// Holds a binary space partition tree representing a 3D solid. Two solids can +// be combined using the `union()`, `subtract()`, and `intersect()` methods. + + +class CSG { + constructor() { + this.polygons = []; + } + clone() { + let csg = new CSG(); + csg.polygons = this.polygons.map(function(p) { + return p.clone(); + }); + return csg; + } + + toPolygons() { + return this.polygons; + } + + union(csg) { + let a = new Node(this.clone().polygons); + let b = new Node(csg.clone().polygons); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.allPolygons()); + return CSG.fromPolygons(a.allPolygons()); + } + + subtract(csg) { + let a = new Node(this.clone().polygons); + let b = new Node(csg.clone().polygons); + a.invert(); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.allPolygons()); + a.invert(); + return CSG.fromPolygons(a.allPolygons()); + } + + intersect(csg) { + let a = new Node(this.clone().polygons); + let b = new Node(csg.clone().polygons); + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.allPolygons()); + a.invert(); + return CSG.fromPolygons(a.allPolygons()); + } + + // Return a new CSG solid with solid and empty space switched. This solid is + // not modified. + inverse() { + let csg = this.clone(); + csg.polygons.forEach(p=>p.flip()); + return csg; + } +} + +// Construct a CSG solid from a list of `Polygon` instances. +CSG.fromPolygons=function(polygons) { + let csg = new CSG(); + csg.polygons = polygons; + return csg; +} + +// # class Vector + +// Represents a 3D vector. +// +// Example usage: +// +// new CSG.Vector(1, 2, 3); + + + +class Vector { + constructor(x=0, y=0, z=0) { + this.x=x; + this.y=y; + this.z=z; + } + copy(v){ + this.x=v.x; + this.y=v.y; + this.z=v.z; + return this + } + clone() { + return new Vector(this.x,this.y,this.z) + } + negate() { + this.x*=-1; + this.y*=-1; + this.z*=-1; + return this + } + add(a) { + this.x+=a.x + this.y+=a.y + this.z+=a.z + return this; + } + sub(a) { + this.x-=a.x + this.y-=a.y + this.z-=a.z + return this + } + times(a) { + this.x*=a + this.y*=a + this.z*=a + return this + } + dividedBy(a) { + this.x/=a + this.y/=a + this.z/=a + return this + } + lerp(a, t) { + return this.add(tv0.copy(a).sub(this).times(t)) + } + unit() { + return this.dividedBy(this.length()) + } + length(){ + return Math.sqrt((this.x**2)+(this.y**2)+(this.z**2)) + } + normalize(){ + return this.unit() + } + cross(b) { + let a = this; + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + + return this; + } + dot(b){ + return (this.x*b.x)+(this.y*b.y)+(this.z*b.z) + } +} + +//Temporaries used to avoid internal allocation.. +let tv0=new Vector() +let tv1=new Vector() + + +// # class Vertex + +// Represents a vertex of a polygon. Use your own vertex class instead of this +// one to provide additional features like texture coordinates and vertex +// colors. Custom vertex classes need to provide a `pos` property and `clone()`, +// `flip()`, and `interpolate()` methods that behave analogous to the ones +// defined by `CSG.Vertex`. This class provides `normal` so convenience +// functions like `CSG.sphere()` can return a smooth vertex normal, but `normal` +// is not used anywhere else. + +class Vertex { + + constructor(pos, normal, uv, color) { + this.pos = new Vector().copy(pos); + this.normal = new Vector().copy(normal); + this.uv = new Vector().copy(uv); + this.uv.z=0; + color && (this.color = new Vector().copy(color)); + } + + clone() { + return new Vertex(this.pos,this.normal,this.uv,this.color); + } + + // Invert all orientation-specific data (e.g. vertex normal). Called when the + // orientation of a polygon is flipped. + flip() { + this.normal.negate(); + } + + // Create a new vertex between this vertex and `other` by linearly + // interpolating all properties using a parameter of `t`. Subclasses should + // override this to interpolate additional properties. + interpolate(other, t) { + return new Vertex(this.pos.clone().lerp(other.pos, t),this.normal.clone().lerp(other.normal, t),this.uv.clone().lerp(other.uv, t), this.color&&other.color&&this.color.clone().lerp(other.color,t)) + } +} +; +// # class Plane + +// Represents a plane in 3D space. + +class Plane { + constructor(normal, w) { + this.normal = normal; + this.w = w; + } + + clone() { + return new Plane(this.normal.clone(),this.w); + } + + flip() { + this.normal.negate(); + this.w = -this.w; + } + + // Split `polygon` by this plane if needed, then put the polygon or polygon + // fragments in the appropriate lists. Coplanar polygons go into either + // `coplanarFront` or `coplanarBack` depending on their orientation with + // respect to this plane. Polygons in front or in back of this plane go into + // either `front` or `back`. + splitPolygon(polygon, coplanarFront, coplanarBack, front, back) { + const COPLANAR = 0; + const FRONT = 1; + const BACK = 2; + const SPANNING = 3; + + // Classify each point as well as the entire polygon into one of the above + // four classes. + let polygonType = 0; + let types = []; + for (let i = 0; i < polygon.vertices.length; i++) { + let t = this.normal.dot(polygon.vertices[i].pos) - this.w; + let type = (t < -Plane.EPSILON) ? BACK : (t > Plane.EPSILON) ? FRONT : COPLANAR; + polygonType |= type; + types.push(type); + } + + // Put the polygon in the correct list, splitting it when necessary. + switch (polygonType) { + case COPLANAR: + (this.normal.dot(polygon.plane.normal) > 0 ? coplanarFront : coplanarBack).push(polygon); + break; + case FRONT: + front.push(polygon); + break; + case BACK: + back.push(polygon); + break; + case SPANNING: + let f = [] + , b = []; + for (let i = 0; i < polygon.vertices.length; i++) { + let j = (i + 1) % polygon.vertices.length; + let ti = types[i] + , tj = types[j]; + let vi = polygon.vertices[i] + , vj = polygon.vertices[j]; + if (ti != BACK) + f.push(vi); + if (ti != FRONT) + b.push(ti != BACK ? vi.clone() : vi); + if ((ti | tj) == SPANNING) { + let t = (this.w - this.normal.dot(vi.pos)) / this.normal.dot(tv0.copy(vj.pos).sub(vi.pos)); + let v = vi.interpolate(vj, t); + f.push(v); + b.push(v.clone()); + } + } + if (f.length >= 3) + front.push(new Polygon(f,polygon.shared)); + if (b.length >= 3) + back.push(new Polygon(b,polygon.shared)); + break; + } + } + +} + +// `Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a +// point is on the plane. +Plane.EPSILON = 1e-5; + +Plane.fromPoints = function(a, b, c) { + let n = tv0.copy(b).sub(a).cross(tv1.copy(c).sub(a)).normalize() + return new Plane(n.clone(),n.dot(a)); +} + + +// # class Polygon + +// Represents a convex polygon. The vertices used to initialize a polygon must +// be coplanar and form a convex loop. They do not have to be `Vertex` +// instances but they must behave similarly (duck typing can be used for +// customization). +// +// Each convex polygon has a `shared` property, which is shared between all +// polygons that are clones of each other or were split from the same polygon. +// This can be used to define per-polygon properties (such as surface color). + +class Polygon { + constructor(vertices, shared) { + this.vertices = vertices; + this.shared = shared; + this.plane = Plane.fromPoints(vertices[0].pos, vertices[1].pos, vertices[2].pos); + } + clone() { + return new Polygon(this.vertices.map(v=>v.clone()),this.shared); + } + flip() { + this.vertices.reverse().map(v=>v.flip()) + this.plane.flip(); + } +} + +// # class Node + +// Holds a node in a BSP tree. A BSP tree is built from a collection of polygons +// by picking a polygon to split along. That polygon (and all other coplanar +// polygons) are added directly to that node and the other polygons are added to +// the front and/or back subtrees. This is not a leafy BSP tree since there is +// no distinction between internal and leaf nodes. + +class Node { + constructor(polygons) { + this.plane = null; + this.front = null; + this.back = null; + this.polygons = []; + if (polygons) + this.build(polygons); + } + clone() { + let node = new Node(); + node.plane = this.plane && this.plane.clone(); + node.front = this.front && this.front.clone(); + node.back = this.back && this.back.clone(); + node.polygons = this.polygons.map(p=>p.clone()); + return node; + } + + // Convert solid space to empty space and empty space to solid space. + invert() { + for (let i = 0; i < this.polygons.length; i++) + this.polygons[i].flip(); + + this.plane && this.plane.flip(); + this.front && this.front.invert(); + this.back && this.back.invert(); + let temp = this.front; + this.front = this.back; + this.back = temp; + } + + // Recursively remove all polygons in `polygons` that are inside this BSP + // tree. + clipPolygons(polygons) { + if (!this.plane) + return polygons.slice(); + let front = [] + , back = []; + for (let i = 0; i < polygons.length; i++) { + this.plane.splitPolygon(polygons[i], front, back, front, back); + } + if (this.front) + front = this.front.clipPolygons(front); + if (this.back) + back = this.back.clipPolygons(back); + else + back = []; + return front.concat(back); + } + + // Remove all polygons in this BSP tree that are inside the other BSP tree + // `bsp`. + clipTo(bsp) { + this.polygons = bsp.clipPolygons(this.polygons); + if (this.front) + this.front.clipTo(bsp); + if (this.back) + this.back.clipTo(bsp); + } + + // Return a list of all polygons in this BSP tree. + allPolygons() { + let polygons = this.polygons.slice(); + if (this.front) + polygons = polygons.concat(this.front.allPolygons()); + if (this.back) + polygons = polygons.concat(this.back.allPolygons()); + return polygons; + } + + // Build a BSP tree out of `polygons`. When called on an existing tree, the + // new polygons are filtered down to the bottom of the tree and become new + // nodes there. Each set of polygons is partitioned using the first polygon + // (no heuristic is used to pick a good split). + build(polygons) { + if (!polygons.length) + return; + if (!this.plane) + this.plane = polygons[0].plane.clone(); + let front = [] + , back = []; + for (let i = 0; i < polygons.length; i++) { + this.plane.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + } + if (front.length) { + if (!this.front) + this.front = new Node(); + this.front.build(front); + } + if (back.length) { + if (!this.back) + this.back = new Node(); + this.back.build(back); + } + } +} + + +CSG.fromJSON=function(json){ + return CSG.fromPolygons(json.polygons.map(p=>new Polygon(p.vertices.map(v=> new Vertex(v.pos,v.normal,v.uv)),p.shared))) +} + +export {CSG,Vertex,Vector,Polygon,Plane} + + + +// Return a new CSG solid representing space in either this solid or in the +// solid `csg`. Neither this solid nor the solid `csg` are modified. +// +// A.union(B) +// +// +-------+ +-------+ +// | | | | +// | A | | | +// | +--+----+ = | +----+ +// +----+--+ | +----+ | +// | B | | | +// | | | | +// +-------+ +-------+ +// +// Return a new CSG solid representing space in this solid but not in the +// solid `csg`. Neither this solid nor the solid `csg` are modified. +// +// A.subtract(B) +// +// +-------+ +-------+ +// | | | | +// | A | | | +// | +--+----+ = | +--+ +// +----+--+ | +----+ +// | B | +// | | +// +-------+ +// +// Return a new CSG solid representing space both this solid and in the +// solid `csg`. Neither this solid nor the solid `csg` are modified. +// +// A.intersect(B) +// +// +-------+ +// | | +// | A | +// | +--+----+ = +--+ +// +----+--+ | +--+ +// | B | +// | | +// +-------+ +// + diff --git a/src/utils/mouseEvents.js b/src/utils/mouseEvents.js index 82cfb7b..b124d8c 100644 --- a/src/utils/mouseEvents.js +++ b/src/utils/mouseEvents.js @@ -21,7 +21,6 @@ export function onHover(e) { let idx = [] if (hoverPts.length) { - // console.log(hoverPts) let minDist = Infinity; for (let i = 0; i < hoverPts.length; i++) { if (!hoverPts[i].distanceToRay) continue; @@ -40,7 +39,8 @@ export function onHover(e) { const obj = this.hovered[this.hovered.length - 1] if (obj && !this.selected.includes(obj)) { - obj.material.color.set(0x555555) + // obj.material.color.set(0x555555) + obj.material.color.set(color[obj.name]) } this.hovered = [] @@ -58,7 +58,7 @@ export function onHover(e) { const obj = this.hovered[this.hovered.length - 1] if (obj && !this.selected.includes(obj)) { - obj.material.color.set(0x555555) + obj.material.color.set(color[obj.name]) } this.hovered = [] @@ -82,7 +82,8 @@ export function onPick(e) { } } else { for (let obj of this.selected) { - obj.material.color.set(0x555555) + // obj.material.color.set(0x555555) + obj.material.color.set(color[obj.name]) } this.dispatchEvent({ type: 'change' }) this.selected = [] diff --git a/src/utils/static.js b/src/utils/static.js index 665d2e3..a813278 100644 --- a/src/utils/static.js +++ b/src/utils/static.js @@ -8,43 +8,52 @@ const _vec3 = new THREE.Vector3() const raycaster = new THREE.Raycaster(); raycaster.params.Line.threshold = 0.8; -raycaster.params.Points.threshold = 1.5; +raycaster.params.Points.threshold = 0.6; const color = { - dark1: 0x555555, hover: 0x00ff00, - extrude: 0x156289, - emissive: 0x072534 + lighting: 0xFFFFFF, + emissive: 0x072534, + Plane: 0xf5bc42, + Line: 0x555555, + Points: 0x555555, + Extrude: 0x156289, } const lineMaterial = new THREE.LineBasicMaterial({ linewidth: 2, - color: color.dark1, + color: color.Line, }) const pointMaterial = new THREE.PointsMaterial({ - color: color.dark1, + color: color.Points, size: 4, }) +const ptObj = (n) => { + const ret = new THREE.Points( + new THREE.BufferGeometry().setAttribute('position', + new THREE.Float32BufferAttribute(n || 3, 3) + ), + pointMaterial.clone() + ); + ret.name = 'Points' + return ret +} - -const ptObj = (n) => new THREE.Points( - new THREE.BufferGeometry().setAttribute('position', - new THREE.Float32BufferAttribute(n || 3, 3) - ), - pointMaterial.clone() -); - -const lineObj = (n=1) => new THREE.Line( - new THREE.BufferGeometry().setAttribute('position', - new THREE.Float32BufferAttribute(3 * (n+1), 3) - ), - lineMaterial.clone() -); +const lineObj = (n = 1) => { + const ret = new THREE.Line( + new THREE.BufferGeometry().setAttribute('position', + new THREE.Float32BufferAttribute(3 * (n + 1), 3) + ), + lineMaterial.clone() + ); + ret.name = 'Line' + return ret +} export { lineMaterial, pointMaterial, _vec2, _vec3, raycaster, color, ptObj, lineObj } \ No newline at end of file diff --git a/src/utils/three-csg.js b/src/utils/three-csg.js new file mode 100644 index 0000000..2d1d778 --- /dev/null +++ b/src/utils/three-csg.js @@ -0,0 +1,200 @@ +"use strict" + +import * as THREE from '../../node_modules/three/src/Three'; +import { CSG, Vertex, Polygon } from "./csg-lib.js" + + +CSG.fromGeometry = function (geom, objectIndex) { + if (!geom.isBufferGeometry) { + console.error("Unsupported CSG input type:" + geom.type) + return + } + let polys = [] + + let posattr = geom.attributes.position + let normalattr = geom.attributes.normal + let uvattr = geom.attributes.uv + let colorattr = geom.attributes.color + let index; + if (geom.index) + index = geom.index.array; + else { + index = new Array((posattr.array.length / posattr.itemSize) | 0); + for (let i = 0; i < index.length; i++) + index[i] = i + } + let triCount = (index.length / 3) | 0 + polys = new Array(triCount) + for (let i = 0, pli = 0, l = index.length; i < l; i += 3, + pli++) { + let vertices = new Array(3) + for (let j = 0; j < 3; j++) { + let vi = index[i + j] + let vp = vi * 3; + let vt = vi * 2; + let x = posattr.array[vp] + let y = posattr.array[vp + 1] + let z = posattr.array[vp + 2] + let nx = normalattr.array[vp] + let ny = normalattr.array[vp + 1] + let nz = normalattr.array[vp + 2] + let u = uvattr.array[vt] + let v = uvattr.array[vt + 1] + vertices[j] = new Vertex({ + x, + y, + z + }, { + x: nx, + y: ny, + z: nz + }, { + x: u, + y: v, + z: 0 + }, colorattr && { x: colorattr.array[vt], y: colorattr.array[vt + 1], z: colorattr.array[vt + 2] }); + } + polys[pli] = new Polygon(vertices, objectIndex) + } + + return CSG.fromPolygons(polys) +} + +let ttvv0 = new THREE.Vector3() +let tmpm3 = new THREE.Matrix3(); +CSG.fromMesh = function (mesh, objectIndex) { + let csg = CSG.fromGeometry(mesh.geometry, objectIndex) + tmpm3.getNormalMatrix(mesh.matrix); + for (let i = 0; i < csg.polygons.length; i++) { + let p = csg.polygons[i] + for (let j = 0; j < p.vertices.length; j++) { + let v = p.vertices[j] + v.pos.copy(ttvv0.copy(v.pos).applyMatrix4(mesh.matrix)); + v.normal.copy(ttvv0.copy(v.normal).applyMatrix3(tmpm3)) + } + } + return csg; +} + +let nbuf3 = (ct) => { + return { + top: 0, + array: new Float32Array(ct), + write: function (v) { (this.array[this.top++] = v.x); (this.array[this.top++] = v.y); (this.array[this.top++] = v.z); } + } +} +let nbuf2 = (ct) => { + return { + top: 0, + array: new Float32Array(ct), + write: function (v) { (this.array[this.top++] = v.x); (this.array[this.top++] = v.y) } + } +} + +CSG.toMesh = function (csg, toMatrix, toMaterial) { + + let ps = csg.polygons; + let geom; + let g2; + if (0) //Old geometry path... + { + geom = new Geometry(); + let vs = geom.vertices; + let fvuv = geom.faceVertexUvs[0] + for (let i = 0; i < ps.length; i++) { + let p = ps[i] + let pvs = p.vertices; + let v0 = vs.length; + let pvlen = pvs.length + + for (let j = 0; j < pvlen; j++) + vs.push(new THREE.Vector3().copy(pvs[j].pos)) + + for (let j = 3; j <= pvlen; j++) { + let fc = new THREE.Face3(); + let fuv = [] + fvuv.push(fuv) + let fnml = fc.vertexNormals; + fc.a = v0; + fc.b = v0 + j - 2; + fc.c = v0 + j - 1; + + fnml.push(new THREE.Vector3().copy(pvs[0].normal)) + fnml.push(new THREE.Vector3().copy(pvs[j - 2].normal)) + fnml.push(new THREE.Vector3().copy(pvs[j - 1].normal)) + fuv.push(new THREE.Vector3().copy(pvs[0].uv)) + fuv.push(new THREE.Vector3().copy(pvs[j - 2].uv)) + fuv.push(new THREE.Vector3().copy(pvs[j - 1].uv)) + + fc.normal = new THREE.Vector3().copy(p.plane.normal) + geom.faces.push(fc) + } + } + geom = new THREE.BufferGeometry().fromGeometry(geom) + geom.verticesNeedUpdate = geom.elementsNeedUpdate = geom.normalsNeedUpdate = true; + } + if (1) { //BufferGeometry path + let triCount = 0; + ps.forEach(p => triCount += (p.vertices.length - 2)) + geom = new THREE.BufferGeometry() + + let vertices = nbuf3(triCount * 3 * 3) + let normals = nbuf3(triCount * 3 * 3) + let uvs = nbuf2(triCount * 2 * 3) + let colors; + let grps = [] + ps.forEach(p => { + let pvs = p.vertices + let pvlen = pvs.length + if (p.shared !== undefined) { + if (!grps[p.shared]) grps[p.shared] = [] + } + if (pvlen && pvs[0].color !== undefined) { + if (!colors) colors = nbuf3(triCount * 3 * 3); + } + for (let j = 3; j <= pvlen; j++) { + (p.shared !== undefined) && (grps[p.shared].push(vertices.top / 3, (vertices.top / 3) + 1, (vertices.top / 3) + 2)); + vertices.write(pvs[0].pos) + vertices.write(pvs[j - 2].pos) + vertices.write(pvs[j - 1].pos) + normals.write(pvs[0].normal) + normals.write(pvs[j - 2].normal) + normals.write(pvs[j - 1].normal) + uvs.write(pvs[0].uv) + uvs.write(pvs[j - 2].uv) + uvs.write(pvs[j - 1].uv) + colors && (colors.write(pvs[0].color) || colors.write(pvs[j - 2].color) || colors.write(pvs[j - 1].color)) + } + } + ) + geom.setAttribute('position', new THREE.BufferAttribute(vertices.array, 3)); + geom.setAttribute('normal', new THREE.BufferAttribute(normals.array, 3)); + geom.setAttribute('uv', new THREE.BufferAttribute(uvs.array, 2)); + colors && geom.setAttribute('color', new THREE.BufferAttribute(colors.array, 3)); + if (grps.length) { + let index = [] + let gbase = 0; + for (let gi = 0; gi < grps.length; gi++) { + geom.addGroup(gbase, grps[gi].length, gi) + gbase += grps[gi].length + index = index.concat(grps[gi]); + } + geom.setIndex(index) + } + g2 = geom; + } + + let inv = new THREE.Matrix4().copy(toMatrix).invert(); + geom.applyMatrix4(inv); + geom.computeBoundingSphere(); + geom.computeBoundingBox(); + let m = new THREE.Mesh(geom, toMaterial); + m.matrix.copy(toMatrix); + m.matrix.decompose(m.position, m.quaternion, m.scale) + m.rotation.setFromQuaternion(m.quaternion) + m.updateMatrixWorld(); + m.castShadow = m.receiveShadow = true; + return m +} + +export default CSG