Ride reflections: quarter-turn env mapping + body patch panels

Mirrored env azimuth rotated 90 degrees in both sky and paint mappings (shared constant): fold lines sit fore/aft instead of mirroring the world at side views. Same render also completes the body patch-up: front side panels, rear quarters, floor pan, taller skirts sealing the doors, lamps/grille seated into the bumper, mirrors rooted in the wall. Verified on the 10x3 grid: reflections track the viz per cell, no seam, no holes at card scale.
This commit is contained in:
Dejvino
2026-09-21 19:09:18 +02:00
parent 3a3022327c
commit cf9b4c111a
2 changed files with 37 additions and 16 deletions
+31 -13
View File
@@ -45,9 +45,9 @@ function vizEnvMaterial(mat, instance, strength) {
vec3 vDir = normalize( vViewPosition );
vec3 rDir = reflect( vDir, normalize( normal ) );
// Mirrored azimuth shared with the sky dome (see sky.js):
// folds ±π onto the same texel so neither surface shows a seam.
float sU = atan( rDir.z, rDir.x ) * 0.15915494 + 0.5;
sU = 1.0 - abs( 1.0 - 2.0 * sU );
// quarter-turn offset puts the fold lines fore and aft.
float sU = atan( rDir.z, rDir.x ) * 0.15915494 + 0.5 + 0.25;
sU = 1.0 - abs( 1.0 - 2.0 * fract( sU ) );
vec2 sUv = vec2(
sU,
asin( clamp( rDir.y, -1.0, 1.0 ) ) * 0.31830988 + 0.5 );
@@ -578,11 +578,27 @@ export const ride = {
bumperF.position.set(0, -0.35, -2.05);
const bumperR = add(new T.Mesh(new T.BoxGeometry(1.8, 0.22, 0.3), trimMat));
bumperR.position.set(0, -0.35, 1.62);
// Side skirts between the wheels.
// Side skirts between the wheels, tall enough to meet the
// door bottoms so no daylight shows under the doors.
for (const side of [-1, 1]) {
const skirt = add(new T.Mesh(new T.BoxGeometry(0.12, 0.14, 2.0), trimMat));
skirt.position.set(side * 0.93, -0.5, -0.15);
const skirt = add(new T.Mesh(new T.BoxGeometry(0.12, 0.5, 2.0), trimMat));
skirt.position.set(side * 0.93, -0.32, -0.15);
}
// Front side panels: fill between hood edge and wheels, where
// the engine bay showed straight through to the ground.
for (const side of [-1, 1]) {
const panel = add(new T.Mesh(new T.BoxGeometry(0.25, 0.35, 1.0), paintMat()));
panel.position.set(side * 0.825, -0.08, -1.5);
}
// Rear quarter panels between doors and rear arches.
for (const side of [-1, 1]) {
const quarter = add(new T.Mesh(new T.BoxGeometry(0.25, 0.35, 0.9), paintMat()));
quarter.position.set(side * 0.825, -0.08, 1.1);
}
// Floor pan: closes the see-through under the cabin between
// the wheels. Overlaps skirts, bumpers and doors all round.
const floorPan = add(new T.Mesh(new T.BoxGeometry(1.78, 0.16, 3.5), trimMat));
floorPan.position.set(0, -0.52, -0.25);
// Fenders: half-torus arches over each wheel. Torus arc 0..π
// spans the upper half in XY; yawed 90° it arches across the
// wheel in the rolling plane.
@@ -592,9 +608,10 @@ export const ride = {
fender.rotation.y = Math.PI / 2;
fender.position.set(wx, -0.62, wz);
}
// Grille + headlamps proud of the nose so no face z-fights.
// Grille + headlamps seated INTO the bumper face (not floating
// above it): both overlap the bumper top edge.
const grille = add(new T.Mesh(new T.BoxGeometry(0.9, 0.16, 0.06), trimMat));
grille.position.set(0, -0.12, -2.21);
grille.position.set(0, -0.2, -2.21);
const lampMat = new T.MeshStandardMaterial({
color: new T.Color(0.1, 0.1, 0.12),
emissive: new T.Color(0.75, 0.82, 1.0),
@@ -603,7 +620,7 @@ export const ride = {
});
for (const side of [-1, 1]) {
const lamp = add(new T.Mesh(new T.CircleGeometry(0.09, 16), lampMat));
lamp.position.set(side * 0.55, -0.05, -2.22);
lamp.position.set(side * 0.55, -0.16, -2.22);
lamp.rotation.y = Math.PI;
}
// Taillight bar set into the bumper's rear face.
@@ -616,12 +633,13 @@ export const ride = {
roughness: 0.4,
})));
tail.position.set(0, -0.28, 1.78);
// Mirrors on stalks at the cabin front corners.
// Mirrors on stalks at the cabin front corners, stalks rooted
// in the cabin wall (not floating beside it).
for (const side of [-1, 1]) {
const stalk = add(new T.Mesh(new T.BoxGeometry(0.04, 0.12, 0.04), trimMat));
stalk.position.set(side * 0.9, 0.72, -0.95);
const stalk = add(new T.Mesh(new T.BoxGeometry(0.06, 0.12, 0.04), trimMat));
stalk.position.set(side * 0.88, 0.72, -0.95);
const mirror = add(new T.Mesh(new T.BoxGeometry(0.14, 0.09, 0.07), paintMat()));
mirror.position.set(side * 0.94, 0.8, -0.95);
mirror.position.set(side * 0.92, 0.8, -0.95);
}
// Rear spoiler: blade on two mounts over the trunk.
for (const side of [-1, 1]) {
+6 -3
View File
@@ -56,10 +56,13 @@ export function buildSky(THREE, { radius = 90 } = {}) {
// Mirrored spherical projection shared with the ride's paint
// (see vizEnvMaterial): direction to sphere UVs, with the azimuth
// folded into a triangle so the ±π wrap lands on the same texel
// instead of a visible seam.
// instead of a visible seam. Offset a quarter turn (shared
// constant with ride.js): the fold lines sit fore and aft, where
// a mirrored join never faces the chase camera head-on — on the
// flanks it mirrored the world at the viewer.
vec2 skyVizUv(vec3 dir) {
float u = atan(dir.z, dir.x) * 0.15915494 + 0.5;
u = 1.0 - abs(1.0 - 2.0 * u);
float u = atan(dir.z, dir.x) * 0.15915494 + 0.5 + 0.25;
u = 1.0 - abs(1.0 - 2.0 * fract(u));
float v = asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988 + 0.5;
return vec2(u, v);
}