Read the pixel size of a texture in a graph (#262)
A graph had no way to know how big a picture is. An icon sprite with a fixed size therefore stretched every icon of another size, an 8x8 pixel-art icon in a 16x16 slot was drawn twice as big. - New data node Texture size. It takes a texture, a frame or a pixel art and hands out width and height in pixels, 0 x 0 for none. Wired into a sprite's width and height, the picture shows pixel for pixel. - GraphLookup gets textureSize. The project answers it with the same pictureSize rule a sprite uses to size itself. Tests: - ./check.sh: 2749 tests, CHECK EXIT 0. - 2 catalog cases (a picture, no picture) and one test against a real project: pixel-art sheet, single frame, missing texture. - No e2e check and no counter-check for this node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d7d0a6e7d4
commit
ef2600493f
6 files changed
+71
No files matched your search
@@ -46,6 +46,8 @@ export interface GraphLookup {
|
||||
curve(assetId: string): readonly VectorPoint[] | null;
|
||||
/** A Struct asset as a run knows it: a name and field names, nothing else travels (#254). */
|
||||
structType(assetId: string): StructShape | null;
|
||||
/** A texture's, frame's or pixel-art frame's own size in pixels; null for no picture. */
|
||||
textureSize(pictureId: string): { w: number; h: number } | null;
|
||||
}
|
||||
|
||||
export interface StructShape {
|
||||
@@ -58,4 +60,5 @@ export const NO_LOOKUP: GraphLookup = {
|
||||
machine: () => null,
|
||||
curve: () => null,
|
||||
structType: () => null,
|
||||
textureSize: () => null,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { asString } from '../../values';
|
||||
import { num } from '../shared';
|
||||
|
||||
/**
|
||||
* A picture's own pixel size: a texture, one of its frames or a pixel-art frame, as a sprite wears
|
||||
* it. Written into a sprite's `width` and `height`, the picture shows pixel for pixel – an icon slot
|
||||
* that holds pictures of different sizes stretches none of them. 0 × 0 for no picture.
|
||||
*/
|
||||
export const TextureSize: NodeDef = {
|
||||
type: 'TextureSize',
|
||||
label: 'Texture size',
|
||||
category: 'data',
|
||||
description: "The picture's own size in pixels, 0 × 0 for none – to show a sprite pixel for pixel.",
|
||||
inputs: [
|
||||
{
|
||||
key: 'texture',
|
||||
label: 'texture',
|
||||
type: 'string',
|
||||
field: { key: 'texture', label: 'Texture', kind: 'texture', default: '' },
|
||||
},
|
||||
],
|
||||
outputs: [num('width'), num('height')],
|
||||
params: [],
|
||||
read: (_ctx, _params, input, _def, lookup) => {
|
||||
const size = lookup.textureSize(asString(input('texture')));
|
||||
return { width: size?.w ?? 0, height: size?.h ?? 0 };
|
||||
},
|
||||
};
|
||||
@@ -99,6 +99,7 @@ import { IndexOf } from './data/IndexOf';
|
||||
import { ItemAt } from './data/ItemAt';
|
||||
import { SetItem } from './data/SetItem';
|
||||
import { SubList } from './data/SubList';
|
||||
import { TextureSize } from './data/TextureSize';
|
||||
import { OnAnimationFinished } from './events/OnAnimationFinished';
|
||||
import { OnClick } from './events/OnClick';
|
||||
import { OnCollisionEnter } from './events/OnCollisionEnter';
|
||||
@@ -184,6 +185,7 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
IsEnabled,
|
||||
Properties,
|
||||
GetPosition,
|
||||
TextureSize,
|
||||
NumberLiteral,
|
||||
BooleanLiteral,
|
||||
StringLiteral,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { GraphLookup } from '../graph/lookup';
|
||||
import { startStateOf, statesOf, transitionsOf } from '../graph/stateMachine';
|
||||
import { pictureSize } from '../properties/aspect';
|
||||
import { resolveAssetProps } from '../properties/resolve';
|
||||
import { bagWithoutItemTypes } from './Properties';
|
||||
import { typeFields } from './structTypes';
|
||||
import { findAssetOf, type Project } from './Project';
|
||||
import { resolvePicture } from './texture';
|
||||
|
||||
/**
|
||||
* What a graph may see of the project around it. A behavior's `defaults` are its variable values;
|
||||
@@ -36,5 +38,9 @@ export function graphLookup(project: Project): GraphLookup {
|
||||
const asset = findAssetOf(project, assetId, 'struct');
|
||||
return asset ? { name: asset.name, fields: typeFields(asset).map((f) => f.key) } : null;
|
||||
},
|
||||
textureSize: (pictureId) => {
|
||||
const ref = resolvePicture(project, pictureId);
|
||||
return ref ? pictureSize(ref) : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -265,6 +265,19 @@ const CASES: Record<string, Case[]> = {
|
||||
after: (ctx) => expect(ctx.states).toEqual({}),
|
||||
},
|
||||
],
|
||||
TextureSize: [
|
||||
{
|
||||
name: "hands out the picture's own pixel size",
|
||||
inputs: { texture: 'coin' },
|
||||
lookup: { ...NO_LOOKUP, textureSize: (id) => (id === 'coin' ? { w: 8, h: 6 } : null) },
|
||||
outputs: { width: 8, height: 6 },
|
||||
},
|
||||
{
|
||||
name: 'no picture is 0 × 0',
|
||||
inputs: { texture: '' },
|
||||
outputs: { width: 0, height: 0 },
|
||||
},
|
||||
],
|
||||
CurrentState: [
|
||||
{
|
||||
name: 'names the state the machine is in, and hands out its id as well',
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
withLayerMoved,
|
||||
withLayerRemoved,
|
||||
} from '../../src/core/model/pixelEdit';
|
||||
import { graphLookup } from '../../src/core/model/graphLookup';
|
||||
import { findAsset, type Project } from '../../src/core/model/Project';
|
||||
import { resolvePicture } from '../../src/core/model/texture';
|
||||
import { pictureChoices, textureRefLabel } from '../../src/core/properties/textureRef';
|
||||
@@ -295,6 +296,23 @@ describe('the picture', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('tells a graph its own pixel size, the sheet for the asset and the cut for a frame', () => {
|
||||
const remove = registerPictureSource('pixelart', pixelArtPicture);
|
||||
try {
|
||||
const { project } = makeProject();
|
||||
const asset = art(project);
|
||||
asset.width = 8;
|
||||
asset.height = 6;
|
||||
asset.frames.push({ id: 'second', cels: {} });
|
||||
const lookup = graphLookup(project);
|
||||
expect(lookup.textureSize(asset.id)).toEqual({ w: 16, h: 6 });
|
||||
expect(lookup.textureSize('second')).toEqual({ w: 8, h: 6 });
|
||||
expect(lookup.textureSize('gone')).toBeNull();
|
||||
} finally {
|
||||
remove();
|
||||
}
|
||||
});
|
||||
|
||||
it('with one frame is offered as the picture alone', () => {
|
||||
const remove = registerPictureSource('pixelart', pixelArtPicture);
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user