Skip to content

Commit 12e3ed1

Browse files
author
DavidQ
committed
Finish shared transform pipeline cleanup across runtime, inspector, and editor preview - PR_26139_013-final-transform-pipeline-cleanup
1 parent bc208d8 commit 12e3ed1

8 files changed

Lines changed: 253 additions & 110 deletions

File tree

src/engine/collision/objectVector.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import { isColliding } from './aabb.js';
88
import { arePolygonsColliding, getPolygonBounds, isPointInPolygon } from './polygon.js';
99
import { areMasksColliding, createRasterMask } from './raster.js';
1010
import {
11+
createObjectVectorTransformPipeline,
1112
normalizeObjectVectorOrigin,
1213
normalizeObjectVectorTransform,
13-
transformObjectVectorInstancePoint,
14-
transformObjectVectorShapePoint,
1514
transformRuntimeOrientedPoints,
1615
} from '../rendering/OrientationTransform.js';
1716

@@ -190,10 +189,6 @@ function shapeLocalPolygons(shape) {
190189
return [];
191190
}
192191

193-
function transformInstancePoint(point, instance) {
194-
return transformObjectVectorInstancePoint(point, instance, { rotationUnit: 'degrees' });
195-
}
196-
197192
export function transformCollisionPoints(points, {
198193
x = 0,
199194
y = 0,
@@ -276,17 +271,23 @@ export function getObjectVectorCollisionOutlinePoints(object, options = {}) {
276271
const tool = shapeTool(candidate);
277272
return tool === 'polygon' || tool === 'polyline';
278273
}) || visibleEffectiveShapes(object, options).find((candidate) => shapeTool(candidate) === 'line');
279-
const transform = shapeTransform(shape);
280-
return shapeLocalOutlinePoints(shape).map((point) => transformObjectVectorShapePoint(point, transform, origin));
274+
return createObjectVectorTransformPipeline({
275+
objectOrigin: origin,
276+
shapeTransform: shapeTransform(shape)
277+
}).localPointsToShape(shapeLocalOutlinePoints(shape));
281278
}
282279

283280
export function createObjectVectorCollisionGeometry(object, instance = {}, options = {}) {
284281
const origin = getObjectVectorOrigin(object);
285282
const polygons = visibleEffectiveShapes(object, options)
286283
.flatMap((shape) => {
287-
const transform = shapeTransform(shape);
284+
const pipeline = createObjectVectorTransformPipeline({
285+
instance,
286+
objectOrigin: origin,
287+
shapeTransform: shapeTransform(shape)
288+
});
288289
return shapeLocalPolygons(shape)
289-
.map((polygon) => polygon.map((point) => transformInstancePoint(transformObjectVectorShapePoint(point, transform, origin), instance)))
290+
.map((polygon) => pipeline.localPointsToWorld(polygon))
290291
.filter((polygon) => polygon.length >= 3);
291292
});
292293
const bounds = boundsFromPolygons(polygons);
@@ -298,7 +299,7 @@ export function createObjectVectorCollisionGeometry(object, instance = {}, optio
298299
mask: maskFromPolygons(polygons, bounds, maskCellSize),
299300
object,
300301
origin,
301-
originWorld: object ? transformInstancePoint(origin, instance) : { x: 0, y: 0 },
302+
originWorld: object ? createObjectVectorTransformPipeline({ instance, objectOrigin: origin }).originWorld() : { x: 0, y: 0 },
302303
polygons,
303304
shapeRotations: [...new Set(visibleEffectiveShapes(object, options).map((shape) => shapeTransform(shape).rotation))],
304305
transformedPoints: polygons.flat(),

src/engine/rendering/ObjectVectorRuntimeAssetService.js

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import {
22
applyObjectVectorCanvasTransform,
3+
combineObjectVectorBounds,
34
normalizeObjectVectorOrigin,
45
normalizeObjectVectorTransform,
6+
objectVectorBoundsCornerPoints,
7+
transformedObjectVectorShapeBounds,
58
objectVectorSvgTransformAttribute
69
} from "./OrientationTransform.js";
710
import { createWorldScreenTransform } from "./WorldScreenTransform.js";
@@ -376,26 +379,12 @@ function objectBounds(object, frame) {
376379
if (!visibleShapes.length) {
377380
return { height: 80, width: 120, x: -60, y: -40 };
378381
}
379-
const bounds = visibleShapes.map((shape) => {
380-
const baseBounds = shapeBounds(shape);
381-
const transform = shapeTransform(shape);
382-
return {
383-
height: Math.max(1, baseBounds.height * transform.scaleY),
384-
width: Math.max(1, baseBounds.width * transform.scaleX),
385-
x: baseBounds.x + transform.x,
386-
y: baseBounds.y + transform.y
387-
};
388-
});
389-
const minX = Math.min(...bounds.map((entry) => entry.x));
390-
const minY = Math.min(...bounds.map((entry) => entry.y));
391-
const maxX = Math.max(...bounds.map((entry) => entry.x + entry.width));
392-
const maxY = Math.max(...bounds.map((entry) => entry.y + entry.height));
393-
return {
394-
height: Number((maxY - minY).toFixed(3)),
395-
width: Number((maxX - minX).toFixed(3)),
396-
x: Number(minX.toFixed(3)),
397-
y: Number(minY.toFixed(3))
398-
};
382+
const transformOrigin = objectTransformOrigin(object);
383+
return combineObjectVectorBounds(visibleShapes.map((shape) => transformedObjectVectorShapeBounds(
384+
objectVectorBoundsCornerPoints(shapeBounds(shape)),
385+
shapeTransform(shape),
386+
transformOrigin
387+
)));
399388
}
400389

401390
function effectiveShapeForFrame(shape, frame, shapeIndex) {
@@ -844,13 +833,11 @@ export class ObjectVectorRuntimeAssetService {
844833

845834
drawObjectToCanvas(context, object, frame, options = {}) {
846835
try {
847-
const renderTransform = createWorldScreenTransform({
836+
const worldScreenTransform = createWorldScreenTransform({
848837
worldScale: options.worldScale
849-
}).objectRenderOptions(options);
838+
});
850839
context.save();
851-
context.translate(renderTransform.x, renderTransform.y);
852-
context.rotate(renderTransform.rotation);
853-
context.scale(renderTransform.scale, renderTransform.scale);
840+
worldScreenTransform.applyObjectRenderTransform(context, options);
854841
let renderedShapes = 0;
855842
const transformOrigin = objectTransformOrigin(object);
856843
sortedShapes(object).forEach((shape, shapeIndex) => {

src/engine/rendering/OrientationTransform.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const DEGREES_PER_RADIAN = 180 / Math.PI;
22
const RADIANS_PER_DEGREE = Math.PI / 180;
3+
const DEFAULT_OBJECT_VECTOR_BOUNDS = Object.freeze({ height: 80, width: 120, x: -60, y: -40 });
34

45
function isRecord(value) {
56
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -89,6 +90,10 @@ export function transformObjectVectorShapePoint(point, transform = {}, origin =
8990
};
9091
}
9192

93+
export function transformObjectVectorShapePoints(points, transform = {}, origin = {}) {
94+
return (Array.isArray(points) ? points : []).map((point) => transformObjectVectorShapePoint(point, transform, origin));
95+
}
96+
9297
export function inverseTransformObjectVectorShapePoint(point, transform = {}, origin = {}) {
9398
const resolvedTransform = normalizeObjectVectorTransform(transform);
9499
const resolvedOrigin = normalizeObjectVectorOrigin(origin);
@@ -115,6 +120,10 @@ export function transformObjectVectorInstancePoint(point, instance = {}, options
115120
});
116121
}
117122

123+
export function transformObjectVectorInstancePoints(points, instance = {}, options = {}) {
124+
return (Array.isArray(points) ? points : []).map((point) => transformObjectVectorInstancePoint(point, instance, options));
125+
}
126+
118127
export function transformRuntimeOrientedPoint(point, {
119128
rotation = 0,
120129
rotationUnit = "radians",
@@ -137,6 +146,100 @@ export function transformRuntimeOrientedPoints(points, options = {}) {
137146
return (Array.isArray(points) ? points : []).map((point) => transformRuntimeOrientedPoint(point, options));
138147
}
139148

149+
export function objectVectorBoundsCornerPoints(bounds = {}) {
150+
const x = numberValue(bounds.x);
151+
const y = numberValue(bounds.y);
152+
const width = numberValue(bounds.width, 1);
153+
const height = numberValue(bounds.height, 1);
154+
return [
155+
{ x, y },
156+
{ x: x + width, y },
157+
{ x: x + width, y: y + height },
158+
{ x, y: y + height }
159+
];
160+
}
161+
162+
export function boundsFromObjectVectorPoints(points, fallback = DEFAULT_OBJECT_VECTOR_BOUNDS) {
163+
const normalizedPoints = (Array.isArray(points) ? points : [])
164+
.map((point) => ({ x: Number(point?.x), y: Number(point?.y) }))
165+
.filter((point) => Number.isFinite(point.x) && Number.isFinite(point.y));
166+
if (!normalizedPoints.length) {
167+
return { ...fallback };
168+
}
169+
const xValues = normalizedPoints.map((point) => point.x);
170+
const yValues = normalizedPoints.map((point) => point.y);
171+
const minX = Math.min(...xValues);
172+
const minY = Math.min(...yValues);
173+
const maxX = Math.max(...xValues);
174+
const maxY = Math.max(...yValues);
175+
const width = Math.max(1, maxX - minX);
176+
const height = Math.max(1, maxY - minY);
177+
return {
178+
height: Number(height.toFixed(3)),
179+
originX: Number((minX + width / 2).toFixed(3)),
180+
originY: Number((minY + height / 2).toFixed(3)),
181+
width: Number(width.toFixed(3)),
182+
x: Number(minX.toFixed(3)),
183+
y: Number(minY.toFixed(3))
184+
};
185+
}
186+
187+
export function transformedObjectVectorShapeBounds(points, transform = {}, origin = {}) {
188+
return boundsFromObjectVectorPoints(transformObjectVectorShapePoints(points, transform, origin));
189+
}
190+
191+
export function combineObjectVectorBounds(boundsList, fallback = DEFAULT_OBJECT_VECTOR_BOUNDS) {
192+
const points = (Array.isArray(boundsList) ? boundsList : []).flatMap((bounds) => objectVectorBoundsCornerPoints(bounds));
193+
return boundsFromObjectVectorPoints(points, fallback);
194+
}
195+
196+
export function createObjectVectorTransformPipeline({
197+
instance = {},
198+
objectOrigin = {},
199+
screenTransform = null,
200+
shapeTransform = {}
201+
} = {}) {
202+
const origin = normalizeObjectVectorOrigin(objectOrigin);
203+
const transform = normalizeObjectVectorTransform(shapeTransform);
204+
const localPointToShape = (point) => transformObjectVectorShapePoint(point, transform, origin);
205+
const localPointsToShape = (points) => transformObjectVectorShapePoints(points, transform, origin);
206+
const shapePointToWorld = (point) => transformObjectVectorInstancePoint(point, instance);
207+
const shapePointsToWorld = (points) => transformObjectVectorInstancePoints(points, instance);
208+
const worldPointToViewport = (point) => (
209+
typeof screenTransform?.worldPointToViewportPoint === "function"
210+
? screenTransform.worldPointToViewportPoint(point)
211+
: point
212+
);
213+
const localPointToWorld = (point) => shapePointToWorld(localPointToShape(point));
214+
const localPointsToWorld = (points) => shapePointsToWorld(localPointsToShape(points));
215+
const localPointToViewport = (point) => worldPointToViewport(localPointToWorld(point));
216+
const localPointsToViewport = (points) => localPointsToWorld(points).map((point) => worldPointToViewport(point));
217+
const originWorld = () => shapePointToWorld(origin);
218+
const originViewport = () => worldPointToViewport(originWorld());
219+
220+
return Object.freeze({
221+
localPointToShape,
222+
localPointToViewport,
223+
localPointToWorld,
224+
localPointsToShape,
225+
localPointsToViewport,
226+
localPointsToWorld,
227+
origin,
228+
originViewport,
229+
originWorld,
230+
shapeBounds(points) {
231+
return transformedObjectVectorShapeBounds(points, transform, origin);
232+
},
233+
shapeTransform: transform,
234+
viewportBounds(points) {
235+
return boundsFromObjectVectorPoints(localPointsToViewport(points));
236+
},
237+
worldBounds(points) {
238+
return boundsFromObjectVectorPoints(localPointsToWorld(points));
239+
}
240+
});
241+
}
242+
140243
export function objectVectorSvgTransformAttribute(transform = {}, origin = {}) {
141244
const resolvedTransform = normalizeObjectVectorTransform(transform);
142245
const resolvedOrigin = normalizeObjectVectorOrigin(origin);

src/engine/rendering/WorldScreenTransform.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ export function createWorldScreenTransform({
5555
}
5656
context.scale(resolvedWorldScale, resolvedWorldScale);
5757
},
58+
applyViewportTransform(context) {
59+
this.applyUserZoom(context);
60+
this.applyWorldToScreen(context);
61+
},
62+
applyObjectRenderTransform(context, options = {}) {
63+
const renderOptions = this.objectRenderOptions(options);
64+
context.translate(renderOptions.x, renderOptions.y);
65+
context.rotate(renderOptions.rotation);
66+
context.scale(renderOptions.scale, renderOptions.scale);
67+
},
5868
clientPointToScreenPoint(event, rect) {
5969
const rectWidth = positiveScale(rect?.width);
6070
const rectHeight = positiveScale(rect?.height);
@@ -64,18 +74,38 @@ export function createWorldScreenTransform({
6474
};
6575
},
6676
objectRenderOptions(options = {}) {
77+
const screenPoint = this.worldPointToScreenPoint({ x: options.x, y: options.y });
6778
return {
6879
rotation: rotationRadians(options.rotation || 0, options.rotationUnit || "radians"),
6980
scale: (Number.isFinite(options.scale) ? options.scale : 1) * resolvedWorldScale,
70-
x: (options.x || 0) * resolvedWorldScale,
71-
y: (options.y || 0) * resolvedWorldScale
81+
x: screenPoint.x,
82+
y: screenPoint.y
83+
};
84+
},
85+
screenPointToWorldPoint(point) {
86+
return {
87+
x: finiteNumber(point?.x) / resolvedWorldScale,
88+
y: finiteNumber(point?.y) / resolvedWorldScale
7289
};
7390
},
7491
screenPointToWorldWithUserZoom(point) {
7592
return {
7693
x: ((finiteNumber(point?.x) - center.x) / resolvedUserZoom + center.x) / resolvedWorldScale,
7794
y: ((finiteNumber(point?.y) - center.y) / resolvedUserZoom + center.y) / resolvedWorldScale
7895
};
96+
},
97+
worldPointToScreenPoint(point) {
98+
return {
99+
x: finiteNumber(point?.x) * resolvedWorldScale,
100+
y: finiteNumber(point?.y) * resolvedWorldScale
101+
};
102+
},
103+
worldPointToViewportPoint(point) {
104+
const screenPoint = this.worldPointToScreenPoint(point);
105+
return {
106+
x: (screenPoint.x - center.x) * resolvedUserZoom + center.x,
107+
y: (screenPoint.y - center.y) * resolvedUserZoom + center.y
108+
};
79109
}
80110
});
81111
}

src/engine/rendering/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,24 @@ export { renderByLayers } from './LayeredRenderSystem.js';
1212
export { transformPoints, drawVectorShape } from './VectorDrawing.js';
1313
export {
1414
applyObjectVectorCanvasTransform,
15+
boundsFromObjectVectorPoints,
16+
combineObjectVectorBounds,
17+
createObjectVectorTransformPipeline,
1518
headingPointFromRotation,
1619
inverseTransformObjectVectorShapePoint,
1720
normalizeObjectVectorOrigin,
1821
normalizeObjectVectorTransform,
1922
normalizeRotationDegrees,
23+
objectVectorBoundsCornerPoints,
2024
objectVectorSvgTransformAttribute,
2125
rotatePointAround,
2226
rotationDegrees,
2327
rotationRadians,
28+
transformedObjectVectorShapeBounds,
2429
transformObjectVectorInstancePoint,
30+
transformObjectVectorInstancePoints,
2531
transformObjectVectorShapePoint,
32+
transformObjectVectorShapePoints,
2633
transformRuntimeOrientedPoint,
2734
transformRuntimeOrientedPoints
2835
} from './OrientationTransform.js';

0 commit comments

Comments
 (0)