refactor: share trace, error boundary, and proxy http helpers
Lift the trace-to-shape geometry into @tts/mesh (traceToShape/ traceToUvBounds), parameterized by scale so the web token viewer and @tts/tabletop share one implementation. Move the CORS proxy HTTP helpers (assetUrl, resolveAssetUrl, traceImage) into a new @tts/http package, and make @tts/tabletop's ErrorBoundary the single source used by the web app. This removes the duplicated ErrorBoundary, assetUrl, tabletopHttp, and trace-to-shape code from apps/web and packages/tabletop.
This commit is contained in:
@@ -4,7 +4,13 @@
|
||||
* app's `cardResolution.ts`).
|
||||
*/
|
||||
import type { Part, Crop } from '@tts/bgm';
|
||||
import { rectShape, roundedRectShape, type Shape } from '@tts/mesh';
|
||||
import {
|
||||
rectShape,
|
||||
roundedRectShape,
|
||||
traceToShape as meshTraceToShape,
|
||||
traceToUvBounds as meshTraceToUvBounds,
|
||||
type Shape,
|
||||
} from '@tts/mesh';
|
||||
|
||||
/** Scale from mm (the format's `size` unit) to world units. */
|
||||
export const MM_TO_WORLD = 1 / 30;
|
||||
@@ -67,15 +73,8 @@ export function traceToShape(
|
||||
width: number,
|
||||
height: number,
|
||||
): Shape {
|
||||
const { shape, width: tw, height: th } = trace;
|
||||
const scale = Math.min(width / tw, height / th);
|
||||
const ox = (tw * scale) / 2;
|
||||
const oy = (th * scale) / 2;
|
||||
const transform = (pts: number[][]) => pts.map(([x, y]) => [x! * scale - ox, (th - y!) * scale - oy]);
|
||||
return {
|
||||
outline: normalizeWinding(transform(shape.outline), true),
|
||||
holes: shape.holes?.map((h) => normalizeWinding(transform(h), false)),
|
||||
};
|
||||
const { width: tw, height: th } = trace;
|
||||
return meshTraceToShape(trace, Math.min(width / tw, height / th));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,25 +87,5 @@ export function traceToUvBounds(
|
||||
width: number,
|
||||
height: number,
|
||||
): { minX: number; minY: number; maxX: number; maxY: number } {
|
||||
const scale = Math.min(width / trace.width, height / trace.height);
|
||||
const ox = (trace.width * scale) / 2;
|
||||
const oy = (trace.height * scale) / 2;
|
||||
return { minX: -ox, minY: -oy, maxX: ox, maxY: oy };
|
||||
}
|
||||
|
||||
/** Ensure a ring has the requested winding. `ccw` true yields CCW (outline). */
|
||||
function normalizeWinding(pts: number[][], ccw: boolean): number[][] {
|
||||
const isCcw = signedArea(pts) > 0;
|
||||
return isCcw === ccw ? pts : [...pts].reverse();
|
||||
}
|
||||
|
||||
/** Signed area of a polygon; positive means counter-clockwise. */
|
||||
function signedArea(points: number[][]): number {
|
||||
let area = 0;
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const [x1, y1] = points[i]!;
|
||||
const [x2, y2] = points[(i + 1) % points.length]!;
|
||||
area += x1! * y2! - x2! * y1!;
|
||||
}
|
||||
return area / 2;
|
||||
return meshTraceToUvBounds(trace, Math.min(width / trace.width, height / trace.height));
|
||||
}
|
||||
Reference in New Issue
Block a user