Cocoscreator: 用四元数实现自转和公转

1,361 阅读2分钟

四元数(Quaternion)是一种复数扩展,用于表示在三维空间中的旋转。它由一个实部和三个虚部构成,可以用四个实数来表示:w+xi+yj+zkw + xi + yj + zk,其中 i,j,ki, j, k 是基本单位向量,满足以下关系:

i2=j2=k2=ijk=1i^2 = j^2 = k^2 = ijk = -1

通过四元数表达旋转时,我们通常将四元数的实部设为 00,而将虚部作为一个三维向量 v\vec{v} 表示旋转轴,并将其长度设为旋转角度 θ\theta。这样就形成了一个单位四元数:q=cos(θ/2)+vsin(θ/2)q = cos(\theta/2) + \vec{v}sin(\theta/2)

四元数的好处是能够快速地进行旋转的叠加运算,比如顺序执行两个旋转,可以将它们对应的四元数相乘得到新的旋转对应的四元数。同时,四元数不会存在万向锁问题,因此在计算机图形学领域广泛使用。

如题,假如我们要实现地球,月球的自转以及月球的公转,具体来说,需要两个变量保存地球和月球的旋转四元数。每帧更新时,先将这两个四元数分别绕着自己的 Y 轴旋转一定角度,然后再更新月球的位置,这个位置是参照地球的位置计算得出的。将月球坐标系中的 Z 轴旋转对应的角度转换到地球坐标系下,就可以得到相对于地球的偏移。

具体代码RotateAround.ts如下:

import { _decorator, Component, Node, Quat, Vec3, v3, quat } from 'cc';

const { ccclass, property } = _decorator;

@ccclass('RotateAround')
export class RotateAround extends Component {

    // 地球节点
    @property({ type: Node })
    earth: Node = null;

    // 月球节点
    @property({ type: Node })
    moon: Node = null;

    // 地球自转速度
    private earthRotationSpeed: number = 1;
    // 月球自转速度 
    private moonRotationSpeed: number = 2; 

    // 存储地球和月球节点自身旋转的四元数
    private earthRotation: Quat = quat();
    private moonRotation: Quat = quat();

    /** 地球节点位置 */
    private earthPos: Vec3 = v3();

    // start 函数会在脚本组件第一次被添加到节点上时被调用
    start () {
        // 复制地球和月球节点的旋转四元数
        this.earthRotation = this.earth.rotation.clone();
        this.moonRotation = this.moon.rotation.clone();
    }

    // update 函数会在每一帧被调用
    update (deltaTime: number) {
        this.updateEarthRotation(deltaTime);
        this.updateMoonRotation(deltaTime);
        this.updateMoonOrbit(deltaTime);
    }

    // 更新地球自身的旋转
    updateEarthRotation (deltaTime: number) {
        Quat.rotateY(this.earthRotation, this.earthRotation, this.earthRotationSpeed * deltaTime);
        this.earth.rotation = this.earthRotation;
    }

    // 更新月球自身的旋转
    updateMoonRotation (deltaTime: number) {
        Quat.rotateY(this.moonRotation, this.moonRotation, this.moonRotationSpeed * deltaTime);
        this.moon.rotation = this.moonRotation;
    }

    // 更新月球绕地球轨道的位置
    updateMoonOrbit (deltaTime: number) {
        // 获取地球当前的世界坐标位置,并存储到 earthPos 中
        this.earth.getWorldPosition(this.earthPos);
        // 指向地球节点的向量
        let dir = new Vec3(0, 0, -1);
        // 将 dir 向量绕着 moonRotation 所表示的轴进行旋转
        Vec3.transformQuat(dir, dir, this.moonRotation);
        // 计算出月球在轨道上应该的位置
        Vec3.scaleAndAdd(this.earthPos, this.earthPos, dir, 3);
        // 设置月球在场景中的位置为计算后的新位置
        this.moon.setWorldPosition(this.earthPos);
    }
}

将此文件添加到场景中的scriptNode节点上,并将bigBall和smallBall节点分别拉到右侧中Earth和Moon选项中,

image.png

点击运行后,效果如下:

ofrul-60byh.gif

由于没有地球和月球模型,这里简单用足球模型表示。

到这里就简单实现用四元数实现了地球的公转和月球的自转