前言
源码
学习目标
- 创建相机轨道控制器
- 操控相机变换视图
知识点
- 位移
- 缩放
前情回顾
之前我们创建了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: 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.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。