Release Healer Man 0.1.26

This commit is contained in:
phenom committed 2026-09-09 22:18:15 -04:00
1 parent e7cc588bfc
commit 147c2804d4
27 files changed
+1010 -55

No files matched your search

+29 -10
View File
@@ -25,9 +25,12 @@ import {
type Material,
type Mesh,
type Object3D,
type Skeleton,
type SkinnedMesh,
type Texture,
} from "three";
import { clone as cloneSkeleton } from "three/examples/jsm/utils/SkeletonUtils.js";
import { shareClonedSkeletons } from "./sharedSkeletons";
import type {
CharacterAppearance,
ClassId,
@@ -123,6 +126,7 @@ function configureMaterial(material: Material): void {
function cloneAvatarScene(root: Object3D): Object3D {
const scene = cloneSkeleton(root);
shareClonedSkeletons(scene);
scene.traverse((node) => {
const mesh = node as Mesh;
if (!mesh.isMesh) return;
@@ -267,13 +271,17 @@ function LoadedCharacterModel({
useEffect(() => () => {
const ownedMaterials = new Set<Material>();
const ownedSkeletons = new Set<Skeleton>();
scene.traverse((node) => {
const skinned = node as SkinnedMesh;
if (skinned.isSkinnedMesh) ownedSkeletons.add(skinned.skeleton);
const mesh = node as Mesh;
if (!mesh.isMesh) return;
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
materials.forEach((material) => ownedMaterials.add(material));
});
ownedMaterials.forEach((material) => material.dispose());
ownedSkeletons.forEach((skeleton) => skeleton.dispose());
}, [scene]);
useLayoutEffect(() => {
@@ -359,22 +367,33 @@ function LoadedCharacterModel({
?? gltf.animations.find((candidate) => candidate.name.toLowerCase().includes(normalized));
};
const selectClip = (motion: CharacterBaseMotion): AnimationClip | undefined => (
selectCharacterBaseClip(gltf.animations, motion, weaponStance)
?? namedClip(entry.animations[motion])
?? namedClip(entry.animations.idle)
?? gltf.animations[0]
);
// The GLTF clip library and weapon stance change far less often than frames.
// Resolve locomotion once so five party members do not repeatedly parse and
// sort the same hundreds of animation names while standing or running.
const baseClips = useMemo(() => Object.fromEntries(
(["idle", "walk", "run"] as const).map((motion) => [
motion,
selectCharacterBaseClip(gltf.animations, motion, weaponStance)
?? namedClip(entry.animations[motion])
?? namedClip(entry.animations.idle)
?? gltf.animations[0],
]),
) as Record<CharacterBaseMotion, AnimationClip | undefined>, [gltf.animations, entry.animations, weaponStance]);
const verticalClips = useMemo(() => ({
rising: [selectCharacterVerticalClip(gltf.animations, "rising", false), selectCharacterVerticalClip(gltf.animations, "rising", true)],
falling: [selectCharacterVerticalClip(gltf.animations, "falling", false), selectCharacterVerticalClip(gltf.animations, "falling", true)],
landing: [selectCharacterVerticalClip(gltf.animations, "landing", false), selectCharacterVerticalClip(gltf.animations, "landing", true)],
}), [gltf.animations]);
const switchMotion = (motion: CharacterLocomotionMotion, moving: boolean) => {
if (overrideAction.current) return;
const verticalSelection = motion === "rising" || motion === "falling" || motion === "landing"
? selectCharacterVerticalClip(gltf.animations, motion, moving)
? verticalClips[motion][moving ? 1 : 0]
: null;
const fallbackMotion: CharacterBaseMotion = moving ? "run" : "idle";
const clip = verticalSelection?.clip ?? selectClip(
motion === "idle" || motion === "walk" || motion === "run" ? motion : fallbackMotion,
);
const clip = verticalSelection?.clip ?? baseClips[
motion === "idle" || motion === "walk" || motion === "run" ? motion : fallbackMotion
];
if (!clip) return;
if (currentMotion.current === motion && currentAction.current?.getClip() === clip) return;
const loop = verticalSelection?.loop ?? true;