feat: 1995
This commit is contained in:
commit
fb89263930
69 changed files with 3359 additions and 0 deletions
72
engine/render/Texture.ts
Normal file
72
engine/render/Texture.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { Color } from "./Color"
|
||||
|
||||
/** A square-or-rectangular image of packed RGBA texels, row-major. */
|
||||
export type Texture = {
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
readonly data: Uint32Array
|
||||
}
|
||||
|
||||
export namespace Texture {
|
||||
/** Generate a `size`x`size` checkerboard of `cells` squares per axis, using
|
||||
* colors `a` (top-left) and `b`. A stand-in until real textures load. */
|
||||
export function checker(size: number, cells: number, a: Color, b: Color): Texture {
|
||||
const data = new Uint32Array(size * size)
|
||||
const cell = size / cells
|
||||
for (let y = 0; y < size; y++) {
|
||||
for (let x = 0; x < size; x++) {
|
||||
const on = (Math.floor(x / cell) + Math.floor(y / cell)) % 2 === 0
|
||||
data[y * size + x] = on ? a : b
|
||||
}
|
||||
}
|
||||
return { width: size, height: size, data }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample a texel. Coordinates wrap (tile) outside 0..1. `nearest` point-
|
||||
* samples for crunchy PS1 texels; `linear` bilinearly blends the four
|
||||
* neighbors for a smooth (non-period) result.
|
||||
*
|
||||
* Note there are no mipmaps: when a textured surface is minified in the
|
||||
* distance, many texels fall inside one pixel and point sampling picks an
|
||||
* essentially random one, so the pattern aliases into a crawling moire as the
|
||||
* camera moves. That shimmer is itself part of the PS1 look here; the usual
|
||||
* cure (mipmaps) is a deliberate future step, not a bug.
|
||||
*/
|
||||
export function sample(tex: Texture, u: number, v: number, filter: "nearest" | "linear"): Color {
|
||||
return filter === "linear" ? bilinear(tex, u, v) : nearest(tex, u, v)
|
||||
}
|
||||
|
||||
function nearest(tex: Texture, u: number, v: number): Color {
|
||||
const x = wrap(Math.floor(frac(u) * tex.width), tex.width)
|
||||
const y = wrap(Math.floor(frac(v) * tex.height), tex.height)
|
||||
return tex.data[y * tex.width + x]
|
||||
}
|
||||
|
||||
function bilinear(tex: Texture, u: number, v: number): Color {
|
||||
// -0.5 aligns the sample grid to texel centers before blending.
|
||||
const fx = frac(u) * tex.width - 0.5
|
||||
const fy = frac(v) * tex.height - 0.5
|
||||
const x0 = Math.floor(fx)
|
||||
const y0 = Math.floor(fy)
|
||||
const tx = fx - x0
|
||||
const ty = fy - y0
|
||||
const top = Color.lerp(texel(tex, x0, y0), texel(tex, x0 + 1, y0), tx)
|
||||
const bottom = Color.lerp(texel(tex, x0, y0 + 1), texel(tex, x0 + 1, y0 + 1), tx)
|
||||
return Color.lerp(top, bottom, ty)
|
||||
}
|
||||
|
||||
function texel(tex: Texture, x: number, y: number): Color {
|
||||
return tex.data[wrap(y, tex.height) * tex.width + wrap(x, tex.width)]
|
||||
}
|
||||
|
||||
/** Fractional part in 0..1 (handles negatives), for uv tiling. */
|
||||
function frac(n: number): number {
|
||||
return n - Math.floor(n)
|
||||
}
|
||||
|
||||
/** Wrap an index into 0..size-1, staying non-negative for negative inputs. */
|
||||
function wrap(n: number, size: number): number {
|
||||
return ((n % size) + size) % size
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue