import type { Container, Mesh, Renderer } from 'pixi.js'; import { shaderOf } from '../core/properties/shaders'; import type { ActiveCamera } from './activeCamera'; import { type CameraPicture, cameraPicture } from './cameraPicture'; import { ShaderPicture } from './shaderPicture'; import { type ShaderSource, syncShader } from './shaders'; import { disposeSpriteMesh, newSpriteMesh, placeNode } from './symbols'; /** The picture `camera`'s shader is laid over, or null while it wears none that compiles. */ export function shadedPicture( camera: ActiveCamera, shaders: ShaderSource, devicePixelsPerUnit: number, onGrid: boolean, ): CameraPicture | null { const id = shaderOf(camera.props); return id && shaders.compiled(id) ? cameraPicture(camera, devicePixelsPerUnit, onGrid) : null; } /** * A camera's shader (#299): the world as the camera sees it is drawn into a picture of its frame, and * the shader draws that picture where the frame stands. */ export class CameraShade { private mesh: Mesh | null = null; private readonly picture = new ShaderPicture(); /** * Draws `world`, which is in world units, into the camera's picture and lays the shader over it. * `clearColor` is the scene's background, or null for a see-through overlay camera. Returns what a * view attaches, in world units – or null, and nothing drawn, while the camera wears no shader that * compiles. `onGrid`: through a pixel perfect camera (see `cameraPicture`). */ draw( world: Container, camera: ActiveCamera, shaders: ShaderSource, devicePixelsPerUnit: number, renderer: Renderer, clearColor: number | null, onGrid = false, ): Mesh | null { const id = shaderOf(camera.props); const compiled = id ? shaders.compiled(id) : null; if (!compiled) return null; const shape = cameraPicture(camera, devicePixelsPerUnit, onGrid); const scaleMode = shape.blocky ? 'nearest' : 'linear'; const picture = this.picture.draw( renderer, world, shape.pixels, shape.toPicture, clearColor, compiled.edge, scaleMode, ); const mesh = (this.mesh ??= newSpriteMesh()); const { x, y } = shape.centre; placeNode(mesh, { a: 1, b: 0, c: 0, d: 1, tx: x, ty: y }, shape.frame.w, shape.frame.h); mesh.texture = picture; // The frame is the edge, so neither Padding nor Vertex output moves it; a pixel counts as a camera pixel. syncShader(mesh, camera.props, shaders, null, id, shape.pixelScale); this.picture.release(); return mesh; } /** The picture's size in pixels while one is drawn (test hook). */ get pixels(): { w: number; h: number } | null { return this.picture.pixels; } destroy(): void { if (this.mesh) disposeSpriteMesh(this.mesh); this.mesh = null; this.picture.destroy(); } }