@ridp/threejs 1.4.2 → 1.4.3
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/{readme.md → README.md}
RENAMED
|
@@ -352,9 +352,13 @@ threeJsIns.setView(model, ViewType.ISO, { scale: 1.0, animate: true });
|
|
|
352
352
|
|
|
353
353
|
### useGLTFLoader()
|
|
354
354
|
|
|
355
|
-
GLTF 模型加载器,支持 IndexedDB
|
|
355
|
+
GLTF 模型加载器,支持 IndexedDB 缓存、内存缓存、重试机制和调试模式。
|
|
356
356
|
|
|
357
357
|
```javascript
|
|
358
|
+
const loader = useGLTFLoader({
|
|
359
|
+
debug: false // 是否开启调试日志(默认: false)
|
|
360
|
+
});
|
|
361
|
+
|
|
358
362
|
const {
|
|
359
363
|
// 核心加载方法
|
|
360
364
|
load, // 基础加载方法
|
|
@@ -366,30 +370,61 @@ const {
|
|
|
366
370
|
loadBatch, // 批量加载模型
|
|
367
371
|
loadBatchSequential, // 顺序批量加载
|
|
368
372
|
|
|
369
|
-
// 缓存管理
|
|
370
|
-
clearCache, //
|
|
373
|
+
// IndexedDB 缓存管理
|
|
374
|
+
clearCache, // 清空 IndexedDB 缓存
|
|
371
375
|
invalidateCache, // 使缓存失效
|
|
372
376
|
getCacheStats, // 获取缓存统计
|
|
373
377
|
logCacheReport, // 输出缓存性能报告
|
|
378
|
+
getCache, // 获取 IDBCache 实例
|
|
379
|
+
resetCacheStats, // 重置缓存统计
|
|
380
|
+
|
|
381
|
+
// 内存缓存管理 (v1.4.2+)
|
|
382
|
+
clearMemoryCache, // 清空内存缓存
|
|
383
|
+
getMemoryCacheInfo, // 获取内存缓存统计
|
|
384
|
+
deleteMemoryCache, // 删除指定模型的内存缓存
|
|
374
385
|
|
|
375
386
|
// 性能监控
|
|
376
387
|
cacheMonitor // 缓存监控器实例
|
|
377
|
-
} =
|
|
388
|
+
} = loader;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**新增功能 (v1.4.2):**
|
|
392
|
+
|
|
393
|
+
1. **内存缓存机制**: 自动缓存已解析的 3D 模型对象,避免重复解析
|
|
394
|
+
2. **调试模式控制**: 通过初始化参数统一控制所有日志输出
|
|
395
|
+
|
|
396
|
+
#### 初始化配置
|
|
397
|
+
|
|
398
|
+
```javascript
|
|
399
|
+
// 生产环境 - 默认静默
|
|
400
|
+
const loader = useGLTFLoader();
|
|
401
|
+
|
|
402
|
+
// 开发环境 - 开启调试日志
|
|
403
|
+
const loader = useGLTFLoader({
|
|
404
|
+
debug: true
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// 所有操作都会输出详细日志
|
|
408
|
+
const model = await loader.asyncFetch('/models/car.glb', '1.0.0');
|
|
409
|
+
// 输出: [ asyncFetch ] ====> 缓存未命中
|
|
410
|
+
// [ fetchArrayBuffer ] 加载模型耗时: 150ms
|
|
411
|
+
// [ 解析模型耗时 ]: 80ms
|
|
412
|
+
// [ 内存缓存 ] 存储模型 /models/car.glb (version: 1.0.0)
|
|
378
413
|
```
|
|
379
414
|
|
|
380
415
|
**常用方法:**
|
|
381
416
|
|
|
382
417
|
##### asyncFetch()
|
|
383
418
|
|
|
384
|
-
|
|
419
|
+
推荐的模型加载方法,集成 IndexedDB 缓存、内存缓存、重试机制、性能监控和模型优化。
|
|
385
420
|
|
|
386
421
|
```javascript
|
|
387
|
-
const
|
|
422
|
+
const model = await asyncFetch(
|
|
388
423
|
url: string, // 模型 URL
|
|
389
424
|
version?: string, // 模型版本(用于缓存失效)
|
|
425
|
+
onProgress?: (percent: number) => void, // 进度回调
|
|
390
426
|
options?: {
|
|
391
|
-
//
|
|
392
|
-
onProgress?: (progress: ProgressEvent) => void,
|
|
427
|
+
// 重试配置
|
|
393
428
|
maxRetries?: number, // 最大重试次数(默认: 3)
|
|
394
429
|
|
|
395
430
|
// 模型优化选项
|
|
@@ -399,35 +434,40 @@ const [err, model] = await asyncFetch(
|
|
|
399
434
|
simplifyOptions?: {
|
|
400
435
|
minFaceCount?: number, // 最小面数阈值,低于此值不简化(默认: 100)
|
|
401
436
|
preserveUVs?: boolean // 是否保留UV坐标(默认: true)
|
|
402
|
-
}
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
// 缓存控制
|
|
440
|
+
useMemoryCache?: boolean // 是否使用内存缓存(默认: true, v1.4.2+)
|
|
403
441
|
}
|
|
404
|
-
): Promise<
|
|
442
|
+
): Promise<THREE.Object3D | null>
|
|
405
443
|
```
|
|
406
444
|
|
|
445
|
+
**缓存层级 (v1.4.2+):**
|
|
446
|
+
|
|
447
|
+
1. **内存缓存** (最快) - 已解析的 3D 对象,仅需克隆(~2ms)
|
|
448
|
+
2. **IndexedDB 缓存** (快) - ArrayBuffer 数据,需要解析(~200ms)
|
|
449
|
+
3. **网络加载** (慢) - 完整下载 + 解析(~2s)
|
|
450
|
+
|
|
407
451
|
**示例:**
|
|
408
452
|
```javascript
|
|
409
453
|
// 基础用法
|
|
410
|
-
const
|
|
454
|
+
const model = await asyncFetch('/models/car.glb', '1.0.0');
|
|
411
455
|
if (model) {
|
|
412
456
|
scene.add(model);
|
|
413
457
|
}
|
|
414
458
|
|
|
415
459
|
// 带进度回调
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
const percent = (progress.loaded / progress.total * 100).toFixed(2);
|
|
419
|
-
console.log(`加载进度: ${percent}%`);
|
|
420
|
-
},
|
|
421
|
-
maxRetries: 5
|
|
460
|
+
const model = await asyncFetch('/models/car.glb', '1.0.0', (percent) => {
|
|
461
|
+
console.log(`加载进度: ${percent}%`);
|
|
422
462
|
});
|
|
423
463
|
|
|
424
464
|
// 启用材质优化
|
|
425
|
-
const
|
|
465
|
+
const model = await asyncFetch('/models/car.glb', '1.0.0', null, {
|
|
426
466
|
optimizeMaterials: true // 自动合并相同材质
|
|
427
467
|
});
|
|
428
468
|
|
|
429
469
|
// 启用几何体简化(适合大型模型)
|
|
430
|
-
const
|
|
470
|
+
const model = await asyncFetch('/models/large-scene.glb', '1.0.0', null, {
|
|
431
471
|
simplifyGeometry: true, // 启用简化
|
|
432
472
|
simplifyRatio: 0.5, // 保留 50% 的面
|
|
433
473
|
simplifyOptions: {
|
|
@@ -436,18 +476,43 @@ const [err, model] = await asyncFetch('/models/large-scene.glb', '1.0.0', {
|
|
|
436
476
|
}
|
|
437
477
|
});
|
|
438
478
|
|
|
479
|
+
// 禁用内存缓存
|
|
480
|
+
const model = await asyncFetch('/models/car.glb', '1.0.0', null, {
|
|
481
|
+
useMemoryCache: false // 跳过内存缓存
|
|
482
|
+
});
|
|
483
|
+
|
|
439
484
|
// 综合优化
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
485
|
+
const model = await asyncFetch('/models/complex.glb', '1.0.0', (p) => {
|
|
486
|
+
console.log(`加载: ${p.toFixed(1)}%`);
|
|
487
|
+
}, {
|
|
488
|
+
maxRetries: 5, // 增加重试次数
|
|
489
|
+
optimizeMaterials: true, // 优化材质
|
|
490
|
+
simplifyGeometry: true, // 简化几何体
|
|
491
|
+
simplifyRatio: 0.7, // 保留 70% 的面
|
|
492
|
+
useMemoryCache: true // 使用内存缓存(默认)
|
|
445
493
|
});
|
|
446
494
|
```
|
|
447
495
|
|
|
496
|
+
**性能对比:**
|
|
497
|
+
|
|
498
|
+
```javascript
|
|
499
|
+
// 首次加载
|
|
500
|
+
await asyncFetch('/models/car.glb', '1.0.0');
|
|
501
|
+
// 耗时: ~2s (网络 + 解析 + 优化)
|
|
502
|
+
|
|
503
|
+
// 二次加载 (内存缓存命中)
|
|
504
|
+
await asyncFetch('/models/car.glb', '1.0.0');
|
|
505
|
+
// 耗时: ~2ms (仅克隆对象)
|
|
506
|
+
|
|
507
|
+
// 二次加载 (仅 IndexedDB 命中,内存缓存被清空)
|
|
508
|
+
loader.clearMemoryCache();
|
|
509
|
+
await asyncFetch('/models/car.glb', '1.0.0');
|
|
510
|
+
// 耗时: ~200ms (从 IndexedDB 读取 + 解析)
|
|
511
|
+
```
|
|
512
|
+
|
|
448
513
|
##### logCacheReport()
|
|
449
514
|
|
|
450
|
-
|
|
515
|
+
输出 IndexedDB 缓存性能报告到控制台。
|
|
451
516
|
|
|
452
517
|
```javascript
|
|
453
518
|
logCacheReport(): void
|
|
@@ -465,6 +530,89 @@ logCacheReport(): void
|
|
|
465
530
|
- 缓存节省时间: ~1.5s
|
|
466
531
|
```
|
|
467
532
|
|
|
533
|
+
##### 内存缓存管理 API (v1.4.2+)
|
|
534
|
+
|
|
535
|
+
**getMemoryCacheInfo()**
|
|
536
|
+
|
|
537
|
+
获取内存缓存统计信息。
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
const info = loader.getMemoryCacheInfo();
|
|
541
|
+
|
|
542
|
+
// 返回值:
|
|
543
|
+
{
|
|
544
|
+
totalPaths: 2, // 缓存的不同模型路径数量
|
|
545
|
+
totalModels: 3, // 缓存的总模型数量
|
|
546
|
+
details: [
|
|
547
|
+
{
|
|
548
|
+
path: '/models/car.glb',
|
|
549
|
+
versions: ['1.0.0', '1.1.0'],
|
|
550
|
+
count: 2
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
path: '/models/truck.glb',
|
|
554
|
+
versions: ['1.0.0'],
|
|
555
|
+
count: 1
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**clearMemoryCache()**
|
|
562
|
+
|
|
563
|
+
清空所有内存缓存,释放已缓存的 3D 模型对象内存。
|
|
564
|
+
|
|
565
|
+
```javascript
|
|
566
|
+
const count = loader.clearMemoryCache();
|
|
567
|
+
console.log(`释放了 ${count} 个模型`);
|
|
568
|
+
|
|
569
|
+
// 使用场景: 内存不足时主动释放
|
|
570
|
+
if (info.totalModels > 100) {
|
|
571
|
+
loader.clearMemoryCache();
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**deleteMemoryCache(path, version?)**
|
|
576
|
+
|
|
577
|
+
删除指定模型的内存缓存。
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
// 删除特定版本
|
|
581
|
+
loader.deleteMemoryCache('/models/car.glb', '1.0.0');
|
|
582
|
+
|
|
583
|
+
// 删除所有版本
|
|
584
|
+
loader.deleteMemoryCache('/models/car.glb');
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**使用示例:**
|
|
588
|
+
|
|
589
|
+
```javascript
|
|
590
|
+
import { useGLTFLoader } from '@ridp/threejs/hooks';
|
|
591
|
+
|
|
592
|
+
const loader = useGLTFLoader({ debug: true });
|
|
593
|
+
|
|
594
|
+
// 首次加载 - 从网络加载并缓存
|
|
595
|
+
const model1 = await loader.asyncFetch('/models/car.glb', '1.0.0');
|
|
596
|
+
// [ asyncFetch ] ====> 缓存未命中
|
|
597
|
+
// [ 内存缓存 ] 存储模型 /models/car.glb (version: 1.0.0)
|
|
598
|
+
|
|
599
|
+
// 二次加载 - 从内存缓存获取(极快)
|
|
600
|
+
const model2 = await loader.asyncFetch('/models/car.glb', '1.0.0');
|
|
601
|
+
// [ 内存缓存命中 ] /models/car.glb (version: 1.0.0)
|
|
602
|
+
// [ 内存缓存克隆耗时 ]: 2ms
|
|
603
|
+
|
|
604
|
+
// 查看内存缓存信息
|
|
605
|
+
const info = loader.getMemoryCacheInfo();
|
|
606
|
+
console.log(`缓存了 ${info.totalModels} 个模型`);
|
|
607
|
+
|
|
608
|
+
// 清空内存缓存
|
|
609
|
+
loader.clearMemoryCache();
|
|
610
|
+
|
|
611
|
+
// 再次加载 - 从 IndexedDB 缓存获取
|
|
612
|
+
const model3 = await loader.asyncFetch('/models/car.glb', '1.0.0');
|
|
613
|
+
// [ asyncFetch ] ====> IndexedDB 缓存命中
|
|
614
|
+
```
|
|
615
|
+
|
|
468
616
|
### useRaycaster()
|
|
469
617
|
|
|
470
618
|
射线拾取工具,用于鼠标交互和物体选择。
|
|
@@ -1152,7 +1300,61 @@ function captureScreenshot() {
|
|
|
1152
1300
|
|
|
1153
1301
|
## 更新日志
|
|
1154
1302
|
|
|
1155
|
-
### v1.
|
|
1303
|
+
### v1.4.2 (2026-01-07)
|
|
1304
|
+
|
|
1305
|
+
**🎉 重大更新:**
|
|
1306
|
+
|
|
1307
|
+
- ✨ **新增内存缓存机制**: 自动缓存已解析的 3D 模型对象,避免重复解析
|
|
1308
|
+
- 首次加载后,二次加载仅需 ~2ms(对象克隆)
|
|
1309
|
+
- 相比完整加载流程,速度提升可达 **100倍以上**
|
|
1310
|
+
- 新增 `clearMemoryCache()` - 清空内存缓存
|
|
1311
|
+
- 新增 `getMemoryCacheInfo()` - 获取内存缓存统计
|
|
1312
|
+
- 新增 `deleteMemoryCache(path, version?)` - 删除指定缓存
|
|
1313
|
+
- 支持 `useMemoryCache` 选项控制是否使用内存缓存
|
|
1314
|
+
|
|
1315
|
+
- ✨ **新增调试模式控制**:
|
|
1316
|
+
- `useGLTFLoader` 现在支持初始化配置 `{ debug: boolean }`
|
|
1317
|
+
- 统一控制所有内部函数的日志输出
|
|
1318
|
+
- 生产环境默认静默,开发环境可开启详细日志
|
|
1319
|
+
- 错误日志始终输出,不受 debug 配置影响
|
|
1320
|
+
|
|
1321
|
+
- 🐛 **修复 Web Worker 在 npm 包中无法加载的问题**:
|
|
1322
|
+
- 使用 `?worker` 语法正确导入 Worker
|
|
1323
|
+
- 配置 `base: './'` 使用相对路径解析
|
|
1324
|
+
- Worker 文件正确打包到 `dist/assets/` 目录
|
|
1325
|
+
- 修复了之前 404 错误和 MIME 类型错误的问题
|
|
1326
|
+
|
|
1327
|
+
**API 变更:**
|
|
1328
|
+
|
|
1329
|
+
```javascript
|
|
1330
|
+
// 旧 API (v1.4.1 及之前)
|
|
1331
|
+
const loader = useGLTFLoader();
|
|
1332
|
+
const model = await loader.asyncFetch(url, version, null, { debug: true });
|
|
1333
|
+
|
|
1334
|
+
// 新 API (v1.4.2+)
|
|
1335
|
+
const loader = useGLTFLoader({ debug: true });
|
|
1336
|
+
const model = await loader.asyncFetch(url, version);
|
|
1337
|
+
|
|
1338
|
+
// 新增内存缓存管理
|
|
1339
|
+
const info = loader.getMemoryCacheInfo();
|
|
1340
|
+
loader.clearMemoryCache();
|
|
1341
|
+
loader.deleteMemoryCache(url, version);
|
|
1342
|
+
```
|
|
1343
|
+
|
|
1344
|
+
**性能提升:**
|
|
1345
|
+
|
|
1346
|
+
```
|
|
1347
|
+
加载场景 (v1.4.1):
|
|
1348
|
+
- 首次: ~2s (网络 + 解析)
|
|
1349
|
+
- 二次: ~200ms (IndexedDB 读取 + 解析)
|
|
1350
|
+
|
|
1351
|
+
加载场景 (v1.4.2+):
|
|
1352
|
+
- 首次: ~2s (网络 + 解析)
|
|
1353
|
+
- 二次: ~2ms (内存缓存克隆)
|
|
1354
|
+
- 提升: 100倍
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
### v1.4.1 (2025-XX-XX)
|
|
1156
1358
|
|
|
1157
1359
|
- 🎯 优化 `setView` 方法的 scale 参数计算逻辑
|
|
1158
1360
|
- 🔧 修复水平 FOV 超过 180° 导致的负数距离问题
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e="1.4.
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e="1.4.3";exports.version=e;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var q=Object.defineProperty;var A=(x,e,t)=>e in x?q(x,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):x[e]=t;var i=(x,e,t)=>A(x,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("three"),H=require("../../../../node_modules/.pnpm/three@0.178.0/node_modules/three/examples/jsm/renderers/CSS3DRenderer.cjs"),P=require("../../../../node_modules/.pnpm/three@0.178.0/node_modules/three/examples/jsm/libs/stats.module.cjs"),X=require("../utils/helper.cjs"),Y=require("../utils/disposeObject.cjs");require("../utils/PredictiveLoader.cjs");const j=require("../../package.json.cjs"),Z=50,_=20,B=20,M={TOP:"top",RIGHT:"right",LEFT:"left",ISO:"iso"},U={enableDamping:!0,dampingFactor:.25,screenSpacePanning:!1,minDistance:.1,maxDistance:1e3,maxPolarAngle:r.MathUtils.degToRad(60)};class y{constructor(e,t){i(this,"isReady",!1);i(this,"scene",null);i(this,"camera",null);i(this,"renderer",null);i(this,"control",null);i(this,"css3dRenderer",null);i(this,"el",null);i(this,"renderRequested",!1);i(this,"selector",null);i(this,"eventsListener",{});i(this,"stats",null);i(this,"isDispose",!1);i(this,"version","0.0.0");i(this,"boxHelper",null);i(this,"initOpt",{css3d:!1,stats:!1,renderType:"change",initListener:!0,initialFov:50,control:{init:!0,options:{}}});i(this,"setup",e=>{if(this.isDispose=!1,this.selector=e,this.el=document.querySelector(e),!this.el){console.error(`ThreeIns: 找不到元素 ${e}`);return}const[t,h]=this.getTargetSize();if(this.updateCameraFOV(t,h),this.camera.position.set(0,0,0),this.camera.lookAt(0,0,0),this.camera.updateProjectionMatrix(),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(t,h),this.el.appendChild(this.renderer.domElement),this.initOpt.control&&this.initOpt.control.init){this.control=X.createOrbitControl(this.camera,this.renderer.domElement);const s=Object.assign(U,this.initOpt.control.options||{});Object.keys(s).forEach(p=>{this.control[p]=s[p]})}setTimeout(()=>{this.isReady=!0},_),this.initOpt.stats&&this.initStats(),this.initOpt.css3d&&this.initCss3dRenderer(),this.initOpt.renderType==="loop"?this.animate():this.initOpt.renderType==="change"&&this.control&&this.control.addEventListener("change",this.requestRenderIfNotRequested),this.initListener()});i(this,"onContextLost",e=>{e.preventDefault(),this.animationFrameId&&cancelAnimationFrame(this.animationFrameId)});i(this,"onContextRestored",e=>{e.preventDefault(),this.dispose(),setTimeout(()=>{this.setup(this.selector)},B)});i(this,"initListener",()=>{this.initOpt.initListener&&window&&window.addEventListener("resize",this.onResize,!1),this.renderer.domElement.addEventListener("webglcontextlost",this.onContextLost,!1),this.renderer.domElement.addEventListener("webglcontextrestored",this.onContextRestored,!1)});i(this,"removeListener",()=>{window&&window.removeEventListener("resize",this.onResize,!1),this.renderer&&this.renderer.domElement&&(this.renderer.domElement.removeEventListener("webglcontextlost",this.onContextLost,!1),this.renderer.domElement.removeEventListener("webglcontextrestored",this.onContextRestored,!1))});i(this,"animate",()=>{this.isDispose||(this.initOpt.renderType==="loop"&&this.onRender(),this.animationFrameId=requestAnimationFrame(this.animate))});i(this,"onRender",()=>{this.isDispose||(this.renderRequested=!1,this.stats&&this.stats.update(),this.control&&this.control.update(),this.renderer.render(this.scene,this.camera),this.css3dRenderer&&this.css3dRenderer.render(this.scene,this.camera),this.eventsListener.onRender&&this.eventsListener.onRender.length&&this.eventsListener.onRender.forEach(e=>e()))});i(this,"requestRenderIfNotRequested",()=>{this.renderRequested||(this.renderRequested=!0,requestAnimationFrame(()=>{this.onRender()}))});i(this,"onResize",()=>{this.resizeTimer&&clearTimeout(this.resizeTimer),this.resizeTimer=setTimeout(()=>{const[e,t]=this.getTargetSize();this.updateCameraFOV(e,t),this.camera.lookAt(this.scene.position),this.renderer.setSize(e,t),this.css3dRenderer&&this.css3dRenderer.setSize(e,t),this.onRender()},Z)});i(this,"frameArea",(e,t)=>(console.warn(`[ThreeIns] frameArea() 已弃用,建议使用 setView() 方法。
|
|
2
2
|
旧用法: threeIns.frameArea(model, scale)
|
|
3
|
-
新用法: threeIns.setView(model, ViewType.ISO, { scale })`),this.setView(e,
|
|
3
|
+
新用法: threeIns.setView(model, ViewType.ISO, { scale })`),this.setView(e,M.ISO,{scale:t,animate:!1,showBox:!1})));i(this,"setView",(e,t,h={})=>{let s=h.scale||.8,p=h.offset||null,F=h.position||"center",R=h.showBox||!1,C=h.boxColor||16776960,E=h.animate!==void 0?h.animate:!0,b=h.duration||1e3;const g=new r.Box3().setFromObject(e);let c=g.getCenter(new r.Vector3);if(typeof F=="string"){const o=g.getSize(new r.Vector3),l={center:new r.Vector3(0,0,0),"top-left":new r.Vector3(-o.x*.3,o.y*.3,o.z*.3),"top-right":new r.Vector3(o.x*.3,o.y*.3,o.z*.3),"bottom-left":new r.Vector3(-o.x*.3,-o.y*.3,o.z*.3),"bottom-right":new r.Vector3(o.x*.3,-o.y*.3,o.z*.3)},d=l[F]||l.center;t==="top"?c.add(new r.Vector3(d.x,0,d.z)):t==="right"||t==="left"?c.add(new r.Vector3(0,d.y,d.z)):c.add(d)}else F instanceof r.Vector3&&c.add(F);p&&c.add(p);const T={top:new r.Vector3(0,1,0),right:new r.Vector3(2,1,1).normalize(),left:new r.Vector3(-2,1,1).normalize(),iso:new r.Vector3(0,1,1).normalize()},z=T[t]||T.iso,n=g.getSize(new r.Vector3),O=this.camera.aspect,a=r.MathUtils.degToRad(this.camera.fov*.5),S=r.MathUtils.degToRad(80),m=Math.min(Math.atan(Math.tan(a)*O),S);let f;if(t==="top"){const o=n.x*.5/(Math.tan(m)*s),l=n.z*.5/(Math.tan(a)*s);f=Math.max(o,l)}else if(t==="right"||t==="left"){const o=n.y*.5/(Math.tan(a)*s),l=n.z*.5/(Math.tan(a)*s);f=Math.max(o,l)}else{const o=n.x*.5/(Math.tan(m)*s),l=n.y*.5/(Math.tan(a)*s),d=n.z*.5/(Math.tan(a)*s);f=Math.max(o,l,d)}const u=z.clone().multiplyScalar(f).add(c);if(R&&(console.log("📍 相机位置验证:"),console.log(" - 方向向量:",z),console.log(" - 距离:",f.toFixed(2)),console.log(" - 包围盒中心:",c),console.log(" - 计算公式: direction * distance + boxCenter"),console.log(" - 目标位置:",u),console.log(" - 实际相机与中心的距离:",u.clone().sub(c).length().toFixed(2))),R?(this.boxHelper&&(this.scene.remove(this.boxHelper),this.boxHelper=null),this.boxHelper=new r.Box3Helper(g,C),this.scene.add(this.boxHelper)):this.boxHelper&&(this.scene.remove(this.boxHelper),this.boxHelper=null),E){const o=this.camera.position.clone(),l=this.control?this.control.target.clone():new r.Vector3(0,0,0),d=c,D=Date.now(),L=()=>{const I=Date.now()-D,V=Math.min(I/b,1),v=1-Math.pow(1-V,3);if(this.camera.position.lerpVectors(o,u,v),this.control){const w=new r.Vector3;w.lerpVectors(l,d,v),this.control.target.copy(w),this.camera.lookAt(w)}else this.camera.lookAt(c);this.camera.updateProjectionMatrix(),V<1?requestAnimationFrame(L):(this.camera.position.copy(u),this.camera.lookAt(c),this.camera.updateProjectionMatrix(),this.control&&(this.control.target.copy(c),this.control.update()),this.onRender())};L()}else this.camera.position.copy(u),this.camera.lookAt(c),this.camera.updateProjectionMatrix(),this.control&&(this.control.target.copy(c),this.control.update()),this.onRender();if(R){if(console.log("🎥 视角切换信息:"),console.log(" - 视角类型:",t),console.log(" - 相机位置:",u),console.log(" - 观察目标:",c),console.log(" - 方向向量:",z),console.log(" - 包围盒尺寸:",n),console.log(" - 包围盒中心:",g.getCenter(new r.Vector3)),console.log(" - 水平 FOV:",r.MathUtils.radToDeg(m*2).toFixed(2)+"°"),console.log(" - 垂直 FOV:",r.MathUtils.radToDeg(a*2).toFixed(2)+"°"),console.log(" - 宽高比:",O.toFixed(4)),console.log(" - 模型宽度:",n.x.toFixed(2)),console.log(" - 模型高度:",n.y.toFixed(2)),console.log(" - 模型深度:",n.z.toFixed(2)),console.log(" - 传入的 scale 参数:",s),t==="top"){const o=n.x*.5/(Math.tan(m)*s),l=n.z*.5/(Math.tan(a)*s);console.log(" - 模型 X 尺寸 (宽度):",n.x.toFixed(2)),console.log(" - 模型 Z 尺寸 (深度):",n.z.toFixed(2)),console.log(" - tan(halfFovX):",Math.tan(m).toFixed(4)),console.log(" - tan(halfFovY):",Math.tan(a).toFixed(4)),console.log(" - X方向距离计算: (",(n.x*.5).toFixed(2),") / (",Math.tan(m).toFixed(4)," *",s,") =",o.toFixed(2)),console.log(" - Z方向距离计算: (",(n.z*.5).toFixed(2),") / (",Math.tan(a).toFixed(4)," *",s,") =",l.toFixed(2)),console.log(" - X方向距离 (scale="+s+"):",o.toFixed(2)),console.log(" - Z方向距离 (scale="+s+"):",l.toFixed(2))}else if(t==="right"||t==="left"){const o=n.y*.5/(Math.tan(a)*s),l=n.z*.5/(Math.tan(a)*s);console.log(" - Y方向距离 (scale="+s+"):",o.toFixed(2)),console.log(" - Z方向距离 (scale="+s+"):",l.toFixed(2))}else{const o=n.x*.5/(Math.tan(m)*s),l=n.y*.5/(Math.tan(a)*s),d=n.z*.5/(Math.tan(a)*s);console.log(" - 模型 X 尺寸 (宽度):",n.x.toFixed(2)),console.log(" - 模型 Y 尺寸 (高度):",n.y.toFixed(2)),console.log(" - 模型 Z 尺寸 (深度):",n.z.toFixed(2)),console.log(" - tan(halfFovX):",Math.tan(m).toFixed(4)),console.log(" - tan(halfFovY):",Math.tan(a).toFixed(4)),console.log(" - X方向距离计算: (",(n.x*.5).toFixed(2),") / (",Math.tan(m).toFixed(4)," *",s,") =",o.toFixed(2)),console.log(" - Y方向距离计算: (",(n.y*.5).toFixed(2),") / (",Math.tan(a).toFixed(4)," *",s,") =",l.toFixed(2)),console.log(" - Z方向距离计算: (",(n.z*.5).toFixed(2),") / (",Math.tan(a).toFixed(4)," *",s,") =",d.toFixed(2)),console.log(" - X方向距离 (scale="+s+"):",o.toFixed(2)),console.log(" - Y方向距离 (scale="+s+"):",l.toFixed(2)),console.log(" - Z方向距离 (scale="+s+"):",d.toFixed(2)),console.log(" - 最大距离 (Max):",Math.max(o,l,d).toFixed(2))}console.log(" - 最终距离:",f.toFixed(2)),console.log(" - 缩放比例:",s),console.log(" - 动画:",E?"是 ("+b+"ms)":"否")}return{position:u,target:c,distance:f,viewType:t}});i(this,"on",(e,t)=>!e||!t||typeof t!="function"?(console.warn("ThreeIns.on: 无效的参数"),()=>{}):(this.eventsListener[e]||(this.eventsListener[e]=[]),this.eventsListener[e].push(t),()=>this.off(e,t)));this.isReady=!1,this.scene=new r.Scene({}),this.camera=new r.PerspectiveCamera(50,1,.1,2e3),this.renderer=new r.WebGLRenderer({antialias:!0,alpha:!0,precision:"mediump",logarithmicDepthBuffer:!0}),this.version=j.version,this.onContextLost=this.onContextLost.bind(this),this.onContextRestored=this.onContextRestored.bind(this),this.onResize=this.onResize.bind(this),this.animate=this.animate.bind(this),this.resizeTimer=null,this.animationFrameId=null,t&&(this.initOpt=Object.assign(this.initOpt,t)),e&&this.setup(e)}updateCameraFOV(e,t){const h=this.initOpt.initialFov||50,s=Math.tan(Math.PI/180*h/2);this.camera.aspect=e/t,this.camera.fov=360/Math.PI*Math.atan(s*(t/e)),this.camera.updateProjectionMatrix()}initStats(){this.stats=new P,this.stats.dom.style.cssText="position:absolute;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000",this.el.appendChild(this.stats.dom)}initCss3dRenderer(){this.css3dRenderer=new H.CSS3DRenderer;const[e,t]=this.getTargetSize();this.css3dRenderer.setSize(e,t),this.css3dRenderer.domElement.style.position="absolute",this.css3dRenderer.domElement.style.pointerEvents="none",this.css3dRenderer.domElement.style.top=0,this.css3dRenderer.domElement.style.left=0,this.el.appendChild(this.css3dRenderer.domElement)}getTargetSize(){return document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement?[window.innerWidth,window.innerHeight]:this.el?!document.body.contains(this.el)&&(console.warn(`ThreeIns: 缓存的元素已失效,重新查询 ${this.selector}`),this.el=document.querySelector(this.selector),!this.el)?[0,0]:[this.el.clientWidth,this.el.clientHeight]:[0,0]}off(e,t){if(!e){this.eventsListener={};return}this.eventsListener[e]&&(t?this.eventsListener[e]=this.eventsListener[e].filter(h=>h!==t):this.eventsListener[e]=[])}dispose(){this.isDispose||(this.isDispose=!0,this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.resizeTimer&&(clearTimeout(this.resizeTimer),this.resizeTimer=null),this.removeListener(),this.eventsListener={},this.stats&&this.stats.dom&&(this.stats.dom.remove(),this.stats=null),this.css3dRenderer&&(this.css3dRenderer.domElement.remove(),this.css3dRenderer=null),this.boxHelper&&(this.scene.remove(this.boxHelper),this.boxHelper=null),this.scene&&(Y.disposeThreeObject(this.scene),this.scene=null),this.renderer&&(this.renderer.dispose(),this.renderer.domElement&&this.renderer.domElement.remove(),this.renderer=null),this.control&&(this.control.dispose(),this.control=null),this.camera=null,this.el=null,this.selector=null,console.log("ThreeIns: 资源已清理完成"))}}i(y,"ViewType",M);exports.ThreeIns=y;exports.ViewType=M;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import { MathUtils as
|
|
5
|
-
import { CSS3DRenderer as
|
|
6
|
-
import
|
|
7
|
-
import { createOrbitControl as
|
|
8
|
-
import { disposeThreeObject as
|
|
1
|
+
var H = Object.defineProperty;
|
|
2
|
+
var V = (x, e, t) => e in x ? H(x, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : x[e] = t;
|
|
3
|
+
var o = (x, e, t) => V(x, typeof e != "symbol" ? e + "" : e, t);
|
|
4
|
+
import { MathUtils as F, Scene as P, PerspectiveCamera as q, WebGLRenderer as X, Box3 as Y, Vector3 as a, Box3Helper as Z } from "three";
|
|
5
|
+
import { CSS3DRenderer as j } from "../../../../node_modules/.pnpm/three@0.178.0/node_modules/three/examples/jsm/renderers/CSS3DRenderer.js";
|
|
6
|
+
import B from "../../../../node_modules/.pnpm/three@0.178.0/node_modules/three/examples/jsm/libs/stats.module.js";
|
|
7
|
+
import { createOrbitControl as _ } from "../utils/helper.js";
|
|
8
|
+
import { disposeThreeObject as N } from "../utils/disposeObject.js";
|
|
9
9
|
import "../utils/PredictiveLoader.js";
|
|
10
|
-
import { version as
|
|
11
|
-
const
|
|
10
|
+
import { version as W } from "../../package.json.js";
|
|
11
|
+
const k = 50, G = 20, U = 20, y = {
|
|
12
12
|
TOP: "top",
|
|
13
13
|
// 俯视(从上往下)
|
|
14
14
|
RIGHT: "right",
|
|
@@ -17,15 +17,15 @@ const W = 50, k = 20, G = 20, C = {
|
|
|
17
17
|
// 左侧视
|
|
18
18
|
ISO: "iso"
|
|
19
19
|
// 正斜视(等轴测视角)
|
|
20
|
-
},
|
|
20
|
+
}, $ = {
|
|
21
21
|
enableDamping: !0,
|
|
22
22
|
dampingFactor: 0.25,
|
|
23
23
|
screenSpacePanning: !1,
|
|
24
24
|
minDistance: 0.1,
|
|
25
25
|
maxDistance: 1e3,
|
|
26
|
-
maxPolarAngle:
|
|
26
|
+
maxPolarAngle: F.degToRad(60)
|
|
27
27
|
};
|
|
28
|
-
class
|
|
28
|
+
class J {
|
|
29
29
|
/**
|
|
30
30
|
* 构造函数
|
|
31
31
|
* @param {string} selector - DOM 容器选择器
|
|
@@ -40,21 +40,21 @@ class $ {
|
|
|
40
40
|
* @param {Object} [option.control.options={}] - 控制器选项
|
|
41
41
|
*/
|
|
42
42
|
constructor(e, t) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
o(this, "isReady", !1);
|
|
44
|
+
o(this, "scene", null);
|
|
45
|
+
o(this, "camera", null);
|
|
46
|
+
o(this, "renderer", null);
|
|
47
|
+
o(this, "control", null);
|
|
48
|
+
o(this, "css3dRenderer", null);
|
|
49
|
+
o(this, "el", null);
|
|
50
|
+
o(this, "renderRequested", !1);
|
|
51
|
+
o(this, "selector", null);
|
|
52
|
+
o(this, "eventsListener", {});
|
|
53
|
+
o(this, "stats", null);
|
|
54
|
+
o(this, "isDispose", !1);
|
|
55
|
+
o(this, "version", "0.0.0");
|
|
56
|
+
o(this, "boxHelper", null);
|
|
57
|
+
o(this, "initOpt", {
|
|
58
58
|
css3d: !1,
|
|
59
59
|
stats: !1,
|
|
60
60
|
renderType: "change",
|
|
@@ -71,25 +71,25 @@ class $ {
|
|
|
71
71
|
* 初始化场景
|
|
72
72
|
* @param {string} selector - DOM 容器选择器
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
o(this, "setup", (e) => {
|
|
75
75
|
if (this.isDispose = !1, this.selector = e, this.el = document.querySelector(e), !this.el) {
|
|
76
76
|
console.error(`ThreeIns: 找不到元素 ${e}`);
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
const [t,
|
|
80
|
-
if (this.updateCameraFOV(t,
|
|
81
|
-
this.control =
|
|
79
|
+
const [t, c] = this.getTargetSize();
|
|
80
|
+
if (this.updateCameraFOV(t, c), this.camera.position.set(0, 0, 0), this.camera.lookAt(0, 0, 0), this.camera.updateProjectionMatrix(), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(t, c), this.el.appendChild(this.renderer.domElement), this.initOpt.control && this.initOpt.control.init) {
|
|
81
|
+
this.control = _(this.camera, this.renderer.domElement);
|
|
82
82
|
const s = Object.assign(
|
|
83
|
-
|
|
83
|
+
$,
|
|
84
84
|
this.initOpt.control.options || {}
|
|
85
85
|
);
|
|
86
|
-
Object.keys(s).forEach((
|
|
87
|
-
this.control[
|
|
86
|
+
Object.keys(s).forEach((p) => {
|
|
87
|
+
this.control[p] = s[p];
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
setTimeout(() => {
|
|
91
91
|
this.isReady = !0;
|
|
92
|
-
},
|
|
92
|
+
}, G), this.initOpt.stats && this.initStats(), this.initOpt.css3d && this.initCss3dRenderer(), this.initOpt.renderType === "loop" ? this.animate() : this.initOpt.renderType === "change" && this.control && this.control.addEventListener(
|
|
93
93
|
"change",
|
|
94
94
|
this.requestRenderIfNotRequested
|
|
95
95
|
), this.initListener();
|
|
@@ -98,22 +98,22 @@ class $ {
|
|
|
98
98
|
* WebGL 上下文丢失处理
|
|
99
99
|
* @param {Event} e - 事件对象
|
|
100
100
|
*/
|
|
101
|
-
|
|
101
|
+
o(this, "onContextLost", (e) => {
|
|
102
102
|
e.preventDefault(), this.animationFrameId && cancelAnimationFrame(this.animationFrameId);
|
|
103
103
|
});
|
|
104
104
|
/**
|
|
105
105
|
* WebGL 上下文恢复处理
|
|
106
106
|
* @param {Event} e - 事件对象
|
|
107
107
|
*/
|
|
108
|
-
|
|
108
|
+
o(this, "onContextRestored", (e) => {
|
|
109
109
|
e.preventDefault(), this.dispose(), setTimeout(() => {
|
|
110
110
|
this.setup(this.selector);
|
|
111
|
-
},
|
|
111
|
+
}, U);
|
|
112
112
|
});
|
|
113
113
|
/**
|
|
114
114
|
* 初始化事件监听器
|
|
115
115
|
*/
|
|
116
|
-
|
|
116
|
+
o(this, "initListener", () => {
|
|
117
117
|
this.initOpt.initListener && window && window.addEventListener("resize", this.onResize, !1), this.renderer.domElement.addEventListener(
|
|
118
118
|
"webglcontextlost",
|
|
119
119
|
this.onContextLost,
|
|
@@ -127,7 +127,7 @@ class $ {
|
|
|
127
127
|
/**
|
|
128
128
|
* 移除事件监听器
|
|
129
129
|
*/
|
|
130
|
-
|
|
130
|
+
o(this, "removeListener", () => {
|
|
131
131
|
window && window.removeEventListener("resize", this.onResize, !1), this.renderer && this.renderer.domElement && (this.renderer.domElement.removeEventListener(
|
|
132
132
|
"webglcontextlost",
|
|
133
133
|
this.onContextLost,
|
|
@@ -141,19 +141,19 @@ class $ {
|
|
|
141
141
|
/**
|
|
142
142
|
* 渲染循环(loop 模式)
|
|
143
143
|
*/
|
|
144
|
-
|
|
144
|
+
o(this, "animate", () => {
|
|
145
145
|
this.isDispose || (this.initOpt.renderType === "loop" && this.onRender(), this.animationFrameId = requestAnimationFrame(this.animate));
|
|
146
146
|
});
|
|
147
147
|
/**
|
|
148
148
|
* 执行渲染
|
|
149
149
|
*/
|
|
150
|
-
|
|
150
|
+
o(this, "onRender", () => {
|
|
151
151
|
this.isDispose || (this.renderRequested = !1, this.stats && this.stats.update(), this.control && this.control.update(), this.renderer.render(this.scene, this.camera), this.css3dRenderer && this.css3dRenderer.render(this.scene, this.camera), this.eventsListener.onRender && this.eventsListener.onRender.length && this.eventsListener.onRender.forEach((e) => e()));
|
|
152
152
|
});
|
|
153
153
|
/**
|
|
154
154
|
* 请求渲染(如果尚未请求)
|
|
155
155
|
*/
|
|
156
|
-
|
|
156
|
+
o(this, "requestRenderIfNotRequested", () => {
|
|
157
157
|
this.renderRequested || (this.renderRequested = !0, requestAnimationFrame(() => {
|
|
158
158
|
this.onRender();
|
|
159
159
|
}));
|
|
@@ -161,11 +161,11 @@ class $ {
|
|
|
161
161
|
/**
|
|
162
162
|
* 处理窗口大小变化(带防抖)
|
|
163
163
|
*/
|
|
164
|
-
|
|
164
|
+
o(this, "onResize", () => {
|
|
165
165
|
this.resizeTimer && clearTimeout(this.resizeTimer), this.resizeTimer = setTimeout(() => {
|
|
166
166
|
const [e, t] = this.getTargetSize();
|
|
167
167
|
this.updateCameraFOV(e, t), this.camera.lookAt(this.scene.position), this.renderer.setSize(e, t), this.css3dRenderer && this.css3dRenderer.setSize(e, t), this.onRender();
|
|
168
|
-
},
|
|
168
|
+
}, k);
|
|
169
169
|
});
|
|
170
170
|
/**
|
|
171
171
|
* @deprecated 此方法已弃用,请使用 setView() 方法代替
|
|
@@ -182,11 +182,11 @@ class $ {
|
|
|
182
182
|
* @param {number} [scale=0.8] - 缩放比例,1=占满画布,0.5=50%,2.0=200%
|
|
183
183
|
* @returns {Object} 返回 setView 的结果
|
|
184
184
|
*/
|
|
185
|
-
|
|
185
|
+
o(this, "frameArea", (e, t) => (console.warn(
|
|
186
186
|
`[ThreeIns] frameArea() 已弃用,建议使用 setView() 方法。
|
|
187
187
|
旧用法: threeIns.frameArea(model, scale)
|
|
188
188
|
新用法: threeIns.setView(model, ViewType.ISO, { scale })`
|
|
189
|
-
), this.setView(e,
|
|
189
|
+
), this.setView(e, y.ISO, { scale: t, animate: !1, showBox: !1 })));
|
|
190
190
|
/**
|
|
191
191
|
* 切换到指定视角
|
|
192
192
|
* 根据视角类型自动调整相机位置和朝向,支持缩放比例控制
|
|
@@ -199,7 +199,10 @@ class $ {
|
|
|
199
199
|
* - 'iso': 正斜视(等轴测视角,45°角)
|
|
200
200
|
* @param {Object} [options] - 可选配置
|
|
201
201
|
* @param {number} [options.scale=1] - 缩放比例,控制模型在画布中的占比
|
|
202
|
-
* @param {Vector3} [options.
|
|
202
|
+
* @param {string|Vector3} [options.position='center'] - 模型在画布中的位置
|
|
203
|
+
* - 字符串选项:'center'(中心), 'top-left'(左上), 'top-right'(右上), 'bottom-left'(左下), 'bottom-right'(右下)
|
|
204
|
+
* - Vector3:自定义偏移向量
|
|
205
|
+
* @param {Vector3} [options.offset] - 额外的手动偏移量,在 position 基础上再次调整
|
|
203
206
|
* @param {boolean} [options.showBox=false] - 是否显示包围盒辅助线
|
|
204
207
|
* @param {number} [options.boxColor=0xffff00] - 包围盒颜色
|
|
205
208
|
* @param {boolean} [options.animate=true] - 是否使用动画过渡
|
|
@@ -213,84 +216,105 @@ class $ {
|
|
|
213
216
|
* // 导入 ViewType 枚举
|
|
214
217
|
* import { ThreeIns, ViewType } from 'threejs';
|
|
215
218
|
*
|
|
216
|
-
* //
|
|
219
|
+
* // 切换到俯视,模型占满画布,居中显示
|
|
217
220
|
* threeIns.setView(model, ViewType.TOP);
|
|
218
221
|
*
|
|
219
|
-
* // 切换到等轴测,模型占画布的 80
|
|
220
|
-
* threeIns.setView(model, ViewType.ISO, {
|
|
222
|
+
* // 切换到等轴测,模型占画布的 80%,左上角显示
|
|
223
|
+
* threeIns.setView(model, ViewType.ISO, {
|
|
224
|
+
* scale: 0.8,
|
|
225
|
+
* position: 'top-left'
|
|
226
|
+
* });
|
|
221
227
|
*
|
|
222
|
-
* //
|
|
228
|
+
* // 切换到斜视,显示包围盒,右上角显示
|
|
223
229
|
* threeIns.setView(model, ViewType.ISO, {
|
|
230
|
+
* position: 'top-right',
|
|
224
231
|
* showBox: true,
|
|
225
232
|
* animate: false
|
|
226
233
|
* });
|
|
227
234
|
*
|
|
228
235
|
* // 切换到侧视,自定义观察点
|
|
229
236
|
* threeIns.setView(model, ViewType.RIGHT, {
|
|
230
|
-
*
|
|
237
|
+
* position: new THREE.Vector3(100, 0, 0),
|
|
238
|
+
* scale: 0.8
|
|
239
|
+
* });
|
|
240
|
+
*
|
|
241
|
+
* // 组合使用 position 和 offset
|
|
242
|
+
* threeIns.setView(model, ViewType.TOP, {
|
|
243
|
+
* position: 'top-left', // 先自动定位到左上
|
|
244
|
+
* offset: new THREE.Vector3(50, 0, 0), // 再额外向右偏移 50
|
|
231
245
|
* scale: 0.8
|
|
232
246
|
* });
|
|
233
247
|
*
|
|
234
248
|
* // 或者使用类静态属性(向后兼容)
|
|
235
249
|
* threeIns.setView(model, ThreeIns.ViewType.TOP);
|
|
236
250
|
*/
|
|
237
|
-
|
|
238
|
-
let s =
|
|
239
|
-
const
|
|
240
|
-
let h =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
o(this, "setView", (e, t, c = {}) => {
|
|
252
|
+
let s = c.scale || 0.8, p = c.offset || null, R = c.position || "center", z = c.showBox || !1, S = c.boxColor || 16776960, M = c.animate !== void 0 ? c.animate : !0, b = c.duration || 1e3;
|
|
253
|
+
const g = new Y().setFromObject(e);
|
|
254
|
+
let h = g.getCenter(new a());
|
|
255
|
+
if (typeof R == "string") {
|
|
256
|
+
const i = g.getSize(new a()), r = {
|
|
257
|
+
center: new a(0, 0, 0),
|
|
258
|
+
"top-left": new a(-i.x * 0.3, i.y * 0.3, i.z * 0.3),
|
|
259
|
+
"top-right": new a(i.x * 0.3, i.y * 0.3, i.z * 0.3),
|
|
260
|
+
"bottom-left": new a(-i.x * 0.3, -i.y * 0.3, i.z * 0.3),
|
|
261
|
+
"bottom-right": new a(i.x * 0.3, -i.y * 0.3, i.z * 0.3)
|
|
262
|
+
}, d = r[R] || r.center;
|
|
263
|
+
t === "top" ? h.add(new a(d.x, 0, d.z)) : t === "right" || t === "left" ? h.add(new a(0, d.y, d.z)) : h.add(d);
|
|
264
|
+
} else R instanceof a && h.add(R);
|
|
265
|
+
p && h.add(p);
|
|
266
|
+
const L = {
|
|
267
|
+
top: new a(0, 1, 0),
|
|
244
268
|
// 从上往下:Y 轴正方向
|
|
245
|
-
right: new
|
|
269
|
+
right: new a(2, 1, 1).normalize(),
|
|
246
270
|
// 从右往左:X 轴正方向
|
|
247
|
-
left: new
|
|
271
|
+
left: new a(-2, 1, 1).normalize(),
|
|
248
272
|
// 从左往右:X 轴负方向
|
|
249
|
-
iso: new
|
|
273
|
+
iso: new a(0, 1, 1).normalize()
|
|
250
274
|
// 等轴测:从对角线上方俯视
|
|
251
|
-
},
|
|
252
|
-
let
|
|
275
|
+
}, w = L[t] || L.iso, n = g.getSize(new a()), O = this.camera.aspect, l = F.degToRad(this.camera.fov * 0.5), D = F.degToRad(80), m = Math.min(Math.atan(Math.tan(l) * O), D);
|
|
276
|
+
let f;
|
|
253
277
|
if (t === "top") {
|
|
254
|
-
const
|
|
255
|
-
|
|
278
|
+
const i = n.x * 0.5 / (Math.tan(m) * s), r = n.z * 0.5 / (Math.tan(l) * s);
|
|
279
|
+
f = Math.max(i, r);
|
|
256
280
|
} else if (t === "right" || t === "left") {
|
|
257
|
-
const
|
|
258
|
-
|
|
281
|
+
const i = n.y * 0.5 / (Math.tan(l) * s), r = n.z * 0.5 / (Math.tan(l) * s);
|
|
282
|
+
f = Math.max(i, r);
|
|
259
283
|
} else {
|
|
260
|
-
const
|
|
261
|
-
|
|
284
|
+
const i = n.x * 0.5 / (Math.tan(m) * s), r = n.y * 0.5 / (Math.tan(l) * s), d = n.z * 0.5 / (Math.tan(l) * s);
|
|
285
|
+
f = Math.max(i, r, d);
|
|
262
286
|
}
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
if (this.camera.position.lerpVectors(
|
|
268
|
-
const
|
|
269
|
-
|
|
287
|
+
const u = w.clone().multiplyScalar(f).add(h);
|
|
288
|
+
if (z && (console.log("📍 相机位置验证:"), console.log(" - 方向向量:", w), console.log(" - 距离:", f.toFixed(2)), console.log(" - 包围盒中心:", h), console.log(" - 计算公式: direction * distance + boxCenter"), console.log(" - 目标位置:", u), console.log(" - 实际相机与中心的距离:", u.clone().sub(h).length().toFixed(2))), z ? (this.boxHelper && (this.scene.remove(this.boxHelper), this.boxHelper = null), this.boxHelper = new Z(g, S), this.scene.add(this.boxHelper)) : this.boxHelper && (this.scene.remove(this.boxHelper), this.boxHelper = null), M) {
|
|
289
|
+
const i = this.camera.position.clone(), r = this.control ? this.control.target.clone() : new a(0, 0, 0), d = h, I = Date.now(), T = () => {
|
|
290
|
+
const A = Date.now() - I, v = Math.min(A / b, 1), C = 1 - Math.pow(1 - v, 3);
|
|
291
|
+
if (this.camera.position.lerpVectors(i, u, C), this.control) {
|
|
292
|
+
const E = new a();
|
|
293
|
+
E.lerpVectors(r, d, C), this.control.target.copy(E), this.camera.lookAt(E);
|
|
270
294
|
} else
|
|
271
295
|
this.camera.lookAt(h);
|
|
272
|
-
this.camera.updateProjectionMatrix(),
|
|
296
|
+
this.camera.updateProjectionMatrix(), v < 1 ? requestAnimationFrame(T) : (this.camera.position.copy(u), this.camera.lookAt(h), this.camera.updateProjectionMatrix(), this.control && (this.control.target.copy(h), this.control.update()), this.onRender());
|
|
273
297
|
};
|
|
274
|
-
|
|
298
|
+
T();
|
|
275
299
|
} else
|
|
276
|
-
this.camera.position.copy(
|
|
277
|
-
if (
|
|
278
|
-
if (console.log("🎥 视角切换信息:"), console.log(" - 视角类型:", t), console.log(" - 相机位置:",
|
|
279
|
-
const
|
|
280
|
-
console.log(" - 模型 X 尺寸 (宽度):",
|
|
300
|
+
this.camera.position.copy(u), this.camera.lookAt(h), this.camera.updateProjectionMatrix(), this.control && (this.control.target.copy(h), this.control.update()), this.onRender();
|
|
301
|
+
if (z) {
|
|
302
|
+
if (console.log("🎥 视角切换信息:"), console.log(" - 视角类型:", t), console.log(" - 相机位置:", u), console.log(" - 观察目标:", h), console.log(" - 方向向量:", w), console.log(" - 包围盒尺寸:", n), console.log(" - 包围盒中心:", g.getCenter(new a())), console.log(" - 水平 FOV:", F.radToDeg(m * 2).toFixed(2) + "°"), console.log(" - 垂直 FOV:", F.radToDeg(l * 2).toFixed(2) + "°"), console.log(" - 宽高比:", O.toFixed(4)), console.log(" - 模型宽度:", n.x.toFixed(2)), console.log(" - 模型高度:", n.y.toFixed(2)), console.log(" - 模型深度:", n.z.toFixed(2)), console.log(" - 传入的 scale 参数:", s), t === "top") {
|
|
303
|
+
const i = n.x * 0.5 / (Math.tan(m) * s), r = n.z * 0.5 / (Math.tan(l) * s);
|
|
304
|
+
console.log(" - 模型 X 尺寸 (宽度):", n.x.toFixed(2)), console.log(" - 模型 Z 尺寸 (深度):", n.z.toFixed(2)), console.log(" - tan(halfFovX):", Math.tan(m).toFixed(4)), console.log(" - tan(halfFovY):", Math.tan(l).toFixed(4)), console.log(" - X方向距离计算: (", (n.x * 0.5).toFixed(2), ") / (", Math.tan(m).toFixed(4), " *", s, ") =", i.toFixed(2)), console.log(" - Z方向距离计算: (", (n.z * 0.5).toFixed(2), ") / (", Math.tan(l).toFixed(4), " *", s, ") =", r.toFixed(2)), console.log(" - X方向距离 (scale=" + s + "):", i.toFixed(2)), console.log(" - Z方向距离 (scale=" + s + "):", r.toFixed(2));
|
|
281
305
|
} else if (t === "right" || t === "left") {
|
|
282
|
-
const
|
|
283
|
-
console.log(" - Y方向距离 (scale=" + s + "):",
|
|
306
|
+
const i = n.y * 0.5 / (Math.tan(l) * s), r = n.z * 0.5 / (Math.tan(l) * s);
|
|
307
|
+
console.log(" - Y方向距离 (scale=" + s + "):", i.toFixed(2)), console.log(" - Z方向距离 (scale=" + s + "):", r.toFixed(2));
|
|
284
308
|
} else {
|
|
285
|
-
const
|
|
286
|
-
console.log(" - 模型 X 尺寸 (宽度):",
|
|
309
|
+
const i = n.x * 0.5 / (Math.tan(m) * s), r = n.y * 0.5 / (Math.tan(l) * s), d = n.z * 0.5 / (Math.tan(l) * s);
|
|
310
|
+
console.log(" - 模型 X 尺寸 (宽度):", n.x.toFixed(2)), console.log(" - 模型 Y 尺寸 (高度):", n.y.toFixed(2)), console.log(" - 模型 Z 尺寸 (深度):", n.z.toFixed(2)), console.log(" - tan(halfFovX):", Math.tan(m).toFixed(4)), console.log(" - tan(halfFovY):", Math.tan(l).toFixed(4)), console.log(" - X方向距离计算: (", (n.x * 0.5).toFixed(2), ") / (", Math.tan(m).toFixed(4), " *", s, ") =", i.toFixed(2)), console.log(" - Y方向距离计算: (", (n.y * 0.5).toFixed(2), ") / (", Math.tan(l).toFixed(4), " *", s, ") =", r.toFixed(2)), console.log(" - Z方向距离计算: (", (n.z * 0.5).toFixed(2), ") / (", Math.tan(l).toFixed(4), " *", s, ") =", d.toFixed(2)), console.log(" - X方向距离 (scale=" + s + "):", i.toFixed(2)), console.log(" - Y方向距离 (scale=" + s + "):", r.toFixed(2)), console.log(" - Z方向距离 (scale=" + s + "):", d.toFixed(2)), console.log(" - 最大距离 (Max):", Math.max(i, r, d).toFixed(2));
|
|
287
311
|
}
|
|
288
|
-
console.log(" - 最终距离:",
|
|
312
|
+
console.log(" - 最终距离:", f.toFixed(2)), console.log(" - 缩放比例:", s), console.log(" - 动画:", M ? "是 (" + b + "ms)" : "否");
|
|
289
313
|
}
|
|
290
314
|
return {
|
|
291
|
-
position:
|
|
315
|
+
position: u,
|
|
292
316
|
target: h,
|
|
293
|
-
distance:
|
|
317
|
+
distance: f,
|
|
294
318
|
viewType: t
|
|
295
319
|
};
|
|
296
320
|
});
|
|
@@ -300,14 +324,14 @@ class $ {
|
|
|
300
324
|
* @param {Function} callback - 回调函数
|
|
301
325
|
* @returns {Function} 返回取消监听的函数
|
|
302
326
|
*/
|
|
303
|
-
|
|
327
|
+
o(this, "on", (e, t) => !e || !t || typeof t != "function" ? (console.warn("ThreeIns.on: 无效的参数"), () => {
|
|
304
328
|
}) : (this.eventsListener[e] || (this.eventsListener[e] = []), this.eventsListener[e].push(t), () => this.off(e, t)));
|
|
305
|
-
this.isReady = !1, this.scene = new
|
|
329
|
+
this.isReady = !1, this.scene = new P({}), this.camera = new q(50, 1, 0.1, 2e3), this.renderer = new X({
|
|
306
330
|
antialias: !0,
|
|
307
331
|
alpha: !0,
|
|
308
332
|
precision: "mediump",
|
|
309
333
|
logarithmicDepthBuffer: !0
|
|
310
|
-
}), this.version =
|
|
334
|
+
}), this.version = W, this.onContextLost = this.onContextLost.bind(this), this.onContextRestored = this.onContextRestored.bind(this), this.onResize = this.onResize.bind(this), this.animate = this.animate.bind(this), this.resizeTimer = null, this.animationFrameId = null, t && (this.initOpt = Object.assign(this.initOpt, t)), e && this.setup(e);
|
|
311
335
|
}
|
|
312
336
|
/**
|
|
313
337
|
* 更新相机 FOV 以适应容器尺寸
|
|
@@ -316,20 +340,20 @@ class $ {
|
|
|
316
340
|
* @param {number} domH - 容器高度
|
|
317
341
|
*/
|
|
318
342
|
updateCameraFOV(e, t) {
|
|
319
|
-
const
|
|
343
|
+
const c = this.initOpt.initialFov || 50, s = Math.tan(Math.PI / 180 * c / 2);
|
|
320
344
|
this.camera.aspect = e / t, this.camera.fov = 360 / Math.PI * Math.atan(s * (t / e)), this.camera.updateProjectionMatrix();
|
|
321
345
|
}
|
|
322
346
|
/**
|
|
323
347
|
* 初始化性能统计
|
|
324
348
|
*/
|
|
325
349
|
initStats() {
|
|
326
|
-
this.stats = new
|
|
350
|
+
this.stats = new B(), this.stats.dom.style.cssText = "position:absolute;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000", this.el.appendChild(this.stats.dom);
|
|
327
351
|
}
|
|
328
352
|
/**
|
|
329
353
|
* 初始化 CSS3D 渲染器
|
|
330
354
|
*/
|
|
331
355
|
initCss3dRenderer() {
|
|
332
|
-
this.css3dRenderer = new
|
|
356
|
+
this.css3dRenderer = new j();
|
|
333
357
|
const [e, t] = this.getTargetSize();
|
|
334
358
|
this.css3dRenderer.setSize(e, t), this.css3dRenderer.domElement.style.position = "absolute", this.css3dRenderer.domElement.style.pointerEvents = "none", this.css3dRenderer.domElement.style.top = 0, this.css3dRenderer.domElement.style.left = 0, this.el.appendChild(this.css3dRenderer.domElement);
|
|
335
359
|
}
|
|
@@ -351,19 +375,19 @@ class $ {
|
|
|
351
375
|
this.eventsListener = {};
|
|
352
376
|
return;
|
|
353
377
|
}
|
|
354
|
-
this.eventsListener[e] && (t ? this.eventsListener[e] = this.eventsListener[e].filter((
|
|
378
|
+
this.eventsListener[e] && (t ? this.eventsListener[e] = this.eventsListener[e].filter((c) => c !== t) : this.eventsListener[e] = []);
|
|
355
379
|
}
|
|
356
380
|
/**
|
|
357
381
|
* 清理所有资源
|
|
358
382
|
*/
|
|
359
383
|
dispose() {
|
|
360
|
-
this.isDispose || (this.isDispose = !0, this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null), this.resizeTimer && (clearTimeout(this.resizeTimer), this.resizeTimer = null), this.removeListener(), this.eventsListener = {}, this.stats && this.stats.dom && (this.stats.dom.remove(), this.stats = null), this.css3dRenderer && (this.css3dRenderer.domElement.remove(), this.css3dRenderer = null), this.boxHelper && (this.scene.remove(this.boxHelper), this.boxHelper = null), this.scene && (
|
|
384
|
+
this.isDispose || (this.isDispose = !0, this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null), this.resizeTimer && (clearTimeout(this.resizeTimer), this.resizeTimer = null), this.removeListener(), this.eventsListener = {}, this.stats && this.stats.dom && (this.stats.dom.remove(), this.stats = null), this.css3dRenderer && (this.css3dRenderer.domElement.remove(), this.css3dRenderer = null), this.boxHelper && (this.scene.remove(this.boxHelper), this.boxHelper = null), this.scene && (N(this.scene), this.scene = null), this.renderer && (this.renderer.dispose(), this.renderer.domElement && this.renderer.domElement.remove(), this.renderer = null), this.control && (this.control.dispose(), this.control = null), this.camera = null, this.el = null, this.selector = null, console.log("ThreeIns: 资源已清理完成"));
|
|
361
385
|
}
|
|
362
386
|
}
|
|
363
387
|
// 存储包围盒辅助器
|
|
364
388
|
// 视角类型枚举(作为类静态属性引用,保持向后兼容)
|
|
365
|
-
|
|
389
|
+
o(J, "ViewType", y);
|
|
366
390
|
export {
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
J as ThreeIns,
|
|
392
|
+
y as ViewType
|
|
369
393
|
};
|