Compare commits

...

2 Commits

Author SHA1 Message Date
32ae719b00 添加两台风机 2026-04-01 16:07:37 +08:00
fdf920c5aa 替换风机模型并使其桨叶转动 2026-04-01 13:43:24 +08:00
8 changed files with 648 additions and 26 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

View File

@@ -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
}
]
}

BIN
public/models/FJ_HuZhao.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
public/models/FJ_LunGu.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
public/models/FJ_TaTong.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -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

View File

@@ -56,7 +56,7 @@ export class OceanScene {
this.fogLayers = [];
this.horizonFog = null;
this.skyHazeBand = null;
this.windTurbine = null;
this.windTurbines = [];
this.rainAudioPool = [];
this.rainAudioActiveIndex = 0;
this.rainAudioIsPlaying = false;
@@ -1509,15 +1509,24 @@ export class OceanScene {
}
async initWindTurbine() {
this.windTurbine = new OffshoreWindTurbineAsset({
position: new THREE.Vector3(280, 0, -2350),
const turbineConfigs = [
{ position: new THREE.Vector3(280, 0, -2350), scale: 0.68, rotorSpeed: 0.24 },
{ position: new THREE.Vector3(-200, 0, -2900), scale: 0.54, rotorSpeed: 0.19 },
{ position: new THREE.Vector3(700, 0, -1850), scale: 0.62, rotorSpeed: 0.28 },
];
await Promise.all(turbineConfigs.map(async (config) => {
const turbine = new OffshoreWindTurbineAsset({
position: config.position,
yaw: 0,
scale: 0.68,
rotorSpeed: 0.24
scale: config.scale,
rotorSpeed: config.rotorSpeed
});
await this.windTurbine.load();
this.windTurbine.addToScene(this.scene);
this.windTurbine.faceDirection(this.sun);
await turbine.load();
turbine.addToScene(this.scene);
turbine.faceDirection(this.sun);
this.windTurbines.push(turbine);
}));
}
async initVegetation() {
@@ -2325,9 +2334,9 @@ export class OceanScene {
this.vegetationSystem.update(time);
}
if (this.windTurbine) {
this.windTurbine.faceDirection(this.sun);
this.windTurbine.update(time, delta);
for (const turbine of this.windTurbines) {
turbine.faceDirection(this.sun);
turbine.update(time, delta);
}
if (this.lightningFlash > 0.001) {

View File

@@ -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);
});
}
}