feat: evolve portfolio into an authored midnight grand tour

This commit is contained in:
2026-09-05 03:55:18 +03:00
parent 642f5b583d
commit c9ac36e249
41 changed files with 812 additions and 65 deletions

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import grandTourerUrl from '@/assets/nightdrive-grand-tourer.glb?url';
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-evolution.glb?url';
import type { SceneResourceBag } from '@/scene/resources';
import type { SceneDetailMode } from '@/scene/types';
@@ -55,7 +55,7 @@ export async function loadGrandTourer(signal: AbortSignal): Promise<AuthoredCarG
if (signal.aborted) throw new DOMException('Scene build aborted', 'AbortError');
const gltf = await new GLTFLoader().parseAsync(bytes, GRAND_TOURER_URL);
if (signal.aborted) throw new DOMException('Scene build aborted', 'AbortError');
const authored = gltf.scene.getObjectByName('chase-car') ?? gltf.scene;
const authored = gltf.scene.getObjectByName('evolution-chase-car') ?? gltf.scene.getObjectByName('chase-car') ?? gltf.scene;
authored.removeFromParent();
return authored as AuthoredCarGroup;
}
@@ -253,6 +253,8 @@ export function createCar(
vec2 centered = (vGlowUv - 0.5) * vec2(1.55, 1.0);
float glow = 1.0 - smoothstep(0.12, 0.72, length(centered));
gl_FragColor = vec4(uColor, glow * uOpacity);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
transparent: true,
@@ -355,7 +357,9 @@ function createAuthoredCar(
const tailMaterials: AuthoredMaterial[] = [];
const cyanMaterials: AuthoredMaterial[] = [];
authoredModel.traverse((object) => {
if (object.name.endsWith('.001')) object.name = object.name.slice(0, -4);
if (object.name.startsWith('evolution-')) object.name = object.name.slice('evolution-'.length);
// Three removes Blender's suffix dot when sanitizing imported node names.
object.name = object.name.replace(/\.?\d{3}$/u, '');
if (!object.isMesh || !object.geometry || !object.material) return;
resources.own('geometry', object.geometry);
const materials = Array.isArray(object.material) ? object.material : [object.material];
@@ -367,7 +371,7 @@ function createAuthoredCar(
&& (object.name === 'car-body' || object.name === 'body-haunches')
) {
material.emissive?.setHex(0x0b2738);
material.emissiveIntensity = 0.58;
material.emissiveIntensity = 0.26;
}
if (material.isMeshStandardMaterial && object.name === 'rear-glass') {
material.emissive?.setHex(0x041725);
@@ -405,6 +409,8 @@ function createAuthoredCar(
vec2 centered = (vGlowUv - 0.5) * vec2(1.7, 1.0);
float glow = 1.0 - smoothstep(0.08, 0.74, length(centered));
gl_FragColor = vec4(uColor * 1.35, glow * uOpacity);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
transparent: true,
@@ -464,7 +470,7 @@ function createAuthoredCar(
group.position.y = baseY - squat + vibration;
group.rotation.x = -squat * 0.035;
group.rotation.z = Math.sin(elapsed * 0.8) * safeSpeed * 0.0018;
for (const material of tailMaterials) material.emissiveIntensity = 4.4 + response * 3.2;
for (const material of tailMaterials) material.emissiveIntensity = 2.2 + response * 1.2;
for (const material of cyanMaterials) material.emissiveIntensity = 2.2 + response * 1.6;
underglowUniforms.uOpacity.value = 0.09 + safeSpeed * 0.04 + response * 0.075;
contact.uniforms.uOpacity.value = 0.21 + response * 0.055;
@@ -497,6 +503,8 @@ function createContactShadow(
vec2 centered = (vShadowUv - 0.5) * vec2(1.65, 1.0);
float shadow = 1.0 - smoothstep(0.16, 0.72, length(centered));
gl_FragColor = vec4(vec3(0.002, 0.004, 0.008), shadow * uOpacity);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
transparent: true,