fix #IKXMP !295 新的CSG实现

pull/295/MERGE
ChenX 5 years ago
parent a96d358d0f
commit c6f01897ab

@ -1,15 +1,17 @@
// //
{ {
"typescript.tsdk": "node_modules\\typescript\\lib", "typescript.tsdk": "node_modules\\typescript\\lib",
// //
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.formatOnPaste": true, "editor.formatOnPaste": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
// "editor.insertSpaces": true,
"typescript.format.placeOpenBraceOnNewLineForFunctions": true, "editor.detectIndentation": false,
"typescript.format.placeOpenBraceOnNewLineForControlBlocks": true, //
"typescript.referencesCodeLens.enabled": true, "typescript.format.placeOpenBraceOnNewLineForFunctions": true,
"javascript.format.placeOpenBraceOnNewLineForFunctions": true, "typescript.format.placeOpenBraceOnNewLineForControlBlocks": true,
"javascript.format.enable": true, "typescript.referencesCodeLens.enabled": true,
"files.insertFinalNewline": true, "javascript.format.placeOpenBraceOnNewLineForFunctions": true,
"javascript.format.enable": true,
"files.insertFinalNewline": true,
} }

@ -17,5 +17,5 @@ test('EdgeGeometry生成', () =>
let geo = line.geometry; let geo = line.geometry;
//@ts-ignore //@ts-ignore
expect(geo.attributes.position.length).toBe(240); expect(geo.attributes.position.length).toBe(246);
}); });

@ -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);
}
}

@ -1,4 +1,5 @@
import { app } from '../ApplicationServices/Application'; import { app } from '../ApplicationServices/Application';
import { GetEntity } from '../Common/Utils';
import { Command } from '../Editor/CommandMachine'; import { Command } from '../Editor/CommandMachine';
import { PromptStatus } from '../Editor/PromptResult'; import { PromptStatus } from '../Editor/PromptResult';
@ -9,6 +10,13 @@ export class Command_Erase implements Command
let ssRes = await app.m_Editor.GetSelection({ UseSelect: true }); let ssRes = await app.m_Editor.GetSelection({ UseSelect: true });
if (ssRes.Status != PromptStatus.OK) return; if (ssRes.Status != PromptStatus.OK) return;
ssRes.SelectSet.SelectEntityList.forEach(e => { e.Erase() }); for (let obj of ssRes.SelectSet.SelectObjectList)
{
let ent = GetEntity(obj);
if (ent)
ent.Erase();
else
obj.visible = false;
}
} }
} }

@ -4,6 +4,7 @@ import { Command } from '../Editor/CommandMachine';
import { PromptStatus } from '../Editor/PromptResult'; import { PromptStatus } from '../Editor/PromptResult';
import { MoveMatrix } from '../Geometry/GeUtils'; import { MoveMatrix } from '../Geometry/GeUtils';
import { JigUtils } from '../Editor/JigUtils'; import { JigUtils } from '../Editor/JigUtils';
// import { IsEntity } from '../Common/Utils';
export class Command_Move implements Command export class Command_Move implements Command
{ {
@ -30,6 +31,12 @@ export class Command_Move implements Command
let moveMatrix = MoveMatrix(p.clone().sub(ptLast)); let moveMatrix = MoveMatrix(p.clone().sub(ptLast));
ensClone.forEach(e => e.ApplyMatrix(moveMatrix)); ensClone.forEach(e => e.ApplyMatrix(moveMatrix));
ptLast.copy(p); ptLast.copy(p);
// //对调试实体进行移动
// for (let obj of ssRes.SelectSet.SelectObjectList)
// {
// if (!IsEntity(obj))
// obj.applyMatrix(moveMatrix);
// }
}, },
BasePoint: ptBase, BasePoint: ptBase,
AllowDrawRubberBand: true AllowDrawRubberBand: true

@ -1,7 +1,7 @@
import * as THREE from 'three'; import { ExtrudeGeometry, Matrix4, Mesh, Shape, Vector2, Vector3 } from 'three';
import { data } from '../../Add-on/data'; import { data } from '../../Add-on/data';
import { polar, equaln } from '../../Geometry/GeUtils'; import { Shape2 } from '../../DatabaseServices/Shape2';
import { equaln, polar } from '../../Geometry/GeUtils';
import { RotateUVs } from '../../Geometry/RotateUV'; import { RotateUVs } from '../../Geometry/RotateUV';
export namespace CreateBoardUtil export namespace CreateBoardUtil
@ -11,16 +11,16 @@ export namespace CreateBoardUtil
{ {
m_StartAn: number; m_StartAn: number;
m_EndAn: number; m_EndAn: number;
m_StartPoint: THREE.Vector2; m_StartPoint: Vector2;
m_EndPoint: THREE.Vector2; m_EndPoint: Vector2;
m_Center: THREE.Vector2; m_Center: Vector2;
m_Radius: number; m_Radius: number;
constructor(p1: THREE.Vector2, p2: THREE.Vector2, bul: number) constructor(p1: Vector2, p2: Vector2, bul: number)
{ {
this.m_StartPoint = p1.clone(); this.m_StartPoint = p1.clone();
this.m_EndPoint = p2.clone(); this.m_EndPoint = p2.clone();
let vec: THREE.Vector2 = p2.clone().sub(p1); let vec: Vector2 = p2.clone().sub(p1);
let len = vec.length(); let len = vec.length();
let an = vec.angle(); let an = vec.angle();
this.m_Radius = len / Math.sin(2 * Math.atan(bul)) / 2; this.m_Radius = len / Math.sin(2 * Math.atan(bul)) / 2;
@ -47,9 +47,9 @@ export namespace CreateBoardUtil
//创建轮廓 通过点表和凸度 //创建轮廓 通过点表和凸度
export function createPath(pts: THREE.Vector2[], buls: number[], shapeOut?: THREE.Shape): THREE.Shape export function createPath(pts: Vector2[], buls: number[]): Shape
{ {
let shape = shapeOut || new THREE.Shape(); let shape = new Shape2();
if (pts.length === 0) return shape; if (pts.length === 0) return shape;
let firstPt = pts[0]; let firstPt = pts[0];
@ -74,24 +74,24 @@ export namespace CreateBoardUtil
} }
return shape; return shape;
} }
export function getVec(data: object): THREE.Vector3 export function getVec(data: object): Vector3
{ {
return new THREE.Vector3(data["x"], data["y"], data["z"]); return new Vector3(data["x"], data["y"], data["z"]);
} }
//创建板件 暂时这么写 //创建板件 暂时这么写
export function createBoard(boardData: object) export function createBoard(boardData: object)
{ {
var pts: THREE.Vector2[] = new Array(); var pts: Vector2[] = new Array();
var buls: number[] = new Array(); var buls: number[] = new Array();
var boardPts = boardData["Pts"]; var boardPts = boardData["Pts"];
var boardBuls = boardData["Buls"]; var boardBuls = boardData["Buls"];
let boardHeight = boardData["H"]; let boardHeight = boardData["H"];
var boardMat = new THREE.Matrix4(); var boardMat = new Matrix4();
var matInv: THREE.Matrix4 = new THREE.Matrix4(); var matInv: Matrix4 = new Matrix4();
//InitBoardMat //InitBoardMat
{ {
@ -109,7 +109,7 @@ export namespace CreateBoardUtil
{ {
var pt = getVec(boardPts[i]).multiplyScalar(0.001); var pt = getVec(boardPts[i]).multiplyScalar(0.001);
pt.applyMatrix4(matInv); pt.applyMatrix4(matInv);
pts.push(new THREE.Vector2(pt.x, pt.y)); pts.push(new Vector2(pt.x, pt.y));
buls.push(boardBuls[i]); buls.push(boardBuls[i]);
} }
@ -120,7 +120,7 @@ export namespace CreateBoardUtil
amount: boardHeight * 0.001 amount: boardHeight * 0.001
}; };
var ext = new THREE.ExtrudeGeometry(sp, extrudeSettings); var ext = new ExtrudeGeometry(sp, extrudeSettings);
ext.translate(0, 0, -boardHeight * 0.001) ext.translate(0, 0, -boardHeight * 0.001)
ext.applyMatrix(boardMat); ext.applyMatrix(boardMat);
@ -129,7 +129,7 @@ export namespace CreateBoardUtil
RotateUVs(ext); RotateUVs(ext);
} }
var mesh = new THREE.Mesh(ext); var mesh = new Mesh(ext);
return mesh; return mesh;
} }

@ -128,12 +128,12 @@ export function arrayMap<T>(arr: Array<T>, mapFunc: (v: T) => T): Array<T>
return arr; return arr;
} }
function sortNumberCompart(e1, e2) function sortNumberCompart(e1: any, e2: any)
{ {
return e1 - e2; return e1 - e2;
} }
function checkEqual(e1, e2): boolean function checkEqual(e1: any, e2: any): boolean
{ {
return e1 === e2; return e1 === e2;
} }

@ -116,16 +116,26 @@ function fallbackCopyTextToClipboard(text)
} }
document.body.removeChild(textArea); document.body.removeChild(textArea);
} }
export async function copyTextToClipboard(text) export async function copyTextToClipboard(text: string)
{ {
if (!navigator["clipboard"]) if (!navigator["clipboard"])
{ {
fallbackCopyTextToClipboard(text); fallbackCopyTextToClipboard(text);
return; return;
} }
await navigator["clipboard"].writeText(text); return await navigator["clipboard"].writeText(text);
} }
window["copy"] = (text: string) =>
{
console.log("你有2秒的时间让页面聚焦,否则将无法拷贝!");
setTimeout(async () =>
{
await copyTextToClipboard(text);
console.log("拷贝成功!");
}, 2000);
};
//ref: https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript //ref: https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript

@ -5,13 +5,14 @@ import { ColorMaterial } from "../Common/ColorPalette";
import { equalCurve } from "../Common/CurveUtils"; import { equalCurve } from "../Common/CurveUtils";
import { DisposeThreeObj } from "../Common/Dispose"; import { DisposeThreeObj } from "../Common/Dispose";
import { Status, UpdateDraw } from "../Common/Status"; import { Status, UpdateDraw } from "../Common/Status";
import { CSG } from "../csg/core/CSG";
import { CSG2Geometry, Geometry2CSG } from "../csg/core/Geometry2CSG";
import { ObjectSnapMode } from "../Editor/ObjectSnapMode"; import { ObjectSnapMode } from "../Editor/ObjectSnapMode";
import { boardUVGenerator } from "../Geometry/BoardUVGenerator"; import { boardUVGenerator } from "../Geometry/BoardUVGenerator";
import { BSPGroupParse } from "../Geometry/BSPGroupParse"; import { BSPGroupParse } from "../Geometry/BSPGroupParse";
import { CreateWireframe } from "../Geometry/CreateWireframe"; import { CreateWireframe } from "../Geometry/CreateWireframe";
import { EdgesGeometry } from "../Geometry/EdgeGeometry"; import { EdgesGeometry } from "../Geometry/EdgeGeometry";
import { cZeroVec, equaln, equalv2, equalv3, isIntersect, isParallelTo, MoveMatrix } from "../Geometry/GeUtils"; import { cZeroVec, equaln, equalv2, equalv3, isIntersect, isParallelTo, MoveMatrix } from "../Geometry/GeUtils";
import { CSG } from "../Geometry/ThreeCSG";
import { ScaleUV } from "../Geometry/UVUtils"; import { ScaleUV } from "../Geometry/UVUtils";
import { RenderType } from "../GraphicsSystem/RenderType"; import { RenderType } from "../GraphicsSystem/RenderType";
import { BlockTableRecord } from "./BlockTableRecord"; import { BlockTableRecord } from "./BlockTableRecord";
@ -502,10 +503,11 @@ export class ExtureSolid extends Entity
let splitEntitys: this[] = []; let splitEntitys: this[] = [];
this.GrooveCheckAll(splitEntitys); this.GrooveCheckAll(splitEntitys);
let ms = this.m_Owner.Object as BlockTableRecord; if (splitEntitys.length > 0 && this.m_Owner)
for (let e of splitEntitys)
{ {
ms.Append(e); let ms = this.m_Owner.Object as BlockTableRecord;
for (let e of splitEntitys)
ms.Append(e);
} }
} }
this.Update(); this.Update();
@ -654,7 +656,8 @@ export class ExtureSolid extends Entity
if (this.csg) if (this.csg)
return this.csg; return this.csg;
this.GetDrawObjectFromRenderType(); //使用这个方法 而不是得到MeshGeometry,以保证实体更新时CSG对象能被更新.
this.GetDrawObjectFromRenderType(RenderType.Physical);
if (!this.csg) if (!this.csg)
this.Update(UpdateDraw.Geometry); this.Update(UpdateDraw.Geometry);
return this.csg; return this.csg;
@ -681,9 +684,9 @@ export class ExtureSolid extends Entity
let zv = this.Normal; let zv = this.Normal;
let xv = yv.clone().cross(zv); let xv = yv.clone().cross(zv);
let m = new Matrix4().makeBasis(xv, yv, zv).copyPosition(this.OCS); let m = new Matrix4().makeBasis(xv, yv, zv).copyPosition(this.OCS);
let mi = new Matrix4().getInverse(m); let mi = new Matrix4().getInverse(m).multiply(this.OCS);
let interBSP = this.CSG.intersect(target.CSG); let interBSP = this.CSG.intersect(target.CSG.transform1(this.OCSInv.multiply(target.OCS)));
let topology = new BSPGroupParse(interBSP); let topology = new BSPGroupParse(interBSP);
let grooves: ExtureSolid[] = []; let grooves: ExtureSolid[] = [];
for (let pts of topology.Parse()) for (let pts of topology.Parse())
@ -995,6 +998,7 @@ export class ExtureSolid extends Entity
this._MeshGeometry = this.GeneralGeometry(); this._MeshGeometry = this.GeneralGeometry();
return this._MeshGeometry; return this._MeshGeometry;
} }
private _EdgeGeometry: EdgesGeometry; private _EdgeGeometry: EdgesGeometry;
private get EdgeGeometry() private get EdgeGeometry()
{ {
@ -1015,20 +1019,21 @@ export class ExtureSolid extends Entity
}; };
let geo = new ExtrudeGeometry(this.ContourCurve.Shape, extrudeSettings); let geo = new ExtrudeGeometry(this.ContourCurve.Shape, extrudeSettings);
geo.applyMatrix(this.contourCurve.OCS); geo.applyMatrix(this.contourCurve.OCS);
this.csg = Geometry2CSG(geo);
this.csg = new CSG(geo, this.m_Matrix);
ScaleUV(geo);
if (this.grooves.length === 0) if (this.grooves.length === 0)
{
ScaleUV(geo);
return geo; return geo;
}
for (let g of this.grooves) for (let g of this.grooves)
{ {
if (g.thickness > 0) if (g.thickness > 0)
this.csg = this.csg.subtract(g.CSG); this.csg = this.csg.subtract(g.CSG.transform1(this.OCSInv.multiply(g.OCS)));
} }
let bgeo = this.csg.toGeometry(); let bgeo = CSG2Geometry(this.csg);
ScaleUV(bgeo); ScaleUV(bgeo);
return bgeo; return bgeo;
} }

@ -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);
}
}

@ -16,7 +16,6 @@ import { Command_Copy } from '../Add-on/Copy';
import { CopyClip } from '../Add-on/CopyClip'; import { CopyClip } from '../Add-on/CopyClip';
import { Command_CopyPoint } from '../Add-on/CopyPoint'; import { Command_CopyPoint } from '../Add-on/CopyPoint';
import { CustomUcs } from '../Add-on/CostumUCS'; import { CustomUcs } from '../Add-on/CostumUCS';
import { CSGSubtraction, CSGUnion } from '../Add-on/CSGOperation';
import { CMD_Divide } from '../Add-on/Divide'; import { CMD_Divide } from '../Add-on/Divide';
import { DrawArc } from '../Add-on/DrawArc'; import { DrawArc } from '../Add-on/DrawArc';
import { DrawBehindBoard } from '../Add-on/DrawBoard/DrawBehindBoard'; import { DrawBehindBoard } from '../Add-on/DrawBoard/DrawBehindBoard';
@ -160,10 +159,6 @@ export function registerCommand()
commandMachine.RegisterCommand("hus", new Command_HideUnselected()) commandMachine.RegisterCommand("hus", new Command_HideUnselected())
commandMachine.RegisterCommand("show", new Command_ShowAll) commandMachine.RegisterCommand("show", new Command_ShowAll)
//CSG Operate
commandMachine.RegisterCommand("csgunion", new CSGUnion())
commandMachine.RegisterCommand("csgsub", new CSGSubtraction())
commandMachine.RegisterCommand("save", new Save()); commandMachine.RegisterCommand("save", new Save());
commandMachine.RegisterCommand("open", new Open()); commandMachine.RegisterCommand("open", new Open());

@ -1,6 +1,7 @@
import { Vector3 } from "three"; import { Vector3 } from "three";
import { ToFixed } from "../Common/Utils"; import { ToFixed } from "../Common/Utils";
import { Polygon, CSG } from "./ThreeCSG"; import { CSG } from "../csg/core/CSG";
import { Polygon } from "../csg/core/math/Polygon3";
interface Vec3 interface Vec3
{ {
x: number; x: number;
@ -8,7 +9,6 @@ interface Vec3
z: number; z: number;
} }
/** /**
* THREEBSP(CSG) . * THREEBSP(CSG) .
* THREEBSP. * THREEBSP.
@ -23,12 +23,12 @@ export class BSPGroupParse
constructor(bsp?: CSG, public fractionDigits = 1) constructor(bsp?: CSG, public fractionDigits = 1)
{ {
if (bsp) if (bsp)
for (let poly of bsp.tree.allPolygons()) for (let poly of bsp.polygons)
this.Add(poly); this.Add(poly);
} }
Add(poly: Polygon) Add(poly: Polygon)
{ {
let strs = poly.vertices.map(p => this.GenerateP(p)); let strs = poly.vertices.map(p => this.GenerateP(p.pos));
let str0 = strs[0]; let str0 = strs[0];
let s0 = this.Get(str0); let s0 = this.Get(str0);
for (let i = 1; i < strs.length; i++) for (let i = 1; i < strs.length; i++)

@ -1,5 +1,6 @@
import { BufferGeometry, Face3, Float32BufferAttribute, Geometry, Line3, Triangle, Vector3 } from "three"; import { BufferGeometry, Face3, Float32BufferAttribute, Geometry, Line3, Triangle, Vector3 } from "three";
import { arraySortByNumber } from "../Common/ArrayExt"; import { arraySortByNumber } from "../Common/ArrayExt";
import { equalv3 } from "./GeUtils";
//ref: https://github.com/mrdoob/js/issues/10517 //ref: https://github.com/mrdoob/js/issues/10517
const keys = ['a', 'b', 'c']; const keys = ['a', 'b', 'c'];
@ -9,7 +10,6 @@ export class EdgesGeometry extends BufferGeometry
constructor(geometry, thresholdAngle: number = 1) constructor(geometry, thresholdAngle: number = 1)
{ {
super(); super();
let geometry2: Geometry; let geometry2: Geometry;
if (geometry.isBufferGeometry) if (geometry.isBufferGeometry)
@ -23,19 +23,18 @@ export class EdgesGeometry extends BufferGeometry
let vertices = geometry2.vertices; let vertices = geometry2.vertices;
let faces = geometry2.faces; let faces = geometry2.faces;
let faceHash = new Set<string>(); let count = faces.length;
for (let i = 0; i < faces.length; i++) for (let i = 0; i < faces.length; i++)
{ {
if (i > 1000)//出错 if (faces.length > count * 2)
{
console.warn("EdgeGeometry的分裂已经到达2倍!");
break; break;
}
let face = faces[i]; let face = faces[i];
//fix CSG produces duplicate faces if (FaceArea(face, vertices) < 1e-5)
let faceStr = `${face.a},${face.b},${face.c}`;
if (faceHash.has(faceStr))
continue; continue;
faceHash.add(faceStr);
for (let j = 0; j < 3; j++) for (let j = 0; j < 3; j++)
{ {
@ -53,17 +52,14 @@ export class EdgesGeometry extends BufferGeometry
let p = vertices[e]; let p = vertices[e];
let closestPoint = line.closestPointToPoint(p, true, new Vector3()); let closestPoint = line.closestPointToPoint(p, true, new Vector3());
if (closestPoint.equals(vertices[e1]) || closestPoint.equals(vertices[e2])) if (equalv3(closestPoint, vertices[e1], 1e-5) || equalv3(closestPoint, vertices[e2]))
continue; continue;
if (closestPoint.distanceTo(p) < 1e-5) if (equalv3(closestPoint, p, 1e-5))
{ {
face["splitted"] = true; face["splitted"] = true;
let f1 = new Face3(e, e3, e1, face.normal); let f1 = new Face3(e, e3, e1, face.normal);
let f2 = new Face3(e, e3, e2, face.normal); let f2 = new Face3(e, e3, e2, face.normal);
faces.push(f1, f2);
if (FaceArea(f1, vertices) > 1
&& FaceArea(f2, vertices) > 1)
faces.push(f1, f2);
break; break;
} }
} }

@ -63,18 +63,10 @@ export class Orbit
} }
/** /**
*
* . * .
* http://help.autodesk.com/view/ACD/2017/CHS/?guid=GUID-E19E5B42-0CC7-4EBA-B29F-5E1D595149EE * http://help.autodesk.com/view/ACD/2017/CHS/?guid=GUID-E19E5B42-0CC7-4EBA-B29F-5E1D595149EE
*
* @static
* @param {Vector3} n 线
* @param {Vector3} [ay]
* @param {Vector3} [ax]
* @returns {Vector3} ay =>up
* @memberof Orbit
*/ */
static ComputUpDirection(n: Vector3, ay = new Vector3(), ax = new Vector3()): Vector3 static ComputUpDirection(n: Vector3, ay: Vector3 = new Vector3(), ax: Vector3 = new Vector3()): Vector3
{ {
if (Math.abs(n.x) < 0.015625 && Math.abs(n.y) < 0.015625) if (Math.abs(n.x) < 0.015625 && Math.abs(n.y) < 0.015625)
ax.crossVectors(cYAxis, n); ax.crossVectors(cYAxis, n);

@ -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,248 @@
import { _CSGDEBUG, EPS } from "../constants";
import { Plane } from "./Plane";
import { Vector3D } from "./Vector3";
import { Vertex3D } from "./Vertex3";
import { arrayRemoveDuplicateBySort } from "../../../Common/ArrayExt";
import { Matrix4 } from "three";
import { IsMirror } from "./IsMirrot";
export enum Type
{
CoplanarFront = 0,
CoplanarBack = 1,
Front = 2,
Back = 3,
Spanning = 4,
}
interface SplitPolygonData
{
type: Type;
front: Polygon;
back: Polygon;
}
/** 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).
* <br>
* 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).
* <br>
* The plane of the polygon is calculated from the vertex coordinates if not provided.
* The plane can alternatively be passed as the third argument to avoid calculations.
*
 *
 *
 *
*/
export class Polygon
{
cachedBoundingSphere: [Vector3D, number];
cachedBoundingBox: [Vector3D, Vector3D];
constructor(public vertices: Vertex3D[], public plane?: Plane)
{
if (!plane)
this.plane = Plane.fromVector3Ds(vertices[0].pos, vertices[1].pos, vertices[2].pos);
if (_CSGDEBUG)
if (!this.checkIfConvex()) throw new Error("Not convex!");
}
/** Check whether the polygon is convex. (it should be, otherwise we will get unexpected results)*/
checkIfConvex(): boolean
{
return Polygon.verticesConvex(this.vertices, this.plane.normal);
}
// returns an array with a Vector3D (center point) and a radius
boundingSphere()
{
if (!this.cachedBoundingSphere)
{
let box = this.boundingBox();
let middle = box[0].clone().add(box[1]).multiplyScalar(0.5);
let radius3 = box[1].clone().sub(middle);
let radius = radius3.length();
this.cachedBoundingSphere = [middle, radius];
}
return this.cachedBoundingSphere;
}
// returns an array of two Vector3Ds (minimum coordinates and maximum coordinates)
boundingBox(): Vector3D[]
{
if (!this.cachedBoundingBox)
{
let minpoint: Vector3D;
let maxpoint: Vector3D;
let vertices = this.vertices;
let numvertices = vertices.length;
if (numvertices === 0)
minpoint = new Vector3D(0, 0, 0);
else
minpoint = vertices[0].pos.clone();
maxpoint = minpoint.clone();
for (let i = 1; i < numvertices; i++)
{
let point = vertices[i].pos;
minpoint.min(point);
maxpoint.max(point);
}
this.cachedBoundingBox = [minpoint, maxpoint];
}
return this.cachedBoundingBox;
}
flipped()
{
let newvertices = this.vertices.map(v => v.flipped());
newvertices.reverse();
let newplane = this.plane.flipped();
return new Polygon(newvertices, newplane);
}
// Affine transformation of polygon. Returns a new Polygon
transform(matrix4x4: Matrix4)
{
let newvertices = this.vertices.map(v => v.transform(matrix4x4));
let newplane = this.plane.transform(matrix4x4);
if (IsMirror(matrix4x4))
{
// need to reverse the vertex order
// in order to preserve the inside/outside orientation:
newvertices.reverse();
}
return new Polygon(newvertices, newplane);
}
splitByPlane(plane: Plane): SplitPolygonData
{
let result: SplitPolygonData = { type: null, front: null, back: null };
// cache in local lets (speedup):
let planeNormal = plane.normal;
let vertices = this.vertices;
let numVertices = vertices.length;
if (this.plane.equals(plane))
{
result.type = Type.CoplanarFront;
}
else
{
let thisW = plane.w;
let hasFront = false;
let hasBack = false;
let vertexIsBack: boolean[] = [];
let MINEPS = -EPS;
for (let i = 0; i < numVertices; i++)
{
let t = planeNormal.dot(vertices[i].pos) - thisW;
let isBack = t < 0;
vertexIsBack.push(isBack);
if (t > EPS) hasFront = true;
if (t < MINEPS) hasBack = true;
}
if (!hasFront && !hasBack)
{
// all points coplanar
let t = planeNormal.dot(this.plane.normal);
result.type = t >= 0 ? Type.CoplanarFront : Type.CoplanarBack;
}
else if (!hasBack)
result.type = Type.Front;
else if (!hasFront)
result.type = Type.Back;
else
{
result.type = Type.Spanning;
let frontVertices: Vertex3D[] = [];
let backVertices: Vertex3D[] = [];
let isBack = vertexIsBack[0];
for (
let vertexIndex = 0;
vertexIndex < numVertices;
vertexIndex++
)
{
let vertex = vertices[vertexIndex];
let nextVertexindex = vertexIndex + 1;
if (nextVertexindex >= numVertices) nextVertexindex = 0;
let nextIsBack = vertexIsBack[nextVertexindex];
if (isBack === nextIsBack)
{
// line segment is on one side of the plane:
if (isBack)
backVertices.push(vertex);
else
frontVertices.push(vertex);
}
else
{
let intersectionVertex = plane.splitLineBetweenPoints(vertex, vertices[nextVertexindex]);
if (isBack)
{
backVertices.push(vertex);
backVertices.push(intersectionVertex);
frontVertices.push(intersectionVertex);
}
else
{
frontVertices.push(vertex);
frontVertices.push(intersectionVertex);
backVertices.push(intersectionVertex);
}
}
isBack = nextIsBack;
} // for vertexindex
// remove duplicate vertices:
let EPS_SQUARED = EPS * EPS;
arrayRemoveDuplicateBySort(backVertices, (v1, v2) =>
{
return v1.pos.distanceToSquared(v2.pos) < EPS_SQUARED;
});
arrayRemoveDuplicateBySort(frontVertices, (v1, v2) =>
{
return v1.pos.distanceToSquared(v2.pos) < EPS_SQUARED;
});
if (frontVertices.length >= 3)
result.front = new Polygon(frontVertices, this.plane);
if (backVertices.length >= 3)
result.back = new Polygon(backVertices, this.plane);
}
}
return result;
}
static verticesConvex(vertices: Vertex3D[], planenormal: Vector3D)
{
let count = vertices.length;
if (count < 3) return false;
let prevPrevPos = vertices[count - 2].pos;
let prevPos = vertices[count - 1].pos;
for (let i = 0; i < count; i++)
{
let pos = vertices[i].pos;
if (!Polygon.isConvexPoint(prevPrevPos, prevPos, pos, planenormal))
return false;
prevPrevPos = prevPos;
prevPos = pos;
}
return true;
}
// 计算3点是否凸角
static isConvexPoint(prevpoint: Vector3D, point: Vector3D, nextpoint: Vector3D, normal: Vector3D)
{
let crossproduct = point.clone().sub(prevpoint).cross(nextpoint.clone().sub(point));
let crossdotnormal = crossproduct.dot(normal);
return crossdotnormal >= 0;
}
}

@ -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,458 @@
import { EPS } from "../constants";
import { fnNumberSort, insertSorted, interpolateBetween2DPointsForY } from "../utils";
import { Line2D } from "./Line2";
import { OrthoNormalBasis } from "./OrthoNormalBasis";
import { Polygon } from "./Polygon3";
import { Vector2D } from "./Vector2";
import { Vertex3D } from "./Vertex3";
//在这个文件中 Top 表示的是 y最小.
// Bottom 表示的是 y最大
interface ActivePolygon
{
polygonindex: number;
leftvertexindex: number;
rightvertexindex: number;
topleft: Vector2D;
bottomleft: Vector2D;
topright: Vector2D;
bottomright: Vector2D;
}
interface OutPolygon
{
topleft: Vector2D;
topright: Vector2D;
bottomleft: Vector2D;
bottomright: Vector2D;
leftline: Line2D;
rightline: Line2D;
outpolygon?: { leftpoints: Vector2D[]; rightpoints: Vector2D[]; };
leftlinecontinues?: boolean;
rightlinecontinues?: boolean;
}
//一组共面多边形的Retesselation函数。 请参阅此文件顶部的介绍。
export function reTesselateCoplanarPolygons(
sourcePolygons: Polygon[],
destpolygons: Polygon[] = []
): Polygon[]
{
let numPolygons = sourcePolygons.length;
if (numPolygons === 0) return;
let plane = sourcePolygons[0].plane;
let orthobasis = new OrthoNormalBasis(plane);
// let xcoordinatebins = {}
let yCoordinateBins: { [key: number]: number } = {}; //整数map
let yCoordinateBinningFactor = (1.0 / EPS) * 10;
let polygonVertices2d: (Vector2D[])[] = []; // (Vector2[])[];
let polygonTopVertexIndexes: number[] = []; // 每个多边形最顶层顶点的索引数组 minIndex
let topY2PolygonIndexes: { [key: number]: number[] } = {}; // Map<minY,polygonIndex[]>
let yCoordinateToPolygonIndexes: { [key: string]: { [key: number]: boolean }; } = {}; // Map<Y,Map<polygonIndex,boole> > Y坐标映射所有的多边形
//将多边形转换为2d点表 polygonVertices2d
//建立y对应的多边形Map yCoordinateToPolygonIndexes
for (let polygonIndex = 0; polygonIndex < numPolygons; polygonIndex++)
{
let poly3d = sourcePolygons[polygonIndex];
let numVertices = poly3d.vertices.length;
if (numVertices === 0) continue;
let vertices2d: Vector2D[] = []; //Vector2d[];
let minIndex = -1;
let miny: number, maxy: number;
for (let i = 0; i < numVertices; i++)
{
let pos2d = orthobasis.to2D(poly3d.vertices[i].pos);
// perform binning of y coordinates: If we have multiple vertices very
// close to each other, give them the same y coordinate:
let yCoordinatebin = Math.floor(pos2d.y * yCoordinateBinningFactor);
let newy: number;
if (yCoordinatebin in yCoordinateBins)
newy = yCoordinateBins[yCoordinatebin];
else if (yCoordinatebin + 1 in yCoordinateBins)
newy = yCoordinateBins[yCoordinatebin + 1];
else if (yCoordinatebin - 1 in yCoordinateBins)
newy = yCoordinateBins[yCoordinatebin - 1];
else
{
newy = pos2d.y;
yCoordinateBins[yCoordinatebin] = pos2d.y;
}
pos2d = new Vector2D(pos2d.x, newy);
vertices2d.push(pos2d);
if (i === 0 || newy < miny)
{
miny = newy;
minIndex = i;
}
if (i === 0 || newy > maxy) maxy = newy;
if (!(newy in yCoordinateToPolygonIndexes))
yCoordinateToPolygonIndexes[newy] = {};
yCoordinateToPolygonIndexes[newy][polygonIndex] = true;
}
//退化多边形所有顶点都具有相同的y坐标。 从现在开始忽略它:
if (miny >= maxy) continue;
if (!(miny in topY2PolygonIndexes)) topY2PolygonIndexes[miny] = [];
topY2PolygonIndexes[miny].push(polygonIndex);
// reverse the vertex order:
vertices2d.reverse();
minIndex = numVertices - minIndex - 1;
polygonVertices2d.push(vertices2d);
polygonTopVertexIndexes.push(minIndex);
}
//所有的y坐标,从小到大排序
let yCoordinates: string[] = [];
for (let ycoordinate in yCoordinateToPolygonIndexes)
yCoordinates.push(ycoordinate);
yCoordinates.sort(fnNumberSort);
//迭代y坐标 从低到高
// activepolygons 'active'的源多边形即与y坐标相交
// 多边形是从左往右排序的
// activepolygons 中的每个元素都具有以下属性:
// polygonindex 源多边形的索引即sourcepolygons的索引 和polygonvertices2d数组
// leftvertexindex 左边 在当前y坐标处或刚好在当前y坐标之上
// rightvertexindex 右边
// topleft bottomleft 与当前y坐标交叉的多边形左侧的坐标
// topright bottomright 与当前y坐标交叉的多边形右侧的坐标
let activePolygons: ActivePolygon[] = [];
let prevOutPolygonRow: OutPolygon[] = []; //上一个输出多边形行?
for (let yindex = 0; yindex < yCoordinates.length; yindex++)
{
let yCoordinateStr = yCoordinates[yindex];
let yCoordinate = Number(yCoordinateStr);
// 用当前的y 更新 activePolygons
// - 删除以y坐标结尾的所有多边形 删除polygon maxy = y 的多边形
// - 更新 leftvertexindex 和 rightvertexindex (指向当前顶点索引)
// 在多边形的左侧和右侧
// 迭代在Y坐标处有一个角的所有多边形
let polygonIndexeSwithCorner =
yCoordinateToPolygonIndexes[yCoordinateStr];
for (
let activePolygonIndex = 0;
activePolygonIndex < activePolygons.length;
activePolygonIndex++
)
{
let activepolygon = activePolygons[activePolygonIndex];
let polygonindex = activepolygon.polygonindex;
if (!polygonIndexeSwithCorner[polygonindex])//如果不在角内
continue;
//多边形在此y坐标处有一个角
let vertices2d = polygonVertices2d[polygonindex];
let numvertices = vertices2d.length;
let newleftvertexindex = activepolygon.leftvertexindex;
let newrightvertexindex = activepolygon.rightvertexindex;
//看看我们是否需要增加 leftvertexindex 或减少 rightvertexindex
while (true)
{
let nextleftvertexindex = newleftvertexindex + 1;
if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0;
if (vertices2d[nextleftvertexindex].y !== yCoordinate) break;
newleftvertexindex = nextleftvertexindex;
}
//减少 rightvertexindex
let nextrightvertexindex = newrightvertexindex - 1;
if (nextrightvertexindex < 0)
nextrightvertexindex = numvertices - 1;
if (vertices2d[nextrightvertexindex].y === yCoordinate)
newrightvertexindex = nextrightvertexindex;
if (
newleftvertexindex !== activepolygon.leftvertexindex //有向上更新
&& newleftvertexindex === newrightvertexindex //指向同一个点
)
{
// We have increased leftvertexindex or decreased rightvertexindex, and now they point to the same vertex
// This means that this is the bottom point of the polygon. We'll remove it:
//我们增加了leftvertexindex或减少了rightvertexindex现在它们指向同一个顶点
//这意味着这是多边形的底点。 我们将删除它:
activePolygons.splice(activePolygonIndex, 1);
--activePolygonIndex;
} else
{
activepolygon.leftvertexindex = newleftvertexindex;
activepolygon.rightvertexindex = newrightvertexindex;
activepolygon.topleft = vertices2d[newleftvertexindex];
activepolygon.topright = vertices2d[newrightvertexindex];
let nextleftvertexindex = newleftvertexindex + 1;
if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0;
activepolygon.bottomleft = vertices2d[nextleftvertexindex];
let nextrightvertexindex = newrightvertexindex - 1;
if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1;
activepolygon.bottomright = vertices2d[nextrightvertexindex];
}
}
let nextYCoordinate: number; // number y
if (yindex >= yCoordinates.length - 1)
{
// last row, all polygons must be finished here:
// 最后一行,所有多边形必须在这里完成:
activePolygons = [];
}
else // yindex < ycoordinates.length-1
{
nextYCoordinate = Number(yCoordinates[yindex + 1]);
let middleYCoordinate = 0.5 * (yCoordinate + nextYCoordinate);
// update activepolygons by adding any polygons that start here:
// 添加从这里开始的多边形 到 activePolygons
let startingPolygonIndexes = topY2PolygonIndexes[yCoordinateStr];
for (let polygonindex_key in startingPolygonIndexes)
{
let polygonindex = startingPolygonIndexes[polygonindex_key];
let vertices2d = polygonVertices2d[polygonindex];
let numvertices = vertices2d.length;
let topVertexIndex = polygonTopVertexIndexes[polygonindex];
// the top of the polygon may be a horizontal line. In that case topvertexindex can point to any point on this line.
// Find the left and right topmost vertices which have the current y coordinate:
// 顶部可以是一条直线,寻找最左边的点和最右边的点
let topleftvertexindex = topVertexIndex;
while (true)
{
let i = topleftvertexindex + 1;
if (i >= numvertices) i = 0;
if (vertices2d[i].y !== yCoordinate) break;
if (i === topVertexIndex) break; // should not happen, but just to prevent endless loops
topleftvertexindex = i;
}
let toprightvertexindex = topVertexIndex;
while (true)
{
let i = toprightvertexindex - 1;
if (i < 0) i = numvertices - 1;
if (vertices2d[i].y !== yCoordinate) break;
if (i === topleftvertexindex) break; // should not happen, but just to prevent endless loops
toprightvertexindex = i;
}
let nextleftvertexindex = topleftvertexindex + 1;
if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0;
let nextrightvertexindex = toprightvertexindex - 1;
if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1;
let newactivepolygon: ActivePolygon = {
polygonindex: polygonindex,
leftvertexindex: topleftvertexindex,
rightvertexindex: toprightvertexindex,
topleft: vertices2d[topleftvertexindex],
topright: vertices2d[toprightvertexindex],
bottomleft: vertices2d[nextleftvertexindex],
bottomright: vertices2d[nextrightvertexindex]
};
//二分插入
insertSorted(activePolygons, newactivepolygon, function (el1: ActivePolygon, el2: ActivePolygon)
{
let x1 = interpolateBetween2DPointsForY(
el1.topleft,
el1.bottomleft,
middleYCoordinate
);
let x2 = interpolateBetween2DPointsForY(
el2.topleft,
el2.bottomleft,
middleYCoordinate
);
if (x1 > x2) return 1;
if (x1 < x2) return -1;
return 0;
});
}
}
//#region
// if( (yindex === ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) )
// if(true)
// {
let newOutPolygonRow: OutPolygon[] = []; //输出多边形
// Build the output polygons for the next row in newOutPolygonRow:
//现在 activepolygons 是最新的
//为 newOutPolygonRow 中的下一行构建输出多边形:
for (let activepolygonKey in activePolygons)
{
let activepolygon = activePolygons[activepolygonKey];
let x = interpolateBetween2DPointsForY(
activepolygon.topleft,
activepolygon.bottomleft,
yCoordinate
);
let topleft = new Vector2D(x, yCoordinate);
x = interpolateBetween2DPointsForY(
activepolygon.topright,
activepolygon.bottomright,
yCoordinate
);
let topright = new Vector2D(x, yCoordinate);
x = interpolateBetween2DPointsForY(
activepolygon.topleft,
activepolygon.bottomleft,
nextYCoordinate
);
let bottomleft = new Vector2D(x, nextYCoordinate);
x = interpolateBetween2DPointsForY(
activepolygon.topright,
activepolygon.bottomright,
nextYCoordinate
);
let bottomright = new Vector2D(x, nextYCoordinate);
let outPolygon = {
topleft: topleft,
topright: topright,
bottomleft: bottomleft,
bottomright: bottomright,
leftline: Line2D.fromPoints(topleft, bottomleft),
rightline: Line2D.fromPoints(bottomright, topright)
};
if (newOutPolygonRow.length > 0)
{
let prevoutpolygon =
newOutPolygonRow[newOutPolygonRow.length - 1];
let d1 = outPolygon.topleft.distanceTo(prevoutpolygon.topright);
let d2 = outPolygon.bottomleft.distanceTo(
prevoutpolygon.bottomright
);
if (d1 < EPS && d2 < EPS)
{
// we can join this polygon with the one to the left:
outPolygon.topleft = prevoutpolygon.topleft;
outPolygon.leftline = prevoutpolygon.leftline;
outPolygon.bottomleft = prevoutpolygon.bottomleft;
newOutPolygonRow.splice(newOutPolygonRow.length - 1, 1);
}
}
newOutPolygonRow.push(outPolygon);
}
if (yindex > 0)
{
// try to match the new polygons against the previous row:
//尝试将新多边形与上一行匹配:
let prevContinuedIndexes: { [key: number]: boolean } = {};
let matchedIndexes: { [key: number]: boolean } = {};
for (let i = 0; i < newOutPolygonRow.length; i++)
{
let thispolygon = newOutPolygonRow[i];
for (let ii = 0; ii < prevOutPolygonRow.length; ii++)
{
if (!matchedIndexes[ii])
{
// not already processed?
// We have a match if the sidelines are equal or if the top coordinates
// are on the sidelines of the previous polygon
let prevpolygon = prevOutPolygonRow[ii];
if (prevpolygon.bottomleft.distanceTo(thispolygon.topleft) < EPS)
{
if (prevpolygon.bottomright.distanceTo(thispolygon.topright) < EPS)
{
// Yes, the top of this polygon matches the bottom of the previous:
matchedIndexes[ii] = true;
// Now check if the joined polygon would remain convex:
let d1 = thispolygon.leftline.direction().x - prevpolygon.leftline.direction().x;
let d2 = thispolygon.rightline.direction().x - prevpolygon.rightline.direction().x;
let leftlinecontinues = Math.abs(d1) < EPS;
let rightlinecontinues = Math.abs(d2) < EPS;
let leftlineisconvex = leftlinecontinues || d1 >= 0;
let rightlineisconvex = rightlinecontinues || d2 >= 0;
if (leftlineisconvex && rightlineisconvex)
{
// yes, both sides have convex corners:
// This polygon will continue the previous polygon
thispolygon.outpolygon = prevpolygon.outpolygon;
thispolygon.leftlinecontinues = leftlinecontinues;
thispolygon.rightlinecontinues = rightlinecontinues;
prevContinuedIndexes[ii] = true;
}
break;
}
}
} // if(!prevcontinuedindexes[ii])
} // for ii
} // for i
for (let ii = 0; ii < prevOutPolygonRow.length; ii++)
{
if (!prevContinuedIndexes[ii])
{
// polygon ends here
// Finish the polygon with the last point(s):
let prevpolygon = prevOutPolygonRow[ii];
prevpolygon.outpolygon.rightpoints.push(prevpolygon.bottomright);
if (prevpolygon.bottomright.distanceTo(prevpolygon.bottomleft) > EPS)
{
// polygon ends with a horizontal line:
prevpolygon.outpolygon.leftpoints.push(prevpolygon.bottomleft);
}
// reverse the left half so we get a counterclockwise circle:
prevpolygon.outpolygon.leftpoints.reverse();
let points2d = prevpolygon.outpolygon.rightpoints.concat(prevpolygon.outpolygon.leftpoints);
let vertices = points2d.map(v => new Vertex3D(orthobasis.to3D(v)));
let polygon = new Polygon(vertices, plane);
destpolygons.push(polygon);
}
}
}
for (let i = 0; i < newOutPolygonRow.length; i++)
{
let thispolygon = newOutPolygonRow[i];
if (!thispolygon.outpolygon)
{
// polygon starts here:
thispolygon.outpolygon = {
leftpoints: [],
rightpoints: []
};
thispolygon.outpolygon.leftpoints.push(thispolygon.topleft);
if (thispolygon.topleft.distanceTo(thispolygon.topright) > EPS)
{
// we have a horizontal line at the top:
thispolygon.outpolygon.rightpoints.push(thispolygon.topright);
}
}
else
{
// continuation of a previous row
if (!thispolygon.leftlinecontinues)
{
thispolygon.outpolygon.leftpoints.push(thispolygon.topleft);
}
if (!thispolygon.rightlinecontinues)
{
thispolygon.outpolygon.rightpoints.push(thispolygon.topright);
}
}
}
prevOutPolygonRow = newOutPolygonRow;
// }
//#endregion
} // for yindex
}

@ -0,0 +1,482 @@
import { EPS, _CSGDEBUG } from "./constants";
import { Plane } from "./math/Plane";
import { Polygon, Type } from "./math/Polygon3";
import { Vector3D } from "./math/Vector3";
// # class PolygonTreeNode
// This class manages hierarchical splits of polygons
// At the top is a root node which doesn hold a polygon, only child PolygonTreeNodes
// Below that are zero or more 'top' nodes; each holds a polygon. The polygons can be in different planes
// splitByPlane() splits a node by a plane. If the plane intersects the polygon, two new child nodes
// are created holding the splitted polygon.
// getPolygons() retrieves the polygon from the tree. If for PolygonTreeNode the polygon is split but
// the two split parts (child nodes) are still intact, then the unsplit polygon is returned.
// This ensures that we can safely split a polygon into many fragments. If the fragments are untouched,
// getPolygons() will return the original unsplit polygon instead of the fragments.
// remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated
// since they are no longer intact.
// constructor creates the root node:
//此类管理多边形的层次分割
//顶部是一个根节点它不包含多边形只有子PolygonTreeNodes
//下面是零个或多个“顶部”节点; 每个都有一个多边形。 多边形可以位于不同的平面中
// splitByPlane按平面拆分节点。 如果平面与多边形相交,则会有两个新的子节点
//创建持有分割多边形。
// getPolygons从树中检索多边形。 如果对于PolygonTreeNode则多边形被拆分但是
//两个分割部分(子节点)仍然完好无损,然后返回未分割的多边形。
//这确保我们可以安全地将多边形拆分为多个片段。 如果碎片未受影响,
// getPolygons将返回原始的未分割多边形而不是片段。
// remove从树中删除多边形。 删除多边形后,父多边形将失效
//因为它们不再完好无损
//构造函数创建根节点:
class PolygonTreeNode
{
parent: PolygonTreeNode;
children: PolygonTreeNode[] = [];
polygon: Polygon;
removed: boolean = false;
constructor(polygon?: Polygon)
{
this.polygon = polygon;
}
// fill the tree with polygons. Should be called on the root node only; child nodes must
// always be a derivate (split) of the parent node.
addPolygons(polygons: Polygon[])
{
// new polygons can only be added to root node; children can only be splitted polygons
if (!this.isRootNode())
throw new Error("Assertion failed");
for (let polygon of polygons)
this.addChild(polygon);
}
// remove a node
// - the siblings become toplevel nodes
// - the parent is removed recursively
remove()
{
if (this.removed) return;
this.removed = true;
if (_CSGDEBUG)
{
if (this.isRootNode()) throw new Error("Assertion failed"); // can't remove root node
if (this.children.length) throw new Error("Assertion failed"); // we shouldn't remove nodes with children
}
// remove ourselves from the parent's children list:
let parentschildren = this.parent.children;
let i = parentschildren.indexOf(this);
if (i < 0) throw new Error("Assertion failed");
parentschildren.splice(i, 1);
// invalidate the parent's polygon, and of all parents above it:
this.parent.recursivelyInvalidatePolygon();
}
isRemoved()
{
return this.removed;
}
isRootNode()
{
return !this.parent;
}
// invert all polygons in the tree. Call on the root node
invert()
{
if (!this.isRootNode()) throw new Error("Assertion failed"); // can only call this on the root node
this.invertSub();
}
getPolygon(): Polygon
{
if (!this.polygon) throw new Error("Assertion failed"); // doesn't have a polygon, which means that it has been broken down
return this.polygon;
}
getPolygons(outPolygons: Polygon[] = []): Polygon[]
{
let children: PolygonTreeNode[] = [this];
let queue = [children];
for (let i = 0; i < queue.length; ++i)
{
// queue size can change in loop, don't cache length
children = queue[i];
for (let node of children)
{
if (node.polygon)
// the polygon hasn't been broken yet. We can ignore the children and return our polygon:
outPolygons.push(node.polygon);
else
// our polygon has been split up and broken, so gather all subpolygons from the children
queue.push(node.children);
}
}
return outPolygons;
}
// split the node by a plane; add the resulting nodes to the frontnodes and backnodes array
// If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays
// If the plane does intersect the polygon, two new child nodes are created for the front and back fragments,
// and added to both arrays.
splitByPlane(
plane: Plane,
coplanarFrontNodes: PolygonTreeNode[],
coplanarBackNodes: PolygonTreeNode[],
frontNodes: PolygonTreeNode[],
backNodes: PolygonTreeNode[]
)
{
if (this.children.length)
{
let queue = [this.children];
for (let i = 0; i < queue.length; i++)
{
// queue.length can increase, do not cache
let nodes = queue[i];
for (let j = 0, l = nodes.length; j < l; j++)
{
// ok to cache length
let node = nodes[j];
if (node.children.length)
queue.push(node.children);
else
{
// no children. Split the polygon:
node.splitByPlaneNotChildren(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes);
}
}
}
}
else
{
this.splitByPlaneNotChildren(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes);
}
}
// only to be called for nodes with no children
// 仅用于没有子节点的节点
private splitByPlaneNotChildren(
plane: Plane,
coplanarFrontNodes: PolygonTreeNode[],
coplanarBackNodes: PolygonTreeNode[],
frontNodes: PolygonTreeNode[],
backNodes: PolygonTreeNode[]
)
{
if (!this.polygon) return;
let polygon = this.polygon;
let bound = polygon.boundingSphere();
let sphereradius = bound[1] + EPS; // FIXME Why add imprecision?
let planenormal = plane.normal;
let spherecenter = bound[0];
let d = planenormal.dot(spherecenter) - plane.w;
if (d > sphereradius)
frontNodes.push(this);
else if (d < -sphereradius)
backNodes.push(this);
else
{
let splitresult = polygon.splitByPlane(plane);
switch (splitresult.type)
{
case Type.CoplanarFront:
coplanarFrontNodes.push(this);
break;
case Type.CoplanarBack:
coplanarBackNodes.push(this);
break;
case Type.Front:
frontNodes.push(this);
break;
case Type.Back:
backNodes.push(this);
break;
case Type.Spanning:
if (splitresult.front)
{
let frontNode = this.addChild(splitresult.front);
frontNodes.push(frontNode);
}
if (splitresult.back)
{
let backNode = this.addChild(splitresult.back);
backNodes.push(backNode);
}
break;
}
}
}
// add child to a node
// this should be called whenever the polygon is split
// a child should be created for every fragment of the split polygon
// returns the newly created child
addChild(polygon: Polygon): PolygonTreeNode
{
let newchild = new PolygonTreeNode(polygon);
newchild.parent = this;
this.children.push(newchild);
return newchild;
}
invertSub()
{
let queue: PolygonTreeNode[][] = [[this]];
for (let i = 0; i < queue.length; i++)
{
let children = queue[i];
for (let j = 0, l = children.length; j < l; j++)
{
let node = children[j];
if (node.polygon)
node.polygon = node.polygon.flipped();
queue.push(node.children);
}
}
}
recursivelyInvalidatePolygon()
{
let node: PolygonTreeNode = this;
while (node.polygon)
{
node.polygon = null;
if (node.parent)
node = node.parent;
}
}
}
// # class Tree
// This is the root of a BSP tree
// We are using this separate class for the root of the tree, to hold the PolygonTreeNode root
// The actual tree is kept in this.rootnode
export class Tree
{
polygonTree = new PolygonTreeNode();
rootNode = new Node(null);
constructor(polygons: Polygon[])
{
this.addPolygons(polygons);
}
invert()
{
this.polygonTree.invert();
this.rootNode.invert();
}
// Remove all polygons in this BSP tree that are inside the other BSP tree
/**
* this tree BSPBSP
* @param tree
* @param [alsoRemovecoplanarFront=false]
*/
clipTo(tree: Tree, alsoRemovecoplanarFront = false)
{
this.rootNode.clipTo(tree, alsoRemovecoplanarFront);
}
allPolygons()
{
return this.polygonTree.getPolygons();
}
addPolygons(polygons: Polygon[])
{
let polygonTreeNodes = polygons.map((p) => this.polygonTree.addChild(p));
this.rootNode.addPolygonTreeNodes(polygonTreeNodes);
}
}
// # 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.
// Polygons are not stored directly in the tree, but in PolygonTreeNodes, stored in
// this.polygontreenodes. Those PolygonTreeNodes are children of the owning
// Tree.polygonTree
// This is not a leafy BSP tree since there is
// no distinction between internal and leaf nodes.
class Node
{
plane: Plane;
front: Node;
back: Node;
polygonTreeNodes: PolygonTreeNode[] = [];
parent: Node;
constructor(parent: Node)
{
this.parent = parent;
}
// Convert solid space to empty space and empty space to solid space.
invert()
{
let queue: Node[] = [this];
for (let i = 0; i < queue.length; i++)
{
let node = queue[i];
if (node.plane) node.plane = node.plane.flipped();
if (node.front) queue.push(node.front);
if (node.back) queue.push(node.back);
let temp = node.front;
node.front = node.back;
node.back = temp;
}
}
// clip polygontreenodes to our plane
// calls remove() for all clipped PolygonTreeNodes
//将polygontreenodes剪辑到我们的飞机上
//为所有剪切的PolygonTreeNodes调用remove
clipPolygons(polygonTreeNodes: PolygonTreeNode[], alsoRemoveCoplanarFront: boolean)
{
interface D
{
node: Node;
polygonTreeNodes: PolygonTreeNode[];
}
let args: D = { node: this, polygonTreeNodes };
let stack: D[] = [];
do
{
let node = args.node;
let polygonTreeNodes1 = args.polygonTreeNodes;
// begin "function"
if (node.plane)
{
let backnodes: PolygonTreeNode[] = [];
let frontnodes: PolygonTreeNode[] = [];
let coplanarfrontnodes = alsoRemoveCoplanarFront ? backnodes : frontnodes;
let plane = node.plane;
for (let node1 of polygonTreeNodes1)
{
if (!node1.isRemoved())
node1.splitByPlane(plane, coplanarfrontnodes, backnodes, frontnodes, backnodes);
}
if (node.front && frontnodes.length > 0)
stack.push({ node: node.front, polygonTreeNodes: frontnodes });
let numbacknodes = backnodes.length;
if (node.back && numbacknodes > 0)
stack.push({ node: node.back, polygonTreeNodes: backnodes });
else
{
// there's nothing behind this plane. Delete the nodes behind this plane:
// 这架飞机背后什么也没有。 删除此平面后面的节点:
for (let i = 0; i < numbacknodes; i++)
backnodes[i].remove();
}
}
args = stack.pop();
}
while (args);
}
// Remove all polygons in this BSP tree that are inside the other BSP tree
// `tree`.
clipTo(tree: Tree, alsoRemovecoplanarFront: boolean)
{
let node: Node = this;
let stack: Node[] = [];
do
{
if (node.polygonTreeNodes.length > 0)
{
tree.rootNode.clipPolygons(
node.polygonTreeNodes,
alsoRemovecoplanarFront
);
}
if (node.front) stack.push(node.front);
if (node.back) stack.push(node.back);
node = stack.pop();
}
while (node);
}
addPolygonTreeNodes(polygonTreeNodes: PolygonTreeNode[])
{
interface D
{
node: Node;
polygontreenodes: PolygonTreeNode[];
}
let args: D = { node: this, polygontreenodes: polygonTreeNodes };
let stack: D[] = [];
do
{
let node = args.node;
polygonTreeNodes = args.polygontreenodes;
if (polygonTreeNodes.length === 0)
{
args = stack.pop();
continue;
}
if (!node.plane)
{
let bestplane = polygonTreeNodes[Math.floor(polygonTreeNodes.length / 2)].getPolygon().plane;
node.plane = bestplane;
}
let frontNodes: PolygonTreeNode[] = [];
let backNodes: PolygonTreeNode[] = [];
for (let i = 0, n = polygonTreeNodes.length; i < n; ++i)
{
polygonTreeNodes[i].splitByPlane(
node.plane,
node.polygonTreeNodes,
backNodes,
frontNodes,
backNodes
);
}
if (frontNodes.length > 0)
{
if (!node.front) node.front = new Node(node);
stack.push({ node: node.front, polygontreenodes: frontNodes });
}
if (backNodes.length > 0)
{
if (!node.back) node.back = new Node(node);
stack.push({ node: node.back, polygontreenodes: backNodes });
}
args = stack.pop();
}
while (args);
}
getParentPlaneNormals(normals: Vector3D[], maxdepth: number)
{
if (maxdepth > 0)
{
if (this.parent)
{
normals.push(this.parent.plane.normal);
this.parent.getParentPlaneNormals(normals, maxdepth - 1);
}
}
}
}

@ -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…
Cancel
Save