diff --git a/public/models/BJY_FJ_Shell.bin b/public/models/BJY_FJ_Shell.bin new file mode 100644 index 0000000..bc9f046 Binary files /dev/null and b/public/models/BJY_FJ_Shell.bin differ diff --git a/public/models/BJY_FJ_Shell.gltf b/public/models/BJY_FJ_Shell.gltf new file mode 100644 index 0000000..8b9c821 --- /dev/null +++ b/public/models/BJY_FJ_Shell.gltf @@ -0,0 +1,570 @@ +{ + "asset": { + "version": "2.0", + "generator": "babylon.js glTF exporter for 3dsmax 2023 v20240312.5" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0, + 5 + ] + } + ], + "nodes": [ + { + "children": [ + 1, + 2, + 3 + ], + "mesh": 0, + "translation": [ + 0.00143703236, + 103.750244, + 4.00849152 + ], + "rotation": [ + -8.14603354E-08, + 0.0, + 0.0, + 1.0 + ], + "name": "FJ_HZ_F001" + }, + { + "mesh": 1, + "translation": [ + -0.000161597272, + 1.64903259, + -5.50921869 + ], + "name": "FJ_HZ_B001" + }, + { + "mesh": 2, + "translation": [ + -0.000161597272, + 1.64903259, + -5.50921869 + ], + "name": "FJ_HZ_R001" + }, + { + "children": [ + 4 + ], + "mesh": 3, + "translation": [ + 0.0001473783, + 1.788208, + 1.96820593 + ], + "rotation": [ + -0.0218098629, + 0.03777547, + 0.8652011, + 0.499524176 + ], + "scale": [ + 1.48850107, + 1.48850155, + 1.48850131 + ], + "name": "FJ_LunGu001" + }, + { + "mesh": 4, + "translation": [ + 0.0158500671, + -0.009151459, + -0.1445036 + ], + "rotation": [ + -9.313228E-10, + 0.0, + 2.980233E-08, + 1.0 + ], + "scale": [ + 0.9999998, + 0.9999997, + 0.999999762 + ], + "name": "FJ_ShanYe001" + }, + { + "mesh": 5, + "translation": [ + 0.00143965613, + 53.50479, + 4.00849056 + ], + "rotation": [ + -8.14603354E-08, + 0.0, + 0.0, + 1.0 + ], + "name": "FJ_TaTong001" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 1, + "NORMAL": 2, + "TEXCOORD_0": 3 + }, + "indices": 0, + "material": 0 + } + ], + "name": "FJ_HZ_F001" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 5, + "NORMAL": 6, + "TEXCOORD_0": 7 + }, + "indices": 4, + "material": 0 + } + ], + "name": "FJ_HZ_B001" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 9, + "NORMAL": 10, + "TEXCOORD_0": 11 + }, + "indices": 8, + "material": 0 + } + ], + "name": "FJ_HZ_R001" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 13, + "NORMAL": 14, + "TEXCOORD_0": 15 + }, + "indices": 12, + "material": 1 + } + ], + "name": "FJ_LunGu001" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 17, + "NORMAL": 18, + "COLOR_0": 19, + "TEXCOORD_0": 20 + }, + "indices": 16, + "material": 2 + } + ], + "name": "FJ_ShanYe001" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 22, + "NORMAL": 23, + "TEXCOORD_0": 24 + }, + "indices": 21, + "material": 2 + } + ], + "name": "FJ_TaTong001" + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5123, + "count": 5262, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 1570, + "max": [ + 1.91735768, + 3.84009123, + 2.02482748 + ], + "min": [ + -1.91583681, + 0.144887447, + -3.62821436 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 1, + "byteOffset": 18840, + "componentType": 5126, + "count": 1570, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 1570, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 10524, + "componentType": 5123, + "count": 4368, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 37680, + "componentType": 5126, + "count": 1127, + "max": [ + 1.91751945, + 1.93533087, + 1.88101923 + ], + "min": [ + -2.16255522, + -1.49184012, + -4.2311306 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 1, + "byteOffset": 51204, + "componentType": 5126, + "count": 1127, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 2, + "byteOffset": 12560, + "componentType": 5126, + "count": 1127, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 19260, + "componentType": 5123, + "count": 756, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 64728, + "componentType": 5126, + "count": 213, + "max": [ + 2.657733, + 1.31850243, + 1.19701219 + ], + "min": [ + 1.91751933, + -1.02121758, + -2.679065 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 1, + "byteOffset": 67284, + "componentType": 5126, + "count": 213, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 2, + "byteOffset": 21576, + "componentType": 5126, + "count": 213, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 20772, + "componentType": 5123, + "count": 12276, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 69840, + "componentType": 5126, + "count": 3032, + "max": [ + 1.61511183, + 1.590561, + 2.664262 + ], + "min": [ + -1.61181664, + -1.20966589, + -0.0402874537 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 1, + "byteOffset": 106224, + "componentType": 5126, + "count": 3032, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 2, + "byteOffset": 23280, + "componentType": 5126, + "count": 3032, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 45324, + "componentType": 5123, + "count": 12366, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 142608, + "componentType": 5126, + "count": 2337, + "max": [ + 49.12784, + 28.6720619, + 4.548005 + ], + "min": [ + -49.41851, + -56.8182373, + 0.3628065 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 1, + "byteOffset": 170652, + "componentType": 5126, + "count": 2337, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 2337, + "type": "VEC4", + "name": "accessorColors" + }, + { + "bufferView": 2, + "byteOffset": 47536, + "componentType": 5126, + "count": 2337, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 70056, + "componentType": 5123, + "count": 5166, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 198696, + "componentType": 5126, + "count": 1389, + "max": [ + 2.067228, + 50.37658, + 2.06722975 + ], + "min": [ + -2.06722379, + -53.5088959, + -2.067222 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 1, + "byteOffset": 215364, + "componentType": 5126, + "count": 1389, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 2, + "byteOffset": 66232, + "componentType": 5126, + "count": 1389, + "type": "VEC2", + "name": "accessorUVs" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 80388, + "name": "bufferViewScalar" + }, + { + "buffer": 0, + "byteOffset": 80388, + "byteLength": 232032, + "byteStride": 12, + "name": "bufferViewFloatVec3" + }, + { + "buffer": 0, + "byteOffset": 312420, + "byteLength": 77344, + "byteStride": 8, + "name": "bufferViewFloatVec2" + }, + { + "buffer": 0, + "byteOffset": 389764, + "byteLength": 37392, + "byteStride": 16, + "name": "bufferViewFloatVec4" + } + ], + "buffers": [ + { + "uri": "BJY_FJ_Shell.bin", + "byteLength": 427156 + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0 + }, + "metallicFactor": 0.0, + "roughnessFactor": 0.399999976 + }, + "doubleSided": true, + "name": "FJ_HuZhao", + "extras": { + "babylonSeparateCullingPass": false + } + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1 + }, + "metallicFactor": 0.0, + "roughnessFactor": 0.399999976 + }, + "doubleSided": true, + "name": "FJ_LunGU", + "extras": { + "babylonSeparateCullingPass": false + } + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 2 + }, + "metallicFactor": 0.0, + "roughnessFactor": 0.399999976 + }, + "doubleSided": true, + "name": "FJ_TaTong", + "extras": { + "babylonSeparateCullingPass": false + } + } + ], + "textures": [ + { + "sampler": 0, + "source": 0, + "name": "FJ_HuZhao.jpg" + }, + { + "sampler": 0, + "source": 1, + "name": "FJ_LunGu.jpg" + }, + { + "sampler": 0, + "source": 2, + "name": "FJ_TaTong.jpg" + } + ], + "images": [ + { + "uri": "FJ_HuZhao.jpg" + }, + { + "uri": "FJ_LunGu.jpg" + }, + { + "uri": "FJ_TaTong.jpg" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987 + } + ] +} \ No newline at end of file diff --git a/public/models/FJ_HuZhao.jpg b/public/models/FJ_HuZhao.jpg new file mode 100644 index 0000000..bdfead2 Binary files /dev/null and b/public/models/FJ_HuZhao.jpg differ diff --git a/public/models/FJ_LunGu.jpg b/public/models/FJ_LunGu.jpg new file mode 100644 index 0000000..6176104 Binary files /dev/null and b/public/models/FJ_LunGu.jpg differ diff --git a/public/models/FJ_TaTong.jpg b/public/models/FJ_TaTong.jpg new file mode 100644 index 0000000..5ae9e4e Binary files /dev/null and b/public/models/FJ_TaTong.jpg differ diff --git a/public/models/README.md b/public/models/README.md index 356e3ee..08d7acd 100644 --- a/public/models/README.md +++ b/public/models/README.md @@ -1,18 +1,10 @@ -将你选中的海上风机资产导出为 GLB 文件,并放到下面这个路径: +将你选中的海上风机资产导出为 glTF 资产,并放到下面这个路径: -`public/models/offshore-wind-turbine.glb` +`public/models/BJY_FJ_Shell.gltf` 当前代码会优先加载这个文件: -- 存在该文件:使用真实 GLB 风机资产 +- 存在该文件:使用真实 glTF 风机资产 - 文件不存在或加载失败:自动回退到程序化风机占位模型 建议优先选择包含完整塔架、机舱、叶片的海上风机模型。 - -当前选定来源: - -- Sketchfab: Generic Wind Turbine (V136 125.5h 145d) -- 链接: https://sketchfab.com/3d-models/generic-wind-turbine-v136-1255h-145d-90ad27be20c541d1a0e4818d4e501679 -- 发布时间: 2020-06-11 -- 许可: CC Attribution -- 规模: 约 2.1k triangles diff --git a/src/objects/OffshoreWindTurbineAsset.js b/src/objects/OffshoreWindTurbineAsset.js index 641ac28..5e6e09c 100644 --- a/src/objects/OffshoreWindTurbineAsset.js +++ b/src/objects/OffshoreWindTurbineAsset.js @@ -2,7 +2,7 @@ import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { WindTurbine } from './WindTurbine.js'; -const DEFAULT_MODEL_URL = '/models/offshore-wind-turbine.glb'; +const DEFAULT_MODEL_URL = '/models/BJY_FJ_Shell.gltf'; export class OffshoreWindTurbineAsset { constructor({ @@ -26,7 +26,10 @@ export class OffshoreWindTurbineAsset { this.rotors = []; this.mixer = null; this.model = null; + this.fallback = null; this.usingFallback = false; + this.nacelleRoot = null; + this.nacelleBaseQuaternion = null; } async load() { @@ -52,6 +55,7 @@ export class OffshoreWindTurbineAsset { const model = gltf.scene; this.fitModelToTargetHeight(model); this.prepareModel(model); + this.captureMovingParts(model); this.alignModelToWaterline(model); this.group.add(model); this.model = model; @@ -71,7 +75,7 @@ export class OffshoreWindTurbineAsset { rotorSpeed: this.rotorSpeed }); this.group.add(fallback.group); - this.rotors = fallback.rotors; + this.fallback = fallback; this.usingFallback = true; } @@ -98,6 +102,27 @@ export class OffshoreWindTurbineAsset { } + captureMovingParts(model) { + this.rotors = []; + this.nacelleRoot = null; + this.nacelleBaseQuaternion = null; + + model.traverse((child) => { + const normalizedName = child.name.toLowerCase(); + + if (!this.nacelleRoot && normalizedName.includes('fj_hz_f')) { + this.nacelleRoot = child; + this.nacelleBaseQuaternion = child.quaternion.clone(); + } + + if (normalizedName.includes('lungu')) { + child.userData.baseQuaternion = child.quaternion.clone(); + child.userData.spinAngle = 0; + this.rotors.push(child); + } + }); + } + fitModelToTargetHeight(model, targetHeight = 340) { const box = new THREE.Box3().setFromObject(model); const size = box.getSize(new THREE.Vector3()); @@ -122,6 +147,16 @@ export class OffshoreWindTurbineAsset { flatDirection.normalize(); const yaw = Math.atan2(flatDirection.x, flatDirection.z); + if (this.nacelleRoot && this.nacelleBaseQuaternion) { + const nacelleYaw = yaw - this.group.rotation.y; + const yawOffset = new THREE.Quaternion().setFromAxisAngle( + new THREE.Vector3(0, 1, 0), + nacelleYaw + ); + this.nacelleRoot.quaternion.copy(this.nacelleBaseQuaternion).premultiply(yawOffset); + return; + } + this.group.rotation.y = yaw; } @@ -129,5 +164,21 @@ export class OffshoreWindTurbineAsset { if (this.mixer) { this.mixer.update(delta); } + + if (this.fallback) { + this.fallback.update(time, delta); + return; + } + + this.rotors.forEach((rotor, index) => { + const gust = 1.0 + Math.sin(time * 0.28 + index * 1.7) * 0.08; + rotor.userData.spinAngle += this.rotorSpeed * gust * delta; + + const spin = new THREE.Quaternion().setFromAxisAngle( + new THREE.Vector3(0, 0, 1), + rotor.userData.spinAngle + ); + rotor.quaternion.copy(rotor.userData.baseQuaternion).multiply(spin); + }); } }