前言
学习目标
- 创建二维对象
- 理解二维对象的基本功能
知识点
- 矩阵变换
前情回顾
之前我们创建了EventDispatcher 对象,接下来我们接着往上写Object2D对象。
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) {}
总结
我们在搭建项目的过程中,若需求清晰,且我们自身也有一定的项目经验,那就可以像我当前这样自底向上一点点的搭建。
若我们有许多不确定的问题,也可以根据主功能,搭一个主干出来,然后再基于这个主干细化功能。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END