Skip to content

Instantly share code, notes, and snippets.

@lukevanin
Created March 16, 2025 16:23
Show Gist options
  • Save lukevanin/3d049265b16b98096ed55740cce0ae02 to your computer and use it in GitHub Desktop.
Save lukevanin/3d049265b16b98096ed55740cce0ae02 to your computer and use it in GitHub Desktop.
Vibe coded 3D engine utilities
// Import Three.js and additional modules
import * as THREE from 'three';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
import { AnimationClip, AnimationMixer, Object3D, Scene, Vector3 } from 'three';
import { AnimationDebugInfo } from './debug';
// Global animation speed multiplier
const ANIMATION_SPEED_MULTIPLIER = 1.2;
// Animation variables
let mixer: THREE.AnimationMixer | null = null;
let character: THREE.Object3D | null = null;
// Function to remove root motion (position) from animation
export function removeRootMotion(animation: AnimationClip): void {
// Find the root bone track (usually the first track with position)
const tracks = animation.tracks;
// Iterate through tracks to find position tracks
for (let i = 0; i < tracks.length; i++) {
const track = tracks[i];
// Check if it's a position track for the root bone
// Position tracks typically end with ".position"
if (track.name.endsWith('.position')) {
// This looks like a root bone position track
console.log(`Removing root motion from track: ${track.name}`);
// Get the values array (contains x,y,z values for each keyframe)
const values = track.values;
// Get the initial position from the first frame
const initialZ = values[2];
// Set all keyframes to keep initial Z position only
for (let j = 0; j < values.length; j += 3) {
// Keep original X,Y values
values[j + 2] = initialZ; // Lock Z position
}
}
}
}
// Update animation mixer
function updateAnimations(deltaTime: number): void {
if (mixer) {
mixer.update(deltaTime);
}
}
// Function to get debug information about the mixer
export function getAnimationDebugInfo(): AnimationDebugInfo {
return {
mixerExists: mixer !== null,
animationCount: 0,
currentAction: null,
availableAnimations: []
};
}
// Function to load a model for an actor
export async function loadActorModel(
modelPath: string,
scene: Scene,
position: Vector3,
scale: number = 1.0 // Default scale is 1.0
): Promise<{ model: Object3D, mixer: AnimationMixer }> {
return new Promise<{ model: Object3D, mixer: AnimationMixer }>((resolve, reject) => {
const fbxLoader = new FBXLoader();
// Determine if the path already includes a file extension
const hasExtension = modelPath.toLowerCase().endsWith('.fbx');
const fullPath = hasExtension ? modelPath : `${modelPath}/model.fbx`;
console.log(`Loading model from path: ${fullPath}`, position, scale);
// Load character model
fbxLoader.load(
fullPath,
(fbx) => {
const model = fbx;
console.log(`Model loaded successfully: ${model.name}`);
// Scale and position the model
model.scale.setScalar(scale); // Use the provided scale parameter
model.position.copy(position);
// Enable shadows for all meshes in the model
model.traverse((child) => {
if ((child as THREE.Mesh).isMesh) {
const mesh = child as THREE.Mesh;
mesh.castShadow = true;
mesh.receiveShadow = false;
// Recompute normals for the mesh geometry
if (mesh.geometry) {
// Make sure we're working with a BufferGeometry
if (mesh.geometry instanceof THREE.BufferGeometry) {
// Check if the geometry has position attribute
if (mesh.geometry.attributes.position) {
// Compute vertex normals
mesh.geometry.computeVertexNormals();
console.log(`Recomputed normals for mesh: ${mesh.name || 'unnamed'}`);
}
}
}
}
});
// Add to scene
scene.add(model);
// Create animation mixer
const newMixer = new AnimationMixer(model);
// Set global references
character = model;
mixer = newMixer;
// Resolve with both the model and mixer
resolve({
model: model,
mixer: newMixer
});
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% model loaded');
},
(error) => {
console.error('Error loading model:', error);
reject(error);
}
);
});
}
// Get character object
function getCharacter(): Object3D | null {
return character;
}
// Get mixer object
function getMixer(): AnimationMixer | null {
return mixer;
}
// Export functions
export {
updateAnimations,
getCharacter,
getMixer
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment