Cesium 相机控制器(1)-wheel 实现原理简析
已经做大量简化, 不是代码最终的样子.
Viewer┖ CesiumWidget┖ ScreenSpaceCameraController(_screenSpaceCameraController)┣ CameraEventAggregator(_aggregator) // 相机事件代理┃ ┖ ScreenSpaceEventHandler(_eventHandler)┖ TweenCollection
1、注册事件监听器
注册事件 wheel
和 mousemove
function registerListener() {element.addEventListener(domType, handleWheel);
}// 事件处理器, 提供设置监听者的能力。 分配活的。
class ScreenSpaceEventHandler {constructor(scene) {registerListener('"wheel"', handleWheel);registerListener('"pointermove"', handlePointerMove);}
}
2、事件监听器的实现
function handleWheel (delta) {const action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.WHEEL,);action(delta);
}function handleMouseMove(screenSpaceEventHandler, event) {const modifier = getModifier(event);const position = getPosition();const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;const action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE,modifier);if (defined(action)) {Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition);Cartesian2.clone(position, mouseMoveEvent.endPosition);action(mouseMoveEvent);}Cartesian2.clone(position, previousPosition);
}
3、事件监听的执行
// 事件代理器, 设置真正的监听者。真正干活的。
class CameraEventAggregator {constructor() {listenToWheel(this, undefined);listenMouseMove(this, undefined);}
}const listenToWheel = function (aggregator) {const key = getKey(CameraEventType.WHEEL);const update = aggregator._update;update[key] = true;let movement = aggregator._movement[key];let lastMovement = aggregator._lastMovement[key];movement.startPosition = new Cartesian2();movement.endPosition = new Cartesian2();Cartesian2.clone(Cartesian2.ZERO, movement.startPosition);aggregator._eventHandler.setInputAction(function (delta) {const arcLength = 7.5 * CesiumMath.toRadians(delta);movement.endPosition.x = 0.0;movement.endPosition.y = arcLength;Cartesian2.clone(movement.endPosition, lastMovement.endPosition);lastMovement.valid = true;update[key] = false;},ScreenSpaceEventType.WHEEL,);
}const listenMouseMove = function (aggregator) {const update = aggregator._update;const movement = aggregator._movement;const lastMovement = aggregator._lastMovement;const isDown = aggregator._isDown;aggregator._eventHandler.setInputAction(function (mouseMovement) {Cartesian2.clone(mouseMovement.endPosition,aggregator._currentMousePosition);},ScreenSpaceEventType.MOUSE_MOVE,);
}
可视化查看位置:
const e = document.querySelector(".end");
const pos = viewer.scene._screenSpaceCameraController._aggregator._currentMousePosition
setInterval(() => {e.style.top = pos.y + "px";e.style.left = pos.x + "px";
}, 10);
4、控制器的更新
`Scene` 的 `render`┖ Scene.prototype.initializeFrame()┖ this._screenSpaceCameraController.update();┖ update3D(this);┖ reactToInput()┖ zoom3D()
5、zoom3D 实现细节
function zoom3D(controller: ScreenSpaceCameraController,startPosition: Cartesian2,movement) {// 屏幕中心位置let windowPosition;windowPosition.x = canvas.clientWidth / 2;windowPosition.y = canvas.clientHeight / 2;// 得到射线const ray = camera.getPickRayPerspective(windowPosition, zoomCVWindowRay);// 相机的当前高度let distance = ellipsoid.cartesianToCartographic(camera.position,zoom3DCartographic).height;const unitPosition = Cartesian3.normalize(camera.position,zoom3DUnitPosition);handleZoom(controller,startPosition,movement,controller._zoomFactor,distance,Cartesian3.dot(unitPosition, camera.direction));
}
6、getPickRayPerspective 实现细节
function getPickRayPerspective(camera, windowPosition, result) {const canvas = camera._scene.canvas;const width = canvas.clientWidth;const height = canvas.clientHeight;const tanPhi = Math.tan(camera.frustum.fovy * 0.5);// tanTheta 是相机视角张角的正切值,表示近平面的宽度与近平面距离的比值const tanTheta = camera.frustum.aspectRatio * tanPhi;const near = camera.frustum.near;// 屏幕空间 转 NDC 空间, [-1, 1]const x = (2.0 / width) * windowPosition.x - 1.0;const y = (2.0 / height) * (height - windowPosition.y) - 1.0;const position = camera.positionWC;Cartesian3.clone(position, result.origin);// near * tanTheta 表示近平面的宽度,将其乘以 x 就可以得到近平面上某个点的水平距离。const nearCenter = Cartesian3.multiplyByScalar(camera.directionWC,near,pickPerspCenter);Cartesian3.add(position, nearCenter, nearCenter);const xDir = Cartesian3.multiplyByScalar(camera.rightWC,x * near * tanTheta,pickPerspXDir);const yDir = Cartesian3.multiplyByScalar(camera.upWC,y * near * tanPhi,pickPerspYDir);const direction = Cartesian3.add(nearCenter, xDir, result.direction);Cartesian3.add(direction, yDir, direction);Cartesian3.subtract(direction, position, direction);Cartesian3.normalize(direction, direction);return result;
}
7、pickPosition 实现细节
const pickGlobeScratchRay = new Ray();
const scratchDepthIntersection = new Cartesian3();
const scratchRayIntersection = new Cartesian3();function pickPosition(controller, mousePosition, result) {const scene = controller._scene;const globe = controller._globe;const camera = scene.camera;let depthIntersection;if (scene.pickPositionSupported) {depthIntersection = scene.pickPositionWorldCoordinates(mousePosition,scratchDepthIntersection);}if (!defined(globe)) {return Cartesian3.clone(depthIntersection, result);}const cullBackFaces = !controller._cameraUnderground;const ray = camera.getPickRay(mousePosition, pickGlobeScratchRay);const rayIntersection = globe.pickWorldCoordinates(ray,scene,cullBackFaces,scratchRayIntersection);const pickDistance = defined(depthIntersection)? Cartesian3.distance(depthIntersection, camera.positionWC): Number.POSITIVE_INFINITY;const rayDistance = defined(rayIntersection)? Cartesian3.distance(rayIntersection, camera.positionWC): Number.POSITIVE_INFINITY;if (pickDistance < rayDistance) {return Cartesian3.clone(depthIntersection, result);}return Cartesian3.clone(rayIntersection, result);
}
8、handleZoom 实现细节
viewer.scene.screenSpaceCameraController._zoomWorldPosition
function handleZoom(distanceMeasure) {let percentage = 1.0;// startPosition 是固定的 (0,0)// endPosition.y 是滚轮距离const diff = movement.endPosition.y - movement.startPosition.y;const minHeight = 0;const maxHeight = Infinity;const minDistance = distanceMeasure - minHeight;let zoomRate = zoomFactor * minDistance;let rangeWindowRatio = diff / object._scene.canvas.clientHeight;let distance = zoomRate * rangeWindowRatio;pickedPosition = pickPosition(object,startPosition,scratchPickCartesian);if (defined(pickedPosition)) {object._useZoomWorldPosition = true;object._zoomWorldPosition = Cartesian3.clone(pickedPosition,object._zoomWorldPosition);} else {object._useZoomWorldPosition = false;}const positionNormal = Cartesian3.normalize(centerPosition,scratchPositionNormal);const pickedNormal = Cartesian3.normalize(controller._zoomWorldPosition,scratchPickNormal);const dotProduct = Cartesian3.dot(pickedNormal, positionNormal);if (dotProduct > 0.0 && dotProduct < 1.0) {const angle = CesiumMath.acosClamped(dotProduct);const axis = Cartesian3.cross(pickedNormal,positionNormal,scratchZoomAxis);const denom =Math.abs(angle) > CesiumMath.toRadians(20.0)? camera.positionCartographic.height * 0.75: camera.positionCartographic.height - distance;const scalar = distance / denom;camera.rotate(axis, angle * scalar);
}
9、相机的旋转
/*** Rotates the camera around <code>axis</code> by <code>angle</code>. The distance* of the camera's position to the center of the camera's reference frame remains the same.** @param {Cartesian3} axis The axis to rotate around given in world coordinates.* @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.** @see Camera#rotateUp* @see Camera#rotateDown* @see Camera#rotateLeft* @see Camera#rotateRight*/
Camera.prototype.rotate = function (axis, angle) {const turnAngle = defaultValue(angle, this.defaultRotateAmount);const quaternion = Quaternion.fromAxisAngle(axis,-turnAngle,rotateScratchQuaternion);const rotation = Matrix3.fromQuaternion(quaternion, rotateScratchMatrix);Matrix3.multiplyByVector(rotation, this.position, this.position);Matrix3.multiplyByVector(rotation, this.direction, this.direction);Matrix3.multiplyByVector(rotation, this.up, this.up);Cartesian3.cross(this.direction, this.up, this.right);Cartesian3.cross(this.right, this.direction, this.up);
};