feat: sculpt portfolio into an immersive midnight expressway

Author physical road furniture, layered terrain and atmosphere, projected illuminated route signs, and detailed Blender coachwork. Refine camera mass, wheel contact, chapter composition, and compact reading space while preserving approved content and audio contracts.

Include isolated Blender source, before/after and hardware Chrome motion evidence, preservation hashes, validated preview checks, and documented inherited unit failures.
This commit is contained in:
2026-09-05 05:10:17 +03:00
parent c9ac36e249
commit b188cac7ed
74 changed files with 3168 additions and 1223 deletions

View File

@@ -1,19 +1,29 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-evolution.glb?url';
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-expressway.glb?url';
import type { SceneResourceBag } from '@/scene/resources';
import type { SceneDetailMode } from '@/scene/types';
export interface CarWorld {
group: CarGroup;
setDetail(detail: SceneDetailMode): void;
update(speed: number, acceleration: number, elapsedSeconds: number): void;
update(
speed: number,
acceleration: number,
elapsedSeconds: number,
distance?: number,
): void;
}
interface CarGroup {
name: string;
children: unknown[];
position: { x: number; y: number; z: number; set(x: number, y: number, z: number): void };
position: {
x: number;
y: number;
z: number;
set(x: number, y: number, z: number): void;
};
rotation: { x: number; y: number; z: number };
scale: { set(x: number, y: number, z: number): void };
visible: boolean;
@@ -35,6 +45,9 @@ interface AuthoredObject {
isMesh?: boolean;
geometry?: { dispose(): void };
material?: AuthoredMaterial | AuthoredMaterial[];
parent?: { name: string };
position: { y: number };
rotation: { x: number; z: number };
}
interface AuthoredCarGroup extends CarGroup {
@@ -46,16 +59,23 @@ const FULL_BASE_Y = 0.58;
const REDUCED_BASE_Y = 0.42;
const GRAND_TOURER_URL = grandTourerUrl;
export async function loadGrandTourer(signal: AbortSignal): Promise<AuthoredCarGroup> {
export async function loadGrandTourer(
signal: AbortSignal,
): Promise<AuthoredCarGroup> {
const response = await fetch(GRAND_TOURER_URL, { signal });
if (!response.ok) {
throw new Error(`Grand tourer asset failed to load (${response.status}).`);
}
const bytes = await response.arrayBuffer();
if (signal.aborted) throw new DOMException('Scene build aborted', 'AbortError');
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('evolution-chase-car') ?? gltf.scene.getObjectByName('chase-car') ?? gltf.scene;
if (signal.aborted)
throw new DOMException('Scene build aborted', 'AbortError');
const authored =
gltf.scene.getObjectByName('evolution-chase-car') ??
gltf.scene.getObjectByName('chase-car') ??
gltf.scene;
authored.removeFromParent();
return authored as AuthoredCarGroup;
}
@@ -71,85 +91,118 @@ export function createCar(
group.position.set(0, baseY, 0.72);
group.scale.set(1.1, 0.82, 0.98);
const bodyMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x304e68,
emissive: 0x091725,
emissiveIntensity: 0.42,
roughness: 0.36,
metalness: 0.68,
}));
const shoulderMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x365b77,
emissive: 0x0a1b2a,
emissiveIntensity: 0.36,
roughness: 0.3,
metalness: 0.74,
}));
const lowerBodyMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x111c29,
roughness: 0.48,
metalness: 0.54,
}));
const glassMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x071522,
emissive: 0x061522,
emissiveIntensity: 0.34,
roughness: 0.12,
metalness: 0.42,
transparent: true,
opacity: 0.92,
}));
const fasciaMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x080d15,
roughness: 0.52,
metalness: 0.42,
}));
const tailMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x8f0b2d,
emissive: 0xff174f,
emissiveIntensity: 1.25,
roughness: 0.26,
metalness: 0.18,
toneMapped: true,
}));
const cyanMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x2b9bab,
emissive: 0x44dceb,
emissiveIntensity: 0.7,
roughness: 0.34,
metalness: 0.32,
toneMapped: true,
}));
const tireMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x020407,
roughness: 0.94,
metalness: 0.04,
}));
const hubMaterial = resources.own('material', new THREE.MeshStandardMaterial({
color: 0x354451,
roughness: 0.42,
metalness: 0.78,
}));
const bodyMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x304e68,
emissive: 0x091725,
emissiveIntensity: 0.42,
roughness: 0.36,
metalness: 0.68,
}),
);
const shoulderMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x365b77,
emissive: 0x0a1b2a,
emissiveIntensity: 0.36,
roughness: 0.3,
metalness: 0.74,
}),
);
const lowerBodyMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x111c29,
roughness: 0.48,
metalness: 0.54,
}),
);
const glassMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x071522,
emissive: 0x061522,
emissiveIntensity: 0.34,
roughness: 0.12,
metalness: 0.42,
transparent: true,
opacity: 0.92,
}),
);
const fasciaMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x080d15,
roughness: 0.52,
metalness: 0.42,
}),
);
const tailMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x8f0b2d,
emissive: 0xff174f,
emissiveIntensity: 1.25,
roughness: 0.26,
metalness: 0.18,
toneMapped: true,
}),
);
const cyanMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x2b9bab,
emissive: 0x44dceb,
emissiveIntensity: 0.7,
roughness: 0.34,
metalness: 0.32,
toneMapped: true,
}),
);
const tireMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x020407,
roughness: 0.94,
metalness: 0.04,
}),
);
const hubMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x354451,
roughness: 0.42,
metalness: 0.78,
}),
);
const rearProfile = createRearProfile();
const silhouetteGeometry = resources.own('geometry', new THREE.ExtrudeGeometry(rearProfile, {
depth: 5.8,
steps: 1,
bevelEnabled: true,
bevelSegments: 1,
bevelSize: 0.11,
bevelThickness: 0.1,
}));
const silhouetteGeometry = resources.own(
'geometry',
new THREE.ExtrudeGeometry(rearProfile, {
depth: 5.8,
steps: 1,
bevelEnabled: true,
bevelSegments: 1,
bevelSize: 0.11,
bevelThickness: 0.1,
}),
);
silhouetteGeometry.translate(0, 0, -2.9);
silhouetteGeometry.computeVertexNormals();
const silhouette = new THREE.Mesh(silhouetteGeometry, bodyMaterial);
silhouette.name = 'rear-silhouette';
group.add(silhouette);
const rearFaceMaterial = resources.own('material', new THREE.MeshBasicMaterial({
color: 0x1b3246,
toneMapped: true,
}));
const rearFaceMaterial = resources.own(
'material',
new THREE.MeshBasicMaterial({
color: 0x1b3246,
toneMapped: true,
}),
);
const rearFace = new THREE.Mesh(
resources.own('geometry', new THREE.ShapeGeometry(rearProfile)),
rearFaceMaterial,
@@ -184,12 +237,15 @@ export function createCar(
rearGlassProfile.lineTo(1.15, 1.41);
rearGlassProfile.lineTo(1.58, 0.8);
rearGlassProfile.closePath();
const rearGlassMaterial = resources.own('material', new THREE.MeshBasicMaterial({
color: 0x0b2537,
transparent: true,
opacity: 0.94,
toneMapped: true,
}));
const rearGlassMaterial = resources.own(
'material',
new THREE.MeshBasicMaterial({
color: 0x0b2537,
transparent: true,
opacity: 0.94,
toneMapped: true,
}),
);
const rearGlass = new THREE.Mesh(
resources.own('geometry', new THREE.ShapeGeometry(rearGlassProfile)),
rearGlassMaterial,
@@ -198,13 +254,19 @@ export function createCar(
rearGlass.position.z = 1.44;
group.add(rearGlass);
const fasciaGeometry = resources.own('geometry', new THREE.BoxGeometry(4.84, 0.56, 0.16));
const fasciaGeometry = resources.own(
'geometry',
new THREE.BoxGeometry(4.84, 0.56, 0.16),
);
const fascia = new THREE.Mesh(fasciaGeometry, fasciaMaterial);
fascia.name = 'rear-fascia';
fascia.position.set(0, 0.02, 3.07);
group.add(fascia);
const lightGeometry = resources.own('geometry', new THREE.BoxGeometry(4.55, 0.13, 0.09));
const lightGeometry = resources.own(
'geometry',
new THREE.BoxGeometry(4.55, 0.13, 0.09),
);
const lightBar = new THREE.Mesh(lightGeometry, tailMaterial);
lightBar.name = 'rear-light-bar';
lightBar.position.set(0, 0.46, 3.17);
@@ -216,9 +278,18 @@ export function createCar(
diffuser.position.z = 3.08;
group.add(diffuser);
const wheelGeometry = resources.own('geometry', new THREE.CylinderGeometry(0.66, 0.66, 0.5, 24));
const hubGeometry = resources.own('geometry', new THREE.CylinderGeometry(0.31, 0.31, 0.515, 16));
for (const [x, side] of [[-2.38, 'left'], [2.38, 'right']] as const) {
const wheelGeometry = resources.own(
'geometry',
new THREE.CylinderGeometry(0.66, 0.66, 0.5, 24),
);
const hubGeometry = resources.own(
'geometry',
new THREE.CylinderGeometry(0.31, 0.31, 0.515, 16),
);
for (const [x, side] of [
[-2.38, 'left'],
[2.38, 'right'],
] as const) {
const wheel = new THREE.Mesh(wheelGeometry, tireMaterial);
wheel.name = `rear-wheel-${side}`;
wheel.rotation.z = Math.PI / 2;
@@ -235,17 +306,22 @@ export function createCar(
uColor: { value: new THREE.Color(0x49d7e5) },
uOpacity: { value: 0.12 },
};
const underglowGeometry = resources.own('geometry', new THREE.PlaneGeometry(3.9, 4.2));
const underglowMaterial = resources.own('material', new THREE.ShaderMaterial({
uniforms: underglowUniforms,
vertexShader: `
const underglowGeometry = resources.own(
'geometry',
new THREE.PlaneGeometry(3.9, 4.2),
);
const underglowMaterial = resources.own(
'material',
new THREE.ShaderMaterial({
uniforms: underglowUniforms,
vertexShader: `
varying vec2 vGlowUv;
void main() {
vGlowUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
fragmentShader: `
uniform vec3 uColor;
uniform float uOpacity;
varying vec2 vGlowUv;
@@ -257,12 +333,13 @@ export function createCar(
#include <colorspace_fragment>
}
`,
transparent: true,
depthWrite: false,
depthTest: true,
blending: THREE.AdditiveBlending,
toneMapped: true,
}));
transparent: true,
depthWrite: false,
depthTest: true,
blending: THREE.AdditiveBlending,
toneMapped: true,
}),
);
const underglow = new THREE.Mesh(underglowGeometry, underglowMaterial);
underglow.name = 'car-underglow';
underglow.rotation.x = -Math.PI / 2;
@@ -274,18 +351,27 @@ export function createCar(
const secondary = new THREE.Group();
secondary.name = 'secondary-detail';
const trimGeometry = resources.own('geometry', new THREE.BoxGeometry(0.38, 0.075, 0.07));
const trimGeometry = resources.own(
'geometry',
new THREE.BoxGeometry(0.38, 0.075, 0.07),
);
for (const x of [-1.98, 1.98]) {
const trim = new THREE.Mesh(trimGeometry, cyanMaterial);
trim.position.set(x, -0.15, 3.13);
secondary.add(trim);
}
const deckGeometry = resources.own('geometry', new THREE.BoxGeometry(3.25, 0.075, 0.62));
const deckGeometry = resources.own(
'geometry',
new THREE.BoxGeometry(3.25, 0.075, 0.62),
);
const rearDeck = new THREE.Mesh(deckGeometry, shoulderMaterial);
rearDeck.position.set(0, 0.86, 2.15);
rearDeck.rotation.x = -0.08;
secondary.add(rearDeck);
const frontWheelGeometry = resources.own('geometry', new THREE.CylinderGeometry(0.58, 0.58, 0.42, 18));
const frontWheelGeometry = resources.own(
'geometry',
new THREE.CylinderGeometry(0.58, 0.58, 0.42, 18),
);
for (const x of [-2.18, 2.18]) {
const wheel = new THREE.Mesh(frontWheelGeometry, tireMaterial);
wheel.rotation.z = Math.PI / 2;
@@ -321,24 +407,30 @@ export function createCar(
update(speed, acceleration, elapsedSeconds) {
const safeSpeed = clamp01(speed);
const response = clamp01(acceleration);
const elapsed = Number.isFinite(elapsedSeconds) ? Math.max(0, elapsedSeconds) : 0;
const deltaSeconds = previousElapsed === null
? 1 / 60
: THREE.MathUtils.clamp(elapsed - previousElapsed, 0, 0.05);
const elapsed = Number.isFinite(elapsedSeconds)
? Math.max(0, elapsedSeconds)
: 0;
const deltaSeconds =
previousElapsed === null
? 1 / 60
: THREE.MathUtils.clamp(elapsed - previousElapsed, 0, 0.05);
previousElapsed = elapsed;
const targetSquat = response * 0.088;
const springAcceleration = (targetSquat - squat) * 34 - squatVelocity * 10;
const springAcceleration =
(targetSquat - squat) * 34 - squatVelocity * 10;
squatVelocity += springAcceleration * deltaSeconds;
squat += squatVelocity * deltaSeconds;
const vibration = Math.sin(elapsed * (8.2 + safeSpeed * 2.4)) * (0.002 + safeSpeed * 0.003);
const vibration =
Math.sin(elapsed * (8.2 + safeSpeed * 2.4)) *
(0.002 + safeSpeed * 0.003);
group.position.y = baseY - squat + vibration;
group.rotation.x = -squat * 0.045;
group.rotation.z = (
Math.sin(elapsed * 0.78) * safeSpeed * 0.0024
+ Math.sin(elapsed * 1.31) * response * 0.0014
);
group.rotation.z =
Math.sin(elapsed * 0.78) * safeSpeed * 0.0024 +
Math.sin(elapsed * 1.31) * response * 0.0014;
tailMaterial.emissiveIntensity = 1.25 + response * 1.35;
underglowUniforms.uOpacity.value = 0.1 + safeSpeed * 0.055 + response * 0.07;
underglowUniforms.uOpacity.value =
0.1 + safeSpeed * 0.055 + response * 0.07;
contact.uniforms.uOpacity.value = 0.2 + response * 0.06;
},
};
@@ -354,21 +446,34 @@ function createAuthoredCar(
authoredModel.rotation.y = Math.PI;
group.add(authoredModel);
const wheels: Array<{ object: AuthoredObject; radius: number }> = [];
const bodyParts: Array<{ object: AuthoredObject; y: number }> = [];
const tailMaterials: AuthoredMaterial[] = [];
const cyanMaterials: AuthoredMaterial[] = [];
authoredModel.traverse((object) => {
if (object.name.startsWith('evolution-')) object.name = object.name.slice('evolution-'.length);
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 (/^wheel-(front|rear)-(left|right)$/u.test(object.name)) {
wheels.push({
object,
radius: object.name.includes('front') ? 0.42 : 0.46,
});
}
if (!object.isMesh || !object.geometry || !object.material) return;
if (!object.parent?.name.startsWith('wheel-'))
bodyParts.push({ object, y: object.position.y });
resources.own('geometry', object.geometry);
const materials = Array.isArray(object.material) ? object.material : [object.material];
const materials = Array.isArray(object.material)
? object.material
: [object.material];
for (const material of materials) {
resources.own('material', material);
ownMaterialTextures(resources, material);
if (
material.isMeshStandardMaterial
&& (object.name === 'car-body' || object.name === 'body-haunches')
material.isMeshStandardMaterial &&
(object.name === 'car-body' || object.name === 'body-haunches')
) {
material.emissive?.setHex(0x0b2738);
material.emissiveIntensity = 0.26;
@@ -380,7 +485,10 @@ function createAuthoredCar(
if (object.name === 'rear-light-bar' && material.isMeshStandardMaterial) {
tailMaterials.push(material);
}
if (object.name === 'car-underglow-source' && material.isMeshStandardMaterial) {
if (
object.name === 'car-underglow-source' &&
material.isMeshStandardMaterial
) {
cyanMaterials.push(material);
}
}
@@ -392,16 +500,18 @@ function createAuthoredCar(
};
const underglow = new THREE.Mesh(
resources.own('geometry', new THREE.PlaneGeometry(1.72, 3.82)),
resources.own('material', new THREE.ShaderMaterial({
uniforms: underglowUniforms,
vertexShader: `
resources.own(
'material',
new THREE.ShaderMaterial({
uniforms: underglowUniforms,
vertexShader: `
varying vec2 vGlowUv;
void main() {
vGlowUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
fragmentShader: `
uniform vec3 uColor;
uniform float uOpacity;
varying vec2 vGlowUv;
@@ -413,12 +523,13 @@ function createAuthoredCar(
#include <colorspace_fragment>
}
`,
transparent: true,
depthWrite: false,
depthTest: true,
blending: THREE.AdditiveBlending,
toneMapped: true,
})),
transparent: true,
depthWrite: false,
depthTest: true,
blending: THREE.AdditiveBlending,
toneMapped: true,
}),
),
);
underglow.name = 'car-underglow';
underglow.rotation.x = -Math.PI / 2;
@@ -454,25 +565,45 @@ function createAuthoredCar(
detail = nextDetail;
setScale(detail);
},
update(speed, acceleration, elapsedSeconds) {
update(speed, acceleration, elapsedSeconds, distance) {
const safeSpeed = clamp01(speed);
const response = clamp01(acceleration);
const elapsed = Number.isFinite(elapsedSeconds) ? Math.max(0, elapsedSeconds) : 0;
const deltaSeconds = previousElapsed === null
? 1 / 60
: THREE.MathUtils.clamp(elapsed - previousElapsed, 0, 0.05);
const elapsed = Number.isFinite(elapsedSeconds)
? Math.max(0, elapsedSeconds)
: 0;
const deltaSeconds =
previousElapsed === null
? 1 / 60
: THREE.MathUtils.clamp(elapsed - previousElapsed, 0, 0.05);
previousElapsed = elapsed;
const targetSquat = response * 0.04;
const springAcceleration = (targetSquat - squat) * 34 - squatVelocity * 10;
const springAcceleration =
(targetSquat - squat) * 34 - squatVelocity * 10;
squatVelocity += springAcceleration * deltaSeconds;
squat += squatVelocity * deltaSeconds;
const vibration = Math.sin(elapsed * (7.4 + safeSpeed * 2.2)) * (0.0014 + safeSpeed * 0.0022);
group.position.y = baseY - squat + vibration;
group.rotation.x = -squat * 0.035;
const vibration =
Math.sin(elapsed * (7.4 + safeSpeed * 2.2)) *
(0.0014 + safeSpeed * 0.0022);
group.position.y = baseY + vibration * 0.2;
for (const part of bodyParts) {
part.object.position.y = part.y - squat * 0.42;
part.object.rotation.x = -squat * 0.11;
part.object.rotation.z = Math.sin(elapsed * 0.65) * safeSpeed * 0.0015;
}
if (distance !== undefined && Number.isFinite(distance)) {
const scale = detail === 'full' ? 2.78 : 2.32;
for (const wheel of wheels)
wheel.object.rotation.x =
(distance / (wheel.radius * scale)) % (Math.PI * 2);
}
group.rotation.x = 0;
group.rotation.z = Math.sin(elapsed * 0.8) * safeSpeed * 0.0018;
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;
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;
},
};
@@ -487,16 +618,18 @@ function createContactShadow(
const uniforms = {
uOpacity: { value: opacity },
};
const material = resources.own('material', new THREE.ShaderMaterial({
uniforms,
vertexShader: `
const material = resources.own(
'material',
new THREE.ShaderMaterial({
uniforms,
vertexShader: `
varying vec2 vShadowUv;
void main() {
vShadowUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
fragmentShader: `
uniform float uOpacity;
varying vec2 vShadowUv;
void main() {
@@ -507,12 +640,13 @@ function createContactShadow(
#include <colorspace_fragment>
}
`,
transparent: true,
depthWrite: false,
depthTest: true,
blending: THREE.NormalBlending,
toneMapped: true,
}));
transparent: true,
depthWrite: false,
depthTest: true,
blending: THREE.NormalBlending,
toneMapped: true,
}),
);
const mesh = new THREE.Mesh(
resources.own('geometry', new THREE.PlaneGeometry(width, depth)),
material,
@@ -535,7 +669,8 @@ function ownMaterialTextures(
'metalnessMap',
'aoMap',
]) {
const texture = material[key] as { isTexture?: boolean; dispose(): void } | null | undefined;
const texture = material[key] as
{ isTexture?: boolean; dispose(): void } | null | undefined;
if (texture?.isTexture) resources.own('texture', texture);
}
}
@@ -559,24 +694,36 @@ function createHaunchGeometry(side: -1 | 1) {
const outer = 2.62 * side;
const frontOuter = 2.25 * side;
const positions = new Float32Array([
inner, 0.48, -1.75,
frontOuter, 0.38, -1.75,
inner, 0.92, -1.75,
outer, 0.68, -1.75,
inner, 0.48, 2.62,
outer, 0.33, 2.62,
inner, 0.92, 2.62,
outer, 0.73, 2.62,
inner,
0.48,
-1.75,
frontOuter,
0.38,
-1.75,
inner,
0.92,
-1.75,
outer,
0.68,
-1.75,
inner,
0.48,
2.62,
outer,
0.33,
2.62,
inner,
0.92,
2.62,
outer,
0.73,
2.62,
]);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setIndex([
0, 1, 2, 2, 1, 3,
4, 6, 5, 6, 7, 5,
0, 4, 1, 1, 4, 5,
2, 3, 6, 3, 7, 6,
1, 5, 3, 3, 5, 7,
0, 2, 4, 2, 6, 4,
0, 1, 2, 2, 1, 3, 4, 6, 5, 6, 7, 5, 0, 4, 1, 1, 4, 5, 2, 3, 6, 3, 7, 6, 1,
5, 3, 3, 5, 7, 0, 2, 4, 2, 6, 4,
]);
geometry.computeVertexNormals();
return geometry;
@@ -584,24 +731,14 @@ function createHaunchGeometry(side: -1 | 1) {
function createCanopyGeometry() {
const positions = new Float32Array([
-1.18, 0.78, -1.9,
1.18, 0.78, -1.9,
-0.86, 1.82, -1.65,
0.86, 1.82, -1.65,
-1.62, 0.78, 1.42,
1.62, 0.78, 1.42,
-1.18, 1.42, 1.06,
1.18, 1.42, 1.06,
-1.18, 0.78, -1.9, 1.18, 0.78, -1.9, -0.86, 1.82, -1.65, 0.86, 1.82, -1.65,
-1.62, 0.78, 1.42, 1.62, 0.78, 1.42, -1.18, 1.42, 1.06, 1.18, 1.42, 1.06,
]);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setIndex([
0, 1, 2, 2, 1, 3,
4, 6, 5, 6, 7, 5,
0, 2, 4, 2, 6, 4,
1, 5, 3, 3, 5, 7,
2, 3, 6, 3, 7, 6,
0, 4, 1, 1, 4, 5,
0, 1, 2, 2, 1, 3, 4, 6, 5, 6, 7, 5, 0, 2, 4, 2, 6, 4, 1, 5, 3, 3, 5, 7, 2,
3, 6, 3, 7, 6, 0, 4, 1, 1, 4, 5,
]);
geometry.computeVertexNormals();
return geometry;