three-base 2.0.142 → 2.0.166

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.
Files changed (231) hide show
  1. package/DirectionalLight.d.ts +6 -6
  2. package/DirectionalLight.js +68 -68
  3. package/EventMap.d.ts +103 -103
  4. package/EventMap.js +17 -17
  5. package/EventMesh.d.ts +18 -18
  6. package/EventMesh.js +59 -59
  7. package/ExtrudeMesh.d.ts +112 -112
  8. package/ExtrudeMesh.js +342 -342
  9. package/FPScontrol.d.ts +531 -511
  10. package/FPScontrol.js +1866 -1799
  11. package/FPScontrol.ts +1922 -1855
  12. package/Fire/Fire.d.ts +28 -28
  13. package/Fire/Fire.js +80 -80
  14. package/Fire/FireGeometry.d.ts +9 -9
  15. package/Fire/FireGeometry.js +40 -40
  16. package/Fire/FireMaterial.d.ts +28 -28
  17. package/Fire/FireMaterial.js +32 -32
  18. package/Fire/index.d.ts +2 -2
  19. package/Fire/index.js +2 -2
  20. package/GLTFDracoLoader.d.ts +71 -71
  21. package/GLTFDracoLoader.js +266 -266
  22. package/GLTFDracoLoader.ts +1 -1
  23. package/Grass/Grass.d.ts +8 -8
  24. package/Grass/Grass.js +38 -38
  25. package/Grass/index.d.ts +2 -2
  26. package/Grass/index.js +2 -2
  27. package/Ground1/Ground1.d.ts +10 -10
  28. package/Ground1/Ground1.js +71 -71
  29. package/Ground1/index.d.ts +1 -1
  30. package/Ground1/index.js +1 -1
  31. package/MeshBase.d.ts +36 -36
  32. package/MeshBase.js +251 -247
  33. package/MeshBase.ts +6 -2
  34. package/Mirror.d.ts +25 -25
  35. package/Mirror.js +143 -143
  36. package/MktMesh/MktMesh.d.ts +301 -291
  37. package/MktMesh/MktMesh.js +1064 -1045
  38. package/MktMesh/MktMesh.ts +32 -11
  39. package/MktMesh/assetUrl.d.ts +59 -35
  40. package/MktMesh/assetUrl.js +165 -69
  41. package/MktMesh/assetUrl.ts +164 -69
  42. package/MktMesh/mktMappingShader.d.ts +12 -12
  43. package/MktMesh/mktMappingShader.js +138 -138
  44. package/MktMesh/mktMappingShader.ts +139 -139
  45. package/MktMesh/mktShader.d.ts +263 -263
  46. package/MktMesh/mktShader.js +1236 -1236
  47. package/MktMesh/mktShader.ts +1344 -1344
  48. package/MktMesh/panoBitmapTexture.d.ts +25 -25
  49. package/MktMesh/panoBitmapTexture.js +52 -52
  50. package/MktMesh/panoBitmapTexture.ts +54 -54
  51. package/MktMesh/panoSlotBind.d.ts +29 -29
  52. package/MktMesh/panoSlotBind.js +106 -106
  53. package/MktMesh/panoSlotBind.ts +153 -153
  54. package/MktMesh/panoSlotRoster.d.ts +22 -22
  55. package/MktMesh/panoSlotRoster.js +169 -169
  56. package/MktMesh/panoSlotRoster.ts +168 -168
  57. package/MktMesh/panoTileStitcher.d.ts +119 -119
  58. package/MktMesh/panoTileStitcher.js +529 -529
  59. package/MktMesh/panoTileStitcher.ts +616 -616
  60. package/MktMesh/types.d.ts +75 -75
  61. package/MktMesh/types.js +1 -1
  62. package/MktMesh/wallCapTessellate.d.ts +33 -33
  63. package/MktMesh/wallCapTessellate.js +97 -97
  64. package/MktMesh/wallCapTessellate.ts +68 -68
  65. package/PipeMesh/PathGeometry.d.ts +44 -44
  66. package/PipeMesh/PathGeometry.js +307 -307
  67. package/PipeMesh/PathPoint.d.ts +17 -17
  68. package/PipeMesh/PathPoint.js +31 -31
  69. package/PipeMesh/PathPointList.d.ts +31 -31
  70. package/PipeMesh/PathPointList.js +209 -209
  71. package/PipeMesh/PathTubeGeometry.d.ts +25 -25
  72. package/PipeMesh/PathTubeGeometry.js +130 -130
  73. package/PipeMesh/PipeMesh.d.ts +25 -25
  74. package/PipeMesh/PipeMesh.js +63 -63
  75. package/PipeMesh/index.d.ts +1 -1
  76. package/PipeMesh/index.js +1 -1
  77. package/PlaneModal1/PlaneModal1.d.ts +34 -34
  78. package/PlaneModal1/PlaneModal1.js +109 -109
  79. package/PlaneModal1/index.d.ts +1 -1
  80. package/PlaneModal1/index.js +1 -1
  81. package/PlaneText.d.ts +27 -27
  82. package/PlaneText.js +35 -35
  83. package/PlaneWithHtml.d.ts +13 -13
  84. package/PlaneWithHtml.js +66 -64
  85. package/PlaneWithHtml.ts +3 -1
  86. package/PointLight/PointLight.d.ts +13 -13
  87. package/PointLight/PointLight.js +64 -64
  88. package/PointLight/index.d.ts +1 -1
  89. package/PointLight/index.js +1 -1
  90. package/Sky/Sky.d.ts +9 -9
  91. package/Sky/Sky.js +22 -22
  92. package/Sky/index.d.ts +1 -1
  93. package/Sky/index.js +1 -1
  94. package/SpotLight.d.ts +6 -6
  95. package/SpotLight.js +72 -72
  96. package/SpriteModal1/SpriteModal1.d.ts +33 -33
  97. package/SpriteModal1/SpriteModal1.js +116 -116
  98. package/SpriteModal1/index.d.ts +1 -1
  99. package/SpriteModal1/index.js +1 -1
  100. package/SpriteText.d.ts +46 -46
  101. package/SpriteText.js +176 -176
  102. package/SpriteText2.d.ts +48 -48
  103. package/SpriteText2.js +223 -223
  104. package/SpriteText3.d.ts +86 -86
  105. package/SpriteText3.js +517 -517
  106. package/SpriteText3.ts +599 -599
  107. package/SpriteWithHtml.d.ts +9 -9
  108. package/SpriteWithHtml.js +46 -44
  109. package/SpriteWithHtml.ts +3 -1
  110. package/Text3D/Text3D.d.ts +20 -20
  111. package/Text3D/Text3D.js +58 -58
  112. package/Text3D/fontController.d.ts +60 -60
  113. package/Text3D/fontController.js +166 -166
  114. package/Text3D/index.d.ts +1 -1
  115. package/Text3D/index.js +1 -1
  116. package/ThreeBase.d.ts +248 -248
  117. package/ThreeBase.js +1202 -1202
  118. package/ThreeBase.ts +1314 -1314
  119. package/ThreeMouse.d.ts +53 -54
  120. package/ThreeMouse.js +453 -453
  121. package/Transition.d.ts +45 -45
  122. package/Transition.js +209 -209
  123. package/Water/Water.d.ts +5 -5
  124. package/Water/Water.js +44 -44
  125. package/Water/index.d.ts +2 -2
  126. package/Water/index.js +2 -2
  127. package/WaterFlow/FloatingDuck.d.ts +88 -88
  128. package/WaterFlow/FloatingDuck.js +307 -307
  129. package/WaterFlow/FloatingDuck.ts +445 -445
  130. package/WaterFlow/GPUFluidSimulation.d.ts +56 -56
  131. package/WaterFlow/GPUFluidSimulation.js +781 -781
  132. package/WaterFlow/GPUFluidSimulation.ts +827 -827
  133. package/WaterFlow/ReservoirSystem.d.ts +106 -106
  134. package/WaterFlow/ReservoirSystem.js +611 -611
  135. package/WaterFlow/ReservoirSystem.ts +754 -754
  136. package/WaterFlow/SPHField.d.ts +35 -35
  137. package/WaterFlow/SPHField.js +203 -203
  138. package/WaterFlow/SPHField.ts +228 -228
  139. package/WaterFlow/ScreenSpaceFluidPass.d.ts +69 -69
  140. package/WaterFlow/ScreenSpaceFluidPass.js +728 -728
  141. package/WaterFlow/ScreenSpaceFluidPass.ts +789 -789
  142. package/WaterFlow/ShallowWaterSurface.d.ts +45 -45
  143. package/WaterFlow/ShallowWaterSurface.js +438 -438
  144. package/WaterFlow/ShallowWaterSurface.ts +510 -510
  145. package/WaterFlow/WaterFlow.d.ts +87 -87
  146. package/WaterFlow/WaterFlow.js +171 -171
  147. package/WaterFlow/WaterFlow.ts +219 -219
  148. package/WaterFlow/types.d.ts +161 -161
  149. package/WaterFlow/types.js +1 -1
  150. package/WaterFlow/types.ts +189 -189
  151. package/WaterFlow/waterFlowPass.d.ts +3 -3
  152. package/WaterFlow/waterFlowPass.js +20 -20
  153. package/WaterFlow/waterFlowPass.ts +40 -40
  154. package/collisionOBB.d.ts +42 -42
  155. package/collisionOBB.js +112 -112
  156. package/collisionOBB.ts +108 -108
  157. package/declare.d.ts +25 -25
  158. package/index.d.ts +1 -1
  159. package/index.js +1 -1
  160. package/instanced/InstancedBatch.d.ts +57 -57
  161. package/instanced/InstancedBatch.js +348 -348
  162. package/instanced/InstancedBatch.ts +406 -406
  163. package/instanced/InstancedChild.d.ts +174 -174
  164. package/instanced/InstancedChild.js +264 -264
  165. package/instanced/InstancedChild.ts +327 -327
  166. package/instanced/InstancedGroup.d.ts +43 -43
  167. package/instanced/InstancedGroup.js +126 -126
  168. package/instanced/InstancedGroup.ts +137 -137
  169. package/instanced/InstancedMeshBase.d.ts +38 -38
  170. package/instanced/InstancedMeshBase.js +282 -282
  171. package/instanced/InstancedTemplate.d.ts +46 -46
  172. package/instanced/InstancedTemplate.js +125 -125
  173. package/keyControls.d.ts +29 -30
  174. package/keyControls.js +101 -101
  175. package/lensflare/LensFlare.d.ts +39 -39
  176. package/lensflare/LensFlare.js +284 -284
  177. package/package.json +6 -1
  178. package/pass/bloom_pass.d.ts +2 -2
  179. package/pass/bloom_pass.js +174 -174
  180. package/pass/cover_pass.d.ts +2 -2
  181. package/pass/cover_pass.js +108 -108
  182. package/pass/debugDepthState.d.ts +20 -20
  183. package/pass/debugDepthState.js +9 -9
  184. package/pass/debugDepthState.ts +22 -22
  185. package/pass/depthTestBias.d.ts +12 -12
  186. package/pass/depthTestBias.js +12 -12
  187. package/pass/depthTestBias.ts +12 -12
  188. package/pass/effectUserData.d.ts +27 -27
  189. package/pass/effectUserData.js +1 -1
  190. package/pass/effectUserData.ts +46 -46
  191. package/pass/frosted_glass_pass.d.ts +2 -2
  192. package/pass/frosted_glass_pass.js +150 -150
  193. package/pass/fsQuad.d.ts +9 -9
  194. package/pass/fsQuad.js +28 -28
  195. package/pass/glow_pass.d.ts +2 -2
  196. package/pass/glow_pass.js +350 -350
  197. package/pass/screen_bloom_pass.d.ts +11 -11
  198. package/pass/screen_bloom_pass.js +136 -136
  199. package/pass/ssao_pass.d.ts +88 -88
  200. package/pass/ssao_pass.js +799 -799
  201. package/pass/ssao_pass.ts +918 -918
  202. package/pass/stroke_pass.d.ts +2 -2
  203. package/pass/stroke_pass.js +267 -267
  204. package/shaders/FXAAShaderMaterial.d.ts +3 -3
  205. package/shaders/FXAAShaderMaterial.js +20 -20
  206. package/shaders/baseDepthVisShader.d.ts +11 -11
  207. package/shaders/baseDepthVisShader.js +36 -36
  208. package/shaders/baseDepthVisShader.ts +36 -36
  209. package/shaders/baseTextureToRenderShader.d.ts +2 -2
  210. package/shaders/baseTextureToRenderShader.js +19 -19
  211. package/shaders/copyShader.d.ts +3 -3
  212. package/shaders/copyShader.js +13 -13
  213. package/shaders/debugDepthCompareShader.d.ts +14 -14
  214. package/shaders/debugDepthCompareShader.js +49 -49
  215. package/shaders/debugDepthCompareShader.ts +50 -50
  216. package/shaders/debugDepthJudgeShader.d.ts +18 -18
  217. package/shaders/debugDepthJudgeShader.js +52 -52
  218. package/shaders/debugDepthJudgeShader.ts +54 -54
  219. package/shaders/debugExcludedShader.d.ts +13 -13
  220. package/shaders/debugExcludedShader.js +46 -46
  221. package/shaders/debugExcludedShader.ts +48 -48
  222. package/spriteTextBg.d.ts +1 -1
  223. package/spriteTextBg.js +20 -20
  224. package/spriteTextBg.ts +20 -20
  225. package/threeLoader.d.ts +35 -35
  226. package/threeLoader.js +518 -518
  227. package/threeUtils.d.ts +71 -53
  228. package/threeUtils.js +363 -329
  229. package/threeUtils.ts +35 -0
  230. package/types.d.ts +2 -2
  231. package/types.js +6 -6
@@ -1,264 +1,264 @@
1
- import { Color, Group, Matrix4, Mesh, Object3D, } from "three";
2
- /**
3
- * 所有活着的 InstancedChild 实例集合,供 InstancedGroup 每帧收集。
4
- * 用全局注册表而非遍历整棵 scene,避免大场景每帧 traverse 全树的性能开销。
5
- *
6
- * ⚠️ 多 InstancedGroup 共存时的归属(owner):见 `instancedChildOwner` 与 `getOwner()`。
7
- * 同一个进程里可能并存多个 InstancedGroup(如主编辑器的 ThreeBase 与预览弹窗各自一套),
8
- * 若不隔离,双方会互相把对方的实例收进自己的 batch,导致「A 场景的立方体出现在 B 场景」。
9
- */
10
- export const instancedChildRegistry = new Set();
11
- /**
12
- * 实例归属(owner tag)注册表:child -> owner 字符串。
13
- * InstancedGroup 构造时可声明自己的 owner,每帧只收集 `owner` 匹配(或未声明 owner)的实例。
14
- * 未在表中登记的实例视为「无归属」,会被**声明了 owner 的组**忽略、
15
- * 被**未声明 owner 的组**(如 ThreeBase 的默认组)收集 —— 保持对既有代码零影响。
16
- */
17
- export const instancedChildOwner = new Map();
18
- /**
19
- * 用户继承的「逻辑实例」基类。自身不参与渲染,只描述"一个实例"的位置 / 颜色 / 事件。
20
- *
21
- * 用法:
22
- * ```ts
23
- * class Cube extends InstancedChild {
24
- * static geometry = new BoxGeometry(1, 1, 1);
25
- * static material = new MeshStandardMaterial({ color: 0xffffff });
26
- * constructor() {
27
- * super();
28
- * this.addEventListener("click", () => {
29
- * this.setColor("#ff0000");
30
- * this.position.set(0, 0, 0);
31
- * });
32
- * }
33
- * }
34
- * const cube = new Cube();
35
- * cube.position.set(1, 2, 3);
36
- * threeBase.scene.add(cube); // 加进任意层级即可
37
- * ```
38
- *
39
- * ThreeBase 里固定的 InstancedGroup 会自动把「同 geometry+material」(或「同 instanceUrl+animation」)
40
- * 的 InstancedChild 合并进同一个 InstancedMesh 批量渲染,无需用户管理实例缓冲。
41
- */
42
- export default class InstancedChild extends Object3D {
43
- /** 内置几何模式:子类覆盖这两个静态字段 */
44
- static geometry;
45
- /** 内置几何模式材质 */
46
- static material;
47
- /** GLB 模式:模型地址(与 geometry/material 互斥,优先于内置) */
48
- static instanceUrl;
49
- /** GLB 模式:要播放的动画剪辑名;不同动画 = 不同 batch(彼此独立) */
50
- static animation;
51
- /** GLB 模式:模板(含材质)加载完成后回调,用于按需修正材质(如瓷器瓶 metalness=1 改 0) */
52
- static prepareTemplate;
53
- /** 实例颜色,写入 InstancedMesh.instanceColor */
54
- color = new Color(0xffffff);
55
- /**
56
- * 脏标记:true 时本帧由 InstancedGroup 把本实例的 matrix/color 写回实例缓冲,
57
- * 写完后立即置 false —— 避免没有改动时重复写入真正参与渲染的 InstancedMesh。
58
- */
59
- needUpdate = false;
60
- /** 模板是否就绪(GLB 异步加载完成后置 true) */
61
- templateReady = false;
62
- /**
63
- * 第一人称碰撞检测标记:为 true 时,本实例所属 batch 的 InstancedMesh 会被打上
64
- * `userData.fpsCollision = true`,从而被 FPScontrol 的碰撞射线命中(实例自身的世界矩阵即碰撞体位置)。
65
- * 需配合 three-base 的 FPScontrol 使用(详见 FPSDemo 示例页)。
66
- *
67
- * ⚠️ 语义约束:真实几何合并在同一个 InstancedMesh 里,故碰撞以「整个 batch
68
- * (相同 geometry+material / 相同 instanceUrl+animation 的所有实例)」为单位开关——
69
- * 同一 batch 内的所有实例要么都参与碰撞、要么都不参与。若只想让部分实例参与碰撞,
70
- * 请把它们拆成不同的 InstancedChild 子类(不同 geometry 或不同 batchKey)。
71
- */
72
- fpsCollision = false;
73
- /**
74
- * 自动脏检测开关(实验用,默认开):
75
- * - true:markDirtyIfWorldChanged 每帧比对 matrixWorld,变化则自动置 needUpdate(位置/旋转/缩放变化无需手动置位);
76
- * - false:关闭自动检测,仅手动置 needUpdate(如 setColor)的实例才会写回实例缓冲。
77
- * 关闭后,静态布置的实例渲染不变,但「运动」(旋转/移动/缩放)将不再反映到画面(因为 matrixWorld 变了却没写回)。
78
- * 可在运行时于控制台切换:InstancedChild.AUTO_MARK_DIRTY = true / false。
79
- */
80
- static AUTO_MARK_DIRTY = true;
81
- /**
82
- * 请求切换到的动画剪辑名(由所属 batch 每帧应用)。
83
- * 实例化是"整批共享一个 mixer",动画是 batch 级而非实例级;
84
- * 默认 null 表示不主动切换,沿用 batch 构造时的 static animation。
85
- */
86
- _pendingClip = null;
87
- _cachedWorld = new Matrix4();
88
- constructor(fpsCollision = false) {
89
- super();
90
- this.fpsCollision = fpsCollision;
91
- // 标记:逻辑实例节点,ThreeMouse 收集候选时跳过(不渲染、无几何,靠 InstancedMesh 命中后路由回来)
92
- this.userData.__instancedChild = true;
93
- instancedChildRegistry.add(this);
94
- }
95
- /** 该实例归属哪个 batch 的 key:相同 geometry+material 或相同 instanceUrl+当前生效动画 合并为一类。
96
- * 因 key 含「当前生效动画」,切换动画 = 实例在「glb+旧动画」与「glb+新动画」两个常驻 batch 间迁移,而非 batch 内切 mixer。 */
97
- getBatchKey() {
98
- const ctor = this.constructor;
99
- if (ctor.instanceUrl) {
100
- const anim = this.getEffectiveAnimation() || "__none__";
101
- return `url:${ctor.instanceUrl}:anim:${anim}`;
102
- }
103
- const g = ctor.geometry;
104
- const m = ctor.material;
105
- if (g && m) {
106
- return `geo:${g.uuid}/${m.uuid}`;
107
- }
108
- console.warn("[InstancedChild] 子类必须定义 static geometry + static material,或 static instanceUrl(可选 static animation)");
109
- return `unknown:${(Math.random() * 1e9) | 0}`;
110
- }
111
- /** 本实例当前生效的动画名:优先 playClip 请求,否则类声明的 static animation;用于分桶 key 与 batch mixer 构建 */
112
- getEffectiveAnimation() {
113
- return this._pendingClip ?? this.constructor.animation;
114
- }
115
- /**
116
- * 为一组 InstancedChild 构建"选择代理"(select proxy):
117
- * 用每个实例的世界矩阵 matrixWorld + 同款 geometry/material 重建独立网格,
118
- * 供 stroke_pass 单独渲染其深度 / 灰度,从而得到这组实例"专属"的描边。
119
- *
120
- * 为什么需要它:InstancedChild 是纯逻辑节点(无几何),真实几何在 InstancedGroup 合并出的
121
- * InstancedMesh 里,且一个 InstancedMesh 通常包含很多实例。要单独描边"某一个 / 某一组"实例,
122
- * 无法靠直接渲染该 InstancedChild(空)或整个 InstancedMesh(会描到所有实例)。
123
- * 本方法把目标实例重新"画"成一份独立几何,就能像普通 Mesh 一样单独取深度、单独描边。
124
- *
125
- * 关键点:
126
- * - 用 instance.matrixWorld 直接作为代理网格的 local matrix(代理本身不在 scene 内、无父变换),
127
- * 因此代理在屏幕上的位置 / 深度与真实实例严格一致,描边轮廓正好贴合,且能被同一张 baseDepthTexture 正确遮挡;
128
- * - 代理对象通常注册到 ThreeBase.addStrokeObject(不在 scene 内),由渲染管线在效果阶段描边,
129
- * 不会被正常 base pass 重复绘制。
130
- *
131
- * 仅支持内置几何模式(static geometry + static material);GLB 模式的几何由 batch 解析,
132
- * 需从对应 InstancedBatch 取几何,此处暂跳过(无 static geometry 的实例不进代理)。
133
- *
134
- * @param children 要描边的一组实例(如某个 Group 的全部 InstancedChild)
135
- * @returns 代理 Group(含顺序与 children 一一对应的代理网格);若没有任何可重建的实例则返回 null
136
- */
137
- static buildSelectProxy(children, options = {}) {
138
- const { stroke = true, glow = false, bloom = false, cover = false, stroke_width, stroke_color = "#ff8800", glow_color = "#ff8800", glow_width = 6, glow_opacity = 1, bloom_strength = 1.2, bloom_radius = 10, cover_color = "#ff8800", cover_opacity = 0.4, } = options;
139
- const proxy = new Group();
140
- const sources = [];
141
- for (const child of children) {
142
- const ctor = child.constructor;
143
- const geometry = ctor.geometry;
144
- const material = ctor.material;
145
- if (!geometry || !material) {
146
- continue; // GLB 模式:无 static geometry,跳过(待扩展)
147
- }
148
- const mesh = new Mesh(geometry, material);
149
- // 代理不在 scene 树内 → 用 matrixWorld 作为 local matrix,render 时世界矩阵即等于实例世界矩阵
150
- mesh.matrixAutoUpdate = false;
151
- mesh.matrix.copy(child.matrixWorld);
152
- proxy.add(mesh);
153
- sources.push(child);
154
- }
155
- if (sources.length === 0) {
156
- return null;
157
- }
158
- // 各后处理 pass 都读 object3d.userData 的对应开关(默认 false 直接 return),
159
- // 代理只需在 userData 上打开需要的开关即可被效果阶段单独处理。开关逐一设置,互不耦合,
160
- // 因此一个选择代理可以同时描边 + 辉光 + 泛光 + 覆盖色,或只开其中任意几个。
161
- if (stroke) {
162
- proxy.userData.stroke_enable = true;
163
- proxy.userData.stroke_color = stroke_color;
164
- // 仅当显式传入时才覆盖默认描边粗细,避免影响其他未传此参数的调用方
165
- if (stroke_width !== undefined) {
166
- proxy.userData.stroke_width = stroke_width;
167
- }
168
- }
169
- if (glow) {
170
- proxy.userData.glow_enable = true;
171
- proxy.userData.glow_color = glow_color;
172
- proxy.userData.glow_width = glow_width;
173
- proxy.userData.glow_opacity = glow_opacity;
174
- }
175
- if (bloom) {
176
- proxy.userData.bloom_enable = true;
177
- proxy.userData.bloom_strength = bloom_strength;
178
- proxy.userData.bloom_radius = bloom_radius;
179
- }
180
- if (cover) {
181
- proxy.userData.cover_enable = true;
182
- proxy.userData.cover_color = cover_color;
183
- proxy.userData.cover_opacity = cover_opacity;
184
- }
185
- // 记录源实例顺序,便于每帧把代理矩阵同步到实例最新 matrixWorld(组旋转 / 实例移动时描边跟着动)
186
- proxy.userData.__selectSources = sources;
187
- return { proxy, sources };
188
- }
189
- /** 把代理网格的矩阵同步到各源实例最新的 matrixWorld(组旋转 / 实例移动时描边跟着动)。
190
- * 代理网格与 sources 一一对应(顺序由 buildSelectProxy 保证)。 */
191
- static syncSelectProxy(proxy) {
192
- const sources = proxy.userData.__selectSources;
193
- if (!sources) {
194
- return;
195
- }
196
- for (let i = 0; i < proxy.children.length; i++) {
197
- const src = sources[i];
198
- const mesh = proxy.children[i];
199
- if (src && mesh) {
200
- mesh.matrix.copy(src.matrixWorld);
201
- }
202
- }
203
- }
204
- /** 设置实例颜色(自动置 needUpdate) */
205
- setColor(c) {
206
- this.color.set(c);
207
- this.needUpdate = true;
208
- return this;
209
- }
210
- /**
211
- * 请求切换本实例的动画剪辑。batch 按「glb + 当前生效动画」分桶(每个动画一个常驻 InstancedBatch),
212
- * 调用 playClip 只改变本实例的生效动画,下一帧 getBatchKey 随之变化,实例便自动从旧动画 batch
213
- * 迁移到新动画 batch(两 batch 常驻、不销毁)。传 null 恢复为 static animation。
214
- * 例:soldier.playClip("Walk") / soldier.playClip("Idle")
215
- */
216
- playClip(name) {
217
- this._pendingClip = name;
218
- return this;
219
- }
220
- /** 供 InstancedBatch 每帧读取当前请求的动画剪辑 */
221
- get pendingClip() {
222
- return this._pendingClip;
223
- }
224
- /**
225
- * 位置 / 缩放 / 旋转直接用 three 原生写法即可(position.set / scale.setScalar / rotation.set),
226
- * 无需任何自定义 setter;任一变换变化都会由 InstancedGroup 每帧的 matrixWorld 比对自动置 needUpdate。
227
- * 注:Vector3 在 three 0.167 没有 onChange 回调;Euler/Quaternion 虽有 onChange,但 three 内部已用它
228
- * 同步 rotation↔quaternion,不能覆盖,故统一用 matrixWorld 比对兜底,最贴合 three 的变换检测机制。
229
- */
230
- /** 设置鼠标手势(悬停时光标样式):pointer / grab / move / text / crosshair ... */
231
- setCursor(c) {
232
- this.userData.cursor = c;
233
- return this;
234
- }
235
- /** 由 InstancedGroup 每帧调用:世界矩阵变化则置 needUpdate(setPosition 等已置过,这里双保险) */
236
- markDirtyIfWorldChanged() {
237
- if (!InstancedChild.AUTO_MARK_DIRTY)
238
- return; // 实验开关:关闭后不做自动脏检测
239
- if (!this.matrixWorld.equals(this._cachedWorld)) {
240
- this.needUpdate = true;
241
- this._cachedWorld.copy(this.matrixWorld);
242
- }
243
- }
244
- /** 从注册表移除(对象销毁时调用,避免内存泄漏) */
245
- dispose() {
246
- instancedChildRegistry.delete(this);
247
- instancedChildOwner.delete(this);
248
- }
249
- /**
250
- * 声明本实例归属哪个 InstancedGroup(owner tag)。多组共存时用于隔离,
251
- * 只有 owner 相同的 InstancedGroup 会收集本实例。
252
- *
253
- * 用法:`new Cube().setOwner("preview")`,配合 `new InstancedGroup("preview")`。
254
- * 不调用则归属为空 —— 会被「未声明 owner 的组」(如 ThreeBase 默认组)收集,保持旧行为。
255
- */
256
- setOwner(tag) {
257
- instancedChildOwner.set(this, tag);
258
- return this;
259
- }
260
- /** 读取本实例的归属 tag(未声明返回 undefined) */
261
- getOwner() {
262
- return instancedChildOwner.get(this);
263
- }
264
- }
1
+ import { Color, Group, Matrix4, Mesh, Object3D, } from "three";
2
+ /**
3
+ * 所有活着的 InstancedChild 实例集合,供 InstancedGroup 每帧收集。
4
+ * 用全局注册表而非遍历整棵 scene,避免大场景每帧 traverse 全树的性能开销。
5
+ *
6
+ * ⚠️ 多 InstancedGroup 共存时的归属(owner):见 `instancedChildOwner` 与 `getOwner()`。
7
+ * 同一个进程里可能并存多个 InstancedGroup(如主编辑器的 ThreeBase 与预览弹窗各自一套),
8
+ * 若不隔离,双方会互相把对方的实例收进自己的 batch,导致「A 场景的立方体出现在 B 场景」。
9
+ */
10
+ export const instancedChildRegistry = new Set();
11
+ /**
12
+ * 实例归属(owner tag)注册表:child -> owner 字符串。
13
+ * InstancedGroup 构造时可声明自己的 owner,每帧只收集 `owner` 匹配(或未声明 owner)的实例。
14
+ * 未在表中登记的实例视为「无归属」,会被**声明了 owner 的组**忽略、
15
+ * 被**未声明 owner 的组**(如 ThreeBase 的默认组)收集 —— 保持对既有代码零影响。
16
+ */
17
+ export const instancedChildOwner = new Map();
18
+ /**
19
+ * 用户继承的「逻辑实例」基类。自身不参与渲染,只描述"一个实例"的位置 / 颜色 / 事件。
20
+ *
21
+ * 用法:
22
+ * ```ts
23
+ * class Cube extends InstancedChild {
24
+ * static geometry = new BoxGeometry(1, 1, 1);
25
+ * static material = new MeshStandardMaterial({ color: 0xffffff });
26
+ * constructor() {
27
+ * super();
28
+ * this.addEventListener("click", () => {
29
+ * this.setColor("#ff0000");
30
+ * this.position.set(0, 0, 0);
31
+ * });
32
+ * }
33
+ * }
34
+ * const cube = new Cube();
35
+ * cube.position.set(1, 2, 3);
36
+ * threeBase.scene.add(cube); // 加进任意层级即可
37
+ * ```
38
+ *
39
+ * ThreeBase 里固定的 InstancedGroup 会自动把「同 geometry+material」(或「同 instanceUrl+animation」)
40
+ * 的 InstancedChild 合并进同一个 InstancedMesh 批量渲染,无需用户管理实例缓冲。
41
+ */
42
+ export default class InstancedChild extends Object3D {
43
+ /** 内置几何模式:子类覆盖这两个静态字段 */
44
+ static geometry;
45
+ /** 内置几何模式材质 */
46
+ static material;
47
+ /** GLB 模式:模型地址(与 geometry/material 互斥,优先于内置) */
48
+ static instanceUrl;
49
+ /** GLB 模式:要播放的动画剪辑名;不同动画 = 不同 batch(彼此独立) */
50
+ static animation;
51
+ /** GLB 模式:模板(含材质)加载完成后回调,用于按需修正材质(如瓷器瓶 metalness=1 改 0) */
52
+ static prepareTemplate;
53
+ /** 实例颜色,写入 InstancedMesh.instanceColor */
54
+ color = new Color(0xffffff);
55
+ /**
56
+ * 脏标记:true 时本帧由 InstancedGroup 把本实例的 matrix/color 写回实例缓冲,
57
+ * 写完后立即置 false —— 避免没有改动时重复写入真正参与渲染的 InstancedMesh。
58
+ */
59
+ needUpdate = false;
60
+ /** 模板是否就绪(GLB 异步加载完成后置 true) */
61
+ templateReady = false;
62
+ /**
63
+ * 第一人称碰撞检测标记:为 true 时,本实例所属 batch 的 InstancedMesh 会被打上
64
+ * `userData.fpsCollision = true`,从而被 FPScontrol 的碰撞射线命中(实例自身的世界矩阵即碰撞体位置)。
65
+ * 需配合 three-base 的 FPScontrol 使用(详见 FPSDemo 示例页)。
66
+ *
67
+ * ⚠️ 语义约束:真实几何合并在同一个 InstancedMesh 里,故碰撞以「整个 batch
68
+ * (相同 geometry+material / 相同 instanceUrl+animation 的所有实例)」为单位开关——
69
+ * 同一 batch 内的所有实例要么都参与碰撞、要么都不参与。若只想让部分实例参与碰撞,
70
+ * 请把它们拆成不同的 InstancedChild 子类(不同 geometry 或不同 batchKey)。
71
+ */
72
+ fpsCollision = false;
73
+ /**
74
+ * 自动脏检测开关(实验用,默认开):
75
+ * - true:markDirtyIfWorldChanged 每帧比对 matrixWorld,变化则自动置 needUpdate(位置/旋转/缩放变化无需手动置位);
76
+ * - false:关闭自动检测,仅手动置 needUpdate(如 setColor)的实例才会写回实例缓冲。
77
+ * 关闭后,静态布置的实例渲染不变,但「运动」(旋转/移动/缩放)将不再反映到画面(因为 matrixWorld 变了却没写回)。
78
+ * 可在运行时于控制台切换:InstancedChild.AUTO_MARK_DIRTY = true / false。
79
+ */
80
+ static AUTO_MARK_DIRTY = true;
81
+ /**
82
+ * 请求切换到的动画剪辑名(由所属 batch 每帧应用)。
83
+ * 实例化是"整批共享一个 mixer",动画是 batch 级而非实例级;
84
+ * 默认 null 表示不主动切换,沿用 batch 构造时的 static animation。
85
+ */
86
+ _pendingClip = null;
87
+ _cachedWorld = new Matrix4();
88
+ constructor(fpsCollision = false) {
89
+ super();
90
+ this.fpsCollision = fpsCollision;
91
+ // 标记:逻辑实例节点,ThreeMouse 收集候选时跳过(不渲染、无几何,靠 InstancedMesh 命中后路由回来)
92
+ this.userData.__instancedChild = true;
93
+ instancedChildRegistry.add(this);
94
+ }
95
+ /** 该实例归属哪个 batch 的 key:相同 geometry+material 或相同 instanceUrl+当前生效动画 合并为一类。
96
+ * 因 key 含「当前生效动画」,切换动画 = 实例在「glb+旧动画」与「glb+新动画」两个常驻 batch 间迁移,而非 batch 内切 mixer。 */
97
+ getBatchKey() {
98
+ const ctor = this.constructor;
99
+ if (ctor.instanceUrl) {
100
+ const anim = this.getEffectiveAnimation() || "__none__";
101
+ return `url:${ctor.instanceUrl}:anim:${anim}`;
102
+ }
103
+ const g = ctor.geometry;
104
+ const m = ctor.material;
105
+ if (g && m) {
106
+ return `geo:${g.uuid}/${m.uuid}`;
107
+ }
108
+ console.warn("[InstancedChild] 子类必须定义 static geometry + static material,或 static instanceUrl(可选 static animation)");
109
+ return `unknown:${(Math.random() * 1e9) | 0}`;
110
+ }
111
+ /** 本实例当前生效的动画名:优先 playClip 请求,否则类声明的 static animation;用于分桶 key 与 batch mixer 构建 */
112
+ getEffectiveAnimation() {
113
+ return this._pendingClip ?? this.constructor.animation;
114
+ }
115
+ /**
116
+ * 为一组 InstancedChild 构建"选择代理"(select proxy):
117
+ * 用每个实例的世界矩阵 matrixWorld + 同款 geometry/material 重建独立网格,
118
+ * 供 stroke_pass 单独渲染其深度 / 灰度,从而得到这组实例"专属"的描边。
119
+ *
120
+ * 为什么需要它:InstancedChild 是纯逻辑节点(无几何),真实几何在 InstancedGroup 合并出的
121
+ * InstancedMesh 里,且一个 InstancedMesh 通常包含很多实例。要单独描边"某一个 / 某一组"实例,
122
+ * 无法靠直接渲染该 InstancedChild(空)或整个 InstancedMesh(会描到所有实例)。
123
+ * 本方法把目标实例重新"画"成一份独立几何,就能像普通 Mesh 一样单独取深度、单独描边。
124
+ *
125
+ * 关键点:
126
+ * - 用 instance.matrixWorld 直接作为代理网格的 local matrix(代理本身不在 scene 内、无父变换),
127
+ * 因此代理在屏幕上的位置 / 深度与真实实例严格一致,描边轮廓正好贴合,且能被同一张 baseDepthTexture 正确遮挡;
128
+ * - 代理对象通常注册到 ThreeBase.addStrokeObject(不在 scene 内),由渲染管线在效果阶段描边,
129
+ * 不会被正常 base pass 重复绘制。
130
+ *
131
+ * 仅支持内置几何模式(static geometry + static material);GLB 模式的几何由 batch 解析,
132
+ * 需从对应 InstancedBatch 取几何,此处暂跳过(无 static geometry 的实例不进代理)。
133
+ *
134
+ * @param children 要描边的一组实例(如某个 Group 的全部 InstancedChild)
135
+ * @returns 代理 Group(含顺序与 children 一一对应的代理网格);若没有任何可重建的实例则返回 null
136
+ */
137
+ static buildSelectProxy(children, options = {}) {
138
+ const { stroke = true, glow = false, bloom = false, cover = false, stroke_width, stroke_color = "#ff8800", glow_color = "#ff8800", glow_width = 6, glow_opacity = 1, bloom_strength = 1.2, bloom_radius = 10, cover_color = "#ff8800", cover_opacity = 0.4, } = options;
139
+ const proxy = new Group();
140
+ const sources = [];
141
+ for (const child of children) {
142
+ const ctor = child.constructor;
143
+ const geometry = ctor.geometry;
144
+ const material = ctor.material;
145
+ if (!geometry || !material) {
146
+ continue; // GLB 模式:无 static geometry,跳过(待扩展)
147
+ }
148
+ const mesh = new Mesh(geometry, material);
149
+ // 代理不在 scene 树内 → 用 matrixWorld 作为 local matrix,render 时世界矩阵即等于实例世界矩阵
150
+ mesh.matrixAutoUpdate = false;
151
+ mesh.matrix.copy(child.matrixWorld);
152
+ proxy.add(mesh);
153
+ sources.push(child);
154
+ }
155
+ if (sources.length === 0) {
156
+ return null;
157
+ }
158
+ // 各后处理 pass 都读 object3d.userData 的对应开关(默认 false 直接 return),
159
+ // 代理只需在 userData 上打开需要的开关即可被效果阶段单独处理。开关逐一设置,互不耦合,
160
+ // 因此一个选择代理可以同时描边 + 辉光 + 泛光 + 覆盖色,或只开其中任意几个。
161
+ if (stroke) {
162
+ proxy.userData.stroke_enable = true;
163
+ proxy.userData.stroke_color = stroke_color;
164
+ // 仅当显式传入时才覆盖默认描边粗细,避免影响其他未传此参数的调用方
165
+ if (stroke_width !== undefined) {
166
+ proxy.userData.stroke_width = stroke_width;
167
+ }
168
+ }
169
+ if (glow) {
170
+ proxy.userData.glow_enable = true;
171
+ proxy.userData.glow_color = glow_color;
172
+ proxy.userData.glow_width = glow_width;
173
+ proxy.userData.glow_opacity = glow_opacity;
174
+ }
175
+ if (bloom) {
176
+ proxy.userData.bloom_enable = true;
177
+ proxy.userData.bloom_strength = bloom_strength;
178
+ proxy.userData.bloom_radius = bloom_radius;
179
+ }
180
+ if (cover) {
181
+ proxy.userData.cover_enable = true;
182
+ proxy.userData.cover_color = cover_color;
183
+ proxy.userData.cover_opacity = cover_opacity;
184
+ }
185
+ // 记录源实例顺序,便于每帧把代理矩阵同步到实例最新 matrixWorld(组旋转 / 实例移动时描边跟着动)
186
+ proxy.userData.__selectSources = sources;
187
+ return { proxy, sources };
188
+ }
189
+ /** 把代理网格的矩阵同步到各源实例最新的 matrixWorld(组旋转 / 实例移动时描边跟着动)。
190
+ * 代理网格与 sources 一一对应(顺序由 buildSelectProxy 保证)。 */
191
+ static syncSelectProxy(proxy) {
192
+ const sources = proxy.userData.__selectSources;
193
+ if (!sources) {
194
+ return;
195
+ }
196
+ for (let i = 0; i < proxy.children.length; i++) {
197
+ const src = sources[i];
198
+ const mesh = proxy.children[i];
199
+ if (src && mesh) {
200
+ mesh.matrix.copy(src.matrixWorld);
201
+ }
202
+ }
203
+ }
204
+ /** 设置实例颜色(自动置 needUpdate) */
205
+ setColor(c) {
206
+ this.color.set(c);
207
+ this.needUpdate = true;
208
+ return this;
209
+ }
210
+ /**
211
+ * 请求切换本实例的动画剪辑。batch 按「glb + 当前生效动画」分桶(每个动画一个常驻 InstancedBatch),
212
+ * 调用 playClip 只改变本实例的生效动画,下一帧 getBatchKey 随之变化,实例便自动从旧动画 batch
213
+ * 迁移到新动画 batch(两 batch 常驻、不销毁)。传 null 恢复为 static animation。
214
+ * 例:soldier.playClip("Walk") / soldier.playClip("Idle")
215
+ */
216
+ playClip(name) {
217
+ this._pendingClip = name;
218
+ return this;
219
+ }
220
+ /** 供 InstancedBatch 每帧读取当前请求的动画剪辑 */
221
+ get pendingClip() {
222
+ return this._pendingClip;
223
+ }
224
+ /**
225
+ * 位置 / 缩放 / 旋转直接用 three 原生写法即可(position.set / scale.setScalar / rotation.set),
226
+ * 无需任何自定义 setter;任一变换变化都会由 InstancedGroup 每帧的 matrixWorld 比对自动置 needUpdate。
227
+ * 注:Vector3 在 three 0.167 没有 onChange 回调;Euler/Quaternion 虽有 onChange,但 three 内部已用它
228
+ * 同步 rotation↔quaternion,不能覆盖,故统一用 matrixWorld 比对兜底,最贴合 three 的变换检测机制。
229
+ */
230
+ /** 设置鼠标手势(悬停时光标样式):pointer / grab / move / text / crosshair ... */
231
+ setCursor(c) {
232
+ this.userData.cursor = c;
233
+ return this;
234
+ }
235
+ /** 由 InstancedGroup 每帧调用:世界矩阵变化则置 needUpdate(setPosition 等已置过,这里双保险) */
236
+ markDirtyIfWorldChanged() {
237
+ if (!InstancedChild.AUTO_MARK_DIRTY)
238
+ return; // 实验开关:关闭后不做自动脏检测
239
+ if (!this.matrixWorld.equals(this._cachedWorld)) {
240
+ this.needUpdate = true;
241
+ this._cachedWorld.copy(this.matrixWorld);
242
+ }
243
+ }
244
+ /** 从注册表移除(对象销毁时调用,避免内存泄漏) */
245
+ dispose() {
246
+ instancedChildRegistry.delete(this);
247
+ instancedChildOwner.delete(this);
248
+ }
249
+ /**
250
+ * 声明本实例归属哪个 InstancedGroup(owner tag)。多组共存时用于隔离,
251
+ * 只有 owner 相同的 InstancedGroup 会收集本实例。
252
+ *
253
+ * 用法:`new Cube().setOwner("preview")`,配合 `new InstancedGroup("preview")`。
254
+ * 不调用则归属为空 —— 会被「未声明 owner 的组」(如 ThreeBase 默认组)收集,保持旧行为。
255
+ */
256
+ setOwner(tag) {
257
+ instancedChildOwner.set(this, tag);
258
+ return this;
259
+ }
260
+ /** 读取本实例的归属 tag(未声明返回 undefined) */
261
+ getOwner() {
262
+ return instancedChildOwner.get(this);
263
+ }
264
+ }