refactor: move world concepts into engine
This commit is contained in:
parent
eeedcb8e48
commit
2d15c7ab8d
52 changed files with 3298 additions and 1558 deletions
224
engine/render/RenderScene.ts
Normal file
224
engine/render/RenderScene.ts
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue