前言
学习目标
- 创建相机轨道控制器
- 操控相机变换视图
知识点
- 位移
- 缩放
前情回顾
之前我们创建了Scene对象,接下来我们建立OrbitControler对象。
1-OrbitControler对象的功能分析
OrbitControler的功能是操控相机变换视图。
OrbitControler会在鼠标按下的时候,平移视图;在滑动滚轮的时候,缩放视口。
2-OrbitControler对象的代码实现
其整体代码如下:
- /src/lmm/core/Scene.ts
import { Vector2 } from '../math/Vector2'import { EventDispatcher } from '../core/EventDispatcher'import { Camera } from '../core/Camera'/* change 事件 */const _changeEvent = { type: 'change' }/* 暂存数据类型 */type Stage = {cameraZoom: numbercameraPosition: Vector2panStart: Vector2}/* 配置项 */type Option = {camera?: CameraenableZoom?: booleanzoomSpeed?: numberenablePan?: booleanpanSpeed?: number}/* 相机轨道控制 */class OrbitControler extends EventDispatcher {// 相机camera: Camera// 允许缩放enableZoom = true// 缩放速度zoomSpeed = 3.0// 允许位移enablePan = true// 位移速度panSpeed = 1.0// 是否正在拖拽中panning = false//变换相机前的暂存数据stage: Stage = {cameraZoom: 1,cameraPosition: new Vector2(),panStart: new Vector2(),}constructor(camera: Camera, option: Option = {}) {super()this.camera = camerathis.setOption(option)}/* 设置属性 */setOption(option: Option) {Object.assign(this, option)}/* 缩放 */doScale(deltaY: number) {const { enableZoom, camera, zoomSpeed, stage } = thisif (!enableZoom) {return}stage.cameraZoom = camera.zoomconst scale = Math.pow(0.95, zoomSpeed)if (deltaY > 0) {camera.zoom /= scale} else {camera.zoom *= scale}this.dispatchEvent(_changeEvent)}/* 鼠标按下 */pointerdown(cx: number, cy: number) {const {enablePan,stage: { cameraPosition, panStart },camera: { position },} = thisif (!enablePan) {return}this.panning = truecameraPosition.copy(position)panStart.set(cx, cy)}/* 鼠标抬起 */pointerup() {this.panning = false}/* 位移 */pointermove(cx: number, cy: number) {const {enablePan,camera: { position },stage: {panStart: { x, y },cameraPosition,},panning,} = thisif (!enablePan || !panning) {return}position.copy(cameraPosition.clone().add(new Vector2(x - cx, y - cy)))this.dispatchEvent(_changeEvent)}}export { OrbitControler }import { Vector2 } from '../math/Vector2' import { EventDispatcher } from '../core/EventDispatcher' import { Camera } from '../core/Camera' /* change 事件 */ const _changeEvent = { type: 'change' } /* 暂存数据类型 */ type Stage = { cameraZoom: number cameraPosition: Vector2 panStart: Vector2 } /* 配置项 */ type Option = { camera?: Camera enableZoom?: boolean zoomSpeed?: number enablePan?: boolean panSpeed?: number } /* 相机轨道控制 */ class OrbitControler extends EventDispatcher { // 相机 camera: Camera // 允许缩放 enableZoom = true // 缩放速度 zoomSpeed = 3.0 // 允许位移 enablePan = true // 位移速度 panSpeed = 1.0 // 是否正在拖拽中 panning = false //变换相机前的暂存数据 stage: Stage = { cameraZoom: 1, cameraPosition: new Vector2(), panStart: new Vector2(), } constructor(camera: Camera, option: Option = {}) { super() this.camera = camera this.setOption(option) } /* 设置属性 */ setOption(option: Option) { Object.assign(this, option) } /* 缩放 */ doScale(deltaY: number) { const { enableZoom, camera, zoomSpeed, stage } = this if (!enableZoom) { return } stage.cameraZoom = camera.zoom const scale = Math.pow(0.95, zoomSpeed) if (deltaY > 0) { camera.zoom /= scale } else { camera.zoom *= scale } this.dispatchEvent(_changeEvent) } /* 鼠标按下 */ pointerdown(cx: number, cy: number) { const { enablePan, stage: { cameraPosition, panStart }, camera: { position }, } = this if (!enablePan) { return } this.panning = true cameraPosition.copy(position) panStart.set(cx, cy) } /* 鼠标抬起 */ pointerup() { this.panning = false } /* 位移 */ pointermove(cx: number, cy: number) { const { enablePan, camera: { position }, stage: { panStart: { x, y }, cameraPosition, }, panning, } = this if (!enablePan || !panning) { return } position.copy(cameraPosition.clone().add(new Vector2(x - cx, y - cy))) this.dispatchEvent(_changeEvent) } } export { OrbitControler }import { Vector2 } from '../math/Vector2' import { EventDispatcher } from '../core/EventDispatcher' import { Camera } from '../core/Camera' /* change 事件 */ const _changeEvent = { type: 'change' } /* 暂存数据类型 */ type Stage = { cameraZoom: number cameraPosition: Vector2 panStart: Vector2 } /* 配置项 */ type Option = { camera?: Camera enableZoom?: boolean zoomSpeed?: number enablePan?: boolean panSpeed?: number } /* 相机轨道控制 */ class OrbitControler extends EventDispatcher { // 相机 camera: Camera // 允许缩放 enableZoom = true // 缩放速度 zoomSpeed = 3.0 // 允许位移 enablePan = true // 位移速度 panSpeed = 1.0 // 是否正在拖拽中 panning = false //变换相机前的暂存数据 stage: Stage = { cameraZoom: 1, cameraPosition: new Vector2(), panStart: new Vector2(), } constructor(camera: Camera, option: Option = {}) { super() this.camera = camera this.setOption(option) } /* 设置属性 */ setOption(option: Option) { Object.assign(this, option) } /* 缩放 */ doScale(deltaY: number) { const { enableZoom, camera, zoomSpeed, stage } = this if (!enableZoom) { return } stage.cameraZoom = camera.zoom const scale = Math.pow(0.95, zoomSpeed) if (deltaY > 0) { camera.zoom /= scale } else { camera.zoom *= scale } this.dispatchEvent(_changeEvent) } /* 鼠标按下 */ pointerdown(cx: number, cy: number) { const { enablePan, stage: { cameraPosition, panStart }, camera: { position }, } = this if (!enablePan) { return } this.panning = true cameraPosition.copy(position) panStart.set(cx, cy) } /* 鼠标抬起 */ pointerup() { this.panning = false } /* 位移 */ pointermove(cx: number, cy: number) { const { enablePan, camera: { position }, stage: { panStart: { x, y }, cameraPosition, }, panning, } = this if (!enablePan || !panning) { return } position.copy(cameraPosition.clone().add(new Vector2(x - cx, y - cy))) this.dispatchEvent(_changeEvent) } } export { OrbitControler }
OrbitControler对象的属性比较简单,都有注释,我就不再多说。
doScale(deltaY) 方法对应的是滚轮事件,它会根据滚轮数据,设置相机的zoom属性,从而实现视图缩放。
pointerdown(cx: number, cy: number) 方法会在鼠标按下时,暂存鼠标状态。
3-OrbitControler对象的测试
在examples文件夹中建立一个OrbitControler.vue文件,用于测试。
<script setup lang="ts">import { ref, onMounted } from 'vue'import { OrbitControler } from '../lmm/controler/OrbitControler'import { Scene } from '../lmm/core/Scene'import { Vector2 } from '../lmm/math/Vector2'import { Img } from '../lmm/objects/Img'// 获取父级属性defineProps({size: { type: Object, default: { width: 0, height: 0 } },})// 对应canvas 画布的Ref对象const canvasRef = ref<HTMLCanvasElement>()/* 场景 */const scene = new Scene()/* 相机轨道控制器 */const orbitControler = new OrbitControler(scene.camera)/* 图案 */const image = new Image()image.src ='https://yxyy-pandora.oss-cn-beijing.aliyuncs.com/stamp-images/1.png'const pattern = new Img({ image })scene.add(pattern)/* 测试 */function test(canvas: HTMLCanvasElement) {const imgSize = new Vector2(image.width, image.height).multiplyScalar(0.6)pattern.setOption({/* 模型矩阵 */rotate: 0.4,position: new Vector2(0, -50),scale: new Vector2(0.5),/* Img属性 */size: imgSize.clone(),offset: imgSize.clone().multiplyScalar(-0.5),})/* 按需渲染 */orbitControler.addEventListener('change', () => {scene.render()})/* 滑动滚轮缩放 */canvas.addEventListener('wheel', ({ deltaY }) => {orbitControler.doScale(deltaY)})/* 按住滚轮平移 */canvas.addEventListener('pointerdown', (event: PointerEvent) => {if (event.button == 1) {orbitControler.pointerdown(event.clientX, event.clientY)}})canvas.addEventListener('pointermove', (event: PointerEvent) => {orbitControler.pointermove(event.clientX, event.clientY)})window.addEventListener('pointerup', (event: PointerEvent) => {if (event.button == 1) {orbitControler.pointerup()}})/* 渲染 */scene.render()}onMounted(() => {const canvas = canvasRef.valueif (canvas) {scene.setOption({ canvas })image.onload = function () {test(canvas)}}})</script><template><canvas ref="canvasRef" :width="size.width" :height="size.height"></canvas></template><style scoped></style><script setup lang="ts"> import { ref, onMounted } from 'vue' import { OrbitControler } from '../lmm/controler/OrbitControler' import { Scene } from '../lmm/core/Scene' import { Vector2 } from '../lmm/math/Vector2' import { Img } from '../lmm/objects/Img' // 获取父级属性 defineProps({ size: { type: Object, default: { width: 0, height: 0 } }, }) // 对应canvas 画布的Ref对象 const canvasRef = ref<HTMLCanvasElement>() /* 场景 */ const scene = new Scene() /* 相机轨道控制器 */ const orbitControler = new OrbitControler(scene.camera) /* 图案 */ const image = new Image() image.src = 'https://yxyy-pandora.oss-cn-beijing.aliyuncs.com/stamp-images/1.png' const pattern = new Img({ image }) scene.add(pattern) /* 测试 */ function test(canvas: HTMLCanvasElement) { const imgSize = new Vector2(image.width, image.height).multiplyScalar(0.6) pattern.setOption({ /* 模型矩阵 */ rotate: 0.4, position: new Vector2(0, -50), scale: new Vector2(0.5), /* Img属性 */ size: imgSize.clone(), offset: imgSize.clone().multiplyScalar(-0.5), }) /* 按需渲染 */ orbitControler.addEventListener('change', () => { scene.render() }) /* 滑动滚轮缩放 */ canvas.addEventListener('wheel', ({ deltaY }) => { orbitControler.doScale(deltaY) }) /* 按住滚轮平移 */ canvas.addEventListener('pointerdown', (event: PointerEvent) => { if (event.button == 1) { orbitControler.pointerdown(event.clientX, event.clientY) } }) canvas.addEventListener('pointermove', (event: PointerEvent) => { orbitControler.pointermove(event.clientX, event.clientY) }) window.addEventListener('pointerup', (event: PointerEvent) => { if (event.button == 1) { orbitControler.pointerup() } }) /* 渲染 */ scene.render() } onMounted(() => { const canvas = canvasRef.value if (canvas) { scene.setOption({ canvas }) image.onload = function () { test(canvas) } } }) </script> <template> <canvas ref="canvasRef" :width="size.width" :height="size.height"></canvas> </template> <style scoped></style><script setup lang="ts"> import { ref, onMounted } from 'vue' import { OrbitControler } from '../lmm/controler/OrbitControler' import { Scene } from '../lmm/core/Scene' import { Vector2 } from '../lmm/math/Vector2' import { Img } from '../lmm/objects/Img' // 获取父级属性 defineProps({ size: { type: Object, default: { width: 0, height: 0 } }, }) // 对应canvas 画布的Ref对象 const canvasRef = ref<HTMLCanvasElement>() /* 场景 */ const scene = new Scene() /* 相机轨道控制器 */ const orbitControler = new OrbitControler(scene.camera) /* 图案 */ const image = new Image() image.src = 'https://yxyy-pandora.oss-cn-beijing.aliyuncs.com/stamp-images/1.png' const pattern = new Img({ image }) scene.add(pattern) /* 测试 */ function test(canvas: HTMLCanvasElement) { const imgSize = new Vector2(image.width, image.height).multiplyScalar(0.6) pattern.setOption({ /* 模型矩阵 */ rotate: 0.4, position: new Vector2(0, -50), scale: new Vector2(0.5), /* Img属性 */ size: imgSize.clone(), offset: imgSize.clone().multiplyScalar(-0.5), }) /* 按需渲染 */ orbitControler.addEventListener('change', () => { scene.render() }) /* 滑动滚轮缩放 */ canvas.addEventListener('wheel', ({ deltaY }) => { orbitControler.doScale(deltaY) }) /* 按住滚轮平移 */ canvas.addEventListener('pointerdown', (event: PointerEvent) => { if (event.button == 1) { orbitControler.pointerdown(event.clientX, event.clientY) } }) canvas.addEventListener('pointermove', (event: PointerEvent) => { orbitControler.pointermove(event.clientX, event.clientY) }) window.addEventListener('pointerup', (event: PointerEvent) => { if (event.button == 1) { orbitControler.pointerup() } }) /* 渲染 */ scene.render() } onMounted(() => { const canvas = canvasRef.value if (canvas) { scene.setOption({ canvas }) image.onload = function () { test(canvas) } } }) </script> <template> <canvas ref="canvasRef" :width="size.width" :height="size.height"></canvas> </template> <style scoped></style>
在上面的代码中,我们可以按住鼠标滚轮移动视口,滑动鼠标滚轮缩放视口。
当然,相机的操作,最终还是要作用于模型上的。
总结
对于相机的操作,提前架构好底层的矩阵变换关系是很重要的,这会让我们之后的代码更加顺畅。
下一章我们会说当前课程最重要的部分-ImgControler。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END