【Babylon提升】重写相机控制器,实现仿地图操作
实现仿地图操作
- 前言
- 一、需求分析
- 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解析
- 声明几个必要字段,记录相机的radius、Alpha、Beta,用于后续使用。
- 给场景增加两个事件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))
平移和旋转的逻辑解析:
- 鼠标按下,并不松开:开始一次平移或旋转。
- 鼠标移动:如果鼠标已经按下并未松开,触发平移或者旋转。
- 鼠标松开。结束一次平移或旋转。
然后根据以上事件逻辑,编写出代码就是上面那部分。
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提升】重写相机控制器,实现仿地图操作相关推荐
- Three.js漫游相机控制器/three.js第三人称视角漫游/three.js第一人称视角漫游
Three.js三维可视化引擎没有实现第三人称漫游控制器,第一人称实现的也很一般,大部分满足不了需求,需要自己手动去写一个.以下是模仿其它平台写的第三人称视角漫游,也可以简便的改写成第一人称. 下面是 ...
- 09 Controls相机控制器
我们在第七节的时候讲解过了相机的相关,也制作了一个简易的相机控制器. 但是,在正常的项目当中,大家的需求都是不一样的,又或者碰上中途需求的改变,对相机的操作需求也不可能和我们做的简易版的相机控制器就可 ...
- 【Windows Server 2019】活动目录 (Active Directory) ——安装Acitve Directory域服务和提升为域控制器
目录 5. 安装 Active Directory 域服务 实验目的 5.1 配置服务器的IP地址 5.2 安装Active Diretory服务 (1)添加角色和功能 (2)进入[开始之前]界面 ( ...
- 仿ios相机apk_icamera相机下载-icamera仿苹果软件v4.0_5577安卓网
icamera仿苹果相机安卓下载推荐给大家!这是一款可以媲美苹果原生相机的手机软件,在拍摄手法和照片处理上还有着自己独特的见识,摄像功能也是相当强大,icamera相机还提供了专属滤镜,欢迎前来体验! ...
- vm虚拟服务器提升为域控制器,虚拟化域控制器体系结构
虚拟化域控制器体系结构 05/31/2017 本文内容 适用范围:Windows Server 2022.Windows Server 2019.Windows Server 2016.Windows ...
- 基于Away3D实现全景的相机控制器。
最近研究打算做个全景的Demo,发现Away3D本身的天空盒跟全景属于两种完全不同东西.最后只能基于HoverController来扩展(原因是HoverController能提供的距离控制,类似拉近 ...
- uniapp框架如何实现仿微信相册插件 | 图视频编辑 + 压缩
在上上篇文章中(),我们基于uniapp框架实现了仿微信相册中的拍照+录像功能.今天,就继续在uni-app中实现: 1: 图片编辑 2: 视频编辑 3: 文件压缩 技术实现 开发环境:Hbuilde ...
- ios仿微博个人首页
前言 最近在公司拿到了一个仿微博个人主页效果的需求,于是在网上找了一个类似的demo.当时是直接拿来用的,之后空闲下来了,就研究了实现方法.于是写了这份笔记 分析 首先我们看看这个效果图 有以下几个特 ...
- Babylon.js 深入
目录 1.第一章 动画 1. 设计动画 设计剪辑 反转动画 2. 动画方法描述 创建动画 设置关键帧 开始动画 可动画化 3. 排序动画 编辑 (1)设计:对于相机 (2)对于门 (3)对于灯光 4 ...
最新文章
- linux数据库什么意思,Linux系统中的数据库命令是什么
- H.264的一些资料整理
- mysql top 语句简介
- 17、Spring Boot普通类调用bean【从零开始学Spring Boot】
- VMware Server使用经验记录
- python 开发gui浏览器_Python编程之gui程序实现简单文件浏览器代码
- Python处理正则表达式超时的办法
- 菜鸟教程python3 mysql_Python 操作 MySQL 数据库
- 设置访问权限_【新思考教学者思】李世松:不要对经典设置访问权限
- python调用父类构造函数需要放在第一句吗_Python继承和调用父类构造函数
- Android-webview加载网页去除标题
- 修改input提示文字样式
- java 读书笔记_《java编程思想》读后感
- 基于SSM框架的狼途汽车门店管理系统的设计与实现
- qzezoj 1641 黑暗城堡
- “车”的故事,我的信息化建设和管理愚见
- win7系统盘瘦身秘诀
- 机器学习模型评估及性能评价(超全)
- 齐岳:环糊精修饰Fe3O4磁性纳米复合材料|十二烷基硫酸钠(SDS)将Fe3O4磁性纳米粒子定量地修饰到多壁碳纳米管
- 32位计算机怎么安装ps,ps怎么安装到电脑上(免费教你安装ps)
热门文章
- 小红伞AntiVir专区
- PCB设计入门—学习记录
- 印度理工学院有多难考?
- 大连理工计算机学硕能调剂到专硕吗,专家提醒:学硕调剂到专硕的注意事项
- 在美国读书的体会 [转]
- 在R语言中,使用“=”和“-”到底有什么不同? 就是等号和箭头号有什么区别,是完全一样还是局部不同?
- bodipy荧光染料BDP R6G maleimide/马来酰亚胺,CAS:2183473-32-5
- 华硕主板如何设置开机自启_华硕主板开机启动项调整的三种方法
- 公交IC卡读写器设计指南
- 天才数学家科学家用电脑研究出“彩票必胜公式必赢方案”,连中14次大奖