css中的2d矩阵的写法
// transform: matrix(a, b, c, d, e, f);
// | a c e |
// | b d f |
// | 0 0 1 |
使用矩阵表示选择
// [
// 1 0 0
// 0 1 0
// 0 0 1
// ]
// known (x,y) startAngle(a) rotatedAngle(o) r
// unchanged variable r
// changed variable x' y'
// unknown (x',y')
旋转之后目标点的计算
// x' = r cos(a + o) y' = r sin(a + o)
二角和公式
// x' = r [cos(a)cos(o) + sin(a)sin(o)]
// y' = r [sin(a)cos(o) - cos(a)sin(o)]
把x和y带入到 x' 和 y' 之后化简
// a = cos(θ)
// b = sin(θ)
// c = -sin(θ)
// d = cos(θ)
不用对坐标进行变换
// e = translateX (translate the element in the x-direction)
// f = translateY (translate the element in the y-direction)
使用四元数表示旋转
// quaternion
// multiply
// rotate v by q
// v' = q * v * q^*
// |q| = sqrt(w^2 + x^2 + y^2 + z^2) = 1
// q^* = w - x*i - y*j - z*k
四元数的简单实现,gpt生成的
class Quaternion {
constructor(w, x, y, z) {
this.w = w;
this.x = x;
this.y = y;
this.z = z;
}
multiply(q) {
return new Quaternion(this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z,
this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y,
this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x,
this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w)
}
static fromAxisRad(axis, rad) {
const half_rad = rad / 2;
const cos = Math.cos(half_rad);
const sin = Math.sin(half_rad);
const {x, y, z} = axis;
return new Quaternion(cos, sin * x, sin * y, sin * z)
}
// v' = q * v * q^*
rotateVector(v){
const qVector = new Quaternion(0, v.x, v.y, v.z);
const conjQ = this.conjugate();
const {x,y,z} = this.multiply(qVector).multiply(conjQ);
return {x,y,z}
}
conjugate() {
const {w, x, y, z} = this
return new Quaternion(w, x, y, z)
}
}
使用四元数来旋转
const axis = { x: 0, y: 1, z: 0 }; // 绕Y轴旋转
const angle = Math.PI / 4; // 45度
const q = Quaternion.fromAxisRad(axis, angle);
结合vue来实现这个有趣的动画 这里我们想要沿着某个中心来运动,而不是以自己为中心来旋转
<script setup lang="ts">
import {CoroutineManager} from "~/utils/Coroutine";
import {computedTransMatrix} from "~/utils/Math";
const containerRef:Ref<HTMLDivElement | null> = ref(null)
const data = reactive({
boxes: [
{classNames: "bg-pink-100", style: ""},
{classNames: "bg-pink-200", style: ""},
{classNames: "bg-pink-300", style: ""},
{classNames: "bg-pink-400", style: ""},
{classNames: "bg-pink-500", style: ""},
{classNames: "bg-pink-600", style: ""},
{classNames: "bg-pink-700", style: ""},
{classNames: "bg-pink-800", style: ""},
{classNames: "bg-blue-300", style: ""},
{classNames: "bg-blue-400", style: ""},
],
rotateAngle:0,
})
onMounted(() => {
// const group = document.querySelector(".group");
// const boxes = Array.from(group.children);
GCM.startCoroutine(animationCo())
GCM.startCoroutine(tickerCo())
})
async function* tickerCo() {
while (1) {
yield await CoroutineManager.WaitForEndOfFrame()
data.rotateAngle +=.1
}
}
async function* animationCo() {
while (1) {
render()
yield await CoroutineManager.WaitForEndOfFrame()
}
}
function render() {
let containerEl = unref(containerRef);
if(!containerEl){
return
}
const {clientWidth: contW, clientHeight: contH} = containerEl;
for (let i = 0; i < data.boxes.length; i++) {
const box = data.boxes[i];
// const {clientWidth: boxW, clientHeight: boxH} = box;
const boxW = 48;
const boxH = 48;
const t = computedTransMatrix(boxW, boxH, contW, contH, data.rotateAngle * (i+1), 100);
box.style = `transform:${t}`
}
}
</script>
<template>
<div ref="containerRef" class="size-[400px] relative">
<div v-for="(box,i) in data.boxes" :key="box.classNames" class="absolute left-0 top-0 size-12 rounded" :class="box.classNames" :style="box.style">
<div class="size-full grid place-items-center text-white font-bold">
{{i}}
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>
相关数学工具的函数
export const angle2Rad = (ang:number) => {
return ang / 180 * Math.PI
}
export const getCenterPos = (nodeW:number, nodeH:number, containerW:number, containerH:number) => {
return {
x: Math.floor(containerH / 2) - Math.floor(nodeH / 2),
y: Math.floor(containerH / 2) - Math.floor(nodeH / 2),
}
}
export const getMoveByAngle = (rotatedAng:number, moveDistance:number) => {
const rad = angle2Rad(rotatedAng);
const r = moveDistance
const y = Math.sin(rad) * r;
const x = Math.cos(rad) * r;
return {x, y}
}
export const computedTransMatrix = (nodeW:number, nodeH:number, containerW:number, containerH:number, rotatedAng:number, moveDistance:number) => {
const centerPos = getCenterPos(nodeW, nodeH, containerW, containerH);
const moveDis = getMoveByAngle(rotatedAng, moveDistance);
// calc final pos
const {x, y} = Array.from([
moveDis,
centerPos
]).reduceRight((a, {x, y}) => {
return {x: a.x + x, y: a.y + y}
});
return `matrix(1,0,0,1,${x},${y})`
}