Anonymous
Guter Code-Rig-Editor, aber er braucht Hilfe [geschlossen]
Post
by Anonymous » 23 Dec 2025, 09:21
Hier der Code, der repariert werden muss. Ich muss gerade für 5 Tage weg. Wenn Sie ihn reparieren können, wäre das großartig, aber hier ist die bessere, aber keine vollständige Korrekturversion. Hinweis: Bitte stimmen Sie nicht ab. Wenn Sie ihn reparieren können, tun Sie dies bitte und können Sie einen Kommentar abgeben, z. B. „Ist es möglich und dies ist kein Backup?“
Code: Select all
let scene, camera, renderer;
let controls, transformControls;
let chicken;
let bones = [];
let boneDots = [];
let frames = [];
let frameTimes = [];
let undoStack = [];
let redoStack = [];
let restPose = [];
let playing = false;
let playIndex = 0;
let playTimer = 0;
let last = performance.now();
const raycaster = new THREE.Raycaster();
raycaster.params.Sprite.threshold = 1.2;
const pointer = new THREE.Vector2();
let isTouching = false;
let lastTouchY = 0;
/* ================= INIT ================= */
init();
loadChicken();
animate();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x444444);
camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 3, 8);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.enablePan = true;
transformControls = new THREE.TransformControls(camera, renderer.domElement);
transformControls.addEventListener("dragging-changed", e => {
controls.enabled = !e.value;
});
scene.add(transformControls);
scene.add(new THREE.AmbientLight(0xffffff, 0.7));
const d = new THREE.DirectionalLight(0xffffff, 0.8);
d.position.set(5, 10, 5);
scene.add(d);
renderer.domElement.addEventListener("pointerdown", onPointerDown, {
passive: false
});
renderer.domElement.addEventListener("pointermove", onPointerMove, {
passive: false
});
renderer.domElement.addEventListener("pointerup", () => isTouching = false);
window.addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
}
/* ================= LOAD ================= */
function loadChicken() {
const loader = new THREE.FBXLoader();
const tex = new THREE.TextureLoader();
loader.load("https://itswardengod.github.io/wardengame/Chicken.FBX", obj => {
chicken = obj;
chicken.scale.setScalar(0.03);
const main = tex.load("https://itswardengod.github.io/wardengame/Main.png");
const alpha = tex.load("https://itswardengod.github.io/wardengame/Opacity.png");
obj.traverse(c => {
if (c.isMesh) {
c.material.map = main;
c.material.alphaMap = alpha;
c.material.alphaTest = 0.5;
c.material.side = THREE.DoubleSide;
}
if (c.isBone) {
bones.push(c);
restPose.push(c.quaternion.clone());
createBoneDot(c);
}
});
scene.add(chicken);
});
}
/* ================= BONE DOT ================= */
function createBoneDot(bone) {
const mat = new THREE.SpriteMaterial({
color: 0xff5555,
depthTest: false
});
const s = new THREE.Sprite(mat);
s.scale.set(1.6, 1.6, 1.6); // BIGGER FOR TOUCH
s.renderOrder = 999;
bone.add(s);
boneDots.push({
bone,
sprite: s
});
}
/* ================= POINTER ================= */
function onPointerDown(e) {
isTouching = true;
lastTouchY = e.clientY;
pointer.x = (e.clientX / innerWidth) * 2 - 1;
pointer.y = -(e.clientY / innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const hits = raycaster.intersectObjects(boneDots.map(b => b.sprite), true);
if (hits.length) {
const hit = boneDots.find(b => b.sprite === hits[0].object);
transformControls.attach(hit.bone);
e.preventDefault();
} else {
transformControls.detach();
}
}
function onPointerMove(e) {
if (!isTouching) return;
// SINGLE FINGER DRAG = ZOOM
if (e.pointerType === "touch" && e.buttons === 1 && !transformControls.dragging) {
const dy = e.clientY - lastTouchY;
camera.position.addScaledVector(
camera.getWorldDirection(new THREE.Vector3()).negate(),
dy * 0.01
);
lastTouchY = e.clientY;
e.preventDefault();
}
}
/* ================= FRAMES ================= */
function snapshot() {
return bones.map(b => b.quaternion.clone());
}
function saveFrame() {
undoStack.push({
frames: [...frames],
times: [...frameTimes]
});
redoStack.length = 0;
frames.push(snapshot());
frameTimes.push(parseFloat(frameTime.value) || 0.4);
rebuildTimeline();
}
function nextFrame() {
if (!frames.length) return;
frames.at(-1).forEach((q, i) => bones[i].quaternion.copy(q));
}
function rebuildTimeline() {
timeline.innerHTML = "";
frames.forEach((_, i) => {
const d = document.createElement("div");
d.className = "frame";
d.textContent = i;
d.onclick = () => frames[i].forEach((q, b) => bones[b].quaternion.copy(q));
timeline.appendChild(d);
});
}
/* ================= UNDO ================= */
function undo() {
if (!undoStack.length) return;
redoStack.push({
frames: [...frames],
times: [...frameTimes]
});
({
frames,
frameTimes
} = undoStack.pop());
rebuildTimeline();
}
function redo() {
if (!redoStack.length) return;
undoStack.push({
frames: [...frames],
times: [...frameTimes]
});
({
frames,
frameTimes
} = redoStack.pop());
rebuildTimeline();
}
/* ================= RESET ================= */
function resetRig() {
bones.forEach((b, i) => b.quaternion.copy(restPose[i]));
}
/* ================= ANIMATION ================= */
function playAnimation() {
if (frames.length < 2) return;
playing = true;
playIndex = 0;
playTimer = 0;
}
function updateAnimation(dt) {
if (!playing) return;
playTimer += dt;
const d = frameTimes[playIndex];
if (playTimer >= d) {
playTimer = 0;
playIndex++;
if (playIndex >= frames.length - 1) {
playing = false;
return;
}
}
const a = playTimer / d;
bones.forEach((b, i) => b.quaternion.slerpQuaternions(
frames[playIndex][i],
frames[playIndex + 1][i], a));
}
/* ================= EXPORT ================= */
function exportGLTF() {
const tracks = [];
let t = 0;
const times = [0];
frameTimes.forEach(d => {
t += d;
times.push(t);
});
bones.forEach((bone, i) => {
const v = [];
frames.forEach(f => {
const q = f[i];
v.push(q.x, q.y, q.z, q.w);
});
tracks.push(new THREE.QuaternionKeyframeTrack(
bone.name + ".quaternion", times, v));
});
const clip = new THREE.AnimationClip("RigAnimation", -1, tracks);
chicken.animations = [clip];
new THREE.GLTFExporter().parse(chicken, g => {
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([JSON.stringify(g)], {
type: "application/json"
}));
a.download = "rigged_animation.gltf";
a.click();
}, {
animations: [clip]
});
}
/* ================= LOOP ================= */
function animate() {
requestAnimationFrame(animate);
const now = performance.now(),
dt = (now - last) / 1000;
last = now;
updateAnimation(dt);
controls.update();
renderer.render(scene, camera);
}
function setMode(m) {
transformControls.setMode(m);
}
Code: Select all
body {
margin: 0;
overflow: hidden;
background: #222;
font-family: sans-serif;
}
#ui {
position: absolute;
bottom: 0;
width: 100%;
background: rgba(0, 0, 0, 0.85);
padding: 6px;
display: flex;
flex-wrap: wrap;
}
button,
input {
font-size: 16px;
margin: 4px;
}
#timeline {
display: flex;
overflow-x: auto;
width: 100%;
}
.frame {
width: 36px;
height: 36px;
background: #444;
margin: 4px;
border-radius: 4px;
text-align: center;
line-height: 36px;
color: white;
}
label {
color: white;
}
Code: Select all
Chicken Rig Editor
Rotate Move Add Frame Next Frame Undo Redo Rig Reset Δt Play Export GLTF
Und hier ein OK, das nicht so gut, aber fester ist
Code: Select all
/* ================= GLOBALS ================= */
let scene, camera, renderer;
let controls, transformControls;
let chicken;
let bones = [];
let boneDots = [];
let frames = [];
let frameTimes = [];
let undoStack = [];
let redoStack = [];
let restPose = [];
let playing = false;
let playIndex = 0;
let playTimer = 0;
let last = performance.now();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
/* ================= INIT ================= */
init();
loadChicken();
animate();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x444444);
camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 3, 8);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
transformControls = new THREE.TransformControls(camera, renderer.domElement);
transformControls.addEventListener("dragging-changed", e => {
controls.enabled = !e.value;
});
scene.add(transformControls);
scene.add(new THREE.AmbientLight(0xffffff, 0.7));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 10, 5);
scene.add(dirLight);
renderer.domElement.addEventListener("pointerdown", onPointerDown);
document.addEventListener("mousemove", e => {
mouse.x = (e.clientX / innerWidth) * 2 - 1;
mouse.y = -(e.clientY / innerHeight) * 2 + 1;
});
window.addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
}
/* ================= LOAD CHICKEN ================= */
function loadChicken() {
const loader = new THREE.FBXLoader();
const texLoader = new THREE.TextureLoader();
loader.load(
"https://itswardengod.github.io/wardengame/Chicken.FBX",
obj => {
chicken = obj;
chicken.scale.setScalar(0.03);
const mainTex = texLoader.load("https://itswardengod.github.io/wardengame/Main.png");
const opacityTex = texLoader.load("https://itswardengod.github.io/wardengame/Opacity.png");
obj.traverse(child => {
if (child.isMesh) {
child.material.map = mainTex;
child.material.alphaMap = opacityTex;
child.material.alphaTest = 0.5;
child.material.side = THREE.DoubleSide;
}
if (child.isBone) {
bones.push(child);
restPose.push(child.quaternion.clone());
createBoneDot(child);
}
});
scene.add(chicken);
}
);
}
/* ================= BONE DOTS ================= */
function createBoneDot(bone) {
const spriteMat = new THREE.SpriteMaterial({
color: 0xff4444,
depthTest: false,
depthWrite: false
});
const sprite = new THREE.Sprite(spriteMat);
sprite.scale.set(1, 1, 1);
sprite.renderOrder = 999;
bone.add(sprite);
boneDots.push({
bone,
sprite
});
}
/* ================= SELECTION ================= */
function onPointerDown(e) {
raycaster.setFromCamera(mouse, camera);
const hits = raycaster.intersectObjects(boneDots.map(b => b.sprite));
if (hits.length) {
transformControls.attach(
boneDots.find(b => b.sprite === hits[0].object).bone
);
}
}
/* ================= FRAMES ================= */
function snapshot() {
return bones.map(b => b.quaternion.clone());
}
function saveFrame() {
undoStack.push({
frames: [...frames],
times: [...frameTimes]
});
redoStack.length = 0;
frames.push(snapshot());
frameTimes.push(parseFloat(frameTime.value) || 0.4);
rebuildTimeline();
}
function nextFrame() {
if (!frames.length) return;
frames[frames.length - 1].forEach((q, i) => bones[i].quaternion.copy(q));
}
function rebuildTimeline() {
timeline.innerHTML = "";
frames.forEach((_, i) => {
const d = document.createElement("div");
d.className = "frame";
d.textContent = i;
d.onclick = () => frames[i].forEach((q, b) => bones[b].quaternion.copy(q));
timeline.appendChild(d);
});
}
/* ================= UNDO / REDO ================= */
function undo() {
if (!undoStack.length) return;
redoStack.push({
frames: [...frames],
times: [...frameTimes]
});
const s = undoStack.pop();
frames = s.frames;
frameTimes = s.times;
rebuildTimeline();
}
function redo() {
if (!redoStack.length) return;
undoStack.push({
frames: [...frames],
times: [...frameTimes]
});
const s = redoStack.pop();
frames = s.frames;
frameTimes = s.times;
rebuildTimeline();
}
/* ================= RESET ================= */
function resetRig() {
bones.forEach((b, i) => b.quaternion.copy(restPose[i]));
}
/* ================= ANIMATION ================= */
function playAnimation() {
if (frames.length < 2) return;
playing = true;
playIndex = 0;
playTimer = 0;
}
function updateAnimation(dt) {
if (!playing) return;
playTimer += dt;
const dur = frameTimes[playIndex];
if (playTimer >= dur) {
playTimer = 0;
playIndex++;
if (playIndex >= frames.length - 1) {
playing = false;
return;
}
}
const a = playTimer / dur;
bones.forEach((b, i) => {
b.quaternion.slerpQuaternions(
frames[playIndex][i],
frames[playIndex + 1][i],
a
);
});
}
/* ================= EXPORT ================= */
function exportGLTF() {
const tracks = [];
let t = 0;
const times = [0];
frameTimes.forEach(d => {
t += d;
times.push(t);
});
bones.forEach((bone, i) => {
const values = [];
frames.forEach(f => {
const q = f[i];
values.push(q.x, q.y, q.z, q.w);
});
tracks.push(new THREE.QuaternionKeyframeTrack(
bone.name + ".quaternion", times, values
));
});
const clip = new THREE.AnimationClip("RigAnimation", -1, tracks);
chicken.animations = [clip];
new THREE.GLTFExporter().parse(chicken, g => {
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([JSON.stringify(g)], {
type: "application/json"
}));
a.download = "rigged_animation.gltf";
a.click();
}, {
animations: [clip]
});
}
/* ================= LOOP ================= */
function animate() {
requestAnimationFrame(animate);
const now = performance.now();
const dt = (now - last) / 1000;
last = now;
updateAnimation(dt);
controls.update();
renderer.render(scene, camera);
}
function setMode(m) {
transformControls.setMode(m);
}
Code: Select all
body {
margin: 0;
overflow: hidden;
background: #222;
font-family: sans-serif;
}
#ui {
position: absolute;
bottom: 0;
width: 100%;
background: rgba(0, 0, 0, 0.85);
padding: 6px;
display: flex;
flex-wrap: wrap;
}
button,
input {
font-size: 16px;
margin: 4px;
}
#timeline {
display: flex;
overflow-x: auto;
width: 100%;
}
.frame {
width: 36px;
height: 36px;
background: #444;
margin: 4px;
border-radius: 4px;
text-align: center;
line-height: 36px;
color: white;
}
label {
color: white;
}
Code: Select all
Chicken Rig Editor
Rotate Move Add Frame Next Frame Undo Redo Rig Reset Δt Play Export GLTF
1766478063
Anonymous
Hier der Code, der repariert werden muss. Ich muss gerade für 5 Tage weg. Wenn Sie ihn reparieren können, wäre das großartig, aber hier ist die bessere, aber keine vollständige Korrekturversion. Hinweis: Bitte stimmen Sie nicht ab. Wenn Sie ihn reparieren können, tun Sie dies bitte und können Sie einen Kommentar abgeben, z. B. „Ist es möglich und dies ist kein Backup?“ [code]let scene, camera, renderer; let controls, transformControls; let chicken; let bones = []; let boneDots = []; let frames = []; let frameTimes = []; let undoStack = []; let redoStack = []; let restPose = []; let playing = false; let playIndex = 0; let playTimer = 0; let last = performance.now(); const raycaster = new THREE.Raycaster(); raycaster.params.Sprite.threshold = 1.2; const pointer = new THREE.Vector2(); let isTouching = false; let lastTouchY = 0; /* ================= INIT ================= */ init(); loadChicken(); animate(); function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x444444); camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000); camera.position.set(0, 3, 8); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.body.appendChild(renderer.domElement); controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.enablePan = true; transformControls = new THREE.TransformControls(camera, renderer.domElement); transformControls.addEventListener("dragging-changed", e => { controls.enabled = !e.value; }); scene.add(transformControls); scene.add(new THREE.AmbientLight(0xffffff, 0.7)); const d = new THREE.DirectionalLight(0xffffff, 0.8); d.position.set(5, 10, 5); scene.add(d); renderer.domElement.addEventListener("pointerdown", onPointerDown, { passive: false }); renderer.domElement.addEventListener("pointermove", onPointerMove, { passive: false }); renderer.domElement.addEventListener("pointerup", () => isTouching = false); window.addEventListener("resize", () => { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); } /* ================= LOAD ================= */ function loadChicken() { const loader = new THREE.FBXLoader(); const tex = new THREE.TextureLoader(); loader.load("https://itswardengod.github.io/wardengame/Chicken.FBX", obj => { chicken = obj; chicken.scale.setScalar(0.03); const main = tex.load("https://itswardengod.github.io/wardengame/Main.png"); const alpha = tex.load("https://itswardengod.github.io/wardengame/Opacity.png"); obj.traverse(c => { if (c.isMesh) { c.material.map = main; c.material.alphaMap = alpha; c.material.alphaTest = 0.5; c.material.side = THREE.DoubleSide; } if (c.isBone) { bones.push(c); restPose.push(c.quaternion.clone()); createBoneDot(c); } }); scene.add(chicken); }); } /* ================= BONE DOT ================= */ function createBoneDot(bone) { const mat = new THREE.SpriteMaterial({ color: 0xff5555, depthTest: false }); const s = new THREE.Sprite(mat); s.scale.set(1.6, 1.6, 1.6); // BIGGER FOR TOUCH s.renderOrder = 999; bone.add(s); boneDots.push({ bone, sprite: s }); } /* ================= POINTER ================= */ function onPointerDown(e) { isTouching = true; lastTouchY = e.clientY; pointer.x = (e.clientX / innerWidth) * 2 - 1; pointer.y = -(e.clientY / innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); const hits = raycaster.intersectObjects(boneDots.map(b => b.sprite), true); if (hits.length) { const hit = boneDots.find(b => b.sprite === hits[0].object); transformControls.attach(hit.bone); e.preventDefault(); } else { transformControls.detach(); } } function onPointerMove(e) { if (!isTouching) return; // SINGLE FINGER DRAG = ZOOM if (e.pointerType === "touch" && e.buttons === 1 && !transformControls.dragging) { const dy = e.clientY - lastTouchY; camera.position.addScaledVector( camera.getWorldDirection(new THREE.Vector3()).negate(), dy * 0.01 ); lastTouchY = e.clientY; e.preventDefault(); } } /* ================= FRAMES ================= */ function snapshot() { return bones.map(b => b.quaternion.clone()); } function saveFrame() { undoStack.push({ frames: [...frames], times: [...frameTimes] }); redoStack.length = 0; frames.push(snapshot()); frameTimes.push(parseFloat(frameTime.value) || 0.4); rebuildTimeline(); } function nextFrame() { if (!frames.length) return; frames.at(-1).forEach((q, i) => bones[i].quaternion.copy(q)); } function rebuildTimeline() { timeline.innerHTML = ""; frames.forEach((_, i) => { const d = document.createElement("div"); d.className = "frame"; d.textContent = i; d.onclick = () => frames[i].forEach((q, b) => bones[b].quaternion.copy(q)); timeline.appendChild(d); }); } /* ================= UNDO ================= */ function undo() { if (!undoStack.length) return; redoStack.push({ frames: [...frames], times: [...frameTimes] }); ({ frames, frameTimes } = undoStack.pop()); rebuildTimeline(); } function redo() { if (!redoStack.length) return; undoStack.push({ frames: [...frames], times: [...frameTimes] }); ({ frames, frameTimes } = redoStack.pop()); rebuildTimeline(); } /* ================= RESET ================= */ function resetRig() { bones.forEach((b, i) => b.quaternion.copy(restPose[i])); } /* ================= ANIMATION ================= */ function playAnimation() { if (frames.length < 2) return; playing = true; playIndex = 0; playTimer = 0; } function updateAnimation(dt) { if (!playing) return; playTimer += dt; const d = frameTimes[playIndex]; if (playTimer >= d) { playTimer = 0; playIndex++; if (playIndex >= frames.length - 1) { playing = false; return; } } const a = playTimer / d; bones.forEach((b, i) => b.quaternion.slerpQuaternions( frames[playIndex][i], frames[playIndex + 1][i], a)); } /* ================= EXPORT ================= */ function exportGLTF() { const tracks = []; let t = 0; const times = [0]; frameTimes.forEach(d => { t += d; times.push(t); }); bones.forEach((bone, i) => { const v = []; frames.forEach(f => { const q = f[i]; v.push(q.x, q.y, q.z, q.w); }); tracks.push(new THREE.QuaternionKeyframeTrack( bone.name + ".quaternion", times, v)); }); const clip = new THREE.AnimationClip("RigAnimation", -1, tracks); chicken.animations = [clip]; new THREE.GLTFExporter().parse(chicken, g => { const a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([JSON.stringify(g)], { type: "application/json" })); a.download = "rigged_animation.gltf"; a.click(); }, { animations: [clip] }); } /* ================= LOOP ================= */ function animate() { requestAnimationFrame(animate); const now = performance.now(), dt = (now - last) / 1000; last = now; updateAnimation(dt); controls.update(); renderer.render(scene, camera); } function setMode(m) { transformControls.setMode(m); }[/code] [code]body { margin: 0; overflow: hidden; background: #222; font-family: sans-serif; } #ui { position: absolute; bottom: 0; width: 100%; background: rgba(0, 0, 0, 0.85); padding: 6px; display: flex; flex-wrap: wrap; } button, input { font-size: 16px; margin: 4px; } #timeline { display: flex; overflow-x: auto; width: 100%; } .frame { width: 36px; height: 36px; background: #444; margin: 4px; border-radius: 4px; text-align: center; line-height: 36px; color: white; } label { color: white; }[/code] [code]Chicken Rig Editor Rotate Move Add Frame Next Frame Undo Redo Rig Reset Δt Play Export GLTF [/code] Und hier ein OK, das nicht so gut, aber fester ist [code]/* ================= GLOBALS ================= */ let scene, camera, renderer; let controls, transformControls; let chicken; let bones = []; let boneDots = []; let frames = []; let frameTimes = []; let undoStack = []; let redoStack = []; let restPose = []; let playing = false; let playIndex = 0; let playTimer = 0; let last = performance.now(); const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); /* ================= INIT ================= */ init(); loadChicken(); animate(); function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x444444); camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000); camera.position.set(0, 3, 8); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.body.appendChild(renderer.domElement); controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; transformControls = new THREE.TransformControls(camera, renderer.domElement); transformControls.addEventListener("dragging-changed", e => { controls.enabled = !e.value; }); scene.add(transformControls); scene.add(new THREE.AmbientLight(0xffffff, 0.7)); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(5, 10, 5); scene.add(dirLight); renderer.domElement.addEventListener("pointerdown", onPointerDown); document.addEventListener("mousemove", e => { mouse.x = (e.clientX / innerWidth) * 2 - 1; mouse.y = -(e.clientY / innerHeight) * 2 + 1; }); window.addEventListener("resize", () => { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); } /* ================= LOAD CHICKEN ================= */ function loadChicken() { const loader = new THREE.FBXLoader(); const texLoader = new THREE.TextureLoader(); loader.load( "https://itswardengod.github.io/wardengame/Chicken.FBX", obj => { chicken = obj; chicken.scale.setScalar(0.03); const mainTex = texLoader.load("https://itswardengod.github.io/wardengame/Main.png"); const opacityTex = texLoader.load("https://itswardengod.github.io/wardengame/Opacity.png"); obj.traverse(child => { if (child.isMesh) { child.material.map = mainTex; child.material.alphaMap = opacityTex; child.material.alphaTest = 0.5; child.material.side = THREE.DoubleSide; } if (child.isBone) { bones.push(child); restPose.push(child.quaternion.clone()); createBoneDot(child); } }); scene.add(chicken); } ); } /* ================= BONE DOTS ================= */ function createBoneDot(bone) { const spriteMat = new THREE.SpriteMaterial({ color: 0xff4444, depthTest: false, depthWrite: false }); const sprite = new THREE.Sprite(spriteMat); sprite.scale.set(1, 1, 1); sprite.renderOrder = 999; bone.add(sprite); boneDots.push({ bone, sprite }); } /* ================= SELECTION ================= */ function onPointerDown(e) { raycaster.setFromCamera(mouse, camera); const hits = raycaster.intersectObjects(boneDots.map(b => b.sprite)); if (hits.length) { transformControls.attach( boneDots.find(b => b.sprite === hits[0].object).bone ); } } /* ================= FRAMES ================= */ function snapshot() { return bones.map(b => b.quaternion.clone()); } function saveFrame() { undoStack.push({ frames: [...frames], times: [...frameTimes] }); redoStack.length = 0; frames.push(snapshot()); frameTimes.push(parseFloat(frameTime.value) || 0.4); rebuildTimeline(); } function nextFrame() { if (!frames.length) return; frames[frames.length - 1].forEach((q, i) => bones[i].quaternion.copy(q)); } function rebuildTimeline() { timeline.innerHTML = ""; frames.forEach((_, i) => { const d = document.createElement("div"); d.className = "frame"; d.textContent = i; d.onclick = () => frames[i].forEach((q, b) => bones[b].quaternion.copy(q)); timeline.appendChild(d); }); } /* ================= UNDO / REDO ================= */ function undo() { if (!undoStack.length) return; redoStack.push({ frames: [...frames], times: [...frameTimes] }); const s = undoStack.pop(); frames = s.frames; frameTimes = s.times; rebuildTimeline(); } function redo() { if (!redoStack.length) return; undoStack.push({ frames: [...frames], times: [...frameTimes] }); const s = redoStack.pop(); frames = s.frames; frameTimes = s.times; rebuildTimeline(); } /* ================= RESET ================= */ function resetRig() { bones.forEach((b, i) => b.quaternion.copy(restPose[i])); } /* ================= ANIMATION ================= */ function playAnimation() { if (frames.length < 2) return; playing = true; playIndex = 0; playTimer = 0; } function updateAnimation(dt) { if (!playing) return; playTimer += dt; const dur = frameTimes[playIndex]; if (playTimer >= dur) { playTimer = 0; playIndex++; if (playIndex >= frames.length - 1) { playing = false; return; } } const a = playTimer / dur; bones.forEach((b, i) => { b.quaternion.slerpQuaternions( frames[playIndex][i], frames[playIndex + 1][i], a ); }); } /* ================= EXPORT ================= */ function exportGLTF() { const tracks = []; let t = 0; const times = [0]; frameTimes.forEach(d => { t += d; times.push(t); }); bones.forEach((bone, i) => { const values = []; frames.forEach(f => { const q = f[i]; values.push(q.x, q.y, q.z, q.w); }); tracks.push(new THREE.QuaternionKeyframeTrack( bone.name + ".quaternion", times, values )); }); const clip = new THREE.AnimationClip("RigAnimation", -1, tracks); chicken.animations = [clip]; new THREE.GLTFExporter().parse(chicken, g => { const a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([JSON.stringify(g)], { type: "application/json" })); a.download = "rigged_animation.gltf"; a.click(); }, { animations: [clip] }); } /* ================= LOOP ================= */ function animate() { requestAnimationFrame(animate); const now = performance.now(); const dt = (now - last) / 1000; last = now; updateAnimation(dt); controls.update(); renderer.render(scene, camera); } function setMode(m) { transformControls.setMode(m); }[/code] [code]body { margin: 0; overflow: hidden; background: #222; font-family: sans-serif; } #ui { position: absolute; bottom: 0; width: 100%; background: rgba(0, 0, 0, 0.85); padding: 6px; display: flex; flex-wrap: wrap; } button, input { font-size: 16px; margin: 4px; } #timeline { display: flex; overflow-x: auto; width: 100%; } .frame { width: 36px; height: 36px; background: #444; margin: 4px; border-radius: 4px; text-align: center; line-height: 36px; color: white; } label { color: white; }[/code] [code]Chicken Rig Editor Rotate Move Add Frame Next Frame Undo Redo Rig Reset Δt Play Export GLTF [/code]