SocketGridExpander
The SocketGridExpander module provides functionality to expand a socket grid definition into multiple individually placed socket instances. It takes a single socket grid group containing a base socket and a plane definition, then generates an array of cloned sockets positioned in a regular grid pattern across the plane.
This is useful for creating repetitive socket layouts (like power outlet grids on walls, connector arrays on equipment, etc.) from a compact parametric definition.
Note
This is a quick first pass at rendering multiple sockets in a grid layout with minimal overhead for both the 3D artist and the developer.
A more robust solution would use a dedicated SocketGrid class with a plane-based geometry and grid interactions rendered via ShaderMaterial—this shader-based approach would offer significantly better performance.
However, we've deferred that implementation due to time constraints and added complexity.
Warning
Meant to be used with ComponentMgr only.
Getting started
import { expandSocketGridGroup } from './SocketGridExpander.js';
// Create a grid group with proper structure
const gridGroup = new THREE.Group();
gridGroup.userData = {
type: 'socketGrid',
rows: 4,
columns: 6
};
gridGroup.name = 'PowerOutletWall';
// Add plane child (defines distribution area)
const planeGeometry = new THREE.PlaneGeometry(2, 1.5); // 2m wide, 1.5m tall
const planeMesh = new THREE.Mesh(planeGeometry, material);
planeMesh.userData = { type: 'socketPlane' };
gridGroup.add(planeMesh);
// Add socket template child
const socketGroup = createSocketGeometry(); // Your socket creation logic
socketGroup.userData = { type: 'socket', voltage: 120 };
gridGroup.add(socketGroup);
// Expand the grid
const { generatedSockets, warnings } = expandSocketGridGroup(gridGroup);
if (warnings.length > 0) {
console.warn('Grid expansion warnings:', warnings);
}
// Add all sockets to scene
generatedSockets.forEach(socket => {
scene.add(socket);
});
Core Function
expandSocketGridGroup(gridGroup)
Purpose: Expands a socket grid group into individual socket clones positioned in a grid pattern.
Parameters:
gridGroup(THREE.Group): The grid group object containing socket grid definition data
Returns:
{
generatedSockets: [], // Array of cloned and positioned socket groups
warnings: [] // Array of validation warning messages
}
Behavior:
- Validates the input grid group structure
- Extracts geometry and transformation information from child nodes
- Calculates grid positions based on the plane bounds and row/column count
- Scales and positions socket clones to fit grid cells
- Returns all generated sockets regardless of warnings (partial success supported)
Input Structure
The gridGroup must be a THREE.Group with the following properties:
userData Requirements
gridGroup.userData = {
type: 'socketGrid', // Required: identifies as socket grid
rows: number, // Required: number of socket rows (≥1)
columns: number // Required: number of socket columns (≥1)
}
Child Nodes
The group must contain exactly two child nodes:
Socket Plane Node (child with
userData.type === 'socketPlane')- Defines the 2D plane where sockets will be distributed
- Must contain a mesh with valid geometry
- Geometry bounds determine the grid dimensions
- Any shape can work as long as it has a clear bounding box
Base Socket Group (child with
userData.type === 'socket')- Template socket to be cloned and positioned
- Must contain a mesh with valid geometry
- Will be cloned once per grid cell
- All userData properties are deep-cloned and preserved
Processing Pipeline
1. Validation Phase
- Checks grid group has correct
userData.type - Validates
rowsandcolumnsare positive integers - Verifies required child nodes exist
- Confirms both nodes contain valid meshes with geometry
- Returns early with warnings if validation fails
2. Transform Calculation Phase
- Updates world matrices for all nodes
- Calculates relative transformations:
planeToGrid: Plane mesh position relative to grid groupbaseSocketToGrid: Base socket mesh position relative to grid groupbaseGroupToGrid: Base socket group position relative to grid group
- These transformations account for nested hierarchies and world-space positions
3. Plane Analysis Phase
- Extracts bounding box from plane mesh geometry
- Determines the two largest axes as U (width) and V (height)
- Calculates the plane's normal axis (N)
- Computes plane center and dimensions
- Returns error if plane is degenerate (near-zero dimensions)
4. Socket Sizing Phase
- Projects base socket geometry onto the grid's U, V, and N axes
- Calculates socket extents in local grid space
- Computes scale factors needed to fit sockets into grid cells:
scaleU: Cell width / socket widthscaleV: Cell height / socket height
- Creates a scaling transformation that scales around the socket's center point
5. Grid Positioning Phase
- Calculates grid cell positions using:
stepU: Distance between columnsstepV: Distance between rows
- For each grid cell (row, column):
- Clones the base socket group with full hierarchy
- Deep-clones userData properties
- Attaches metadata about the parent grid
- Applies scaling transformation
- Applies translation transformation to position in grid
- Adds to results array
Key Algorithms
Plane Extraction (extractPlaneInfo)
Determines the primary 2D plane from a 3D geometry:
- Computes geometry bounding box
- Transforms bounding box axes to grid space
- Sorts axes by length (transformed scale)
- Takes the two longest axes as U and V
- Computes normal axis N as cross product of U and V
- Returns orthonormal basis and dimensions
Geometry Projection (projectGeometryExtents)
Projects a 3D geometry onto three orthogonal axes:
- Extracts all 8 corners of bounding box
- Transforms corners to target space
- Computes dot products along each axis
- Tracks min/max for each axis
- Returns size in each dimension
Scale-Around-Point (makeAxisScaleAroundPoint)
Creates a matrix that scales around a point in custom coordinate space:
- Builds basis matrix from three orthonormal axes
- Creates scaling matrix in basis space
- Transforms scale to world space
- Applies translation to anchor around specified point
- Combines all transformations: translate to origin → scale → translate back
Output
Generated Sockets
Each cloned socket receives:
- Full geometry hierarchy from base socket
- Deep-cloned userData with additional properties:
socketGridName: Name of parent grid groupsocketGridRows: Total rows in gridsocketGridColumns: Total columns in grid
- Applied transformations:
- Scaled to fit grid cell
- Positioned in grid layout
- Maintains local orientation from base socket
Grid Positioning Details
- U-axis ordering: Columns increase left to right
- V-axis ordering: Rows increase bottom to top
- Center reference: Grid centered at plane center, sockets scaled around their local centers
- Cell spacing: Uniform spacing calculated as
dimension / (count - 1)for count > 1
Error Handling
Validation Warnings
The function collects non-fatal warnings but continues processing:
- Missing socketPlane or socket child nodes
- Invalid or missing rows/columns values
- Missing or invalid meshes in required nodes
- Degenerate plane (near-zero dimensions)
Return Behavior
- Always returns
{ generatedSockets, warnings } generatedSocketsmay be empty if validation failswarningsexplains all detected issues- Caller can check warnings to understand partial or complete failure
Helper Functions
findFirstMesh(root)
Traverses a THREE.Object3D tree and returns the first mesh found, or null.
toPositiveInt(value)
Safely converts a value to a positive integer (≥1), returns null if invalid.
deepCloneUserData(userData)
Deep-clones userData object using JSON serialization, falls back to shallow clone if that fails.
ensureBoundingBox(geometry)
Lazy-computes and returns bounding box, computing it if not already present.
getBoundingBoxCorners(boundingBox)
Extracts all 8 corner points of a bounding box.
Performance Considerations
- Memory: Creates N × M clones (one per grid cell), each with full geometry
- Scale factors: Computed once per grid, not per socket
- Transformations: Matrix operations are performed per socket (2 transformations each)
- Large grids: For >1000 sockets, consider instancing or LOD techniques
Constraints & Limitations
- Grid must be rectangular (even spacing in U and V)
- Plane should be roughly planar (geometry will use bounding box axes regardless)
- Cells are uniform-sized (no non-uniform spacing)
- Cannot skip grid positions (always fills complete grid)
- Scaling is uniform in U and V directions (based on cell dimensions)
- Sockets are positioned at cell centers