Paste where the view shows it, editor offsets included (#263)
Paste mixed two frames. The pointer comes from the Scene View, which draws every object moved by the editor offsets of it and its parents. Paste turned that point into a local position through the parents' plain world transform, so the copy landed beside the pointer by the offset of the parent it went into. Without a pointer, an original with an offset of its own had its copy placed beside its game position, which the view does not show. - A copied root now remembers where the view showed it (`ClipRoot.shown`, from `editorTransform`) instead of its world position. - New `editorLocalPositionFor` in `properties/transform.ts` turns a shown point into a local position through the parent chain with the editor offsets, as the view draws it. Paste uses it. - The copy itself still gets no editor offset of its own, as since #240. In the game it stands where it was drawn, minus the offsets of its parents. - Re-parenting under a parent with an offset still keeps the game position, so the object jumps in the view by that offset. That is a different rule from paste and is left as it is. Tests: - Unit (clipboard, groups, references, transform): 50 passed, 2 new. - e2e 51 on melektaus-atx: ALL PASSED, 12 checks, 1 new (51f). - Gate on melektaus-atx: 195 files, 2829 tests, CHECK EXIT 0. - Counter-checks: without the paste fix both new unit tests fail (copy at 540/470 instead of 500/500) and 51f fails by exactly the 60/-40 offset. With the root remembering its world position again, the own-offset unit test fails. - No full run. The e2e total of 1591 is 1590 plus 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9cff5282b6
commit
6d4247ebf8
7 files changed
+100
-23
No files matched your search
@@ -99,10 +99,19 @@ export function worldPosition(project: Project, tree: ObjectTree, object: SceneO
|
||||
return positionOf(worldTransform(project, tree, object));
|
||||
}
|
||||
|
||||
function parentChain(project: Project, tree: ObjectTree, parentId: string, withEditorOffset: boolean) {
|
||||
const parent = parentId ? findObject(tree, parentId) : undefined;
|
||||
return parent ? chain(project, tree, parent, withEditorOffset) : { m: IDENTITY, z: 0 };
|
||||
}
|
||||
|
||||
/** The world transform of a parent ('' = top level, the identity). */
|
||||
export function parentTransform(project: Project, tree: ObjectTree, parentId: string): ChainTransform {
|
||||
const parent = parentId ? findObject(tree, parentId) : undefined;
|
||||
return parent ? worldTransform(project, tree, parent) : { m: IDENTITY, z: 0 };
|
||||
return parentChain(project, tree, parentId, false);
|
||||
}
|
||||
|
||||
function localIn(parent: ChainTransform, at: Position): Position {
|
||||
const local = apply(invert(parent.m), at);
|
||||
return { x: local.x, y: local.y, z: at.z - parent.z };
|
||||
}
|
||||
|
||||
/** The local position that puts an object at `world` under the given parent ('' = top level). */
|
||||
@@ -112,9 +121,20 @@ export function localPositionFor(
|
||||
parentId: string,
|
||||
world: Position,
|
||||
): Position {
|
||||
const parent = parentTransform(project, tree, parentId);
|
||||
const local = apply(invert(parent.m), world);
|
||||
return { x: local.x, y: local.y, z: world.z - parent.z };
|
||||
return localIn(parentTransform(project, tree, parentId), world);
|
||||
}
|
||||
|
||||
/**
|
||||
* The local position that shows an object without an editor offset of its own at `shown` in the
|
||||
* editor: the offsets of the parents above it count, as they do on screen (#263).
|
||||
*/
|
||||
export function editorLocalPositionFor(
|
||||
project: Project,
|
||||
tree: ObjectTree,
|
||||
parentId: string,
|
||||
shown: Position,
|
||||
): Position {
|
||||
return localIn(parentChain(project, tree, parentId, true), shown);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user