| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- class ModelEditor {
- constructor(viewer, entity) {
- this.viewer = viewer;
- this.entity = entity;
- this.handler = null;
- this.billboardCollection = null;
- this.polylineCollection = null;
- this.hoverBillboard = null;
- this.isDragging = false;
- this.dragType = null;
- this.dragAxis = null;
- this.startMousePos = null;
- this.startScreenPos = null;
- this.startWorldPos = null;
- this.dragWorldAxis = null;
- this.startModelMatrix = null;
- this.startCenter = null;
- this.center = null;
- this.radius = 2;
- this.init();
- }
- init() {
- this.cleanup();
- const scene = this.viewer.scene;
- const canvas = scene.canvas;
- this.handler = new Cesium.ScreenSpaceEventHandler(canvas);
- this.billboardCollection = scene.primitives.add(new Cesium.BillboardCollection());
- this.polylineCollection = scene.primitives.add(new Cesium.PolylineCollection());
- this.center = this.entity.position.getValue(this.viewer.clock.currentTime);
- this.createAllControls();
- this.setupEvents();
- }
- createAllControls() {
- const axisLength = this.radius * 0.8;
- const rotateRadius = this.radius;
- const axes = [
- { key: 'X', color: Cesium.Color.RED, dir: new Cesium.Cartesian3(1, 0, 0) },
- { key: 'Y', color: Cesium.Color.GREEN, dir: new Cesium.Cartesian3(0, 1, 0) },
- { key: 'Z', color: Cesium.Color.BLUE, dir: new Cesium.Cartesian3(0, 0, 1) },
- ];
- // 平移轴
- axes.forEach(({ key, color, dir }) => {
- const end = this.localToWorld(new Cesium.Cartesian3(dir.x * axisLength, dir.y * axisLength, dir.z * axisLength));
- this.billboardCollection.add({
- position: end,
- image: this.makeArrow(color),
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
- id: { type: 'translate', axis: key }
- });
- this.polylineCollection.add({
- positions: [this.center, end],
- width: 3,
- material: Cesium.Material.fromType('Color', { color })
- });
- });
- // 旋转圆环
- axes.forEach(({ key, color }) => {
- const points = [];
- for (let i = 0; i <= 64; i++) {
- const ang = i / 64 * Math.PI * 2;
- let p;
- if (key === 'X') p = new Cesium.Cartesian3(0, Math.cos(ang) * rotateRadius, Math.sin(ang) * rotateRadius);
- else if (key === 'Y') p = new Cesium.Cartesian3(Math.cos(ang) * rotateRadius, 0, Math.sin(ang) * rotateRadius);
- else p = new Cesium.Cartesian3(Math.cos(ang) * rotateRadius, Math.sin(ang) * rotateRadius, 0);
- points.push(this.localToWorld(p));
- }
- this.polylineCollection.add({
- positions: points,
- width: 2,
- material: Cesium.Material.fromType('Color', { color })
- });
- let rotPos;
- if (key === 'X') rotPos = this.localToWorld(new Cesium.Cartesian3(0, rotateRadius, 0));
- else if (key === 'Y') rotPos = this.localToWorld(new Cesium.Cartesian3(rotateRadius, 0, 0));
- else rotPos = this.localToWorld(new Cesium.Cartesian3(rotateRadius, 0, 0));
- this.billboardCollection.add({
- position: rotPos,
- image: this.makeRing(color),
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
- id: { type: 'rotate', axis: key }
- });
- });
- // 缩放方块
- axes.forEach(({ key, color, dir }) => {
- const end = this.localToWorld(new Cesium.Cartesian3(dir.x * axisLength, dir.y * axisLength, dir.z * axisLength));
- this.billboardCollection.add({
- position: end,
- image: this.makeBox(color),
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
- id: { type: 'scale', axis: key }
- });
- });
- }
- setupEvents() {
- // 鼠标移动 - 悬停
- this.handler.setInputAction((e) => {
- if (this.isDragging) return;
- const picked = this.pick(e.endPosition);
- if (picked !== this.hoverBillboard) {
- if (this.hoverBillboard) {
- this.hoverBillboard.scale = 1.0;
- this.hoverBillboard.color = Cesium.Color.WHITE;
- }
- this.hoverBillboard = picked;
- if (this.hoverBillboard) {
- this.hoverBillboard.scale = 1.5;
- this.hoverBillboard.color = Cesium.Color.YELLOW;
- }
- }
- this.viewer.canvas.style.cursor = picked ? 'pointer' : 'default';
- }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
- // 左键按下
- this.handler.setInputAction((e) => {
- const bb = this.pick(e.position);
- if (!bb) return;
- this.isDragging = true;
- this.dragType = bb.id.type;
- this.dragAxis = bb.id.axis;
- this.startMousePos = { x: e.position.x, y: e.position.y };
- this.startScreenPos = { x: e.position.x, y: e.position.y };
-
- const localAxis = this.getAxis(this.dragAxis);
- this.dragWorldAxis = this.localToWorld(localAxis);
- Cesium.Cartesian3.normalize(this.dragWorldAxis, this.dragWorldAxis);
-
- this.startCenter = this.entity.position.getValue ? this.entity.position.getValue(this.viewer.clock.currentTime) : this.entity.position;
- this.center = Cesium.Cartesian3.clone(this.startCenter);
- if (this.entity.model) {
- this.startModelMatrix = Cesium.Matrix4.clone(this.entity.model.modelMatrix);
- } else {
- this.startModelMatrix = Cesium.Matrix4.IDENTITY.clone();
- }
- this.viewer.scene.screenSpaceCameraController.enableInputs = false;
- }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
- // 拖拽
- this.handler.setInputAction((e) => {
- if (!this.isDragging || !this.startScreenPos) return;
-
- const dx = e.endPosition.x - this.startScreenPos.x;
- const dy = e.endPosition.y - this.startScreenPos.y;
-
- if (this.dragType === 'rotate') {
- const rotateDx = e.endPosition.x - this.startMousePos.x;
- this.doRotate(rotateDx);
- this.startMousePos = { x: e.endPosition.x, y: e.endPosition.y };
- } else {
- this.startScreenPos = { x: e.endPosition.x, y: e.endPosition.y };
- if (this.dragType === 'translate') this.doTranslate(dx, dy);
- if (this.dragType === 'scale') this.doScale(dx, dy);
- }
- }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
- // 左键抬起
- this.handler.setInputAction(() => {
- this.isDragging = false;
- this.dragType = null;
- this.startScreenPos = null;
- this.dragWorldAxis = null;
- this.viewer.scene.screenSpaceCameraController.enableInputs = true;
- }, Cesium.ScreenSpaceEventType.LEFT_UP);
- }
- // 平移
- doTranslate(dx, dy) {
- const camera = this.viewer.camera;
- const center = this.entity.position.getValue ? this.entity.position.getValue(this.viewer.clock.currentTime) : this.entity.position;
- const dist = Cesium.Cartesian3.distance(camera.position, center);
- const sensitivity = dist * 0.002;
-
- const right = camera.right;
- const up = camera.up;
-
- const mat = Cesium.Transforms.eastNorthUpToFixedFrame(center);
- const xAxis = Cesium.Matrix4.multiplyByPoint(mat, new Cesium.Cartesian3(1, 0, 0), new Cesium.Cartesian3());
- const yAxis = Cesium.Matrix4.multiplyByPoint(mat, new Cesium.Cartesian3(0, 1, 0), new Cesium.Cartesian3());
- const zAxis = Cesium.Matrix4.multiplyByPoint(mat, new Cesium.Cartesian3(0, 0, 1), new Cesium.Cartesian3());
- Cesium.Cartesian3.subtract(xAxis, center, xAxis);
- Cesium.Cartesian3.subtract(yAxis, center, yAxis);
- Cesium.Cartesian3.subtract(zAxis, center, zAxis);
-
- let moveDir;
- if (this.dragAxis === 'X') {
- moveDir = xAxis;
- } else if (this.dragAxis === 'Y') {
- moveDir = yAxis;
- } else {
- moveDir = zAxis;
- }
- Cesium.Cartesian3.normalize(moveDir, moveDir);
-
- const screenDirX = right.x * dx + up.x * (-dy);
- const screenDirY = right.y * dx + up.y * (-dy);
- const screenDirZ = right.z * dx + up.z * (-dy);
- const screenMove = new Cesium.Cartesian3(screenDirX, screenDirY, screenDirZ);
- const dot = Cesium.Cartesian3.dot(screenMove, moveDir);
-
- const moveAmount = dot * sensitivity;
- const offset = Cesium.Cartesian3.multiplyByScalar(moveDir, moveAmount, new Cesium.Cartesian3());
-
- this.center = Cesium.Cartesian3.add(this.startCenter, offset, new Cesium.Cartesian3());
- this.entity.position = Cesium.Cartesian3.clone(this.center);
- this.startCenter = Cesium.Cartesian3.clone(this.center);
-
- this.updateControls();
- this.viewer.scene.requestRender();
- }
-
- updateControls() {
- if (this.billboardCollection) {
- this.viewer.scene.primitives.remove(this.billboardCollection);
- this.viewer.scene.primitives.remove(this.polylineCollection);
- this.billboardCollection = this.viewer.scene.primitives.add(new Cesium.BillboardCollection());
- this.polylineCollection = this.viewer.scene.primitives.add(new Cesium.PolylineCollection());
- this.createAllControls();
- }
- }
- // 缩放
- doScale(dx, dy) {
- const scaleDelta = (dx + dy) * 0.01;
- const scale = Math.max(0.1, 1 + scaleDelta);
-
- const modelMatrix = this.entity.model.modelMatrix;
- const scaleVec = new Cesium.Cartesian3(1, 1, 1);
- if (this.dragAxis === 'X') scaleVec.x = scale;
- if (this.dragAxis === 'Y') scaleVec.y = scale;
- if (this.dragAxis === 'Z') scaleVec.z = scale;
-
- const currentScale = Cesium.Matrix4.getScale(modelMatrix, new Cesium.Cartesian3());
- const newScale = new Cesium.Cartesian3(
- currentScale.x * scaleVec.x,
- currentScale.y * scaleVec.y,
- currentScale.z * scaleVec.z
- );
-
- const translation = Cesium.Matrix4.getTranslation(modelMatrix, new Cesium.Cartesian3());
- const rotation = Cesium.Matrix4.getRotation(modelMatrix, new Cesium.Matrix3());
-
- const newModelMatrix = Cesium.Matrix4.fromTranslationRotationScale(translation, rotation, newScale);
- this.entity.model.modelMatrix = newModelMatrix;
- this.viewer.scene.requestRender();
- }
- // 旋转
- doRotate(dx) {
- if (!this.entity.model) return;
-
- const center = this.entity.position.getValue ? this.entity.position.getValue(this.viewer.clock.currentTime) : this.entity.position;
- const angleSensitivity = 0.005;
- const angle = dx * angleSensitivity;
-
- let rotateAxis;
- if (this.dragAxis === 'X') {
- rotateAxis = new Cesium.Cartesian3(1, 0, 0);
- } else if (this.dragAxis === 'Y') {
- rotateAxis = new Cesium.Cartesian3(0, 1, 0);
- } else {
- rotateAxis = new Cesium.Cartesian3(0, 0, 1);
- }
-
- const mat = Cesium.Transforms.eastNorthUpToFixedFrame(center);
- const worldAxis = Cesium.Matrix4.multiplyByPoint(mat, rotateAxis, new Cesium.Cartesian3());
- Cesium.Cartesian3.subtract(worldAxis, center, worldAxis);
- Cesium.Cartesian3.normalize(worldAxis, worldAxis);
-
- const quat = Cesium.Quaternion.fromAxisAngle(worldAxis, angle);
- const rotationMatrix = Cesium.Matrix3.fromQuaternion(quat);
-
- const modelMatrix = this.entity.model.modelMatrix;
- const translation = Cesium.Matrix4.getTranslation(modelMatrix, new Cesium.Cartesian3());
- const scale = Cesium.Matrix4.getScale(modelMatrix, new Cesium.Cartesian3());
-
- const currentRotMatrix = Cesium.Matrix4.getRotation(modelMatrix, new Cesium.Matrix3());
- const newRotMatrix = Cesium.Matrix3.multiply(currentRotMatrix, rotationMatrix, new Cesium.Matrix3());
-
- const newModelMatrix = Cesium.Matrix4.fromTranslationRotationScale(translation, newRotMatrix, scale);
-
- this.entity.model.modelMatrix = newModelMatrix;
- this.viewer.scene.requestRender();
- }
- // 拾取
- pick(windowPos) {
- const bbs = this.billboardCollection._billboards || [];
- for (const bb of bbs) {
- if (!bb?.position || !bb.id) continue;
- const screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, bb.position);
- if (!screenPos) continue;
- const dist = Math.hypot(screenPos.x - windowPos.x, screenPos.y - windowPos.y);
- if (dist < 50) return bb;
- }
- return null;
- }
- getAxis(axis) {
- switch (axis) {
- case 'X': return new Cesium.Cartesian3(1, 0, 0);
- case 'Y': return new Cesium.Cartesian3(0, 1, 0);
- case 'Z': return new Cesium.Cartesian3(0, 0, 1);
- default: return new Cesium.Cartesian3();
- }
- }
- localToWorld(v) {
- const mat = Cesium.Transforms.eastNorthUpToFixedFrame(this.center);
- return Cesium.Matrix4.multiplyByPoint(mat, v, new Cesium.Cartesian3());
- }
- makeArrow(color) {
- const c = document.createElement('canvas');
- c.width = 32; c.height = 32;
- const ctx = c.getContext('2d');
- ctx.fillStyle = `rgb(${color.red * 255},${color.green * 255},${color.blue * 255})`;
- ctx.beginPath();
- ctx.moveTo(4, 16); ctx.lineTo(24, 16); ctx.lineTo(24, 6); ctx.lineTo(30, 16); ctx.lineTo(24, 26); ctx.closePath();
- ctx.fill();
- return c.toDataURL();
- }
- makeBox(color) {
- const c = document.createElement('canvas');
- c.width = 24; c.height = 24;
- const ctx = c.getContext('2d');
- ctx.fillStyle = `rgb(${color.red * 255},${color.green * 255},${color.blue * 255})`;
- ctx.fillRect(6, 6, 12, 12);
- return c.toDataURL();
- }
- makeRing(color) {
- const c = document.createElement('canvas');
- c.width = 24; c.height = 24;
- const ctx = c.getContext('2d');
- ctx.strokeStyle = `rgb(${color.red * 255},${color.green * 255},${color.blue * 255})`;
- ctx.lineWidth = 3;
- ctx.beginPath();
- ctx.arc(12, 12, 8, 0, Math.PI * 2);
- ctx.stroke();
- return c.toDataURL();
- }
- cleanup() {
- if (this.handler) this.handler.destroy();
- if (this.billboardCollection) this.viewer.scene.primitives.remove(this.billboardCollection);
- if (this.polylineCollection) this.viewer.scene.primitives.remove(this.polylineCollection);
- this.viewer.scene.screenSpaceCameraController.enableInputs = true;
- }
- destroy() { this.cleanup(); }
- }
- export default ModelEditor;
|