UNPKG

anim-to-bvh

Version:

Anim to BVH converter(mostly for Second Life, including Bento bones). Anim, BVH parsers.

196 lines (147 loc) 5.01 kB
import {Vector3} from "./model"; import {Quaternion} from "quaternion"; export const RAD_TO_DEG: number = 180.0 / Math.PI; export function toQuaternion(v: Vector3): Quaternion { const wSqr: number = 1.0 - (v.x * v.x + v.y * v.y + v.z * v.z); return new Quaternion({x: v.x, y: v.y, z: v.z, w :wSqr > 0 ? Math.sqrt(wSqr) : 0}); } export function toEulers(truncatedQuanterion: Vector3) { return toQuaternion({ x: truncatedQuanterion.z, y: truncatedQuanterion.x, z: truncatedQuanterion.y }).toEuler().map(item1 => item1 * 180 / Math.PI); } export function quaternionToEulers(quaternion: Quaternion): Vector3 { const eulers = new Quaternion({ x: quaternion.z, y: quaternion.x, z: quaternion.y, w: quaternion.w }).toEuler().map(item1 => item1 * 180 / Math.PI); return {x: eulers[0], y: eulers[1], z: eulers[2]}; } export function append(text: string, times: number): string { let result: string = ""; for(let i = 0; i < times; i++) { result += text; } return result; } export function floatToString(value: number, fraction: number): string { return value.toLocaleString("un-US", {minimumFractionDigits: fraction}).replaceAll(",", "") } function lerpValue(x1: number, x2: number, t: number) { if(t > 1) { return x2; } if(t < 0) { return x1; } return x1 + (x2 - x1) * t; } function optimizedAmimationLength(duration: number, originalFrameLength: number): number { if(originalFrameLength > duration) { return 2; } const hourLength: number = 3600 / originalFrameLength; let bestLength = 0; for(let i = 1; i < hourLength; i++) { const optimizedFrameLength = duration / i; const error = Math.abs(originalFrameLength - optimizedFrameLength); const prevError = Math.abs(originalFrameLength - bestLength); if(error < prevError) { bestLength = optimizedFrameLength; } } return bestLength; } export function getUniformTimes(duration: number, singleFrameDuration: number): number[] { const times: number[] = []; const optimizedFrameDuration = optimizedAmimationLength(duration, singleFrameDuration); const length = duration / optimizedFrameDuration; for(let i = 0; i < length; i++) { times.push(i * optimizedFrameDuration); } times[length - 1] = duration; return times; } function closest(left: number, right: number, value: number): number { if(Math.abs(left - value) < Math.abs(right - value)) { return left; } return right; } export function clipTimesToClosestBVHTime(animTimes: number[], bvhTimes: number[]): number[] { const fixedTimes: number[] = animTimes.map((item: number) => item); for(let i = 1; i < bvhTimes.length; i++) { for(let j = 0; j < animTimes.length; j++) { const animTime = animTimes[j]; const bvhTimeLeft = bvhTimes[i - 1]; const bvhTimeRight = bvhTimes[i]; if((bvhTimeLeft <= animTime) && (animTime <= bvhTimeRight)) { fixedTimes[j] = closest(bvhTimeLeft, bvhTimeRight, animTime); } } } return fixedTimes; } function getFactors(bvhTimes: number[], animTimes: number[]): any[] { return bvhTimes.map((item: number) => { if(item <= animTimes[0]) { return { leftAnimIndex: 0, rightAnimIndex: 1, factor: 0 }; } if(item >= animTimes[animTimes.length - 1]) { return { leftAnimIndex: animTimes.length - 2, rightAnimIndex: animTimes.length - 1, factor: 1 }; } for(let i = 1; i < animTimes.length; i++) { const leftTime: number = animTimes[i - 1]; const rightTime: number = animTimes[i]; if((leftTime <= item) && (item < rightTime)) { const rangeSize = rightTime - leftTime; return { leftAnimIndex: i - 1, rightAnimIndex: i, factor: (item - leftTime) / rangeSize }; } } }); } export function distributeValue(position: any, rotation: any, channel: string, value: number): void { const key: string = channel.toLowerCase()[0]; const recipient = channel.includes("pos") ? position : rotation; recipient[key] = value; } export function lerpVector(leftValue: Vector3, rightValue: Vector3, factor: number): Vector3 { return { x: lerpValue(leftValue.x, rightValue.x, factor), y: lerpValue(leftValue.y, rightValue.y, factor), z: lerpValue(leftValue.z, rightValue.z, factor), } } export function lerpQuaternion(leftValue: Quaternion, rightValue: Quaternion, factor: number): Quaternion { return leftValue.slerp(rightValue)(factor); } export function lerpValues<T>(values: T[], animTimes: number[], uniformTimes: number[], defaultValue: T, lerpFunction: (leftValue: T, rightValue: T, factor: number) => T): T[] { if(!values?.length) { return uniformTimes.map(item => defaultValue); } if(values.length == 1) { return uniformTimes.map(item => values[0]); } const factors: any[] = getFactors(uniformTimes, animTimes); return factors.map(item => { const leftFrame: T = values[item.leftAnimIndex]; const rightFrame: T = values[item.rightAnimIndex]; return lerpFunction(leftFrame, rightFrame, item.factor); }); }