transform inheritance
group 안의 child는 parent transform의 영향을 받습니다. parent가 이동하면 child도 같이 이동합니다. parent가 회전하면 child의 world 위치도 회전합니다.
이것을 transform inheritance라고 볼 수 있습니다.
local transform과 world transform
node가 저장하는 transform은 보통 parent 기준 local transform입니다.
child.localMatrix
화면에 그릴 때 필요한 것은 world transform입니다.
child.worldMatrix = parent.worldMatrix * child.localMatrix
root node라면 parent world는 identity입니다.
traversal 중에 world matrix를 계산한다
scene graph를 순회하면서 parent world matrix를 child에게 넘깁니다.
function buildWorldMatrices(document) {
const worldById = new Map();
function visit(nodeId, parentWorld) {
const node = document.nodesById.get(nodeId);
const world = multiply3(parentWorld, node.localMatrix);
worldById.set(nodeId, world);
for (const childId of node.children) {
visit(childId, world);
}
}
for (const rootId of document.rootIds) {
visit(rootId, identity3());
}
return worldById;
}
이 계산은 렌더링뿐 아니라 hit testing, selection bounds, reparenting에서도 쓰입니다.
renderer snapshot에 world matrix를 붙인다
문서에는 local matrix만 저장하고, renderer로 넘길 snapshot에서 계산 결과를 붙이면 core와 renderer의 책임이 깔끔해집니다.
function createRendererSnapshot(editorState) {
const worldById = buildWorldMatrices(editorState.document);
const drawList = buildDrawList(editorState.document).map((node) => ({
...node,
worldMatrix: worldById.get(node.id)
}));
return {
scene: editorState.document,
camera: editorState.camera,
selection: editorState.selection,
tool: editorState.tool,
drawList
};
}
world 값을 저장할지 계산할지 정한다
world matrix를 매번 계산할 수도 있고 cache할 수도 있습니다. 하지만 source of truth는 local transform이어야 합니다.
local transform: 문서 상태
world transform: 계산 결과 또는 cache
cache는 invalidation이 필요합니다. parent가 바뀌면 모든 descendant world cache가 달라질 수 있습니다.
오늘의 핵심
transform inheritance는 parent/child matrix 곱입니다.
root world = identity * root local
child world = parent world * child local
이 공식을 지키면 group, frame, nested editing이 같은 모델로 설명됩니다.