three-base 2.0.169 → 2.0.175
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FPScontrol.d.ts +55 -30
- package/FPScontrol.js +278 -225
- package/FPScontrol.ts +177 -127
- package/Ground1/Ground1.d.ts +4 -2
- package/Ground1/Ground1.js +77 -57
- package/Ground1/Ground1.ts +77 -57
- package/Mirror.js +0 -4
- package/Mirror.ts +0 -4
- package/MktMesh/MktMesh.d.ts +8 -3
- package/MktMesh/MktMesh.js +43 -14
- package/MktMesh/MktMesh.ts +40 -14
- package/MktMesh/mktMappingShader.d.ts +6 -5
- package/MktMesh/mktMappingShader.js +26 -19
- package/MktMesh/mktMappingShader.ts +28 -19
- package/MktMesh/mktShader.d.ts +127 -58
- package/MktMesh/mktShader.js +192 -117
- package/MktMesh/mktShader.ts +251 -127
- package/MktMesh/panoSlotRoster.d.ts +2 -2
- package/MktMesh/panoSlotRoster.js +9 -7
- package/MktMesh/panoSlotRoster.ts +9 -7
- package/MktMesh/panoTileStitcher.d.ts +20 -13
- package/MktMesh/panoTileStitcher.js +97 -76
- package/MktMesh/panoTileStitcher.ts +119 -90
- package/MktMesh/types.d.ts +5 -1
- package/MktMesh/types.ts +5 -1
- package/PipeMesh/PipeJoint.d.ts +22 -0
- package/PipeMesh/PipeJoint.js +33 -0
- package/PipeMesh/PipeJoint.ts +38 -0
- package/PipeMesh/PipeMesh.d.ts +94 -22
- package/PipeMesh/PipeMesh.js +192 -53
- package/PipeMesh/PipeMesh.ts +223 -76
- package/PipeMesh/PipeStraight.d.ts +19 -0
- package/PipeMesh/PipeStraight.js +34 -0
- package/PipeMesh/PipeStraight.ts +45 -0
- package/PipeMesh/axisSnap.d.ts +52 -0
- package/PipeMesh/axisSnap.js +52 -0
- package/PipeMesh/axisSnap.ts +93 -0
- package/PipeMesh/directRoute.d.ts +85 -0
- package/PipeMesh/directRoute.js +126 -0
- package/PipeMesh/directRoute.ts +190 -0
- package/PipeMesh/index.d.ts +6 -0
- package/PipeMesh/index.js +6 -0
- package/PipeMesh/index.ts +7 -1
- package/PipeMesh/pipeMaterial.d.ts +16 -0
- package/PipeMesh/pipeMaterial.js +23 -0
- package/PipeMesh/pipeMaterial.ts +26 -0
- package/ThreeBase.d.ts +2 -49
- package/ThreeBase.js +43 -164
- package/ThreeBase.ts +1176 -1314
- package/instanced/InstancedBatch.d.ts +14 -1
- package/instanced/InstancedBatch.js +53 -3
- package/instanced/InstancedBatch.ts +62 -3
- package/instanced/InstancedChild.d.ts +24 -0
- package/instanced/InstancedChild.js +40 -0
- package/instanced/InstancedChild.ts +41 -0
- package/instanced/InstancedGroup.d.ts +48 -0
- package/instanced/InstancedGroup.js +45 -1
- package/instanced/InstancedGroup.ts +85 -0
- package/package.json +2 -7
- package/pass/bloom_pass.js +7 -14
- package/pass/bloom_pass.ts +7 -16
- package/pass/cover_pass.js +7 -15
- package/pass/cover_pass.ts +7 -17
- package/pass/debugDepthState.d.ts +16 -8
- package/pass/debugDepthState.js +4 -0
- package/pass/debugDepthState.ts +27 -9
- package/pass/glow_pass.js +19 -110
- package/pass/glow_pass.ts +19 -115
- package/pass/ssao_pass.d.ts +14 -59
- package/pass/ssao_pass.js +66 -171
- package/pass/ssao_pass.ts +63 -207
- package/pass/stroke_pass.js +13 -16
- package/pass/stroke_pass.ts +14 -18
- package/shaders/baseDepthVisShader.d.ts +1 -1
- package/shaders/baseDepthVisShader.js +1 -1
- package/shaders/baseDepthVisShader.ts +1 -1
- package/shaders/debugDepthCompareShader.d.ts +2 -2
- package/shaders/debugDepthCompareShader.js +2 -2
- package/shaders/debugDepthCompareShader.ts +2 -2
- package/shaders/debugDepthJudgeShader.d.ts +1 -1
- package/shaders/debugDepthJudgeShader.js +1 -1
- package/shaders/debugDepthJudgeShader.ts +1 -1
- package/PipeMesh/PathGeometry.d.ts +0 -44
- package/PipeMesh/PathGeometry.js +0 -307
- package/PipeMesh/PathGeometry.ts +0 -343
- package/PipeMesh/PathPoint.d.ts +0 -17
- package/PipeMesh/PathPoint.js +0 -31
- package/PipeMesh/PathPoint.ts +0 -36
- package/PipeMesh/PathPointList.d.ts +0 -31
- package/PipeMesh/PathPointList.js +0 -209
- package/PipeMesh/PathPointList.ts +0 -227
- package/PipeMesh/PathTubeGeometry.d.ts +0 -25
- package/PipeMesh/PathTubeGeometry.js +0 -130
- package/PipeMesh/PathTubeGeometry.ts +0 -146
- package/WaterFlow/FloatingDuck.d.ts +0 -88
- package/WaterFlow/FloatingDuck.js +0 -307
- package/WaterFlow/FloatingDuck.ts +0 -445
- package/WaterFlow/GPUFluidSimulation.d.ts +0 -56
- package/WaterFlow/GPUFluidSimulation.js +0 -785
- package/WaterFlow/GPUFluidSimulation.ts +0 -827
- package/WaterFlow/ReservoirSystem.d.ts +0 -106
- package/WaterFlow/ReservoirSystem.js +0 -621
- package/WaterFlow/ReservoirSystem.ts +0 -754
- package/WaterFlow/SPHField.d.ts +0 -35
- package/WaterFlow/SPHField.js +0 -204
- package/WaterFlow/SPHField.ts +0 -228
- package/WaterFlow/ScreenSpaceFluidPass.d.ts +0 -69
- package/WaterFlow/ScreenSpaceFluidPass.js +0 -736
- package/WaterFlow/ScreenSpaceFluidPass.ts +0 -789
- package/WaterFlow/ShallowWaterSurface.d.ts +0 -45
- package/WaterFlow/ShallowWaterSurface.js +0 -441
- package/WaterFlow/ShallowWaterSurface.ts +0 -510
- package/WaterFlow/WaterFlow.d.ts +0 -87
- package/WaterFlow/WaterFlow.js +0 -171
- package/WaterFlow/WaterFlow.ts +0 -219
- package/WaterFlow/types.d.ts +0 -161
- package/WaterFlow/types.js +0 -1
- package/WaterFlow/types.ts +0 -189
- package/WaterFlow/waterFlowPass.d.ts +0 -3
- package/WaterFlow/waterFlowPass.js +0 -20
- package/WaterFlow/waterFlowPass.ts +0 -40
- package/shaders/debugExcludedShader.d.ts +0 -13
- package/shaders/debugExcludedShader.js +0 -46
- package/shaders/debugExcludedShader.ts +0 -48
package/PipeMesh/PipeMesh.ts
CHANGED
|
@@ -1,76 +1,223 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
import { Color, Group } from "three";
|
|
2
|
+
import InstancedChild, { instancedChildRegistry } from "../instanced/InstancedChild";
|
|
3
|
+
import PipeJoint from "./PipeJoint";
|
|
4
|
+
import PipeStraight from "./PipeStraight";
|
|
5
|
+
import { PIPE_DEFAULT_COLOR } from "./pipeMaterial";
|
|
6
|
+
import { planDirectLayout, makePipeJson, parsePipeJson, type PipeJson, type Vec3 } from "./directRoute";
|
|
7
|
+
|
|
8
|
+
export interface PipeMeshProps {
|
|
9
|
+
/** 控制点(米)。**相邻两点之间直连一根直管**(可斜),拐角自动补一颗同径球 */
|
|
10
|
+
points?: Vec3[];
|
|
11
|
+
/** 管道直径(米),默认 1 */
|
|
12
|
+
diameter?: number;
|
|
13
|
+
/** 管壁颜色(逐实例色,不是材质底色) */
|
|
14
|
+
color?: Color | number | string;
|
|
15
|
+
/** 多 InstancedGroup 共存时的归属(配合 `new InstancedGroup(owner)`,如预览弹窗) */
|
|
16
|
+
owner?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 组合管道(点与点直连):**直管 + 拐角补角球全部是 InstancedChild**,由 ThreeBase 的 InstancedGroup
|
|
21
|
+
* 每帧自动合批。
|
|
22
|
+
*
|
|
23
|
+
* ⭐ 为什么这样划算:全场景只有两份几何(单位圆柱、单位球),所以**不管多少折点,
|
|
24
|
+
* draw call 恒为 2**;改直径/改长度只动实例矩阵,不重建任何几何。旧的 PipeMesh(现
|
|
25
|
+
* `three-base/PipeMeshLegacy/`)是一条折线扫一张合并 BufferGeometry,改点要重算顶点,两者路线互斥。
|
|
26
|
+
*
|
|
27
|
+
* ⭐ 走线口径(皇上旨 2026-10-25「直接就是点与点直接连接,最简单的方案」):
|
|
28
|
+
* 控制点 i 与 i+1 之间就是一根直管 —— 长度 = 两点欧氏距离、方向 = 连线单位向量、中点 = 两点中点。
|
|
29
|
+
* 没有轴序、没有正交拆段、没有 90° 弯头。走线算术全在 `directRoute.ts`(零依赖,可纯数字断言)。
|
|
30
|
+
*
|
|
31
|
+
* 用法:
|
|
32
|
+
* ```ts
|
|
33
|
+
* const pipe = new PipeMesh({ points: [[-4,0,0],[0,3,0],[4,3,-4]], diameter: 1 });
|
|
34
|
+
* scene.add(pipe);
|
|
35
|
+
* pipe.setDiameter(0.6); // 只改缩放
|
|
36
|
+
* pipe.movePoint(1, [0, 5, 0]); // 只改控制点,走线自动重算
|
|
37
|
+
* const json = pipe.exportJson(); // 存档 / 导出
|
|
38
|
+
* PipeMesh.fromJson(json); // 引用 JSON 重建
|
|
39
|
+
* pipe.setShown(false); // 藏起这条管道(走注册表,不是 visible)
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ 实例池是**差分复用**的:段数变少时多出来的实例会被摘出注册表并 dispose,不会留在注册表里
|
|
43
|
+
* 继续被合批(注册表是全局 Set,`scene.remove` 停不了它)。
|
|
44
|
+
*/
|
|
45
|
+
export default class PipeMesh extends Group {
|
|
46
|
+
points: Vec3[] = [];
|
|
47
|
+
diameter: number = 1;
|
|
48
|
+
color: Color = new Color(PIPE_DEFAULT_COLOR);
|
|
49
|
+
/** 最近一次走线的退化提示(供面板显示;不影响渲染) */
|
|
50
|
+
warnings: string[] = [];
|
|
51
|
+
/** 直管实例(段数 = 有效控制点对数) */
|
|
52
|
+
readonly straights: PipeStraight[] = [];
|
|
53
|
+
/** 拐角补角球实例(内部控制点各一颗) */
|
|
54
|
+
readonly joints: PipeJoint[] = [];
|
|
55
|
+
|
|
56
|
+
private _owner?: string;
|
|
57
|
+
private _shown: boolean = true;
|
|
58
|
+
|
|
59
|
+
constructor(props: PipeMeshProps = {}) {
|
|
60
|
+
super();
|
|
61
|
+
(this as { type: string }).type = "PipeMesh";
|
|
62
|
+
this.name = "PipeMesh";
|
|
63
|
+
this._owner = props.owner;
|
|
64
|
+
this.diameter = props.diameter ?? 1;
|
|
65
|
+
if (props.color !== undefined) this.color.set(props.color);
|
|
66
|
+
this.points = (props.points ?? []).map((p) => [p[0], p[1], p[2]] as Vec3);
|
|
67
|
+
this.rebuild();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 显隐:走注册表摘除/挂回,而不是靠 `visible` 标志。
|
|
72
|
+
* ⚠️ 实例化那条路的真实几何在 InstancedGroup 的 batch 里(不在这棵树下面),
|
|
73
|
+
* 所以 `pipe.visible = false` 对它**不起作用**;要藏起来就调本方法。
|
|
74
|
+
*/
|
|
75
|
+
setShown(shown: boolean): this {
|
|
76
|
+
this._shown = shown;
|
|
77
|
+
for (const child of this._allInstances()) {
|
|
78
|
+
if (shown) instancedChildRegistry.add(child);
|
|
79
|
+
else instancedChildRegistry.delete(child);
|
|
80
|
+
}
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get shown(): boolean {
|
|
85
|
+
return this._shown;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 按当前 points/diameter 重算实例位姿;只在段数变化时增删实例 */
|
|
89
|
+
rebuild(): this {
|
|
90
|
+
const plan = planDirectLayout(this.points, { diameter: this.diameter });
|
|
91
|
+
this.warnings = plan.warnings;
|
|
92
|
+
|
|
93
|
+
this._fitPool(this.straights, plan.straights.length, () => new PipeStraight());
|
|
94
|
+
this._fitPool(this.joints, plan.joints.length, () => new PipeJoint());
|
|
95
|
+
|
|
96
|
+
plan.straights.forEach((part, i) => {
|
|
97
|
+
const inst = this.straights[i];
|
|
98
|
+
inst.applyAt(part.center, part.dir, this.diameter, part.length);
|
|
99
|
+
inst.setPartIndex(part.index);
|
|
100
|
+
inst.setColor(this.color);
|
|
101
|
+
});
|
|
102
|
+
plan.joints.forEach((part, i) => {
|
|
103
|
+
const inst = this.joints[i];
|
|
104
|
+
inst.applyAt(part.center, this.diameter);
|
|
105
|
+
inst.setPartIndex(part.index);
|
|
106
|
+
inst.setColor(this.color);
|
|
107
|
+
});
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private _allInstances(): InstancedChild[] {
|
|
112
|
+
return [...this.straights, ...this.joints];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 把池子拉到目标长度:不足 new + add 进本组,超出摘出并 dispose(连带注册表) */
|
|
116
|
+
private _fitPool<T extends InstancedChild>(pool: T[], want: number, make: () => T): void {
|
|
117
|
+
while (pool.length < want) {
|
|
118
|
+
const inst = make();
|
|
119
|
+
if (this._owner) inst.setOwner(this._owner);
|
|
120
|
+
this.add(inst);
|
|
121
|
+
pool.push(inst);
|
|
122
|
+
}
|
|
123
|
+
while (pool.length > want) {
|
|
124
|
+
const inst = pool.pop() as T;
|
|
125
|
+
this.remove(inst);
|
|
126
|
+
inst.dispose();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ===== 参数入口:每一条都收口到 rebuild(),避免各处各自改矩阵 =====
|
|
131
|
+
|
|
132
|
+
setPoints(points: Vec3[]): this {
|
|
133
|
+
this.points = points.map((p) => [p[0], p[1], p[2]] as Vec3);
|
|
134
|
+
return this.rebuild();
|
|
135
|
+
}
|
|
136
|
+
/** 改单个控制点(拖拽现场用,比整表重设省一次数组拷贝) */
|
|
137
|
+
movePoint(index: number, p: Vec3): this {
|
|
138
|
+
if (index < 0 || index >= this.points.length) return this;
|
|
139
|
+
this.points[index] = [p[0], p[1], p[2]];
|
|
140
|
+
return this.rebuild();
|
|
141
|
+
}
|
|
142
|
+
addPoint(p: Vec3): this {
|
|
143
|
+
this.points.push([p[0], p[1], p[2]]);
|
|
144
|
+
return this.rebuild();
|
|
145
|
+
}
|
|
146
|
+
insertPoint(index: number, p: Vec3): this {
|
|
147
|
+
if (index < 0 || index > this.points.length) return this;
|
|
148
|
+
this.points.splice(index, 0, [p[0], p[1], p[2]]);
|
|
149
|
+
return this.rebuild();
|
|
150
|
+
}
|
|
151
|
+
removePoint(index: number): this {
|
|
152
|
+
if (index < 0 || index >= this.points.length) return this;
|
|
153
|
+
this.points.splice(index, 1);
|
|
154
|
+
return this.rebuild();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 控制点顺序整体颠倒(皇上旨 2026-10-25「做一个节点倒序的功能,让管道点的顺序颠倒一下」)。
|
|
158
|
+
*
|
|
159
|
+
* ⚠️ 直连方案下几何**逐段等价**(圆柱两端对称、球压在点上,都不认方向)⇒ 画面上看不出变化,
|
|
160
|
+
* 变的是**数据序**:存档 / 导出 JSON / 将来带方向性的特性(流向箭头、渐变、开孔位置)都按这个序。
|
|
161
|
+
* 所以别指望点一下画面有动静 —— 要验它,去面板看「控制点 #N」的编号,或导出 JSON 比对。
|
|
162
|
+
*/
|
|
163
|
+
reversePoints(): this {
|
|
164
|
+
this.points.reverse();
|
|
165
|
+
return this.rebuild();
|
|
166
|
+
}
|
|
167
|
+
setDiameter(d: number): this {
|
|
168
|
+
this.diameter = d;
|
|
169
|
+
return this.rebuild();
|
|
170
|
+
}
|
|
171
|
+
setColor(c: Color | number | string): this {
|
|
172
|
+
this.color.set(c);
|
|
173
|
+
// 颜色走逐实例 instanceColor,与位姿无关 ⇒ 不动矩阵、不必 rebuild
|
|
174
|
+
for (const inst of this._allInstances()) inst.setColor(this.color);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ===== JSON:对象与项目存档共用的形状 =====
|
|
179
|
+
|
|
180
|
+
/** 导出当前管道的 JSON 数据(不碰 three 的 Object3D 序列化格式,那个是场景树用的) */
|
|
181
|
+
exportJson(): PipeJson {
|
|
182
|
+
return makePipeJson(
|
|
183
|
+
this.points.map((p) => [p[0], p[1], p[2]] as Vec3),
|
|
184
|
+
this.diameter,
|
|
185
|
+
`#${this.color.getHexString()}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 引用 JSON 重建当前管道:非法数据不动现场,返回人话原因 */
|
|
190
|
+
importJson(input: unknown): { ok: boolean; reason?: string } {
|
|
191
|
+
const parsed = parsePipeJson(input);
|
|
192
|
+
if (!parsed.ok || !parsed.json) return { ok: false, reason: parsed.reason ?? "JSON 解析失败" };
|
|
193
|
+
const json = parsed.json;
|
|
194
|
+
this.diameter = json.diameter;
|
|
195
|
+
if (json.color) this.color.set(json.color);
|
|
196
|
+
this.points = json.points.map((p) => [p[0], p[1], p[2]] as Vec3);
|
|
197
|
+
this.rebuild();
|
|
198
|
+
return { ok: true };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 用 JSON 起一个新管道对象;失败时 pipe 为 undefined,reason 可直接弹给用户 */
|
|
202
|
+
static fromJson(input: unknown): { pipe?: PipeMesh; ok: boolean; reason?: string } {
|
|
203
|
+
const parsed = parsePipeJson(input);
|
|
204
|
+
if (!parsed.ok || !parsed.json) return { ok: false, reason: parsed.reason ?? "JSON 解析失败" };
|
|
205
|
+
const json = parsed.json;
|
|
206
|
+
const pipe = new PipeMesh({
|
|
207
|
+
points: json.points,
|
|
208
|
+
diameter: json.diameter,
|
|
209
|
+
color: json.color,
|
|
210
|
+
});
|
|
211
|
+
return { pipe, ok: true };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 摘掉全部实例并释放注册表登记(组本身留在 scene 里由调用方 remove) */
|
|
215
|
+
dispose(): void {
|
|
216
|
+
for (const inst of this._allInstances()) {
|
|
217
|
+
this.remove(inst);
|
|
218
|
+
inst.dispose();
|
|
219
|
+
}
|
|
220
|
+
this.straights.length = 0;
|
|
221
|
+
this.joints.length = 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CylinderGeometry } from "three";
|
|
2
|
+
import InstancedChild from "../instanced/InstancedChild";
|
|
3
|
+
/**
|
|
4
|
+
* 直管实例:**直径 1、长度 1、沿 +Y、端面开口**的单位圆柱。
|
|
5
|
+
*
|
|
6
|
+
* 长度靠实例矩阵的 Y 缩放拉伸(非均匀缩放不伤法线:圆柱侧面法线是水平的,X/Z 缩放等比 ⇒ 方向不变;
|
|
7
|
+
* 端面本来就 openEnded 没有盖子),半径靠 X/Z 缩放,因此全场景只有这一份几何、一个 batch。
|
|
8
|
+
*/
|
|
9
|
+
export default class PipeStraight extends InstancedChild {
|
|
10
|
+
static geometry: CylinderGeometry;
|
|
11
|
+
static material: import("three").MeshStandardMaterial;
|
|
12
|
+
/** 控制台取证用:第几段直管(由 PipeMesh 容器在 rebuild 时写入) */
|
|
13
|
+
partIndex: number;
|
|
14
|
+
constructor();
|
|
15
|
+
/** 落到某段:中心位置 + 轴向 + [直径, 净长, 直径] 缩放 */
|
|
16
|
+
applyAt(center: [number, number, number], dir: [number, number, number], diameter: number, length: number): void;
|
|
17
|
+
/** 段序号变化时同步控制台名字(复用实例时避免留着上一段的编号) */
|
|
18
|
+
setPartIndex(i: number): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CylinderGeometry, Vector3 } from "three";
|
|
2
|
+
import InstancedChild from "../instanced/InstancedChild";
|
|
3
|
+
import { pipeMaterial } from "./pipeMaterial";
|
|
4
|
+
/** 单位圆柱的轴向:几何按 +Y 生成,实例旋转把 +Y 对到走线方向 */
|
|
5
|
+
const UP = new Vector3(0, 1, 0);
|
|
6
|
+
const _dir = new Vector3();
|
|
7
|
+
/**
|
|
8
|
+
* 直管实例:**直径 1、长度 1、沿 +Y、端面开口**的单位圆柱。
|
|
9
|
+
*
|
|
10
|
+
* 长度靠实例矩阵的 Y 缩放拉伸(非均匀缩放不伤法线:圆柱侧面法线是水平的,X/Z 缩放等比 ⇒ 方向不变;
|
|
11
|
+
* 端面本来就 openEnded 没有盖子),半径靠 X/Z 缩放,因此全场景只有这一份几何、一个 batch。
|
|
12
|
+
*/
|
|
13
|
+
export default class PipeStraight extends InstancedChild {
|
|
14
|
+
static geometry = new CylinderGeometry(0.5, 0.5, 1, 48, 1, true);
|
|
15
|
+
static material = pipeMaterial;
|
|
16
|
+
/** 控制台取证用:第几段直管(由 PipeMesh 容器在 rebuild 时写入) */
|
|
17
|
+
partIndex = -1;
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.type = "PipeStraight";
|
|
21
|
+
this.name = "PipeStraight";
|
|
22
|
+
}
|
|
23
|
+
/** 落到某段:中心位置 + 轴向 + [直径, 净长, 直径] 缩放 */
|
|
24
|
+
applyAt(center, dir, diameter, length) {
|
|
25
|
+
this.position.set(center[0], center[1], center[2]);
|
|
26
|
+
this.quaternion.setFromUnitVectors(UP, _dir.set(dir[0], dir[1], dir[2]));
|
|
27
|
+
this.scale.set(diameter, length, diameter);
|
|
28
|
+
}
|
|
29
|
+
/** 段序号变化时同步控制台名字(复用实例时避免留着上一段的编号) */
|
|
30
|
+
setPartIndex(i) {
|
|
31
|
+
this.partIndex = i;
|
|
32
|
+
this.name = `PipeStraight[${i}]`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CylinderGeometry, Vector3 } from "three";
|
|
2
|
+
import InstancedChild from "../instanced/InstancedChild";
|
|
3
|
+
import { pipeMaterial } from "./pipeMaterial";
|
|
4
|
+
|
|
5
|
+
/** 单位圆柱的轴向:几何按 +Y 生成,实例旋转把 +Y 对到走线方向 */
|
|
6
|
+
const UP = new Vector3(0, 1, 0);
|
|
7
|
+
const _dir = new Vector3();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 直管实例:**直径 1、长度 1、沿 +Y、端面开口**的单位圆柱。
|
|
11
|
+
*
|
|
12
|
+
* 长度靠实例矩阵的 Y 缩放拉伸(非均匀缩放不伤法线:圆柱侧面法线是水平的,X/Z 缩放等比 ⇒ 方向不变;
|
|
13
|
+
* 端面本来就 openEnded 没有盖子),半径靠 X/Z 缩放,因此全场景只有这一份几何、一个 batch。
|
|
14
|
+
*/
|
|
15
|
+
export default class PipeStraight extends InstancedChild {
|
|
16
|
+
static geometry = new CylinderGeometry(0.5, 0.5, 1, 48, 1, true);
|
|
17
|
+
static material = pipeMaterial;
|
|
18
|
+
|
|
19
|
+
/** 控制台取证用:第几段直管(由 PipeMesh 容器在 rebuild 时写入) */
|
|
20
|
+
partIndex: number = -1;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
(this as { type: string }).type = "PipeStraight";
|
|
25
|
+
this.name = "PipeStraight";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 落到某段:中心位置 + 轴向 + [直径, 净长, 直径] 缩放 */
|
|
29
|
+
applyAt(
|
|
30
|
+
center: [number, number, number],
|
|
31
|
+
dir: [number, number, number],
|
|
32
|
+
diameter: number,
|
|
33
|
+
length: number
|
|
34
|
+
): void {
|
|
35
|
+
this.position.set(center[0], center[1], center[2]);
|
|
36
|
+
this.quaternion.setFromUnitVectors(UP, _dir.set(dir[0], dir[1], dir[2]));
|
|
37
|
+
this.scale.set(diameter, length, diameter);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 段序号变化时同步控制台名字(复用实例时避免留着上一段的编号) */
|
|
41
|
+
setPartIndex(i: number): void {
|
|
42
|
+
this.partIndex = i;
|
|
43
|
+
this.name = `PipeStraight[${i}]`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 控制点轴向吸附(纯数字,零运行时依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 皇上旨 2026-10-25:「添加节点和编辑节点的时候,实时计算当前节点在 x/y/z 轴上的投影距离,
|
|
5
|
+
* 距离谁最近就改到哪里,让管道随时保持横平竖直。」
|
|
6
|
+
*
|
|
7
|
+
* ⭐ 什么叫「投影到轴上」——不是投影到世界坐标轴,而是投影到**过参考点(相邻控制点)的三条轴线**:
|
|
8
|
+
* 三条候选各保留一个坐标、另两个坐标抄参考点:
|
|
9
|
+
* 只沿 X 走 ⇒ [p.x, ref.y, ref.z],偏离量 = √(Δy² + Δz²)
|
|
10
|
+
* 只沿 Y 走 ⇒ [ref.x, p.y, ref.z],偏离量 = √(Δx² + Δz²)
|
|
11
|
+
* 只沿 Z 走 ⇒ [ref.x, ref.y, p.z],偏离量 = √(Δx² + Δy²)
|
|
12
|
+
* 取偏离量最小的那条 ⇒ 该段只剩一根轴有位移 ⇒ 横平竖直。
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ 三个候选都**保留待吸附点自身的那根轴坐标**(比如「只沿 X」保留 p.x)——这是关键:
|
|
15
|
+
* 沿 X 箭头拖动时 p.x 能自由变化,吸附不会把点钉死;被拉回的只是另外两根轴的偏移。
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ 这里改的是**控制点本身**(数据层),不是走线算术:PipeMesh 仍是相邻点直连(directRoute.ts),
|
|
18
|
+
* 只是点的位置被挪到轴线上。⛔ 别在这里插折点 —— 那是已删除的 orthogonalRoute 老路,
|
|
19
|
+
* 两件事的区别是:正交走线「自动加拐点把斜线拆成三段」,这里是「把点挪正,段还是那一段」。
|
|
20
|
+
*
|
|
21
|
+
* 留成零运行时依赖(只用 `import type`,编译时擦除)⇒ `test/pipeRoute.test.js` 能直接 require
|
|
22
|
+
* 编译产物逐条断言,不必起浏览器。
|
|
23
|
+
*/
|
|
24
|
+
import type { Vec3 } from "./directRoute";
|
|
25
|
+
/** 轴名(与编辑页面板的 X / Y / Z 三行同口径) */
|
|
26
|
+
export type AxisName = "x" | "y" | "z";
|
|
27
|
+
/** 吸附结果:落点 + 保留的轴 + 原位置到落点的欧氏距离 */
|
|
28
|
+
export interface AxisSnapResult {
|
|
29
|
+
/** 吸附后的位置(新数组,不改入参) */
|
|
30
|
+
point: Vec3;
|
|
31
|
+
/** 被保留(即该段唯一有位移)的轴;另两根轴已对齐到参考点 */
|
|
32
|
+
axis: AxisName;
|
|
33
|
+
/** 原位置到吸附位置的欧氏距离(米)= 这次「挪了多少」 */
|
|
34
|
+
distance: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 沿指定轴吸附:保留 p 在这根轴上的坐标,另外两根轴抄参考点。
|
|
38
|
+
* 用于面板数字输入 —— 用户输入 X 就是「沿 X 走这么远」,Y/Z 自动回落到来路,段仍是横平竖直。
|
|
39
|
+
*/
|
|
40
|
+
export declare function snapAlongAxis(p: Vec3, ref: Vec3, axis: AxisName): Vec3;
|
|
41
|
+
/**
|
|
42
|
+
* 把 p 吸到「过 ref 的三条轴线」上最近的那条。
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ 永远返回结果(不返回 null):p 已经在某条轴线上时,该候选的 distance 就是 0,
|
|
45
|
+
* 写回后坐标一个不变 —— 调用方不必再判空。
|
|
46
|
+
*/
|
|
47
|
+
export declare function snapToNearestAxis(p: Vec3, ref: Vec3): AxisSnapResult;
|
|
48
|
+
/**
|
|
49
|
+
* 两点是否轴对齐(三个坐标里至少两个相等 ⇒ 这一段只沿一根轴)。
|
|
50
|
+
* 纯判定、不改数据:编辑页面板拿它给「这一段是不是歪的」上提示,测试拿它做断言。
|
|
51
|
+
*/
|
|
52
|
+
export declare function isAxisAligned(a: Vec3, b: Vec3, eps?: number): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** 轴名 → 分量下标 */
|
|
2
|
+
const AXIS_INDEX = { x: 0, y: 1, z: 2 };
|
|
3
|
+
/**
|
|
4
|
+
* 候选顺序固定 x → y → z。
|
|
5
|
+
* 偏离量打平时(浮点噪声级)保留先出现的那个 ⇒ 结果可复现,不会因为一次 1e-17 的抖动换轴。
|
|
6
|
+
*/
|
|
7
|
+
const AXIS_ORDER = ["x", "y", "z"];
|
|
8
|
+
/** 判定「两轴坐标相同」的容差(与走线模块的 PIPE_EPS 同量级) */
|
|
9
|
+
const ALIGN_EPS = 1e-6;
|
|
10
|
+
/**
|
|
11
|
+
* 沿指定轴吸附:保留 p 在这根轴上的坐标,另外两根轴抄参考点。
|
|
12
|
+
* 用于面板数字输入 —— 用户输入 X 就是「沿 X 走这么远」,Y/Z 自动回落到来路,段仍是横平竖直。
|
|
13
|
+
*/
|
|
14
|
+
export function snapAlongAxis(p, ref, axis) {
|
|
15
|
+
const k = AXIS_INDEX[axis];
|
|
16
|
+
const q = [ref[0], ref[1], ref[2]];
|
|
17
|
+
q[k] = p[k];
|
|
18
|
+
return q;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 把 p 吸到「过 ref 的三条轴线」上最近的那条。
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ 永远返回结果(不返回 null):p 已经在某条轴线上时,该候选的 distance 就是 0,
|
|
24
|
+
* 写回后坐标一个不变 —— 调用方不必再判空。
|
|
25
|
+
*/
|
|
26
|
+
export function snapToNearestAxis(p, ref) {
|
|
27
|
+
let best = null;
|
|
28
|
+
for (const axis of AXIS_ORDER) {
|
|
29
|
+
const point = snapAlongAxis(p, ref, axis);
|
|
30
|
+
const dx = point[0] - p[0];
|
|
31
|
+
const dy = point[1] - p[1];
|
|
32
|
+
const dz = point[2] - p[2];
|
|
33
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
34
|
+
// 用「严格小于」+ 容差:平局时保留先出现的轴,不被浮点噪声换来换去
|
|
35
|
+
if (!best || distance < best.distance - 1e-12)
|
|
36
|
+
best = { point, axis, distance };
|
|
37
|
+
}
|
|
38
|
+
// 循环一定跑过一轮 ⇒ best 非空;写这个兜底只为满足 strict 关闭时的可读性
|
|
39
|
+
return best;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 两点是否轴对齐(三个坐标里至少两个相等 ⇒ 这一段只沿一根轴)。
|
|
43
|
+
* 纯判定、不改数据:编辑页面板拿它给「这一段是不是歪的」上提示,测试拿它做断言。
|
|
44
|
+
*/
|
|
45
|
+
export function isAxisAligned(a, b, eps = ALIGN_EPS) {
|
|
46
|
+
let same = 0;
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
if (Math.abs(a[i] - b[i]) < eps)
|
|
49
|
+
same++;
|
|
50
|
+
}
|
|
51
|
+
return same >= 2;
|
|
52
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 控制点轴向吸附(纯数字,零运行时依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 皇上旨 2026-10-25:「添加节点和编辑节点的时候,实时计算当前节点在 x/y/z 轴上的投影距离,
|
|
5
|
+
* 距离谁最近就改到哪里,让管道随时保持横平竖直。」
|
|
6
|
+
*
|
|
7
|
+
* ⭐ 什么叫「投影到轴上」——不是投影到世界坐标轴,而是投影到**过参考点(相邻控制点)的三条轴线**:
|
|
8
|
+
* 三条候选各保留一个坐标、另两个坐标抄参考点:
|
|
9
|
+
* 只沿 X 走 ⇒ [p.x, ref.y, ref.z],偏离量 = √(Δy² + Δz²)
|
|
10
|
+
* 只沿 Y 走 ⇒ [ref.x, p.y, ref.z],偏离量 = √(Δx² + Δz²)
|
|
11
|
+
* 只沿 Z 走 ⇒ [ref.x, ref.y, p.z],偏离量 = √(Δx² + Δy²)
|
|
12
|
+
* 取偏离量最小的那条 ⇒ 该段只剩一根轴有位移 ⇒ 横平竖直。
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ 三个候选都**保留待吸附点自身的那根轴坐标**(比如「只沿 X」保留 p.x)——这是关键:
|
|
15
|
+
* 沿 X 箭头拖动时 p.x 能自由变化,吸附不会把点钉死;被拉回的只是另外两根轴的偏移。
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ 这里改的是**控制点本身**(数据层),不是走线算术:PipeMesh 仍是相邻点直连(directRoute.ts),
|
|
18
|
+
* 只是点的位置被挪到轴线上。⛔ 别在这里插折点 —— 那是已删除的 orthogonalRoute 老路,
|
|
19
|
+
* 两件事的区别是:正交走线「自动加拐点把斜线拆成三段」,这里是「把点挪正,段还是那一段」。
|
|
20
|
+
*
|
|
21
|
+
* 留成零运行时依赖(只用 `import type`,编译时擦除)⇒ `test/pipeRoute.test.js` 能直接 require
|
|
22
|
+
* 编译产物逐条断言,不必起浏览器。
|
|
23
|
+
*/
|
|
24
|
+
import type { Vec3 } from "./directRoute";
|
|
25
|
+
|
|
26
|
+
/** 轴名(与编辑页面板的 X / Y / Z 三行同口径) */
|
|
27
|
+
export type AxisName = "x" | "y" | "z";
|
|
28
|
+
|
|
29
|
+
/** 吸附结果:落点 + 保留的轴 + 原位置到落点的欧氏距离 */
|
|
30
|
+
export interface AxisSnapResult {
|
|
31
|
+
/** 吸附后的位置(新数组,不改入参) */
|
|
32
|
+
point: Vec3;
|
|
33
|
+
/** 被保留(即该段唯一有位移)的轴;另两根轴已对齐到参考点 */
|
|
34
|
+
axis: AxisName;
|
|
35
|
+
/** 原位置到吸附位置的欧氏距离(米)= 这次「挪了多少」 */
|
|
36
|
+
distance: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 轴名 → 分量下标 */
|
|
40
|
+
const AXIS_INDEX: Record<AxisName, 0 | 1 | 2> = { x: 0, y: 1, z: 2 };
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 候选顺序固定 x → y → z。
|
|
44
|
+
* 偏离量打平时(浮点噪声级)保留先出现的那个 ⇒ 结果可复现,不会因为一次 1e-17 的抖动换轴。
|
|
45
|
+
*/
|
|
46
|
+
const AXIS_ORDER: AxisName[] = ["x", "y", "z"];
|
|
47
|
+
|
|
48
|
+
/** 判定「两轴坐标相同」的容差(与走线模块的 PIPE_EPS 同量级) */
|
|
49
|
+
const ALIGN_EPS = 1e-6;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 沿指定轴吸附:保留 p 在这根轴上的坐标,另外两根轴抄参考点。
|
|
53
|
+
* 用于面板数字输入 —— 用户输入 X 就是「沿 X 走这么远」,Y/Z 自动回落到来路,段仍是横平竖直。
|
|
54
|
+
*/
|
|
55
|
+
export function snapAlongAxis(p: Vec3, ref: Vec3, axis: AxisName): Vec3 {
|
|
56
|
+
const k = AXIS_INDEX[axis];
|
|
57
|
+
const q: Vec3 = [ref[0], ref[1], ref[2]];
|
|
58
|
+
q[k] = p[k];
|
|
59
|
+
return q;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 把 p 吸到「过 ref 的三条轴线」上最近的那条。
|
|
64
|
+
*
|
|
65
|
+
* ⚠️ 永远返回结果(不返回 null):p 已经在某条轴线上时,该候选的 distance 就是 0,
|
|
66
|
+
* 写回后坐标一个不变 —— 调用方不必再判空。
|
|
67
|
+
*/
|
|
68
|
+
export function snapToNearestAxis(p: Vec3, ref: Vec3): AxisSnapResult {
|
|
69
|
+
let best: AxisSnapResult | null = null;
|
|
70
|
+
for (const axis of AXIS_ORDER) {
|
|
71
|
+
const point = snapAlongAxis(p, ref, axis);
|
|
72
|
+
const dx = point[0] - p[0];
|
|
73
|
+
const dy = point[1] - p[1];
|
|
74
|
+
const dz = point[2] - p[2];
|
|
75
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
76
|
+
// 用「严格小于」+ 容差:平局时保留先出现的轴,不被浮点噪声换来换去
|
|
77
|
+
if (!best || distance < best.distance - 1e-12) best = { point, axis, distance };
|
|
78
|
+
}
|
|
79
|
+
// 循环一定跑过一轮 ⇒ best 非空;写这个兜底只为满足 strict 关闭时的可读性
|
|
80
|
+
return best as AxisSnapResult;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 两点是否轴对齐(三个坐标里至少两个相等 ⇒ 这一段只沿一根轴)。
|
|
85
|
+
* 纯判定、不改数据:编辑页面板拿它给「这一段是不是歪的」上提示,测试拿它做断言。
|
|
86
|
+
*/
|
|
87
|
+
export function isAxisAligned(a: Vec3, b: Vec3, eps = ALIGN_EPS): boolean {
|
|
88
|
+
let same = 0;
|
|
89
|
+
for (let i = 0; i < 3; i++) {
|
|
90
|
+
if (Math.abs(a[i] - b[i]) < eps) same++;
|
|
91
|
+
}
|
|
92
|
+
return same >= 2;
|
|
93
|
+
}
|