Cesium 相机控制器(1)-wheel 实现原理简析

Cesium 相机控制器(1)-wheel 实现原理简析

已经做大量简化, 不是代码最终的样子.

js 复制代码
Viewer
  ┖ CesiumWidget
      ┖ ScreenSpaceCameraController(_screenSpaceCameraController)
          ┣ CameraEventAggregator(_aggregator) // 相机事件代理
          ┃  ┖ ScreenSpaceEventHandler(_eventHandler)
          ┖ TweenCollection
1、注册事件监听器

注册事件 wheelmousemove

js 复制代码
function registerListener() {
  element.addEventListener(domType, handleWheel);
}

// 事件处理器, 提供设置监听者的能力。 分配活的。
class ScreenSpaceEventHandler {
  constructor(scene) {
    registerListener('"wheel"', handleWheel);
    registerListener('"pointermove"', handlePointerMove);
  }
}
2、事件监听器的实现
js 复制代码
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、事件监听的执行
js 复制代码
// 事件代理器, 设置真正的监听者。真正干活的。
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,
  );
}

可视化查看位置:

js 复制代码
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、控制器的更新
js 复制代码
`Scene` 的 `render`
    ┖ Scene.prototype.initializeFrame()
        ┖ this._screenSpaceCameraController.update();
          ┖ update3D(this);
            ┖ reactToInput()
                ┖ zoom3D()
5、zoom3D 实现细节
js 复制代码
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 实现细节
js 复制代码
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 实现细节
js 复制代码
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 实现细节
js 复制代码
viewer.scene.screenSpaceCameraController._zoomWorldPosition
js 复制代码
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、相机的旋转
js 复制代码
/**
 * 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);
};
相关推荐
undefined&&懒洋洋3 天前
Cesium使用flyToBoundingSphere实现倾斜相机视角观察物体
前端·javascript·cesium·webgis
GIS瞧葩菜6 天前
GeoSever发布图层(保姆姬)
wms·geoserver·cesium
新中地GIS开发老师6 天前
地理信息科学专业想搞GIS开发:学前端还是后端?
前端·javascript·arcgis·前端框架·cesium
qbbmnnnnnn13 天前
【WebGis开发 - Cesium】三维可视化项目教程---图层管理拓展图层顺序调整功能
vue.js·webgl·三维可视化·cesium·vue3.0·webgis·vuedraggable
smiler15 天前
cesium两种方式实现贴地
前端·cesium
按图索迹17 天前
100GB,台湾台东县绿岛倾斜摄影3DTiles数据来了
三维可视化·cesium·倾斜摄影·3dtiles·台湾倾斜摄影
htsitr24 天前
Cesium如果链接着色器的?
cesium·着色器
T0uken1 个月前
【WebGIS】Cesium:Viewer 初始化、地图加载与基础交互
gis·cesium·webgis
qbbmnnnnnn1 个月前
【WebGis开发 - Cesium】三维可视化项目教程---图层管理基础
前端·wmts·cesium·vue3.0·webgis·3dtiles·图层管理
按图索迹1 个月前
台湾高雄三维倾斜摄影模型3DTiles样例数据介绍
人工智能·3d·cesium·倾斜摄影·3dtiles·台湾·3dgis