Shader assets: graph-built shaders, the Shader tab and sprite rows from the graph (#62)
game-maker / suite (push) Failing after 3m59s
game-maker / suite (push) Failing after 3m59s
A shader is an asset carrying a node graph (Texture color, Output, Property, Color, Add/Subtract/Multiply/Divide, Mix, Luminance) that compiles to the GLSL fragment of a Pixi filter. The Shader tab reuses the Behavior canvas; a sprite wears a shader through its Shader picker, and the rows below it are the graph's Property nodes. Property schema, resolution and deviation take the project because a sprite's rows now live in another asset. The built-in registry of the prototype is gone; "Adjust colors" ships as a graph in the fixture and the three example games. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
6141d07d4f
commit
42cafa7543
62 files changed
+1682
-144
No files matched your search
@@ -8,9 +8,11 @@ import {
|
||||
type GraphNode,
|
||||
isValidPortName,
|
||||
fixedPortKeys,
|
||||
type NodeDef,
|
||||
nodeDef,
|
||||
STATE,
|
||||
} from '../graph';
|
||||
import { isShaderNodeDef } from '../shader/nodes';
|
||||
import type { Asset, TextureFrame } from '../model/Asset';
|
||||
import { isAssetType } from '../model/AssetTypes';
|
||||
import { emptyEdit, type InstanceEdit, isEmptyEdit } from '../model/instance';
|
||||
@@ -45,6 +47,7 @@ import {
|
||||
type StructFieldDef,
|
||||
variableDef,
|
||||
} from '../properties/PropertySchema';
|
||||
import { SHADER_KEY, shaderParamDefs } from '../properties/shaders';
|
||||
|
||||
/**
|
||||
* Turns untrusted JSON (server file, import) into a well-formed Project: unknown keys are dropped,
|
||||
@@ -57,16 +60,33 @@ export function deserializeProject(raw: unknown, fallbackId = ''): Project {
|
||||
// Pass 1: asset ids and their effective schemas, needed to coerce overrides in pass 2.
|
||||
const schemas = new Map<string, PropertySchema>();
|
||||
const variables = new Map<string, PropertyDef[]>();
|
||||
const rawProps = new Map<string, Record<string, unknown>>();
|
||||
const shaderGraphs = new Map<string, Graph>();
|
||||
const shaderRows = new Map<string, PropertyDef[]>();
|
||||
for (const a of rawAssets) {
|
||||
if (typeof a.id !== 'string' || !a.id || !isAssetType(a.type)) continue;
|
||||
rawProps.set(a.id, asRecord(a.props));
|
||||
if (a.type === 'behavior') {
|
||||
const vars = normalizeVariables(a.variables);
|
||||
variables.set(a.id, vars);
|
||||
schemas.set(a.id, [...COMMON_PROPS, ...vars]);
|
||||
} else {
|
||||
if (a.type === 'shader') {
|
||||
const graph = normalizeGraph(a.graph, isShaderNodeDef);
|
||||
shaderGraphs.set(a.id, graph);
|
||||
shaderRows.set(a.id, shaderParamDefs(graph));
|
||||
}
|
||||
schemas.set(a.id, schemaFor(a.type));
|
||||
}
|
||||
}
|
||||
/** A sprite's rows end with the parameters of the shader its bag names – an override's, else the asset's. */
|
||||
const schemaOf = (assetId: string, bag: Record<string, unknown>): PropertySchema | undefined => {
|
||||
const schema = schemas.get(assetId);
|
||||
if (!schema || schema !== schemaFor('sprite')) return schema;
|
||||
const own = bag[SHADER_KEY];
|
||||
const shader = typeof own === 'string' ? own : rawProps.get(assetId)?.[SHADER_KEY];
|
||||
return [...schema, ...(shaderRows.get(typeof shader === 'string' ? shader : '') ?? [])];
|
||||
};
|
||||
|
||||
// Pass 2: build assets. Frame ids share the reference space with asset ids, so `normalizeTexture`
|
||||
// is handed the ids already spoken for.
|
||||
@@ -74,13 +94,13 @@ export function deserializeProject(raw: unknown, fallbackId = ''): Project {
|
||||
const takenIds = new Set(schemas.keys());
|
||||
for (const a of rawAssets) {
|
||||
const id = typeof a.id === 'string' ? a.id : '';
|
||||
const schema = schemas.get(id);
|
||||
const schema = schemaOf(id, asRecord(a.props));
|
||||
if (!schema || !isAssetType(a.type)) continue;
|
||||
const type = a.type;
|
||||
const name = typeof a.name === 'string' ? a.name : type;
|
||||
const props = normalizeBag(a.props, schema, false);
|
||||
if (type === 'scene' || type === 'container') {
|
||||
assets.push({ id, type, name, props, objects: normalizeObjects(a.objects, schemas) });
|
||||
assets.push({ id, type, name, props, objects: normalizeObjects(a.objects, schemaOf) });
|
||||
} else if (type === 'behavior') {
|
||||
assets.push({
|
||||
id,
|
||||
@@ -91,7 +111,9 @@ export function deserializeProject(raw: unknown, fallbackId = ''): Project {
|
||||
graph: normalizeGraph(a.graph),
|
||||
});
|
||||
} else if (type === 'statemachine') {
|
||||
assets.push({ id, type, name, props, graph: normalizeGraph(a.graph, STATE) });
|
||||
assets.push({ id, type, name, props, graph: normalizeGraph(a.graph, (def) => def.type === STATE) });
|
||||
} else if (type === 'shader') {
|
||||
assets.push({ id, type, name, props, graph: shaderGraphs.get(id) ?? createEmptyGraph() });
|
||||
} else if (type === 'texture') {
|
||||
assets.push({ id, type, name, props, ...normalizeTexture(a, takenIds) });
|
||||
} else if (type === 'animation') {
|
||||
@@ -130,12 +152,15 @@ export function serializeProject(project: Project): unknown {
|
||||
return JSON.parse(JSON.stringify(project));
|
||||
}
|
||||
|
||||
function normalizeObjects(raw: unknown, schemas: Map<string, PropertySchema>): SceneObject[] {
|
||||
function normalizeObjects(
|
||||
raw: unknown,
|
||||
schemaOf: (assetId: string, overrides: Record<string, unknown>) => PropertySchema | undefined,
|
||||
): SceneObject[] {
|
||||
const objects: SceneObject[] = [];
|
||||
for (const item of Array.isArray(raw) ? raw : []) {
|
||||
const o = asRecord(item);
|
||||
if (typeof o.id !== 'string' || !o.id || typeof o.assetId !== 'string') continue;
|
||||
const schema = schemas.get(o.assetId);
|
||||
const schema = schemaOf(o.assetId, asRecord(o.overrides));
|
||||
if (!schema || schema === schemaFor('scene')) continue; // dangling reference or illegal scene-in-scene
|
||||
const p = asRecord(o.position);
|
||||
const offset = asRecord(o.editorOffset);
|
||||
@@ -245,8 +270,8 @@ function normalizePorts(raw: unknown, fixed: readonly string[], writable: boolea
|
||||
return ports;
|
||||
}
|
||||
|
||||
/** `only` restricts the node types kept: a state machine's graph holds states and nothing else. */
|
||||
function normalizeGraph(raw: unknown, only?: string): Graph {
|
||||
/** `keep` restricts the node types kept: a state machine's graph holds states, a shader's shader nodes, and nothing else. */
|
||||
function normalizeGraph(raw: unknown, keep?: (def: NodeDef) => boolean): Graph {
|
||||
const g = asRecord(raw);
|
||||
const graph = createEmptyGraph();
|
||||
const nodeIds = new Set<string>();
|
||||
@@ -255,7 +280,7 @@ function normalizeGraph(raw: unknown, only?: string): Graph {
|
||||
const id = typeof n.id === 'string' ? n.id : '';
|
||||
const type = typeof n.type === 'string' ? n.type : '';
|
||||
const def = nodeDef(type);
|
||||
if (!id || nodeIds.has(id) || !def || (only !== undefined && type !== only)) continue;
|
||||
if (!id || nodeIds.has(id) || !def || (keep !== undefined && !keep(def))) continue;
|
||||
nodeIds.add(id);
|
||||
// Params carry both settings and inline input values, so there is no schema to coerce against.
|
||||
const params: PropertyBag = {};
|
||||
|
||||
Reference in new issue
Block a user