Object2D二维对象

前言

学习目标

  • 创建二维对象
  • 理解二维对象的基本功能

知识点

  • 矩阵变换

前情回顾

之前我们创建了EventDispatcher 对象,接下来我们接着往上写Object2D对象。

image-20230301224856781

1-Object2D对象的功能分析

我们之前做架构的时候说过,Object2D 是所有二维对象的基类,其中的属性是所有二维对象都具备的,比如模型矩阵相关的属性。

模型矩阵相关的属性就是物体的旋转、缩放和位移属性,这三个属性是所有对象都应该具备的。

与此同时,我们还可以给它一些其它属性,比如可见性、渲染顺序、名称等。

2-Object2D对象的代码实现

其整体代码如下:

  • /src/lmm/objects/Object2D.ts
import { Vector2 } from '../math/Vector2'
import { Group } from './Group'
import { Scene } from '../core/Scene'
import { EventDispatcher } from '../core/EventDispatcher'
import { Matrix3 } from '../math/Matrix3'
import { generateUUID } from '../math/MathUtils.js'

export type Object2DType = {
    position?: Vector2
    rotate?: number
    scale?: Vector2
    visible?: boolean
    index?: number
    name?: string
    parent?: Scene | Group | undefined
    enableCamera?: boolean
    [key: string]: any
}

class Object2D extends EventDispatcher {
    // 自定义属性
    [key: string]: any
    // 位置
    position = new Vector2()
    // 旋转
    rotate = 0
    // 缩放
    scale = new Vector2(1, 1)
    // 可见性
    visible = true
    // 渲染顺序
    index = 0
    // 名称
    name = ''
    // 父级
    parent: Scene | Group | undefined
    // 是否受相机影响-只适用于Scene的children元素
    enableCamera = true
    // UUID
    uuid = generateUUID()

    // 类型
    readonly isObject2D = true

    /* 本地模型矩阵 */
    get matrix(): Matrix3 {
        const { position, rotate, scale } = this
        return new Matrix3()
            .scale(scale.x, scale.y)
            .rotate(rotate)
            .translate(position.x, position.y)
    }

    /* 世界模型矩阵 */
    get worldMatrix(): Matrix3 {
        const { parent, matrix } = this
        if (parent) {
            return parent.worldMatrix.multiply(matrix)
        } else {
            return matrix
        }
    }

    /* pvm 投影视图模型矩阵 */
    get pvmMatrix(): Matrix3 {
        const scene = this.getScene()
        if (scene) {
            const { camera } = scene
            return new Matrix3().multiplyMatrices(camera.pvMatrix, this.worldMatrix)
        } else {
            return this.worldMatrix
        }
    }

    /* 总缩放量 */
    get worldScale(): Vector2 {
        const { scale, parent } = this
        if (parent) {
            return scale.clone().multiply(parent.worldScale)
        } else {
            return scale
        }
    }

    /* 先变换(缩放+旋转)后位移 */
    transform(ctx: CanvasRenderingContext2D) {
        const { position, rotate, scale } = this
        ctx.translate(position.x, position.y)
        ctx.rotate(rotate)
        ctx.scale(scale.x, scale.y)
    }

    /* 从父级中删除自身 */
    remove() {
        const { parent } = this
        parent && parent.remove(this)
    }

    /* 获取场景 */
    getScene(): Scene | null {
        if ('isScene' in this) {
            return this as unknown as Scene
        } else if (this.parent) {
            return this.parent.getScene()
        } else {
            return null
        }
    }

    /* 绘图 */
    draw(ctx: CanvasRenderingContext2D) {
        if (!this.visible) {
            return
        }
        ctx.save()
        /*  矩阵变换 */
        this.transform(ctx)
        /* 绘制图形 */
        this.drawShape(ctx)
        ctx.restore()
    }

    /* 绘制图形-接口 */
    drawShape(ctx: CanvasRenderingContext2D) {}

    /* 创建路径-接口 */
    crtPath(ctx: CanvasRenderingContext2D, projectionMatrix: Matrix3) {}
}

export { Object2D }

Object2DType 是之后用于设置Object2D的子类的构造参数的。

Object2D对象的属性

  • position、rotate、scale 是Object2D 对象的属性,position和scale 是Vector2() 对象,因为我们开发的就是二维项目。rotate 是绕z轴(垂直于屏幕的轴)旋转的度数,所以是个数字。
  • visible:可见性,渲染时会用它判断是否绘制当前对象。
  • index:渲染顺序,从小到大绘制。
  • name:当前对象的名称,便于查找和识别。
  • parent :父对象。
  • enableCamera:表示绘制当前对象时是否受相机矩阵影响,只适用于Scene的子级元素。
  • uuid:唯一标识。
  • isObject2D:当前对象的类型判断。

后面还有几个通过get 取值的属性:

  • matrix:本地矩阵模型,是通过position, rotate, scale合成的。
get matrix(): Matrix3 {
  const { position, rotate, scale } = this
  return new Matrix3()
    .scale(scale.x, scale.y)
    .rotate(rotate)
    .translate(position.x, position.y)
}

我当前的这种变换方式是Matrix3对象里的绝对变换,即我先在世界坐标系里缩放,然后在世界坐标系里旋转,最后再在世界坐标系里位移。

其矩阵算法如下:

本地模型矩阵matrix=位移矩阵translate * 旋转矩阵rotate * 缩放矩阵scale
  • worldMatrix:世界模型矩阵,自上而下的本地模型矩阵相乘。
get worldMatrix(): Matrix3 {
    const { parent, matrix } = this
    if (parent) {

        return parent.worldMatrix.multiply(matrix)
    } else {

        return matrix
    }

}

  • pvmMatrix:投影视图模型矩阵,将相机的投影视图矩阵乘以物体的世界模型矩阵。
get pvmMatrix(): Matrix3 {
    const scene = this.getScene()
    if (scene) {
        const { camera } = scene
        return new Matrix3().multiplyMatrices(camera.pvMatrix, this.worldMatrix)
    } else {

        return this.worldMatrix
    }

}

  • worldScale:物体从本地到世界的总缩放量。
get worldScale(): Vector2 {
    const { scale, parent } = this
    if (parent) {

        return scale.clone().multiply(parent.worldScale)
    } else {

        return scale
    }

}

Object2D对象的方法

  • transform() :基于模型矩阵的变换。
transform(ctx: CanvasRenderingContext2D) {
    const { position, rotate, scale } = this
    ctx.translate(position.x, position.y)
    ctx.rotate(rotate)
    ctx.scale(scale.x, scale.y)
}

translate(),rotate(),scale() 都是canvas 内置的矩阵方法,它会自动与图形的初始点位做运算。

当然我们想自己变换图形顶点也不行,因为除了moveTo(),lineTo()路径,canvas 并没把其它图形顶点暴露给我们。

  • remove():从父级中删除自身。
remove() {
    const { parent } = this
    parent && parent.remove(this)
}

其父级必然是一个Group对象,之后我们会给Group对象一个remove() 子对象的方法。

  • getScene():获取场景。
getScene(): Scene | null {
    if ('isScene' in this) {
        return this as unknown as Scene
    } else if (this.parent) {
        return this.parent.getScene()
    } else {

        return null
    }

}

之后可能会在子对象中调用场景数据。

  • draw():绘图方法。
draw(ctx: CanvasRenderingContext2D) {
    if (!this.visible) {
        return
    }
    ctx.save()
    /*  矩阵变换 */
    this.transform(ctx)
    /* 绘制图形 */
    this.drawShape(ctx)
    ctx.restore()
}

此方法会封装好可见性、状态管理和变换方法,drawShape()会交给子类来覆盖。

/* 绘制图形-接口 */
drawShape(ctx: CanvasRenderingContext2D) {}
  • crtPath():在子类中建立路径,可用于绘图和选择。
/* 创建路径-接口 */
crtPath(ctx: CanvasRenderingContext2D, projectionMatrix: Matrix3) {}

总结

我们在搭建项目的过程中,若需求清晰,且我们自身也有一定的项目经验,那就可以像我当前这样自底向上一点点的搭建。

若我们有许多不确定的问题,也可以根据主功能,搭一个主干出来,然后再基于这个主干细化功能。

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

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

昵称

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