perf: faster still
This commit is contained in:
parent
ef5029da1d
commit
67cd54fe33
8 changed files with 280 additions and 203 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import type { Vec3 } from "../math/Vec3"
|
||||
import type { Mesh } from "./Mesh"
|
||||
import { STRIDE, type Mesh } from "./Mesh"
|
||||
|
||||
const TAU = Math.PI * 2
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export namespace Boulder {
|
|||
const cy = boulder.position.y + sy * 0.55
|
||||
|
||||
const jitter = jitterGrid(seg, rings, rand)
|
||||
const start = mesh.vertices.length
|
||||
const start = mesh.verts.length / STRIDE
|
||||
for (let ir = 0; ir <= rings; ir++) {
|
||||
const phi = (ir / rings) * Math.PI
|
||||
const cyv = Math.cos(phi)
|
||||
|
|
@ -45,14 +45,13 @@ export namespace Boulder {
|
|||
for (let is = 0; is <= seg; is++) {
|
||||
const theta = (is / seg) * TAU
|
||||
const j = jitter[ir][is]
|
||||
mesh.vertices.push({
|
||||
pos: {
|
||||
x: cx + crv * Math.cos(theta) * sx * j,
|
||||
y: cy + cyv * sy * j,
|
||||
z: cz + crv * Math.sin(theta) * sz * j,
|
||||
},
|
||||
uv: { x: (is / seg) * 1.5, y: (ir / rings) * 1.5 },
|
||||
})
|
||||
mesh.verts.push(
|
||||
cx + crv * Math.cos(theta) * sx * j,
|
||||
cy + cyv * sy * j,
|
||||
cz + crv * Math.sin(theta) * sz * j,
|
||||
(is / seg) * 1.5,
|
||||
(ir / rings) * 1.5,
|
||||
)
|
||||
}
|
||||
}
|
||||
const row = seg + 1
|
||||
|
|
|
|||
|
|
@ -1,11 +1,26 @@
|
|||
import type { Vec2 } from "../math/Vec2"
|
||||
import type { Vec3 } from "../math/Vec3"
|
||||
/** Floats per vertex in `Mesh.verts`: position x, y, z then texture u, v. */
|
||||
export const STRIDE = 5
|
||||
|
||||
/** One mesh vertex: a world-space position and its texture coordinate. uv is in
|
||||
* tile units, not 0..1, so values >1 repeat the texture (see Texture.sample). */
|
||||
export type Vertex = { pos: Vec3; uv: Vec2 }
|
||||
/**
|
||||
* Indexed triangle mesh, stored flat for speed. `verts` is a packed run of
|
||||
* `STRIDE` floats per vertex (x, y, z, u, v) instead of an array of nested
|
||||
* `{pos, uv}` objects, so the transform loop reads contiguous numbers with no
|
||||
* pointer chasing or per-vertex allocation. `indices` holds three vertex indices
|
||||
* per triangle (an index `i` addresses `verts[i * STRIDE ..]`); sharing vertices
|
||||
* keeps seams welded and shrinks the data. uv is in tile units, not 0..1, so
|
||||
* values >1 repeat the texture (see Texture.sample). Build with `Mesh.push`.
|
||||
*/
|
||||
export type Mesh = { verts: number[]; indices: number[] }
|
||||
|
||||
/** Indexed triangle mesh in world space. `indices` holds three entries per
|
||||
* triangle, each indexing into `vertices`; sharing vertices between triangles
|
||||
* keeps seams welded and shrinks the data. */
|
||||
export type Mesh = { vertices: Vertex[]; indices: number[] }
|
||||
export namespace Mesh {
|
||||
export function create(): Mesh {
|
||||
return { verts: [], indices: [] }
|
||||
}
|
||||
|
||||
/** Append a vertex, returning its index (for wiring up `indices`). */
|
||||
export function push(mesh: Mesh, x: number, y: number, z: number, u: number, v: number): number {
|
||||
const index = mesh.verts.length / STRIDE
|
||||
mesh.verts.push(x, y, z, u, v)
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,12 +34,9 @@ export namespace Sprite {
|
|||
const lz = p.z - rz * hw
|
||||
const gx = p.x + rx * hw
|
||||
const gz = p.z + rz * hw
|
||||
const vertices = [
|
||||
{ pos: { x: lx, y: y0, z: lz }, uv: { x: 0, y: 1 } },
|
||||
{ pos: { x: gx, y: y0, z: gz }, uv: { x: 1, y: 1 } },
|
||||
{ pos: { x: gx, y: y1, z: gz }, uv: { x: 1, y: 0 } },
|
||||
{ pos: { x: lx, y: y1, z: lz }, uv: { x: 0, y: 0 } },
|
||||
]
|
||||
return { vertices, indices: [0, 1, 2, 0, 2, 3] }
|
||||
// Flat verts (x, y, z, u, v) per corner: bottom-left, bottom-right, top-right,
|
||||
// top-left.
|
||||
const verts = [lx, y0, lz, 0, 1, gx, y0, gz, 1, 1, gx, y1, gz, 1, 0, lx, y1, lz, 0, 0]
|
||||
return { verts, indices: [0, 1, 2, 0, 2, 3] }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Mesh } from "./Mesh"
|
||||
import { STRIDE, type Mesh } from "./Mesh"
|
||||
|
||||
/** A procedural heightfield surrounding the room. It is the single source of
|
||||
* ground height: the outdoor mesh is built from it and the player stands on the
|
||||
|
|
@ -59,15 +59,15 @@ export namespace Terrain {
|
|||
rows: number,
|
||||
uvScale: number,
|
||||
): void {
|
||||
const base = mesh.vertices.length
|
||||
const base = mesh.verts.length / STRIDE
|
||||
const dx = (x1 - x0) / cols
|
||||
const dz = (z1 - z0) / rows
|
||||
const stride = cols + 1
|
||||
const rowLen = cols + 1
|
||||
for (let i = 0; i <= rows; i++) {
|
||||
const z = z0 + i * dz
|
||||
for (let j = 0; j <= cols; j++) {
|
||||
const x = x0 + j * dx
|
||||
mesh.vertices.push({ pos: { x, y: height(t, x, z), z }, uv: { x: x * uvScale, y: z * uvScale } })
|
||||
mesh.verts.push(x, height(t, x, z), z, x * uvScale, z * uvScale)
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < rows; i++) {
|
||||
|
|
@ -77,9 +77,9 @@ export namespace Terrain {
|
|||
if (Math.max(Math.abs(cx), Math.abs(cz)) < t.inner) {
|
||||
continue
|
||||
}
|
||||
const p = base + i * stride + j
|
||||
const p = base + i * rowLen + j
|
||||
// Wound so the surface faces up/out, matching the backface-cull sign.
|
||||
mesh.indices.push(p, p + stride + 1, p + 1, p, p + stride, p + stride + 1)
|
||||
mesh.indices.push(p, p + rowLen + 1, p + 1, p, p + rowLen, p + rowLen + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Vec3 } from "../math/Vec3"
|
||||
import type { Mesh } from "./Mesh"
|
||||
import { STRIDE, type Mesh } from "./Mesh"
|
||||
|
||||
const TAU = Math.PI * 2
|
||||
|
||||
|
|
@ -95,13 +95,15 @@ export namespace Tree {
|
|||
const axis = Vec3.normalize(Vec3.sub(b, a))
|
||||
const [u, v] = basis(axis)
|
||||
const len = Vec3.length(Vec3.sub(b, a))
|
||||
const start = mesh.vertices.length
|
||||
const start = mesh.verts.length / STRIDE
|
||||
for (let i = 0; i <= sides; i++) {
|
||||
const angle = (i / sides) * TAU
|
||||
const dir = Vec3.add(Vec3.scale(u, Math.cos(angle)), Vec3.scale(v, Math.sin(angle)))
|
||||
const dx = u.x * Math.cos(angle) + v.x * Math.sin(angle)
|
||||
const dy = u.y * Math.cos(angle) + v.y * Math.sin(angle)
|
||||
const dz = u.z * Math.cos(angle) + v.z * Math.sin(angle)
|
||||
const s = i / sides
|
||||
mesh.vertices.push({ pos: Vec3.add(a, Vec3.scale(dir, ra)), uv: { x: s * 1.5, y: 0 } })
|
||||
mesh.vertices.push({ pos: Vec3.add(b, Vec3.scale(dir, rb)), uv: { x: s * 1.5, y: len * 0.5 } })
|
||||
mesh.verts.push(a.x + dx * ra, a.y + dy * ra, a.z + dz * ra, s * 1.5, 0)
|
||||
mesh.verts.push(b.x + dx * rb, b.y + dy * rb, b.z + dz * rb, s * 1.5, len * 0.5)
|
||||
}
|
||||
for (let i = 0; i < sides; i++) {
|
||||
const p = start + i * 2
|
||||
|
|
@ -111,17 +113,15 @@ export namespace Tree {
|
|||
|
||||
/** A cone standing on a base ring, apex `height` above it (one spruce tier). */
|
||||
function cone(mesh: Mesh, base: Vec3, height: number, radius: number, sides: number): void {
|
||||
const start = mesh.vertices.length
|
||||
mesh.vertices.push({ pos: { x: base.x, y: base.y + height, z: base.z }, uv: { x: 0.5, y: 0 } })
|
||||
const start = mesh.verts.length / STRIDE
|
||||
mesh.verts.push(base.x, base.y + height, base.z, 0.5, 0)
|
||||
for (let i = 0; i <= sides; i++) {
|
||||
const angle = (i / sides) * TAU
|
||||
mesh.vertices.push({
|
||||
pos: { x: base.x + Math.cos(angle) * radius, y: base.y, z: base.z + Math.sin(angle) * radius },
|
||||
uv: { x: (i / sides) * 2, y: 1 },
|
||||
})
|
||||
mesh.verts.push(base.x + Math.cos(angle) * radius, base.y, base.z + Math.sin(angle) * radius, (i / sides) * 2, 1)
|
||||
}
|
||||
for (let i = 0; i < sides; i++) {
|
||||
mesh.indices.push(start, start + 1 + i, start + 2 + i)
|
||||
// Wound so the outer surface faces out, matching the backface-cull sign.
|
||||
mesh.indices.push(start, start + 2 + i, start + 1 + i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ export namespace Tree {
|
|||
function blob(mesh: Mesh, center: Vec3, radius: number, rand: () => number): void {
|
||||
const seg = 5
|
||||
const rings = 3
|
||||
const start = mesh.vertices.length
|
||||
const start = mesh.verts.length / STRIDE
|
||||
for (let r = 0; r <= rings; r++) {
|
||||
const phi = (r / rings) * Math.PI
|
||||
const cy = Math.cos(phi)
|
||||
|
|
@ -138,14 +138,13 @@ export namespace Tree {
|
|||
const scale = radius * (0.85 + rand() * 0.3)
|
||||
for (let s = 0; s <= seg; s++) {
|
||||
const theta = (s / seg) * TAU
|
||||
mesh.vertices.push({
|
||||
pos: {
|
||||
x: center.x + cr * Math.cos(theta) * scale,
|
||||
y: center.y + cy * scale,
|
||||
z: center.z + cr * Math.sin(theta) * scale,
|
||||
},
|
||||
uv: { x: (s / seg) * 2, y: (r / rings) * 2 },
|
||||
})
|
||||
mesh.verts.push(
|
||||
center.x + cr * Math.cos(theta) * scale,
|
||||
center.y + cy * scale,
|
||||
center.z + cr * Math.sin(theta) * scale,
|
||||
(s / seg) * 2,
|
||||
(r / rings) * 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
const row = seg + 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue