Fit sprite size to the texture aspect ratio
Add ↔ next to Width and ↕ next to Height in the inspector. Each sets its field so the sprite matches the ratio of its assigned texture or frame, reading the frame's own rect rather than the sheet's. The buttons reuse the existing FieldActions convention, so they simply do not appear when no texture is assigned, when the sprite is already proportional, or on assets whose schema has no texture property. Comparison is a cross product with a relative tolerance: 3135 of the 40000 size pairs up to 200x200 do not survive a round trip through a division, so an exact check would leave the button standing after its own click. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d84231b9f
commit
0b34bdd990
9 files changed
+412
-65
No files matched your search
@@ -0,0 +1,47 @@
|
||||
import type { PropertyBag } from '../model/Properties';
|
||||
import type { Project } from '../model/Project';
|
||||
import { resolveTexture } from '../model/texture';
|
||||
|
||||
export type SizeAxis = 'width' | 'height';
|
||||
|
||||
export interface PixelSize {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pixel size of the picture a sprite wears: its frame's rectangle, else the whole image.
|
||||
* Null while that is not known – an upload still running has no measured size yet, and a
|
||||
* ratio taken from 0 × 0 would be invented.
|
||||
*/
|
||||
export function texturePixelSize(project: Project, props: PropertyBag): PixelSize | null {
|
||||
const ref = resolveTexture(project, props);
|
||||
if (!ref) return null;
|
||||
const w = ref.rect ? ref.rect.w : ref.sourceWidth;
|
||||
const h = ref.rect ? ref.rect.h : ref.sourceHeight;
|
||||
return w > 0 && h > 0 ? { w, h } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* What `axis` has to become for the sprite to carry the picture's proportions, leaving the
|
||||
* other side where it is. Null when the other side is not a usable number.
|
||||
*/
|
||||
export function fitAxisToAspect(size: PixelSize, props: PropertyBag, axis: SizeAxis): number | null {
|
||||
const other = Number(props[axis === 'width' ? 'height' : 'width']);
|
||||
if (!Number.isFinite(other) || other <= 0) return null;
|
||||
const fitted = axis === 'width' ? (other * size.w) / size.h : (other * size.h) / size.w;
|
||||
return Number.isFinite(fitted) && fitted > 0 ? fitted : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the sprite already has the picture's proportions. Compared as a cross product with a
|
||||
* relative tolerance, because fitting one side and then asking about the other must not answer
|
||||
* "no" over a rounding error: 3135 of the 40 000 sizes up to 200 × 200 fail an exact test after
|
||||
* their own fit (1 × 11 at height 100 gives 9.090909090909092, whose cross product is 100.000…1).
|
||||
*/
|
||||
export function matchesAspect(size: PixelSize, props: PropertyBag): boolean {
|
||||
const w = Number(props.width);
|
||||
const h = Number(props.height);
|
||||
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return false;
|
||||
return Math.abs(w * size.h - h * size.w) <= 1e-9 * w * size.h;
|
||||
}
|
||||
Reference in new issue
Block a user