mirror of https://gitee.com/cf-fz/WebCAD.git
fix #IKXMP !295 新的CSG实现
parent
a96d358d0f
commit
c6f01897ab
@ -1,61 +0,0 @@
|
||||
import * as THREE from 'three';
|
||||
import { app } from '../ApplicationServices/Application';
|
||||
import { Command } from '../Editor/CommandMachine';
|
||||
import { PromptSsgetResult, PromptStatus } from '../Editor/PromptResult';
|
||||
import { CSG } from '../Geometry/ThreeCSG';
|
||||
import { RenderType } from '../GraphicsSystem/RenderType';
|
||||
|
||||
async function GetEntity(msg: string)
|
||||
{
|
||||
let enRes: PromptSsgetResult
|
||||
enRes = await app.m_Editor.GetSelection({
|
||||
Msg: msg
|
||||
});
|
||||
if (enRes.Status === PromptStatus.OK)
|
||||
return enRes.SelectSet.SelectEntityList[0];
|
||||
else if (enRes.Status === PromptStatus.Cancel)
|
||||
return;
|
||||
}
|
||||
|
||||
abstract class CSGOperation implements Command
|
||||
{
|
||||
async exec()
|
||||
{
|
||||
let en1 = await GetEntity("请选择第一个对象");
|
||||
let en2 = await GetEntity("请选择第二个对象");
|
||||
|
||||
if (!en1 || !en2) return;
|
||||
let mesh1 = en1.GetDrawObjectFromRenderType(RenderType.Wireframe) as THREE.Mesh;
|
||||
let mesh2 = en2.GetDrawObjectFromRenderType(RenderType.Wireframe) as THREE.Mesh;
|
||||
|
||||
let csg1 = new CSG(mesh1);
|
||||
let csg2 = new CSG(mesh2);
|
||||
|
||||
let csg3 = this.Operate(csg1, csg2);
|
||||
|
||||
let mesh = csg3.toMesh(mesh1.material);
|
||||
app.m_Viewer.Scene.add(mesh);
|
||||
en1.Erase();
|
||||
en2.Erase();
|
||||
}
|
||||
protected Operate(csg1: CSG, csg2: CSG): CSG
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class CSGUnion extends CSGOperation
|
||||
{
|
||||
protected Operate(csg1: CSG, csg2: CSG)
|
||||
{
|
||||
return csg1.union(csg2);
|
||||
}
|
||||
}
|
||||
|
||||
export class CSGSubtraction extends CSGOperation
|
||||
{
|
||||
protected Operate(csg1: CSG, csg2: CSG)
|
||||
{
|
||||
return csg1.subtract(csg2);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { Face3, Geometry, Mesh, Object3D, Vector3 } from "three";
|
||||
import { RenderType } from "../GraphicsSystem/RenderType";
|
||||
import { CADFiler } from "./CADFiler";
|
||||
import { Entity } from "./Entity";
|
||||
import { Factory } from "./CADFactory";
|
||||
|
||||
@Factory
|
||||
export class FaceEntity extends Entity
|
||||
{
|
||||
constructor(private p1: Vector3 = new Vector3(), private p2: Vector3 = new Vector3(), private p3: Vector3 = new Vector3(), private normal: Vector3 = new Vector3())
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
protected InitDrawObject(renderType: RenderType = RenderType.Wireframe): Object3D
|
||||
{
|
||||
let g = new Geometry();
|
||||
|
||||
g.vertices.push(this.p1, this.p2, this.p3);
|
||||
g.faces.push(new Face3(0, 1, 2));
|
||||
|
||||
return new Mesh(g);
|
||||
}
|
||||
|
||||
|
||||
//对象从文件中读取数据,初始化自身
|
||||
ReadFile(file: CADFiler)
|
||||
{
|
||||
let ver = file.Read();
|
||||
super.ReadFile(file);
|
||||
|
||||
this.p1.fromArray(file.Read());
|
||||
this.p2.fromArray(file.Read());
|
||||
this.p3.fromArray(file.Read());
|
||||
|
||||
this.normal.fromArray(file.Read());
|
||||
|
||||
}
|
||||
//对象将自身数据写入到文件.
|
||||
WriteFile(file: CADFiler)
|
||||
{
|
||||
file.Write(1);
|
||||
super.WriteFile(file);
|
||||
|
||||
file.Write(this.p1.toArray());
|
||||
file.Write(this.p2.toArray());
|
||||
file.Write(this.p3.toArray());
|
||||
|
||||
file.Write(this.normal.toArray());
|
||||
|
||||
}
|
||||
//#endregion
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import { Shape, Vector2, EllipseCurve } from "three";
|
||||
import { equalv2 } from "../Geometry/GeUtils";
|
||||
|
||||
export class Shape2 extends Shape
|
||||
{
|
||||
getPoints(divisions: number = 12)
|
||||
{
|
||||
divisions = divisions || 12;
|
||||
let points = [], last: Vector2;
|
||||
for (let i = 0, curves = this.curves; i < curves.length; i++)
|
||||
{
|
||||
let curve = curves[i];
|
||||
//@ts-ignore
|
||||
let resolution = (curve && curve.isEllipseCurve) ? divisions * 2
|
||||
//@ts-ignore
|
||||
: (curve && (curve.isLineCurve || curve.isLineCurve3)) ? 1
|
||||
//@ts-ignore
|
||||
: (curve && curve.isSplineCurve) ? divisions * curve.points.length
|
||||
: divisions;
|
||||
|
||||
let pts = curve.getPoints(resolution);
|
||||
|
||||
for (let j = 0; j < pts.length; j++)
|
||||
{
|
||||
let point = pts[j];
|
||||
if (last && equalv2(last, point, 1e-4)) 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;
|
||||
}
|
||||
|
||||
|
||||
absellipse(aX: number, aY: number, xRadius: number, yRadius: number, aStartAngle: number, aEndAngle: number, aClockwise: boolean, aRotation: number)
|
||||
{
|
||||
let 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
|
||||
let firstPoint = curve.getPoint(0);
|
||||
if (!equalv2(firstPoint, this.currentPoint))
|
||||
{
|
||||
this.lineTo(firstPoint.x, firstPoint.y);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
this.curves.push(curve);
|
||||
|
||||
let lastPoint = curve.getPoint(1);
|
||||
this.currentPoint.copy(lastPoint);
|
||||
}
|
||||
}
|
@ -1,506 +0,0 @@
|
||||
import { BufferGeometry, Face3, Geometry, Material, Matrix4, Mesh, Vector2, Vector3 } from "three";
|
||||
|
||||
//ref: https://github.com/chandlerprall/ThreeCSG/blob/master/threeCSG.es6
|
||||
|
||||
|
||||
const EPSILON = 1e-5;
|
||||
|
||||
enum Side
|
||||
{
|
||||
Coplanar = 0,
|
||||
Front = 1,
|
||||
Back = 2,
|
||||
Spanning = 3
|
||||
}
|
||||
|
||||
export class CSG
|
||||
{
|
||||
tree: Node;
|
||||
matrix: Matrix4;
|
||||
constructor(obj: Geometry | Mesh | Node, matrix?: Matrix4)
|
||||
{
|
||||
let geometry: Geometry;
|
||||
if (obj instanceof Geometry)
|
||||
{
|
||||
geometry = obj;
|
||||
this.matrix = matrix || new Matrix4();
|
||||
}
|
||||
else if (obj instanceof Mesh)
|
||||
{
|
||||
// #todo: add hierarchy support
|
||||
this.matrix = obj.matrix.clone();
|
||||
let geo = obj.geometry;
|
||||
if (geo instanceof BufferGeometry)
|
||||
geometry = new Geometry().fromBufferGeometry(geo);
|
||||
else
|
||||
geometry = geo;
|
||||
}
|
||||
else if (obj instanceof Node)
|
||||
{
|
||||
this.tree = obj;
|
||||
this.matrix = matrix || new Matrix4();
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw '未支持的类型';
|
||||
}
|
||||
|
||||
|
||||
let polgons: Polygon[] = [];
|
||||
for (let i = 0; i < geometry.faces.length; i++)
|
||||
{
|
||||
let face = geometry.faces[i];
|
||||
let faceVertexUvs = geometry.faceVertexUvs[0][i];
|
||||
let polygon = new Polygon();
|
||||
|
||||
if (face instanceof Face3)
|
||||
{
|
||||
let uvs = faceVertexUvs ? faceVertexUvs[0].clone() : null;
|
||||
let vertex1 = new Vertex(geometry.vertices[face.a], face.vertexNormals[0], uvs);
|
||||
vertex1.applyMatrix4(this.matrix);
|
||||
polygon.vertices.push(vertex1);
|
||||
|
||||
uvs = faceVertexUvs ? faceVertexUvs[1].clone() : null;
|
||||
let vertex2 = new Vertex(geometry.vertices[face.b], face.vertexNormals[1], uvs);
|
||||
vertex2.applyMatrix4(this.matrix);
|
||||
polygon.vertices.push(vertex2);
|
||||
|
||||
uvs = faceVertexUvs ? faceVertexUvs[2].clone() : null;
|
||||
let vertex3 = new Vertex(geometry.vertices[face.c], face.vertexNormals[2], uvs);
|
||||
vertex3.applyMatrix4(this.matrix);
|
||||
polygon.vertices.push(vertex3);
|
||||
}
|
||||
|
||||
polygon.calculateProperties();
|
||||
polgons.push(polygon);
|
||||
}
|
||||
|
||||
this.tree = new Node(polgons);
|
||||
}
|
||||
|
||||
subtract(other_tree: CSG)
|
||||
{
|
||||
let a = this.tree.clone(),
|
||||
b = other_tree.tree.clone();
|
||||
|
||||
a.invert();
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.build(b.allPolygons());
|
||||
a.invert();
|
||||
return new CSG(a, this.matrix);
|
||||
}
|
||||
|
||||
union(other_tree: CSG)
|
||||
{
|
||||
let a = this.tree.clone(),
|
||||
b = other_tree.tree.clone();
|
||||
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.build(b.allPolygons());
|
||||
return new CSG(a, this.matrix);
|
||||
}
|
||||
|
||||
intersect(other_tree: CSG)
|
||||
{
|
||||
let a = this.tree.clone(),
|
||||
b = other_tree.tree.clone();
|
||||
|
||||
a.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
a.build(b.allPolygons());
|
||||
a.invert();
|
||||
return new CSG(a, this.matrix);
|
||||
}
|
||||
|
||||
toGeometry()
|
||||
{
|
||||
let matrix = new Matrix4().getInverse(this.matrix),
|
||||
geometry = new Geometry(),
|
||||
polygons = this.tree.allPolygons(),
|
||||
polygon_count = polygons.length,
|
||||
vertice_dict = {},
|
||||
vertex_idx_a: number, vertex_idx_b: number, vertex_idx_c: number;
|
||||
|
||||
let m0 = matrix.clone().setPosition(new Vector3());
|
||||
|
||||
for (let i = 0; i < polygon_count; i++)
|
||||
{
|
||||
let polygon = polygons[i];
|
||||
let polygon_vertice_count = polygon.vertices.length;
|
||||
|
||||
let normal = polygon.normal.clone().applyMatrix4(m0);
|
||||
|
||||
for (let j = 2; j < polygon_vertice_count; j++)
|
||||
{
|
||||
let verticeUvs = [];
|
||||
|
||||
let vertex = polygon.vertices[0];
|
||||
verticeUvs.push(new Vector2(vertex.uv.x, vertex.uv.y));
|
||||
let vertex1 = new Vector3(vertex.x, vertex.y, vertex.z);
|
||||
vertex1.applyMatrix4(matrix);
|
||||
|
||||
if (typeof vertice_dict[vertex1.x + ',' + vertex1.y + ',' + vertex1.z] !== 'undefined')
|
||||
{
|
||||
vertex_idx_a = vertice_dict[vertex1.x + ',' + vertex1.y + ',' + vertex1.z];
|
||||
} else
|
||||
{
|
||||
geometry.vertices.push(vertex1);
|
||||
vertex_idx_a = vertice_dict[vertex1.x + ',' + vertex1.y + ',' + vertex1.z] = geometry.vertices.length - 1;
|
||||
}
|
||||
|
||||
vertex = polygon.vertices[j - 1];
|
||||
verticeUvs.push(new Vector2(vertex.uv.x, vertex.uv.y));
|
||||
let vertex2 = new Vector3(vertex.x, vertex.y, vertex.z);
|
||||
vertex2.applyMatrix4(matrix);
|
||||
if (typeof vertice_dict[vertex2.x + ',' + vertex2.y + ',' + vertex2.z] !== 'undefined')
|
||||
{
|
||||
vertex_idx_b = vertice_dict[vertex2.x + ',' + vertex2.y + ',' + vertex2.z];
|
||||
} else
|
||||
{
|
||||
geometry.vertices.push(vertex2);
|
||||
vertex_idx_b = vertice_dict[vertex2.x + ',' + vertex2.y + ',' + vertex2.z] = geometry.vertices.length - 1;
|
||||
}
|
||||
|
||||
vertex = polygon.vertices[j];
|
||||
verticeUvs.push(new Vector2(vertex.uv.x, vertex.uv.y));
|
||||
let vertex3 = new Vector3(vertex.x, vertex.y, vertex.z);
|
||||
vertex3.applyMatrix4(matrix);
|
||||
if (typeof vertice_dict[vertex3.x + ',' + vertex3.y + ',' + vertex3.z] !== 'undefined')
|
||||
{
|
||||
vertex_idx_c = vertice_dict[vertex3.x + ',' + vertex3.y + ',' + vertex3.z];
|
||||
}
|
||||
else
|
||||
{
|
||||
geometry.vertices.push(vertex3);
|
||||
vertex_idx_c = vertice_dict[vertex3.x + ',' + vertex3.y + ',' + vertex3.z] = geometry.vertices.length - 1;
|
||||
}
|
||||
|
||||
let face = new Face3(
|
||||
vertex_idx_a,
|
||||
vertex_idx_b,
|
||||
vertex_idx_c,
|
||||
normal
|
||||
);
|
||||
|
||||
geometry.faces.push(face);
|
||||
geometry.faceVertexUvs[0].push(verticeUvs);
|
||||
}
|
||||
|
||||
}
|
||||
return geometry;
|
||||
}
|
||||
|
||||
toMesh(material?: Material | Material[])
|
||||
{
|
||||
let geometry = this.toGeometry(),
|
||||
mesh = new Mesh(geometry, material);
|
||||
|
||||
mesh.applyMatrix(this.matrix)
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
export class Polygon
|
||||
{
|
||||
constructor(
|
||||
public vertices: Vertex[] = [],
|
||||
public normal?: Vector3,
|
||||
public w?: number)
|
||||
{
|
||||
if (vertices.length > 0 && !normal)
|
||||
this.calculateProperties();
|
||||
}
|
||||
|
||||
calculateProperties()
|
||||
{
|
||||
let a = this.vertices[0],
|
||||
b = this.vertices[1],
|
||||
c = this.vertices[2];
|
||||
|
||||
this.normal = b.clone().sub(a)
|
||||
.cross(c.clone().sub(a))
|
||||
.normalize();
|
||||
|
||||
this.w = this.normal.dot(a);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
clone()
|
||||
{
|
||||
return new Polygon(
|
||||
this.vertices.map(v => v.clone()),
|
||||
this.normal.clone(),
|
||||
this.w
|
||||
);
|
||||
}
|
||||
|
||||
flip()
|
||||
{
|
||||
this.normal.multiplyScalar(-1);
|
||||
this.w *= -1;
|
||||
this.vertices.reverse();
|
||||
return this;
|
||||
}
|
||||
|
||||
classifyVertex(vertex: Vector3 | Vertex): Side
|
||||
{
|
||||
let side_value = this.normal.dot(vertex) - this.w;
|
||||
|
||||
if (side_value < -EPSILON)
|
||||
{
|
||||
return Side.Back;
|
||||
}
|
||||
else if (side_value > EPSILON)
|
||||
{
|
||||
return Side.Front;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Side.Coplanar;
|
||||
}
|
||||
}
|
||||
|
||||
classifySide(polygon: Polygon): Side
|
||||
{
|
||||
let num_positive = 0,
|
||||
num_negative = 0,
|
||||
vertice_count = polygon.vertices.length;
|
||||
|
||||
for (let i = 0; i < vertice_count; i++)
|
||||
{
|
||||
let vertex = polygon.vertices[i];
|
||||
let classification = this.classifyVertex(vertex);
|
||||
if (classification === Side.Front)
|
||||
num_positive++;
|
||||
else if (classification === Side.Back)
|
||||
num_negative++;
|
||||
}
|
||||
|
||||
if (num_positive > 0 && num_negative === 0)
|
||||
return Side.Front;
|
||||
else if (num_positive === 0 && num_negative > 0)
|
||||
return Side.Back;
|
||||
else if (num_positive === 0 && num_negative === 0)
|
||||
return Side.Coplanar;
|
||||
else
|
||||
return Side.Spanning;
|
||||
}
|
||||
|
||||
splitPolygon(polygon: Polygon, coplanar_front: Polygon[], coplanar_back: Polygon[], front: Polygon[], back: Polygon[])
|
||||
{
|
||||
let classification = this.classifySide(polygon);
|
||||
|
||||
if (classification === Side.Coplanar)
|
||||
{
|
||||
(this.normal.dot(polygon.normal) > 0 ? coplanar_front : coplanar_back).push(polygon);
|
||||
}
|
||||
else if (classification === Side.Front)
|
||||
{
|
||||
front.push(polygon);
|
||||
}
|
||||
else if (classification === Side.Back)
|
||||
{
|
||||
back.push(polygon);
|
||||
}
|
||||
else
|
||||
{
|
||||
let f = [];
|
||||
let b = [];
|
||||
|
||||
for (let i = 0, vertice_count = polygon.vertices.length; i < vertice_count; i++)
|
||||
{
|
||||
let j = (i + 1) % vertice_count;
|
||||
let vi = polygon.vertices[i];
|
||||
let vj = polygon.vertices[j];
|
||||
let ti = this.classifyVertex(vi);
|
||||
let tj = this.classifyVertex(vj);
|
||||
|
||||
if (ti != Side.Back) f.push(vi);
|
||||
if (ti != Side.Front) b.push(vi);
|
||||
if ((ti | tj) === Side.Spanning)
|
||||
{
|
||||
let t = (this.w - this.normal.dot(vi)) / this.normal.dot(vj.clone().sub(vi));
|
||||
let v = vi.clone().lerp(vj, t);
|
||||
f.push(v);
|
||||
b.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
if (f.length >= 3) front.push(new Polygon(f).calculateProperties());
|
||||
if (b.length >= 3) back.push(new Polygon(b).calculateProperties());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Vertex extends Vector3
|
||||
{
|
||||
constructor(
|
||||
pos: Vector3,
|
||||
public normal = new Vector3(),
|
||||
public uv = new Vector2())
|
||||
{
|
||||
super(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
clone(): Vertex
|
||||
{
|
||||
return new Vertex(this, this.normal.clone(), this.uv.clone());
|
||||
}
|
||||
|
||||
lerp(v: Vertex, alpha: number)
|
||||
{
|
||||
super.lerp(v, alpha);
|
||||
this.normal.lerp(v.normal, alpha);
|
||||
this.uv.lerp(v.uv, alpha);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class Node
|
||||
{
|
||||
divider: Polygon;
|
||||
back: Node;
|
||||
front: Node;
|
||||
constructor(public polygons: Polygon[] = [])
|
||||
{
|
||||
let front: Polygon[] = [],
|
||||
back: Polygon[] = [];
|
||||
|
||||
this.front = this.back = undefined;
|
||||
|
||||
if (polygons.length === 0) return;
|
||||
|
||||
this.divider = polygons[0].clone();
|
||||
|
||||
for (let i = 0, polygon_count = polygons.length; i < polygon_count; i++)
|
||||
{
|
||||
this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back);
|
||||
}
|
||||
|
||||
if (front.length > 0)
|
||||
{
|
||||
this.front = new Node(front);
|
||||
}
|
||||
|
||||
if (back.length > 0)
|
||||
{
|
||||
this.back = new Node(back);
|
||||
}
|
||||
}
|
||||
|
||||
isConvex(polygons: Polygon[])
|
||||
{
|
||||
for (let i = 0; i < polygons.length; i++)
|
||||
{
|
||||
for (let j = 0; j < polygons.length; j++)
|
||||
{
|
||||
if (i !== j && polygons[i].classifySide(polygons[j]) !== Side.Back)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
build(polygons: Polygon[])
|
||||
{
|
||||
let front: Polygon[] = [],
|
||||
back: Polygon[] = [];
|
||||
|
||||
if (!this.divider)
|
||||
{
|
||||
this.divider = polygons[0].clone();
|
||||
}
|
||||
|
||||
for (let i = 0, polygon_count = polygons.length; i < polygon_count; i++)
|
||||
{
|
||||
this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back);
|
||||
}
|
||||
|
||||
if (front.length > 0)
|
||||
{
|
||||
if (!this.front) this.front = new Node();
|
||||
this.front.build(front);
|
||||
}
|
||||
|
||||
if (back.length > 0)
|
||||
{
|
||||
if (!this.back) this.back = new Node();
|
||||
this.back.build(back);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
clone()
|
||||
{
|
||||
let node = new Node();
|
||||
|
||||
node.divider = this.divider.clone();
|
||||
node.polygons = this.polygons.map(p => p.clone());
|
||||
node.front = this.front && this.front.clone();
|
||||
node.back = this.back && this.back.clone();
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
invert()
|
||||
{
|
||||
for (let p of this.polygons)
|
||||
p.flip();
|
||||
|
||||
this.divider.flip();
|
||||
if (this.front) this.front.invert();
|
||||
if (this.back) this.back.invert();
|
||||
|
||||
let temp = this.front;
|
||||
this.front = this.back;
|
||||
this.back = temp;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
clipPolygons(polygons: Polygon[])
|
||||
{
|
||||
if (!this.divider) return polygons.slice();
|
||||
|
||||
let front: Polygon[] = [];
|
||||
let back: Polygon[] = [];
|
||||
|
||||
for (let polygon of polygons)
|
||||
this.divider.splitPolygon(polygon, 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);
|
||||
}
|
||||
|
||||
clipTo(node: Node)
|
||||
{
|
||||
this.polygons = node.clipPolygons(this.polygons);
|
||||
if (this.front) this.front.clipTo(node);
|
||||
if (this.back) this.back.clipTo(node);
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import { Matrix4 } from "three";
|
||||
import { CSG } from "./CSG";
|
||||
import { Polygon } from "./math/Polygon3";
|
||||
import { Side } from "./math/Side";
|
||||
import { Vector2D } from "./math/Vector2";
|
||||
import { Vector3D } from "./math/Vector3";
|
||||
import { Vertex3D } from "./math/Vertex3";
|
||||
import { Tree } from "./trees";
|
||||
import { canonicalizeCAG } from "./utils/canonicalize";
|
||||
|
||||
/**
|
||||
* Class CAG
|
||||
* Holds a solid area geometry like CSG but 2D.
|
||||
* Each area consists of a number of sides.
|
||||
* Each side is a line between 2 points.
|
||||
*/
|
||||
export class CAG
|
||||
{
|
||||
isCanonicalized: boolean = false;
|
||||
constructor(public sides: Side[] = [])
|
||||
{
|
||||
}
|
||||
flipped()
|
||||
{
|
||||
let newsides = this.sides.map(side => side.flipped());
|
||||
newsides.reverse();
|
||||
return new CAG(newsides);
|
||||
}
|
||||
|
||||
getBounds()
|
||||
{
|
||||
let minpoint: Vector2D;
|
||||
if (this.sides.length === 0)
|
||||
minpoint = new Vector2D(0, 0);
|
||||
else
|
||||
minpoint = this.sides[0].vertex0.pos;
|
||||
let maxpoint = minpoint.clone();
|
||||
this.sides.forEach(side =>
|
||||
{
|
||||
minpoint.min(side.vertex0.pos);
|
||||
minpoint.min(side.vertex1.pos);
|
||||
maxpoint.max(side.vertex0.pos);
|
||||
maxpoint.max(side.vertex1.pos);
|
||||
});
|
||||
return [minpoint, maxpoint];
|
||||
}
|
||||
|
||||
canonicalized(): CAG
|
||||
{
|
||||
if (this.isCanonicalized) return this;
|
||||
|
||||
return canonicalizeCAG(this);
|
||||
}
|
||||
|
||||
extrude(offsetVector: Vector3D): CSG
|
||||
{
|
||||
//bottom
|
||||
let polygons = this.toPolygons(true);
|
||||
//top
|
||||
let moveMtx4 = new Matrix4().setPosition(offsetVector);
|
||||
polygons.push(
|
||||
...polygons.map(poly => poly.flipped().transform(moveMtx4)),
|
||||
...this.toCSGWall(0, offsetVector.z).polygons
|
||||
);
|
||||
|
||||
return new CSG(polygons);
|
||||
}
|
||||
|
||||
private toCSGWall(z0: number, z1: number)
|
||||
{
|
||||
let polygons = this.sides.map(side => side.toPolygon3D(z0, z1));
|
||||
return new CSG(polygons);
|
||||
}
|
||||
|
||||
private toPolygons(bottom = false)
|
||||
{
|
||||
let bounds = this.getBounds();
|
||||
bounds[0].sub(new Vector2D(1, 1));
|
||||
bounds[1].add(new Vector2D(1, 1));
|
||||
let csgShell = this.toCSGWall(-1, 1);
|
||||
let csgPlane = new CSG([
|
||||
new Polygon([
|
||||
new Vertex3D(new Vector3D(bounds[0].x, bounds[0].y, 0)),
|
||||
new Vertex3D(new Vector3D(bounds[1].x, bounds[0].y, 0)),
|
||||
new Vertex3D(new Vector3D(bounds[1].x, bounds[1].y, 0)),
|
||||
new Vertex3D(new Vector3D(bounds[0].x, bounds[1].y, 0))
|
||||
])
|
||||
]);
|
||||
if (bottom)
|
||||
csgPlane = csgPlane.invert();
|
||||
|
||||
//简化代码
|
||||
let a = new Tree(csgPlane.polygons);
|
||||
let b = new Tree(csgShell.polygons);
|
||||
b.invert();
|
||||
a.clipTo(b);
|
||||
|
||||
return a.allPolygons();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { CAG } from "./CAG";
|
||||
import { CSG } from "./CSG";
|
||||
import { Side } from "./math/Side";
|
||||
import { Vector2D } from "./math/Vector2";
|
||||
import { Vertex2D } from "./math/Vertex2";
|
||||
|
||||
// Converts a CSG to a The CSG must consist of polygons with only z coordinates +1 and -1
|
||||
// as constructed by _toCSGWall(-1, 1). This is so we can use the 3D union(), intersect() etc
|
||||
export function fromFakeCSG(csg: CSG)
|
||||
{
|
||||
let sides = csg.polygons
|
||||
.map(p => Side._fromFakePolygon(p))
|
||||
.filter(s => s !== null);
|
||||
return new CAG(sides);
|
||||
}
|
||||
|
||||
|
||||
/** Construct a CAG from a list of points (a polygon).
|
||||
* Like fromPoints() but does not check if the result is a valid polygon.
|
||||
* The points MUST rotate counter clockwise.
|
||||
* The points can define a convex or a concave polygon.
|
||||
* The polygon must not self intersect.
|
||||
*/
|
||||
export function fromPointsNoCheck(points: Vector2D[]): CAG
|
||||
{
|
||||
let sides: Side[] = [];
|
||||
let prevpoint = points[points.length - 1];
|
||||
let prevvertex = new Vertex2D(prevpoint);
|
||||
for (let point of points)
|
||||
{
|
||||
let vertex = new Vertex2D(point);
|
||||
let side = new Side(prevvertex, vertex);
|
||||
sides.push(side);
|
||||
prevvertex = vertex;
|
||||
}
|
||||
return new CAG(sides);
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
import { Matrix4 } from "three";
|
||||
import { IsMirror } from "./math/IsMirrot";
|
||||
import { Plane } from "./math/Plane";
|
||||
import { Polygon } from "./math/Polygon3";
|
||||
import { Vector3D } from "./math/Vector3";
|
||||
import { Vertex3D } from "./math/Vertex3";
|
||||
import { Tree } from "./trees";
|
||||
import { canonicalizeCSG } from "./utils/canonicalize";
|
||||
import { bounds } from "./utils/csgMeasurements";
|
||||
import { reTesselate } from "./utils/retesellate";
|
||||
|
||||
/** Class CSG
|
||||
* Holds a binary space partition tree representing a 3D solid. Two solids can
|
||||
* be combined using the `union()`, `subtract()`, and `intersect()` methods.
|
||||
* @constructor
|
||||
*/
|
||||
export class CSG
|
||||
{
|
||||
isCanonicalized: boolean = false;
|
||||
isRetesselated: boolean = false;
|
||||
cachedBoundingBox: Vector3D[];
|
||||
constructor(public polygons: Polygon[] = [])
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Return a new CSG solid representing the space in either this solid or
|
||||
* in the given solids. Neither this solid nor the given solids are modified.
|
||||
* @param {CSG[]} csg - list of CSG objects
|
||||
* @returns {CSG} new CSG object
|
||||
* @example
|
||||
* let C = A.union(B)
|
||||
* @example
|
||||
* +-------+ +-------+
|
||||
* | | | |
|
||||
* | A | | |
|
||||
* | +--+----+ = | +----+
|
||||
* +----+--+ | +----+ |
|
||||
* | B | | |
|
||||
* | | | |
|
||||
* +-------+ +-------+
|
||||
*/
|
||||
union(csg: CSG | CSG[]): CSG
|
||||
{
|
||||
let csgs: CSG[];
|
||||
if (csg instanceof Array)
|
||||
{
|
||||
csgs = csg.slice(0);
|
||||
csgs.push(this);
|
||||
}
|
||||
else csgs = [this, csg];
|
||||
|
||||
let i: number;
|
||||
// combine csg pairs in a way that forms a balanced binary tree pattern
|
||||
for (i = 1; i < csgs.length; i += 2)
|
||||
{
|
||||
csgs.push(csgs[i - 1].unionSub(csgs[i]));
|
||||
}
|
||||
return csgs[i - 1].reTesselated().canonicalized();
|
||||
}
|
||||
|
||||
unionSub(csg: CSG, retesselate = false, canonicalize = false): CSG
|
||||
{
|
||||
if (!this.mayOverlap(csg))
|
||||
return this.unionForNonIntersecting(csg);
|
||||
|
||||
let a = new Tree(this.polygons);
|
||||
let b = new Tree(csg.polygons);
|
||||
a.clipTo(b);
|
||||
|
||||
// b.clipTo(a, true); // ERROR: this doesn't work
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
|
||||
let newpolygons = [...a.allPolygons(), ...b.allPolygons()];
|
||||
let resultCSG = new CSG(newpolygons);
|
||||
if (retesselate) resultCSG = resultCSG.reTesselated();
|
||||
if (canonicalize) resultCSG = resultCSG.canonicalized();
|
||||
return resultCSG;
|
||||
}
|
||||
|
||||
// Like union, but when we know that the two solids are not intersecting
|
||||
// Do not use if you are not completely sure that the solids do not intersect!
|
||||
unionForNonIntersecting(csg: CSG): CSG
|
||||
{
|
||||
let newpolygons = [...this.polygons, ...csg.polygons];
|
||||
let result = new CSG(newpolygons);
|
||||
result.isCanonicalized = this.isCanonicalized && csg.isCanonicalized;
|
||||
result.isRetesselated = this.isRetesselated && csg.isRetesselated;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new CSG solid representing space in this solid but
|
||||
* not in the given solids. Neither this solid nor the given solids are modified.
|
||||
* @returns new CSG object
|
||||
* @example
|
||||
* let C = A.subtract(B)
|
||||
* @example
|
||||
* +-------+ +-------+
|
||||
* | | | |
|
||||
* | A | | |
|
||||
* | +--+----+ = | +--+
|
||||
* +----+--+ | +----+
|
||||
* | B |
|
||||
* | |
|
||||
* +-------+
|
||||
*/
|
||||
subtract(csg: CSG | CSG[]): CSG
|
||||
{
|
||||
let csgs: CSG[];
|
||||
if (csg instanceof Array)
|
||||
csgs = csg;
|
||||
else
|
||||
csgs = [csg];
|
||||
let result: CSG = this;
|
||||
for (let i = 0; i < csgs.length; i++)
|
||||
{
|
||||
let islast = i === csgs.length - 1;
|
||||
result = result.subtractSub(csgs[i], islast, islast);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
subtractSub(csg: CSG, retesselate = false, canonicalize = false): CSG
|
||||
{
|
||||
let a = new Tree(this.polygons);
|
||||
let b = new Tree(csg.polygons);
|
||||
a.invert();
|
||||
a.clipTo(b);
|
||||
b.clipTo(a, true);
|
||||
a.addPolygons(b.allPolygons());
|
||||
a.invert();
|
||||
let result = new CSG(a.allPolygons());
|
||||
// if (retesselate) result = result.reTesselated();
|
||||
// if (canonicalize) result = result.canonicalized();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new CSG solid representing space in both this solid and
|
||||
* in the given solids. Neither this solid nor the given solids are modified.
|
||||
* let C = A.intersect(B)
|
||||
* @returns new CSG object
|
||||
* @example
|
||||
* +-------+
|
||||
* | |
|
||||
* | A |
|
||||
* | +--+----+ = +--+
|
||||
* +----+--+ | +--+
|
||||
* | B |
|
||||
* | |
|
||||
* +-------+
|
||||
*/
|
||||
intersect(csg: CSG | CSG[]): CSG
|
||||
{
|
||||
let csgs: CSG[];
|
||||
if (csg instanceof Array)
|
||||
csgs = csg;
|
||||
else
|
||||
csgs = [csg];
|
||||
let result: CSG = this;
|
||||
for (let i = 0; i < csgs.length; i++)
|
||||
{
|
||||
let islast = i === csgs.length - 1;
|
||||
result = result.intersectSub(csgs[i], islast, islast);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
intersectSub(csg: CSG, retesselate = false, canonicalize = false): CSG
|
||||
{
|
||||
let a = new Tree(this.polygons);
|
||||
let b = new Tree(csg.polygons);
|
||||
a.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
a.addPolygons(b.allPolygons());
|
||||
a.invert();
|
||||
let result = new CSG(a.allPolygons());
|
||||
if (retesselate) result = result.reTesselated();
|
||||
if (canonicalize) result = result.canonicalized();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new CSG solid with solid and empty space switched.
|
||||
* This solid is not modified.
|
||||
*/
|
||||
invert(): CSG
|
||||
{
|
||||
let flippedpolygons = this.polygons.map(p => p.flipped());
|
||||
return new CSG(flippedpolygons);
|
||||
}
|
||||
|
||||
// Affine transformation of CSG object. Returns a new CSG object
|
||||
transform1(matrix4x4: Matrix4)
|
||||
{
|
||||
let newpolygons = this.polygons.map(p =>
|
||||
{
|
||||
return p.transform(matrix4x4);
|
||||
});
|
||||
let result = new CSG(newpolygons);
|
||||
result.isCanonicalized = this.isCanonicalized;
|
||||
result.isRetesselated = this.isRetesselated;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new CSG solid that is transformed using the given Matrix.
|
||||
* Several matrix transformations can be combined before transforming this solid.
|
||||
* @param {CSG.Matrix4x4} matrix4x4 - matrix to be applied
|
||||
* @returns {CSG} new CSG object
|
||||
* @example
|
||||
* var m = new CSG.Matrix4x4()
|
||||
* m = m.multiply(CSG.Matrix4x4.rotationX(40))
|
||||
* m = m.multiply(CSG.Matrix4x4.translation([-.5, 0, 0]))
|
||||
* let B = A.transform(m)
|
||||
*/
|
||||
transform(matrix4x4: Matrix4): this
|
||||
{
|
||||
let ismirror = IsMirror(matrix4x4);
|
||||
let transformedvertices = {};
|
||||
let transformedplanes = {};
|
||||
let newpolygons = this.polygons.map(p =>
|
||||
{
|
||||
let newplane: Plane;
|
||||
let plane = p.plane;
|
||||
let planetag = plane.getTag();
|
||||
if (planetag in transformedplanes)
|
||||
{
|
||||
newplane = transformedplanes[planetag];
|
||||
} else
|
||||
{
|
||||
newplane = plane.transform(matrix4x4);
|
||||
transformedplanes[planetag] = newplane;
|
||||
}
|
||||
let newvertices = p.vertices.map(v =>
|
||||
{
|
||||
let newvertex: Vertex3D;
|
||||
let vertextag = v.getTag();
|
||||
if (vertextag in transformedvertices)
|
||||
{
|
||||
newvertex = transformedvertices[vertextag];
|
||||
}
|
||||
else
|
||||
{
|
||||
newvertex = v.transform(matrix4x4);
|
||||
transformedvertices[vertextag] = newvertex;
|
||||
}
|
||||
return newvertex;
|
||||
});
|
||||
if (ismirror) newvertices.reverse();
|
||||
return new Polygon(newvertices, newplane);
|
||||
});
|
||||
let result = new CSG(newpolygons);
|
||||
result.isRetesselated = this.isRetesselated;
|
||||
result.isCanonicalized = this.isCanonicalized;
|
||||
return result as this;
|
||||
}
|
||||
canonicalized()
|
||||
{
|
||||
if (this.isCanonicalized) return this;
|
||||
return canonicalizeCSG(this);
|
||||
}
|
||||
reTesselated()
|
||||
{
|
||||
if (this.isRetesselated) return this;
|
||||
return reTesselate(this);
|
||||
}
|
||||
|
||||
//如果两个实体有可能重叠,返回true
|
||||
mayOverlap(csg: CSG): boolean
|
||||
{
|
||||
if (this.polygons.length === 0 || csg.polygons.length === 0)
|
||||
return false;
|
||||
|
||||
let mybounds = bounds(this);
|
||||
let otherbounds = bounds(csg);
|
||||
if (mybounds[1].x < otherbounds[0].x) return false;
|
||||
if (mybounds[0].x > otherbounds[1].x) return false;
|
||||
if (mybounds[1].y < otherbounds[0].y) return false;
|
||||
if (mybounds[0].y > otherbounds[1].y) return false;
|
||||
if (mybounds[1].z < otherbounds[0].z) return false;
|
||||
if (mybounds[0].z > otherbounds[1].z) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
toTriangles(): Polygon[]
|
||||
{
|
||||
let polygons: Polygon[] = [];
|
||||
for (let poly of this.polygons)
|
||||
{
|
||||
let firstVertex = poly.vertices[0];
|
||||
for (let i = poly.vertices.length - 3; i >= 0; i--)
|
||||
{
|
||||
polygons.push(
|
||||
new Polygon(
|
||||
[
|
||||
firstVertex,
|
||||
poly.vertices[i + 1],
|
||||
poly.vertices[i + 2]
|
||||
],
|
||||
poly.plane
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
|
||||
// //////////////////////////////
|
||||
// ## class fuzzyFactory
|
||||
// This class acts as a factory for objects. We can search for an object with approximately
|
||||
// the desired properties (say a rectangle with width 2 and height 1)
|
||||
// The lookupOrCreate() method looks for an existing object (for example it may find an existing rectangle
|
||||
// with width 2.0001 and height 0.999. If no object is found, the user supplied callback is
|
||||
// called, which should generate a new object. The new object is inserted into the database
|
||||
// so it can be found by future lookupOrCreate() calls.
|
||||
// Constructor:
|
||||
// numdimensions: the number of parameters for each object
|
||||
// for example for a 2D rectangle this would be 2
|
||||
// tolerance: The maximum difference for each parameter allowed to be considered a match
|
||||
export class FuzzyFactory
|
||||
{
|
||||
lookuptable: {};
|
||||
multiplier: number;
|
||||
constructor(numdimensions: number, tolerance: number)
|
||||
{
|
||||
this.lookuptable = {};
|
||||
this.multiplier = 1.0 / tolerance;
|
||||
}
|
||||
|
||||
// let obj = f.lookupOrCreate([el1, el2, el3], function(elements) {/* create the new object */});
|
||||
// Performs a fuzzy lookup of the object with the specified elements.
|
||||
// If found, returns the existing object
|
||||
// If not found, calls the supplied callback function which should create a new object with
|
||||
// the specified properties. This object is inserted in the lookup database.
|
||||
lookupOrCreate<T>(els: number[], object: T): T
|
||||
{
|
||||
let hash = "";
|
||||
let multiplier = this.multiplier;
|
||||
els.forEach(el =>
|
||||
{
|
||||
let valueQuantized = Math.round(el * multiplier);
|
||||
hash += valueQuantized + "/";
|
||||
});
|
||||
if (hash in this.lookuptable) return this.lookuptable[hash];
|
||||
else
|
||||
{
|
||||
let hashparts = els.map(el =>
|
||||
{
|
||||
let q0 = Math.floor(el * multiplier);
|
||||
let q1 = q0 + 1;
|
||||
return ["" + q0 + "/", "" + q1 + "/"];
|
||||
});
|
||||
let numelements = els.length;
|
||||
let numhashes = 1 << numelements;
|
||||
for (let hashmask = 0; hashmask < numhashes; ++hashmask)
|
||||
{
|
||||
let hashmaskShifted = hashmask;
|
||||
hash = "";
|
||||
hashparts.forEach(hashpart =>
|
||||
{
|
||||
hash += hashpart[hashmaskShifted & 1];
|
||||
hashmaskShifted >>= 1;
|
||||
});
|
||||
this.lookuptable[hash] = object;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { EPS } from "./constants";
|
||||
import { FuzzyFactory } from "./FuzzyFactory";
|
||||
import { Side } from "./math/Side";
|
||||
import { Vertex2D } from "./math/Vertex2";
|
||||
|
||||
export class FuzzyCAGFactory
|
||||
{
|
||||
vertexfactory = new FuzzyFactory(2, EPS);
|
||||
constructor() { }
|
||||
|
||||
getVertex(sourcevertex: Vertex2D)
|
||||
{
|
||||
let elements = [sourcevertex.pos.x, sourcevertex.pos.y];
|
||||
let result = this.vertexfactory.lookupOrCreate(elements, sourcevertex);
|
||||
return result;
|
||||
}
|
||||
|
||||
getSide(sourceside: Side)
|
||||
{
|
||||
let vertex0 = this.getVertex(sourceside.vertex0);
|
||||
let vertex1 = this.getVertex(sourceside.vertex1);
|
||||
return new Side(vertex0, vertex1);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import { FuzzyFactory } from "./FuzzyFactory";
|
||||
import { EPS } from "./constants";
|
||||
import { Polygon } from "./math/Polygon3";
|
||||
import { Plane } from "./math/Plane";
|
||||
import { Vertex3D } from "./math/Vertex3";
|
||||
|
||||
export class FuzzyCSGFactory
|
||||
{
|
||||
vertexfactory = new FuzzyFactory(3, EPS);
|
||||
planefactory = new FuzzyFactory(4, EPS);
|
||||
constructor() { }
|
||||
|
||||
getVertex(sourcevertex: Vertex3D): Vertex3D
|
||||
{
|
||||
let elements = [sourcevertex.pos.x, sourcevertex.pos.y, sourcevertex.pos.z];
|
||||
let result = this.vertexfactory.lookupOrCreate(elements, sourcevertex);
|
||||
return result;
|
||||
}
|
||||
|
||||
getPlane(sourceplane: Plane): Plane
|
||||
{
|
||||
let elements: number[] = [sourceplane.normal.x, sourceplane.normal.y, sourceplane.normal.z, sourceplane.w];
|
||||
let result = this.planefactory.lookupOrCreate(elements, sourceplane);
|
||||
return result;
|
||||
}
|
||||
|
||||
getPolygon(sourcePolygon: Polygon, outputPolygon = sourcePolygon): Polygon
|
||||
{
|
||||
let newPlane = this.getPlane(sourcePolygon.plane);
|
||||
let newVertices = sourcePolygon.vertices.map(vertex => this.getVertex(vertex));
|
||||
// two vertices that were originally very close may now have become
|
||||
// truly identical (referring to the same Vertex object).
|
||||
// Remove duplicate vertices:
|
||||
let newVerticesDedup: Vertex3D[] = [];//新的顶点列表(已过滤重复)
|
||||
if (newVertices.length > 0)
|
||||
{
|
||||
let prevVertexTag = newVertices[newVertices.length - 1].getTag();
|
||||
for (let vertex of newVertices)
|
||||
{
|
||||
let vertextag = vertex.getTag();
|
||||
if (vertextag !== prevVertexTag)
|
||||
newVerticesDedup.push(vertex);
|
||||
prevVertexTag = vertextag;
|
||||
}
|
||||
}
|
||||
// If it's degenerate, remove all vertices:
|
||||
if (newVerticesDedup.length < 3)
|
||||
newVerticesDedup = [];
|
||||
|
||||
outputPolygon.vertices = newVertices;
|
||||
outputPolygon.plane = newPlane;
|
||||
return outputPolygon;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import { Geometry, Face3, Vector3, Vector2 } from "three";
|
||||
import { CSG } from "./CSG";
|
||||
import { Polygon } from "./math/Polygon3";
|
||||
import { Vertex3D } from "./math/Vertex3";
|
||||
import { Vector2D } from "./math/Vector2";
|
||||
import { Vector3D } from "./math/Vector3";
|
||||
|
||||
export function Geometry2CSG(geometry: Geometry): CSG
|
||||
{
|
||||
let polygons: Polygon[] = [];
|
||||
for (let i = 0; i < geometry.faces.length; i++)
|
||||
{
|
||||
let face = geometry.faces[i];
|
||||
let faceVertexUvs = geometry.faceVertexUvs[0][i];
|
||||
let vertices: Vertex3D[] = [];
|
||||
|
||||
if (face instanceof Face3)
|
||||
{
|
||||
let uv = faceVertexUvs ? faceVertexUvs[0].clone() : null;
|
||||
let vertex1 = new Vertex3D(Vector3ToVector3D(geometry.vertices[face.a]), new Vector2D(uv.x, uv.y));
|
||||
vertices.push(vertex1);
|
||||
|
||||
uv = faceVertexUvs ? faceVertexUvs[1].clone() : null;
|
||||
let vertex2 = new Vertex3D(Vector3ToVector3D(geometry.vertices[face.b]), new Vector2D(uv.x, uv.y));
|
||||
vertices.push(vertex2);
|
||||
|
||||
uv = faceVertexUvs ? faceVertexUvs[2].clone() : null;
|
||||
let vertex3 = new Vertex3D(Vector3ToVector3D(geometry.vertices[face.c]), new Vector2D(uv.x, uv.y));
|
||||
vertices.push(vertex3);
|
||||
}
|
||||
|
||||
let polygon = new Polygon(vertices);
|
||||
if (!isNaN(polygon.plane.w))
|
||||
polygons.push(polygon);
|
||||
}
|
||||
|
||||
return new CSG(polygons);
|
||||
}
|
||||
|
||||
export function CSG2Geometry(csg: CSG): Geometry
|
||||
{
|
||||
let geo = new Geometry;
|
||||
let uvs: Vector2[][] = geo.faceVertexUvs[0];
|
||||
|
||||
for (let poly of csg.polygons)
|
||||
{
|
||||
for (let v of poly.vertices)
|
||||
{
|
||||
v.tag = geo.vertices.length;
|
||||
geo.vertices.push(Vector3DToVector3(v.pos));
|
||||
}
|
||||
let normal = Vector3DToVector3(poly.plane.normal);
|
||||
|
||||
let firstVertex = poly.vertices[0];
|
||||
|
||||
for (let i = poly.vertices.length - 3; i >= 0; i--)
|
||||
{
|
||||
let [a, b, c] = [
|
||||
firstVertex.tag,
|
||||
poly.vertices[i + 1].tag,
|
||||
poly.vertices[i + 2].tag
|
||||
];
|
||||
let f = new Face3(a, b, c, normal);
|
||||
|
||||
geo.faces.push(f);
|
||||
uvs.push([
|
||||
Vector2DToVector2(firstVertex.uv),
|
||||
Vector2DToVector2(poly.vertices[i + 1].uv),
|
||||
Vector2DToVector2(poly.vertices[i + 2].uv)
|
||||
]);
|
||||
}
|
||||
}
|
||||
return geo;
|
||||
}
|
||||
|
||||
function Vector3ToVector3D(v: Vector3): Vector3D
|
||||
{
|
||||
return new Vector3D(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
function Vector3DToVector3(v: Vector3D): Vector3
|
||||
{
|
||||
return new Vector3(v.x, v.y, v.z);
|
||||
}
|
||||
function Vector2DToVector2(v: Vector2D): Vector2
|
||||
{
|
||||
return new Vector2(v.x, v.y);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
export const _CSGDEBUG = false;
|
||||
|
||||
/** Epsilon used during determination of near zero distances.
|
||||
* @default
|
||||
*/
|
||||
export const EPS = 1e-5;
|
||||
|
||||
// Tag factory: we can request a unique tag through CSG.getTag()
|
||||
export let staticTag = 1;
|
||||
export const getTag = () => staticTag++;
|
@ -0,0 +1,15 @@
|
||||
import { Matrix4, Vector3 } from "three";
|
||||
|
||||
export function IsMirror(mtx: Matrix4)
|
||||
{
|
||||
let x = new Vector3();
|
||||
let y = new Vector3();
|
||||
let z = new Vector3();
|
||||
mtx.extractBasis(x, y, z);
|
||||
|
||||
// for a true orthogonal, non-mirrored base, u.cross(v) == w
|
||||
// If they have an opposite direction then we are mirroring
|
||||
const mirrorvalue = x.cross(y).dot(z);
|
||||
const ismirror = (mirrorvalue < 0);
|
||||
return ismirror;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Vector2D } from "./Vector2";
|
||||
|
||||
/** class Line2D
|
||||
* Represents a directional line in 2D space
|
||||
* A line is parametrized by its normal vector (perpendicular to the line, rotated 90 degrees counter clockwise)
|
||||
* and w. The line passes through the point <normal>.times(w).
|
||||
* Equation: p is on line if normal.dot(p)==w
|
||||
*/
|
||||
export class Line2D
|
||||
{
|
||||
normal: Vector2D;
|
||||
w: number;
|
||||
constructor(normal: Vector2D, w: number)
|
||||
{
|
||||
this.normal = normal.clone();
|
||||
let l = this.normal.length();
|
||||
w *= l;
|
||||
this.normal.normalize();
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
direction()
|
||||
{
|
||||
return this.normal;
|
||||
}
|
||||
static fromPoints(p1: Vector2D, p2: Vector2D)
|
||||
{
|
||||
let direction = p2.clone().sub(p1);
|
||||
let normal = direction
|
||||
.normal()
|
||||
.negate()
|
||||
.normalize();
|
||||
let w = p1.dot(normal);
|
||||
return new Line2D(normal, w);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import { Plane } from "./Plane";
|
||||
import { Vector2D } from "./Vector2";
|
||||
import { Vector3D } from "./Vector3";
|
||||
|
||||
/** class OrthoNormalBasis
|
||||
* Reprojects points on a 3D plane onto a 2D plane
|
||||
* or from a 2D plane back onto the 3D plane
|
||||
*/
|
||||
|
||||
export class OrthoNormalBasis
|
||||
{
|
||||
v: Vector3D;
|
||||
u: Vector3D;
|
||||
plane: Plane;
|
||||
planeorigin: Vector3D;
|
||||
constructor(plane: Plane, rightVector: Vector3D = plane.normal.randomNonParallelVector())
|
||||
{
|
||||
this.v = plane.normal.clone().cross(rightVector).normalize();
|
||||
this.u = this.v.clone().cross(plane.normal);
|
||||
this.plane = plane;
|
||||
this.planeorigin = plane.normal.clone().multiplyScalar(plane.w);
|
||||
}
|
||||
to2D(vec3: Vector3D)
|
||||
{
|
||||
return new Vector2D(vec3.dot(this.u), vec3.dot(this.v));
|
||||
}
|
||||
|
||||
to3D(vec2: Vector2D)
|
||||
{
|
||||
return this.planeorigin.clone()
|
||||
.add(this.u.clone().multiplyScalar(vec2.x))
|
||||
.add(this.v.clone().multiplyScalar(vec2.y));
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import { Matrix4 } from "three";
|
||||
import { getTag } from "../constants";
|
||||
import { IsMirror } from "./IsMirrot";
|
||||
import { Vector3D } from "./Vector3";
|
||||
import { Vertex3D } from "./Vertex3";
|
||||
|
||||
// # class Plane
|
||||
// Represents a plane in 3D space.
|
||||
export class Plane
|
||||
{
|
||||
normal: Vector3D;
|
||||
w: number;
|
||||
tag: number;
|
||||
constructor(normal: Vector3D, w: number)
|
||||
{
|
||||
this.normal = normal;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
flipped()
|
||||
{
|
||||
return new Plane(this.normal.clone().negate(), -this.w);
|
||||
}
|
||||
|
||||
getTag()
|
||||
{
|
||||
if (!this.tag)
|
||||
this.tag = getTag();
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
equals(plane: Plane)
|
||||
{
|
||||
return this.normal.equals(plane.normal) && this.w === plane.w;
|
||||
}
|
||||
|
||||
transform(matrix4x4: Matrix4)
|
||||
{
|
||||
// get two vectors in the plane:
|
||||
let r = this.normal.randomNonParallelVector();
|
||||
let u = this.normal.clone().cross(r);
|
||||
let v = this.normal.clone().cross(u);
|
||||
// get 3 points in the plane:
|
||||
let point1 = this.normal.clone().multiplyScalar(this.w);
|
||||
let point2 = u.add(point1);
|
||||
let point3 = v.add(point1);
|
||||
// transform the points:
|
||||
point1.applyMatrix4(matrix4x4);
|
||||
point2.applyMatrix4(matrix4x4);
|
||||
point3.applyMatrix4(matrix4x4);
|
||||
// and create a new plane from the transformed points:
|
||||
let newplane = Plane.fromVector3Ds(point1, point2, point3);
|
||||
if (IsMirror(matrix4x4))
|
||||
{
|
||||
// the transform is mirroring
|
||||
// We should mirror the plane:
|
||||
newplane = newplane.flipped();
|
||||
}
|
||||
return newplane;
|
||||
}
|
||||
|
||||
splitLineBetweenPoints(p1: Vertex3D, p2: Vertex3D): Vertex3D
|
||||
{
|
||||
let direction = p2.pos.clone().sub(p1.pos);
|
||||
let labda = (this.w - this.normal.dot(p1.pos)) / this.normal.dot(direction);
|
||||
if (isNaN(labda)) labda = 0;
|
||||
if (labda > 1) labda = 1;
|
||||
if (labda < 0) labda = 0;
|
||||
let pos = p1.pos.clone().add(direction.multiplyScalar(labda));
|
||||
let uv = p1.uv.clone().lerp(p2.uv, labda);
|
||||
return new Vertex3D(pos, uv);
|
||||
}
|
||||
|
||||
static fromVector3Ds(a: Vector3D, b: Vector3D, c: Vector3D)
|
||||
{
|
||||
let n = b.clone()
|
||||
.sub(a)
|
||||
.cross(c.clone().sub(a))
|
||||
.normalize();
|
||||
return new Plane(n, n.dot(a));
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { Polygon } from "./Polygon3";
|
||||
import { Vector2D } from "./Vector2";
|
||||
import { Vertex2D } from "./Vertex2";
|
||||
import { Vertex3D } from "./Vertex3";
|
||||
|
||||
export class Side
|
||||
{
|
||||
constructor(public vertex0: Vertex2D, public vertex1: Vertex2D) { }
|
||||
toPolygon3D(z0: number, z1: number)
|
||||
{
|
||||
// console.log(this.vertex0.pos)
|
||||
const vertices = [
|
||||
new Vertex3D(this.vertex0.pos.toVector3D(z0)),
|
||||
new Vertex3D(this.vertex1.pos.toVector3D(z0)),
|
||||
new Vertex3D(this.vertex1.pos.toVector3D(z1)),
|
||||
new Vertex3D(this.vertex0.pos.toVector3D(z1))
|
||||
];
|
||||
return new Polygon(vertices);
|
||||
}
|
||||
|
||||
flipped()
|
||||
{
|
||||
return new Side(this.vertex1, this.vertex0);
|
||||
}
|
||||
|
||||
length()
|
||||
{
|
||||
return this.vertex0.pos.distanceTo(this.vertex1.pos);
|
||||
}
|
||||
|
||||
//wall polygon 展平成side
|
||||
static _fromFakePolygon(polygon: Polygon): Side
|
||||
{
|
||||
// this can happen based on union, seems to be residuals -
|
||||
// return null and handle in caller
|
||||
if (polygon.vertices.length < 4)
|
||||
return null;
|
||||
const vert1Indices: number[] = [];
|
||||
const pts2d = polygon.vertices
|
||||
.filter((v, i) =>
|
||||
{
|
||||
if (v.pos.z > 0)
|
||||
{
|
||||
vert1Indices.push(i);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(v => new Vector2D(v.pos.x, v.pos.y));
|
||||
if (pts2d.length !== 2)
|
||||
{
|
||||
throw new Error(
|
||||
"Assertion failed: _fromFakePolygon: not enough points found"
|
||||
);
|
||||
}
|
||||
const d = vert1Indices[1] - vert1Indices[0];
|
||||
if (d === 1 || d === 3)
|
||||
{
|
||||
if (d === 1)
|
||||
pts2d.reverse();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error(
|
||||
"Assertion failed: _fromFakePolygon: unknown index ordering"
|
||||
);
|
||||
}
|
||||
const result = new Side(new Vertex2D(pts2d[0]), new Vertex2D(pts2d[1]));
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { Vector3D } from "./Vector3";
|
||||
import { Vector2 } from "three";
|
||||
|
||||
export class Vector2D extends Vector2
|
||||
{
|
||||
// extend to a 3D vector by adding a z coordinate:
|
||||
toVector3D(z: number)
|
||||
{
|
||||
return new Vector3D(this.x, this.y, z);
|
||||
}
|
||||
clone()
|
||||
{
|
||||
return new Vector2D(this.x, this.y);
|
||||
}
|
||||
// returns the vector rotated by 90 degrees clockwise
|
||||
normal()
|
||||
{
|
||||
return new Vector2D(this.y, -this.x);
|
||||
}
|
||||
|
||||
cross(a: Vector2)
|
||||
{
|
||||
return this.x * a.y - this.y * a.x;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { Vector3 } from "three";
|
||||
/** Class Vector3D
|
||||
* Represents a 3D vector with X, Y, Z coordinates.
|
||||
*/
|
||||
export class Vector3D extends Vector3
|
||||
{
|
||||
clone()
|
||||
{
|
||||
return new Vector3D(this.x, this.y, this.z);
|
||||
}
|
||||
// find a vector that is somewhat perpendicular to this one
|
||||
randomNonParallelVector()
|
||||
{
|
||||
let x = Math.abs(this.x);
|
||||
let y = Math.abs(this.y);
|
||||
let z = Math.abs(this.z);
|
||||
|
||||
if (x <= y && x <= z)
|
||||
return new Vector3D(1, 0, 0);
|
||||
else if (y <= x && y <= z)
|
||||
return new Vector3D(0, 1, 0);
|
||||
else
|
||||
return new Vector3D(0, 0, 1);
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return (
|
||||
"(" +
|
||||
this.x.toFixed(5) +
|
||||
", " +
|
||||
this.y.toFixed(5) +
|
||||
", " +
|
||||
this.z.toFixed(5) +
|
||||
")"
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { getTag } from "../constants";
|
||||
import { Vector2D } from "./Vector2";
|
||||
|
||||
export class Vertex2D
|
||||
{
|
||||
tag: any;
|
||||
pos: Vector2D;
|
||||
constructor(pos: Vector2D)
|
||||
{
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return "(" + this.pos.x.toFixed(5) + "," + this.pos.y.toFixed(5) + ")";
|
||||
}
|
||||
|
||||
getTag()
|
||||
{
|
||||
let result = this.tag;
|
||||
if (!result)
|
||||
{
|
||||
result = getTag();
|
||||
this.tag = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { getTag } from "../constants";
|
||||
import { Vector2D } from "./Vector2";
|
||||
import { Vector3D } from "./Vector3";
|
||||
import { Matrix4 } from "three";
|
||||
|
||||
// # 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
|
||||
// `flipped()`, and `interpolate()` methods that behave analogous to the ones
|
||||
// FIXME: And a lot MORE (see plane.fromVector3Ds for ex) ! This is fragile code
|
||||
// defined by `Vertex`.
|
||||
export class Vertex3D
|
||||
{
|
||||
tag: number;
|
||||
constructor(public pos: Vector3D, public uv = new Vector2D()) { }
|
||||
|
||||
// Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the
|
||||
// orientation of a polygon is flipped.
|
||||
flipped()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
getTag()
|
||||
{
|
||||
let result = this.tag;
|
||||
if (!result)
|
||||
{
|
||||
result = getTag();
|
||||
this.tag = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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: Vertex3D, t: number)
|
||||
{
|
||||
let pos = this.pos.clone().lerp(other.pos, t);
|
||||
let uv = this.uv.clone().lerp(other.uv, t);
|
||||
return new Vertex3D(pos, uv);
|
||||
}
|
||||
|
||||
// Affine transformation of vertex. Returns a new Vertex
|
||||
|
||||
transform(matrix4x4: Matrix4)
|
||||
{
|
||||
const newpos = this.pos.clone().applyMatrix4(matrix4x4);
|
||||
return new Vertex3D(newpos, this.uv);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { solve2Linear } from "../utils";
|
||||
import { EPS } from "../constants";
|
||||
import { Vector2D } from "./Vector2";
|
||||
|
||||
// see if the line between p0start and p0end intersects with the line between p1start and p1end
|
||||
// returns true if the lines strictly intersect, the end points are not counted!
|
||||
export function linesIntersect(p0start: Vector2D, p0end: Vector2D, p1start: Vector2D, p1end: Vector2D)
|
||||
{
|
||||
if (p0end.equals(p1start) || p1end.equals(p0start))
|
||||
{
|
||||
let d = p1end
|
||||
.sub(p1start)
|
||||
.normalize()
|
||||
.add(p0end.sub(p0start).normalize())
|
||||
.length();
|
||||
if (d < EPS) return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
let d0 = p0end.sub(p0start);
|
||||
let d1 = p1end.sub(p1start);
|
||||
// FIXME These epsilons need review and testing
|
||||
if (Math.abs(d0.cross(d1)) < 1e-9)
|
||||
return false; // lines are parallel
|
||||
let alphas = solve2Linear(-d0.x, d1.x, -d0.y, d1.y, p0start.x - p1start.x, p0start.y - p1start.y);
|
||||
if (
|
||||
alphas[0] > 1e-6 &&
|
||||
alphas[0] < 0.999999 &&
|
||||
alphas[1] > 1e-5 &&
|
||||
alphas[1] < 0.999999
|
||||
)
|
||||
return true;
|
||||
// if( (alphas[0] >= 0) && (alphas[0] <= 1) && (alphas[1] >= 0) && (alphas[1] <= 1) ) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
@ -0,0 +1,61 @@
|
||||
import { Vector2D } from "./math/Vector2";
|
||||
|
||||
export function fnNumberSort(a, b)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
|
||||
export const solve2Linear = function (a: number, b: number, c: number, d: number, u: number, v: number)
|
||||
{
|
||||
let det = a * d - b * c;
|
||||
let invdet = 1.0 / det;
|
||||
let x = u * d - b * v;
|
||||
let y = -u * c + a * v;
|
||||
x *= invdet;
|
||||
y *= invdet;
|
||||
return [x, y];
|
||||
};
|
||||
|
||||
export function insertSorted<T>(array: T[], element: T, comparefunc: (a: T, b: T) => number)
|
||||
{
|
||||
let leftbound = 0;
|
||||
let rightbound = array.length;
|
||||
while (rightbound > leftbound)
|
||||
{
|
||||
let testindex = Math.floor((leftbound + rightbound) / 2);
|
||||
let testelement = array[testindex];
|
||||
let compareresult = comparefunc(element, testelement);
|
||||
if (compareresult > 0)
|
||||
// element > testelement
|
||||
leftbound = testindex + 1;
|
||||
else
|
||||
rightbound = testindex;
|
||||
}
|
||||
array.splice(leftbound, 0, element);
|
||||
}
|
||||
|
||||
// Get the x coordinate of a point with a certain y coordinate, interpolated between two
|
||||
// points (CSG.Vector2D).
|
||||
// Interpolation is robust even if the points have the same y coordinate
|
||||
export function interpolateBetween2DPointsForY(point1: Vector2D, point2: Vector2D, y: number)
|
||||
{
|
||||
let f1 = y - point1.y;
|
||||
let f2 = point2.y - point1.y;
|
||||
if (f2 < 0)
|
||||
{
|
||||
f1 = -f1;
|
||||
f2 = -f2;
|
||||
}
|
||||
let t: number;
|
||||
if (f1 <= 0)
|
||||
t = 0.0;
|
||||
else if (f1 >= f2)
|
||||
t = 1.0;
|
||||
else if (f2 < 1e-10)
|
||||
// FIXME Should this be CSG.EPS?
|
||||
t = 0.5;
|
||||
else
|
||||
t = f1 / f2;
|
||||
let result = point1.x + t * (point2.x - point1.x);
|
||||
return result;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { CAG } from "../CAG";
|
||||
import { linesIntersect } from "../math/lineUtils";
|
||||
|
||||
export function isSelfIntersecting(cag: CAG, debug = false)
|
||||
{
|
||||
let numsides = cag.sides.length;
|
||||
for (let i = 0; i < numsides; i++)
|
||||
{
|
||||
let side0 = cag.sides[i];
|
||||
for (let ii = i + 1; ii < numsides; ii++)
|
||||
{
|
||||
let side1 = cag.sides[ii];
|
||||
if (
|
||||
linesIntersect(side0.vertex0.pos, side0.vertex1.pos,
|
||||
side1.vertex0.pos, side1.vertex1.pos)
|
||||
)
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
console.log("side " + i + ": " + side0);
|
||||
console.log("side " + ii + ": " + side1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
import { FuzzyCSGFactory } from "../FuzzyFactory3d";
|
||||
import { FuzzyCAGFactory } from "../FuzzyFactory2d";
|
||||
import { CSG } from "../CSG";
|
||||
import { CAG } from "../CAG";
|
||||
import { EPS } from "../constants";
|
||||
import { Polygon } from "../math/Polygon3";
|
||||
|
||||
/**
|
||||
* Returns a cannoicalized version of the input csg : ie every very close
|
||||
* points get deduplicated
|
||||
*
|
||||
* 返回删除重复点的csg,重复点将被合并
|
||||
*/
|
||||
export function canonicalizeCSG(csg: CSG): CSG
|
||||
{
|
||||
const factory = new FuzzyCSGFactory();
|
||||
let result = CSGFromCSGFuzzyFactory(factory, csg);
|
||||
result.isCanonicalized = true;
|
||||
result.isRetesselated = csg.isRetesselated;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function canonicalizeCAG(cag: CAG)
|
||||
{
|
||||
let factory = new FuzzyCAGFactory();
|
||||
let result = CAGFromCAGFuzzyFactory(factory, cag);
|
||||
result.isCanonicalized = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function CSGFromCSGFuzzyFactory(factory: FuzzyCSGFactory, sourcecsg: CSG)
|
||||
{
|
||||
let newpolygons: Polygon[] = sourcecsg.polygons.filter(poly =>
|
||||
{
|
||||
return factory.getPolygon(poly).vertices.length >= 3;
|
||||
});
|
||||
return new CSG(newpolygons);
|
||||
}
|
||||
|
||||
function CAGFromCAGFuzzyFactory(factory: FuzzyCAGFactory, sourcecag: CAG)
|
||||
{
|
||||
let newsides = sourcecag.sides
|
||||
.map(side => factory.getSide(side))
|
||||
// remove bad sides (mostly a user input issue)
|
||||
.filter((side) => side.length() > EPS);
|
||||
return new CAG(newsides);
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import { Vector3D } from "../math/Vector3";
|
||||
import { CSG } from "../CSG";
|
||||
/**
|
||||
* Returns an array of Vector3D, providing minimum coordinates and maximum coordinates
|
||||
* of this solid.
|
||||
* @example
|
||||
* let bounds = A.getBounds()
|
||||
* let minX = bounds[0].x
|
||||
*/
|
||||
export function bounds(csg: CSG): Vector3D[]
|
||||
{
|
||||
if (!csg.cachedBoundingBox)
|
||||
{
|
||||
let minpoint: Vector3D;
|
||||
let maxpoint: Vector3D;
|
||||
let polygons = csg.polygons;
|
||||
let numpolygons = polygons.length;
|
||||
for (let i = 0; i < numpolygons; i++)
|
||||
{
|
||||
let polygon = polygons[i];
|
||||
let bounds = polygon.boundingBox();
|
||||
if (i === 0)
|
||||
{
|
||||
minpoint = bounds[0].clone();
|
||||
maxpoint = bounds[1].clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
minpoint.min(bounds[0]);
|
||||
maxpoint.max(bounds[1]);
|
||||
}
|
||||
}
|
||||
// FIXME: not ideal, we are mutating the input, we need to move some of it out
|
||||
csg.cachedBoundingBox = [minpoint, maxpoint];
|
||||
}
|
||||
return csg.cachedBoundingBox;
|
||||
};
|
@ -0,0 +1,41 @@
|
||||
import { FuzzyCSGFactory } from "../FuzzyFactory3d";
|
||||
import { reTesselateCoplanarPolygons } from "../math/reTesselateCoplanarPolygons";
|
||||
import { CSG } from "../CSG";
|
||||
import { Polygon } from "../math/Polygon3";
|
||||
|
||||
export function reTesselate(csg: CSG): CSG
|
||||
{
|
||||
if (csg.isRetesselated) return csg;
|
||||
|
||||
let polygonsPerPlane: { [key: number]: Polygon[] } = {};
|
||||
let isCanonicalized = csg.isCanonicalized;
|
||||
let fuzzyfactory = new FuzzyCSGFactory();
|
||||
|
||||
for (let polygon of csg.polygons)
|
||||
{
|
||||
let plane = polygon.plane;
|
||||
if (!isCanonicalized)
|
||||
{
|
||||
// in order to identify polygons having the same plane, we need to canonicalize the planes
|
||||
// We don't have to do a full canonizalization (including vertices), to save time only do the planes and the shared data:
|
||||
plane = fuzzyfactory.getPlane(plane);
|
||||
}
|
||||
let tag = plane.getTag();
|
||||
if (!(tag in polygonsPerPlane)) polygonsPerPlane[tag] = [polygon];
|
||||
else polygonsPerPlane[tag].push(polygon);
|
||||
}
|
||||
|
||||
let destpolygons: Polygon[] = [];
|
||||
for (let planetag in polygonsPerPlane)
|
||||
{
|
||||
let sourcepolygons = polygonsPerPlane[planetag];
|
||||
if (sourcepolygons.length < 2)
|
||||
destpolygons.push(...sourcepolygons);
|
||||
else
|
||||
reTesselateCoplanarPolygons(sourcepolygons, destpolygons);
|
||||
}
|
||||
let resultCSG = new CSG(destpolygons);
|
||||
resultCSG.isRetesselated = true;
|
||||
// result = result.canonicalized();
|
||||
return resultCSG;
|
||||
};
|
Loading…
Reference in new issue