thejustinwalsh/three-flatland
thejustinwalsh/three-flatland has 36 GitHub stars and 2 forks. GitHot measured 0 stars in 24 hours, 0 in 7 days, and 0 in 15 days. Last checked 2026-11-11 00:28 UTC. Last push 2026-10-19.
The composable 2D library for Three.js
What you can do
- 2D primitives on the Three.js scene graph. Sprite2D, AnimatedSprite2D, SpriteGroup, and TileMap2D as plain Object3D subclasses. Transform hierarchy and render order are independent (sortLayer + zIndex).
- TSL-native effect composition. Effects are TSL node graphs that ride on a shared material. Sprites in a batch stay batched as effects come and go.
- Effect-aware sprite batching. Compatible sprites stay batched as effects change, with per-sprite values packed into shared GPU buffers.
- Spritesheet animation with frame-precise timing. AnimationController handles play/pause/onComplete; declare named animations against a sheet.
Measured star growth
- in 24h
- 0
- in 7 days
- 0
- in 15 days
- 0
- in 1 month
- Update delayed
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
Original
three-flatland
2D sprites, tilemaps, and TSL-composable effects for Three.js, built for WebGPU.
[!IMPORTANT] Alpha — three-flatland is in active development. The library targets GPU-driven 2D rendering on WebGPU; the API will evolve as the underlying systems settle. Open an issue or comment on GitHub to shape what gets built.
Features
- 2D primitives on the Three.js scene graph.
Sprite2D,AnimatedSprite2D,SpriteGroup, andTileMap2Das plainObject3Dsubclasses. Transform hierarchy and render order are independent (sortLayer+zIndex). - TSL-native effect composition. Effects are TSL node graphs that ride on a shared material. Sprites in a batch stay batched as effects come and go.
- Effect-aware sprite batching. Compatible sprites stay batched as effects change, with per-sprite values packed into shared GPU buffers.
- Spritesheet animation with frame-precise timing.
AnimationControllerhandles play/pause/onComplete; declare named animations against a sheet. - Tilemap loaders for Tiled and LDtk. Animated tiles supported.
- Pixel-perfect by default.
FlatlandConfigcoordinates texture loading, integer camera scale, and projected-pivot snapping; override the whole pipeline or one subsystem. - Render to texture for 2D-on-3D. The
Flatlandclass composes a 2D scene with a pixel-perfect orthographic camera and optionalRenderTarget; sample the result on any 3D material (mesh.material.map = flatland.texture). - React Three Fiber integration via
three-flatland/react. Re-exports the core surface plus JSX type augmentation;attachEffectcovers the add/remove lifecycle. - Tree-shakeable subpath exports.
three-flatland/sprites,/animation,/loaders,/cameras,/config,/pipeline,/tilemap,/materials. Import only what you use.
Installation
# Core library (Three.js)
npm install three-flatland three
# For React Three Fiber
npm install three-flatland @react-three/[email protected] react@~19.2.0 react-dom@~19.2.0
# TSL shader nodes
npm install @three-flatland/nodes
For a new project, run pnpm create three-flatland and choose the Three.js or React template.
See installation for the available options.
Quick Start
Three.js
import { WebGPURenderer } from 'three/webgpu'
import { Scene } from 'three'
import { PixelPerfectCamera, Sprite2D, SpriteGroup, TextureLoader } from 'three-flatland'
const scene = new Scene()
const camera = new PixelPerfectCamera({ viewSize: 600 })
const renderer = new WebGPURenderer()
renderer.setSize(800, 600)
camera.setDrawingBufferSize(renderer.domElement.width, renderer.domElement.height)
renderer.setViewport(camera.getLogicalViewport(renderer.getPixelRatio()))
document.body.appendChild(renderer.domElement)
await renderer.init()
const texture = await TextureLoader.load('/sprite.png')
// SpriteGroup handles automatic batching
const group = new SpriteGroup()
scene.add(group)
const sprite = new Sprite2D({ texture, anchor: [0.5, 0.5] })
group.add(sprite)
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
React Three Fiber
import { Canvas, extend, useLoader } from '@react-three/fiber/webgpu'
import { Suspense } from 'react'
import { Sprite2D, SpriteGroup, TextureLoader, usePixelPerfectCamera } from 'three-flatland/react'
extend({ Sprite2D, SpriteGroup })
function Camera() {
usePixelPerfectCamera({ viewSize: 600 })
return null
}
function Sprite() {
const texture = useLoader(TextureLoader, '/sprite.png')
return (
<spriteGroup>
<sprite2D texture={texture} anchor={[0.5, 0.5]} />
</spriteGroup>
)
}
export default function App() {
return (
<Canvas orthographic>
<Camera />
<Suspense>
<Sprite />
</Suspense>
</Canvas>
)
}
Core Concepts
Sort layers and z-ordering
Unlike traditional 3D engines, three-flatland separates transform hierarchy from render order:
import { SortLayers, Sprite2D } from 'three-flatland'
// Scene graph controls position inheritance
const player = new THREE.Group()
const shadow = new Sprite2D({ texture: shadowTex })
const body = new Sprite2D({ texture: bodyTex })
player.add(shadow, body)
// Render order is explicit and independent
shadow.sortLayer = SortLayers.SHADOWS // Renders first
body.sortLayer = SortLayers.ENTITIES // Renders on top
// Shadow moves with player but always renders below
player.position.x += 10
Animation
import { AnimatedSprite2D, SpriteSheetLoader } from 'three-flatland'
const sheet = await SpriteSheetLoader.load('/sprites/player.json')
const player = new AnimatedSprite2D({
spriteSheet: sheet,
animationSet: {
animations: {
idle: { frames: ['idle_0', 'idle_1', 'idle_2'], fps: 8 },
run: { frames: ['run_0', 'run_1', 'run_2', 'run_3'], fps: 12 },
attack: { frames: ['attack_0', 'attack_1'], fps: 15, loop: false },
},
},
animation: 'idle',
})
player.update(deltaMs)
player.play('run')
player.play('attack', { onComplete: () => player.play('idle') })
Composable TSL Effects
import { createMaterialEffect } from 'three-flatland'
import { tintAdditive } from '@three-flatland/nodes'
import { vec4 } from 'three/tsl'
const DamageFlash = createMaterialEffect({
name: 'damageFlash',
schema: { intensity: 1 } as const,
node: ({ inputColor, attrs }) => {
const flashed = tintAdditive(inputColor, [1, 1, 1], attrs.intensity)
return vec4(flashed.rgb.mul(inputColor.a), inputColor.a)
},
})
const flash = new DamageFlash()
sprite.addEffect(flash)
flash.intensity = 0.8 // Animate per frame
Packages
| Package | Description |
|---|---|
three-flatland |
Default entry. Sprites, animation, tilemaps, materials, lights, events, everyday loaders. |
three-flatland/react |
React Three Fiber subpath. Re-exports core plus JSX type augmentation. |
create-three-flatland |
Project scaffolder. npm create three-flatland@latest. |
@three-flatland/nodes |
A specific 2D shader effect (retro/CRT, blur, distortion, color, upscale) without hand-writing TSL. |
@three-flatland/presets |
Lit sprites working immediately. Thin — DefaultLightEffect and NormalMapProvider. |
@three-flatland/normals |
Dynamic lighting on flat 2D art without hand-authoring normal maps. |
@three-flatland/atlas |
Loose sprite PNGs into one draw-call-friendly atlas, optionally polygon-trimmed for overdraw. |
@three-flatland/alphamap |
Pixel-perfect pointer hit testing on transparent sprites instead of bounding-box hits. |
@three-flatland/image |
PNG/WebP/AVIF/KTX2 encode and decode, plus Ktx2Loader. |
@three-flatland/bake |
Offline-bake framework and the flatland-bake binary. |
@three-flatland/slug |
Text that stays sharp at any zoom or perspective; thousands of glyphs in one draw call. |
@three-flatland/skia |
Immediate-mode 2D canvas in the scene: arbitrary paths, boolean ops, filters, gradients, images. |
@three-flatland/devtools |
Live inspection of scene, material, and sprite state. |
@three-flatland/skills |
Agent skills for TSL and the three-flatland ecosystem. |
Requirements
- three ^0.185.1 (TSL/WebGPU support)
- React ~19.2.0 (for
three-flatland/react, capped below 19.3 by R3F alpha.3) - @react-three/fiber 10.0.0-alpha.3 (for React, WebGPU support)
Documentation
Full docs, interactive examples, and API reference at tjw.dev/three-flatland.
When not to reach for three-flatland
- 3D scenes. Use Three.js directly. three-flatland adds nothing for non-2D work and the batching system assumes orthographic-style 2D composition.
- WebGL-1-only targets.
WebGPURenderercan use Three.js's WebGL 2 node backend when WebGPU is unavailable; legacy WebGL 1 is out of scope. - DOM-overlay UI. For HTML UI layered over a canvas, use the DOM. three-flatland is a renderer, not a UI toolkit.
Roadmap
- Core sprite system (Sprite2D, materials, loaders)
- Animation system (AnimatedSprite2D, AnimationController)
- 2D render pipeline with effect-aware sprite batching
- TSL effect nodes (composable shader nodes)
- Tilemap support (Tiled, LDtk)
- React Three Fiber integration
- Skia GPU text rendering via WASM
- Render targets for 2D-on-3D (via
Flatlandclass) - Slug text rendering (
@three-flatland/slug) - 2D lighting & shadows (
@three-flatland/presets) - VS Code tools (
three-flatland.fl-tools) - Effect presets (
@three-flatland/presets) - Particles
License
Explore related projects
ytdl-org/youtube-dl
Command-line program to download videos from YouTube.com and other video sites
141,527 starsexcalidraw/excalidraw
Virtual whiteboard for sketching hand-drawn like diagrams
135,492 starsgodotengine/godot
Godot Engine – Multi-platform 2D and 3D game engine
119,942 starsmrdoob/three.js
JavaScript 3D Library.
118,122 stars