three-base 2.0.172 → 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 +15 -24
- package/FPScontrol.js +63 -87
- package/FPScontrol.ts +64 -88
- 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 +3 -2
- package/MktMesh/MktMesh.js +27 -8
- package/MktMesh/MktMesh.ts +24 -8
- 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/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 +1 -39
- package/ThreeBase.js +38 -147
- package/ThreeBase.ts +1176 -1302
- 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/debugDepthState.d.ts +16 -8
- package/pass/debugDepthState.js +4 -0
- package/pass/debugDepthState.ts +27 -9
- package/pass/glow_pass.js +11 -94
- package/pass/glow_pass.ts +11 -97
- package/pass/ssao_pass.d.ts +9 -23
- package/pass/ssao_pass.js +41 -128
- package/pass/ssao_pass.ts +36 -166
- package/pass/stroke_pass.js +3 -0
- package/pass/stroke_pass.ts +4 -0
- package/shaders/debugDepthCompareShader.d.ts +1 -1
- package/shaders/debugDepthCompareShader.js +1 -1
- package/shaders/debugDepthCompareShader.ts +1 -1
- 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 -15
- package/shaders/debugExcludedShader.js +0 -48
- package/shaders/debugExcludedShader.ts +0 -50
package/PipeMesh/PipeMesh.js
CHANGED
|
@@ -1,63 +1,202 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { DoubleSide, DynamicDrawUsage, Mesh, MeshLambertMaterial } from "three";
|
|
8
|
-
import { PathPointList } from "./PathPointList";
|
|
9
|
-
import { PathTubeGeometry } from "./PathTubeGeometry";
|
|
1
|
+
import { Color, Group } from "three";
|
|
2
|
+
import { 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 } from "./directRoute";
|
|
10
7
|
/**
|
|
11
|
-
*
|
|
8
|
+
* 组合管道(点与点直连):**直管 + 拐角补角球全部是 InstancedChild**,由 ThreeBase 的 InstancedGroup
|
|
9
|
+
* 每帧自动合批。
|
|
10
|
+
*
|
|
11
|
+
* ⭐ 为什么这样划算:全场景只有两份几何(单位圆柱、单位球),所以**不管多少折点,
|
|
12
|
+
* draw call 恒为 2**;改直径/改长度只动实例矩阵,不重建任何几何。旧的 PipeMesh(现
|
|
13
|
+
* `three-base/PipeMeshLegacy/`)是一条折线扫一张合并 BufferGeometry,改点要重算顶点,两者路线互斥。
|
|
14
|
+
*
|
|
15
|
+
* ⭐ 走线口径(皇上旨 2026-10-25「直接就是点与点直接连接,最简单的方案」):
|
|
16
|
+
* 控制点 i 与 i+1 之间就是一根直管 —— 长度 = 两点欧氏距离、方向 = 连线单位向量、中点 = 两点中点。
|
|
17
|
+
* 没有轴序、没有正交拆段、没有 90° 弯头。走线算术全在 `directRoute.ts`(零依赖,可纯数字断言)。
|
|
18
|
+
*
|
|
19
|
+
* 用法:
|
|
20
|
+
* ```ts
|
|
21
|
+
* const pipe = new PipeMesh({ points: [[-4,0,0],[0,3,0],[4,3,-4]], diameter: 1 });
|
|
22
|
+
* scene.add(pipe);
|
|
23
|
+
* pipe.setDiameter(0.6); // 只改缩放
|
|
24
|
+
* pipe.movePoint(1, [0, 5, 0]); // 只改控制点,走线自动重算
|
|
25
|
+
* const json = pipe.exportJson(); // 存档 / 导出
|
|
26
|
+
* PipeMesh.fromJson(json); // 引用 JSON 重建
|
|
27
|
+
* pipe.setShown(false); // 藏起这条管道(走注册表,不是 visible)
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ 实例池是**差分复用**的:段数变少时多出来的实例会被摘出注册表并 dispose,不会留在注册表里
|
|
31
|
+
* 继续被合批(注册表是全局 Set,`scene.remove` 停不了它)。
|
|
12
32
|
*/
|
|
13
|
-
export default class PipeMesh extends
|
|
14
|
-
|
|
33
|
+
export default class PipeMesh extends Group {
|
|
34
|
+
points = [];
|
|
35
|
+
diameter = 1;
|
|
36
|
+
color = new Color(PIPE_DEFAULT_COLOR);
|
|
37
|
+
/** 最近一次走线的退化提示(供面板显示;不影响渲染) */
|
|
38
|
+
warnings = [];
|
|
39
|
+
/** 直管实例(段数 = 有效控制点对数) */
|
|
40
|
+
straights = [];
|
|
41
|
+
/** 拐角补角球实例(内部控制点各一颗) */
|
|
42
|
+
joints = [];
|
|
43
|
+
_owner;
|
|
44
|
+
_shown = true;
|
|
45
|
+
constructor(props = {}) {
|
|
15
46
|
super();
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
this.type = "PipeMesh";
|
|
48
|
+
this.name = "PipeMesh";
|
|
49
|
+
this._owner = props.owner;
|
|
50
|
+
this.diameter = props.diameter ?? 1;
|
|
51
|
+
if (props.color !== undefined)
|
|
52
|
+
this.color.set(props.color);
|
|
53
|
+
this.points = (props.points ?? []).map((p) => [p[0], p[1], p[2]]);
|
|
54
|
+
this.rebuild();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 显隐:走注册表摘除/挂回,而不是靠 `visible` 标志。
|
|
58
|
+
* ⚠️ 实例化那条路的真实几何在 InstancedGroup 的 batch 里(不在这棵树下面),
|
|
59
|
+
* 所以 `pipe.visible = false` 对它**不起作用**;要藏起来就调本方法。
|
|
60
|
+
*/
|
|
61
|
+
setShown(shown) {
|
|
62
|
+
this._shown = shown;
|
|
63
|
+
for (const child of this._allInstances()) {
|
|
64
|
+
if (shown)
|
|
65
|
+
instancedChildRegistry.add(child);
|
|
66
|
+
else
|
|
67
|
+
instancedChildRegistry.delete(child);
|
|
68
|
+
}
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
get shown() {
|
|
72
|
+
return this._shown;
|
|
73
|
+
}
|
|
74
|
+
/** 按当前 points/diameter 重算实例位姿;只在段数变化时增删实例 */
|
|
75
|
+
rebuild() {
|
|
76
|
+
const plan = planDirectLayout(this.points, { diameter: this.diameter });
|
|
77
|
+
this.warnings = plan.warnings;
|
|
78
|
+
this._fitPool(this.straights, plan.straights.length, () => new PipeStraight());
|
|
79
|
+
this._fitPool(this.joints, plan.joints.length, () => new PipeJoint());
|
|
80
|
+
plan.straights.forEach((part, i) => {
|
|
81
|
+
const inst = this.straights[i];
|
|
82
|
+
inst.applyAt(part.center, part.dir, this.diameter, part.length);
|
|
83
|
+
inst.setPartIndex(part.index);
|
|
84
|
+
inst.setColor(this.color);
|
|
43
85
|
});
|
|
86
|
+
plan.joints.forEach((part, i) => {
|
|
87
|
+
const inst = this.joints[i];
|
|
88
|
+
inst.applyAt(part.center, this.diameter);
|
|
89
|
+
inst.setPartIndex(part.index);
|
|
90
|
+
inst.setColor(this.color);
|
|
91
|
+
});
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
_allInstances() {
|
|
95
|
+
return [...this.straights, ...this.joints];
|
|
96
|
+
}
|
|
97
|
+
/** 把池子拉到目标长度:不足 new + add 进本组,超出摘出并 dispose(连带注册表) */
|
|
98
|
+
_fitPool(pool, want, make) {
|
|
99
|
+
while (pool.length < want) {
|
|
100
|
+
const inst = make();
|
|
101
|
+
if (this._owner)
|
|
102
|
+
inst.setOwner(this._owner);
|
|
103
|
+
this.add(inst);
|
|
104
|
+
pool.push(inst);
|
|
105
|
+
}
|
|
106
|
+
while (pool.length > want) {
|
|
107
|
+
const inst = pool.pop();
|
|
108
|
+
this.remove(inst);
|
|
109
|
+
inst.dispose();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ===== 参数入口:每一条都收口到 rebuild(),避免各处各自改矩阵 =====
|
|
113
|
+
setPoints(points) {
|
|
114
|
+
this.points = points.map((p) => [p[0], p[1], p[2]]);
|
|
115
|
+
return this.rebuild();
|
|
44
116
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
this.
|
|
117
|
+
/** 改单个控制点(拖拽现场用,比整表重设省一次数组拷贝) */
|
|
118
|
+
movePoint(index, p) {
|
|
119
|
+
if (index < 0 || index >= this.points.length)
|
|
120
|
+
return this;
|
|
121
|
+
this.points[index] = [p[0], p[1], p[2]];
|
|
122
|
+
return this.rebuild();
|
|
50
123
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return this.
|
|
124
|
+
addPoint(p) {
|
|
125
|
+
this.points.push([p[0], p[1], p[2]]);
|
|
126
|
+
return this.rebuild();
|
|
54
127
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
128
|
+
insertPoint(index, p) {
|
|
129
|
+
if (index < 0 || index > this.points.length)
|
|
130
|
+
return this;
|
|
131
|
+
this.points.splice(index, 0, [p[0], p[1], p[2]]);
|
|
132
|
+
return this.rebuild();
|
|
133
|
+
}
|
|
134
|
+
removePoint(index) {
|
|
135
|
+
if (index < 0 || index >= this.points.length)
|
|
136
|
+
return this;
|
|
137
|
+
this.points.splice(index, 1);
|
|
138
|
+
return this.rebuild();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 控制点顺序整体颠倒(皇上旨 2026-10-25「做一个节点倒序的功能,让管道点的顺序颠倒一下」)。
|
|
142
|
+
*
|
|
143
|
+
* ⚠️ 直连方案下几何**逐段等价**(圆柱两端对称、球压在点上,都不认方向)⇒ 画面上看不出变化,
|
|
144
|
+
* 变的是**数据序**:存档 / 导出 JSON / 将来带方向性的特性(流向箭头、渐变、开孔位置)都按这个序。
|
|
145
|
+
* 所以别指望点一下画面有动静 —— 要验它,去面板看「控制点 #N」的编号,或导出 JSON 比对。
|
|
146
|
+
*/
|
|
147
|
+
reversePoints() {
|
|
148
|
+
this.points.reverse();
|
|
149
|
+
return this.rebuild();
|
|
150
|
+
}
|
|
151
|
+
setDiameter(d) {
|
|
152
|
+
this.diameter = d;
|
|
153
|
+
return this.rebuild();
|
|
154
|
+
}
|
|
155
|
+
setColor(c) {
|
|
156
|
+
this.color.set(c);
|
|
157
|
+
// 颜色走逐实例 instanceColor,与位姿无关 ⇒ 不动矩阵、不必 rebuild
|
|
158
|
+
for (const inst of this._allInstances())
|
|
159
|
+
inst.setColor(this.color);
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
// ===== JSON:对象与项目存档共用的形状 =====
|
|
163
|
+
/** 导出当前管道的 JSON 数据(不碰 three 的 Object3D 序列化格式,那个是场景树用的) */
|
|
164
|
+
exportJson() {
|
|
165
|
+
return makePipeJson(this.points.map((p) => [p[0], p[1], p[2]]), this.diameter, `#${this.color.getHexString()}`);
|
|
166
|
+
}
|
|
167
|
+
/** 引用 JSON 重建当前管道:非法数据不动现场,返回人话原因 */
|
|
168
|
+
importJson(input) {
|
|
169
|
+
const parsed = parsePipeJson(input);
|
|
170
|
+
if (!parsed.ok || !parsed.json)
|
|
171
|
+
return { ok: false, reason: parsed.reason ?? "JSON 解析失败" };
|
|
172
|
+
const json = parsed.json;
|
|
173
|
+
this.diameter = json.diameter;
|
|
174
|
+
if (json.color)
|
|
175
|
+
this.color.set(json.color);
|
|
176
|
+
this.points = json.points.map((p) => [p[0], p[1], p[2]]);
|
|
177
|
+
this.rebuild();
|
|
178
|
+
return { ok: true };
|
|
179
|
+
}
|
|
180
|
+
/** 用 JSON 起一个新管道对象;失败时 pipe 为 undefined,reason 可直接弹给用户 */
|
|
181
|
+
static fromJson(input) {
|
|
182
|
+
const parsed = parsePipeJson(input);
|
|
183
|
+
if (!parsed.ok || !parsed.json)
|
|
184
|
+
return { ok: false, reason: parsed.reason ?? "JSON 解析失败" };
|
|
185
|
+
const json = parsed.json;
|
|
186
|
+
const pipe = new PipeMesh({
|
|
187
|
+
points: json.points,
|
|
188
|
+
diameter: json.diameter,
|
|
189
|
+
color: json.color,
|
|
190
|
+
});
|
|
191
|
+
return { pipe, ok: true };
|
|
58
192
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
193
|
+
/** 摘掉全部实例并释放注册表登记(组本身留在 scene 里由调用方 remove) */
|
|
194
|
+
dispose() {
|
|
195
|
+
for (const inst of this._allInstances()) {
|
|
196
|
+
this.remove(inst);
|
|
197
|
+
inst.dispose();
|
|
198
|
+
}
|
|
199
|
+
this.straights.length = 0;
|
|
200
|
+
this.joints.length = 0;
|
|
62
201
|
}
|
|
63
202
|
}
|
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
|
+
}
|