UNPKG

pex-renderer

Version:

Physically Based Renderer (PBR) and scene graph designed as ECS for PEX: define entities to be rendered as collections of components with their update orchestrated by systems.

165 lines (143 loc) 5 kB
import { vec3, vec4, quat } from "pex-math"; import { TEMP_QUAT, TEMP_VEC3 } from "../utils.js"; function updateAnimation(animation, deltaTime) { if (!animation.prevTime) { animation.time = 0; animation.prevTime = performance.now(); animation.playing = true; } if (animation.playing) { const animationLength = animation.duration || animation.channels[0].input[animation.channels[0].input.length - 1]; const now = performance.now(); deltaTime ||= (now - animation.prevTime) / 1000; animation.prevTime = now; animation.time += deltaTime; if (animation.time > animationLength) { if (animation.loop) { animation.time %= animationLength; } else { animation.time = 0; animation.playing = false; } } animation.needsUpdate = true; } if (!animation.needsUpdate) return; animation.needsUpdate = false; for (let i = 0; i < animation.channels.length; i++) { const channel = animation.channels[i]; const inputData = channel.input; let prevIndex; let nextIndex; for (let j = 0; j < inputData.length; j++) { nextIndex = j; if (inputData[j] >= animation.time) break; prevIndex = nextIndex; } const isRotation = channel.path === "rotation"; const outputData = channel.output; const prevInput = inputData[prevIndex]; const nextInput = inputData[nextIndex]; const scale = nextInput - prevInput || 1; const t = (animation.time - prevInput) / scale; if (prevIndex !== undefined) { switch (channel.interpolation) { case "STEP": if (isRotation) { quat.set(TEMP_QUAT, outputData[prevIndex]); } else { vec3.set(TEMP_VEC3, outputData[prevIndex]); } break; case "CUBICSPLINE": { const vec = isRotation ? vec4 : vec3; const tt = t * t; const ttt = tt * t; // Each input value corresponds to three output values of the same type: in-tangent, data point, and out-tangent. // p0 const prevPosition = vec.copy(outputData[prevIndex * 3 + 1]); // p1 const nextPos = vec.copy(outputData[nextIndex * 3 + 1]); // m0 = (tk+1 - tk)bk const prevOutTangent = prevIndex ? vec.scale(vec.copy(outputData[prevIndex * 3 + 2]), scale) : vec.create(); // m1 = (tk+1 - tk)ak+1 const nextInTangent = nextIndex !== inputData.length - 1 ? vec.scale(vec.copy(outputData[prevIndex * 3]), scale) : vec.create(); // p(t) = (2t³ - 3t² + 1)p0 + (t³ - 2t² + t)m0 + (-2t³ + 3t²)p1 + (t³ - t²)m1 const p0 = vec.scale(prevPosition, 2 * ttt - 3 * tt + 1); const m0 = vec.scale(prevOutTangent, ttt - 2 * tt + t); const p1 = vec.scale(nextPos, -2 * ttt + 3 * tt); const m1 = vec.scale(nextInTangent, ttt - tt); if (isRotation) { quat.set( TEMP_QUAT, quat.normalize([ p0[0] + m0[0] + p1[0] + m1[0], p0[1] + m0[1] + p1[1] + m1[1], p0[2] + m0[2] + p1[2] + m1[2], p0[3] + m0[3] + p1[3] + m1[3], ]), ); } else { vec3.set(TEMP_VEC3, vec3.add(vec3.add(vec3.add(p0, m0), p1), m1)); } break; } default: // LINEAR if (isRotation) { quat.slerp( quat.set(TEMP_QUAT, outputData[prevIndex]), outputData[nextIndex], t, ); } else { vec3.lerp( vec3.set(TEMP_VEC3, outputData[prevIndex]), outputData[nextIndex], t, ); } } if (isRotation) { quat.set(channel.target.transform.rotation, TEMP_QUAT); channel.target.transform.dirty = true; } else if (channel.path === "translation") { vec3.set(channel.target.transform.position, TEMP_VEC3); channel.target.transform.dirty = true; } else if (channel.path === "scale") { vec3.set(channel.target.transform.scale, TEMP_VEC3); channel.target.transform.dirty = true; } else if (channel.path === "weights") { channel.target.morph.weights = outputData[nextIndex].slice(); } } } } /** * Animation system * @returns {import("../types.js").System} * @alias module:systems.animation */ export default () => ({ type: "animation-system", updateAnimation, update(entities, { deltaTime }) { for (let i = 0; i < entities.length; i++) { const entity = entities[i]; if (entity.animations) { for (let j = 0; j < entity.animations.length; j++) { updateAnimation(entity.animations[j], deltaTime); } } else if (entity.animation) { updateAnimation(entity.animation, deltaTime); } } }, });