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

news2024/9/24 23:30:06

请添加图片描述

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

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

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

注册事件 wheelmousemove

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);
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1976039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.创建了Vue项目,需要导入什么插件以及怎么导入

如果你不知道怎么创建Vue项目,建议可以看一看这篇文章 怎么安装Vue的环境和搭建Vue的项目-CSDN博客 1.在idea中打开目标文件 2.系在一个插件Vue.js 3.下载ELement UI 在Terminal中输入 # 切换到项目根目录 cd vueadmin-vue # 或者直接在idea中执行下面命令 # 安装element-u…

WordPress 轻量级产品官网类主题 CeoNova-Pro_v4.4绕授权开心版

CeoNova-Pro 主题是一款轻量级、且简洁大气、产品官网类主题&#xff0c;定位于高端产品官网、同时包含了知识付费、定制服务、问答社区、论坛交流、网址导航、以及付费产品购买下载等全方位覆盖。 源码下载&#xff1a;ceonova-pro4.4.zip 变更日志 新增虚拟资源隐藏信息增…

limit 以及分页 SQL 语句

目录 1. 作用 2. 演示 3. 分页 SQL 语句 1. 作用 获取结果集的一部分&#xff1b; 2. 演示 &#xff08;1&#xff09;如下&#xff0c;获取表的前三行&#xff1b; &#xff08;2&#xff09;只有一个数字&#xff0c;默认从 0 开始&#xff1b; &#xff08;3&#x…

Linux 项目自动化构建工具 —— make/makefile

Linux 项目自动化构建工具 —— make/makefile 入门使用原理自动化构建递归式自动推导 清理注意 特殊符号 会不会写 makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&a…

神奇海洋养鱼小程序游戏广告联盟流量主休闲小游戏源码

在海洋养鱼小程序中&#xff0c;饲料、任务系统、系统操作日志、签到、看广告、完成喂养、每日签到、系统公告、积分商城、界面设计、拼手气大转盘抽奖以及我的好友等功能共同构建了一个丰富而互动的游戏体验。以下是对这些功能的进一步扩展介绍&#xff1a; 饲料 任务奖励&a…

使用JWT的SpringSecurity实现前后端分离

1. SpringSecurity完成前后端完全分离 分析&#xff1a; 前后端分离&#xff1a;响应的数据必须为JSON数据&#xff0c;之前响应的是网页 需要修改的代码有&#xff1a; 登录成功需要返回json数据登录失败需要返回json数据权限不足时返回json数据未登录访问资源返回json数据 1.…

英国AI大学排名

计算机学科英国Top10 “计算机科学与信息系统”学科除了最受关注的“计算机科学”专业&#xff0c;还包括了“人工智能”“软件工程”“计算机金融”等众多分支专业。 1.帝国理工学院 Imperial College London 单以计算机专业本科来讲&#xff0c;仅Computing这个专业&#x…

双线性插值(Bilinear Interpolation)

文章目录 一.双线性插值3.双线性插值的优化 一.双线性插值 假设源图像大小为mxn&#xff0c;目标图像为axb。那么两幅图像的边长比分别为&#xff1a;m/a和n/b。注意&#xff0c;通常这个比例不是整数&#xff0c;编程存储的时候要用浮点型。目标图像的第&#xff08;i,j&…

正点原子imx6ull-mini-Linux驱动之Linux LCD 驱动实验(19)

LCD 是很常用的一个外设&#xff0c;在裸机篇中我们讲解了如何编写 LCD 裸机驱动&#xff0c;在 Linux 下 LCD 的使用更加广泛&#xff0c;在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。本章我们 就来学习一下如何在 Linux 下驱动 LCD 屏幕。 1&#xff1a;Linux …

奇安信高管合计套现7.7亿,总裁个人套现1.9亿

【文末送&#xff1a;技战法】 昨天网安一哥&#xff0c;奇安信发布《关于中电金投增持公司股份暨持股 5% 以上股东协议转让公司股份的权益变动的提示性公告》&#xff0c;公告显示中国电子将再次收购奇安信5%的股份。 公告显示&#xff0c;奇安壹号合伙人中&#xff1a;天津…

[Meachines] [Easy] OpenAdmin OpenNetAdmin-RCE+RSA私钥解密+Nano权限提升

信息收集 IP AddressOpening Ports10.10.10.171TCP:22,80 $ nmap -p- 10.10.10.171 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 4b:98:df:85:d1:7…

深入理解操作系统--进程(1)

文章目录 概述进程&#xff0c;轻量级进程和线程进程描述符linux进程四要素创建进程linux3个系统调用创建新的进程do_fork函数copy_process函数 撤销进程 小结 概述 这一章&#xff0c;主要讲的是进程的概念&#xff0c;即程序执行的一个实例。在linux源代码中&#xff0c;通常…

图像像素增强albumentations库的使用

albumentations是一个快速的图像增强库,用于机器学习任务。它支持各种类型的图像变换,包括但不限于旋转、平移、缩放、剪切、翻转、噪声注入、遮挡等。albumentations库可以与深度学习框架如PyTorch和TensorFlow很好地集成, 支持种类丰富的像素级变换,包括雨天、雾天、色域变…

使用Python绘制雷达图的简单示例

雷达图&#xff08;Radar Chart&#xff09;也被称为蜘蛛网图、星形图或极坐标图&#xff0c;是一种用于显示多变量数据的图形方法。它以一个中心点为起点&#xff0c;从中心点向外延伸出多条射线&#xff0c;每条射线代表一个特定的变量或指标。每条射线上的点或线段表示该变量…

面试官:如何保证缓存和数据库的一致性?

你好呀&#xff0c;我是苍何&#xff01; 办公室里鸦雀无声&#xff0c;我木然的看着窗外射进来的阳光&#xff0c;它照在光滑的地板上&#xff0c;又反射到天花板上&#xff0c;再从天花板上反射下来时&#xff0c;就变成一片弥散的白光。 我在白光里偷偷放了一个恶毒的臭屁…

二百五十四、OceanBase——Linux上安装OceanBase数据库(四):登录ocp-express,配置租户管理等信息

一、目的 在部署OceanBase成功后&#xff0c;接下来就是登录ocp-express&#xff0c;配置租户管理等信息&#xff01; 二、ocp-express网址以及账密信息 三、实施步骤 1 登录ocp-express 2 集群总览 3 租户管理 3.1 新建租户 3.2 配置新租户信息 剩下的几个模块了解即可&am…

redis实现的分布式锁redisson

redis服务宕机出现的概率很低&#xff0c;redis集群整体的思想是AP思想&#xff08;优先保证高可用性&#xff09; 如果非要保证业务数据强一致性建议采用CP思想&#xff0c;用zookeeper实现分布式锁。

C++自定义接口类设计器之模板代码生成四

关键代码 QStringList multis templateStr.split(\n);bool startConfig false;bool startVar false;bool startTemplate false;for (const auto& line : multis) {if(startConfig) {if(line.trimmed().startsWith("camealCase")) {auto name_val line.split…

Web开发-html篇-上

HTML发展史 HTML的历史可以追溯到20世纪90年代初。当时&#xff0c;互联网尚处于起步阶段&#xff0c;Web浏览器也刚刚问世。HTML的创建者是蒂姆伯纳斯-李&#xff08;Tim Berners-Lee&#xff09;&#xff0c;他在1991年首次提出了HTML的概念。HTML的初衷是为了方便不同计算机…

TOA/TDOA测距定位,三维任意(>3)个锚节点,对一个未知点进行定位|MATLAB源代码

目录 程序介绍程序截图和运行结果程序截图运行结果 源代码代码修改建议 程序介绍 TOA/TDOA使用三点法测距&#xff0c;在空间中&#xff0c;有4个锚节点就可以定位&#xff0c;但如果有多个节点&#xff0c;定位效果会更好。 锚点不同时&#xff0c;修改程序中的向量和矩阵维度…