import { Object3D } from "three"; import { HideFlags } from "../engine_types.js"; export function shouldExport_HideFlags(obj: Object3D) { const dontExport = HideFlags.DontExport; if (obj.hideFlags & dontExport) return false; return true; } export function detachObjectsExcludedFromExport(root: Object3D): Array<() => void> { return detachObjectsExcludedFromExportRecursive(root); } function detachObjectsExcludedFromExportRecursive(parent: Object3D, undo: Array<() => void> = []): Array<() => void> { // `index` is the child's original position in the parent's children array, // captured before any detaching happened. restoreDetachedObject uses it to // splice the object back into its original slot so the order is preserved. // We detach immediately, but do NOT advance `index` on removal: after // detaching the array has shifted down by one, so the next child now sits // at the same array position `i` while its original index is `index`. // Tracking both keeps restore order-correct. let index = 0; for (let i = 0; i < parent.children.length; index++) { const child = parent.children[i]; if (!shouldExport_HideFlags(child)) { // Detach without going through removeFromParent()/remove(): those // dispatch "removed"/"childremoved" events. This is a temporary, // export-only detach that is undone right after export, so firing // scene-graph events (and triggering listeners) would be wrong. parent.children.splice(i, 1); child.parent = null; const originalIndex = index; undo.push(() => restoreDetachedObject(child, parent, originalIndex)); // If this object is excluded, its whole subtree is excluded too. // Do not advance `i`: the array shrank, so the next child is now here. continue; } detachObjectsExcludedFromExportRecursive(child, undo); i++; } return undo; } /** * Re-inserts an object detached during export back into its original slot. * * Like the detach side, this deliberately manipulates parent/children directly * instead of calling parent.add(): add() dispatches "added"/"childadded" events. * Restoring the scene after export must be transparent — no events, no listener * side effects. * * The target `index` is clamped to the current children length. If restore ever * runs out of order and the slot no longer exists, we still re-attach the object * (at the end) rather than throw: this runs during export, and leaving the object * detached would be worse than placing it at a slightly wrong index. */ function restoreDetachedObject(object: Object3D, parent: Object3D, index: number) { object.parent = parent; parent.children.splice(Math.min(index, parent.children.length), 0, object); }