【threejs】实现绕指定点/物体旋转

背景

实现方块A绕方块B旋转(方块B在原点,且不动)

 

思路

将方块A平移到方块B所在位置,旋转方块A,将方块A平移回去

方向上没错,但缺少细节,下面我们分两种情况实践一下。(区别就在于:平移时,使用的坐标系)

方法一:对方块A,基于世界坐标系平移,再基于自身/局部坐标系旋转,最后基于世界坐标系平移回去。

方法二:对方块A,基于自身坐标系平移,再基于自身/局部坐标系旋转,最后基于自身坐标系平移回去。

 

过程

方法一:

  • 基于世界坐标系平移(按照红轴方向,平移-N个单位)

  • 再基于自身/局部坐标系旋转(绕蓝轴旋转K度)

  • 最后基于世界坐标系平移回去(按照红轴方向,平移N个单位)

表现:对比最初的状态,方块A看起来是在原地绕自身旋转。

 

方法二:

  • 基于自身坐标系平移(按照红轴方向,平移-N个单位)

  • 再基于自身/局部坐标系旋转(绕蓝轴旋转K度)

  • 最后基于自身坐标系平移回去(按照红轴方向,平移N个单位)

表现:对比最初的状态,方块A表现出绕物体B旋转。

 

分析

将两方法结合对比可得:

第一步:效果一致

按世界坐标系的红轴 和 自身坐标系的红轴方向完全一致,所以平移效果一致。

第二步:效果一致

实现一致,都是按照自身坐标系旋转

第三步:效果不一致

方法一世界坐标系的红轴不受物体A旋转影响,世界的红轴方向不变。所以平移回去,还是最初的位置。

方法二自身坐标系受到的旋转的影响,此时自身坐标系的红轴方向改变,因此根据变化方向后的红轴平移回去的结果,和最初的位置不同。

 

疑问

Q:为什么第一步和第三步的平移,要么都是按世界坐标系,要么都是按自身坐标系?不可以第一步按世界坐标系,第三步按自身坐标系吗?

A:为了方便,因为计算第一步的平移矩阵M后,第三步的平移矩阵只需要用api求出M的逆矩阵

第二个问题是可以按照这个思路,你只要确保算出的平移矩阵是对的就行。

 

 

总结

绕定点/物体旋转,最简单的方式是:将动点基于自身坐标系平移到定点的位置,(动点)再基于自身坐标系旋转,最后(动点)基于自身坐标系平移回去。

 

部分核心ts代码

感受threejs 里multiply和premultiply的应用,

还涉及到“同一向量在不同基底上表示为不同坐标”的计算(应用场景:某物体在世界坐标系下平移到目标点的向量是T1,那转换到某物体的局部坐标系下,T1在局部坐标系下如何计算)。

(链接为gitchat专栏《机器学习中的数据:线性代数》 的第二篇 空间:从向量和基底谈起  ,专栏29元,16篇,个人觉得还挺划算的)

// 世界平移-自身旋转-世界平移的逆(表现为原地旋转)
    public worldTranslation_SelfRotate() {
        let worldPosZero = this.getWorldPosition(this.cubeArr[0]);//本质上就是Object3D里的getWorldPosition() 获取世界坐标
        let worldPosOne = this.getWorldPosition(this.cubeArr[1]);
        let worldOffset = worldPosZero.sub(worldPosOne);
        let goZeroMatrix = new Matrix4().makeTranslation(worldOffset.x, worldOffset.y, worldOffset.z);
        this.cubeArr[1].matrix.premultiply(goZeroMatrix);

        let angle = 30 * Math.PI / 180;
        let absAngle = Math.abs(angle);
        this.cubeArr[1].matrix.multiply(new THREE.Matrix4().makeRotationZ(absAngle));

        let mat4I = new THREE.Matrix4();
        mat4I.copy(goZeroMatrix).invert();// mat4I.getInverse(goZeroMatrix);getInverse函数已弃用
        this.cubeArr[1].matrix.premultiply(mat4I);
    }

    // 自身平移-自身旋转-自身平移的逆(表现为绕轴旋转)
    public SelfTranslation_SelfRotate() {
        let worldPosZero = this.getWorldPosition(this.cubeArr[0]);
        let worldPosOne = this.getWorldPosition(this.cubeArr[1]);
        let worldOffset = worldPosZero.sub(worldPosOne);
        // 世界空间转局部空间 
        let itemBaseVec = this.getBasisVec(this.cubeArr[1].matrix);//本质上就是extractBasis() 获取基坐标系
        let localOffset = this.vectorChangBasic(worldOffset,itemBaseVec);

        let goZeroMatrix = new Matrix4().makeTranslation(localOffset.x, localOffset.y, localOffset.z);
        this.cubeArr[1].matrix.multiply(goZeroMatrix);

        let angle = 30 * Math.PI / 180;
        let absAngle = Math.abs(angle);
        this.cubeArr[1].matrix.multiply(new THREE.Matrix4().makeRotationZ(absAngle));


        let mat4I = new THREE.Matrix4();
        mat4I.copy(goZeroMatrix).invert();// mat4I.getInverse(goZeroMatrix);getInverse函数已弃用
        this.cubeArr[1].matrix.multiply(mat4I);
    }

    private vectorChangBasic(needChangeVec: Vector3, baseVec: baseVectorObj) {
        // 世界空间的相对位置,转成基于baseVec坐标系下的位置
        let newVec: Vector3 = new Vector3();
        newVec.x = needChangeVec.dot(baseVec.x);
        newVec.y = needChangeVec.dot(baseVec.y);
        newVec.z = needChangeVec.dot(baseVec.z);
        return newVec;
    }

项目

https://github.com/LJLCarrien/threejsCube 分支 test_2Rotate

初衷:为了学下矩阵和threejs

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值