meat/engine/render/RenderScene.ts

225 lines
5.7 KiB
TypeScript
Raw Normal View History

import { Mat4 as Matrix, type Mat4 } from "../math/Mat4"
import type { Vec2 } from "../math/Vec2"
import type { Vec3 } from "../math/Vec3"
import type { Camera } from "../scene/Camera"
import { Sprite } from "../scene/Sprite"
import { Framebuffer, type Framebuffer as Frame } from "./Framebuffer"
import { Frustum } from "./Frustum"
import type { DrawGroup, Material } from "./Material"
import { Rasterizer } from "./Rasterizer"
import type { RenderConfig } from "./RenderConfig"
import { Sky, type SkyConfig } from "./Sky"
import { Chunk, type Chunk as RenderChunk } from "./Chunk"
export type Billboard = {
position: Vec3
size: Vec2
material: Material
}
export type RenderPrototype = {
readonly groups: readonly DrawGroup[]
readonly radius: number
readonly minY: number
readonly maxY: number
}
export type RenderTransform = {
x: number
y: number
z: number
heading: number
scale: number
}
export type RenderInstance = RenderTransform & {
prototype: number
}
/** Clone-safe render projection of a live level. Contains no behavior callbacks or
* game-content registries, so workers need only engine code. */
export type RenderScene = {
readonly chunks: readonly RenderChunk[]
readonly staticGroups: readonly DrawGroup[]
readonly billboards: readonly Billboard[]
readonly prototypes: readonly RenderPrototype[]
readonly maxInstances: number
readonly sky: SkyConfig
}
export namespace RenderScene {
export function visibleChunks(
scene: RenderScene,
viewProjection: Mat4,
): number[] {
const frustum = Frustum.fromViewProj(viewProjection)
const visible: number[] = []
for (let i = 0; i < scene.chunks.length; i++) {
const chunk = scene.chunks[i]
if (
Frustum.intersectsAabb(
frustum,
chunk.minX,
chunk.minY,
chunk.minZ,
chunk.maxX,
chunk.maxY,
chunk.maxZ,
)
) {
visible.push(i)
}
}
return visible
}
export function visibleInstances(
scene: RenderScene,
instances: RenderInstance[],
viewProjection: Mat4,
): RenderInstance[] {
const frustum = Frustum.fromViewProj(viewProjection)
const visible: RenderInstance[] = []
for (const instance of instances) {
const prototype = scene.prototypes[instance.prototype]
if (prototype === undefined) {
continue
}
const radius = prototype.radius * instance.scale
if (
Frustum.intersectsAabb(
frustum,
instance.x - radius,
instance.y + prototype.minY * instance.scale,
instance.z - radius,
instance.x + radius,
instance.y + prototype.maxY * instance.scale,
instance.z + radius,
)
) {
visible.push(instance)
}
}
return visible
}
export function renderBand(
framebuffer: Frame,
scene: RenderScene,
camera: Camera,
viewProjection: Mat4,
visible: number[],
instances: RenderInstance[],
config: RenderConfig,
skyStep: number,
time: number,
y0: number,
y1: number,
): void {
Sky.render(framebuffer, camera, scene.sky, time, skyStep, y0, y1)
drawGroups(framebuffer, scene.staticGroups, viewProjection, config, y0, y1)
for (const index of visible) {
const chunk = scene.chunks[index]
const groups = Chunk.isFar(chunk, camera.position, config.lodDistance)
? chunk.far
: chunk.near
drawGroups(framebuffer, groups, viewProjection, config, y0, y1)
}
for (const billboard of scene.billboards) {
const sprite = {
position: billboard.position,
size: billboard.size,
texture: billboard.material.texture,
}
Rasterizer.draw(
framebuffer,
Sprite.billboard(sprite, camera),
billboard.material.texture,
viewProjection,
config,
billboard.material.cull,
y0,
y1,
)
}
for (const instance of instances) {
const prototype = scene.prototypes[instance.prototype]
if (prototype === undefined) {
continue
}
const modelViewProjection = Matrix.multiply(
viewProjection,
Matrix.compose(
instance.x,
instance.y,
instance.z,
instance.heading,
instance.scale,
),
)
drawGroups(
framebuffer,
prototype.groups,
modelViewProjection,
config,
y0,
y1,
)
}
Framebuffer.quantize(framebuffer, config, y0, y1)
}
export function triangleCount(
scene: RenderScene,
visible: number[],
instances: RenderInstance[],
eye: Vec3,
lodDistance: number,
): number {
let indices = groupIndices(scene.staticGroups) + scene.billboards.length * 6
for (const index of visible) {
const chunk = scene.chunks[index]
indices += groupIndices(
Chunk.isFar(chunk, eye, lodDistance) ? chunk.far : chunk.near,
)
}
for (const instance of instances) {
const prototype = scene.prototypes[instance.prototype]
if (prototype !== undefined) {
indices += groupIndices(prototype.groups)
}
}
return (indices / 3) | 0
}
function drawGroups(
framebuffer: Frame,
groups: readonly DrawGroup[],
matrix: Mat4,
config: RenderConfig,
y0: number,
y1: number,
): void {
for (const group of groups) {
Rasterizer.draw(
framebuffer,
group.mesh,
group.material.texture,
matrix,
config,
group.material.cull,
y0,
y1,
)
}
}
function groupIndices(groups: readonly DrawGroup[]): number {
let count = 0
for (const group of groups) {
count += group.mesh.indices.length
}
return count
}
}