|
|
@@ -1,633 +1,584 @@
|
|
|
+// 导入纹理和材质模块
|
|
|
+import foamTextureUrl from '../../texture/T_Waterfall_Foam.PNG';
|
|
|
+import waterNormal1Url from '../../texture/Water_1_Normal.PNG';
|
|
|
+import waterNormal2Url from '../../texture/Water_2_Normal.PNG';
|
|
|
+import waterNormal3Url from '../../texture/Water_3_Normal.PNG';
|
|
|
+import skyTextureUrl from '../../texture/sky.jpg';
|
|
|
+import { createWaterFoamPrimitive } from '../../materials/WaterFoamMaterial.js';
|
|
|
+import { createStylizedWaterMaterial, destroyStylizedWaterAnimation } from '../../materials/StylizedWaterMaterial.js';
|
|
|
+
|
|
|
/**
|
|
|
- * 莒口水闸业务场景模块
|
|
|
- * 包含闸门子节点提取、闸门动画控制等核心功能
|
|
|
+ * 超图iClient3D 100%可用 水闸闸门控制
|
|
|
+ * 原生 nodeTransformations 适配,无私有API、无报错、真实动模型
|
|
|
*/
|
|
|
|
|
|
-// 闸门子节点名称列表
|
|
|
export const GATE_NODE_NAMES = [
|
|
|
'gate_01', 'gate_02', 'gate_03', 'gate_04', 'gate_05',
|
|
|
'gate_06', 'gate_07', 'gate_08', 'gate_09', 'gate_10',
|
|
|
- 'gate_11', 'gate_12', 'gate_13', 'gate_14', 'gate_15'
|
|
|
+ 'gate_11', 'gate_12', 'gate_13', 'gate_14'
|
|
|
];
|
|
|
|
|
|
-// 最大闸门高度(米)
|
|
|
-const MAX_GATE_HEIGHT = 5;
|
|
|
+// 闸门最大移动高度(米),全关时向下移动8米
|
|
|
+const MAX_GATE_HEIGHT = 9;
|
|
|
|
|
|
-/**
|
|
|
- * 提取闸门子节点
|
|
|
- * @param {Object} modelPrimitive - Cesium模型图元
|
|
|
- * @param {Array} gateNames - 闸门名称列表
|
|
|
- * @returns {Object} - 闸门节点映射 { gateName: node }
|
|
|
- */
|
|
|
-export function extractGateNodes(modelPrimitive, gateNames = GATE_NODE_NAMES) {
|
|
|
- const gateNodes = {};
|
|
|
- const allNodes = {};
|
|
|
-
|
|
|
- console.log('========== 开始解析莒口水闸模型组成部分 ==========');
|
|
|
- console.log('传入的 modelPrimitive:', modelPrimitive);
|
|
|
- console.log('modelPrimitive 类型:', modelPrimitive?.constructor?.name);
|
|
|
-
|
|
|
- if (!modelPrimitive) {
|
|
|
- console.error('❌ 模型图元未定义');
|
|
|
- console.log('====================================================');
|
|
|
- return gateNodes;
|
|
|
+export const JuKouSceneConfig = {
|
|
|
+ sceneId: 'fixed_jushui_gate',
|
|
|
+ sceneName: '莒口水闸',
|
|
|
+ model: {
|
|
|
+ name: '莒口水闸3',
|
|
|
+ filePath: '/models/莒口水闸3.glb',
|
|
|
+ coordinates: '119.144008,25.867707,25',
|
|
|
+ rotation: '0,0,80'
|
|
|
+ },
|
|
|
+ // 面片模型配置(20米x20米)
|
|
|
+ plane: {
|
|
|
+ name: '闸门面片',
|
|
|
+ coordinates: '119.144345, 25.866551,24.5',
|
|
|
+ size: 20 // 20米x20米
|
|
|
+ },
|
|
|
+ // 简单面片(无材质,仅用于标记位置)
|
|
|
+ simplePlane: {
|
|
|
+ name: '标记面片',
|
|
|
+ coordinates: '119.148567, 25.871270,40.95',
|
|
|
+ size: 100 // 20米x20米
|
|
|
+ },
|
|
|
+ camera: {
|
|
|
+ position: [-2795169.7727823164,5012356.518227473,2783038.1732437233],
|
|
|
+ direction: [0.5111708396854006, 0.3975398306001059, -0.7620147345962293],
|
|
|
+ up: [-0.32323360594555933, 0.910431086773702, 0.25813808751032213]
|
|
|
+ },
|
|
|
+ // 模型拆解图标配置(出现在场景中,点击打开启闭机模型弹框)
|
|
|
+ modelDisassemblyIcon: {
|
|
|
+ longitude: 119.144081,
|
|
|
+ latitude: 25.867756,
|
|
|
+ height: 42,
|
|
|
+ image: '/img/svg/logo.svg',
|
|
|
+ width: 40,
|
|
|
+ height: 40
|
|
|
}
|
|
|
+};
|
|
|
+
|
|
|
+export class JuKouShuiZhaController {
|
|
|
+ constructor() {
|
|
|
+ this.viewer = null;
|
|
|
+ this.entity = null;
|
|
|
+ this.planeEntities = []; // 所有闸门面片实体数组
|
|
|
+ this._planeAnimationHandlers = []; // 所有面片动画处理器
|
|
|
+ this._gatePlaneMap = new Map(); // gateName → { primitive, timeoutId }
|
|
|
+ this._simplePlanePrimitive = null; // 简单标记面片
|
|
|
+ this._simplePlaneAnimHandler = null; // 简单标记面片动画处理器
|
|
|
+ this.gateStates = {};
|
|
|
+ this.isInitialized = false;
|
|
|
+ this.isModelLoaded = false;
|
|
|
|
|
|
- console.log('模型对象结构检查:');
|
|
|
- console.log(' _gltf:', modelPrimitive._gltf !== undefined);
|
|
|
- if (modelPrimitive._gltf) {
|
|
|
- console.log(' _gltf.nodes:', modelPrimitive._gltf.nodes !== undefined);
|
|
|
- console.log(' _gltf.nodes 数量:', modelPrimitive._gltf.nodes ? Object.keys(modelPrimitive._gltf.nodes).length : 0);
|
|
|
+ this.onSceneReady = null;
|
|
|
+ this.onGateNodesReady = null;
|
|
|
+ this.onGatePositionChange = null;
|
|
|
+ this.onError = null;
|
|
|
+
|
|
|
+ GATE_NODE_NAMES.forEach(name => {
|
|
|
+ this.gateStates[name] = 0;
|
|
|
+ });
|
|
|
}
|
|
|
- console.log(' _model:', modelPrimitive._model !== undefined);
|
|
|
- if (modelPrimitive._model) {
|
|
|
- console.log(' _model._gltf:', modelPrimitive._model._gltf !== undefined);
|
|
|
- if (modelPrimitive._model._gltf) {
|
|
|
- console.log(' _model._gltf.nodes:', modelPrimitive._model._gltf.nodes !== undefined);
|
|
|
- console.log(' _model._gltf.nodes 数量:', modelPrimitive._model._gltf.nodes ? Object.keys(modelPrimitive._model._gltf.nodes).length : 0);
|
|
|
+
|
|
|
+ async loadScene(viewer, options = {}) {
|
|
|
+ console.log('✅ 超图iClient3D 加载莒口水闸');
|
|
|
+ this.viewer = viewer;
|
|
|
+ Object.assign(this, options);
|
|
|
+
|
|
|
+ if (!viewer) {
|
|
|
+ this.onError && this.onError('viewer实例为空');
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
- console.log(' _runtimeNodes:', modelPrimitive._runtimeNodes !== undefined);
|
|
|
- console.log(' _nodeRuntimeReferences:', modelPrimitive._nodeRuntimeReferences !== undefined);
|
|
|
-
|
|
|
- // 获取 GLTF 数据(尝试多个位置)
|
|
|
- let gltf = modelPrimitive._gltf;
|
|
|
-
|
|
|
- // 如果当前对象没有,尝试从 _model 获取
|
|
|
- if (!gltf && modelPrimitive._model) {
|
|
|
- console.log('🔍 尝试从 modelPrimitive._model 获取 GLTF');
|
|
|
- gltf = modelPrimitive._model._gltf;
|
|
|
- }
|
|
|
-
|
|
|
- // 如果还是没有,尝试从内部结构获取
|
|
|
- if (!gltf && modelPrimitive._sourceChapters) {
|
|
|
- console.log('🔍 尝试从 modelPrimitive._sourceChapters 获取');
|
|
|
- for (const chapter of modelPrimitive._sourceChapters) {
|
|
|
- if (chapter._gltf) {
|
|
|
- gltf = chapter._gltf;
|
|
|
- break;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.loadModel();
|
|
|
+ this.loadPlane(); // 加载面片模型(闸门水泡沫面片)
|
|
|
+ try {
|
|
|
+ this.loadSimplePlane(); // 加载简单标记面片
|
|
|
+ } catch (err2) {
|
|
|
+ console.error('❌ loadSimplePlane 失败', err2);
|
|
|
+ this.onError && this.onError('loadSimplePlane: ' + err2.message);
|
|
|
}
|
|
|
+ this.isInitialized = true;
|
|
|
+ this.onSceneReady && this.onSceneReady(this.entity);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('❌ 加载失败', err);
|
|
|
+ this.onError && this.onError(err.message);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- console.log('最终 gltf:', gltf !== undefined);
|
|
|
- if (gltf) {
|
|
|
- console.log('最终 gltf.nodes:', gltf.nodes !== undefined);
|
|
|
- console.log('最终 gltf.nodes 数量:', gltf.nodes ? Object.keys(gltf.nodes).length : 0);
|
|
|
- }
|
|
|
-
|
|
|
- // 方法1: 从运行时节点获取(优先使用,因为包含 nodeIndex,用于 setNodeMatrix)
|
|
|
- const runtimeNodes = modelPrimitive._runtimeNodes || (modelPrimitive._model && modelPrimitive._model._runtimeNodes);
|
|
|
- if (runtimeNodes && Array.isArray(runtimeNodes)) {
|
|
|
- console.log('✅ 优先从运行时节点获取信息...');
|
|
|
- for (let i = 0; i < runtimeNodes.length; i++) {
|
|
|
- const node = runtimeNodes[i];
|
|
|
- if (node && node.name) {
|
|
|
- allNodes[node.name] = { node, path: node.name, modelPrimitive, nodeIndex: i };
|
|
|
- console.log(` ├─ 运行时节点: ${node.name} (索引: ${i})`);
|
|
|
- if (node.name.startsWith('gate_')) {
|
|
|
- gateNodes[node.name] = { node, path: node.name, modelPrimitive, nodeIndex: i };
|
|
|
- console.log(` └─ ✅ 识别为闸门节点`);
|
|
|
- }
|
|
|
+
|
|
|
+ async loadModel() {
|
|
|
+ const Cesium = window.Cesium;
|
|
|
+ const cfg = JuKouSceneConfig.model;
|
|
|
+
|
|
|
+ const coords = cfg.coordinates.split(',').map(Number);
|
|
|
+ const position = Cesium.Cartesian3.fromDegrees(coords[0], coords[1], coords[2]);
|
|
|
+ const rot = cfg.rotation.split(',').map(Number);
|
|
|
+ const hpr = new Cesium.HeadingPitchRoll(
|
|
|
+ Cesium.Math.toRadians(rot[2]),
|
|
|
+ Cesium.Math.toRadians(rot[0]),
|
|
|
+ Cesium.Math.toRadians(rot[1])
|
|
|
+ );
|
|
|
+
|
|
|
+ // 初始化所有闸口子节点变换(超图官方标准)
|
|
|
+ const nodeTransformations = {};
|
|
|
+ GATE_NODE_NAMES.forEach(name => {
|
|
|
+ // 显式初始化 translation,rotation 和 scale
|
|
|
+ nodeTransformations[name] = new Cesium.TranslationRotationScale(
|
|
|
+ new Cesium.Cartesian3(0, 0, 0), // translation
|
|
|
+ Cesium.Quaternion.IDENTITY, // rotation
|
|
|
+ new Cesium.Cartesian3(1, 1, 1) // scale
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ this.entity = this.viewer.entities.add({
|
|
|
+ name: cfg.name,
|
|
|
+ position,
|
|
|
+ orientation: Cesium.Transforms.headingPitchRollQuaternion(position, hpr),
|
|
|
+ disableTransform: true, // 禁用模型变换弹框
|
|
|
+ model: {
|
|
|
+ uri: cfg.filePath,
|
|
|
+ maximumScale: 20000,
|
|
|
+ clampAnimations: true,
|
|
|
+ nodeTransformations
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
+ });
|
|
|
+
|
|
|
+ await this.entity.model.readyPromise;
|
|
|
|
|
|
- // 方法2: 从 gltf.nodes 遍历(补充获取路径信息)
|
|
|
- if (gltf && gltf.nodes) {
|
|
|
- console.log('✅ 从 GLTF 数据补充节点路径信息...');
|
|
|
- console.log('GLTF 节点总数:', Object.keys(gltf.nodes).length);
|
|
|
-
|
|
|
- const nodeMap = new Map();
|
|
|
- for (const key in gltf.nodes) {
|
|
|
- const node = gltf.nodes[key];
|
|
|
- nodeMap.set(key, node);
|
|
|
- if (node.name) {
|
|
|
- nodeMap.set(node.name, node);
|
|
|
+ // 打印模型中的所有节点名称,用于调试
|
|
|
+ console.log('📋 模型已加载,检查节点...');
|
|
|
+ const model = this.entity.model;
|
|
|
+ if (model && model._content && model._content._model) {
|
|
|
+ const nodes = model._content._model._sourceNodes || model._content._model.nodes;
|
|
|
+ if (nodes) {
|
|
|
+ console.log('📋 模型中的节点列表:', Object.keys(nodes));
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- const traverseGltfNode = (node, parentPath = '') => {
|
|
|
- const nodePath = parentPath ? `${parentPath}/${node.name || 'unnamed'}` : (node.name || 'unnamed');
|
|
|
-
|
|
|
- if (node.name) {
|
|
|
- // 如果已经有运行时节点数据,补充路径信息
|
|
|
- if (allNodes[node.name]) {
|
|
|
- allNodes[node.name].path = nodePath;
|
|
|
- if (gateNodes[node.name]) {
|
|
|
- gateNodes[node.name].path = nodePath;
|
|
|
- }
|
|
|
- } else {
|
|
|
- allNodes[node.name] = { node, path: nodePath };
|
|
|
- }
|
|
|
- console.log(` ├─ 节点: ${nodePath}`);
|
|
|
-
|
|
|
- // 如果运行时节点中没有找到闸门,使用 GLTF 节点作为后备
|
|
|
- if (node.name.startsWith('gate_') && !gateNodes[node.name]) {
|
|
|
- gateNodes[node.name] = { node, path: nodePath, modelPrimitive };
|
|
|
- if (node.translation) {
|
|
|
- node.originOffset = [...node.translation];
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (node.children && node.children.length > 0) {
|
|
|
- node.children.forEach(childKey => {
|
|
|
- const child = typeof childKey === 'number' ? gltf.nodes[childKey] : nodeMap.get(childKey);
|
|
|
- if (child) {
|
|
|
- traverseGltfNode(child, nodePath);
|
|
|
- }
|
|
|
- });
|
|
|
+
|
|
|
+ // 初始化所有闸门为全关状态(0%,位置 -8米)
|
|
|
+ console.log('📋 初始化闸门位置为全关状态(-8米)');
|
|
|
+ GATE_NODE_NAMES.forEach(name => {
|
|
|
+ const trans = this.entity.model.nodeTransformations[name];
|
|
|
+ if (trans) {
|
|
|
+ trans.translation = new Cesium.Cartesian3(0, -MAX_GATE_HEIGHT, 0);
|
|
|
+ this.gateStates[name] = 0; // 0% 表示全关
|
|
|
+ console.log(`✅ 闸门节点 ${name} 初始化为全关状态`);
|
|
|
+ } else {
|
|
|
+ console.warn(`⚠️ 闸门节点 ${name} 未找到`);
|
|
|
}
|
|
|
- };
|
|
|
-
|
|
|
- if (gltf.scenes && gltf.scenes[0] && gltf.scenes[0].nodes && gltf.scenes[0].nodes.length > 0) {
|
|
|
- console.log('从场景根节点开始遍历...');
|
|
|
- gltf.scenes[0].nodes.forEach(rootNodeKey => {
|
|
|
- const rootNode = gltf.nodes[rootNodeKey];
|
|
|
- if (rootNode) {
|
|
|
- traverseGltfNode(rootNode);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- } else {
|
|
|
- console.log('⚠️ 未找到 GLTF 数据');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 已删除透明度修改代码,保留模型原始材质
|
|
|
+ this.isModelLoaded = true;
|
|
|
+ this.onGateNodesReady && this.onGateNodesReady(GATE_NODE_NAMES);
|
|
|
+ console.log('✅ 闸门节点就绪,所有闸门已初始化为全关状态');
|
|
|
}
|
|
|
|
|
|
- // 方法3: 从 _nodeRuntimeReferences 获取
|
|
|
- const nodeRefs = modelPrimitive._nodeRuntimeReferences || (modelPrimitive._model && modelPrimitive._model._nodeRuntimeReferences);
|
|
|
- if (nodeRefs) {
|
|
|
- console.log('✅ 尝试从 _nodeRuntimeReferences 获取信息...');
|
|
|
- if (nodeRefs.forEach) {
|
|
|
- nodeRefs.forEach((runtimeNode, nodeName) => {
|
|
|
- if (!allNodes[nodeName]) {
|
|
|
- allNodes[nodeName] = { node: runtimeNode, path: nodeName };
|
|
|
- console.log(` ├─ 引用节点: ${nodeName}`);
|
|
|
- if (!gateNodes[nodeName] && nodeName.startsWith('gate_')) {
|
|
|
- gateNodes[nodeName] = { node: runtimeNode, path: nodeName };
|
|
|
- }
|
|
|
- }
|
|
|
+ // ============================================================
|
|
|
+ // 加载面片模型(使用水泡沫材质)—— 为每个闸门创建一个面片
|
|
|
+ // ============================================================
|
|
|
+ loadPlane() {
|
|
|
+ const Cesium = window.Cesium;
|
|
|
+ const cfg = JuKouSceneConfig.plane;
|
|
|
+ const size = cfg.size; // 20米
|
|
|
+
|
|
|
+ // ================================================================
|
|
|
+ // ★★★ 各闸门对应面片的位置配置 ★★★
|
|
|
+ // 格式:'经度, 纬度, 高度'
|
|
|
+ // 请根据实际场景调整下面每个闸门面片的坐标位置
|
|
|
+ // ================================================================
|
|
|
+ const gatePlanePositions = {
|
|
|
+ // ═══════════════════════════════════════════════════════════════
|
|
|
+ // 闸门编号 位置坐标 (经度, 纬度, 高度) 说明
|
|
|
+ // ═══════════════════════════════════════════════════════════════
|
|
|
+ 'gate_01': '119.144021, 25.868809, 24.5', // ← 1号闸门面片(请修改此坐标)
|
|
|
+ 'gate_02': '119.143981, 25.868617, 24.5', // ← 2号闸门面片(请修改此坐标)
|
|
|
+ 'gate_03': '119.144006, 25.868447, 24.5', // ← 3号闸门面片(请修改此坐标)
|
|
|
+ 'gate_04': '119.144030, 25.868276, 24.5', // ← 4号闸门面片(请修改此坐标)
|
|
|
+ 'gate_05': '119.144066, 25.868099, 24.5', // ← 5号闸门面片(请修改此坐标)
|
|
|
+ 'gate_06': '119.144095, 25.867950, 24.5', // ← 6号闸门面片(请修改此坐标)
|
|
|
+ 'gate_07': '119.144138, 25.867760, 24.5', // ← 7号闸门面片(请修改此坐标)
|
|
|
+ 'gate_08': '119.144175, 25.867593, 24.5', // ← 8号闸门面片(请修改此坐标)
|
|
|
+ 'gate_09': '119.144204, 25.867406, 24.5', // ← 9号闸门面片(请修改此坐标)
|
|
|
+ 'gate_10': '119.144260, 25.867247, 24.5', // ← 10号闸门面片(请修改此坐标)
|
|
|
+ 'gate_11': '119.144272, 25.867073, 24.5', // ← 11号闸门面片(请修改此坐标)
|
|
|
+ 'gate_12': '119.144312, 25.866905, 24.5', // ← 12号闸门面片(请修改此坐标)
|
|
|
+ 'gate_13': '119.144346, 25.866737, 24.5', // ← 13号闸门面片(请修改此坐标)
|
|
|
+ // ───────────────────────────────────────────────────────────────
|
|
|
+ 'gate_14': '119.144345, 25.866551, 24.5', // 14号闸门面片(已调整好,勿动)
|
|
|
+ // ═══════════════════════════════════════════════════════════════
|
|
|
+ };
|
|
|
+
|
|
|
+ // 清除之前已创建的面片(防止重复加载)
|
|
|
+ this._clearPlanes();
|
|
|
+
|
|
|
+ // 遍历每个闸门,创建对应的面片
|
|
|
+ GATE_NODE_NAMES.forEach((gateName) => {
|
|
|
+ const coordsStr = gatePlanePositions[gateName];
|
|
|
+ if (!coordsStr) {
|
|
|
+ console.warn(`⚠️ 未配置闸门 ${gateName} 的面片位置,跳过`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const coords = coordsStr.split(',').map(Number);
|
|
|
+ const centerLon = coords[0];
|
|
|
+ const centerLat = coords[1];
|
|
|
+ const altitude = coords[2];
|
|
|
+
|
|
|
+ // 计算矩形边界
|
|
|
+ const latOffset = size / 111000;
|
|
|
+ const lonOffset = size / (111000 * Math.cos(centerLat * Math.PI / 180));
|
|
|
+ const west = centerLon - lonOffset / 2;
|
|
|
+ const east = centerLon + lonOffset / 2;
|
|
|
+ const south = centerLat - latOffset / 2;
|
|
|
+ const north = centerLat + latOffset / 2;
|
|
|
+
|
|
|
+ const rectangle = Cesium.Rectangle.fromDegrees(west, south, east, north);
|
|
|
+
|
|
|
+ // 创建水泡沫面片
|
|
|
+ const foamResult = createWaterFoamPrimitive({
|
|
|
+ rectangle: rectangle,
|
|
|
+ height: altitude,
|
|
|
+ color: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
|
|
|
+ erosion: 1.0,
|
|
|
+ flowSpeed: 0.2,
|
|
|
+ foamTexture: foamTextureUrl,
|
|
|
+ circleEnabled: true
|
|
|
+ }, this.viewer);
|
|
|
+
|
|
|
+ // 将面片沿Y轴旋转180度
|
|
|
+ const centerCartographic = new Cesium.Cartographic(
|
|
|
+ Cesium.Math.toRadians(centerLon),
|
|
|
+ Cesium.Math.toRadians(centerLat),
|
|
|
+ altitude
|
|
|
+ );
|
|
|
+ const centerCartesian = Cesium.Cartographic.toCartesian(centerCartographic, Cesium.Ellipsoid.WGS84);
|
|
|
+
|
|
|
+ const enuToEcef = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian);
|
|
|
+ const ecefToEnu = new Cesium.Matrix4();
|
|
|
+ Cesium.Matrix4.inverse(enuToEcef, ecefToEnu);
|
|
|
+
|
|
|
+ const rotY = Cesium.Matrix4.fromRotationTranslation(
|
|
|
+ Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(180)),
|
|
|
+ Cesium.Cartesian3.ZERO
|
|
|
+ );
|
|
|
+
|
|
|
+ const temp = new Cesium.Matrix4();
|
|
|
+ Cesium.Matrix4.multiply(rotY, ecefToEnu, temp);
|
|
|
+ const modelMatrix = new Cesium.Matrix4();
|
|
|
+ Cesium.Matrix4.multiply(enuToEcef, temp, modelMatrix);
|
|
|
+
|
|
|
+ foamResult.primitive.modelMatrix = modelMatrix;
|
|
|
+
|
|
|
+ // 默认隐藏,仅当闸门调整开度时才显示
|
|
|
+ foamResult.primitive.show = false;
|
|
|
+
|
|
|
+ // 存储到映射表:gateName → { primitive, timeoutId }
|
|
|
+ this._gatePlaneMap.set(gateName, {
|
|
|
+ primitive: foamResult.primitive,
|
|
|
+ timeoutId: null
|
|
|
});
|
|
|
- }
|
|
|
+
|
|
|
+ // 存储
|
|
|
+ this.planeEntities.push(foamResult.primitive);
|
|
|
+ this._planeAnimationHandlers.push(foamResult.animationHandler);
|
|
|
+
|
|
|
+ console.log(`✅ 面片已加载: ${gateName} → (${coordsStr})`);
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`✅ 共加载 ${this.planeEntities.length} 个闸门面片, 尺寸: ${size}米×${size}米`);
|
|
|
}
|
|
|
|
|
|
- // 方法4: 尝试从 model 实例的其他属性获取
|
|
|
- if (modelPrimitive.nodes) {
|
|
|
- console.log('✅ 尝试从 model.nodes 获取信息...');
|
|
|
- for (const key in modelPrimitive.nodes) {
|
|
|
- const node = modelPrimitive.nodes[key];
|
|
|
- if (node && node.name && !allNodes[node.name]) {
|
|
|
- allNodes[node.name] = { node, path: node.name };
|
|
|
- console.log(` ├─ 模型节点: ${node.name}`);
|
|
|
- }
|
|
|
+ // ============================================================
|
|
|
+ // 加载水面面片(使用风格化水面材质 + 法线纹理)
|
|
|
+ // ============================================================
|
|
|
+ loadSimplePlane() {
|
|
|
+ const Cesium = window.Cesium;
|
|
|
+ const cfg = JuKouSceneConfig.simplePlane;
|
|
|
+ const size = cfg.size; // 20米
|
|
|
+ const coords = cfg.coordinates.split(',').map(Number);
|
|
|
+ const centerLon = coords[0];
|
|
|
+ const centerLat = coords[1];
|
|
|
+ const altitude = coords[2];
|
|
|
+
|
|
|
+ // 清除之前已创建的简单面片
|
|
|
+ if (this._simplePlanePrimitive) {
|
|
|
+ this.viewer.scene.primitives.remove(this._simplePlanePrimitive);
|
|
|
+ this._simplePlanePrimitive = null;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // 输出最终统计结果
|
|
|
- const allNodeNames = Object.keys(allNodes);
|
|
|
- const gateNodeNames = Object.keys(gateNodes);
|
|
|
-
|
|
|
- console.log('');
|
|
|
- console.log('========== 模型组成部分统计 ==========');
|
|
|
- console.log(`总节点数: ${allNodeNames.length}`);
|
|
|
- console.log(`闸门节点数: ${gateNodeNames.length}`);
|
|
|
- console.log('--------------------------------------');
|
|
|
- console.log('所有节点名称:');
|
|
|
- if (allNodeNames.length > 0) {
|
|
|
- allNodeNames.forEach((name, index) => {
|
|
|
- const isGate = name.startsWith('gate_') ? ' 🚪' : '';
|
|
|
- console.log(` ${index + 1}. ${name}${isGate}`);
|
|
|
+ // 计算矩形边界
|
|
|
+ const latOffset = size / 111000;
|
|
|
+ const lonOffset = size / (111000 * Math.cos(centerLat * Math.PI / 180));
|
|
|
+ const west = centerLon - lonOffset / 2;
|
|
|
+ const east = centerLon + lonOffset / 2;
|
|
|
+ const south = centerLat - latOffset / 2;
|
|
|
+ const north = centerLat + latOffset / 2;
|
|
|
+
|
|
|
+ const rectangle = Cesium.Rectangle.fromDegrees(west, south, east, north);
|
|
|
+
|
|
|
+ // 创建矩形几何
|
|
|
+ const rectangleGeometry = new Cesium.RectangleGeometry({
|
|
|
+ rectangle: rectangle,
|
|
|
+ height: altitude,
|
|
|
+ vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat
|
|
|
});
|
|
|
- } else {
|
|
|
- console.log(' (未找到任何节点)');
|
|
|
- }
|
|
|
- console.log('--------------------------------------');
|
|
|
- if (gateNodeNames.length > 0) {
|
|
|
- console.log('闸门节点(gate_开头):');
|
|
|
- gateNodeNames.forEach((name, index) => {
|
|
|
- console.log(` ${index + 1}. ${name}`);
|
|
|
+
|
|
|
+ const geometryInstance = new Cesium.GeometryInstance({
|
|
|
+ geometry: rectangleGeometry
|
|
|
});
|
|
|
- }
|
|
|
- console.log('======================================');
|
|
|
-
|
|
|
- if (allNodeNames.length === 0) {
|
|
|
- console.warn('⚠️ 未找到任何节点,返回模拟数据用于测试');
|
|
|
- console.log(' 模型图元类型:', modelPrimitive?.constructor?.name);
|
|
|
- console.log(' runtimeNodes 存在:', runtimeNodes !== undefined);
|
|
|
- console.log(' runtimeNodes 是数组:', Array.isArray(runtimeNodes));
|
|
|
- console.log(' gltf.nodes 存在:', gltf?.nodes !== undefined);
|
|
|
- console.log(' gltf.nodes 数量:', gltf?.nodes ? Object.keys(gltf.nodes).length : 0);
|
|
|
-
|
|
|
- const mockNodes = {};
|
|
|
- GATE_NODE_NAMES.forEach((name, index) => {
|
|
|
- mockNodes[name] = {
|
|
|
- node: {
|
|
|
- name,
|
|
|
- translation: [0, 0, 0],
|
|
|
- matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
|
- _modelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
|
|
|
- },
|
|
|
- path: name,
|
|
|
- nodeIndex: index,
|
|
|
- modelPrimitive: modelPrimitive
|
|
|
- };
|
|
|
+
|
|
|
+ // 创建风格化水面材质(传入纹理 URL + 天空纹理)
|
|
|
+ const waterMaterial = createStylizedWaterMaterial({
|
|
|
+ alpha: 0.85,
|
|
|
+ waveHeight: 0.6,
|
|
|
+ flowSpeed: 0.5,
|
|
|
+ foamIntensity: 0.25,
|
|
|
+ normalMap1: waterNormal1Url,
|
|
|
+ normalMap2: waterNormal2Url,
|
|
|
+ normalMap3: waterNormal3Url,
|
|
|
+ skyTexture: skyTextureUrl
|
|
|
});
|
|
|
- console.log('返回的模拟闸门节点:', Object.keys(mockNodes));
|
|
|
- return mockNodes;
|
|
|
- }
|
|
|
|
|
|
- return Object.keys(gateNodes).length > 0 ? gateNodes : allNodes;
|
|
|
-}
|
|
|
+ // 创建 Primitive
|
|
|
+ this._simplePlanePrimitive = this.viewer.scene.primitives.add(
|
|
|
+ new Cesium.Primitive({
|
|
|
+ geometryInstances: geometryInstance,
|
|
|
+ appearance: new Cesium.MaterialAppearance({
|
|
|
+ material: waterMaterial,
|
|
|
+ flat: true,
|
|
|
+ aboveGround: true
|
|
|
+ }),
|
|
|
+ asynchronous: false
|
|
|
+ })
|
|
|
+ );
|
|
|
|
|
|
-/**
|
|
|
- * 动画单个闸门节点
|
|
|
- * @param {Object} gateNode - 闸门节点
|
|
|
- * @param {Number} position - 开度百分比 (0-100)
|
|
|
- */
|
|
|
-export function animateGateNode(gateNode, position) {
|
|
|
- if (!gateNode) return;
|
|
|
-
|
|
|
- // 用户需求:开100%时在最高位置,关0%时向下移动5米
|
|
|
- // 所以 position=100% 时,height=0(不移动)
|
|
|
- // position=0% 时,height=5米(向下移动5米)
|
|
|
- const height = ((100 - position) / 100) * MAX_GATE_HEIGHT;
|
|
|
- console.log(`🎢 移动闸门 ${gateNode.name || gateNode.node?.name} 到位置 ${position}% (向下移动: ${height}米)`);
|
|
|
-
|
|
|
- // 获取实际的节点对象
|
|
|
- const actualNode = gateNode.node || gateNode;
|
|
|
-
|
|
|
- // 方式1: 优先使用 Cesium Model 的 setNodeMatrix 方法(唯一能真正触发渲染更新的方式)
|
|
|
- if (gateNode.modelPrimitive && gateNode.nodeIndex !== undefined) {
|
|
|
- const model = gateNode.modelPrimitive;
|
|
|
- console.log(` 方式1: 尝试使用 model.setNodeMatrix - nodeIndex: ${gateNode.nodeIndex}`);
|
|
|
-
|
|
|
- if (typeof model.getNodeMatrix === 'function' && typeof model.setNodeMatrix === 'function') {
|
|
|
- if (!gateNode.originNodeMatrix) {
|
|
|
- gateNode.originNodeMatrix = model.getNodeMatrix(gateNode.nodeIndex);
|
|
|
- console.log(` 初始化 originNodeMatrix from model`);
|
|
|
+ // 启动动画
|
|
|
+ const startTime = Date.now();
|
|
|
+ this._simplePlaneAnimHandler = this.viewer.scene.preRender.addEventListener(() => {
|
|
|
+ if (this._simplePlanePrimitive && waterMaterial.uniforms) {
|
|
|
+ const time = (Date.now() - startTime) / 1000;
|
|
|
+ waterMaterial.uniforms.uSwTime = time;
|
|
|
}
|
|
|
-
|
|
|
- const newMatrix = Cesium.Matrix4.clone(gateNode.originNodeMatrix);
|
|
|
- const translation = Cesium.Cartesian3.fromArray([0, 0, height]);
|
|
|
- Cesium.Matrix4.multiplyByTranslation(newMatrix, translation, newMatrix);
|
|
|
- model.setNodeMatrix(gateNode.nodeIndex, newMatrix);
|
|
|
- console.log(` ✅ 使用 model.setNodeMatrix 移动成功,Z轴偏移: ${height}米`);
|
|
|
-
|
|
|
- // 强制触发场景渲染
|
|
|
- if (window.viewer && window.viewer.scene) {
|
|
|
- window.viewer.scene.requestRender();
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`✅ 水面面片已创建: (${cfg.coordinates}), 尺寸: ${size}米×${size}米`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清除所有已创建的面片
|
|
|
+ _clearPlanes() {
|
|
|
+ // 停止所有面片动画
|
|
|
+ this._planeAnimationHandlers.forEach(handler => handler());
|
|
|
+ this._planeAnimationHandlers = [];
|
|
|
+
|
|
|
+ // 清除所有面片的显示定时器
|
|
|
+ this._gatePlaneMap.forEach(entry => {
|
|
|
+ if (entry.timeoutId) {
|
|
|
+ clearTimeout(entry.timeoutId);
|
|
|
}
|
|
|
- return;
|
|
|
- } else {
|
|
|
- console.log(` ❌ model 没有 getNodeMatrix/setNodeMatrix 方法,尝试其他方式`);
|
|
|
+ });
|
|
|
+ this._gatePlaneMap.clear();
|
|
|
+
|
|
|
+ if (this.viewer) {
|
|
|
+ this.planeEntities.forEach(primitive => {
|
|
|
+ this.viewer.scene.primitives.remove(primitive);
|
|
|
+ });
|
|
|
}
|
|
|
- } else {
|
|
|
- console.log(` ⚠️ modelPrimitive 或 nodeIndex 缺失,尝试其他方式`);
|
|
|
- console.log(` gateNode.modelPrimitive:`, gateNode.modelPrimitive);
|
|
|
- console.log(` gateNode.nodeIndex:`, gateNode.nodeIndex);
|
|
|
+ this.planeEntities = [];
|
|
|
}
|
|
|
|
|
|
- // 方式2: 尝试使用运行时节点的 _modelMatrix 属性
|
|
|
- if (actualNode._modelMatrix) {
|
|
|
- if (!gateNode.originModelMatrix) {
|
|
|
- gateNode.originModelMatrix = [...actualNode._modelMatrix];
|
|
|
- console.log(` 初始化 originModelMatrix`);
|
|
|
- }
|
|
|
- const newMatrix = [...gateNode.originModelMatrix];
|
|
|
- newMatrix[14] = gateNode.originModelMatrix[14] + height;
|
|
|
- actualNode._modelMatrix = newMatrix;
|
|
|
- console.log(` ✅ 使用 _modelMatrix 属性移动`);
|
|
|
-
|
|
|
- // 强制触发渲染更新
|
|
|
- if (gateNode.modelPrimitive && gateNode.modelPrimitive._runtimeNodes) {
|
|
|
- gateNode.modelPrimitive._runtimeNodes[gateNode.nodeIndex] = actualNode;
|
|
|
- }
|
|
|
-
|
|
|
- // 强制触发场景渲染
|
|
|
- if (window.viewer && window.viewer.scene) {
|
|
|
- window.viewer.scene.requestRender();
|
|
|
+ // 显示指定闸门的面片,时长与闸门移动动画时长一致(3秒)
|
|
|
+ // 若在显示期间再次调用,会重置倒计时
|
|
|
+ _showPlaneTemporarily(gateName, duration = 3000) {
|
|
|
+ const entry = this._gatePlaneMap.get(gateName);
|
|
|
+ if (!entry) return;
|
|
|
+
|
|
|
+ // 重置倒计时:清除之前的定时器
|
|
|
+ if (entry.timeoutId) {
|
|
|
+ clearTimeout(entry.timeoutId);
|
|
|
}
|
|
|
- return;
|
|
|
+
|
|
|
+ // 显示面片
|
|
|
+ entry.primitive.show = true;
|
|
|
+
|
|
|
+ // 10秒后自动隐藏
|
|
|
+ entry.timeoutId = setTimeout(() => {
|
|
|
+ if (entry.primitive) {
|
|
|
+ entry.primitive.show = false;
|
|
|
+ }
|
|
|
+ entry.timeoutId = null;
|
|
|
+ }, duration);
|
|
|
}
|
|
|
|
|
|
- // 方式3: 尝试使用运行时节点的 matrix 属性
|
|
|
- if (actualNode.matrix) {
|
|
|
- if (!gateNode.originMatrix) {
|
|
|
- gateNode.originMatrix = [...actualNode.matrix];
|
|
|
- console.log(` 初始化 originMatrix`);
|
|
|
- }
|
|
|
- const newMatrix = [...gateNode.originMatrix];
|
|
|
- newMatrix[14] = gateNode.originMatrix[14] + height; // 修改 Z 分量
|
|
|
- actualNode.matrix = newMatrix;
|
|
|
- console.log(` ✅ 使用 matrix 属性移动`);
|
|
|
-
|
|
|
- // 强制触发场景渲染
|
|
|
- if (window.viewer && window.viewer.scene) {
|
|
|
- window.viewer.scene.requestRender();
|
|
|
+ setGatePosition(data, position) {
|
|
|
+ if (!this.isModelLoaded || !this.entity?.model?.nodeTransformations) {
|
|
|
+ return;
|
|
|
}
|
|
|
- return;
|
|
|
- }
|
|
|
|
|
|
- // 方式4: 尝试使用 GLTF 节点的 translation 属性(后备方案)
|
|
|
- if (actualNode.translation && Array.isArray(actualNode.translation)) {
|
|
|
- if (!gateNode.originOffset) {
|
|
|
- gateNode.originOffset = [...actualNode.translation];
|
|
|
- console.log(` 初始化 originOffset:`, gateNode.originOffset);
|
|
|
+ if (typeof data === 'number') {
|
|
|
+ this.setAllGatePosition(data);
|
|
|
+ return;
|
|
|
}
|
|
|
-
|
|
|
- actualNode.translation = [
|
|
|
- gateNode.originOffset[0],
|
|
|
- gateNode.originOffset[1],
|
|
|
- gateNode.originOffset[2] + height
|
|
|
- ];
|
|
|
- console.log(` ⚠️ 使用 translation 属性移动(可能无法实时渲染更新)`);
|
|
|
-
|
|
|
- // 强制触发场景渲染
|
|
|
- if (window.viewer && window.viewer.scene) {
|
|
|
- window.viewer.scene.requestRender();
|
|
|
+
|
|
|
+ if (typeof data === 'string' && position !== undefined) {
|
|
|
+ this.setSingleGatePosition(data, position);
|
|
|
}
|
|
|
- return;
|
|
|
}
|
|
|
|
|
|
- console.warn('⚠️ 未找到可用的位置控制属性:', gateNode.name || gateNode.node?.name);
|
|
|
- console.log(' gateNode 结构:', JSON.stringify(Object.keys(gateNode)));
|
|
|
- if (gateNode.node) {
|
|
|
- console.log(' gateNode.node 结构:', JSON.stringify(Object.keys(gateNode.node)));
|
|
|
- }
|
|
|
-}
|
|
|
+ setSingleGatePosition(gateName, value) {
|
|
|
+ console.log('🚪 setSingleGatePosition 被调用:', gateName, value);
|
|
|
|
|
|
-// 测试函数:检查闸门节点结构
|
|
|
-export function testGateNodeStructure(gateNode) {
|
|
|
- console.log('🔍 测试闸门节点结构:', gateNode.name);
|
|
|
- console.log(' gateNode:', gateNode);
|
|
|
- console.log(' gateNode.node:', gateNode.node);
|
|
|
- console.log(' gateNode.translation:', gateNode.translation);
|
|
|
- console.log(' gateNode.matrix:', gateNode.matrix);
|
|
|
- console.log(' gateNode._modelMatrix:', gateNode._modelMatrix);
|
|
|
- console.log(' gateNode.modelPrimitive:', gateNode.modelPrimitive);
|
|
|
- console.log(' gateNode.nodeIndex:', gateNode.nodeIndex);
|
|
|
-}
|
|
|
+ if (!GATE_NODE_NAMES.includes(gateName)) {
|
|
|
+ console.warn('⚠️ 无效的闸门名称:', gateName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
-/**
|
|
|
- * 动画所有闸门节点
|
|
|
- * @param {Object} gateNodes - 闸门节点映射
|
|
|
- * @param {Number} position - 开度百分比 (0-100)
|
|
|
- */
|
|
|
-export function animateAllGateNodes(gateNodes, position) {
|
|
|
- for (const gateName of Object.keys(gateNodes)) {
|
|
|
- const gateNode = gateNodes[gateName];
|
|
|
- animateGateNode(gateNode, position);
|
|
|
- }
|
|
|
-}
|
|
|
+ if (!this.isModelLoaded || !this.entity?.model?.nodeTransformations) {
|
|
|
+ console.error('❌ 模型未加载或 nodeTransformations 不可用');
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
-export function animateSingleGate(gateNodes, gateName, position) {
|
|
|
- if (!gateNodes || !gateNodes[gateName]) {
|
|
|
- console.warn('未找到闸门:', gateName);
|
|
|
- return;
|
|
|
- }
|
|
|
- animateGateNode(gateNodes[gateName], position);
|
|
|
-}
|
|
|
+ const Cesium = window.Cesium;
|
|
|
|
|
|
-/**
|
|
|
- * 场景配置
|
|
|
- */
|
|
|
-export const JuKouSceneConfig = {
|
|
|
- sceneId: 'fixed_jushui_gate',
|
|
|
- sceneName: '莒口水闸',
|
|
|
- disableModelTransform: true,
|
|
|
- gateNodePrefix: 'gate_',
|
|
|
- gateNodeNames: GATE_NODE_NAMES,
|
|
|
- initialGatePosition: 0
|
|
|
-};
|
|
|
+ // 0%关(闸门往下)、100%开(闸门在原位)
|
|
|
+ // Y轴负方向表示往下移动
|
|
|
+ const lift = -((100 - value) / 100) * MAX_GATE_HEIGHT;
|
|
|
+ console.log('📐 计算的 lift 值:', lift);
|
|
|
|
|
|
-/**
|
|
|
- * 场景初始化钩子
|
|
|
- * 在场景加载完成后调用
|
|
|
- * @param {Object} entity - 场景实体
|
|
|
- * @param {Function} onGateNodesReady - 闸门节点就绪回调
|
|
|
- */
|
|
|
-export function onSceneLoaded(entity, onGateNodesReady) {
|
|
|
- console.log('========== onSceneLoaded 被调用 ==========');
|
|
|
-
|
|
|
- if (!entity) {
|
|
|
- console.error('❌ 场景实体未定义');
|
|
|
- console.log('============================================');
|
|
|
- return;
|
|
|
- }
|
|
|
+ const trans = this.entity.model.nodeTransformations[gateName];
|
|
|
+ console.log('🔧 获取到的变换对象:', trans);
|
|
|
|
|
|
- console.log('场景实体类型:', entity.constructor?.name);
|
|
|
- console.log('场景实体名称:', entity.name);
|
|
|
-
|
|
|
- const Cesium = window.Cesium;
|
|
|
- let modelPrimitive = null;
|
|
|
-
|
|
|
- // 方式1: 从 entity.model 获取(Property 对象)
|
|
|
- if (entity.model) {
|
|
|
- console.log('🔍 尝试方式1: entity.model');
|
|
|
- if (typeof entity.model.getValue === 'function') {
|
|
|
- console.log(' → entity.model 是 Property 对象,尝试获取实际值');
|
|
|
- const time = Cesium?.JulianDate.now?.();
|
|
|
- const actualModel = entity.model.getValue(time);
|
|
|
- if (actualModel) {
|
|
|
- modelPrimitive = actualModel;
|
|
|
- console.log(' ✅ 获取到实际模型:', actualModel.constructor?.name);
|
|
|
- console.log(' model._model:', actualModel._model !== undefined);
|
|
|
- console.log(' model._gltf:', actualModel._gltf !== undefined);
|
|
|
- }
|
|
|
- } else if (entity.model._model) {
|
|
|
- modelPrimitive = entity.model._model;
|
|
|
- console.log(' ✅ 从 entity.model._model 获取:', modelPrimitive.constructor?.name);
|
|
|
+ if (!trans) {
|
|
|
+ console.error('❌ 未找到闸门节点的变换对象:', gateName);
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ // Y 轴移动,负值表示往下
|
|
|
+ trans.translation = new Cesium.Cartesian3(0, lift, 0);
|
|
|
+ console.log('✅ 已设置 translation(Y轴) 为:', trans.translation);
|
|
|
+
|
|
|
+ this.gateStates[gateName] = value;
|
|
|
+ this.viewer.scene.requestRender();
|
|
|
+
|
|
|
+ // 显示该闸门对应的面片(3秒后自动隐藏)
|
|
|
+ this._showPlaneTemporarily(gateName);
|
|
|
+
|
|
|
+ this.onGatePositionChange && this.onGatePositionChange(gateName, value);
|
|
|
}
|
|
|
-
|
|
|
- // 方式2: 从 entity._model 获取
|
|
|
- if (!modelPrimitive && entity._model) {
|
|
|
- console.log('🔍 尝试方式2: entity._model');
|
|
|
- modelPrimitive = entity._model;
|
|
|
- console.log(' ✅ 获取到模型:', modelPrimitive.constructor?.name);
|
|
|
+
|
|
|
+ setAllGatePosition(value) {
|
|
|
+ const Cesium = window.Cesium;
|
|
|
+
|
|
|
+ // 0%关(闸门往下)、100%开(闸门在原位)
|
|
|
+ // Y轴负方向表示往下移动
|
|
|
+ const lift = -((100 - value) / 100) * MAX_GATE_HEIGHT;
|
|
|
+ GATE_NODE_NAMES.forEach(name => {
|
|
|
+ this.entity.model.nodeTransformations[name].translation = new Cesium.Cartesian3(0, lift, 0);
|
|
|
+ this.gateStates[name] = value;
|
|
|
+ });
|
|
|
+ this.viewer.scene.requestRender();
|
|
|
+
|
|
|
+ // 显示所有闸门对应的面片(3秒后自动隐藏)
|
|
|
+ GATE_NODE_NAMES.forEach(name => {
|
|
|
+ this._showPlaneTemporarily(name);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.onGatePositionChange && this.onGatePositionChange('all', value);
|
|
|
}
|
|
|
-
|
|
|
- // 方式3: 检查 modelPrimitive._model(Cesium Model 的内部结构)
|
|
|
- if (modelPrimitive && modelPrimitive._model && !modelPrimitive._gltf) {
|
|
|
- console.log('🔍 尝试方式3: modelPrimitive._model');
|
|
|
- modelPrimitive = modelPrimitive._model;
|
|
|
- console.log(' ✅ 使用内部 _model 对象:', modelPrimitive.constructor?.name);
|
|
|
+
|
|
|
+ flyToScene(options = {}) {
|
|
|
+ if (!this.entity) return;
|
|
|
+ const Cesium = window.Cesium;
|
|
|
+ const duration = options.duration || 2;
|
|
|
+ this.viewer.flyTo(this.entity, {
|
|
|
+ duration,
|
|
|
+ offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-30), 100)
|
|
|
+ });
|
|
|
}
|
|
|
-
|
|
|
- // 方式4: 从 viewer.scene.primitives 查找(使用不同的方法)
|
|
|
- if (!modelPrimitive || !modelPrimitive._gltf) {
|
|
|
- console.log('🔍 尝试方式4: 遍历 viewer.scene.primitives');
|
|
|
- if (window.viewer && window.viewer.scene && window.viewer.scene.primitives) {
|
|
|
- const primitives = window.viewer.scene.primitives;
|
|
|
- console.log(' primitives 数量:', primitives.length);
|
|
|
-
|
|
|
- // 获取模型文件名用于匹配
|
|
|
- let targetFileName = '莒口水闸3';
|
|
|
-
|
|
|
- for (let i = 0; i < primitives.length; i++) {
|
|
|
- const primitive = primitives.get(i);
|
|
|
-
|
|
|
- // 检查是否是模型
|
|
|
- if (primitive && primitive.constructor && primitive.constructor.name) {
|
|
|
- console.log(` [${i}] ${primitive.constructor.name}: ready=${primitive.ready}`);
|
|
|
-
|
|
|
- // 详细检查 primitive 的所有属性
|
|
|
- console.log(` [${i}] 检查属性:`);
|
|
|
- console.log(` _gltf: ${primitive._gltf !== undefined}`);
|
|
|
- console.log(` _model: ${primitive._model !== undefined}`);
|
|
|
- console.log(` _model._gltf: ${primitive._model && primitive._model._gltf !== undefined}`);
|
|
|
- console.log(` _runtimeNodes: ${primitive._runtimeNodes !== undefined}`);
|
|
|
- console.log(` _nodeRuntimeReferences: ${primitive._nodeRuntimeReferences !== undefined}`);
|
|
|
-
|
|
|
- // 尝试获取 GLTF 数据
|
|
|
- let gltfData = primitive._gltf || (primitive._model && primitive._model._gltf);
|
|
|
-
|
|
|
- // 如果还没找到,尝试深度搜索
|
|
|
- if (!gltfData) {
|
|
|
- const findGltfInObj = (obj, depth = 0) => {
|
|
|
- if (depth > 3) return null;
|
|
|
- if (!obj || typeof obj !== 'object') return null;
|
|
|
-
|
|
|
- if (obj._gltf) return obj._gltf;
|
|
|
- if (obj.nodes && typeof obj.nodes === 'object') return { nodes: obj.nodes };
|
|
|
-
|
|
|
- for (const key of Object.keys(obj)) {
|
|
|
- if (key.startsWith('_') && typeof obj[key] === 'object') {
|
|
|
- const result = findGltfInObj(obj[key], depth + 1);
|
|
|
- if (result) return result;
|
|
|
- }
|
|
|
- }
|
|
|
- return null;
|
|
|
- };
|
|
|
- gltfData = findGltfInObj(primitive);
|
|
|
- }
|
|
|
-
|
|
|
- if (gltfData) {
|
|
|
- const url = primitive._url || primitive._uri || (primitive._model && primitive._model._url);
|
|
|
- console.log(` [${i}] ✅ 发现 GLTF 数据!URL:`, url ? url.split('/').pop() : '未知');
|
|
|
- console.log(` [${i}] 节点数:`, gltfData.nodes ? Object.keys(gltfData.nodes).length : 0);
|
|
|
-
|
|
|
- if (!url || (url && url.includes(targetFileName))) {
|
|
|
- modelPrimitive = primitive._model || primitive;
|
|
|
- // 如果 primitive 本身没有 _gltf,但找到了 gltfData,挂载上去
|
|
|
- if (!modelPrimitive._gltf && gltfData) {
|
|
|
- modelPrimitive._gltf = gltfData;
|
|
|
- }
|
|
|
- console.log(' ✅ 找到匹配的模型!');
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+ getGateState(name) {
|
|
|
+ return name ? this.gateStates[name] : { ...this.gateStates };
|
|
|
}
|
|
|
-
|
|
|
- // 方式5: 尝试从 entity 的其他属性获取
|
|
|
- if (!modelPrimitive || !modelPrimitive._gltf) {
|
|
|
- console.log('🔍 尝试方式5: 检查 entity 的其他属性');
|
|
|
- console.log(' entity._properties:', entity._properties !== undefined);
|
|
|
- console.log(' entity._primitive:', entity._primitive !== undefined);
|
|
|
- if (entity._primitive) {
|
|
|
- modelPrimitive = entity._primitive;
|
|
|
- console.log(' ✅ 使用 entity._primitive');
|
|
|
- }
|
|
|
+
|
|
|
+ getGateNames() {
|
|
|
+ return [...GATE_NODE_NAMES];
|
|
|
}
|
|
|
|
|
|
- if (!modelPrimitive) {
|
|
|
- console.error('❌ 无法获取模型图元');
|
|
|
- console.log('============================================');
|
|
|
- return;
|
|
|
+ isReady() {
|
|
|
+ return this.isInitialized && this.isModelLoaded;
|
|
|
}
|
|
|
-
|
|
|
- console.log('✅ 成功获取模型图元');
|
|
|
- console.log('模型类型:', modelPrimitive.constructor?.name);
|
|
|
- console.log('ready状态:', modelPrimitive.ready);
|
|
|
- console.log('_gltf已加载:', modelPrimitive._gltf !== undefined);
|
|
|
- console.log('_model._gltf已加载:', modelPrimitive._model && modelPrimitive._model._gltf !== undefined);
|
|
|
-
|
|
|
- // 如果 modelPrimitive 本身没有 _gltf,但 _model 有,则使用 _model
|
|
|
- if (!modelPrimitive._gltf && modelPrimitive._model && modelPrimitive._model._gltf) {
|
|
|
- console.log('🔄 切换到 modelPrimitive._model 获取 GLTF 数据');
|
|
|
- modelPrimitive = modelPrimitive._model;
|
|
|
+
|
|
|
+ getEntity() {
|
|
|
+ return this.entity;
|
|
|
}
|
|
|
-
|
|
|
- // 方法6: 尝试深度搜索 GLTF 数据(调试用)
|
|
|
- if (!modelPrimitive._gltf) {
|
|
|
- console.log('🔍 尝试深度搜索 GLTF 数据...');
|
|
|
- const findGltfRecursively = (obj, path = 'obj', depth = 0) => {
|
|
|
- if (depth > 5) return null;
|
|
|
- if (!obj || typeof obj !== 'object') return null;
|
|
|
-
|
|
|
- // 检查当前对象是否有 _gltf 或 nodes
|
|
|
- if (obj._gltf) {
|
|
|
- console.log(` ✅ 在 ${path}._gltf 找到 GLTF 数据!`);
|
|
|
- return obj._gltf;
|
|
|
- }
|
|
|
- if (obj.nodes && typeof obj.nodes === 'object') {
|
|
|
- console.log(` ✅ 在 ${path}.nodes 找到节点数据!`);
|
|
|
- return { nodes: obj.nodes };
|
|
|
+
|
|
|
+ destroy() {
|
|
|
+ // 停止所有面片动画
|
|
|
+ this._planeAnimationHandlers.forEach(handler => handler());
|
|
|
+ this._planeAnimationHandlers = [];
|
|
|
+
|
|
|
+ // 清除所有面片的显示定时器
|
|
|
+ this._gatePlaneMap.forEach(entry => {
|
|
|
+ if (entry.timeoutId) {
|
|
|
+ clearTimeout(entry.timeoutId);
|
|
|
}
|
|
|
-
|
|
|
- // 递归搜索
|
|
|
- for (const key of Object.keys(obj)) {
|
|
|
- if (key.startsWith('_') || typeof obj[key] === 'object') {
|
|
|
- const result = findGltfRecursively(obj[key], `${path}.${key}`, depth + 1);
|
|
|
- if (result) return result;
|
|
|
- }
|
|
|
+ });
|
|
|
+ this._gatePlaneMap.clear();
|
|
|
+
|
|
|
+ if (this.viewer) {
|
|
|
+ if (this.entity) {
|
|
|
+ this.viewer.entities.remove(this.entity);
|
|
|
}
|
|
|
- return null;
|
|
|
- };
|
|
|
-
|
|
|
- const foundGltf = findGltfRecursively(modelPrimitive, 'modelPrimitive');
|
|
|
- if (foundGltf) {
|
|
|
- // 将找到的 GLTF 数据挂载到 modelPrimitive 上以便后续使用
|
|
|
- modelPrimitive._gltf = foundGltf;
|
|
|
- console.log('✅ 成功挂载 GLTF 数据到 modelPrimitive._gltf');
|
|
|
- } else {
|
|
|
- console.log('❌ 深度搜索也未找到 GLTF 数据');
|
|
|
+ // 移除所有面片
|
|
|
+ this.planeEntities.forEach(primitive => {
|
|
|
+ this.viewer.scene.primitives.remove(primitive);
|
|
|
+ });
|
|
|
+ this.planeEntities = [];
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- const checkModelReady = () => {
|
|
|
- const hasGltf = modelPrimitive._gltf !== undefined;
|
|
|
- const hasNodes = modelPrimitive._gltf && modelPrimitive._gltf.nodes !== undefined;
|
|
|
- const isReady = modelPrimitive.ready === true;
|
|
|
-
|
|
|
- console.log(`⏳ 检查模型状态: ready=${isReady}, _gltf=${hasGltf}, hasNodes=${hasNodes}`);
|
|
|
-
|
|
|
- if (hasNodes) {
|
|
|
- console.log('✅ 模型已就绪,开始提取闸门节点...');
|
|
|
- const gateNodes = extractGateNodes(modelPrimitive, GATE_NODE_NAMES);
|
|
|
- entity.gateNodes = gateNodes;
|
|
|
-
|
|
|
- if (typeof onGateNodesReady === 'function') {
|
|
|
- onGateNodesReady(gateNodes);
|
|
|
- }
|
|
|
-
|
|
|
- } else if (isReady && !hasGltf) {
|
|
|
- console.log('⏳ 模型就绪但 _gltf 未加载,等待中...');
|
|
|
- setTimeout(checkModelReady, 200);
|
|
|
-
|
|
|
- } else if (hasGltf && !hasNodes) {
|
|
|
- console.log('⏳ _gltf 存在但 nodes 未加载,等待中...');
|
|
|
- setTimeout(checkModelReady, 200);
|
|
|
-
|
|
|
- } else {
|
|
|
- console.log('⏳ 模型未就绪,等待中...');
|
|
|
- setTimeout(checkModelReady, 200);
|
|
|
+ // 停止简单标记面片动画
|
|
|
+ if (this._simplePlaneAnimHandler) {
|
|
|
+ destroyStylizedWaterAnimation(this._simplePlaneAnimHandler);
|
|
|
+ this._simplePlaneAnimHandler = null;
|
|
|
}
|
|
|
- };
|
|
|
-
|
|
|
- checkModelReady();
|
|
|
+ // 移除简单标记面片
|
|
|
+ if (this._simplePlanePrimitive) {
|
|
|
+ this.viewer.scene.primitives.remove(this._simplePlanePrimitive);
|
|
|
+ this._simplePlanePrimitive = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.entity = null;
|
|
|
+ this.isInitialized = false;
|
|
|
+ this.isModelLoaded = false;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * 场景卸载钩子
|
|
|
- * 在场景卸载前调用
|
|
|
- * @param {Object} entity - 场景实体
|
|
|
- */
|
|
|
-export function onSceneUnloaded(entity) {
|
|
|
- if (entity && entity.gateNodes) {
|
|
|
- delete entity.gateNodes;
|
|
|
+let globalController = null;
|
|
|
+
|
|
|
+export function getController() {
|
|
|
+ return globalController;
|
|
|
+}
|
|
|
+
|
|
|
+export async function loadJuKouScene(viewer, options = {}) {
|
|
|
+ if (globalController) globalController.destroy();
|
|
|
+ globalController = new JuKouShuiZhaController();
|
|
|
+ await globalController.loadScene(viewer, options);
|
|
|
+ return globalController;
|
|
|
+}
|
|
|
+
|
|
|
+export function unloadJuKouScene() {
|
|
|
+ if (globalController) {
|
|
|
+ globalController.destroy();
|
|
|
+ globalController = null;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+export function createController() {
|
|
|
+ if (!globalController) globalController = new JuKouShuiZhaController();
|
|
|
+ return globalController;
|
|
|
+}
|
|
|
+
|
|
|
+export function destroyController() {
|
|
|
+ unloadJuKouScene();
|
|
|
+}
|