393 lines
13 KiB
JavaScript
393 lines
13 KiB
JavaScript
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;
|
||
}
|
||
}
|