实现仿地图操作

  • 前言
  • 一、需求分析
    • 1.1 旋转
    • 1.2 缩放
    • 1.3 拖拽(平移)
  • 二、简单介绍ArcRotateCamera相机
    • 2.1 hello world
    • 2.2 属性介绍
  • 三、重写ICameraInput
    • 3.1 完整代码
    • 3.2 结构解析
      • 3.2.1 ICameraInput 重构接口
      • 3.2.2 attachControl解析
      • 3.2.3 onPointerObservable事件逻辑
  • 四、如何使用重构类
  • 总结

前言

真正准备用Babylon搭场景后才发现,这操作有点怪。与常规的地图操作还是有点区别的,虽然不大,但却非常不舒服。

Babylon沙盒

地图操作


一、需求分析

1.1 旋转

效果基本一样,Babylon默认使用左键触发,常规地图用右键,这个应该简单。

1.2 缩放

效果基本一样,都是滚轮触发。

1.3 拖拽(平移)

这里有很细微的不同,也正是这点不同,让我操作起来感觉别扭,接着深度分析两者区别。
地图:点击地图上某一点,鼠标不松进行移动,鼠标移动多少,地图平移多少!可以看到从开始移动到结束,鼠标始终指在同一个位置!
Babylon:点击空间任意点,鼠标不松进行移动,鼠标移动多少和空间移动多少好像有一个特别的关系!这样的操作方式特别难准确拖拽内容。特别是越靠近时,拖拽幅度很大;很远离时,拖拽幅度又很小。
备注:看代码后知道,默认的拖拽是可以设置偏移值的,但是固定值,所以不能简单通过设置合理值来解决。

二、简单介绍ArcRotateCamera相机

2.1 hello world

// 创建相机的配置项:name, alpha, beta, radius, target position, scene
const camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 10, new BABYLON.Vector3(0, 0, 0), scene);// 通过设置相机位置来覆盖 alpha, beta, radius
camera.setPosition(new BABYLON.Vector3(0, 0, 20));// 将相机绑定到画布上面
camera.attachControl(canvas, true);

2.2 属性介绍

三、重写ICameraInput

说明:这块内容主要介绍重写的逻辑,稍微有点难度,不适应可以直接跳过看第四段,怎么使用。至于怎么想到要重新这个类能满足效果,是通过参考很多相似案例得出结论的。

3.1 完整代码

// @ts-nocheck
/* eslint-disable */
// import * as BABYLON from 'babylonjs'
/*** 仿地图操作,拖拽、旋转。(不包括缩放)*/
const isIPointerEvent = (event: BABYLON.IEvent): event is BABYLON.IPointerEvent => 'pointerId' in eventexport class CustomPointersInput implements BABYLON.ICameraInput<BABYLON.ArcRotateCamera> {camera: BABYLON.ArcRotateCamera;_scene: BABYLON.Nullable<BABYLON.Scene> = null;_pointerObserver: BABYLON.Nullable<BABYLON.Observer<BABYLON.PointerInfo>> = null;_beforeRenderObserver: BABYLON.Nullable<BABYLON.Observer<BABYLON.Scene>> = null;_pickedPoints: Map<number, BABYLON.Vector3> = new Map();_pickedDoublePoints: Map<number, BABYLON.Vector3> = new Map();_targetTarget: BABYLON.Vector3 = BABYLON.Vector3.Zero();_targetRadius = 0;_targetAlpha = 0;_targetBeta = 0;// 因版本升级,在出现精灵是,down事件会出问题,所以使用该标志位,替代down事件记录point,改到move事件记录。firstDown = false;_debugSpheres: Map<string, BABYLON.Mesh> = new Map();getClassName (): 'CustomPointersInput' { return 'CustomPointersInput' }getSimpleName (): 'pointers' { return 'pointers' }constructor () {}// 绑定attachControl (noPreventDefault?: boolean, defaultTarget?: BABYLON.Vector3, radius?: number): void {if (this._scene) this.detachControl()if (defaultTarget) {this._targetTarget = defaultTarget}if (radius) {this.camera.radius = radius}noPreventDefault = BABYLON.Tools.BackCompatCameraNoPreventDefault(arguments)this.camera.mapPanning = truethis._scene = this.camera.getScene()this._targetRadius = this.camera.radiusthis._targetAlpha = this.camera.alphathis._targetBeta = this.camera.beta// 鼠标事件绑定this._pointerObserver = this._scene.onPointerObservable.add((p: BABYLON.PointerInfo, s: BABYLON.EventState) => {if (!noPreventDefault) {p.event.preventDefault()}// 重点重构this._handlePointers(p, noPreventDefault)},BABYLON.PointerEventTypes.POINTERDOWN | BABYLON.PointerEventTypes.POINTERUP |BABYLON.PointerEventTypes.POINTERMOVE)// 场景渲染前事件绑定this._beforeRenderObserver = this._scene.onBeforeRenderObservable.add(() => {this.camera._target = this._targetTargetthis.camera.alpha = this._targetAlphathis.camera.beta = this._targetBeta})}// 注销方法detachControl (): void {if (this._pointerObserver) {this._scene.onPointerObservable.remove(this._pointerObserver)}if (this._beforeRenderObserver) {this._scene.onBeforeRenderObservable.remove(this._beforeRenderObserver)}this._scene = nullthis._pointerObserver = nullthis._beforeRenderObserver = nullthis._pickedPoints = new Map()}checkInputs(): void {}_handlePointers ({ type, event, pickInfo: { ray } }: BABYLON.PointerInfo, noPreventDefault?: boolean /* , s: BABYLON.EventState */) {if (!isIPointerEvent(event)) {return}const { buttons, pointerId } = event// 第一次按下, 需要触发MOVE事件记录this._pickedPointsswitch (type) {case BABYLON.PointerEventTypes.POINTERDOWN:this.firstDown = truethis._targetTarget = this.camera.target.clone()this._targetAlpha = this.camera.alphathis._targetBeta = this.camera.betaif (!noPreventDefault) {event.preventDefault()}breakcase BABYLON.PointerEventTypes.POINTERUP:this.firstDown = falsethis._pickedPoints.delete(pointerId)this._pickedDoublePoints.delete(pointerId)if (!noPreventDefault) {event.preventDefault()}breakcase BABYLON.PointerEventTypes.POINTERMOVE:if ( this.firstDown ) { //  console.log('存储一次位置')const point = ray.intersectsAxis('y')this._pickedPoints.set(pointerId, point)console.log('存储:',point._x)this.firstDown = false}if (!noPreventDefault) {event.preventDefault()}if (this._pickedPoints.size === 0) {return}if (this._pickedPoints.size === 1) {// get the previous picked point from the "store"const prevPickedPoint = this._pickedPoints.get(pointerId)// get the current picked point, we'll store it laterconst pickedPoint = ray.intersectsAxis('y')// 1、左键; 2、右键if (buttons === 1) {// we'll move the camera's target to this point before the next renderif (pickedPoint && prevPickedPoint) {this._targetTarget.addInPlace(prevPickedPoint.subtract(pickedPoint))}return}if (buttons === 2) {const { lowerAlphaLimit, upperAlphaLimit, lowerBetaLimit, upperBetaLimit, angularSensibilityX, angularSensibilityY } = this.cameraconst offsetX = event.movementX || event.mozMovementX || event.webkitMovementX || event.msMovementX || 0const offsetY = event.movementY || event.mozMovementY || event.webkitMovementY || event.msMovementY || 0this._targetAlpha -= offsetX / 500this._targetBeta -= offsetY / 500if (lowerAlphaLimit && this._targetAlpha < lowerAlphaLimit) {this._targetAlpha = lowerAlphaLimit}if (upperAlphaLimit && this._targetAlpha > upperAlphaLimit) {this._targetAlpha = upperAlphaLimit}if (lowerBetaLimit && this._targetBeta < lowerBetaLimit) {this._targetBeta = lowerBetaLimit}if (upperBetaLimit && this._targetBeta > upperBetaLimit) {this._targetBeta = upperBetaLimit}return}return}breakdefault://  throw new Error(`Unexpected pointer event type ${type}`)}}
}

3.2 结构解析

3.2.1 ICameraInput 重构接口

  • camera: // 属性
  • getClassName(): string; // 随便写个命名
  • getSimpleName(): string; // 随便写个命名
  • attachControl(noPreventDefault?: boolean): void; // 核心方法,在相机执行attachControl方法中,会循环调用input的attachControl方法。可以理解为绑定功能。
  • detachControl(): void; // 注销,相机注销时会调研到这个方法。
  • checkInputs?: () => void; // 暂时没用到,看说明是性能优化

3.2.2 attachControl解析

  1. 声明几个必要字段,记录相机的radius、Alpha、Beta,用于后续使用。
  2. 给场景增加两个事件onPointerObservable和onBeforeRenderObservable。
    onPointerObservable:鼠标触发事件,对鼠标的POINTERDOWN、POINTERUP、POINTERMOVE事件进行监听。核心方法。
    onBeforeRenderObservable:在场景每次渲染前,触发该监听事件。对radius、Alpha、Beta进行赋值,保障内部记录的属性严格控制相机这个三个属性。

3.2.3 onPointerObservable事件逻辑

两个关键方法:

// 1、获取鼠标发射的射线与y平面的交点point。   大部分场景中y平面刚好是地图平铺的面。
const point = ray.intersectsAxis('y')// 2、subtract是计算两个相位的差值。 addInPlace是增加制定的相位值。 所以这个方法是_targetTarget增加prevPickedPoint和pickedPoint的差值。
// 类似  _targetTarget = _targetTarget + (prevPickedPoint - pickedPoint)
this._targetTarget.addInPlace(prevPickedPoint.subtract(pickedPoint))

平移和旋转的逻辑解析:

  1. 鼠标按下,并不松开:开始一次平移或旋转。
  2. 鼠标移动:如果鼠标已经按下并未松开,触发平移或者旋转。
  3. 鼠标松开。结束一次平移或旋转。

然后根据以上事件逻辑,编写出代码就是上面那部分。
POINTERDOWN事件中记录一个标志位firstDown和初始点prevPickedPoint;
POINTERMOVE事件中根据标志位firstDown判断是否触发平移或旋转;根据左右键按钮buttons值判断是平移或旋转。
POINTERUP事件中清理标志位和初始点。

四、如何使用重构类

// 3.![请添加图片描述](https://img-blog.csdnimg.cn/df986ebdf2b54f3cba938c4a4d0738bb.gif)
1完整代码
import { CustomPointersInput } from '../../../functions/common/CustomPointersInput'// 创建相机的配置项:name, alpha, beta, radius, target position, scene
const camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 10, new BABYLON.Vector3(0, 0, 0), scene);// 移除原来input
camera.inputs.remove(camera.inputs.attached.pointers)
this.cameraControl = new CustomPointersInput()
camera.inputs.add(this.cameraControl)// 将相机绑定到画布上面
camera.attachControl(canvas, true);

总结

真正实现起来不难,主要是要知道先分析需求,再找到几个核心方法。

【Babylon提升】重写相机控制器,实现仿地图操作相关推荐

  1. Three.js漫游相机控制器/three.js第三人称视角漫游/three.js第一人称视角漫游

    Three.js三维可视化引擎没有实现第三人称漫游控制器,第一人称实现的也很一般,大部分满足不了需求,需要自己手动去写一个.以下是模仿其它平台写的第三人称视角漫游,也可以简便的改写成第一人称. 下面是 ...

  2. 09 Controls相机控制器

    我们在第七节的时候讲解过了相机的相关,也制作了一个简易的相机控制器. 但是,在正常的项目当中,大家的需求都是不一样的,又或者碰上中途需求的改变,对相机的操作需求也不可能和我们做的简易版的相机控制器就可 ...

  3. 【Windows Server 2019】活动目录 (Active Directory) ——安装Acitve Directory域服务和提升为域控制器

    目录 5. 安装 Active Directory 域服务 实验目的 5.1 配置服务器的IP地址 5.2 安装Active Diretory服务 (1)添加角色和功能 (2)进入[开始之前]界面 ( ...

  4. 仿ios相机apk_icamera相机下载-icamera仿苹果软件v4.0_5577安卓网

    icamera仿苹果相机安卓下载推荐给大家!这是一款可以媲美苹果原生相机的手机软件,在拍摄手法和照片处理上还有着自己独特的见识,摄像功能也是相当强大,icamera相机还提供了专属滤镜,欢迎前来体验! ...

  5. vm虚拟服务器提升为域控制器,虚拟化域控制器体系结构

    虚拟化域控制器体系结构 05/31/2017 本文内容 适用范围:Windows Server 2022.Windows Server 2019.Windows Server 2016.Windows ...

  6. 基于Away3D实现全景的相机控制器。

    最近研究打算做个全景的Demo,发现Away3D本身的天空盒跟全景属于两种完全不同东西.最后只能基于HoverController来扩展(原因是HoverController能提供的距离控制,类似拉近 ...

  7. uniapp框架如何实现仿微信相册插件 | 图视频编辑 + 压缩

    在上上篇文章中(),我们基于uniapp框架实现了仿微信相册中的拍照+录像功能.今天,就继续在uni-app中实现: 1: 图片编辑 2: 视频编辑 3: 文件压缩 技术实现 开发环境:Hbuilde ...

  8. ios仿微博个人首页

    前言 最近在公司拿到了一个仿微博个人主页效果的需求,于是在网上找了一个类似的demo.当时是直接拿来用的,之后空闲下来了,就研究了实现方法.于是写了这份笔记 分析 首先我们看看这个效果图 有以下几个特 ...

  9. Babylon.js 深入

    目录 1.第一章 动画 1. 设计动画 设计剪辑 反转动画 2. 动画方法描述 创建动画 设置关键帧 开始动画 可动画化 3. 排序动画 ​编辑 (1)设计:对于相机 (2)对于门 (3)对于灯光 4 ...

最新文章

  1. linux数据库什么意思,Linux系统中的数据库命令是什么
  2. H.264的一些资料整理
  3. mysql top 语句简介
  4. 17、Spring Boot普通类调用bean【从零开始学Spring Boot】
  5. VMware Server使用经验记录
  6. python 开发gui浏览器_Python编程之gui程序实现简单文件浏览器代码
  7. Python处理正则表达式超时的办法
  8. 菜鸟教程python3 mysql_Python 操作 MySQL 数据库
  9. 设置访问权限_【新思考教学者思】李世松:不要对经典设置访问权限
  10. python调用父类构造函数需要放在第一句吗_Python继承和调用父类构造函数
  11. Android-webview加载网页去除标题
  12. 修改input提示文字样式
  13. java 读书笔记_《java编程思想》读后感
  14. 基于SSM框架的狼途汽车门店管理系统的设计与实现
  15. qzezoj 1641 黑暗城堡
  16. “车”的故事,我的信息化建设和管理愚见
  17. win7系统盘瘦身秘诀
  18. 机器学习模型评估及性能评价(超全)
  19. 齐岳:环糊精修饰Fe3O4磁性纳米复合材料|十二烷基硫酸钠(SDS)将Fe3O4磁性纳米粒子定量地修饰到多壁碳纳米管
  20. 32位计算机怎么安装ps,ps怎么安装到电脑上(免费教你安装ps)

热门文章

  1. 小红伞AntiVir专区
  2. PCB设计入门—学习记录
  3. 印度理工学院有多难考?
  4. 大连理工计算机学硕能调剂到专硕吗,专家提醒:学硕调剂到专硕的注意事项
  5. 在美国读书的体会 [转]
  6. 在R语言中,使用“=”和“-”到底有什么不同? 就是等号和箭头号有什么区别,是完全一样还是局部不同?
  7. bodipy荧光染料BDP R6G maleimide/马来酰亚胺,CAS:2183473-32-5
  8. 华硕主板如何设置开机自启_华硕主板开机启动项调整的三种方法
  9. 公交IC卡读写器设计指南
  10. 天才数学家科学家用电脑研究出“彩票必胜公式必赢方案”,连中14次大奖