我有以下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta property="og:type" content="website">
<title>RGBCube</title>
<meta name="author" content="RGBCube" />
<meta property="og:site_name" content="RGBCube" />
<meta property="og:title" content="RGBCube" />
<meta name="description" content="The official website and link portal of RGBCube and his work." />
<meta property="og:description" content="The official website and link portal of RGBCube and his work." />
<link rel="icon" href="favicon.gif" type="image/gif">
<meta property="og:image" content="/thumbnail.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:height" content="" />
<meta property="og:image:width" content="" />
<meta property="og:url" content="https://rgbcube.github.io/" />
<link rel="canonical" href="https://rgbcube.github.io/">
</head>
<body>
<style>
@font-face {
font-family: "Bai Jamjuree";
font-weight: 700;
src: url(BaiJamjuree700.woff2) format(woff2);
}
body,
html {
height: 100%;
margin: 0;
}
html {
background-color: #000000;
font-family: "Bai Jamjuree", sans;
font-size: 450%;
}
a {
color: #000000;
text-decoration-line: none;
}
.frame {
background-color: #FFFFFF;
width: min-content;
padding: 0 .3em;
border-radius: 1em;
user-select: none;
}
.frame:hover {
background-color: #FFFF00;
}
.scene {
height: 100%;
width: 100%;
perspective: 15em;
display: flex;
align-items: center;
justify-content: center;
}
.cube {
height: 5em;
width: 5em;
position: relative;
transform: translateZ(-2.5em);
transform-style: preserve-3d;
}
.face {
background-size: cover;
background-position: center;
width: 5em;
height: 5em;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
}
.face::after {
z-index: -1;
content: "";
width: inherit;
height: inherit;
position: absolute;
}
.front {
transform: rotateY(0deg) translateZ(2.5em);
background: linear-gradient(to bottom, cyan, blue);
}
.front::after {
background: linear-gradient(to bottom, white, magenta);
mask-image: linear-gradient(to left, magenta, transparent);
}
.top {
transform: rotateX(90deg) translateZ(2.5em);
background: linear-gradient(to bottom, lime, cyan);
}
.top::after {
background: linear-gradient(to bottom, yellow, white);
mask-image: linear-gradient(to left, white, transparent);
}
.back {
transform: rotateY(180deg) translateZ(2.5em);
background: linear-gradient(to bottom, yellow, red);
}
.back::after {
background: linear-gradient(to bottom, lime, black);
mask-image: linear-gradient(to left, black, transparent);
}
.bottom {
transform: rotateX(-90deg) translateZ(2.5em);
background: linear-gradient(to bottom, blue, black);
}
.bottom::after {
background: linear-gradient(to bottom, magenta, red);
mask-image: linear-gradient(to left, red, transparent);
}
.right {
transform: rotateY(90deg) translateZ(2.5em);
background: linear-gradient(to bottom, white, magenta);
}
.right::after {
background: linear-gradient(to bottom, yellow, red);
mask-image: linear-gradient(to left, red, transparent);
}
.left {
transform: rotateY(-90deg) translateZ(2.5em);
background: linear-gradient(to bottom, lime, black);
}
.left::after {
background: linear-gradient(to bottom, cyan, blue);
mask-image: linear-gradient(to left, blue, transparent);
}
</style>
<div class="scene">
<div class="cube">
<div class="face front">
<a href="/contact">
<div class="frame">
<p>contact</p>
</div>
</a>
</div>
<div class="face top">
<a href="https://github.com/RGBCube">
<div class="frame">
<p>github</p>
</div>
</a>
</div>
<div class="face back">
<a href="/">
<div class="frame">
<p></p>
</div>
</a>
</div>
<div class="face bottom">
<a href="/">
<div class="frame">
<p></p>
</div>
</a>
</div>
<div class="face right">
<a href="/">
<div class="frame">
<p></p>
</div>
</a>
</div>
<div class="face left">
<a href="/">
<div class="frame">
<p></p>
</div>
</a>
</div>
</div>
</div>
<script>
const __cube = document.querySelector(".cube");
class Vec3 {
static up = new Vec3(0, 1, 0);
static right = new Vec3(1, 0, 0);
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
normalize() {
const length = Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2);
if (length != 0) {
this.x /= length;
this.y /= length;
this.z /= length;
}
}
static sub(v, t) {
return new Vec3(
v.x - t.x,
v.y - t.y,
v.z - t.z,
)
}
}
class Vec2 extends Vec3 {
constructor(x, y) {
super(x, y, 0)
}
}
class Quat {
constructor(x, y, z, w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
static fromAngleAxis(angle, axis) {
axis.normalize();
const half = angle / 2;
const sinHalf = Math.sin(half);
const cosHalf = Math.cos(half);
const x = axis.x * sinHalf;
const y = axis.y * sinHalf;
const z = axis.z * sinHalf;
const w = cosHalf;
return new Quat(x, y, z, w);
}
static mul(q, r) {
return new Quat(
q.w * r.x + q.x * r.w + q.y * r.z - q.z * r.y,
q.w * r.y - q.x * r.z + q.y * r.w + q.z * r.x,
q.w * r.z + q.x * r.y - q.y * r.x + q.z * r.w,
q.w * r.w - q.x * r.x - q.y * r.y - q.z * r.z,
);
}
apply() {
__cube.style.transform = `rotate3d(${this.x}, ${this.y}, ${this.z}, ${this.w}rad)`;
}
}
let friction = 0.01;
let sensitivity = 0.01;
let velocity = 0;
const orientation = {
__value: new Quat(0, 0, 0, 1),
set(value) {
this.__value = value;
this.__value.apply();
},
get() {
return this.__value;
},
};
(() => {
const mouse = {
down: false,
lastMove: window.performance.now(),
previous: new Vec2(0, 0),
};
document.addEventListener("mouseleave", () => {
mouse.down = false;
});
document.addEventListener("mouseup", () => {
mouse.down = false;
});
document.addEventListener("mousedown", (event) => {
// Disables link dragging that occurs when spinning.
event.preventDefault();
mouse.down = true;
});
document.addEventListener("mousemove", (event) => {
if (!mouse.down) return;
const newMouse = new Vec2(event.clientX, event.clientY);
if (window.performance.now() - mouse.lastMove > 100) {
// This is a fresh scroll.
mouse.previous = newMouse;
}
const delta = Vec2.sub(newMouse, mouse.previous);
mouse.previous = newMouse;
mouse.lastMove = window.performance.now();
const rotation = Quat.mul(
Quat.fromAngleAxis(delta.x * sensitivity, Vec3.up),
Quat.fromAngleAxis(delta.y * sensitivity, Vec3.right),
);
orientation.set(Quat.mul(orientation.get(), rotation));
});
})();
</script>
</body>
</html>
我看不出我哪里做错了,我使用 .fromAngleAxis 使用鼠标增量计算移动四元数并将其应用于方向,但立方体根本无法正确旋转。
修复此问题后,我想使用速度和摩擦力(当前未使用)保持立方体旋转,因此任何与此相关的提示/代码将不胜感激。
这是我希望它如何表现的示例:https://projects.defmech.com/thirdjsobjectrotationwithquaternion/
解决了,这是未来任何人的代码:
或者如果您想要交互式演示,请看这里:https://rgbcube.github.io/
"use strict";
class Vec3 {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
static zero() {
return new Vec(0, 0, 0);
}
length() {
return Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2);
}
scale(factor) {
this.x *= factor;
this.y *= factor;
this.z *= factor;
return this;
}
normalize() {
const length = this.length();
if (length != 0) {
this.x /= length;
this.y /= length;
this.z /= length;
}
return this;
}
static sub(v, t) {
return new Vec3(
v.x - t.x,
v.y - t.y,
v.z - t.z,
)
}
static sum(v, t) {
return new Vec3(
v.x + t.x,
v.y + t.y,
v.z + t.z,
)
}
}
class Quat {
constructor(x, y, z, w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
static fromAxis(axis) {
const angle = axis.length();
axis.normalize();
const half = angle / 2;
const sinHalf = Math.sin(half);
const cosHalf = Math.cos(half);
const x = axis.x * sinHalf;
const y = axis.y * sinHalf;
const z = axis.z * sinHalf;
const w = cosHalf;
return new Quat(x, y, z, w);
}
static mul(q, r) {
return new Quat(
q.w * r.x + q.x * r.w + q.y * r.z - q.z * r.y,
q.w * r.y - q.x * r.z + q.y * r.w + q.z * r.x,
q.w * r.z + q.x * r.y - q.y * r.x + q.z * r.w,
q.w * r.w - q.x * r.x - q.y * r.y - q.z * r.z,
);
}
}
let friction = 3;
let sensitivity = 0.01;
let velocity = new Vec3(0, 0, 0);
const orientation = {
__cube: document.querySelector(".cube"),
__value: new Quat(0, 0, 0, 1),
set(value) {
this.__value = value;
const q = this.__value;
this.__cube.style.transform = `rotate3d(${q.x}, ${q.y}, ${q.z}, ${Math.acos(q.w) * 2}rad)`;
},
get() {
return this.__value;
},
};
{
const mouse = {
down: false,
lastMove: -10_000,
previous: null,
};
let impulseThisFrame = new Vec3(0, 0, 0);
const handleUp = () => {
mouse.down = false;
};
document.addEventListener("mouseup", handleUp);
document.addEventListener("touchend", handleUp);
const handleDown = (event) => {
// Disables link dragging that occurs when spinning.
event.preventDefault();
mouse.down = true;
velocity = new Vec3(0, 0, 0);
};
document.addEventListener("mousedown", handleDown);
document.addEventListener("touchstart", handleDown);
const handleMove = (event) => {
// Disables scrolling.
event.preventDefault();
if (!mouse.down) return;
const newMouse = new Vec3(event.clientX, event.clientY, 0);
const timeDelta = (window.performance.now() - mouse.lastMove) / 1000;
if (timeDelta > 0.1) {
// This is a fresh scroll.
mouse.previous = newMouse;
}
const delta = Vec3.sub(newMouse, mouse.previous);
mouse.previous = newMouse;
mouse.lastMove = window.performance.now();
const axis = new Vec3(-delta.y, delta.x, 0)
.normalize()
.scale(delta.length() * sensitivity);
impulseThisFrame = Vec3.sum(impulseThisFrame, axis);
const rotation = Quat.fromAxis(axis);
orientation.set(Quat.mul(rotation, orientation.get()));
};
document.addEventListener("mousemove", handleMove);
document.addEventListener("touchmove", (event) => {
const delta = event.changedTouches[0];
event.clientX = delta.clientX;
event.clientY = delta.clientY;
handleMove(event);
});
let lastUpdate = 0;
const updateFrame = (timestamp) => {
if (lastUpdate == 0) lastUpdate = timestamp;
const delta = (timestamp - lastUpdate) / 1000;
lastUpdate = timestamp;
if (mouse.down) {
velocity = impulseThisFrame.scale(1 / delta);
impulseThisFrame = new Vec3(0, 0, 0);
} else {
const decay = Math.exp(-delta * friction);
const effectiveDelta = friction > 0 ? (1 - decay) / friction : delta;
let theta = effectiveDelta * velocity.length();
velocity.x *= decay;
velocity.y *= decay;
velocity.z *= decay;
if (friction > 0 && velocity.length() < 0.00001) {
theta += velocity.length() / friction;
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
}
if (window.performance.now() - mouse.lastMove > 10_000) {
velocity = new Vec3(1, 1, -1);
}
const axis = new Vec3(velocity.x, velocity.y, velocity.z)
.normalize()
.scale(theta);
const rotation = Quat.fromAxis(axis);
orientation.set(Quat.mul(rotation, orientation.get()));
}
requestAnimationFrame(updateFrame);
};
updateFrame(0);
}