UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

277 lines (274 loc) 9.95 kB
import { math } from '../../core/math/math.js'; import { Color } from '../../core/math/color.js'; import { Quat } from '../../core/math/quat.js'; import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; import { PROJECTION_PERSPECTIVE } from '../../scene/constants.js'; import { ArcShape } from './shape/arc-shape.js'; import { GIZMOAXIS_FACE, GIZMOAXIS_Y, GIZMOAXIS_X, GIZMOAXIS_Z, GIZMOSPACE_LOCAL } from './constants.js'; import { TransformGizmo } from './transform-gizmo.js'; const tmpV1 = new Vec3(); const tmpV2 = new Vec3(); const tmpV3 = new Vec3(); const tmpV4 = new Vec3(); const tmpM1 = new Mat4(); const tmpQ1 = new Quat(); const tmpQ2 = new Quat(); const FACING_THRESHOLD = 0.9; const GUIDE_ANGLE_COLOR = new Color(0, 0, 0, 0.3); class RotateGizmo extends TransformGizmo { constructor(camera, layer){ super(camera, layer), this._shapes = { z: new ArcShape(this._device, { axis: GIZMOAXIS_Z, layers: [ this._layer.id ], shading: this._shading, rotation: new Vec3(90, 0, 90), defaultColor: this._meshColors.axis.z, hoverColor: this._meshColors.hover.z, sectorAngle: 180 }), x: new ArcShape(this._device, { axis: GIZMOAXIS_X, layers: [ this._layer.id ], shading: this._shading, rotation: new Vec3(0, 0, -90), defaultColor: this._meshColors.axis.x, hoverColor: this._meshColors.hover.x, sectorAngle: 180 }), y: new ArcShape(this._device, { axis: GIZMOAXIS_Y, layers: [ this._layer.id ], shading: this._shading, rotation: new Vec3(0, 0, 0), defaultColor: this._meshColors.axis.y, hoverColor: this._meshColors.hover.y, sectorAngle: 180 }), face: new ArcShape(this._device, { axis: GIZMOAXIS_FACE, layers: [ this._layer.id ], shading: this._shading, rotation: this._getLookAtEulerAngles(this._camera.entity.getPosition()), defaultColor: this._meshColors.axis.f, hoverColor: this._meshColors.hover.f, ringRadius: 0.55 }) }, this._selectionStartAngle = 0, this._nodeLocalRotations = new Map(), this._nodeRotations = new Map(), this._nodeOffsets = new Map(), this._guideAngleStartColor = GUIDE_ANGLE_COLOR.clone(), this._guideAngleStart = new Vec3(), this._guideAngleEnd = new Vec3(), this.snapIncrement = 5, this.orbitRotation = false; this._createTransform(); this.on(TransformGizmo.EVENT_TRANSFORMSTART, (point, x, y)=>{ this._selectionStartAngle = this._calculateAngle(point, x, y); this._storeNodeRotations(); this._storeGuidePoints(); this._drag(true); }); this.on(TransformGizmo.EVENT_TRANSFORMMOVE, (point, x, y)=>{ const axis = this._selectedAxis; let angleDelta = this._calculateAngle(point, x, y) - this._selectionStartAngle; if (this.snap) { angleDelta = Math.round(angleDelta / this.snapIncrement) * this.snapIncrement; } this._setNodeRotations(axis, angleDelta); this._updateGuidePoints(angleDelta); }); this.on(TransformGizmo.EVENT_TRANSFORMEND, ()=>{ this._drag(false); }); this.on(TransformGizmo.EVENT_NODESDETACH, ()=>{ this._nodeLocalRotations.clear(); this._nodeRotations.clear(); this._nodeOffsets.clear(); }); this._app.on('prerender', ()=>{ this._shapesLookAtCamera(); if (this._dragging) { const gizmoPos = this.root.getPosition(); this._drawGuideAngleLine(gizmoPos, this._selectedAxis, this._guideAngleStart, this._guideAngleStartColor); this._drawGuideAngleLine(gizmoPos, this._selectedAxis, this._guideAngleEnd); } }); } set xyzTubeRadius(value) { this._setDiskProp('tubeRadius', value); } get xyzTubeRadius() { return this._shapes.x.tubeRadius; } set xyzRingRadius(value) { this._setDiskProp('ringRadius', value); } get xyzRingRadius() { return this._shapes.x.ringRadius; } set faceTubeRadius(value) { this._shapes.face.tubeRadius = value; } get faceTubeRadius() { return this._shapes.face.tubeRadius; } set faceRingRadius(value) { this._shapes.face.ringRadius = value; } get faceRingRadius() { return this._shapes.face.ringRadius; } set ringTolerance(value) { this._setDiskProp('tolerance', value); this._shapes.face.tolerance = value; } get ringTolerance() { return this._shapes.x.tolerance; } _setDiskProp(prop, value) { this._shapes.x[prop] = value; this._shapes.y[prop] = value; this._shapes.z[prop] = value; } _storeGuidePoints() { const gizmoPos = this.root.getPosition(); const axis = this._selectedAxis; const isFacing = axis === GIZMOAXIS_FACE; const scale = isFacing ? this.faceRingRadius : this.xyzRingRadius; this._guideAngleStart.copy(this._selectionStartPoint).sub(gizmoPos).normalize(); this._guideAngleStart.mulScalar(scale); this._guideAngleEnd.copy(this._guideAngleStart); } _updateGuidePoints(angleDelta) { const axis = this._selectedAxis; const isFacing = axis === GIZMOAXIS_FACE; if (isFacing) { tmpV1.copy(this.facing); } else { tmpV1.set(0, 0, 0); tmpV1[axis] = 1; this._rootStartRot.transformVector(tmpV1, tmpV1); } tmpQ1.setFromAxisAngle(tmpV1, angleDelta); tmpQ1.transformVector(this._guideAngleStart, this._guideAngleEnd); } _drawGuideAngleLine(pos, axis, point, color = this._guideColors[axis]) { tmpV1.set(0, 0, 0); tmpV2.copy(point).mulScalar(this._scale); this._app.drawLine(tmpV1.add(pos), tmpV2.add(pos), color, false, this._layer); } _getLookAtEulerAngles(position) { tmpV1.set(0, 0, 0); tmpM1.setLookAt(tmpV1, position, Vec3.UP); tmpQ1.setFromMat4(tmpM1); tmpQ1.getEulerAngles(tmpV1); tmpV1.x += 90; return tmpV1; } _shapesLookAtCamera() { if (this._camera.projection === PROJECTION_PERSPECTIVE) { this._shapes.face.entity.lookAt(this._camera.entity.getPosition()); this._shapes.face.entity.rotateLocal(90, 0, 0); } else { tmpQ1.copy(this._camera.entity.getRotation()).getEulerAngles(tmpV1); this._shapes.face.entity.setEulerAngles(tmpV1); this._shapes.face.entity.rotateLocal(-90, 0, 0); } const facingDir = tmpV1.copy(this.facing); tmpQ1.copy(this.root.getRotation()).invert().transformVector(facingDir, facingDir); let angle = Math.atan2(facingDir.z, facingDir.y) * math.RAD_TO_DEG; this._shapes.x.entity.setLocalEulerAngles(0, angle - 90, -90); angle = Math.atan2(facingDir.x, facingDir.z) * math.RAD_TO_DEG; this._shapes.y.entity.setLocalEulerAngles(0, angle, 0); angle = Math.atan2(facingDir.y, facingDir.x) * math.RAD_TO_DEG; this._shapes.z.entity.setLocalEulerAngles(90, 0, angle + 90); } _drag(state) { for(const axis in this._shapes){ const shape = this._shapes[axis]; if (axis === this._selectedAxis) { shape.drag(state); } else { shape.hide(state); } } this.fire(TransformGizmo.EVENT_RENDERUPDATE); } _storeNodeRotations() { const gizmoPos = this.root.getPosition(); for(let i = 0; i < this.nodes.length; i++){ const node = this.nodes[i]; this._nodeLocalRotations.set(node, node.getLocalRotation().clone()); this._nodeRotations.set(node, node.getRotation().clone()); this._nodeOffsets.set(node, node.getPosition().clone().sub(gizmoPos)); } } _setNodeRotations(axis, angleDelta) { const gizmoPos = this.root.getPosition(); const isFacing = axis === GIZMOAXIS_FACE; tmpQ1.setFromAxisAngle(this._dirFromAxis(axis, tmpV1), angleDelta); for(let i = 0; i < this.nodes.length; i++){ const node = this.nodes[i]; if (!isFacing && this._coordSpace === GIZMOSPACE_LOCAL) { const rot = this._nodeLocalRotations.get(node); if (!rot) { continue; } tmpQ2.copy(rot).mul(tmpQ1); node.setLocalRotation(tmpQ2); } else { const rot = this._nodeRotations.get(node); if (!rot) { continue; } const offset = this._nodeOffsets.get(node); if (!offset) { continue; } tmpV1.copy(offset); tmpQ1.transformVector(tmpV1, tmpV1); tmpQ2.copy(tmpQ1).mul(rot); node.setEulerAngles(tmpQ2.getEulerAngles()); node.setPosition(tmpV1.add(gizmoPos)); } } if (this._coordSpace === GIZMOSPACE_LOCAL) { this._updateRotation(); } } _screenToPoint(x, y) { const mouseWPos = this._camera.screenToWorld(x, y, 1); const axis = this._selectedAxis; const ray = this._createRay(mouseWPos); const plane = this._createPlane(axis, axis === GIZMOAXIS_FACE, false); const point = new Vec3(); plane.intersectsRay(ray, point); return point; } _calculateAngle(point, x, y) { const gizmoPos = this.root.getPosition(); const axis = this._selectedAxis; const plane = this._createPlane(axis, axis === GIZMOAXIS_FACE, false); let angle = 0; const facingDir = tmpV2.copy(this.facing); const facingDot = plane.normal.dot(facingDir); if (this.orbitRotation || Math.abs(facingDot) > FACING_THRESHOLD) { tmpV1.sub2(point, gizmoPos); tmpQ1.copy(this._camera.entity.getRotation()).invert().transformVector(tmpV1, tmpV1); angle = Math.sign(facingDot) * Math.atan2(tmpV1.y, tmpV1.x) * math.RAD_TO_DEG; } else { tmpV1.copy(gizmoPos); tmpV2.cross(plane.normal, facingDir).normalize().add(gizmoPos); this._camera.worldToScreen(tmpV1, tmpV3); this._camera.worldToScreen(tmpV2, tmpV4); tmpV1.sub2(tmpV4, tmpV3).normalize(); tmpV2.set(x, y, 0); angle = tmpV1.dot(tmpV2); } return angle; } } export { RotateGizmo };