export class ModelPathMover { /** * 构造函数 * @param {Cesium.Viewer} viewer - Cesium viewer实例 * @param {Object} options - 配置选项 * @param {Number} [options.baseHeight=0] - 外部传入的统一高度(米),将叠加到所有位置点上 */ constructor(viewer, options) { this.viewer = viewer; this.modelUrl = options.modelUrl; this.positions = options.positions || []; this.speed = options.speed || 10; // 米/秒 this.loop = options.loop !== undefined ? options.loop : true; this.pathStyle = options.pathStyle || { color: Cesium.Color.YELLOW, width: 3 }; this.markerStyle = options.markerStyle || { color: Cesium.Color.RED, pixelSize: 10, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }; this.iconUrl = options.iconUrl; this.iconSize = options.iconSize || 32; this.iconOffset = options.iconOffset || 0; this.headingOffset = options.headingOffset || 0; // 新增:接收外部传入的统一高度(默认0米) this.baseHeight = options.baseHeight || 0; // 内部状态(保持不变) this.modelEntity = null; this.pathEntity = null; this.markerEntities = []; this.iconEntity = null; this.currentIndex = 0; this.isMoving = false; this.lastTime = 0; this.animationFrameId = null; this.currentPosition = null; this.progress = 0; this.modelHeight = 0; // 初始化 this.init(); // 调试信息(增加baseHeight打印) console.log('ModelPathMover初始化完成', { positionCount: this.positions.length, speed: this.speed, loop: this.loop, baseHeight: this.baseHeight // 打印传入的统一高度 }); } /** * 初始化(保持不变) */ init() { if (this.positions.length < 2) { console.warn('至少需要2个位置点才能进行移动'); } this.createPath(); this.createMarkers(); this.createModel(); if (this.iconUrl) { this.createIcon(); } } /** * 创建模型(修改:叠加baseHeight) */ createModel() { if (this.positions.length === 0) return; const firstPos = this.positions[0]; // 关键修改:在原始高度基础上叠加baseHeight const finalHeight = (firstPos.height || 0) + this.baseHeight; this.currentPosition = Cesium.Cartesian3.fromDegrees( firstPos.lon, firstPos.lat, finalHeight // 使用叠加后的高度 ); // 初始朝向计算(保持不变) let initialOrientation = Cesium.Transforms.headingPitchRollQuaternion(this.currentPosition, new Cesium.HeadingPitchRoll(0, 0, 0)); if (this.positions.length > 1) { const nextPos = this.positions[1]; // 计算下一个点时同样叠加baseHeight const nextFinalHeight = (nextPos.height || 0) + this.baseHeight; const nextCartesian = Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, nextFinalHeight); const heading = this.calculateHeading(this.currentPosition, nextCartesian); initialOrientation = Cesium.Transforms.headingPitchRollQuaternion(this.currentPosition, new Cesium.HeadingPitchRoll(heading, 0, 0)); } this.modelEntity = this.viewer.entities.add({ name: 'Moving Model', position: this.currentPosition, orientation: initialOrientation, model: { uri: this.modelUrl, minimumPixelSize: 64, readyPromise: (model) => { const boundingSphere = model.boundingSphere; if (boundingSphere) { this.modelHeight = boundingSphere.radius * 2; console.log('模型高度检测完成:', this.modelHeight, '米'); } } } }); } /** * 创建路径(修改:叠加baseHeight) */ createPath() { if (this.positions.length < 2) return; // 关键修改:遍历所有点时,给每个点的高度叠加baseHeight const pathPositions = this.positions.map((pos) => { const finalHeight = (pos.height || 0) + this.baseHeight; return Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, finalHeight); }); this.pathEntity = this.viewer.entities.add({ name: 'Model Path', polyline: { positions: pathPositions, width: this.pathStyle.width, material: this.pathStyle.color, clampToGround: false } }); } /** * 创建位置标记(修改:叠加baseHeight) */ createMarkers() { this.markerEntities = this.positions.map((pos, index) => { // 关键修改:标记点高度同样叠加baseHeight const finalHeight = (pos.height || 0) + this.baseHeight; const position = Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, finalHeight); const marker = this.viewer.entities.add({ name: `Position Marker ${index}`, position: position, point: { color: this.markerStyle.color, pixelSize: this.markerStyle.pixelSize, outlineColor: this.markerStyle.outlineColor, outlineWidth: this.markerStyle.outlineWidth }, label: { text: pos.name || `Point ${index + 1}`, font: '14px sans-serif', pixelOffset: new Cesium.Cartesian2(0, -20), fillColor: Cesium.Color.WHITE } }); return marker; }); } /** * 创建模型上方的图标(保持不变) */ createIcon() { this.iconEntity = this.viewer.entities.add({ name: 'Model Icon', position: new Cesium.CallbackProperty(() => { return this.adjustCartesianHeight(this.currentPosition, 0.3); }, false), billboard: { image: this.iconUrl, width: this.iconSize, height: this.iconSize, verticalOrigin: Cesium.VerticalOrigin.BOTTOM }, label: { text: 'Model Icon', font: '14px sans-serif', pixelOffset: new Cesium.Cartesian2(0, -40), fillColor: Cesium.Color.WHITE } }); } /** * 调整笛卡尔坐标的高度(在原有高度基础上叠加指定值) * @param {Cesium.Cartesian3} cartesian - 原始笛卡尔坐标 * @param {Number} heightOffset - 要增加的高度值(米,可为负数) * @param {Cesium.Ellipsoid} [ellipsoid=Cesium.Ellipsoid.WGS84] - 参考椭球体 * @returns {Cesium.Cartesian3|null} 调整高度后的笛卡尔坐标(失败返回null) */ adjustCartesianHeight(cartesian, heightOffset, ellipsoid = Cesium.Ellipsoid.WGS84) { // 1. 校验输入的笛卡尔坐标是否有效 // if (!Cesium.Cartesian3.isValid(cartesian)) { // console.error("无效的笛卡尔坐标,无法调整高度"); // return null; // } // 2. 笛卡尔坐标 → Cartographic(地理坐标,含弧度经纬度和高度) const cartographic = Cesium.Cartographic.fromCartesian(cartesian, ellipsoid); if (!cartographic) { console.error('笛卡尔坐标转换为Cartographic失败'); return null; } // 3. 调整高度(在原有高度上叠加offset,可为负数) cartographic.height += heightOffset; // 可选:确保高度不低于0(根据需求决定是否保留) // cartographic.height = Math.max(cartographic.height, 0); // 4. Cartographic → 笛卡尔坐标(使用弧度经纬度转换) const adjustedCartesian = Cesium.Cartesian3.fromRadians( cartographic.longitude, // 经度(弧度) cartographic.latitude, // 纬度(弧度) cartographic.height, // 调整后的高度(米) ellipsoid ); return adjustedCartesian; } /** * 开始移动(保持不变) */ start() { if (this.isMoving) { console.log('模型已经在移动中'); return; } if (this.positions.length < 2) { console.error('无法移动:位置点数量不足(至少需要2个)'); return; } this.isMoving = true; this.lastTime = performance.now(); this.progress = 0; console.log('开始移动,速度:', this.speed, '米/秒'); this.animate(); } /** * 动画循环函数(保持不变) */ animate() { if (!this.isMoving) return; const currentTime = performance.now(); const timeDeltaMs = currentTime - this.lastTime; this.lastTime = currentTime; const timeDelta = timeDeltaMs / 1000; this.updatePosition(timeDelta); this.animationFrameId = requestAnimationFrame(() => this.animate()); } /** * 停止移动(保持不变) */ stop() { if (!this.isMoving) return; this.isMoving = false; console.log('停止移动'); if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; } } /** * 更新模型位置(修改:叠加baseHeight) */ updatePosition(timeDelta) { if (!this.isMoving) return; const distanceToMove = this.speed * timeDelta; const currentPos = this.positions[this.currentIndex]; const nextIndex = (this.currentIndex + 1) % this.positions.length; const nextPos = this.positions[nextIndex]; // 关键修改:计算当前点和下一点时,均叠加baseHeight const currentFinalHeight = (currentPos.height || 0) + this.baseHeight; const nextFinalHeight = (nextPos.height || 0) + this.baseHeight; const currentCartesian = Cesium.Cartesian3.fromDegrees(currentPos.lon, currentPos.lat, currentFinalHeight); const nextCartesian = Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, nextFinalHeight); // 后续逻辑保持不变 const totalDistance = Cesium.Cartesian3.distance(currentCartesian, nextCartesian); if (totalDistance < 0.001) { this.currentIndex = nextIndex; this.progress = 0; this.setModelPosition(nextCartesian, this.getNextPositionCartesian(nextIndex)); return; } this.progress += distanceToMove / totalDistance; if (this.progress >= 1.0) { const remainingDistance = (this.progress - 1.0) * totalDistance; this.currentIndex = nextIndex; if (!this.loop && this.currentIndex === this.positions.length - 1) { this.setModelPosition(nextCartesian, null); this.stop(); console.log('已到达终点,停止移动'); return; } const newTotalDistance = Cesium.Cartesian3.distance(nextCartesian, this.getNextPositionCartesian(this.currentIndex)); this.progress = newTotalDistance > 0.001 ? remainingDistance / newTotalDistance : 0; this.setModelPosition(nextCartesian, this.getNextPositionCartesian(this.currentIndex)); } else { const newPosition = Cesium.Cartesian3.lerp(currentCartesian, nextCartesian, this.progress, new Cesium.Cartesian3()); this.setModelPosition(newPosition, nextCartesian); } this.viewer.scene.requestRender(); } /** * 获取下一个位置的笛卡尔坐标(修改:叠加baseHeight) */ getNextPositionCartesian(currentIndex) { const nextIndex = (currentIndex + 1) % this.positions.length; const nextPos = this.positions[nextIndex]; // 关键修改:返回下一点时叠加baseHeight const finalHeight = (nextPos.height || 0) + this.baseHeight; return Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, finalHeight); } /** * 设置模型位置和方向(保持不变) */ setModelPosition(position, targetPosition) { if (!this.modelEntity) return; this.currentPosition = position; this.modelEntity.position.setValue(position); if (targetPosition) { const heading = this.calculateHeading(position, targetPosition); this.modelEntity.orientation.setValue(Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, 0, 0))); } } /** * 计算两点之间的朝向(保持不变) */ calculateHeading(from, to) { const fromCartographic = Cesium.Cartographic.fromCartesian(from); const toCartographic = Cesium.Cartographic.fromCartesian(to); const startLat = fromCartographic.latitude; const startLon = fromCartographic.longitude; const endLat = toCartographic.latitude; const endLon = toCartographic.longitude; const dLon = endLon - startLon; const y = Math.sin(dLon) * Math.cos(endLat); const x = Math.cos(startLat) * Math.sin(endLat) - Math.sin(startLat) * Math.cos(endLat) * Math.cos(dLon); let heading = Math.atan2(y, x); heading = (heading + Cesium.Math.TWO_PI) % Cesium.Math.TWO_PI; heading = (heading + this.headingOffset) % Cesium.Math.TWO_PI; return heading; } /** * 手动设置模型高度(保持不变) */ setModelHeight(height) { this.modelHeight = height; console.log('手动设置模型高度:', height, '米'); } /** * 设置航向角偏移(保持不变) */ setHeadingOffset(offsetDegrees) { this.headingOffset = Cesium.Math.toRadians(offsetDegrees); console.log('设置航向角偏移:', offsetDegrees, '度'); } /** * 销毁所有资源(保持不变) */ destroy() { this.stop(); if (this.modelEntity) this.viewer.entities.remove(this.modelEntity); if (this.pathEntity) this.viewer.entities.remove(this.pathEntity); this.markerEntities.forEach((marker) => this.viewer.entities.remove(marker)); if (this.iconEntity) this.viewer.entities.remove(this.iconEntity); this.modelEntity = null; this.pathEntity = null; this.markerEntities = []; this.iconEntity = null; } }