This repository contains a TypeScript animation framework called demolished-rail, designed for creating complex and dynamic animations with ease. The framework provides a structured approach to building animations using scenes, entities, and a sequence controller. It also includes features like BPM synchronization, beat and tick events, and audio analysis (FFT) to create interactive and audio-reactive visualizations.
Scene-based animation: Organize animations into scenes with defined start times and durations.
Entity management: Create and manage individual animatable entities within scenes.
Canvas-based entities: Draw animations using the HTML5 Canvas API.
Shader-based entities: Leverage WebGL shaders or WebGPU shaders for high-performance graphics.
WebGL support: Create entities with WebGL shaders using GLSLShaderEntity
and GLSLShaderRenderer
.
WGSL support: Create entities with WGSL shaders for modern, safe, and portable graphics.
Multi-pass rendering: Supports 1-n shader programs/buffers for complex effects.
Texture support: Use 1-n textures within each shader program.
Custom uniforms: Pass custom data to your shaders for dynamic control.
Entity timing: Control the lifetime of entities within a scene using startTimeinMs
and durationInMs
properties.
Entity event listeners: Attach event listeners directly to entities to trigger actions on beats, ticks, or bars.
Sequence control: Orchestrate the playback of scenes in a sequence.
BPM synchronization: Synchronize animations with beats per minute (BPM).
Beat and tick events: Trigger events on beats and ticks for precise timing control.
Audio analysis (FFT): Analyze audio frequencies to create audio-reactive visualizations.
Asset loading: Helper functions for loading images, audio, and managing a texture cache.
Transitions: Define transition effects for entities to create smooth entrances and exits.
Easy customization: Create custom transition functions to achieve a wide range of effects (e.g., fading, scaling, sliding).
Helper functions: Use createFadeInTransition
and createFadeOutTransition
to easily add fade effects.
Post-processing: Apply visual effects to the entire scene or individual entities.
Sequence-level post-processing: Add post-processing effects to the Sequence
to affect the final output.
Entity-level post-processing: Add post-processing effects to individual entities for customized visual enhancements.
Helper functions: Use built-in post-processing functions like createBlurPostProcessor
, createGrayscalePostProcessor
, and createInvertPostProcessor
.
Beat-synced effects: Create post-processing effects that respond to the beat, such as the createBeatShakePostProcessor
.
Scene Builder: Use the SceneBuilder
class to easily create scenes with automatically calculated start times based on their durations.
Entity Builder: Use the EntityBuilder
class to easily arrange entities within a scene with precise timing control.
Sequence Helper: Use the SequenceHelper
class to calculate durations based on beats, bars, or ticks, given a specific BPM and time signature.
Compressor: Compress your animation and JavaScript code into a single, self-contained PNG image using the integrated Compressor
class.
Conductor: Use the Conductor
class to create a timeline of events that trigger actions on entities based on time, beats, bars, or custom conditions.
Effects and Helpers for common tasks:
EntityRenderer: A helper class to render and test individual entities or combinations of entities without needing a full Sequence
.
npm install demolished-rail
1. Create a Sequence:
Create an instance of the Sequence class, providing the target canvas, BPM, time signature, audio loader, and an optional array of scenes. Initialize the sequence using the initialize() method. This will load the audio and prepare the sequence for playback.
2. Create Scene objects:
Create instances of the Scene class, defining the name, start time, and duration of each scene. Add entities to each scene using the addEntity() or addEntities() methods.
3. Add scenes to the Sequence:
Use the addScene() or addScenes() methods of the Sequence to add your scenes to the animation sequence.
4. Start the animation:
Call the play() method on the Sequence instance to start the animation.
Example code
import { Scene, Sequence, DefaultAudioLoader, Entity } from 'demolished-rail';
// ... import other classes and effects ...
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const audioLoader = new DefaultAudioLoader('/path/to/your/audio.mp3'); // Use DefaultAudioLoader
const sequence = new Sequence(canvas, 120, 4, 4, audioLoader);
await sequence.initialize();
const scene1 = new Scene("Scene 1", 0, 10000);
// Example entity (replace with your actual entities)
const myEntity = new Entity(
"MyEntity",
{
// ... entity properties ...
},
(ts, ctx, props) => {
// ... entity action ...
}
);
scene1.addEntity(myEntity);
// ... create and add other scenes and entities ...
sequence.addScenes(scene1 /*, ... other scenes */);
sequence.play();
Demolished-rail provides a flexible way to create and animate entities using the HTML5 Canvas API. Here’s how to set up a Canvas2D entity in your production:
1. Define Entity Properties
2. Create the Entity
Entity
instance, passing the entity name, properties, and an action function.ctx
).Example Code
import { Entity } from 'demolished-rail';
// Define entity properties
interface ICircleProps {
x: number;
y: number;
radius: number;
color: string;
}
// Create the entity
const circleEntity = new Entity<ICircleProps>(
"MyCircle",
{
x: 100,
y: 100,
radius: 50,
color: 'red'
},
(ts, ctx, props) => {
// Draw a circle on the canvas
ctx.beginPath();
ctx.arc(props.x, props.y, props.radius, 0, 2 * Math.PI);
ctx.fillStyle = props.color;
ctx.fill();
}
);
// Add the entity to a scene
someScene.addEntity(circleEntity);
Demolished-rail also supports WebGL, allowing you to create shader entities using GLSL (OpenGL Shading Language). Here’s how to set up a WebGL shader entity in your production: 1. Shader Properties
IGLSLShaderProperties
to configure the shader entity. This object includes:
mainVertexShader
: The main vertex shader code (as a string).mainFragmentShader
: The main fragment shader code (as a string).renderBuffers
: An array of render buffer configurations, each with:
name
: The name of the buffer.vertex
: The vertex shader code for the buffer.fragment
: The fragment shader code for the buffer.textures
: An array of texture names or URLs used by the buffer.customUniforms
: An object defining any custom uniforms that need to be passed to the buffer’s shaders.2. Create the Shader Entity
GLSLShaderEntity
instance, passing the entity name, shader properties, an optional action function, and dimensions.Example Code
import {
DefaultAudioLoader,
Entity,
GLSLShaderEntity,
IEntity,
Scene,
Sequence,
SequenceHelper,
} from 'demolished-rail';
import { earthShader } from '../assets/shaders/earthShader';
import { mainFragment } from '../assets/shaders/mainFragment';
import { mainVertex } from '../assets/shaders/mainVertex';
// ... (inside your demo setup) ...
const cameraPositions = [
[0.0, 1.2, 0.7],
[0.5, 1.0, 0.9],
// ... more camera positions
];
let cameraPos = cameraPositions[0];
let amountOfLightning = 1500.0;
const shader = new GLSLShaderEntity<IEarthShader>(
"earthShader",
{
cameraPos: cameraPositions[0],
amountOfLightning: 1500,
mainFragmentShader: mainFragment,
mainVertexShader: mainVertex,
renderBuffers: [
{
name: "a_buffer",
fragment: earthShader,
vertex: mainVertex,
textures: [],
customUniforms: {
"amountOfLightning": (uniformLocation, gl, program, time, entity) => {
gl.uniform1f(uniformLocation, entity.props!.amountOfLightning);
},
"cameraPos": (uniformLocation, gl, program, time, entity) => {
gl.uniform3fv(uniformLocation, entity.props!.cameraPos);
}
}
}
]
},
(ts, render, propertybag) => {
// ... your additional logic for the shader entity ...
},
800, // Canvas width
450 // Canvas height
);
shader.onBar<IEarthShader>((ts, count, propertyBag) => {
const positionIndex = count % cameraPositions.length;
propertyBag!.cameraPos = cameraPositions[positionIndex];
});
shader.onTick<IEarthShader>((ts, count, propertyBag) => {
// ... update amountOfLightning based on audio ...
});
// Add the entity to a scene
someScene.addEntity(shader);
Demolished-rail now includes support for WebGPU, allowing you to create high-performance shader entities using the WGSL (WebGPU Shading Language). Here’s a breakdown of how to set up a WebGPU shader entity in your production:
1. Initialization
initWebGPU()
function.WGSLTextureLoader
.2. Shader Properties
IWGSLShaderProperties
to configure the shader entity. This object includes:
canvas
: The canvas element.device
: The WebGPU device.context
: The WebGPU canvas context.shader
: The main shader (usually the defaultMainShader
).renderBuffers
: An array of render buffer configurations, each with:
name
: The name of the buffer.shader
: A Material
object containing the WGSL vertex and fragment shader code.geometry
: A Geometry
object defining the geometry to be rendered.textures
: An array of IWgslTextureData
objects representing the textures to be used.3. Create the Shader Entity
WGSLShaderEntity
instance, passing the entity name, shader properties, and an optional action function.Example Code
import {
defaultMainShader,
Geometry,
IWGSLShaderProperties,
Material,
WGSLShaderEntity,
WGSLTextureLoader,
WGSLTextureType,
} from 'demolished-rail';
import { rectGeometry } from 'demolished-rail/Engine/ShaderRenderers/WebGPU/Geometry';
import { initWebGPU } from 'demolished-rail/Engine/ShaderRenderers/WebGPU/WGSLShaderRenderer';
import { wgslFlamesShader } from '../assets/shaders/wglsl/wgslFlamesShader';
// ... (inside your demo setup) ...
const wgslCanvas = document.createElement("canvas");
wgslCanvas.width = 800; // Set your desired width
wgslCanvas.height = 450; // Set your desired height
const webgpu = await initWebGPU(wgslCanvas);
const wsglTextures = await WGSLTextureLoader.loadAll(webgpu.device, {
key: "NOISE-TEXTURE",
source: "assets/images/noise.png",
type: WGSLTextureType.IMAGE,
});
const wgslShaderProps: IWGSLShaderProperties = {
canvas: wgslCanvas,
device: webgpu.device,
context: webgpu.context!,
shader: defaultMainShader,
renderBuffers: [
{
name: "buffer-01",
shader: new Material(webgpu.device, wgslFlamesShader),
geometry: new Geometry(webgpu.device, rectGeometry),
textures: wsglTextures
}
]
};
const wgslShaderEntity = new WGSLShaderEntity(
"wgsl-shader",
wgslShaderProps,
(ts: number, wgslRenderer: WGSLShaderRenderer, props: IWGSLShaderProperties) => {
// Perform operations, like modifying uniforms, per frame
}
);
// Add the entity to a scene
someScene.addEntity(wgslShaderEntity)
Contributions are welcome! Feel free to open issues or submit pull requests.