225 lines
5.7 KiB
TypeScript
225 lines
5.7 KiB
TypeScript
|
|
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
|
||
|
|
}
|
||
|
|
}
|