Add textures for sprites
Images are uploaded to a content-addressed blob endpoint and stored as data/textures/<sha256>.<ext>; the project only keeps the file name. The sprite points at a texture asset through an ordinary property of the new kind `asset`, so instance overrides, "Apply to container", "Reset to asset", undo and `Set property` work without any extra code. The new kind also replaces the ad-hoc asset picker the graph nodes faked as an enum. Import goes through + -> Texture, drag & drop onto the canvas or Ctrl+V. Images wider than 2048px are downscaled client-side and re-encoded to WebP if needed, because nginx caps uploads at 1MB; an image that already fits is uploaded byte for byte so pixel art is never resampled. The sprite colour default changes from red to white, since colour tints the texture multiplicatively and a tint of white shows an image as it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6168df41f8
commit
5a6af74e44
47 files changed
+1995
-110
No files matched your search
@@ -1,21 +1,28 @@
|
||||
import { createEmptyGraph } from '../graph/Graph';
|
||||
import { newId } from '../ids';
|
||||
import type { Asset } from '../model/Asset';
|
||||
import type { Asset, TextureAsset } from '../model/Asset';
|
||||
import type { AssetType } from '../model/AssetTypes';
|
||||
import { removeSubtrees } from '../model/hierarchy';
|
||||
import type { PropertyBag, PropertyValue } from '../model/Properties';
|
||||
import { objectHolders, type Project } from '../model/Project';
|
||||
import type { SceneObject } from '../model/SceneObject';
|
||||
import { isTextureFile } from '../model/texture';
|
||||
import { coerceValue, defaultsFor } from '../properties/PropertySchema';
|
||||
import { type Removed, removeById, removeFirst, restoreAt } from '../util/list';
|
||||
import { type Command, CommandError } from './Command';
|
||||
import { requireAsset, requirePropDef } from './require';
|
||||
import { SetBagValueCommand } from './SetBagValueCommand';
|
||||
|
||||
/** A new asset of the given type with default properties (and empty content for scenes, behaviors, containers). */
|
||||
/**
|
||||
* A new asset of the given type with default properties (and empty content for scenes,
|
||||
* behaviors, containers). A texture starts without a picture; the upload fills it in, which
|
||||
* is why `CreateTextureCommand` exists rather than a plain create followed by an edit.
|
||||
*/
|
||||
export function buildAsset(type: AssetType, name: string, id = newId()): Asset {
|
||||
const base = { id, name, props: defaultsFor(type) };
|
||||
switch (type) {
|
||||
case 'texture':
|
||||
return { ...base, type: 'texture', file: '', width: 0, height: 0 };
|
||||
case 'scene':
|
||||
return { ...base, type: 'scene', objects: [] };
|
||||
case 'camera':
|
||||
@@ -56,6 +63,40 @@ export class CreateAssetCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
/** The upload result a texture asset is built from (see `TextureStore.upload`). */
|
||||
export interface TextureImage {
|
||||
file: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a texture asset for an image that is already stored on the server. Uploading is I/O and
|
||||
* therefore happens before the command: this one only records the result, so undo and redo of
|
||||
* an import cost nothing and never touch the network.
|
||||
*/
|
||||
export class CreateTextureCommand implements Command {
|
||||
readonly label: string;
|
||||
readonly asset: TextureAsset;
|
||||
|
||||
constructor(name: string, image: TextureImage) {
|
||||
this.asset = { ...buildAsset('texture', name), type: 'texture', ...image };
|
||||
this.label = `Import texture "${name}"`;
|
||||
}
|
||||
|
||||
execute(project: Project): void {
|
||||
// The name is the hash the server derived from the bytes; anything else never was uploaded
|
||||
// and would leave a texture that silently draws nothing.
|
||||
if (!isTextureFile(this.asset.file))
|
||||
throw new CommandError(`"${this.asset.file}" is not a stored image file`);
|
||||
project.assets.push(this.asset);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
project.assets = removeById(project.assets, this.asset.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an asset and, as a cascade, every scene object referencing it – together with the
|
||||
* objects parented below those (a subtree is removed as a whole). Container templates
|
||||
|
||||
Reference in new issue
Block a user