@dcversus/babylon-anyup
v0.1.0
Published
Babylon.js plugin for Z-up coordinate system compatibility with automatic transforms
Maintainers
Readme
babylon-anyup
A Babylon.js plugin for seamless Z-up to Y-up coordinate system conversion with automatic transforms.
Why This Exists
babylon-anyup was born from real-world pain while building Edge Craft - a WebGL-based RTS game engine. Our goal is to let modmakers port their Warcraft 3 and StarCraft 2 maps and mods to the web. But there's a problem:
Blizzard games use Z-up coordinates. Babylon.js uses Y-up coordinates.
This means every terrain vertex, every model, every camera position needs manual transformation. Without babylon-anyup, our codebase was littered with hundreds of lines of error-prone coordinate conversion math. Rotating models by -90°, swapping Y and Z axes, inverting signs - it was a nightmare to maintain.
We built this library to solve that problem once and for all. Now you can load Blizzard-format maps directly into Babylon.js without thinking about coordinate systems.
Who This Is For
This library is perfect for developers working with:
- Game Development: Porting Warcraft 3, StarCraft 2, or other Z-up games to Babylon.js
- CAD Import: Bringing CAD models (typically Z-up) into web-based Babylon.js viewers
- Blender Integration: Loading Blender exports (Z-up) without manual rotation
- 3D Content Pipelines: Building tools that need to support multiple coordinate systems
- RTS Engines: Building real-time strategy games with terrain and unit systems
Overview
babylon-anyup provides a type-safe, zero-dependency solution for working with different coordinate systems in Babylon.js. It automatically handles the mathematical transformations needed to convert between:
- Z-up (Warcraft 3, StarCraft 2, Blizzard games, many 3D modeling tools)
- Y-up (Babylon.js default, many game engines)
Status
Current Phase: Initial Development Version: 0.1.0 Stability: Alpha Release Date: TBD
Live Demo
Coming soon! Check back after v0.1.0 release for interactive examples on GitHub Pages.
Features
- Automatic coordinate system conversion (Z-up ↔ Y-up)
- Position, rotation, and scaling transformations
- Preserves original transforms (optional)
- Type-safe with full TypeScript support
- Zero dependencies (peer dependency: @babylonjs/core)
- Lightweight (<10KB minified)
Installation
npm install @dcversus/babylon-anyupQuick Start
import { Engine, Scene, MeshBuilder } from '@babylonjs/core';
import { AnyUpPlugin } from '@dcversus/babylon-anyup';
const scene = new Scene(engine);
// Initialize the plugin
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up',
targetSystem: 'y-up',
autoConvert: true,
preserveOriginal: true,
});
plugin.initialize(scene);
// All meshes in the scene are automatically converted
const mesh = MeshBuilder.CreateBox('box', { size: 2 }, scene);API Reference
AnyUpPlugin
Main plugin class for coordinate system conversion.
Constructor
new AnyUpPlugin(options: AnyUpPluginOptions)Options:
interface AnyUpPluginOptions {
sourceSystem: 'y-up' | 'z-up'; // Source coordinate system
targetSystem: 'y-up' | 'z-up'; // Target coordinate system
autoConvert: boolean; // Auto-convert all meshes on initialize()
preserveOriginal: boolean; // Store original transforms in metadata
}sourceSystem- Input coordinate system ('z-up'for Warcraft 3, Blender;'y-up'for Babylon.js)targetSystem- Output coordinate system (must differ from source)autoConvert- Iftrue, automatically converts all existing meshes wheninitialize()is calledpreserveOriginal- Iftrue, stores original transforms inmesh.metadatabefore conversion
Properties
name: string- Plugin identifier (readonly, always"AnyUpPlugin")options: AnyUpPluginOptions- Configuration options (readonly)
Methods
initialize(scene: Scene): void
Initialize the plugin with a Babylon.js scene.
const scene = new Scene(engine);
plugin.initialize(scene);Behavior:
- If
autoConvert: true, converts all existing meshes and transform nodes - Stores plugin reference in
scene.metadatafor later access
convertMesh(mesh: AbstractMesh): void
Convert a single mesh's transform to the target coordinate system.
const box = MeshBuilder.CreateBox('box', { size: 2 }, scene);
plugin.convertMesh(box);Behavior:
- Transforms
position,rotationQuaternion, andscaling - If
preserveOriginal: true, stores original values inmesh.metadata - Handles null
rotationQuaterniongracefully
Metadata (when preserveOriginal: true):
mesh.metadata = {
originalPosition: Vector3,
originalRotation: Quaternion | null,
originalScaling: Vector3,
};convertTransformNode(node: TransformNode): void
Convert a transform node (same as convertMesh() but for nodes without geometry).
const parent = new TransformNode('parent', scene);
plugin.convertTransformNode(parent);dispose(): void
Clean up plugin resources. Call when the plugin is no longer needed.
plugin.dispose();Transform Strategies
Low-level transformation strategies (used internally by AnyUpPlugin).
ZUpToYUpStrategy
Converts Z-up coordinates to Y-up (Blizzard games → Babylon.js).
import { ZUpToYUpStrategy } from '@dcversus/babylon-anyup';
const strategy = new ZUpToYUpStrategy();
const converted = strategy.convertPosition(new Vector3(1, 2, 3));
// Result: Vector3(1, 3, -2)Transformation:
- Position:
(x, y, z)→(x, z, -y) - Rotation: Applies -90° X-axis correction
- Scaling:
(x, y, z)→(x, z, y)
YUpToZUpStrategy
Converts Y-up coordinates to Z-up (Babylon.js → Blizzard games).
import { YUpToZUpStrategy } from '@dcversus/babylon-anyup';
const strategy = new YUpToZUpStrategy();
const converted = strategy.convertPosition(new Vector3(1, 2, 3));
// Result: Vector3(1, -3, 2)Transformation:
- Position:
(x, y, z)→(x, -z, y) - Rotation: Applies +90° X-axis correction
- Scaling:
(x, y, z)→(x, z, y)
TransformStrategyFactory
Factory for creating transformation strategies.
import { TransformStrategyFactory } from '@dcversus/babylon-anyup';
const strategy = TransformStrategyFactory.createStrategy('z-up', 'y-up');Throws:
Errorifsource === targetErrorif conversion not supported
Usage Examples
Warcraft 3 Terrain Import
import { Engine, Scene } from '@babylonjs/core';
import { AnyUpPlugin } from '@dcversus/babylon-anyup';
const engine = new Engine(canvas);
const scene = new Scene(engine);
// Initialize plugin for Z-up (Warcraft 3)
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up',
targetSystem: 'y-up',
autoConvert: true,
preserveOriginal: false,
});
plugin.initialize(scene);
// Load Warcraft 3 terrain (automatically converted)
const terrain = await loadW3Terrain('map.w3e');
scene.addMesh(terrain); // Already in Y-up coordinates!Selective Conversion (Manual Mode)
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up',
targetSystem: 'y-up',
autoConvert: false, // Disable auto-conversion
preserveOriginal: false,
});
plugin.initialize(scene);
// Only convert specific meshes
const terrain = loadWarcraft3Terrain();
plugin.convertMesh(terrain);
const units = loadWarcraft3Units();
// Don't convert units (keep original orientation)Preserve Original Transforms
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up',
targetSystem: 'y-up',
autoConvert: true,
preserveOriginal: true, // Store originals
});
plugin.initialize(scene);
// Access original transforms later
scene.meshes.forEach(mesh => {
console.log('Original:', mesh.metadata.originalPosition);
console.log('Converted:', mesh.position);
// Revert if needed
if (someCondition) {
mesh.position = mesh.metadata.originalPosition.clone();
}
});Blender Model Import
import { SceneLoader } from '@babylonjs/core';
import { AnyUpPlugin } from '@dcversus/babylon-anyup';
// Initialize plugin
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up',
targetSystem: 'y-up',
autoConvert: false,
preserveOriginal: false,
});
plugin.initialize(scene);
// Load Blender export (typically Z-up)
SceneLoader.ImportMesh('', '/models/', 'blender_export.glb', scene, (meshes) => {
// Convert imported meshes
meshes.forEach(mesh => plugin.convertMesh(mesh));
});Direct Strategy Usage (Advanced)
import { ZUpToYUpStrategy } from '@dcversus/babylon-anyup';
import { Vector3, Quaternion } from '@babylonjs/core';
const strategy = new ZUpToYUpStrategy();
// Convert individual components
const zUpPos = new Vector3(10, 20, 30);
const yUpPos = strategy.convertPosition(zUpPos);
console.log(yUpPos); // Vector3(10, 30, -20)
// Convert rotation
const zUpRot = Quaternion.FromEulerAngles(0, Math.PI / 4, 0);
const yUpRot = strategy.convertRotation(zUpRot);
// Convert scaling
const zUpScale = new Vector3(1, 2, 3);
const yUpScale = strategy.convertScaling(zUpScale);
console.log(yUpScale); // Vector3(1, 3, 2)Migration Guide
From Manual Transforms
Before (manual transformation):
// ❌ Old way: Manual coordinate conversion everywhere
function loadW3Terrain(w3e: W3E) {
for (let i = 0; i < w3e.vertices.length; i++) {
// Manual Y↔Z swap and negation
vertices[i * 3] = w3e.vertices[i].x;
vertices[i * 3 + 1] = w3e.vertices[i].z; // Y becomes Z
vertices[i * 3 + 2] = -w3e.vertices[i].y; // Z becomes -Y
}
// Hardcoded -90° rotation for models
mesh.rotation.x = -Math.PI / 2;
}After (with babylon-anyup):
// ✅ New way: Automatic transformation
import { AnyUpPlugin } from '@dcversus/babylon-anyup';
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up',
targetSystem: 'y-up',
autoConvert: true,
preserveOriginal: false,
});
plugin.initialize(scene);
// Load terrain normally - plugin handles conversion
function loadW3Terrain(w3e: W3E) {
// Just use data as-is, plugin converts automatically
const terrain = createTerrainMesh(w3e);
// Done! No manual transformation needed
}Migration Steps
- Install babylon-anyup:
npm install @dcversus/babylon-anyupRemove manual transforms:
- Delete all Y↔Z swapping code
- Remove hardcoded rotations (e.g.,
mesh.rotation.x = -Math.PI / 2) - Remove position/scale coordinate conversions
Add plugin initialization:
import { AnyUpPlugin } from '@dcversus/babylon-anyup';
const plugin = new AnyUpPlugin({
sourceSystem: 'z-up', // Your source data format
targetSystem: 'y-up', // Babylon.js format
autoConvert: true, // Convert everything automatically
preserveOriginal: false,
});
plugin.initialize(scene);Test thoroughly:
- Verify meshes are positioned correctly
- Check rotations are correct
- Validate scaling matches expectations
- Test camera controls feel natural
Clean up (optional):
- Remove helper functions for coordinate conversion
- Simplify data loading code
- Update comments/documentation
Common Issues During Migration
Issue: Meshes are upside down or rotated incorrectly
- Solution: Ensure you're using correct
sourceSystemandtargetSystem - Tip: Try swapping them if objects appear inverted
Issue: Some meshes convert, others don't
- Solution: Use
autoConvert: trueor manually callplugin.convertMesh()for each mesh - Tip: Check if meshes were added before
plugin.initialize()
Issue: Transformations are applied twice
- Solution: Remove manual coordinate conversions from your code
- Tip: Search codebase for Y/Z swaps, hardcoded rotations
Contributing
Want to contribute? See CONTRIBUTING.md for development workflow, quality standards, and PR guidelines
Performance Benchmarks
Benchmarks run on MacBook, version 0.1.0 (2025-10-27):
| Operation | Target (PRP) | Actual | Status | |-----------|--------------|--------|--------| | Vector3 Transform | >100,000 ops/sec | ~22,000,000 ops/sec | ✅ 220x faster | | Quaternion Transform | >66,000 ops/sec | ~19,000,000 ops/sec | ✅ 287x faster | | Bulk Transform (1K vectors) | N/A | 183,549 ops/sec | ✅ | | Bulk Transform (1K quaternions) | N/A | 35,493 ops/sec | ✅ | | Bundle Size | <10KB minified | 8.2KB | ✅ |
Real-world impact: Transforming 10,000 meshes takes ~0.5ms - negligible overhead in production scenes.
Run Benchmarks Yourself
npm run benchDetailed Results
Vector3 Transform:
- ZUpToYUpStrategy: 22,299,067 ops/sec (fastest)
- YUpToZUpStrategy: 19,933,199 ops/sec
Quaternion Transform:
- ZUpToYUpStrategy: 18,273,103 ops/sec
- YUpToZUpStrategy: 19,600,341 ops/sec (fastest)
Scaling Transform:
- ZUpToYUpStrategy: 20,312,662 ops/sec
- YUpToZUpStrategy: 20,744,284 ops/sec (fastest)
The library achieves exceptional performance due to minimal overhead, optimized Babylon.js math operations, and zero dynamic dispatch.
Browser Support
Supports all modern browsers that support WebGL 2:
- Chrome 56+
- Firefox 51+
- Safari 15+
- Edge 79+
Requires Babylon.js 7.0.0 or higher.
Troubleshooting
My meshes are still rotated incorrectly
Ensure you're using autoConvert: true or manually calling plugin.convertMesh(mesh) after loading. If the mesh was already in the scene before plugin initialization, convert it manually:
const plugin = new AnyUpPlugin({ sourceSystem: 'z-up', targetSystem: 'y-up' });
plugin.initialize(scene);
// For existing meshes
scene.meshes.forEach(mesh => plugin.convertMesh(mesh));Performance is slower than expected
- Check if you're converting the same mesh multiple times (use
mesh.metadata.coordinateSystemConvertedto track) - Use
autoConvert: falseand batch convert meshes during load - Profile with browser DevTools to identify bottlenecks
Transformations are not reversible
Ensure you're using preserveOriginal: true to store original transforms in metadata. Note that floating-point precision may cause small errors in round-trip conversions.
Plugin not working with imported models
Some model loaders apply their own transformations. Initialize the plugin before loading models, or manually convert after import completes.
License
GNU Affero General Public License v3.0 (AGPL-3.0) - see LICENSE for details.
Author: Vasilisa Versus (@dcversus) Contact:
- Email: [email protected]
- Telegram: t.me/dcversus
- Social: @dcversus
Related Projects
- Edge Craft - WebGL RTS game engine (the project that inspired babylon-anyup)
- Babylon.js - Powerful 3D engine for the web
- mdx-m3-viewer - Warcraft 3 and StarCraft 2 model viewer
Roadmap
v0.1.0 (Current)
- [x] Core Z-up to Y-up transformation
- [x] Plugin system with auto-convert
- [ ] Comprehensive test suite
- [ ] Performance benchmarks
- [ ] Documentation and examples
v0.2.0 (Planned)
- [ ] X-up coordinate system support
- [ ] Custom coordinate system definitions
- [ ] Animation retargeting
- [ ] Vertex-level transformations
- [ ] glTF/OBJ loader integration
v1.0.0 (Future)
- [ ] Handedness conversion (left ↔ right)
- [ ] UV coordinate adjustments
- [ ] Performance optimizations (SIMD, workers)
- [ ] Visual debugging tools
Sponsors
This project is currently maintained by @dcversus. If you find this library useful, consider sponsoring development:
- GitHub Sponsors (coming soon)
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: See SECURITY.md for vulnerability reporting
