变换

上次修改时间:2021-05-29 00:11:76

在创建一个新的节点时,会自动为该节点添加变换组件。变换组件能够对节点的位移,旋转,缩放,矩阵等进行操作,完成节点的几何变换。

/**
 * @title Transform
 * @category Basic
 */
import { AmbientLight, Camera, Color, DirectLight, Entity, GLTFResource, Script, WebGLEngine } from "oasis-engine";
import { OrbitControl } from "@oasis-engine/controls";

// Create engine
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

// Create yellow duck
engine.resourceManager
  .load<GLTFResource>("https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf")
  .then((gltf) => {
    // Create root entity.
    const rootEntity = engine.sceneManager.activeScene.createRootEntity();

    // Create camera.
    const cameraEntity = rootEntity.createChild("CameraEntity");
    cameraEntity.transform.setPosition(0, 3, 9);
    cameraEntity.addComponent(Camera);
    cameraEntity.addComponent(OrbitControl);

    // Create light.
    const lightEntity = rootEntity.createChild("LightEntity");
    const ambient = lightEntity.addComponent(AmbientLight);
    const directLight = lightEntity.addComponent(DirectLight);
    ambient.color = new Color(0.5, 0.5, 0.5);
    directLight.color = new Color(0.5, 0.5, 0.5);

    // Create three duck modles, set rotation and position.
    const duck0 = gltf.defaultSceneRoot;
    duck0.transform.rotate(0, -45, 0);

    const duck1 = duck0.clone();
    const duck2 = duck0.clone();
    duck1.transform.setPosition(-3, 0, 0);
    duck2.transform.setPosition(3, 0, 0);

    // Create root entity and add transform script.
    const script = rootEntity.addComponent(TransformScript);
    script.duck0 = duck0;
    script.duck1 = duck1;
    script.duck2 = duck2;

    // Add ducks to scene.
    rootEntity.addChild(duck0);
    rootEntity.addChild(duck1);
    rootEntity.addChild(duck2);

    //Run engine.
    engine.run();
  });

/**
 * Script for updating ducks position, rotation, and scale.
 */
class TransformScript extends Script {
  /** Duck0. */
  duck0: Entity;
  /** Duck1. */
  duck1: Entity;
  /** Duck2. */
  duck2: Entity;

  private _totalTime: number = 0;

  /**
   * @override
   * The main loop, called frame by frame.
   * @param deltaTime - The deltaTime when the script update.
   */
  onUpdate(deltaTime: number): void {
    this._totalTime += deltaTime;
    const sinFactor = Math.sin(this._totalTime / 500);

    // Update duck0's position.
    const positionFactor = Math.max(sinFactor, 0);
    this.duck0.transform.setPosition(0, positionFactor, 0);

    // Update duck1's roatation.
    const rotateFactor = this._totalTime * 0.1;
    this.duck1.transform.setRotation(0, rotateFactor, 0);

    // Update duck2's scale.
    const scaleFactor = (sinFactor + 1.0) * 0.01;
    this.duck2.transform.setScale(scaleFactor, scaleFactor, scaleFactor);
  }
}

基本用法

// 创建节点
const scene = engine.sceneManager.activeScene;
const root = scene.createRootEntity('root');
const cubeEntity = root.createChild('cube');

// entity 在创建后会默认自带变换组件
// 通过变换组件能够对实体进行几何变换

// 修改节点位移,旋转,缩放
transform.position = new Vector3();
// 也可以 transform.setPosition(0, 0, 0);

transform.rotation = new Vector3(90, 0, 0);
// 也可以 transform.setRotation(90, 0, 0);

// 也可以通过实体的属性获取到 transform 组件
cubeEntity.transform.scale = new Vector3(2, 1, 1);
// 也可以 cubeEntity.transform.setScale(2, 1, 1);

// 局部位移 cube 实体
cubeEntity.transform.translate(new Vector3(10, 0, 0), true);

// 局部旋转 cube 实体
cubeEntity.transform.rotate(new Vector3(45, 0, 0), true);

组件属性

属性名称属性释义
position局部位移
rotation局部旋转 - 欧拉角
rotationQuaternion局部旋转 - 四元数
scale局部缩放
worldPosition世界位移
worldRotation世界旋转 - 欧拉角
worldRotationQuaternion世界旋转 - 四元数
lossyWorldScale世界有损缩放 - 当父节点有缩放,子节点有旋转时,缩放会倾斜,无法使用Vector3正确表示,必须使用Matrix3x3矩阵才能正确表示
localMatrix局部矩阵
worldMatrix世界矩阵

组件方法

方法名称方法释义
getWorldUp获取世界矩阵上向量
getWorldRight获取世界矩阵右向量
getWorldForward获取世界矩阵前向量
lookAt旋转并且保证世界前向量指向目标世界位置
registerWorldChangeFlag注册世界变换改变标记
rotate根据指定欧拉角旋转
rotateByAxis根据指定角度绕着指定轴旋转
translate根据指定的方向和距离进行位移

常见QA

  • 为什么要添加 transform 组件?

3D 场景中,物体的几何变换应该统一进行管理,而不是把变换相关的数据杂糅到节点类中。 同时,新的 transform 组件内部用脏标记作了大量优化,优化计算,提升性能。

  • registerWorldChangeFlag 有什么作用?

当外部需要关注当前 transform 的 worldMatrix 变化时,则可以调用这个方法,这个方法会返回一个更新标记,当前 transform 的 worldMatrix 被修改时会触发标记的更改。具体用法可以参考:

class Camera {
	onAwake() {
  	this._transform = this.entity.transform;
    this._isViewMatrixDirty = this._transform.registerWorldChangeFlag();
  }
  get viewMatrix() {
  	if (this._isViewMatrixDirty.flag) {
      this._isViewMatrixDirty.flag = false;
      Matrix.invert(this._transform.worldMatrix, this._viewMatrix);
    }
    return this._viewMatrix;
  }
}