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
  • Snappable

Snappable

A THREE.Group subclass that wraps a GLTF block node. It separates sockets from base geometry, handles rotation via a _content sub-group, and maintains a lazy AABB cache for collision detection.

Warning

Meant to be used with ComponentMgr only.

Getting started

const snappable = new Snappable({ object3d })

// add to scene directly
scene.add(snappable)

// or add to component manager
componentMgr.add(snappable)
  1. Expands any socketGrid children into regular socket groups
  2. Separates socket groups from geometry children
  3. Creates Socket instances for each socket group
  4. Wraps all content in a _content sub-group for isolated rotation
  5. Extracts blockId from userData.id

Internal Sub-Group

When ComponentMgr calls snapItemToSocket():

  1. Snappable is reset to origin (position=(0,0,0), identity quaternion)
  2. _content.quaternion is reset to identity
  3. The snap matrix is applied to Snappable
  4. Rotation offset is applied to _content around the computed local axis

Why a sub-group?

The snap matrix calculation in snapItemToSocket needs to know the world position of a socket before any rotation is applied.

It resets the Snappable to the world origin first, then computes the matrix.

If rotation were stored on the Snappable itself, resetting the outer transform would also destroy the rotation, making it impossible to apply both in the correct order. Separating the two concerns.

Snappable holds position/orientation from snapping, _content holds rotation — keeps each layer independent and lets snapItemToSocket always start from a clean slate.

Think of it like a spinning office chair. The wheels stay exactly where they are on the floor — that's the Snappable, locked to its snapped position. The seat spins freely on top — that's _content. Rotating the seat does not drag the whole chair across the room.

Rotation System

Rotation is applied to the _content sub-group (a THREE.Group child of the Snappable) rather than to the Snappable itself.

Axis selection

Block stateRotation axis
Root block (no parent)World Y axis (0, 1, 0) — spinning a free-standing block on its vertical axis
Child block (snapped to parent)Socket forward axis — the world normal of the target socket, converted to the Snappable's local space

For child blocks, the target socket's world normal is converted to local space because _content.quaternion is set in the Snappable's local coordinate system.

Applying a world-space axis to a local quaternion would produce the wrong rotation once the Snappable has a non-identity orientation.

The conversion (worldAxis → snappable.quaternion.invert() → localAxis) ensures the axis is expressed in the same space as the quaternion being set.

The axis is recalculated each time rotationOffset is set, always from the identity orientation (_content reset to (0,0,0,1) before applying) to avoid accumulated floating-point drift across repeated rotations.

API

Constructor(object3d)

Constructor initialises the new Snappable instance

Parameters
  • object3d - The GLTF block root node (must have userData.type = "block" and userData.id)

Methods

getWorldAABB() → THREE.Box3

Returns the world-space axis-aligned bounding box of this block, excluding socket meshes. The result is cached and recomputed lazily when _isAABBDirty is true.

invalidateAABB()

Marks the AABB cache as dirty. Called automatically by ComponentMgr.applyRotationOffset() after rotation. Call this yourself if you move the block outside of ComponentMgr.

tick()

Calls socket.tick() on all sockets (animates socket material colours). Called automatically by ComponentMgr.tick().

zFightFix(cameraPosition)

Nudges socket meshes along their normals by a camera-distance-dependent offset to prevent z-fighting. Called automatically by ComponentMgr.tick() after snapping.

Parameters
  • cameraPosition - The position of the camera

clone(recursive?) → Snappable

Creates a new Snappable from the original block node. Copies rotationOffset and applies the current world matrix to the clone.

Parameters
  • recursive - Whether to clone the object recursively

dispose()

Disposes all socket instances and clears the content sub-group. Does not remove the object from the scene — call scene.remove(snappable) or componentMgr.remove(snappable) first.

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