Anonymous
Хороший редактор кода, но ему нужна помощь [закрыто]
Сообщение
Anonymous » 23 дек 2025, 11:21
Вот код, который нужно исправить, мне просто нужно уйти на 5 дней, так что если вы можете это исправить, это было бы здорово, но вот лучше, но не примечание к полной исправленной версии, пожалуйста, не голосуйте против, и если вы можете это исправить, пожалуйста, сделайте и можете ли вы прокомментировать, например, возможно ли это, и это не резервная копия
Код: Выделить всё
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);
}
Код: Выделить всё
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;
}
Код: Выделить всё
Chicken Rig Editor
Rotate Move Add Frame Next Frame Undo Redo Rig Reset Δt Play Export GLTF
А вот неплохой вариант, не такой хороший, но более фиксированный
Код: Выделить всё
/* ================= 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);
}
Код: Выделить всё
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;
}
Код: Выделить всё
Chicken Rig Editor
Rotate Move Add Frame Next Frame Undo Redo Rig Reset Δt Play Export GLTF
Подробнее здесь:
https://stackoverflow.com/questions/798 ... assistance
1766478063
Anonymous
Вот код, который нужно исправить, мне просто нужно уйти на 5 дней, так что если вы можете это исправить, это было бы здорово, но вот лучше, но не примечание к полной исправленной версии, пожалуйста, не голосуйте против, и если вы можете это исправить, пожалуйста, сделайте и можете ли вы прокомментировать, например, возможно ли это, и это не резервная копия [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] А вот неплохой вариант, не такой хороший, но более фиксированный [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] Подробнее здесь: [url]https://stackoverflow.com/questions/79853322/good-code-rig-editor-but-it-needs-assistance[/url]