Skip to content

BaseGroundPlugin ​

Example — Source Code — API Reference

Base Ground Plugin adds a simple horizontal ground plane to the scene that automatically positions itself below the model. It provides a foundation for displaying 3D objects with proper grounding and can be extended by plugins like AdvancedGroundPlugin for more advanced features.

The plugin creates a plane mesh with a configurable PhysicalMaterial, automatically adjusts its position and scale based on the scene bounds, and provides options for camera limits, depth rendering, and tonemapping.

Features ​

  • Auto-Positioning: Automatically positions ground below scene bounding box
  • Configurable Size: Adjustable ground plane dimensions
  • Height Offset: Fine-tune vertical position
  • Material Access: Full control over PhysicalMaterial properties
  • Camera Limits: Optional constraint to keep camera above ground
  • Depth Rendering: Control whether ground appears in depth buffer
  • Tonemap Control: Toggle tonemapping for the ground material
  • Extensible: Base class for advanced ground plugins
  • Serializable: Full state serialization support

Installation ​

BaseGroundPlugin is part of the core threepipe package:

bash
npm install threepipe

Basic Setup ​

typescript
import {ThreeViewer, BaseGroundPlugin} from 'threepipe'

const viewer = new ThreeViewer({
    canvas: document.getElementById('canvas'),
    msaa: true
})

// Add ground plugin
const ground = viewer.addPluginSync(new BaseGroundPlugin())

// Load model - ground will auto-position below it
await viewer.load('model.glb')

The ground plane will automatically position itself below the model's bounding box.

Configuration ​

Visibility and Size ​

Control basic ground appearance:

typescript
const ground = viewer.getPlugin(BaseGroundPlugin)

// Visibility
ground.visible = true  // Show ground (default)
ground.visible = false // Hide ground
ground.enabled = true  // Same as visible

// Size (in scene units)
ground.size = 8   // Default, 8x8 units
ground.size = 15  // Larger ground
ground.size = 3   // Smaller ground
ground.size = 0   // Hide ground (alternative to visible = false)

The ground plane scales uniformly in X and Z directions.

Position ​

Adjust the vertical position of the ground:

typescript
// Height offset (Y position relative to model bottom)
ground.yOffset = 0     // Default, at model's base
ground.yOffset = -0.5  // Lower ground (0.5 units below)
ground.yOffset = 0.1   // Raised ground (0.1 units above)

The yOffset is relative to the bottom of the scene's bounding box.

Auto-Adjustment ​

Control automatic positioning:

typescript
// Auto-adjust ground to fit under scene
ground.autoAdjustTransform = true  // Default, automatic
ground.autoAdjustTransform = false // Manual control

// When true, ground automatically:
// - Centers below the scene's bounding box
// - Updates when objects are added or scene changes
// - Respects the yOffset setting

Material Configuration ​

Access and modify the ground material:

typescript
const material = ground.material

// Material is a PhysicalMaterial
material.color.set('#ffffff')  // White ground
material.color.set('#8b8b8b')  // Gray ground
material.roughness = 0.8       // Default
material.metalness = 0.5       // Default
material.opacity = 1.0

// Add textures
material.map = await viewer.load('ground_texture.jpg')
material.normalMap = await viewer.load('ground_normal.jpg')
material.roughnessMap = await viewer.load('ground_roughness.jpg')

// Must call setDirty after changes
material.setDirty()

The ground uses a standard PhysicalMaterial with all PBR properties available.

Camera Limits ​

Prevent camera from going below ground:

typescript
// Limit camera to stay above ground
ground.limitCameraAboveGround = true  // Enable limit
ground.limitCameraAboveGround = false // No limit (default)

// Only works with OrbitControls3 or three.js OrbitControls
// Camera's maxPolarAngle is set to π/2 (90°) when enabled

This is useful for architectural visualizations or when you want to prevent the camera from viewing the scene from below.

Rendering Options ​

Control how the ground appears in rendering buffers:

typescript
// Render to depth passes (used by GBuffer, SSR, SSAO)
ground.renderToDepth = true  // Default, included in depth passes
ground.renderToDepth = false // Excluded from depth passes

// Apply tonemapping (requires GBufferPlugin and renderToDepth = true)
ground.tonemapGround = true  // Default, tonemap with scene
ground.tonemapGround = false // No tonemapping (keeps HDR values)

These options are useful when combining with post-processing effects that rely on depth or require special handling of ground colors.

Advanced Usage ​

Custom Material ​

Replace the default material with your own:

typescript
import {PhysicalMaterial} from 'threepipe'

const ground = viewer.getPlugin(BaseGroundPlugin)

// Create custom material
const customMaterial = new PhysicalMaterial({
    name: 'CustomGroundMaterial',
    color: 0x3a5f0b,  // Green
    roughness: 0.9,
    metalness: 0.0
})

// Assign to ground (via mesh.material binding)
ground.mesh.material = customMaterial

// Or access the protected _material directly
// Note: This is not recommended for general use

Custom Geometry ​

Replace the plane with custom geometry:

typescript
import {CircleGeometry} from 'three'

const ground = viewer.getPlugin(BaseGroundPlugin)

// Create circular ground
const circleGeometry = new CircleGeometry(5, 64)
ground.setGeometry(circleGeometry)

// Ground will use the new geometry
// UV coordinates (uv2) are automatically created

Manual Positioning ​

Take full control of ground position:

typescript
const ground = viewer.getPlugin(BaseGroundPlugin)

// Disable auto-adjustment
ground.autoAdjustTransform = false

// Access mesh directly
const mesh = ground.mesh

// Set custom position
mesh.position.set(0, -1, 0)

// Set custom rotation (ground is rotated -90° around X by default)
mesh.rotation.set(-Math.PI / 2, 0, Math.PI / 4) // 45° Z rotation

// Set custom scale
mesh.scale.set(10, 10, 1)

// Update matrices
mesh.updateMatrixWorld()

Extend for Custom Ground ​

Create a custom ground plugin by extending BaseGroundPlugin:

typescript
import {BaseGroundPlugin, PhysicalMaterial, ThreeViewer} from 'threepipe'

class MyCustomGroundPlugin extends BaseGroundPlugin {
    static readonly PluginType = 'MyCustomGroundPlugin'

    // Override material creation
    protected _createMaterial(material?: PhysicalMaterial): PhysicalMaterial {
        if (!material) {
            material = new PhysicalMaterial({
                name: 'MyCustomGroundMaterial',
                color: 0xff0000,  // Red ground
                roughness: 0.5,
                metalness: 0.8
            })
        }
        return super._createMaterial(material)
    }

    // Override positioning logic
    protected _refreshTransform() {
        const updated = super._refreshTransform()
        
        // Add custom transform logic here
        if (updated && this._mesh) {
            // Custom modifications to position/rotation/scale
        }
        
        return updated
    }

    // Add custom functionality
    onAdded(viewer: ThreeViewer) {
        super.onAdded(viewer)
        // Custom initialization
    }
}

// Use your custom plugin
const ground = viewer.addPluginSync(new MyCustomGroundPlugin())

Scene Bounds Control ​

Control which objects affect ground positioning:

typescript
const ground = viewer.getPlugin(BaseGroundPlugin)

// Use only model bounds (default)
ground.useModelBounds = true  // Only scene.modelRoot children

// Use full scene bounds
ground.useModelBounds = false // All objects in scene

// Manually refresh ground position
ground.refreshTransform()

Physics Integration ​

The ground is automatically configured for physics:

typescript
const ground = viewer.getPlugin(BaseGroundPlugin)
const mesh = ground.mesh

// Physics properties (already set by plugin)
console.log(mesh.userData.physicsMass)      // 0 (static)
console.log(mesh.userData.physicsBodyType)  // 'static'
console.log(mesh.userData.userSelectable)   // false
console.log(mesh.userData.isGroundMesh)     // true

// These properties are used by physics plugins

Shadow Configuration ​

Configure shadow casting and receiving:

typescript
const ground = viewer.getPlugin(BaseGroundPlugin)
const mesh = ground.mesh

// Shadows (enabled by default)
mesh.castShadow = true     // Ground casts shadows
mesh.receiveShadow = true  // Ground receives shadows

// Disable shadows
mesh.castShadow = false
mesh.receiveShadow = false

UI Integration ​

Use with TweakpaneUiPlugin for runtime controls:

typescript
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))
const ground = viewer.getPlugin(BaseGroundPlugin)

// Add ground controls to UI
ui.setupPluginUi(BaseGroundPlugin)

// Now you have interactive controls for:
// - Visible toggle
// - Size slider
// - Height (yOffset) slider
// - Render to Depth toggle
// - Tonemap Ground toggle
// - Limit Camera Above Ground toggle
// - Auto Adjust Transform toggle
// - Material properties folder

Extended Plugins ​

BaseGroundPlugin serves as the foundation for more advanced ground plugins:

AdvancedGroundPlugin ​

Extends BaseGroundPlugin with planar reflections and baked shadows:

typescript
import {AdvancedGroundPlugin} from '@threepipe/webgi-plugins'

// AdvancedGroundPlugin extends BaseGroundPlugin
const ground = viewer.addPluginSync(new AdvancedGroundPlugin())

// Has all BaseGroundPlugin features plus:
ground.groundReflection = true   // Planar reflections
ground.bakedShadows = true       // Baked soft shadows
ground.physicalReflections = true

See AdvancedGroundPlugin for full documentation.

ContactShadowGroundPlugin ​

Extends BaseGroundPlugin with contact shadow effects:

typescript
import {ContactShadowGroundPlugin} from 'threepipe'

const ground = viewer.addPluginSync(new ContactShadowGroundPlugin())

// Has all BaseGroundPlugin features plus contact shadows

See ContactShadowGroundPlugin for full documentation.

HDRiGroundPlugin ​

A separate plugin (does not extend BaseGroundPlugin) that projects the environment map onto a virtual ground plane by patching the background shader. It does not create a mesh — it works entirely in shader space.

typescript
import {HDRiGroundPlugin} from 'threepipe'

const hdriGround = viewer.addPluginSync(new HDRiGroundPlugin())

See HDRiGroundPlugin for full documentation.

Technical Details ​

How It Works ​

  1. Initialization: Creates plane geometry and default PhysicalMaterial
  2. Scene Integration: Adds mesh to scene root on plugin addition
  3. Auto-Positioning: Listens to scene updates and adjusts transform
  4. Material Management: Tracks and manages material depth/tonemap settings
  5. Camera Integration: Optionally limits camera polar angle

Geometry ​

  • Type: PlaneGeometry (1x1, subdivisions: 1x1)
  • Orientation: Rotated -90° around X axis (horizontal)
  • UVs: Both uv and uv2 attributes available
  • Scale: Controlled by size property

Material ​

  • Type: PhysicalMaterial
  • Default Color: White (#ffffff)
  • Default Roughness: 0.8
  • Default Metalness: 0.5
  • Runtime Material: Marked with userData.runtimeMaterial = true

Performance ​

  • Minimal Impact: Single plane mesh with standard material
  • Efficient Updates: Only refreshes transform when needed
  • Lazy Evaluation: Transform refresh deferred to postFrame

Events ​

The plugin listens to:

  • scene.sceneUpdate - Refresh ground when scene changes
  • scene.addSceneObject - Refresh ground when objects added
  • viewer.preRender - Pre-render setup
  • viewer.postFrame - Post-frame transform updates

Common Use Cases ​

BaseGroundPlugin is ideal for:

  1. Simple Model Display: Quick ground plane for object presentation
  2. Base for Extensions: Foundation for custom ground plugins
  3. Prototyping: Fast setup without complex features
  4. Mobile Friendly: Lightweight ground solution
  5. Physics Simulations: Static ground for physics engines
  6. Architectural Viz: Base ground for buildings and structures

Troubleshooting ​

Ground not visible:

  • Check ground.visible is true
  • Verify ground.size is greater than 0
  • Ensure scene has objects (ground auto-sizes to scene bounds)
  • Check camera position can see ground plane

Ground in wrong position:

  • Enable autoAdjustTransform for automatic positioning
  • Check yOffset value
  • Verify scene bounds are correct
  • Try calling ground.refreshTransform() to schedule a transform update (deferred to next frame)

Ground not updating:

  • Ensure autoAdjustTransform is enabled
  • Check that scene events are firing
  • Verify objects are properly added to scene
  • Call ground.refresh() manually if needed

Camera goes below ground:

  • Enable limitCameraAboveGround
  • Check that OrbitControls3 is being used
  • Verify camera controls are active

Material changes not applied:

  • Call material.setDirty() after changes
  • Check material is properly assigned to mesh
  • Verify changes to correct material instance

Ground not receiving shadows:

  • Check mesh.receiveShadow is true
  • Ensure lights have castShadow enabled
  • Verify shadow maps are configured in viewer

API Reference ​

See the BaseGroundPlugin API documentation for detailed information on all properties and methods.

Threepipe - Next generation 3D toolkit for the web