二维相机轨道控制器

前言

学习目标

  • 创建相机轨道控制器
  • 操控相机变换视图

知识点

  • 位移
  • 缩放

前情回顾

之前我们创建了Scene对象,接下来我们建立OrbitControler对象。

image-20230301224856781

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 }
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.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>
<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。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYqAkYZ0' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片