BBC's guide to development
  • General

    • About
    • Tools
    • Git(hub)
    • Showpad
    • Hosting
    • Maintenance
    • Security
    • Go live checklist
  • Front-end development

    • Bundlers
    • CSS/SCSS
    • Javascript
    • Vue
    • PHP
    • Mails
    • Dev Faq
  • Functions
  • Mixins
  • General

    • OOP Structure
  • Component Classes

    • Accordion
    • App
    • Component
    • HighwayApp
    • Popup
    • PNG Sequencer
    • Tab
  • Manager Classes

    • BountListenerMgr
    • Cache
    • Configuration
    • InViewStateMgr
    • Instance Manager
    • Event dispatcher
  • Factories

    • SwiperFactory
  • PDF

    • AssetLoader
    • BasePdfDoc
    • TemplatePdfDoc
    • CustomPdfDoc
  • Utility functions

    • canvas
    • Connection Status
    • css
    • dev
    • placeholder
    • dom
    • fetch
    • json
    • object
    • scroll
    • scrollbar
    • spreadsheets
    • string
    • url
  • General

    • ComponentMgr
    • ThreeJsViewer
  • Components

    • ComponentMgr
    • GltfModel
    • Snappable
    • Socket
    • ThreeJsViewer
    • ThreeJsViewerCamera
  • Loaders

    • ConfigurationSerializer
    • GltfBlockParser
  • Utils

    • CanvasInputAdapter
    • CollisionManager
    • SocketGridExpander
    • blender
    • headless
  • General

    • Troubleshooting
    • Legacy
  • Components

    • AssetBar
    • ConfigGenerator
    • ShowpadApp
  • Managers

    • Assets
    • AppsDb
    • Config
  • Utils

    • Connection Status
    • general
    • showpad-interactive
    • showpad-upload
  • Components

    • Accordion
    • BackButton
    • Breadcrumb
    • ByltButton
    • Hamburger
    • Icon
    • Logo
    • Loader
    • Modal
    • Popup
    • Prompt
    • ProgressBar
    • TextLoader
  • Composables

    • useDebugMode
    • useConnectionStatus
  • Utils

    • dom
    • props
  • General

    • General
    • Tracking
  • Components

    • Accordion
    • ActionButton
    • AssetItem
    • AssetList
    • BackButton
    • ConfigGenButton
    • Logo
    • Media
    • Modal
    • Popup
    • Prompt
    • SPButton
    • SPRouterView
    • SPTrackedRouterLink
    • TextLoader
    • View
  • Composables

    • useConnectionStatus
  • Stores

    • useAppsDbStore
    • useBreadcrumbStore
    • useShowpadAPIStore
    • useShowpadSDKStore
    • useSpConfigStore
    • useSpStore
    • useSpTrackingStore
  • The New Kit

    • General
    • Installation & Usage
    • ACF Blocks
    • PHPCS
    • Functions
    • Vite
    • WP Config
    • Staging Deployment
  • Best Practices

    • Page Structure
    • Fonts/Typography
  • Todo
GitHub
  • General

    • About
    • Tools
    • Git(hub)
    • Showpad
    • Hosting
    • Maintenance
    • Security
    • Go live checklist
  • Front-end development

    • Bundlers
    • CSS/SCSS
    • Javascript
    • Vue
    • PHP
    • Mails
    • Dev Faq
  • Functions
  • Mixins
  • General

    • OOP Structure
  • Component Classes

    • Accordion
    • App
    • Component
    • HighwayApp
    • Popup
    • PNG Sequencer
    • Tab
  • Manager Classes

    • BountListenerMgr
    • Cache
    • Configuration
    • InViewStateMgr
    • Instance Manager
    • Event dispatcher
  • Factories

    • SwiperFactory
  • PDF

    • AssetLoader
    • BasePdfDoc
    • TemplatePdfDoc
    • CustomPdfDoc
  • Utility functions

    • canvas
    • Connection Status
    • css
    • dev
    • placeholder
    • dom
    • fetch
    • json
    • object
    • scroll
    • scrollbar
    • spreadsheets
    • string
    • url
  • General

    • ComponentMgr
    • ThreeJsViewer
  • Components

    • ComponentMgr
    • GltfModel
    • Snappable
    • Socket
    • ThreeJsViewer
    • ThreeJsViewerCamera
  • Loaders

    • ConfigurationSerializer
    • GltfBlockParser
  • Utils

    • CanvasInputAdapter
    • CollisionManager
    • SocketGridExpander
    • blender
    • headless
  • General

    • Troubleshooting
    • Legacy
  • Components

    • AssetBar
    • ConfigGenerator
    • ShowpadApp
  • Managers

    • Assets
    • AppsDb
    • Config
  • Utils

    • Connection Status
    • general
    • showpad-interactive
    • showpad-upload
  • Components

    • Accordion
    • BackButton
    • Breadcrumb
    • ByltButton
    • Hamburger
    • Icon
    • Logo
    • Loader
    • Modal
    • Popup
    • Prompt
    • ProgressBar
    • TextLoader
  • Composables

    • useDebugMode
    • useConnectionStatus
  • Utils

    • dom
    • props
  • General

    • General
    • Tracking
  • Components

    • Accordion
    • ActionButton
    • AssetItem
    • AssetList
    • BackButton
    • ConfigGenButton
    • Logo
    • Media
    • Modal
    • Popup
    • Prompt
    • SPButton
    • SPRouterView
    • SPTrackedRouterLink
    • TextLoader
    • View
  • Composables

    • useConnectionStatus
  • Stores

    • useAppsDbStore
    • useBreadcrumbStore
    • useShowpadAPIStore
    • useShowpadSDKStore
    • useSpConfigStore
    • useSpStore
    • useSpTrackingStore
  • The New Kit

    • General
    • Installation & Usage
    • ACF Blocks
    • PHPCS
    • Functions
    • Vite
    • WP Config
    • Staging Deployment
  • Best Practices

    • Page Structure
    • Fonts/Typography
  • Todo
GitHub
  • ComponentMgr

ComponentMgr

Orchestrates block placement, snapping, selection, rotation, pointer interaction, and collision in a Three.js scene. Extends THREE.EventDispatcher.

Getting started

import { ComponentMgr } from './configurator';

const componentMgr = new ComponentMgr({ 
    camera,
    scene 
});

Custom Ghost Processor

The built-in processor clones the Snappable and applies opacity = 0.5 to all of its materials.

If you need different ghost visuals — a wireframe outline, a custom colour tint, or a simplified mesh — provide your own function:

const componentMgr = new ComponentMgr({
  camera, scene,
  ghostProcessor: (snappable) => {
    const ghost = snappable.clone(true);
    // apply your own material changes to ghost here
    return ghost;
  }
});

The function receives the original Snappable and must return a new Snappable instance.

The returned object is added to the scene and repositioned each frame by tick().

It is removed when clearCurrent() is called or a block is placed.

Warning

Do not add the ghost to the scene yourself — ComponentMgr manages its lifecycle.

API

constructor({ camera, scene, isGhostEnabled, ghostProcessor })

The constructor method initialises the new ComponentMgr instance

Parameters

  • camera: Camera used for raycasting
  • scene: Scene where blocks are added
  • isGhostEnabled: Whether to show a ghost preview when hovering sockets
  • ghostProcessor: Custom function to create the ghost clone: (snappable) => Snappable

Properties / Getters

objects

All placed blocks

sockets

All sockets across all placed blocks

currentItem

Block currently being previewed

selectedBlock

Currently selected placed block

childrenMap

Parent-to-children mapping

isGhostEnabled

get/set — enables or disables ghost preview

isCategoryFilteringDisabled

get/set — when true, all sockets are treated as wildcards regardless of their category strings.

Useful during development to test block geometry and snapping positions without worrying about compatibility rules, or for a "free build" mode where the user can connect any piece to any other. Does not affect already-placed and locked sockets; it only changes which target sockets are highlighted as compatible on the next tick().

isSocketVisible

get — current socket visibility state

isSocketUpVisible

get — current up indicator visibility state

isDraggingBlock

true while a block drag is in progress

dragBlock

The block currently being dragged, or null

Methods

add(snappable)

Places a Snappable into the scene. Registers its sockets, applies current debug/visibility settings, and dispatches block-placed.

Parameters
  • snappable: The block to place
Dispatches
  • block-placed

remove(snappable, isRemoveChildren?)

Removes a Snappable from the scene. Unlocks all its sockets and cleans up all internal references.

Parameters
  • snappable: The block to remove
  • isRemoveChildren: Whether to remove all child blocks recursively (default: true)
Dispatches
  • block-removed, and block-deselected if the removed block was selected

setCurrent(snappable)

Sets the block being previewed/dragged. Creates a ghost clone and shows it over hovered sockets. If the scene is empty when called, the block is placed immediately at the origin.

Parameters
  • snappable: Activate block in the scene
Note
  • Resets selectedSocketIndex to 0

clearCurrent()

Clears the current preview block and removes the ghost from the scene.

selectBlock(snappable)

Selects a placed block (must already be in componentMgr.objects).

Parameters
  • snappable: Activate placed block in the scene
Dispatches
  • block-selected

clearSelection()

Deselects the currently selected block.

Dispatches
  • block-deselected

removeSelectedBlock()

Calls remove() on the currently selected block. No-op if nothing is selected.

setSelectedSocketIndex(index)

Sets which socket on the current item is used as the snap point. Index must be within [0, sockets.length - 1].

Parameters
  • index: Socket index (default: 0)

setSocketVisibility(isVisible)

Shows or hides all socket meshes across all placed blocks.

Parameters
  • isVisible: Visibility state (default: true)

setSocketUpVisibility(isVisible)

Shows or hides all up indicator objects across all sockets.

Parameters
  • isVisible: Visibility state (default: true)

setSocketColors(colors)

Overrides the socket state colours used by all Socket instances. Calls Socket.updateColors(colors).

Parameters
  • colors: Object
    • default: Initial state (default: #ffffff)
    • hovered: Hovered state (default: #0000ff)
    • clicked: Clicked state (default: #00ff00)
    • compatible: Compatible state (default: #ffff00)
    • locked: Locked state (default: #ff0000)
    • blocked: Blocked state (default: #333333)

rotateCurrentItem(deltaSteps) → {success, steps, degrees, radians}

Adds deltaSteps × 45° to the current item's rotation.

Parameters
  • deltaSteps: Number of 45° steps to add to the current item's rotation
Returns
  • Object: {success: boolean, steps: number, degrees: number, radians: number} or {success: false, reason: 'no-current-item'}

setCurrentItemRotationSteps(steps) → {success, steps, degrees, radians}

Sets the current item's rotation to an absolute step count.

Parameters
  • steps — number
Returns
  • Object: {success: boolean, steps: number, degrees: number, radians: number} or {success: false, reason: 'no-current-item'}

setCurrentItemRotationRadians(radians) → {success, steps, degrees, radians}

Sets the current item's rotation to an absolute radian value.

Parameters
  • radians: Rotation angle in radians
Returns
  • Object: {success: boolean, steps: number, degrees: number, radians: number} or {success: false, reason: 'no-current-item'}

getCurrentItemRotationSteps() → number

Returns the current item's rotation rounded to the nearest 45° step. Returns 0 if no current item.

Returns
  • number: The current item's rotation in steps

getCurrentItemRotationRadians() → number

Returns the current item's rotation in radians. Returns 0 if no current item.

Returns
  • number: The current item's rotation in radians

rotateSelectedBlock(deltaSteps) → {success, steps, degrees, radians, blockId}

Adds deltaSteps × 45° to the selected block's rotation and re-snaps all descendants.

Parameters
  • deltaSteps: Number of 45° steps to add to the selected block's rotation
Returns
  • Object: {success: boolean, steps: number, degrees: number, radians: number, blockId: string} or {success: false, reason: 'no-selected-block'}

setSelectedBlockRotationRadians(radians) → {success, steps, degrees, radians, blockId}

Sets the selected block's rotation to an absolute radian value and re-snaps descendants.

Parameters
  • radians: Rotation angle in radians
Returns
  • Object: {success: boolean, steps: number, degrees: number, radians: number, blockId: string} or {success: false, reason: 'no-selected-block'}

getSelectedBlockRotationSteps() → number

Returns the selected block's rotation rounded to the nearest 45° step. Returns 0 if no block is selected.

Returns
  • number: The selected block's rotation in steps

getSelectedBlockRotationRadians() → number

Returns the selected block's rotation in radians. Returns 0 if no block is selected.

Returns
  • number: The selected block's rotation in radians

applyRotationOffset(snappable, radians)

Single source of truth for rotating a Snappable and updating all its descendants. Sets rotationOffset on the block, then re-snaps every descendant to its parent socket.

Parameters
  • snappable: The block to rotate
  • radians: Absolute rotation offset in radians

canRotateSelectedBlock() → boolean

Returns true if the selected block has a parent socket anchor (i.e. it was snapped to another block and can be rotated around the snap axis). Root blocks placed at the origin return false.

Returns
  • boolean

updatePointer(ndcX, ndcY)

Updates the raycaster position. Call on every pointermove (or mousemove).

Parameters
  • ndcX: Normalized device coordinates (0-1)
  • ndcY: Normalized device coordinates (0-1)

onPointerDown(ndcX, ndcY, screenX, screenY) → boolean

Records pointer-down state.

Call on pointerdown (or mousedown).

If the raycast hits the currently selected block, starts a block drag and returns true, otherwise returns false. When true, the app can disable camera controls and show a drag ghost (see Block drag).

onPointerUp(ndcX, ndcY, screenX, screenY) → boolean

Processes pointer up.

If a block drag was in progress, it ends first (block is moved to the hovered valid socket or snapped back to its original socket; root blocks restore world position) and returns true — selection logic is skipped.

Otherwise, if no socket was clicked this frame, checks if a block mesh was clicked and dispatches block-selected or block-deselected. Ignores drags > 5 px threshold. Returns false when no drag was ended.

The 5 px drag threshold is measured in screen pixels between the pointer down and pointer up positions.

Its purpose is to distinguish a deliberate click from the end of a camera orbit drag.

If the user panned the camera and released over a block, the selection should not fire. Any movement larger than 5 px is treated as a drag and the click is suppressed. This value is fixed and not currently configurable. Block drag does not use this threshold

Drag starts on pointer down when the raycast hits the selected block.

createGhostForBlock(block) → Snappable

Returns a ghost clone of the given Snappable using the same ghostProcessor as the placement preview. The app can add it to the scene, sync its transform from dragBlock each frame, and remove it on block-drag-end.

Parameters
  • block: The block to create a ghost for
Returns
  • Snappable: The ghost clone with transparent materials

tick()

Must be called once per frame inside your render loop. Drives:

  • Snappable.tick() on all placed blocks (socket colour animations)
  • Raycasting against sockets and blocks
  • Socket state updates (hovered, clicked, compatible, blocked)
  • Ghost preview positioning
  • Block drag position update (when a placed block is being dragged: raycast excluding the dragged block, snap to compatible socket or plane-follow)
  • Collision detection for hovered sockets

Think of it like a gym membership: it only does anything if you actually show up every frame. Forgetting to call it doesn't throw an error; the configurator just quietly sits on the couch while your render loop runs laps around it, wondering why nothing is responding.

zFightFix(cameraPosition)

Propagates a z-fight mitigation fix to the ghost and all placed blocks, passing down the camera position for depth-offset updates.

Parameters
  • cameraPosition: THREE.Vector3 representing the active camera's position

enableDebugMode()

Replaces all block materials with a shared grey debug material. Enables arrow helpers on all sockets showing normals and up directions.

disableDebugMode()

Restores original materials and removes socket arrow helpers.

setDebugOpacity(opacity)

Controls the opacity of all socket materials in debug mode. Range: [0, 1].

Parameters
  • opacity: Sets the opacity of all socket materials in debug mode. Range: [0, 1]

setCollisionDebug(isEnabled)

Enables BVH visualisation on all placed blocks via CollisionManager.visualizeCollisions().

Parameters
  • isEnabled: Enables/disables BVH visualisation on all placed blocks

setCollisionDebugParams(options)

Passes options to CollisionManager.setDebugOptions(). See CollisionManager for option keys.

Parameters
  • options: object
    • showAllCollisionAreas (boolean): Show all overlap boxes, not just the first
    • maxCollisionAreas (number): Maximum number of overlap areas shown
    • refreshEveryNFrames (number): How often (in frames) to refresh the area list

setCollisionVisualDebug(isEnabled)

Enables/disables the in-scene visual overlay (red sphere + ring at the collision hotspot, coloured overlays on colliding meshes).

Parameters
  • isEnabled: Enables/disables the in-scene visual overlay

setCollisionVisualDebugParams(options)

Configures the visual debug overlay:

Parameters
  • options: object
    • showAllCollisionAreas (boolean): Show all overlap boxes, not just the first
    • maxCollisionAreas (number): Maximum number of overlap areas shown
    • refreshEveryNFrames (number): How often (in frames) to refresh the area list

dispose()

Removes the ghost, clears all collision visualisations, disposes debug materials, and nulls internal references. Call when tearing down the scene.

Events

block-placed

After a block is added to the scene (after add())

block-removed

After a block is removed from the scene (after remove())

block-selected

After a block is selected (after selectBlock())

block-deselected

After a block is deselected (after clearSelection() or when selected block is removed)

block-drag-start

When a block drag starts (after onPointerDown())

block-drag-end

When a block drag ends (after onPointerUp())

Exported Utility

calculateSnapMatrix(targetSocket, itemSocket, rotationOffsetRadians?) → THREE.Matrix4

Computes the world-space transformation matrix that aligns itemSocket face-to-face with targetSocket, with an optional rotation offset around the snap axis.

Parameters
  • targetSocket — Socket — the stationary socket to snap to
  • itemSocket — Socket — the socket on the block being moved
  • rotationOffsetRadians — number, default 0
Returns
  • THREE.Matrix4

Import: import { calculateSnapMatrix } from './configurator'

snapItemToSocket(item, targetSocket, itemSocket, rotationOffsetRadians?)

Snaps an item to a target socket, handling reset-to-base and rotation offset internally. This ensures all rotations are calculated from a consistent base state (origin/identity).

Parameters
  • item — THREE.Object3D — the object to snap (will be modified)
  • targetSocket — Socket — the socket to snap to (on the stationary object)
  • itemSocket — Socket — the socket being moved (on the object to transform)
  • rotationOffsetRadians — number, default 0

Import: import { snapItemToSocket } from './configurator'

Edit this page
Last Updated: 4/27/26, 12:56 PM
Contributors: Nicolas Jaenen