SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 1ba42f1ca1d6…
- Author-recorded commit
- baecad10b1cd…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// An approximation of Shockwave 3D's #standard shader: Lambert lighting from
// the level's directional + ambient lights, base texture (or diffuse colour),
// then texture layer 2 multiplied in (lightMapManager's lightmaps).
import { Color, DoubleSide, Matrix3, ShaderMaterial, type Texture, Vector3 } from 'three';
export interface DirectorLight {
direction: Vector3; // direction the light travels (node -Z)
color: Color;
}
export interface LightRig {
ambient: Color;
directional: DirectorLight[];
}
/**
* The level's authored lights (W3D 0x50 resources + 0x71 nodes): direction is
* each node's -Z axis. The resources carry an extra intensity value (0.167,
* Direct05 0.5) that Director's light objects do not expose; colours are white.
*/
export function levelLights(useIntensity: boolean): LightRig {
const i = (v: number) => (useIntensity ? v : 1);
const white = (v: number) => new Color(i(v), i(v), i(v));
return {
ambient: new Color(0.078, 0.11, 0.141),
directional: [
{ direction: new Vector3(0, -0.771, -0.637), color: white(0.167) }, // Direct02
{ direction: new Vector3(0.891, 0.363, -0.273), color: white(0.167) }, // Direct04
{ direction: new Vector3(0, 0, 1), color: white(0.5) }, // Direct05 (from below)
{ direction: new Vector3(-0.926, -0.176, -0.334), color: white(0.167) }, // Direct06
{ direction: new Vector3(-0.915, 0.207, -0.346), color: white(0.167) }, // Direct07
{ direction: new Vector3(0.94, -0.185, -0.288), color: white(0.167) }, // Direct08
],
};
}
/**
* Whether the W3D light intensities apply. Compared side by side with the
* original under Wine, full-strength white lights match its floor and walls.
*/
export const LIGHT_SETTINGS = { useIntensity: false };
/** arrowcam's rootNode is the arrow itself, so no scene light reaches it. */
export const UNLIT: LightRig = { ambient: new Color(0, 0, 0), directional: [] };
export let LEVEL_LIGHTS: LightRig = levelLights(LIGHT_SETTINGS.useIntensity);
export interface StandardShaderOptions {
diffuse: Color;
map: Texture | null;
lightMap: Texture | null;
opacity?: number;
alphaTest?: number;
lights?: LightRig;
/** Scale applied to the lightmap multiply (Director multiplies 1:1). */
lightMapGain?: number;
emissive?: Color;
/** Material ambient reflectance (times the scene ambient light). */
ambient?: Color;
doubleSided?: boolean;
/** Light comes from the geometry's baked `bake` attribute instead of the rig. */
baked?: boolean;
}
const vertexShader = /* glsl */ `
attribute vec2 uv1;
attribute vec4 bake;
uniform mat3 mapTransform;
varying vec2 vUv;
varying vec2 vUv1;
varying vec3 vNormal;
varying vec3 vBake;
void main() {
vUv = (mapTransform * vec3(uv, 1.0)).xy;
vUv1 = uv1;
vBake = bake.rgb;
vNormal = normalize(mat3(modelMatrix) * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = /* glsl */ `
uniform vec3 diffuse;
uniform vec3 emissive;
uniform vec3 ambient;
uniform float opacity;
uniform float alphaTest;
uniform bool hasMap;
uniform bool hasLightMap;
uniform bool baked;
uniform sampler2D map;
uniform sampler2D lightMap;
uniform float lightMapGain;
uniform vec3 ambientLight;
uniform vec3 lightDir[6];
uniform vec3 lightColor[6];
varying vec2 vUv;
varying vec2 vUv1;
varying vec3 vNormal;
varying vec3 vBake;
void main() {
vec4 base = hasMap ? texture2D(map, vUv) : vec4(1.0);
if (base.a < alphaTest) discard;
vec3 n = normalize(vNormal) * (gl_FrontFacing ? 1.0 : -1.0);
vec3 lit = ambient * ambientLight;
for (int i = 0; i < 6; i++) lit += lightColor[i] * max(dot(n, -lightDir[i]), 0.0);
vec3 color = base.rgb * diffuse * (baked ? vBake : min(lit, vec3(1.0))) + emissive;
if (hasLightMap) color *= texture2D(lightMap, vUv1).rgb * lightMapGain;
gl_FragColor = vec4(color, base.a * opacity);
#include <colorspace_fragment>
}
`;
export function standardShader(o: StandardShaderOptions): ShaderMaterial {
const lights = o.lights ?? LEVEL_LIGHTS;
const dirs = lights.directional.map((l) => l.direction.clone().normalize());
const cols = lights.directional.map((l) => new Vector3(l.color.r, l.color.g, l.color.b));
while (dirs.length < 6) {
dirs.push(new Vector3(0, 0, -1));
cols.push(new Vector3());
}
const material = new ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
diffuse: { value: o.diffuse },
emissive: { value: o.emissive ?? new Color(0, 0, 0) },
ambient: { value: o.ambient ?? new Color(1, 1, 1) },
opacity: { value: o.opacity ?? 1 },
alphaTest: { value: o.alphaTest ?? 0 },
hasMap: { value: !!o.map },
hasLightMap: { value: !!o.lightMap },
baked: { value: !!o.baked },
map: { value: o.map },
// KHR_texture_transform tiling from editor levels; identity for everything else.
mapTransform: { value: o.map ? o.map.matrix : new Matrix3() },
lightMap: { value: o.lightMap },
lightMapGain: { value: o.lightMapGain ?? 1 },
ambientLight: { value: new Vector3(lights.ambient.r, lights.ambient.g, lights.ambient.b) },
lightDir: { value: dirs.slice(0, 6) },
lightColor: { value: cols.slice(0, 6) },
},
transparent: (o.opacity ?? 1) < 1,
side: o.doubleSided === false ? undefined : DoubleSide,
});
return material;
}
