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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* @Description:
|
|
2
|
+
* @Description: 9 瓦片全景的「离屏拼合器」= **shader 逐格画进 WebGLRenderTarget**(本项目唯一一份拼合实现)。
|
|
3
3
|
*
|
|
4
4
|
* 三道旨意叠成现在这一份:
|
|
5
5
|
* ① 2026-09-29:一张全景切多片(每片所有 GPU 都装得下),运行时离屏拼回若干张固定尺寸的「槽纹理」,
|
|
@@ -18,54 +18,66 @@
|
|
|
18
18
|
* ⇒ 旨②的 8×8=64 片 / 2×2 四槽 **作废**:改 **8×4=32 片 / 左右 2 槽**(槽纹理跟着变 4096² 正方形)。
|
|
19
19
|
* ⭐ 净效果:sampler 总数仍 16(硬顶)⇒ 同屏全景数 **4 → 8**;画质与单全景显存**分毫未动**;
|
|
20
20
|
* 单全景下载片数 64 → 32(**减半**)。旧存档 64 条地址**不兼容**,皇上「有异常我会重传」。
|
|
21
|
+
* ④ 2026-10-23(皇上旨,逐字):「瓦片拆成 9 个了,每个瓦片的尺寸是 4096 * 2048 所有,全景的 RT 由原来的
|
|
22
|
+
* 2 个分成 3 个,每个 RT 的尺寸是 4096 * 6144,竖直排列 3 个瓦片」
|
|
23
|
+
* ⇒ 改 **3×3=9 片 / 按列 3 槽**,每槽 = 原图一整列(横 1 格 × 竖 3 格)= **4096×6144**、与原图 1:1(不再缩小)。
|
|
24
|
+
* ⚠️ 代价(皇上在「A/B/C/D」里点了 **A**):sampler = 同屏景点数 × 3,硬顶 16 ⇒ **同屏全景点数 8 → 5**(5×3=15)。
|
|
25
|
+
* 单全景 RT 显存 170MB → **288MB**(含 mipmap ≈384MB)。旧存档 32 条地址**不兼容**,「有异常我会重传」。
|
|
21
26
|
*
|
|
22
27
|
* ⚠️ **仍然有 canvas,但只在上传切图那一步**(`src/views/MktMeshEdit/panoramaUpload.ts` 用 2D 画布把原图
|
|
23
|
-
* 切成
|
|
28
|
+
* 切成 9 个 JPEG 传 OSS)—— 那是**离线、一次性、产出的是数据**;运行时这条路上一个 canvas 都没有。
|
|
24
29
|
*
|
|
25
30
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
26
|
-
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔
|
|
31
|
+
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
27
32
|
* 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
|
|
28
33
|
* 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
|
|
29
34
|
* 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
|
|
30
35
|
* 到 GPU 里面去」)。这一趟**只上传、不画** ⇒ 上传与绘制永不落在同一拍(09-29 那句「两个大头别在同一
|
|
31
36
|
* 帧」的延续),而且第 2 步天然只能画「上一帧及更早」传上去的图 ⇒ 每帧 = 1 上传 + 1 绘制(稳态)。
|
|
32
37
|
*
|
|
33
|
-
* ⚠️
|
|
34
|
-
* 10-
|
|
35
|
-
* 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等
|
|
38
|
+
* ⚠️ 于是「一帧一片」的帧数随网格走:16 片 → 32 片(30ms/帧 ⇒ 全量到位约 1.0s)→ **9 片/组 = 9 帧**(约 0.27s,
|
|
39
|
+
* 10-23 旨)。每格像素 = `RT_STITCH_SLOT_W/1 × RT_STITCH_SLOT_H/3` = **4096×2048**(原图切出来那片多大就多大,一比一)。
|
|
40
|
+
* 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等 9 帧。
|
|
36
41
|
*
|
|
37
|
-
* ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的
|
|
42
|
+
* ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的 `cellsPerSlot` 格画满,
|
|
38
43
|
* **画过 ≥1 格就交出去**。为了保证交出去的那张 RT 里绝无未定义像素,RT 建好后的第一件事是
|
|
39
44
|
* 整张涂成缺失灰(`_clearRt`)⇒ 观感 = 该象限从左上角起逐格显影,缺数据的格子一直是灰的。
|
|
40
45
|
*
|
|
41
46
|
* ── 画质四条(⛔ 改任何一条都是改画质,先请旨)──────────────────────────────
|
|
42
|
-
* 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **4096×
|
|
43
|
-
* 全在 `./mktShader
|
|
44
|
-
*
|
|
45
|
-
*
|
|
47
|
+
* 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **4096×6144**、每槽「横 1 格 × 竖 3 格」、原图**横 3 × 竖 3** 片,
|
|
48
|
+
* 全在 `./mktShader`(单一来源)。⭐ 10-23 旨后格子尺寸 = 那片瓦片的尺寸 ⇒ **一比一**:11968×5984 那类原图
|
|
49
|
+
* 切 3×3 得 ~3989×1994 的片,铺进 4096×2048 的格子几乎不缩(缩放比 0.97,上一版 8×4 是 1.46 = 缩小)
|
|
50
|
+
* ⇒ 单全景有效分辨率 33.5Mpx → **75.5Mpx**,是**升**画质,与「千万不要降低质量」红线同向。
|
|
51
|
+
* ⛔ 不许把槽改回 4096² 正方形:那是上一版「每槽覆盖横 180°×竖 180°」的产物;现口径每槽只覆盖**横 120° × 竖 180°**,
|
|
52
|
+
* 宽高比 2:3 必须与「原图一整列」一致,否则格子被强行拉伸 = 几何挤歪。
|
|
46
53
|
* 2. **色彩**:瓦片纹理 `colorSpace = SRGBColorSpace` ⇒ WebGL2 内部格式 `SRGB8_ALPHA8`,采样时硬件解码成
|
|
47
54
|
* linear;RT 纹理同样标 sRGB ⇒ 写入时硬件再编码回 sRGB。一进一出**逐字节还原**。
|
|
48
55
|
* ⚠️ 另一半证据在 three 里:写进非 XR 的 render target 时片元末尾的 `linearToOutputTexel` 恒按
|
|
49
56
|
* LinearSRGB(= 不转换),编码完全交给硬件 ⇒ ⛔ 我们的片元绝不 `#include <colorspace_fragment>`,
|
|
50
57
|
* 也不带 tone mapping 的 chunk(那两处都会再转一遍 ⇒ 画面发白)。
|
|
51
58
|
* 3. **摩尔纹**:两层都开 mipmap ——
|
|
52
|
-
* ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter
|
|
53
|
-
*
|
|
59
|
+
* ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:10-23 旨后一片原图(~3989×1994)铺进 4096×2048
|
|
60
|
+
* 的格子是**一比一**,但墙面在远处仍会缩到格子以下 ⇒ ⛔ 单点双线性会采出锯齿,mipmap 照旧开
|
|
61
|
+
* (WebGL2 对 NPOT 也能生成 mipmap,three r163 起只支持 WebGL2)。
|
|
54
62
|
* ② 槽 RT 标 `generateMipmaps`:three 每次渲染进 RT 后自己 `glGenerateMipmap`(`WebGLRenderer.js`
|
|
55
63
|
* `updateRenderTargetMipmap`)⇒ 与当年 CanvasTexture 上传后生成 mipmap 同一口径。
|
|
56
64
|
* ⚠️ 代价:每格一次(旧版每格也是一次),都在 GPU 上、不占主线程。
|
|
57
|
-
* 4.
|
|
65
|
+
* 4. **接缝**:槽内「横 1 × 竖 3」⇒ 一张槽内部的格子线是 **2 横 0 竖**(上一版 4096² 是 3 横 3 竖);
|
|
66
|
+
* 槽与槽之间另有 u=1/3、u=2/3 两道**跨纹理**竖缝,那道治不了(见 `PanoTexMode` 说明②)。治法 = 上传时每片向四边**多切**
|
|
58
67
|
* `RT_STITCH_SEAM_MARGIN_PX` 像素、拼合时**只采中间那块**(`uSrcRect`)⇒ 边界插值拿得到真邻居像素,
|
|
59
68
|
* 不再复制自己的边缘。⛔ 这条不许「顺手简化」成直接铺满格子:几何会挤歪,缝也还在。
|
|
60
69
|
* 详见 `./mktShader` 里那三个 `rtStitch*` 纯函数 —— **上传侧与拼合侧必须共用同一份几何**。
|
|
61
70
|
*
|
|
62
|
-
* 资源账(⚠️ 如实记;10-
|
|
63
|
-
* - 常驻显存:
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* -
|
|
71
|
+
* 资源账(⚠️ 如实记;10-23 旨改 3×3=9 片 / 按列 3 槽 / 4096×6144 后重算):
|
|
72
|
+
* - 常驻显存:3 槽 × (4096×**6144**×4B + mipmap≈33%) ≈ **384MB/全景**(不含 mipmap 288MB)
|
|
73
|
+
* —— 上一版 2 槽 4096² 是 **170MB/全景** ⇒ 单全景变成 **2.26 倍**,代价来自「槽与原图一比一」那次**升分辨率**,
|
|
74
|
+
* 不是浪费:33.5Mpx → 75.5Mpx 的有效像素全在 RT 里。
|
|
75
|
+
* ⚠️ 组**运行中不淘汰**(见文末那条旨)⇒ 13 张全景全常驻 ≈ **4.9GB**(上一版 2.2GB)。同屏景点数虽收到 5,
|
|
76
|
+
* 那只省 sampler 绑定、**不省显存**。⚠️ 这是本轮最大的风险项,已另记待皇上裁决(见「RT 不回收」)。
|
|
77
|
+
* - 上传总量:同一张原图切完 ⇒ **总像素与 32 片那版逐字节同量**,只是片数 32 → 9、单片边长 ~2.7 倍
|
|
78
|
+
* ⇒ 下载发起数减 72%、单请求变大。
|
|
79
|
+
* - 瓦片纹理画完即弃 ⇒ 同时在册的只有「下载中 + 已解码待上传」那些,上限 8+8 片;
|
|
80
|
+
* ⚠️ 每片解码位图 4096×2048×4B = **32MB**(上一版每片 8.5MB)⇒ 峰值 CPU 侧 ≈ **512MB**(原 ~136MB)。
|
|
69
81
|
*
|
|
70
82
|
* ⚠️ RT 是 GL 上下文私有资源:`pump(renderer)` 换 renderer 时整组作废(`_switchRenderer`),下一帧消费方
|
|
71
83
|
* 照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存重新下载)。现况是**每个持有者一份拼合器 + 一个 renderer**
|
|
@@ -100,16 +112,14 @@ import { loadPanoTexture, releasePanoTexture } from "./panoBitmapTexture";
|
|
|
100
112
|
import threeUtils from "../threeUtils";
|
|
101
113
|
import {
|
|
102
114
|
MISSING_TILE_GRAY,
|
|
103
|
-
RT_STITCH_CELLS_PER_SLOT,
|
|
104
|
-
RT_STITCH_SLOT_COUNT,
|
|
105
|
-
RT_STITCH_SLOT_H,
|
|
106
|
-
RT_STITCH_SLOT_TILES,
|
|
107
|
-
RT_STITCH_SLOT_W,
|
|
108
|
-
RT_STITCH_SLOT_GRID,
|
|
109
|
-
RT_STITCH_TILES_PER_BATCH,
|
|
110
115
|
rtStitchBatchOf,
|
|
111
116
|
rtStitchCellPos,
|
|
112
117
|
rtStitchTileInset,
|
|
118
|
+
type TileLayout,
|
|
119
|
+
PANO_TILE_LAYOUT,
|
|
120
|
+
cellsPerSlotOf,
|
|
121
|
+
tilesPerBatchOf,
|
|
122
|
+
slotTilesOf,
|
|
113
123
|
} from "./mktShader";
|
|
114
124
|
|
|
115
125
|
/**
|
|
@@ -120,7 +130,7 @@ import {
|
|
|
120
130
|
*/
|
|
121
131
|
const PANO_STITCH_UPLOADS_PER_FRAME = 1;
|
|
122
132
|
|
|
123
|
-
/** 每帧最多发起几张下载(
|
|
133
|
+
/** 每帧最多发起几张下载(10-23 起一批 9 片,⛔ 不加以限制就是一帧甩 9 条 XHR + 9 次 800 万像素解码) */
|
|
124
134
|
const PANO_STITCH_DOWNLOADS_PER_FRAME = 2;
|
|
125
135
|
|
|
126
136
|
/** 「下载中」的片数上限(跨所有批次) */
|
|
@@ -185,15 +195,23 @@ interface TileRec {
|
|
|
185
195
|
uploaded: boolean;
|
|
186
196
|
}
|
|
187
197
|
|
|
188
|
-
/**
|
|
198
|
+
/** 一个瓦片批次的拼合组(⛔ 名字仍叫 StitchGroup,不改对外接口 —— 只是多了 layout) */
|
|
189
199
|
interface StitchGroup {
|
|
190
|
-
/**
|
|
200
|
+
/** 布局(⛔ 由 `ensure` 传入;全景 = PANO_TILE_LAYOUT,图片平面 = imageTileLayout(…)) */
|
|
201
|
+
layout: TileLayout;
|
|
202
|
+
/** 该布局下槽 slot 使用哪些瓦片下标(预计算 `slotTilesOf(layout)` ⇒ 每帧三道工序反复用,不再每帧现算) */
|
|
203
|
+
slotTiles: number[][];
|
|
204
|
+
/** 该布局一批几条地址(补空串占位到此长度) */
|
|
205
|
+
tileCount: number;
|
|
206
|
+
/** 该布局每槽几格(cell < cellsPerSlot) */
|
|
207
|
+
cellsPerSlot: number;
|
|
208
|
+
/** 瓦片地址数组(图像左上起行优先) */
|
|
191
209
|
urls: string[];
|
|
192
|
-
/**
|
|
210
|
+
/** layout.slotCount 个槽的渲染目标(懒建) */
|
|
193
211
|
rts: (WebGLRenderTarget | null)[];
|
|
194
|
-
/**
|
|
212
|
+
/** 每槽一个位掩码:bit `cell` = 1 ⇒ 这一格已画进 RT */
|
|
195
213
|
drawn: number[];
|
|
196
|
-
/** 每槽已画格数(=
|
|
214
|
+
/** 每槽已画格数(= 掩码 popcount) */
|
|
197
215
|
drawnCount: number[];
|
|
198
216
|
/** 还剩几槽没画满 */
|
|
199
217
|
pending: number;
|
|
@@ -204,8 +222,10 @@ interface StitchGroup {
|
|
|
204
222
|
* 10-06 之前它是「两份实现共用一份接口」里的 canvas 那一份 ⇒ 那份已奉旨删除,接口留在原地当文档。
|
|
205
223
|
*/
|
|
206
224
|
export interface PanoStitcher {
|
|
207
|
-
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒
|
|
208
|
-
|
|
225
|
+
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
226
|
+
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 3×3 / 3 槽 / 4096×6144,10-23 旨);
|
|
227
|
+
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。 */
|
|
228
|
+
ensure(urls: string[], layout?: TileLayout): void;
|
|
209
229
|
/**
|
|
210
230
|
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
211
231
|
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
@@ -270,7 +290,8 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
270
290
|
depthWrite: false,
|
|
271
291
|
blending: NoBlending,
|
|
272
292
|
});
|
|
273
|
-
/** 几何是 2×2(铺满正交相机的 [-1,1]
|
|
293
|
+
/** 几何是 2×2(铺满正交相机的 [-1,1]²),每格按 `1/slotCellCols × 1/slotCellRows` 缩放再挪过去
|
|
294
|
+
* ⇒ 全景一格 = 槽的 1/3(横 1 竖 3,10-23 旨),图片平面一格 = 1/4(横 2 竖 2) */
|
|
274
295
|
private _quad: Mesh<PlaneGeometry, ShaderMaterial> = new Mesh(new PlaneGeometry(2, 2), this._mat);
|
|
275
296
|
/** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
|
|
276
297
|
private _blank: DataTexture = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
|
|
@@ -284,28 +305,36 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
284
305
|
}
|
|
285
306
|
|
|
286
307
|
/** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
|
|
287
|
-
* ⭐ 09-29 晚旨「不足的涂灰」+ 10-
|
|
308
|
+
* ⭐ 09-29 晚旨「不足的涂灰」+ 10-23 旨「3×3 = 9 片」:1~9 条皆可入组,内部补空串占位到 9;
|
|
288
309
|
* 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
|
|
289
|
-
* ⛔ 旧版 16 条(4×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
|
|
290
|
-
*
|
|
291
|
-
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null
|
|
292
|
-
ensure(urls: string[]): void {
|
|
293
|
-
|
|
310
|
+
* ⛔ 旧版 16 条(4×4)、32 条(8×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
|
|
311
|
+
* 超 9 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
|
|
312
|
+
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null]` —— 槽数再变会漏改)。 */
|
|
313
|
+
ensure(urls: string[], layout?: TileLayout): void {
|
|
314
|
+
const L = layout ?? PANO_TILE_LAYOUT;
|
|
315
|
+
const maxTiles = tilesPerBatchOf(L);
|
|
316
|
+
if (!urls || urls.length === 0 || urls.length > maxTiles) return;
|
|
294
317
|
const u = urls.slice();
|
|
295
|
-
while (u.length <
|
|
318
|
+
while (u.length < maxTiles) u.push("");
|
|
296
319
|
const batch = rtStitchBatchOf(u);
|
|
297
|
-
if (this._groups.has(batch)) return;
|
|
320
|
+
if (this._groups.has(batch)) return;
|
|
321
|
+
const slotCount = L.slotCount;
|
|
322
|
+
const cellsPerSlot = cellsPerSlotOf(L);
|
|
298
323
|
this._groups.set(batch, {
|
|
324
|
+
layout: L,
|
|
325
|
+
slotTiles: slotTilesOf(L),
|
|
326
|
+
tileCount: maxTiles,
|
|
327
|
+
cellsPerSlot,
|
|
299
328
|
urls: u,
|
|
300
|
-
rts: Array.from({ length:
|
|
301
|
-
drawn: Array.from({ length:
|
|
302
|
-
drawnCount: Array.from({ length:
|
|
303
|
-
pending:
|
|
329
|
+
rts: Array.from({ length: slotCount }, () => null),
|
|
330
|
+
drawn: Array.from({ length: slotCount }, () => 0),
|
|
331
|
+
drawnCount: Array.from({ length: slotCount }, () => 0),
|
|
332
|
+
pending: slotCount,
|
|
304
333
|
});
|
|
305
334
|
this._stamp++;
|
|
306
335
|
}
|
|
307
336
|
|
|
308
|
-
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等
|
|
337
|
+
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
309
338
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
310
339
|
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null {
|
|
311
340
|
const g = this._groups.get(batch);
|
|
@@ -398,13 +427,13 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
398
427
|
let budget = PANO_STITCH_DOWNLOADS_PER_FRAME;
|
|
399
428
|
for (const g of this._groups.values()) {
|
|
400
429
|
if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
|
|
401
|
-
for (let slot = 0; slot <
|
|
402
|
-
const ids =
|
|
403
|
-
for (let cell = 0; cell <
|
|
404
|
-
if (g.drawn[slot] & (1 << cell)) continue;
|
|
430
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
431
|
+
const ids = g.slotTiles[slot];
|
|
432
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
433
|
+
if (g.drawn[slot] & (1 << cell)) continue;
|
|
405
434
|
const url = g.urls[ids[cell]];
|
|
406
|
-
if (!url || this._dead.has(url)) continue;
|
|
407
|
-
if (this._texs.has(url)) continue;
|
|
435
|
+
if (!url || this._dead.has(url)) continue;
|
|
436
|
+
if (this._texs.has(url)) continue;
|
|
408
437
|
this._loadTile(url);
|
|
409
438
|
inflight++;
|
|
410
439
|
if (--budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
|
|
@@ -419,20 +448,20 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
419
448
|
private _drawPass(renderer: WebGLRenderer): void {
|
|
420
449
|
for (const g of this._groups.values()) {
|
|
421
450
|
if (g.pending <= 0) continue;
|
|
422
|
-
for (let slot = 0; slot <
|
|
423
|
-
if (g.drawnCount[slot] >=
|
|
424
|
-
const ids =
|
|
425
|
-
for (let cell = 0; cell <
|
|
451
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
452
|
+
if (g.drawnCount[slot] >= g.cellsPerSlot) continue;
|
|
453
|
+
const ids = g.slotTiles[slot];
|
|
454
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
426
455
|
if (g.drawn[slot] & (1 << cell)) continue;
|
|
427
456
|
const url = g.urls[ids[cell]];
|
|
428
457
|
if (!url || this._dead.has(url)) {
|
|
429
|
-
this._stitchCell(renderer, g, slot, cell, null);
|
|
458
|
+
this._stitchCell(renderer, g, slot, cell, null);
|
|
430
459
|
continue;
|
|
431
460
|
}
|
|
432
461
|
const rec = this._texs.get(url);
|
|
433
|
-
if (!rec || !rec.tex || !rec.uploaded) continue;
|
|
462
|
+
if (!rec || !rec.tex || !rec.uploaded) continue;
|
|
434
463
|
this._stitchCell(renderer, g, slot, cell, rec.tex);
|
|
435
|
-
this._dropTile(url);
|
|
464
|
+
this._dropTile(url);
|
|
436
465
|
}
|
|
437
466
|
}
|
|
438
467
|
}
|
|
@@ -456,27 +485,22 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
456
485
|
* 与 `_drawPass` 天然错开(上传与绘制不落同一拍,正是 09-29「两个大头别在同一帧」的延续)。
|
|
457
486
|
*/
|
|
458
487
|
private _uploadPass(renderer: WebGLRenderer): void {
|
|
459
|
-
// ⛔⛔ 队列里已有待上传项 ⇒ **本次直接不排**。
|
|
460
|
-
// 这一句是本改法的关键闸门:若只靠下面 `has(rec)` 跳过「同一片」,遍历会**继续往下找下一片**
|
|
461
|
-
// 于是 pump 几次就把多张全排进队列(13 全景 × 32 片 = 416 项 ⇒ workList 膨胀、`includes` 退化成 O(n)、
|
|
462
|
-
// 队尾那张要等几百帧才传完)。先判「队列非空就收手」⇒ 队列恒 ≤2 项(一占位+一真活),延迟最低。
|
|
463
488
|
if (this._uploadQueued.size > 0) return;
|
|
464
489
|
for (const g of this._groups.values()) {
|
|
465
490
|
if (g.pending <= 0) continue;
|
|
466
|
-
for (let slot = 0; slot <
|
|
467
|
-
const ids =
|
|
468
|
-
for (let cell = 0; cell <
|
|
491
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
492
|
+
const ids = g.slotTiles[slot];
|
|
493
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
469
494
|
if (g.drawn[slot] & (1 << cell)) continue;
|
|
470
495
|
const url = g.urls[ids[cell]];
|
|
471
496
|
if (!url || this._dead.has(url)) continue;
|
|
472
497
|
const rec = this._texs.get(url);
|
|
473
498
|
if (!rec || !rec.tex || rec.uploaded) continue;
|
|
474
|
-
// ⛔ 已在队列里 ⇒ 跳过(`addWork` 按引用判重对「新闭包」无效 ⇒ 必须自备 Set,见 `_uploadQueued`)
|
|
475
499
|
if (this._uploadQueued.has(rec)) continue;
|
|
476
500
|
this._uploadQueued.add(rec);
|
|
477
|
-
threeUtils.addWork(() => {});
|
|
501
|
+
threeUtils.addWork(() => {});
|
|
478
502
|
threeUtils.addWork(() => this._uploadTile(rec));
|
|
479
|
-
return;
|
|
503
|
+
return;
|
|
480
504
|
}
|
|
481
505
|
}
|
|
482
506
|
}
|
|
@@ -493,8 +517,9 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
493
517
|
if (!renderer || !rec.tex) return;
|
|
494
518
|
try {
|
|
495
519
|
renderer.initTexture(rec.tex);
|
|
496
|
-
} catch
|
|
497
|
-
|
|
520
|
+
} catch {
|
|
521
|
+
// 皇上旨 2026-10-12「日志都去掉」⇒ 不再出声;下面照记 `uploaded = true`,
|
|
522
|
+
// 最坏退回绘制那一帧自己上传。
|
|
498
523
|
}
|
|
499
524
|
rec.uploaded = true;
|
|
500
525
|
this._stamp++;
|
|
@@ -502,24 +527,26 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
502
527
|
|
|
503
528
|
// ───────────────────────── 内部:单格绘制 ─────────────────────────
|
|
504
529
|
|
|
505
|
-
/** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell`
|
|
506
|
-
*
|
|
530
|
+
/** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(每槽 `slotCellCols × slotCellRows` 格里的一格,
|
|
531
|
+
* 10-23 旨后全景是「横 1 × 竖 3」= 3 格、图片平面是 2×2 = 4 格)。
|
|
532
|
+
* ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 cellsPerSlot 帧**,
|
|
507
533
|
* 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
|
|
508
534
|
private _stitchCell(renderer: WebGLRenderer, g: StitchGroup, slot: number, cell: number, tex: Texture | null): void {
|
|
509
535
|
const rt = this._ensureRt(renderer, g, slot);
|
|
510
|
-
const tileIdx =
|
|
511
|
-
const [cc, rr] = rtStitchCellPos(cell);
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
536
|
+
const tileIdx = g.slotTiles[slot][cell];
|
|
537
|
+
const [cc, rr] = rtStitchCellPos(cell, g.layout.slotCellCols);
|
|
538
|
+
// ⚠️ 横竖**分轴**缩放/摆位(旧的单一 `slotGrid` 方形假设整条作废):10-23 旨一槽 = 原图一整列 ⇒ 1 横 3 竖
|
|
539
|
+
const cw = g.layout.slotCellCols;
|
|
540
|
+
const ch = g.layout.slotCellRows;
|
|
541
|
+
this._quad.scale.set(1 / cw, 1 / ch, 1);
|
|
515
542
|
this._quad.position.set(
|
|
516
|
-
(2 * cc + 1) /
|
|
517
|
-
1 - (2 * rr + 1) /
|
|
543
|
+
(2 * cc + 1) / cw - 1,
|
|
544
|
+
1 - (2 * rr + 1) / ch,
|
|
518
545
|
0,
|
|
519
546
|
);
|
|
520
547
|
this._mat.uniforms.uMap.value = tex ?? this._blank;
|
|
521
548
|
this._mat.uniforms.uMissing.value = tex ? 0 : 1;
|
|
522
|
-
const rect = tex ? rtStitchTileInset(tileIdx, texW(tex), texH(tex)) : null;
|
|
549
|
+
const rect = tex ? rtStitchTileInset(tileIdx, texW(tex), texH(tex), g.layout) : null;
|
|
523
550
|
const uv = this._mat.uniforms.uSrcRect.value as Vector4;
|
|
524
551
|
if (rect) uv.set(rect[0], rect[1], rect[2], rect[3]);
|
|
525
552
|
else uv.set(0, 0, 1, 1);
|
|
@@ -527,7 +554,7 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
527
554
|
g.drawn[slot] |= 1 << cell;
|
|
528
555
|
g.drawnCount[slot]++;
|
|
529
556
|
this._stamp++;
|
|
530
|
-
if (g.drawnCount[slot] >=
|
|
557
|
+
if (g.drawnCount[slot] >= g.cellsPerSlot) g.pending--;
|
|
531
558
|
if (g.pending <= 0) this._releaseGroupWanted(g);
|
|
532
559
|
}
|
|
533
560
|
|
|
@@ -627,12 +654,14 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
627
654
|
this._dead.clear();
|
|
628
655
|
}
|
|
629
656
|
|
|
630
|
-
/**
|
|
631
|
-
*
|
|
657
|
+
/** 懒建某槽的渲染目标:尺寸取自 `g.layout.rtW/rtH`(全景档 = **4096×6144**,10-23 旨)、RGBA8 + sRGB + mipmap,
|
|
658
|
+
* 并**立刻整张涂缺失灰**(⛔ 未定义像素不许进 shader:`peek()` 现在画过 1 格就交出去)。
|
|
659
|
+
* ⚠️ 6144 这个数受本机 `MAX_TEXTURE_SIZE` / 最大渲染目标尺寸约束(ANGLE/D3D11 的 11_0 是 16384,远超;
|
|
660
|
+
* 只有老 Intel 集显才可能卡 4096 —— 真建不出来 three 会报 `GL_INVALID_VALUE`,届时墙上整片缺失灰)。 */
|
|
632
661
|
private _ensureRt(renderer: WebGLRenderer, g: StitchGroup, slot: number): WebGLRenderTarget {
|
|
633
662
|
const exist = g.rts[slot];
|
|
634
663
|
if (exist) return exist;
|
|
635
|
-
const rt = new WebGLRenderTarget(
|
|
664
|
+
const rt = new WebGLRenderTarget(g.layout.rtW, g.layout.rtH, {
|
|
636
665
|
wrapS: ClampToEdgeWrapping,
|
|
637
666
|
wrapT: ClampToEdgeWrapping,
|
|
638
667
|
minFilter: LinearMipmapLinearFilter,
|
|
@@ -667,7 +696,7 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
667
696
|
|
|
668
697
|
/** 释放一组的全部 GPU 资源:2 张 RT + 还没画掉的瓦片纹理 */
|
|
669
698
|
private _dropGroupGpu(g: StitchGroup): void {
|
|
670
|
-
for (let slot = 0; slot <
|
|
699
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
671
700
|
const rt = g.rts[slot];
|
|
672
701
|
if (rt) rt.dispose();
|
|
673
702
|
g.rts[slot] = null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SphereGeometry } from "three";
|
|
2
|
+
import InstancedChild from "../instanced/InstancedChild";
|
|
3
|
+
/**
|
|
4
|
+
* 拐角补角球实例:**直径 1** 的单位球(半径 0.5,与直管半径同量级)。
|
|
5
|
+
*
|
|
6
|
+
* ⭐ 为什么必须有它:直管是开口圆柱(`CylinderGeometry(0.5, 0.5, 1, 48, 1, true)`),两段斜管
|
|
7
|
+
* 在拐角处端面是斜的、只有轴线相交 ⇒ 夹角外侧必然露一个楔形缺口。球心压在控制点上、
|
|
8
|
+
* 缩放 = 直径 ⇒ 球面正好把缺口盖满;球与管同径,共线时完全看不出来。
|
|
9
|
+
*
|
|
10
|
+
* 姿态无所谓(球各向同性),所以只写 position / scale,不碰 quaternion —— 比 PipeBend 那套
|
|
11
|
+
* 三列基向量简单一整个量级,也正是「最简单的方案」想要的。
|
|
12
|
+
*/
|
|
13
|
+
export default class PipeJoint extends InstancedChild {
|
|
14
|
+
static geometry: SphereGeometry;
|
|
15
|
+
static material: import("three").MeshStandardMaterial;
|
|
16
|
+
/** 控制台取证用:对应第几个控制点(由 PipeMesh 容器在 rebuild 时写入) */
|
|
17
|
+
partIndex: number;
|
|
18
|
+
constructor();
|
|
19
|
+
/** 落到某个拐角:球心 + 直径缩放 */
|
|
20
|
+
applyAt(center: [number, number, number], diameter: number): void;
|
|
21
|
+
setPartIndex(i: number): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SphereGeometry } from "three";
|
|
2
|
+
import InstancedChild from "../instanced/InstancedChild";
|
|
3
|
+
import { pipeMaterial } from "./pipeMaterial";
|
|
4
|
+
/**
|
|
5
|
+
* 拐角补角球实例:**直径 1** 的单位球(半径 0.5,与直管半径同量级)。
|
|
6
|
+
*
|
|
7
|
+
* ⭐ 为什么必须有它:直管是开口圆柱(`CylinderGeometry(0.5, 0.5, 1, 48, 1, true)`),两段斜管
|
|
8
|
+
* 在拐角处端面是斜的、只有轴线相交 ⇒ 夹角外侧必然露一个楔形缺口。球心压在控制点上、
|
|
9
|
+
* 缩放 = 直径 ⇒ 球面正好把缺口盖满;球与管同径,共线时完全看不出来。
|
|
10
|
+
*
|
|
11
|
+
* 姿态无所谓(球各向同性),所以只写 position / scale,不碰 quaternion —— 比 PipeBend 那套
|
|
12
|
+
* 三列基向量简单一整个量级,也正是「最简单的方案」想要的。
|
|
13
|
+
*/
|
|
14
|
+
export default class PipeJoint extends InstancedChild {
|
|
15
|
+
static geometry = new SphereGeometry(0.5, 32, 16);
|
|
16
|
+
static material = pipeMaterial;
|
|
17
|
+
/** 控制台取证用:对应第几个控制点(由 PipeMesh 容器在 rebuild 时写入) */
|
|
18
|
+
partIndex = -1;
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this.type = "PipeJoint";
|
|
22
|
+
this.name = "PipeJoint";
|
|
23
|
+
}
|
|
24
|
+
/** 落到某个拐角:球心 + 直径缩放 */
|
|
25
|
+
applyAt(center, diameter) {
|
|
26
|
+
this.position.set(center[0], center[1], center[2]);
|
|
27
|
+
this.scale.setScalar(diameter);
|
|
28
|
+
}
|
|
29
|
+
setPartIndex(i) {
|
|
30
|
+
this.partIndex = i;
|
|
31
|
+
this.name = `PipeJoint[${i}]`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SphereGeometry } from "three";
|
|
2
|
+
import InstancedChild from "../instanced/InstancedChild";
|
|
3
|
+
import { pipeMaterial } from "./pipeMaterial";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 拐角补角球实例:**直径 1** 的单位球(半径 0.5,与直管半径同量级)。
|
|
7
|
+
*
|
|
8
|
+
* ⭐ 为什么必须有它:直管是开口圆柱(`CylinderGeometry(0.5, 0.5, 1, 48, 1, true)`),两段斜管
|
|
9
|
+
* 在拐角处端面是斜的、只有轴线相交 ⇒ 夹角外侧必然露一个楔形缺口。球心压在控制点上、
|
|
10
|
+
* 缩放 = 直径 ⇒ 球面正好把缺口盖满;球与管同径,共线时完全看不出来。
|
|
11
|
+
*
|
|
12
|
+
* 姿态无所谓(球各向同性),所以只写 position / scale,不碰 quaternion —— 比 PipeBend 那套
|
|
13
|
+
* 三列基向量简单一整个量级,也正是「最简单的方案」想要的。
|
|
14
|
+
*/
|
|
15
|
+
export default class PipeJoint extends InstancedChild {
|
|
16
|
+
static geometry = new SphereGeometry(0.5, 32, 16);
|
|
17
|
+
static material = pipeMaterial;
|
|
18
|
+
|
|
19
|
+
/** 控制台取证用:对应第几个控制点(由 PipeMesh 容器在 rebuild 时写入) */
|
|
20
|
+
partIndex: number = -1;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
(this as { type: string }).type = "PipeJoint";
|
|
25
|
+
this.name = "PipeJoint";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 落到某个拐角:球心 + 直径缩放 */
|
|
29
|
+
applyAt(center: [number, number, number], diameter: number): void {
|
|
30
|
+
this.position.set(center[0], center[1], center[2]);
|
|
31
|
+
this.scale.setScalar(diameter);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setPartIndex(i: number): void {
|
|
35
|
+
this.partIndex = i;
|
|
36
|
+
this.name = `PipeJoint[${i}]`;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/PipeMesh/PipeMesh.d.ts
CHANGED
|
@@ -1,25 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { Color, Group } from "three";
|
|
2
|
+
import PipeJoint from "./PipeJoint";
|
|
3
|
+
import PipeStraight from "./PipeStraight";
|
|
4
|
+
import { type PipeJson, type Vec3 } from "./directRoute";
|
|
5
|
+
export interface PipeMeshProps {
|
|
6
|
+
/** 控制点(米)。**相邻两点之间直连一根直管**(可斜),拐角自动补一颗同径球 */
|
|
7
|
+
points?: Vec3[];
|
|
8
|
+
/** 管道直径(米),默认 1 */
|
|
9
|
+
diameter?: number;
|
|
10
|
+
/** 管壁颜色(逐实例色,不是材质底色) */
|
|
11
|
+
color?: Color | number | string;
|
|
12
|
+
/** 多 InstancedGroup 共存时的归属(配合 `new InstancedGroup(owner)`,如预览弹窗) */
|
|
13
|
+
owner?: string;
|
|
14
|
+
}
|
|
9
15
|
/**
|
|
10
|
-
*
|
|
16
|
+
* 组合管道(点与点直连):**直管 + 拐角补角球全部是 InstancedChild**,由 ThreeBase 的 InstancedGroup
|
|
17
|
+
* 每帧自动合批。
|
|
18
|
+
*
|
|
19
|
+
* ⭐ 为什么这样划算:全场景只有两份几何(单位圆柱、单位球),所以**不管多少折点,
|
|
20
|
+
* draw call 恒为 2**;改直径/改长度只动实例矩阵,不重建任何几何。旧的 PipeMesh(现
|
|
21
|
+
* `three-base/PipeMeshLegacy/`)是一条折线扫一张合并 BufferGeometry,改点要重算顶点,两者路线互斥。
|
|
22
|
+
*
|
|
23
|
+
* ⭐ 走线口径(皇上旨 2026-10-25「直接就是点与点直接连接,最简单的方案」):
|
|
24
|
+
* 控制点 i 与 i+1 之间就是一根直管 —— 长度 = 两点欧氏距离、方向 = 连线单位向量、中点 = 两点中点。
|
|
25
|
+
* 没有轴序、没有正交拆段、没有 90° 弯头。走线算术全在 `directRoute.ts`(零依赖,可纯数字断言)。
|
|
26
|
+
*
|
|
27
|
+
* 用法:
|
|
28
|
+
* ```ts
|
|
29
|
+
* const pipe = new PipeMesh({ points: [[-4,0,0],[0,3,0],[4,3,-4]], diameter: 1 });
|
|
30
|
+
* scene.add(pipe);
|
|
31
|
+
* pipe.setDiameter(0.6); // 只改缩放
|
|
32
|
+
* pipe.movePoint(1, [0, 5, 0]); // 只改控制点,走线自动重算
|
|
33
|
+
* const json = pipe.exportJson(); // 存档 / 导出
|
|
34
|
+
* PipeMesh.fromJson(json); // 引用 JSON 重建
|
|
35
|
+
* pipe.setShown(false); // 藏起这条管道(走注册表,不是 visible)
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ 实例池是**差分复用**的:段数变少时多出来的实例会被摘出注册表并 dispose,不会留在注册表里
|
|
39
|
+
* 继续被合批(注册表是全局 Set,`scene.remove` 停不了它)。
|
|
11
40
|
*/
|
|
12
|
-
export default class PipeMesh extends
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
export default class PipeMesh extends Group {
|
|
42
|
+
points: Vec3[];
|
|
43
|
+
diameter: number;
|
|
44
|
+
color: Color;
|
|
45
|
+
/** 最近一次走线的退化提示(供面板显示;不影响渲染) */
|
|
46
|
+
warnings: string[];
|
|
47
|
+
/** 直管实例(段数 = 有效控制点对数) */
|
|
48
|
+
readonly straights: PipeStraight[];
|
|
49
|
+
/** 拐角补角球实例(内部控制点各一颗) */
|
|
50
|
+
readonly joints: PipeJoint[];
|
|
51
|
+
private _owner?;
|
|
52
|
+
private _shown;
|
|
53
|
+
constructor(props?: PipeMeshProps);
|
|
54
|
+
/**
|
|
55
|
+
* 显隐:走注册表摘除/挂回,而不是靠 `visible` 标志。
|
|
56
|
+
* ⚠️ 实例化那条路的真实几何在 InstancedGroup 的 batch 里(不在这棵树下面),
|
|
57
|
+
* 所以 `pipe.visible = false` 对它**不起作用**;要藏起来就调本方法。
|
|
58
|
+
*/
|
|
59
|
+
setShown(shown: boolean): this;
|
|
60
|
+
get shown(): boolean;
|
|
61
|
+
/** 按当前 points/diameter 重算实例位姿;只在段数变化时增删实例 */
|
|
62
|
+
rebuild(): this;
|
|
63
|
+
private _allInstances;
|
|
64
|
+
/** 把池子拉到目标长度:不足 new + add 进本组,超出摘出并 dispose(连带注册表) */
|
|
65
|
+
private _fitPool;
|
|
66
|
+
setPoints(points: Vec3[]): this;
|
|
67
|
+
/** 改单个控制点(拖拽现场用,比整表重设省一次数组拷贝) */
|
|
68
|
+
movePoint(index: number, p: Vec3): this;
|
|
69
|
+
addPoint(p: Vec3): this;
|
|
70
|
+
insertPoint(index: number, p: Vec3): this;
|
|
71
|
+
removePoint(index: number): this;
|
|
72
|
+
/**
|
|
73
|
+
* 控制点顺序整体颠倒(皇上旨 2026-10-25「做一个节点倒序的功能,让管道点的顺序颠倒一下」)。
|
|
74
|
+
*
|
|
75
|
+
* ⚠️ 直连方案下几何**逐段等价**(圆柱两端对称、球压在点上,都不认方向)⇒ 画面上看不出变化,
|
|
76
|
+
* 变的是**数据序**:存档 / 导出 JSON / 将来带方向性的特性(流向箭头、渐变、开孔位置)都按这个序。
|
|
77
|
+
* 所以别指望点一下画面有动静 —— 要验它,去面板看「控制点 #N」的编号,或导出 JSON 比对。
|
|
78
|
+
*/
|
|
79
|
+
reversePoints(): this;
|
|
80
|
+
setDiameter(d: number): this;
|
|
81
|
+
setColor(c: Color | number | string): this;
|
|
82
|
+
/** 导出当前管道的 JSON 数据(不碰 three 的 Object3D 序列化格式,那个是场景树用的) */
|
|
83
|
+
exportJson(): PipeJson;
|
|
84
|
+
/** 引用 JSON 重建当前管道:非法数据不动现场,返回人话原因 */
|
|
85
|
+
importJson(input: unknown): {
|
|
86
|
+
ok: boolean;
|
|
87
|
+
reason?: string;
|
|
88
|
+
};
|
|
89
|
+
/** 用 JSON 起一个新管道对象;失败时 pipe 为 undefined,reason 可直接弹给用户 */
|
|
90
|
+
static fromJson(input: unknown): {
|
|
91
|
+
pipe?: PipeMesh;
|
|
92
|
+
ok: boolean;
|
|
93
|
+
reason?: string;
|
|
94
|
+
};
|
|
95
|
+
/** 摘掉全部实例并释放注册表登记(组本身留在 scene 里由调用方 remove) */
|
|
96
|
+
dispose(): void;
|
|
25
97
|
}
|