Specialized components
World editor
Gizmos
22 min
overview the gizmos component is a critical element of the ir engine's world editor that provides interactive 3d controls for manipulating objects in the viewport it implements specialized visual handles that enable direct manipulation of entity properties, particularly transformations like position, rotation, and scale by creating intuitive spatial controls that correspond to the underlying component data, this system bridges the gap between visual editing and data manipulation this chapter explores the implementation, interaction, and types of gizmos available in the world editor core concepts gizmo types the editor implements several types of gizmos for different purposes transform gizmo handles for moving, rotating, and scaling entities camera gizmo controls for viewport navigation and camera positioning light gizmo visual indicators and controls for light sources audio gizmo spatial representations of sound sources and ranges collider gizmo visualizations of physics collision boundaries custom gizmos specialized controls for specific component types these gizmos provide intuitive visual interfaces for manipulating different aspects of entities gizmo modes the transform gizmo operates in different modes for specific transformations translate mode arrows and planes for moving entities along axes rotate mode rings for rotating entities around axes scale mode handles for resizing entities along axes universal mode combined controls for multiple transformation types local/world space options for working in different coordinate systems these modes provide specialized controls for different transformation operations gizmo interaction gizmos implement a consistent interaction model selection gizmos appear when compatible entities are selected highlighting visual feedback indicates which gizmo element is active dragging mouse movement translates to component property changes snapping optional grid alignment for precise positioning multi entity editing support for transforming multiple entities simultaneously this interaction model creates an intuitive and responsive editing experience implementation transform gizmo system the transform gizmo system manages the creation and behavior of transformation controls // simplified from src/systems/transformgizmosystem ts import { definesystem, definequery, hascomponent } from '@ir engine/ecs'; import { usehookstate } from '@hookstate/core'; import { entitystate } from ' /services/entityservices'; import { editorstate } from ' /services/editorservices'; import { transformgizmocontrolcomponent } from ' /components/gizmo/transformgizmocontrolcomponent'; import { transformgizmovisualcomponent } from ' /components/gizmo/transformgizmovisualcomponent'; import { creategizmoentity, updategizmotransform } from ' /functions/gizmofunctions'; / system for managing transform gizmos / export const transformgizmosystem = definesystem({ name 'transformgizmosystem', // queries for entities with transform gizmo components queries { gizmocontrols definequery(\[transformgizmocontrolcomponent]), gizmovisuals definequery(\[transformgizmovisualcomponent]) }, // system setup setup () => { // create the gizmo entity const gizmoentity = creategizmoentity(); return { gizmoentity }; }, // system execution execute (system, world) => { const { gizmoentity } = system; const entitystate = usehookstate(entitystate state); const editorstate = usehookstate(editorstate state); // get selected entities const selectedentityids = entitystate selectedentityids value; const selectedentities = selectedentityids map(id => entitystate entities value find(e => e id === id) ) filter(boolean); // check if any selected entity has a transform component const hastransformable = selectedentities some(entity => hascomponent(entity, 'transform') ); // show or hide gizmo based on selection if (selectedentities length > 0 && hastransformable) { // show gizmo if (!hascomponent(gizmoentity, transformgizmocontrolcomponent)) { addcomponent(gizmoentity, transformgizmocontrolcomponent, { controlledentities selectedentities, mode editorstate transformmode value, space editorstate transformspace value, dragging false, axis null }); } // update gizmo transform to match selection updategizmotransform(gizmoentity, selectedentities); // update gizmo mode if it changed const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); if (gizmocontrol mode !== editorstate transformmode value) { updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, mode editorstate transformmode value }); } } else { // hide gizmo if (hascomponent(gizmoentity, transformgizmocontrolcomponent)) { removecomponent(gizmoentity, transformgizmocontrolcomponent); } } // process gizmo interaction const gizmocontrols = system queries gizmocontrols entities; for (const gizmoentity of gizmocontrols) { processgizmointeraction(gizmoentity, world); } } }); this system creates and manages the gizmo entity shows or hides the gizmo based on entity selection updates the gizmo's position and orientation to match selected entities changes the gizmo mode based on editor state processes user interaction with the gizmo gizmo control component the gizmo control component defines the state and behavior of a gizmo // simplified from src/components/gizmo/transformgizmocontrolcomponent ts import { definecomponent } from '@ir engine/ecs'; / component for transform gizmo control / export const transformgizmocontrolcomponent = definecomponent({ name 'transformgizmocontrolcomponent', // component schema schema { // entities controlled by this gizmo controlledentities { type 'array', default \[] }, // current transformation mode mode { type 'string', default 'translate' }, // coordinate space (local or world) space { type 'string', default 'world' }, // is the gizmo currently being dragged dragging { type 'boolean', default false }, // currently active axis or plane axis { type 'string', default null }, // start position for drag operation startposition { type 'vector3', default { x 0, y 0, z 0 } }, // start rotation for drag operation startrotation { type 'vector3', default { x 0, y 0, z 0 } }, // start scale for drag operation startscale { type 'vector3', default { x 1, y 1, z 1 } }, // mouse position at drag start mousestart { type 'vector2', default { x 0, y 0 } }, // current mouse position during drag mousecurrent { type 'vector2', default { x 0, y 0 } }, // snapping settings snap { type 'boolean', default false }, snapvalue { type 'number', default 1 } } }); this component stores references to the entities being controlled tracks the current transformation mode and coordinate space maintains state for the active drag operation records starting values for transformations stores mouse positions for calculating changes gizmo visual component the gizmo visual component manages the 3d representation of the gizmo // simplified from src/components/gizmo/transformgizmovisualcomponent ts import { definecomponent } from '@ir engine/ecs'; import { object3d } from 'three'; / component for transform gizmo visuals / export const transformgizmovisualcomponent = definecomponent({ name 'transformgizmovisualcomponent', // component schema schema { // root object for all gizmo visuals root { type 'object', default null }, // translate mode visuals translatevisuals { type 'object', default null }, // rotate mode visuals rotatevisuals { type 'object', default null }, // scale mode visuals scalevisuals { type 'object', default null }, // currently active visuals activevisuals { type 'object', default null }, // highlight materials for active elements highlightmaterials { type 'object', default {} }, // normal materials for inactive elements normalmaterials { type 'object', default {} }, // visibility state visible { type 'boolean', default true } }, // component initialization init (component) => { // create root object component root = new object3d(); // create visuals for each mode component translatevisuals = createtranslatevisuals(); component rotatevisuals = createrotatevisuals(); component scalevisuals = createscalevisuals(); // add visuals to root component root add(component translatevisuals); component root add(component rotatevisuals); component root add(component scalevisuals); // hide all initially component translatevisuals visible = false; component rotatevisuals visible = false; component scalevisuals visible = false; // create materials setupmaterials(component); return component; } }); this component creates and manages the 3d objects that represent the gizmo maintains separate visual sets for different transformation modes handles visibility of the appropriate visual elements manages materials for normal and highlighted states provides a structure for organizing complex gizmo visuals gizmo interaction the gizmo interaction functions handle user input and apply transformations // simplified from src/functions/gizmofunctions ts import { getcomponent, updatecomponent } from '@ir engine/ecs'; import { vector2, vector3, raycaster } from 'three'; import { transformgizmocontrolcomponent } from ' /components/gizmo/transformgizmocontrolcomponent'; / processes gizmo interaction @param gizmoentity the gizmo entity @param world the ecs world / export const processgizmointeraction = (gizmoentity, world) => { const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); // if not dragging, check for drag start if (!gizmocontrol dragging) { if (world input mousedown && world input mouseovergizmo) { startdrag(gizmoentity, world); } } else { // if dragging, update or end drag if (world input mousedown) { updatedrag(gizmoentity, world); } else { enddrag(gizmoentity, world); } } }; / starts a gizmo drag operation @param gizmoentity the gizmo entity @param world the ecs world / export const startdrag = (gizmoentity, world) => { const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); const raycaster = new raycaster(); // set up raycaster from mouse position const mouseposition = new vector2( (world input mousex / window\ innerwidth) 2 1, (world input mousey / window\ innerheight) 2 + 1 ); raycaster setfromcamera(mouseposition, world camera); // determine which axis was clicked const axis = determineclickedaxis(raycaster, gizmoentity); if (!axis) return; // store starting values for controlled entities const startvalues = gizmocontrol controlledentities map(entity => { const transform = getcomponent(entity, 'transform'); return { entityid entity id, position { transform position }, rotation { transform rotation }, scale { transform scale } }; }); // update gizmo control state updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, dragging true, axis, startvalues, mousestart { x world input mousex, y world input mousey }, mousecurrent { x world input mousex, y world input mousey } }); // highlight the active axis highlightaxis(gizmoentity, axis); }; / updates a gizmo drag operation @param gizmoentity the gizmo entity @param world the ecs world / export const updatedrag = (gizmoentity, world) => { const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); // update current mouse position updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, mousecurrent { x world input mousex, y world input mousey } }); // calculate delta from start position const deltax = world input mousex gizmocontrol mousestart x; const deltay = world input mousey gizmocontrol mousestart y; // apply transformation based on mode switch (gizmocontrol mode) { case 'translate' applytranslation(gizmoentity, deltax, deltay, world); break; case 'rotate' applyrotation(gizmoentity, deltax, deltay, world); break; case 'scale' applyscale(gizmoentity, deltax, deltay, world); break; } }; / ends a gizmo drag operation @param gizmoentity the gizmo entity @param world the ecs world / export const enddrag = (gizmoentity, world) => { const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); // reset gizmo control state updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, dragging false, axis null }); // reset axis highlighting resetaxishighlighting(gizmoentity); }; these functions handle the start, update, and end of drag operations determine which axis or plane the user is interacting with calculate transformation changes based on mouse movement apply changes to the controlled entities' transform components provide visual feedback through axis highlighting camera gizmo the camera gizmo provides controls for viewport navigation // simplified from src/components/gizmo/cameragizmocomponent ts import { definecomponent } from '@ir engine/ecs'; import { object3d, mesh, spheregeometry, meshbasicmaterial } from 'three'; / component for camera gizmo / export const cameragizmocomponent = definecomponent({ name 'cameragizmocomponent', // component schema schema { // root object for camera gizmo root { type 'object', default null }, // individual axis objects xaxis { type 'object', default null }, yaxis { type 'object', default null }, zaxis { type 'object', default null }, // center sphere center { type 'object', default null }, // position in viewport (0 1) viewportposition { type 'vector2', default { x 0 9, y 0 1 } }, // size in pixels size { type 'number', default 80 } }, // component initialization init (component) => { // create root object component root = new object3d(); // create center sphere const spheregeometry = new spheregeometry(0 2, 16, 16); const spherematerial = new meshbasicmaterial({ color 0xffffff }); component center = new mesh(spheregeometry, spherematerial); component root add(component center); // create axis indicators component xaxis = createaxisindicator(0xff0000, new vector3(1, 0, 0)); component yaxis = createaxisindicator(0x00ff00, new vector3(0, 1, 0)); component zaxis = createaxisindicator(0x0000ff, new vector3(0, 0, 1)); component root add(component xaxis); component root add(component yaxis); component root add(component zaxis); return component; } }); this component creates a visual representation of the camera orientation provides clickable elements for standard views (top, front, side) maintains a fixed position in the viewport corner updates to reflect the current camera orientation handles user interaction for quick view changes gizmo workflow the complete gizmo workflow follows this sequence sequencediagram participant user participant viewport participant selectionsystem participant transformgizmosystem participant gizmovisuals participant entitytransform user >>viewport selects entity viewport >>selectionsystem updates selection selectionsystem >>transformgizmosystem notifies of selection change transformgizmosystem >>transformgizmosystem checks for transform component transformgizmosystem >>gizmovisuals creates/updates gizmo gizmovisuals >>viewport displays gizmo around entity user >>viewport clicks gizmo axis viewport >>transformgizmosystem detects axis interaction transformgizmosystem >>gizmovisuals highlights selected axis transformgizmosystem >>transformgizmosystem stores initial state user >>viewport drags mouse viewport >>transformgizmosystem updates mouse position transformgizmosystem >>transformgizmosystem calculates transformation transformgizmosystem >>entitytransform updates transform component entitytransform >>viewport entity moves/rotates/scales user >>viewport releases mouse button viewport >>transformgizmosystem ends drag operation transformgizmosystem >>gizmovisuals resets axis highlighting transformgizmosystem >>transformgizmosystem finalizes transformation this diagram illustrates the gizmo appears when an entity with a transform component is selected the user interacts with a specific part of the gizmo (axis, ring, etc ) as the user drags, the system calculates and applies transformations the entity updates in real time, providing immediate visual feedback the operation completes when the user releases the mouse button integration with other components the gizmo system integrates with several other components of the world editor entity component system gizmos interact directly with the ecs to modify entity components // example of ecs integration import { getcomponent, updatecomponent } from '@ir engine/ecs'; import { transformgizmocontrolcomponent } from ' /components/gizmo/transformgizmocontrolcomponent'; / applies a translation to controlled entities @param gizmoentity the gizmo entity @param deltax mouse x movement @param deltay mouse y movement @param world the ecs world / export const applytranslation = (gizmoentity, deltax, deltay, world) => { const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); const { axis, controlledentities, startvalues, snap, snapvalue } = gizmocontrol; // calculate world space movement based on mouse delta const worlddelta = calculateworlddelta(gizmoentity, deltax, deltay, world); // apply to each controlled entity controlledentities foreach((entity, index) => { const startvalue = startvalues\[index]; const transform = getcomponent(entity, 'transform'); // calculate new position based on axis and delta let newposition = { transform position }; if (axis === 'x' || axis === 'xy' || axis === 'xz') { newposition x = startvalue position x + worlddelta x; if (snap) newposition x = math round(newposition x / snapvalue) snapvalue; } if (axis === 'y' || axis === 'xy' || axis === 'yz') { newposition y = startvalue position y + worlddelta y; if (snap) newposition y = math round(newposition y / snapvalue) snapvalue; } if (axis === 'z' || axis === 'xz' || axis === 'yz') { newposition z = startvalue position z + worlddelta z; if (snap) newposition z = math round(newposition z / snapvalue) snapvalue; } // update the entity's transform component updatecomponent(entity, 'transform', { transform, position newposition }); }); }; this integration retrieves and updates transform components directly applies transformations based on gizmo interaction supports features like snapping for precise positioning handles multiple selected entities simultaneously maintains the connection between visual manipulation and data editor state gizmos respond to changes in editor state // example of editor state integration import { usehookstate } from '@hookstate/core'; import { editorstate } from ' /services/editorservices'; import { transformgizmocontrolcomponent } from ' /components/gizmo/transformgizmocontrolcomponent'; import { transformgizmovisualcomponent } from ' /components/gizmo/transformgizmovisualcomponent'; / updates gizmo based on editor state changes @param gizmoentity the gizmo entity / export const updategizmofromeditorstate = (gizmoentity) => { const editorstate = usehookstate(editorstate state); const gizmocontrol = getcomponent(gizmoentity, transformgizmocontrolcomponent); const gizmovisual = getcomponent(gizmoentity, transformgizmovisualcomponent); // update transform mode if (gizmocontrol mode !== editorstate transformmode value) { updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, mode editorstate transformmode value }); // update visuals for new mode updategizmovisuals(gizmoentity, editorstate transformmode value); } // update coordinate space if (gizmocontrol space !== editorstate transformspace value) { updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, space editorstate transformspace value }); // update gizmo orientation updategizmoorientation(gizmoentity, editorstate transformspace value); } // update snap settings if (gizmocontrol snap !== editorstate snapenabled value || gizmocontrol snapvalue !== editorstate snapvalue value) { updatecomponent(gizmoentity, transformgizmocontrolcomponent, { gizmocontrol, snap editorstate snapenabled value, snapvalue editorstate snapvalue value }); } }; this integration monitors editor state for changes to transformation settings updates gizmo configuration to match current editor mode switches between different visual representations adjusts coordinate systems based on local/world space setting applies snapping settings for precise transformations viewport system gizmos integrate with the viewport for rendering and interaction // example of viewport integration import { object3d, raycaster, vector2 } from 'three'; import { cameragizmocomponent } from ' /components/gizmo/cameragizmocomponent'; / updates camera gizmo position in viewport @param gizmoentity the camera gizmo entity @param viewport the viewport information / export const updatecameragizmoviewport = (gizmoentity, viewport) => { const gizmo = getcomponent(gizmoentity, cameragizmocomponent); // calculate screen position const x = viewport width gizmo viewportposition x; const y = viewport height gizmo viewportposition y; // position gizmo in corner of viewport positionobjectinviewport(gizmo root, x, y, gizmo size, viewport camera); }; / checks if mouse is over a gizmo @param mousex mouse x position @param mousey mouse y position @param gizmoentities array of gizmo entities @param viewport the viewport information @returns object with hit information or null / export const checkgizmointersection = (mousex, mousey, gizmoentities, viewport) => { // create raycaster from mouse position const raycaster = new raycaster(); const mouseposition = new vector2( (mousex / viewport width) 2 1, (mousey / viewport height) 2 + 1 ); raycaster setfromcamera(mouseposition, viewport camera); // collect all gizmo objects const gizmoobjects = \[]; gizmoentities foreach(entity => { // get visual component const visual = getcomponent(entity, transformgizmovisualcomponent) || getcomponent(entity, cameragizmocomponent); if (visual && visual root) { gizmoobjects push(visual root); } }); // check for intersections const intersects = raycaster intersectobjects(gizmoobjects, true); return intersects length > 0 ? intersects\[0] null; }; this integration positions gizmos correctly in the viewport handles mouse interaction through raycasting maintains proper scale regardless of camera distance ensures gizmos are visible and usable in different viewport configurations provides hit testing for determining which gizmo element is clicked benefits of gizmos the gizmos component provides several key advantages intuitive editing provides direct manipulation of objects in 3d space visual feedback shows transformation axes and planes clearly precision enables accurate positioning, rotation, and scaling efficiency speeds up common transformation operations consistency maintains familiar controls across different object types flexibility supports different transformation modes and coordinate spaces discoverability makes spatial editing capabilities visually apparent these benefits create a more intuitive and efficient editing experience for 3d world creation next steps with an understanding of gizmos and how they enable direct manipulation of entities, the next chapter explores how the visual scripting system provides a node based approach to defining entity behavior next visual scripting system docid\ gbz5litd5invam5xhovie