mirror of
https://github.com/SamEyeBam/animate.git
synced 2026-03-22 14:45:59 +00:00
V1.1
Giant refactor. added layers. ui overhaul. added save/load and we now got presets
This commit is contained in:
267
docs/js/core/Scene.js
Normal file
267
docs/js/core/Scene.js
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Scene - Manages multiple shapes on a single canvas
|
||||
*
|
||||
* Features:
|
||||
* - Multiple shapes rendered in layer order
|
||||
* - Per-shape visibility and pause controls
|
||||
* - Collapsible control panels per shape
|
||||
* - Add/remove shapes dynamically
|
||||
*/
|
||||
class Scene {
|
||||
constructor() {
|
||||
this.layers = []; // Array of { id, shape, visible, paused, collapsed }
|
||||
this.nextLayerId = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shape to the scene
|
||||
* @param {string} shapeName - Name of the shape class
|
||||
* @param {object} initialValues - Initial property values
|
||||
* @returns {number} Layer ID
|
||||
*/
|
||||
addShape(shapeName, initialValues = {}) {
|
||||
const shape = shapeRegistry.createInstance(shapeName, initialValues);
|
||||
|
||||
if (!shape) {
|
||||
throw new Error(`Failed to create shape: ${shapeName}`);
|
||||
}
|
||||
|
||||
const layer = {
|
||||
id: this.nextLayerId++,
|
||||
name: shapeName,
|
||||
shape: shape,
|
||||
visible: true,
|
||||
paused: false,
|
||||
collapsed: false
|
||||
};
|
||||
|
||||
this.layers.push(layer);
|
||||
return layer.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a shape from the scene
|
||||
* @param {number} layerId - Layer ID to remove
|
||||
*/
|
||||
removeShape(layerId) {
|
||||
const index = this.layers.findIndex(l => l.id === layerId);
|
||||
if (index !== -1) {
|
||||
const layer = this.layers[index];
|
||||
// Clean up controls
|
||||
if (layer.shape.remove) {
|
||||
layer.shape.remove();
|
||||
}
|
||||
this.layers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a layer by ID
|
||||
* @param {number} layerId
|
||||
* @returns {object|null}
|
||||
*/
|
||||
getLayer(layerId) {
|
||||
return this.layers.find(l => l.id === layerId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle layer visibility
|
||||
* @param {number} layerId
|
||||
*/
|
||||
toggleVisibility(layerId) {
|
||||
const layer = this.getLayer(layerId);
|
||||
if (layer) {
|
||||
layer.visible = !layer.visible;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle layer pause state
|
||||
* @param {number} layerId
|
||||
*/
|
||||
togglePause(layerId) {
|
||||
const layer = this.getLayer(layerId);
|
||||
if (layer) {
|
||||
layer.paused = !layer.paused;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle layer control panel collapsed state
|
||||
* @param {number} layerId
|
||||
*/
|
||||
toggleCollapsed(layerId) {
|
||||
const layer = this.getLayer(layerId);
|
||||
if (layer) {
|
||||
layer.collapsed = !layer.collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a layer up in the render order
|
||||
* @param {number} layerId
|
||||
*/
|
||||
moveUp(layerId) {
|
||||
const index = this.layers.findIndex(l => l.id === layerId);
|
||||
if (index > 0) {
|
||||
[this.layers[index - 1], this.layers[index]] = [this.layers[index], this.layers[index - 1]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a layer down in the render order
|
||||
* @param {number} layerId
|
||||
*/
|
||||
moveDown(layerId) {
|
||||
const index = this.layers.findIndex(l => l.id === layerId);
|
||||
if (index < this.layers.length - 1) {
|
||||
[this.layers[index], this.layers[index + 1]] = [this.layers[index + 1], this.layers[index]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw all visible shapes
|
||||
* @param {number} elapsed - Total elapsed time
|
||||
* @param {number} deltaTime - Time since last frame
|
||||
*/
|
||||
draw(elapsed, deltaTime) {
|
||||
for (const layer of this.layers) {
|
||||
if (layer.visible && !layer.paused) {
|
||||
layer.shape.draw(elapsed, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all layers
|
||||
* @returns {array}
|
||||
*/
|
||||
getLayers() {
|
||||
return this.layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all layers
|
||||
*/
|
||||
clear() {
|
||||
for (const layer of this.layers) {
|
||||
if (layer.shape.remove) {
|
||||
layer.shape.remove();
|
||||
}
|
||||
}
|
||||
this.layers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Export scene to JSON
|
||||
* @returns {object} Scene data
|
||||
*/
|
||||
exportToJSON() {
|
||||
return {
|
||||
version: 1,
|
||||
layers: this.layers.map(layer => {
|
||||
const shape = layer.shape;
|
||||
const ShapeClass = shape.constructor;
|
||||
const config = ShapeClass.config || [];
|
||||
|
||||
// Extract current values from shape
|
||||
const values = {};
|
||||
config.forEach(item => {
|
||||
if (item.property && shape.hasOwnProperty(item.property)) {
|
||||
values[item.property] = shape[item.property];
|
||||
}
|
||||
});
|
||||
|
||||
// Also save speedMultiplier
|
||||
values.speedMultiplier = shape.speedMultiplier;
|
||||
|
||||
// Extract filter settings and control bounds from shape.controls
|
||||
const filters = {};
|
||||
const controlBounds = {};
|
||||
if (shape.controls) {
|
||||
for (const control of shape.controls) {
|
||||
const property = control.config?.property;
|
||||
if (!property) continue;
|
||||
|
||||
// Save filters
|
||||
if (control.filters && control.filters.length > 0) {
|
||||
filters[property] = control.filters.map(f => ({
|
||||
type: f.type,
|
||||
params: { ...f.params }
|
||||
}));
|
||||
}
|
||||
|
||||
// Save custom min/max bounds if different from original
|
||||
if (control.configState) {
|
||||
const { min, max, originalMin, originalMax } = control.configState;
|
||||
if (min !== originalMin || max !== originalMax) {
|
||||
controlBounds[property] = { min, max };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: layer.name,
|
||||
visible: layer.visible,
|
||||
paused: layer.paused,
|
||||
collapsed: layer.collapsed,
|
||||
values: values,
|
||||
filters: filters,
|
||||
controlBounds: controlBounds
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import scene from JSON
|
||||
* @param {object} data - Scene data
|
||||
* @param {SceneUI} sceneUI - UI instance to rebuild panels
|
||||
*/
|
||||
importFromJSON(data, sceneUI) {
|
||||
// Clear existing
|
||||
this.clear();
|
||||
if (sceneUI) {
|
||||
sceneUI.clearAllPanels();
|
||||
}
|
||||
|
||||
// Validate version
|
||||
if (!data.version || !data.layers) {
|
||||
throw new Error('Invalid scene data');
|
||||
}
|
||||
|
||||
// Recreate layers
|
||||
for (const layerData of data.layers) {
|
||||
try {
|
||||
// Check if shape type exists
|
||||
if (!shapeRegistry.get(layerData.name)) {
|
||||
console.warn(`Shape "${layerData.name}" not found, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const layerId = this.addShape(layerData.name, layerData.values);
|
||||
const layer = this.getLayer(layerId);
|
||||
|
||||
if (!layer || !layer.shape) {
|
||||
console.warn(`Failed to create layer for "${layerData.name}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
layer.visible = layerData.visible !== false;
|
||||
layer.paused = layerData.paused || false;
|
||||
layer.collapsed = layerData.collapsed || false;
|
||||
|
||||
// Create UI panel (this also creates controls, applies filters and bounds)
|
||||
if (sceneUI) {
|
||||
sceneUI.createLayerPanel(layer, layerData.filters, layerData.controlBounds);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Failed to load shape "${layerData.name}":`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global scene instance
|
||||
const scene = new Scene();
|
||||
Reference in New Issue
Block a user