import { WebGPURenderer } from 'three/webgpu'
import { DataTexture, Group, NearestFilter, RGBAFormat, Scene } from 'three'
import { createPane } from '@three-flatland/devtools'
import { PixelPerfectCamera, Sprite2D, SpriteGroup, createDevtoolsProvider } from 'three-flatland'
import { gemGradientNode } from './GemBackground'
import { GEM } from './gem'
import { initializeRenderer } from './renderStartupError'
import { configureExampleRendererColor } from './rendererColorManagement'
const palette = [0x55, 0xd6, 0xbe, 0xff, 0xb4, 0x8e, 0xff, 0xff, 0xff, 0xc8, 0x57, 0xff, 0xff, 0x73, 0xa8, 0xff]
const texture = new DataTexture(new Uint8Array(palette), 4, 1, RGBAFormat)
texture.magFilter = NearestFilter
texture.needsUpdate = true
const scene = new Scene()
const sceneWithBackground = scene as unknown as { backgroundNode: unknown }
sceneWithBackground.backgroundNode = gemGradientNode({ gem: GEM })
const camera = new PixelPerfectCamera({ viewSize: 320, viewWidth: 480 })
const viewport = new SpriteGroup({ clipRect: [-120, -80, 240, 160] })
viewport.rotation.z = -0.08
/** Create one retained hierarchy of batched palette sprites. */
function makeView(): Group {
for (let i = 0; i < 36; i++) {
const sprite = new Sprite2D({ texture, anchor: [0.5, 0.5] })
sprite.position.set((i % 6) * 42 - 105, Math.floor(i / 6) * 42 - 105, 0)
sprite.scale.set(34, 34, 1)
const firstView = makeView()
const secondView = makeView()
secondView.rotation.z = Math.PI / 4
secondView.visible = false
viewport.add(firstView, secondView)
const renderer = new WebGPURenderer({ antialias: false })
configureExampleRendererColor(renderer)
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
renderer.setSize(innerWidth, innerHeight)
camera.setDrawingBufferSize(renderer.domElement.width, renderer.domElement.height)
renderer.setViewport(camera.getLogicalViewport(renderer.getPixelRatio()))
document.body.appendChild(renderer.domElement)
if (!(await initializeRenderer(renderer))) return
const paneBundle = createPane({ driver: 'manual' })
const devtools = createDevtoolsProvider({ name: 'three-hierarchy-clipping' })
let lastSwap = performance.now()
const motionPreference = matchMedia('(prefers-reduced-motion: reduce)')
/** Animate hierarchy visibility and transforms before rendering each frame. */
function frame(now: number) {
raf = requestAnimationFrame(frame)
if (!motionPreference.matches) {
if (now - lastSwap > 2200) {
firstView.visible = active === 0
secondView.visible = active === 1
const view = active === 0 ? firstView : secondView
view.position.y = Math.sin(now * 0.001) * 55
devtools.beginFrame(now, renderer)
renderer.render(scene, camera)
devtools.endFrame(renderer)
/** Keep the orthographic camera and renderer fitted to the viewport. */
renderer.setSize(innerWidth, innerHeight)
camera.setDrawingBufferSize(renderer.domElement.width, renderer.domElement.height)
renderer.setViewport(camera.getLogicalViewport(renderer.getPixelRatio()))
addEventListener('resize', resize)
import.meta.hot.dispose(() => {
cancelAnimationFrame(raf)
removeEventListener('resize', resize)
paneBundle.pane.dispose()
renderer.domElement.remove()
void main().catch((error: unknown) => console.error('[three-flatland] Example startup failed', error))