废话不多说,
上代码:
class EditTileset {
constructor(options) {
this.rotateEnabled = options.rotateEnabled !== undefined ? options.rotateEnabled : true; //控制旋转圆
this.scaleEnabled = options.scaleEnabled !== undefined ? options.scaleEnabled : true; //控制缩放平面
this.translateEnabled = options.translateEnabled !== undefined ? options.translateEnabled : true;//控制平移线
this.xAxisLength = options.xAxisLength || options.radius;
this.yAxisLength = options.yAxisLength || options.radius;
this.zAxisLength = options.zAxisLength || options.radius;
this.scaleAxisLength = options.scaleAxisLength || options.radius;
this.rotatePlaneRadius = options.rotatePlaneRadius || options.radius;
this.viewer = options.viewer || null;
this.tileset = null;
this.handler = null;
this._primitives = []; // 存储所有控制图元
this._activeAxis = null; // 当前激活的控制轴
this.modelMatrix = null; // 模型矩阵
this._mouseUpHandler = null; // 鼠标抬起事件处理器
this._lastPosition = null; // 上一次鼠标位置
this._inverseModelMatrix = new Cesium.Matrix4(); // 模型矩阵的逆矩阵
// 预创建常用的向量和矩阵对象,避免频繁创建
this._cartesian3_1 = new Cesium.Cartesian3();
this._cartesian3_2 = new Cesium.Cartesian3();
this._matrix4 = new Cesium.Matrix4();
this._quaternion = new Cesium.Quaternion();
this._matrix3 = new Cesium.Matrix3();
// 事件回调
this.postTransformEvent = {
_listeners: [],
addEventListener: function(callback) {
this._listeners.push(callback);
},
removeEventListener: function(callback) {
const index = this._listeners.indexOf(callback);
if (index !== -1) {
this._listeners.splice(index, 1);
}
},
fire: function(modelMatrix) {
this._listeners.forEach(callback => callback(modelMatrix));
}
};
}
/**
* 将辅助编辑器添加到场景中
* @param {*} viewer cesium实例
*/
addTo(viewer) {
this.viewer = viewer; // 保存viewer引用
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
return this;
}
/**
* 将编辑器绑定到tileset上
* @param {*} tileset 瓦片集
*/
bind(tileset) {
this.tileset = tileset;
this.modelMatrix = Cesium.Matrix4.clone(tileset.modelMatrix);
Cesium.Matrix4.inverse(this.modelMatrix, this._inverseModelMatrix);
this.addEventListener();
this.createPrimitive();
return this;
}
/**
* 绑定位置(用于点位编辑)
* @param {Cesium.Cartesian3} position 位置
*/
bindPosition(position) {
// 创建一个以该位置为中心的模型矩阵
this.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);
Cesium.Matrix4.inverse(this.modelMatrix, this._inverseModelMatrix);
this.addEventListener();
this.createPrimitive(false); // false表示不是模型,不需要旋转和缩放
return this;
}
/**
* 添加鼠标悬停事件
*/
addEventListener() {
// 鼠标按下
this.handler.setInputAction((e) => {
const pickResult = this.viewer.scene.pick(e.position);
if (pickResult && pickResult.id && this._primitives.includes(pickResult.id)) {
// 激活选中的控制器
this._activeAxis = pickResult.id;
this._lastPosition = e.position.clone(); // 使用clone确保不会被后续操作修改
// 禁用相机控制,以便拖动控制器时不会移动相机
this.viewer.scene.screenSpaceCameraController.enableRotate = false;
this.viewer.scene.screenSpaceCameraController.enableTranslate = false;
this.viewer.scene.screenSpaceCameraController.enableZoom = false;
// 改变鼠标样式为抓取状态
this.viewer.canvas.style.cursor = 'grabbing';
// 添加移动事件 - 使用requestAnimationFrame提高流畅度
let animationFrameId = null;
let lastMoveEvent = null;
const updateTransform = () => {
if (lastMoveEvent && this._lastPosition) {
this.transform(this._lastPosition, lastMoveEvent.endPosition);
this._lastPosition = lastMoveEvent.endPosition.clone();
lastMoveEvent = null;
}
if (this._activeAxis) {
animationFrameId = requestAnimationFrame(updateTransform);
}
};
this.handler.setInputAction((moveEvent) => {
lastMoveEvent = moveEvent;
if (!animationFrameId) {
animationFrameId = requestAnimationFrame(updateTransform);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 添加鼠标抬起事件
this._mouseUpHandler = this.handler.setInputAction(() => {
this._activeAxis = null;
this._lastPosition = null;
// 取消动画帧
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
// 重新启用相机控制
this.viewer.scene.screenSpaceCameraController.enableRotate = true;
this.viewer.scene.screenSpaceCameraController.enableTranslate = true;
this.viewer.scene.screenSpaceCameraController.enableZoom = true;
// 移除移动事件
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 重置鼠标样式
this.viewer.canvas.style.cursor = 'default';
// 触发变换完成事件
if (this.tileset) {
this.postTransformEvent.fire(this.tileset.modelMatrix);
} else {
this.postTransformEvent.fire(this.modelMatrix);
}
}, Cesium.ScreenSpaceEventType.LEFT_UP);
} else if (pickResult && pickResult.primitive && this._primitives.includes(pickResult.primitive)) {
// 激活选中的控制器
this._activeAxis = pickResult.primitive;
this._lastPosition = e.position.clone(); // 使用clone确保不会被后续操作修改
// 禁用相机控制,以便拖动控制器时不会移动相机
this.viewer.scene.screenSpaceCameraController.enableRotate = false;
this.viewer.scene.screenSpaceCameraController.enableTranslate = false;
this.viewer.scene.screenSpaceCameraController.enableZoom = false;
// 改变鼠标样式为抓取状态
this.viewer.canvas.style.cursor = 'grabbing';
// 添加移动事件 - 使用requestAnimationFrame提高流畅度
let animationFrameId = null;
let lastMoveEvent = null;
const updateTransform = () => {
if (lastMoveEvent && this._lastPosition) {
this.transform(this._lastPosition, lastMoveEvent.endPosition);
this._lastPosition = lastMoveEvent.endPosition.clone();
lastMoveEvent = null;
}
if (this._activeAxis) {
animationFrameId = requestAnimationFrame(updateTransform);
}
};
this.handler.setInputAction((moveEvent) => {
lastMoveEvent = moveEvent;
if (!animationFrameId) {
animationFrameId = requestAnimationFrame(updateTransform);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 添加鼠标抬起事件
this._mouseUpHandler = this.handler.setInputAction(() => {
this._activeAxis = null;
this._lastPosition = null;
// 取消动画帧
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
// 重新启用相机控制
this.viewer.scene.screenSpaceCameraController.enableRotate = true;
this.viewer.scene.screenSpaceCameraController.enableTranslate = true;
this.viewer.scene.screenSpaceCameraController.enableZoom = true;
// 移除移动事件
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 重置鼠标样式
this.viewer.canvas.style.cursor = 'default';
// 触发变换完成事件
if (this.tileset) {
this.postTransformEvent.fire(this.tileset.modelMatrix);
} else {
this.postTransformEvent.fire(this.modelMatrix);
}
}, Cesium.ScreenSpaceEventType.LEFT_UP);
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 添加鼠标悬停事件
this.handler.setInputAction((e) => {
// 如果正在拖拽中,不处理悬停事件
if (this._activeAxis) return;
const pickResult = this.viewer.scene.pick(e.endPosition);
// 重置所有控制器的样式
this._primitives.forEach(primitive => {
if (primitive instanceof Cesium.Entity) {
if (primitive.point) {
// 修复缩放控制点的大小
primitive.point.pixelSize = 18;
primitive.point.outlineWidth = 2;
} else if (primitive.polyline) {
// 重置线宽
primitive.polyline.width = 3;
}
} else if (primitive instanceof Cesium.Primitive) {
// 对于Primitive类型的控制器,无法直接修改属性
// 这里不做处理,在拾取时会特殊处理
}
});
// 如果鼠标悬停在控制器上,改变样式
if (pickResult && pickResult.id && this._primitives.includes(pickResult.id)) {
// 改变鼠标样式为小手
this.viewer.canvas.style.cursor = 'pointer';
// 放大当前悬停的控制器
if (pickResult.id.point) {
pickResult.id.point.pixelSize = 20;
pickResult.id.point.outlineWidth = 3;
} else if (pickResult.id.polyline) {
// 增加线宽
pickResult.id.polyline.width = 5;
}
} else if (pickResult && pickResult.primitive && this._primitives.includes(pickResult.primitive)) {
// 改变鼠标样式为小手
this.viewer.canvas.style.cursor = 'pointer';
} else {
// 恢复默认鼠标样式
this.viewer.canvas.style.cursor = 'default';
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
/**
* 鼠标移动
* @param {Cesium.Cartesian2} sP 开始位置
* @param {Cesium.Cartesian2} eP 结束位置
*/
transform(sP, eP) {
if (!this._activeAxis) return;
// 根据控制器类型处理变换
if (this._activeAxis.axisType === 'translate') {
this.handleTranslate(sP, eP);
} else if (this._activeAxis.axisType === 'rotate') {
this.handleRotate(sP, eP);
} else if (this._activeAxis.axisType === 'scale') {
this.handleScale(sP, eP);
}
// 更新控制器位置
this.updateControlsPosition();
}
/**
* 处理平移变换
* @param {Cesium.Cartesian2} sP 开始位置
* @param {Cesium.Cartesian2} eP 结束位置
*/
handleTranslate(sP, eP) {
// 获取中心点
const center = this.tileset ?
this.tileset.boundingSphere.center :
Cesium.Matrix4.getTranslation(this.modelMatrix, new Cesium.Cartesian3());
// 获取选中的轴向
const direction = this._activeAxis.direction;
// 获取屏幕上的中心点
const centerScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, center);
if (!centerScreen) return;
// 获取选中轴在世界坐标系中的方向向量
let axisDirection;
if (direction === 'x') {
axisDirection = Cesium.Matrix4.getColumn(this.tileset ? this.tileset.modelMatrix : this.modelMatrix, 0, new Cesium.Cartesian3());
} else if (direction === 'y') {
axisDirection = Cesium.Matrix4.getColumn(this.tileset ? this.tileset.modelMatrix : this.modelMatrix, 1, new Cesium.Cartesian3());
} else if (direction === 'z') {
axisDirection = Cesium.Matrix4.getColumn(this.tileset ? this.tileset.modelMatrix : this.modelMatrix, 2, new Cesium.Cartesian3());
}
Cesium.Cartesian3.normalize(axisDirection, axisDirection);
// 将轴方向投影到屏幕上
const axisEnd = Cesium.Cartesian3.add(center, Cesium.Cartesian3.multiplyByScalar(axisDirection, 100, new Cesium.Cartesian3()), new Cesium.Cartesian3());
const axisEndScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, axisEnd);
if (!axisEndScreen) return;
// 计算屏幕上的轴方向向量
const screenAxisDirection = {
x: axisEndScreen.x - centerScreen.x,
y: axisEndScreen.y - centerScreen.y
};
const screenAxisLength = Math.sqrt(screenAxisDirection.x * screenAxisDirection.x + screenAxisDirection.y * screenAxisDirection.y);
if (screenAxisLength < 0.001) return; // 轴在屏幕上投影太小,忽略
// 归一化屏幕轴方向
screenAxisDirection.x /= screenAxisLength;
screenAxisDirection.y /= screenAxisLength;
// 计算鼠标移动向量
const mouseVector = {
x: eP.x - sP.x,
y: eP.y - sP.y
};
// 计算鼠标移动在轴方向上的投影长度
const projectionLength = mouseVector.x * screenAxisDirection.x + mouseVector.y * screenAxisDirection.y;
// 根据投影比例和轴方向计算世界坐标系中的偏移量
let moveScale = -projectionLength * 0.3; // 默认方向相反
// 特殊处理Z轴(蓝色轴),使其方向与鼠标上下移动一致
if (direction === 'z') {
moveScale = projectionLength * 0.3;
}
const worldDelta = Cesium.Cartesian3.multiplyByScalar(
axisDirection,
moveScale,
new Cesium.Cartesian3()
);
// 应用平移
this.translate(worldDelta.x, worldDelta.y, worldDelta.z);
}
/**
* 处理旋转变换
* @param {Cesium.Cartesian2} sP 开始位置
* @param {Cesium.Cartesian2} eP 结束位置
*/
handleRotate(sP, eP) {
// 获取模型中心点
const center = this.tileset ? this.tileset.boundingSphere.center : Cesium.Matrix4.getTranslation(this.modelMatrix, new Cesium.Cartesian3());
// 获取屏幕上的中心点
const centerScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, center);
if (!centerScreen) return;
// 计算开始和结束点相对于中心的向量
const startVector = {
x: sP.x - centerScreen.x,
y: sP.y - centerScreen.y
};
const endVector = {
x: eP.x - centerScreen.x,
y: eP.y - centerScreen.y
};
// 计算旋转角度 (弧度)
const startAngle = Math.atan2(startVector.y, startVector.x);
const endAngle = Math.atan2(endVector.y, endVector.x);
// let angle = endAngle - startAngle; // 移除负号,使旋转方向与鼠标一致
let angle = startAngle - endAngle; // 移除负号,使旋转方向与鼠标一致
// 确定旋转轴
const direction = this._activeAxis.direction;
let rx = 0, ry = 0, rz = 0;
// 获取旋转轴在世界坐标系中的方向
let rotationAxis;
if (direction === 'x') {
rotationAxis = Cesium.Matrix4.getColumn(this.tileset.modelMatrix, 0, this._cartesian3_2);
rx = Cesium.Math.toDegrees(angle);
} else if (direction === 'y') {
rotationAxis = Cesium.Matrix4.getColumn(this.tileset.modelMatrix, 1, this._cartesian3_2);
ry = Cesium.Math.toDegrees(angle);
} else if (direction === 'z') {
rotationAxis = Cesium.Matrix4.getColumn(this.tileset.modelMatrix, 2, this._cartesian3_2);
rz = Cesium.Math.toDegrees(angle);
}
// 根据相机位置调整旋转方向
const cameraPosition = this.viewer.camera.position;
const toCamera = Cesium.Cartesian3.subtract(cameraPosition, center, this._cartesian3_1);
Cesium.Cartesian3.normalize(toCamera, toCamera);
// 如果轴指向相机,需要反转旋转方向
if (Cesium.Cartesian3.dot(rotationAxis, toCamera) > 0) { // 修改判断条件,改变旋转方向的逻辑
rx = -rx;
ry = -ry;
rz = -rz;
}
// 应用旋转
this.rotate(rx, ry, rz);
}
/**
* 处理缩放变换
* @param {Cesium.Cartesian2} sP 开始位置
* @param {Cesium.Cartesian2} eP 结束位置
*/
handleScale(sP, eP) {
if (!this.tileset) return; // 点位不支持缩放
// 获取模型中心点
const center = this.tileset.boundingSphere.center;
// 获取选中的轴向
const direction = this._activeAxis.direction;
// 获取屏幕上的中心点
const centerScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, center);
if (!centerScreen) return;
// 获取选中轴在模型坐标系中的方向向量
let axisDirection;
if (direction === 'x') {
axisDirection = Cesium.Matrix4.getColumn(this.tileset.modelMatrix, 0, new Cesium.Cartesian3());
} else if (direction === 'y') {
axisDirection = Cesium.Matrix4.getColumn(this.tileset.modelMatrix, 1, new Cesium.Cartesian3());
} else if (direction === 'z') {
axisDirection = Cesium.Matrix4.getColumn(this.tileset.modelMatrix, 2, new Cesium.Cartesian3());
}
Cesium.Cartesian3.normalize(axisDirection, axisDirection);
// 将轴方向投影到屏幕上
const axisEnd = Cesium.Cartesian3.add(center, Cesium.Cartesian3.multiplyByScalar(axisDirection, 100, new Cesium.Cartesian3()), new Cesium.Cartesian3());
const axisEndScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, axisEnd);
if (!axisEndScreen) return;
// 计算屏幕上的轴方向向量
const screenAxisDirection = {
x: axisEndScreen.x - centerScreen.x,
y: axisEndScreen.y - centerScreen.y
};
const screenAxisLength = Math.sqrt(screenAxisDirection.x * screenAxisDirection.x + screenAxisDirection.y * screenAxisDirection.y);
if (screenAxisLength < 0.001) return; // 轴在屏幕上投影太小,忽略
// 归一化屏幕轴方向
screenAxisDirection.x /= screenAxisLength;
screenAxisDirection.y /= screenAxisLength;
// 计算鼠标移动向量
const mouseVector = {
x: eP.x - sP.x,
y: eP.y - sP.y
};
// 计算鼠标移动在轴方向上的投影长度
const projectionLength = mouseVector.x * screenAxisDirection.x + mouseVector.y * screenAxisDirection.y;
// 计算缩放因子 - 基于投影长度的相对变化
let scaleFactor;
if (direction === 'z') {
// Z轴特殊处理:向上移动放大,向下移动缩小
scaleFactor = 1.0 + projectionLength / 100.0;
} else {
// X轴和Y轴保持相反方向
scaleFactor = 1.0 - projectionLength / 100.0;
}
// 处理特殊情况
if (isNaN(scaleFactor) || !isFinite(scaleFactor) || Math.abs(scaleFactor - 1.0) < 0.001) {
return;
}
// 限制缩放因子范围
const limitedScaleFactor = Math.max(0.5, Math.min(2.0, scaleFactor));
// 根据轴向应用缩放
let sx = 1, sy = 1, sz = 1;
if (direction === 'x') {
sx = limitedScaleFactor;
} else if (direction === 'y') {
sy = limitedScaleFactor;
} else if (direction === 'z') {
sz = limitedScaleFactor;
}
// 应用缩放
this.scale(sx, sy, sz);
}
/**
* 更新控制器位置
*/
updateControlsPosition() {
// 移除旧的控制器
this._primitives.forEach(primitive => {
if (primitive instanceof Cesium.Entity) {
this.viewer.entities.remove(primitive);
} else {
this.viewer.scene.primitives.remove(primitive);
}
});
this._primitives = [];
// 重新创建控制器
this.createPrimitive(this.tileset !== null);
}
/**
* 创建所有控制图元
* @param {boolean} isModel 是否是模型(true为模型,false为点位)
*/
createPrimitive(isModel = true) {
// 获取中心点
const center = this.tileset ?
this.tileset.boundingSphere.center :
Cesium.Matrix4.getTranslation(this.modelMatrix, new Cesium.Cartesian3());
// 创建ENU坐标系
const enuMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
// 创建平移轴
if (this.translateEnabled) {
this.createTranslateAxis(center, enuMatrix);
}
// 创建旋转环
if (isModel && this.rotateEnabled) {
this.createRotateAxis(center, enuMatrix);
}
// 创建缩放控制器
if (isModel && this.scaleEnabled) {
this.createScaleAxis(center, enuMatrix);
}
}
/**
* 创建平移轴
* @param {Cesium.Cartesian3} center 中心点
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
*/
createTranslateAxis(center, enuMatrix) {
// X轴 - 红色
const xAxis = this.createAxis(
center,
new Cesium.Cartesian3(this.xAxisLength, 0, 0),
Cesium.Color.RED,
enuMatrix
);
xAxis.axisType = 'translate';
xAxis.direction = 'x';
xAxis.directionVector = new Cesium.Cartesian3(1, 0, 0);
// Y轴 - 绿色
const yAxis = this.createAxis(
center,
new Cesium.Cartesian3(0, this.yAxisLength, 0),
Cesium.Color.GREEN,
enuMatrix
);
yAxis.axisType = 'translate';
yAxis.direction = 'y';
yAxis.directionVector = new Cesium.Cartesian3(0, 1, 0);
// Z轴 - 蓝色
const zAxis = this.createAxis(
center,
new Cesium.Cartesian3(0, 0, this.zAxisLength),
Cesium.Color.BLUE,
enuMatrix
);
zAxis.axisType = 'translate';
zAxis.direction = 'z';
zAxis.directionVector = new Cesium.Cartesian3(0, 0, 1);
}
/**
* 创建轴线
* @param {Cesium.Cartesian3} center 中心点
* @param {Cesium.Cartesian3} direction 方向向量
* @param {Cesium.Color} color 颜色
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
* @returns {Cesium.Entity} 轴线实体
*/
createAxis(center, direction, color, enuMatrix) {
// 计算轴线终点
const endPoint = Cesium.Matrix4.multiplyByPoint(
enuMatrix,
direction,
new Cesium.Cartesian3()
);
// 创建轴线实体
const entity = this.viewer.entities.add({
polyline: {
positions: [center, endPoint],
width: 3, // 默认线宽改小一点
material: color,
clampToGround: false,
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
// 确保控制器始终可见
disableDepthTestDistance: Number.POSITIVE_INFINITY
});
this._primitives.push(entity);
return entity;
}
/**
* 创建旋转轴
* @param {Cesium.Cartesian3} center 中心点
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
*/
createRotateAxis(center, enuMatrix) {
// X轴旋转环 - 红色
this.createRotationRing(center, 'x', Cesium.Color.RED, enuMatrix);
// Y轴旋转环 - 绿色
this.createRotationRing(center, 'y', Cesium.Color.GREEN, enuMatrix);
// Z轴旋转环 - 蓝色
this.createRotationRing(center, 'z', Cesium.Color.BLUE, enuMatrix);
}
/**
* 创建缩放控制器
* @param {Cesium.Cartesian3} center 中心点
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
*/
createScaleAxis(center, enuMatrix) {
// 创建轴端点的缩放控制点
const axisLength = this.scaleAxisLength * 0.8; // 缩放控制点距离中心的距离
// X轴端点 - 红色
this.createScalePoint(center, 'x', new Cesium.Cartesian3(axisLength, 0, 0), Cesium.Color.RED, enuMatrix);
// Y轴端点 - 绿色
this.createScalePoint(center, 'y', new Cesium.Cartesian3(0, axisLength, 0), Cesium.Color.GREEN, enuMatrix);
// Z轴端点 - 蓝色
this.createScalePoint(center, 'z', new Cesium.Cartesian3(0, 0, axisLength), Cesium.Color.BLUE, enuMatrix);
}
/**
* 创建缩放控制点
* @param {Cesium.Cartesian3} center 中心点
* @param {string} axis 轴向 ('x', 'y', 'z', 'xyz')
* @param {Cesium.Cartesian3} offset 偏移量
* @param {Cesium.Color} color 颜色
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
*/
createScalePoint(center, axis, offset, color, enuMatrix) {
// 计算控制点位置
const pointPosition = Cesium.Matrix4.multiplyByPoint(
enuMatrix,
offset,
new Cesium.Cartesian3()
);
// 创建点实体
const entity = this.viewer.entities.add({
position: pointPosition,
point: {
color: color,
pixelSize: 18,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
// 确保控制器始终可见
disableDepthTestDistance: Number.POSITIVE_INFINITY
});
// 设置属性
entity.axisType = 'scale';
entity.direction = axis;
// 设置方向向量和法向量
if (axis === 'x') {
entity.directionVector = new Cesium.Cartesian3(1, 0, 0);
entity.normal = new Cesium.Cartesian3(1, 0, 0);
} else if (axis === 'y') {
entity.directionVector = new Cesium.Cartesian3(0, 1, 0);
entity.normal = new Cesium.Cartesian3(0, 1, 0);
} else if (axis === 'z') {
entity.directionVector = new Cesium.Cartesian3(0, 0, 1);
entity.normal = new Cesium.Cartesian3(0, 0, 1);
} else if (axis === 'xyz') {
entity.directionVector = new Cesium.Cartesian3(1, 1, 1);
entity.normal = new Cesium.Cartesian3(1, 1, 1);
}
// 添加到控制器列表
this._primitives.push(entity);
return entity;
}
/**
* 创建辅助编辑器的平移轴向
* @param {Cesium.Cartesian3} center 中心点
* @param {Cesium.Cartesian3} direction 方向
* @param {Cesium.Color} color 颜色
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
* @returns {Cesium.Primitive} 创建的图元
*/
createAxis(center, direction, color, enuMatrix) {
// 计算轴的起点和终点
const start = center;
const end = Cesium.Matrix4.multiplyByPoint(
enuMatrix,
direction,
new Cesium.Cartesian3()
);
// 创建线段几何体
const instance = new Cesium.GeometryInstance({
geometry: new Cesium.PolylineGeometry({
positions: [start, end],
width: 5.0,
vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color),
// 使用相同颜色,确保被遮挡时显示一致
depthFailColor: Cesium.ColorGeometryInstanceAttribute.fromColor(color)
},
id: `axis-${color.toString()}`
});
// 创建图元
const primitive = new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PolylineColorAppearance({
translucent: false
}),
allowPicking: true,
// 确保控制器始终可见,不被3DTiles遮挡
depthFailAppearance: new Cesium.PolylineColorAppearance({
translucent: false
}),
// 增加拾取宽度,使轴更容易被选中
asynchronous: false,
// 增加拾取优先级
pickPrimitive: true
});
// 添加到场景
this.viewer.scene.primitives.add(primitive);
this._primitives.push(primitive);
return primitive;
}
/**
* 创建旋转环
* @param {Cesium.Cartesian3} center 中心点
* @param {string} axis 旋转轴 ('x', 'y', 'z')
* @param {Cesium.Color} color 颜色
* @param {Cesium.Matrix4} enuMatrix ENU坐标系矩阵
*/
createRotationRing(center, axis, color, enuMatrix) {
// 创建圆环点集
const positions = [];
const segments = 60;
const radius = this.rotatePlaneRadius;
for (let i = 0; i <= segments; i++) {
const theta = (i / segments) * Cesium.Math.TWO_PI;
const x = radius * Math.cos(theta);
const y = radius * Math.sin(theta);
let position;
if (axis === 'x') {
position = new Cesium.Cartesian3(0, y, x);
} else if (axis === 'y') {
position = new Cesium.Cartesian3(x, 0, y);
} else { // z
position = new Cesium.Cartesian3(x, y, 0);
}
// 转换到世界坐标
const worldPos = Cesium.Matrix4.multiplyByPoint(
enuMatrix,
position,
new Cesium.Cartesian3()
);
positions.push(worldPos);
}
// 创建线段几何体
const instance = new Cesium.GeometryInstance({
geometry: new Cesium.PolylineGeometry({
positions: positions,
width: 5.0, // 增加线宽,使旋转环更粗
vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color),
// 使用相同颜色,确保被遮挡时显示一致
depthFailColor: Cesium.ColorGeometryInstanceAttribute.fromColor(color)
},
id: `rotate-${axis}`
});
// 创建图元
const primitive = new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PolylineColorAppearance({
translucent: false
}),
allowPicking: true,
// 确保控制器始终可见,不被3DTiles遮挡
depthFailAppearance: new Cesium.PolylineColorAppearance({
translucent: false
}),
// 增加拾取宽度,使旋转环更容易被选中
asynchronous: false,
// 增加拾取优先级
pickPrimitive: true
});
// 设置属性
primitive.axisType = 'rotate';
primitive.direction = axis;
// 设置方向向量
if (axis === 'x') {
primitive.directionVector = new Cesium.Cartesian3(1, 0, 0);
} else if (axis === 'y') {
primitive.directionVector = new Cesium.Cartesian3(0, 1, 0);
} else { // z
primitive.directionVector = new Cesium.Cartesian3(0, 0, 1);
}
// 添加到场景
this.viewer.scene.primitives.add(primitive);
this._primitives.push(primitive);
return primitive;
}
/**
* 平移模型
* @param {number} dx x轴偏移量。单位:米
* @param {number} dy y轴偏移量。单位:米
* @param {number} dz z轴偏移量。单位:米
*/
translate(dx, dy, dz) {
if (dx === 0 && dy === 0 && dz === 0) return;
// 获取中心点
const origin = this.tileset ?
this.tileset.boundingSphere.center :
Cesium.Matrix4.getTranslation(this.modelMatrix, new Cesium.Cartesian3());
// 以该点建立ENU坐标系
const toWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
// 该坐标系下平移后的位置
const translatePosition = new Cesium.Cartesian3(dx, dy, dz);
// 获取平移后位置的世界坐标
const worldPosition = Cesium.Matrix4.multiplyByPoint(
toWorldMatrix,
translatePosition,
new Cesium.Cartesian3()
);
// 计算世界坐标下的各个平移量
const offset = Cesium.Cartesian3.subtract(
worldPosition,
origin,
new Cesium.Cartesian3()
);
// 从世界坐标下的平移量计算世界坐标的平移矩阵
const translateMatrix = Cesium.Matrix4.fromTranslation(offset);
// 应用平移矩阵
if (this.tileset) {
this.tileset.modelMatrix = Cesium.Matrix4.multiply(
translateMatrix,
this.tileset.modelMatrix,
new Cesium.Matrix4()
);
} else {
this.modelMatrix = Cesium.Matrix4.multiply(
translateMatrix,
this.modelMatrix,
new Cesium.Matrix4()
);
}
// 触发变换事件
this.postTransformEvent.fire(this.tileset ? this.tileset.modelMatrix : this.modelMatrix);
}
/**
* 缩放模型
* @param {number} sx x轴缩放倍数
* @param {number} sy y轴缩放倍数
* @param {number} sz z轴缩放倍数
*/
scale(sx, sy, sz) {
if (!this.tileset) return; // 点位不支持缩放
if (sx <= 0 || sy <= 0 || sz <= 0) {
console.error('缩放倍数必须大于0');
return;
}
if (sx === 1 && sy === 1 && sz === 1) return;
// 获取中心点
const origin = this.tileset.boundingSphere.center;
// 以该点建立ENU坐标系
const toWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
// 获取ENU矩阵的逆矩阵
const toLocalMatrix = Cesium.Matrix4.inverse(toWorldMatrix, new Cesium.Matrix4());
// 计算缩放矩阵
const scaleMatrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(sx, sy, sz));
// ENU坐标系下的结果矩阵
const localResultMatrix = Cesium.Matrix4.multiply(
scaleMatrix,
toLocalMatrix,
new Cesium.Matrix4()
);
// 世界坐标系下的结果矩阵
const worldResultMatrix = Cesium.Matrix4.multiply(
toWorldMatrix,
localResultMatrix,
new Cesium.Matrix4()
);
// 应用结果
this.tileset.modelMatrix = Cesium.Matrix4.multiply(
worldResultMatrix,
this.tileset.modelMatrix,
new Cesium.Matrix4()
);
// 触发变换事件
this.postTransformEvent.fire(this.tileset.modelMatrix);
}
/**
* 旋转模型
* @param {number} rx 绕X轴旋转的角度。单位:度
* @param {number} ry 绕Y轴旋转的角度。单位:度
* @param {number} rz 绕Z轴旋转的角度。单位:度
*/
rotate(rx, ry, rz) {
if (!this.tileset) return; // 点位不支持旋转
if (rx === 0 && ry === 0 && rz === 0) return;
// 获取中心点
const origin = this.tileset.boundingSphere.center;
// 以该点建立ENU坐标系
const toWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
// 获取ENU矩阵的逆矩阵
const toLocalMatrix = Cesium.Matrix4.inverse(toWorldMatrix, new Cesium.Matrix4());
// 计算旋转矩阵
const rotateMatrix = Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY);
if (rx !== 0) {
const rotateXMatrix = Cesium.Matrix4.fromRotation(
Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(rx))
);
Cesium.Matrix4.multiply(rotateXMatrix, rotateMatrix, rotateMatrix);
}
if (ry !== 0) {
const rotateYMatrix = Cesium.Matrix4.fromRotation(
Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(ry))
);
Cesium.Matrix4.multiply(rotateYMatrix, rotateMatrix, rotateMatrix);
}
if (rz !== 0) {
const rotateZMatrix = Cesium.Matrix4.fromRotation(
Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(rz))
);
Cesium.Matrix4.multiply(rotateZMatrix, rotateMatrix, rotateMatrix);
}
// ENU坐标系下的结果矩阵
const localResultMatrix = Cesium.Matrix4.multiply(
rotateMatrix,
toLocalMatrix,
new Cesium.Matrix4()
);
// 世界坐标系下的结果矩阵
const worldResultMatrix = Cesium.Matrix4.multiply(
toWorldMatrix,
localResultMatrix,
new Cesium.Matrix4()
);
// 应用结果
this.tileset.modelMatrix = Cesium.Matrix4.multiply(
worldResultMatrix,
this.tileset.modelMatrix,
new Cesium.Matrix4()
);
// 触发变换事件
this.postTransformEvent.fire(this.tileset.modelMatrix);
}
/**
* 获取鼠标在场景中的世界坐标
* @param {Cesium.Cartesian2} screenPos 屏幕坐标
* @returns {Cesium.Cartesian3} 返回世界坐标
*/
getWorldPosition(screenPos) {
const ray = this.viewer.camera.getPickRay(screenPos);
if (!ray) return null;
const result = this.viewer.scene.globe.pick(ray, this.viewer.scene);
if (!result) {
// 如果没有与地球相交,则使用射线与平面相交的方法
const center = this.tileset ?
this.tileset.boundingSphere.center :
Cesium.Matrix4.getTranslation(this.modelMatrix, new Cesium.Cartesian3());
const plane = Cesium.Plane.fromPointNormal(
center,
this.viewer.camera.direction,
new Cesium.Plane()
);
return Cesium.IntersectionTests.rayPlane(ray, plane);
}
return result;
}
/**
* 获取射线与平面的交点
* @param {Cesium.Cartesian2} pixel 屏幕坐标
* @param {Cesium.Cartesian3} planeCenter 平面中心点
* @param {Cesium.Cartesian3} planeNormal 平面法向量
* @returns {Cesium.Cartesian3} 交点
*/
getPositionInPlane(pixel, planeCenter, planeNormal) {
// 获取射线
const ray = this.viewer.camera.getPickRay(pixel);
if (!ray) return null;
// 创建平面
const plane = Cesium.Plane.fromPointNormal(planeCenter, planeNormal, new Cesium.Plane());
// 计算交点
return Cesium.IntersectionTests.rayPlane(ray, plane);
}
/**
* 销毁辅助编辑器
*/
destroyed() {
// 移除所有控制图元
this._primitives.forEach(primitive => {
if (primitive instanceof Cesium.Entity) {
this.viewer.entities.remove(primitive);
} else {
this.viewer.scene.primitives.remove(primitive);
}
});
this._primitives = [];
// 销毁事件处理器
if (this.handler) {
this.handler.destroy();
this.handler = null;
}
// 重置状态
this._activeAxis = null;
this.tileset = null;
this.modelMatrix = null;
}
}
使用:
tileset = new EditTileset({
viewer: viewer,
translateEnabled: true,
rotateEnabled: true,
scaleEnabled: true,
radius: pick.primitive.boundingSphere.radius / 3
})
tileset.addTo(viewer)
tileset.bind(pick.primitive)
难点:矩阵的变换。轴控制平移,环控制旋转,点控制缩放