feat: build deterministic freeway and Lumbridge EV v2
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
export {
|
||||
MODEL_X_METRICS,
|
||||
LUMBRIDGE_EV_METRICS,
|
||||
advanceModelXWheels,
|
||||
buildModelX,
|
||||
buildLumbridgeEV,
|
||||
cloneModelX,
|
||||
createModelXMaterials,
|
||||
disposeModelX,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* A procedural, unbadged black Model X-style electric crossover.
|
||||
* Lumbridge EV-01: an original, unbadged procedural electric grand tourer.
|
||||
*
|
||||
* The asset is authored in metres with its origin on the road at the centre of
|
||||
* the wheelbase. It faces -Z at yaw zero, matching the rest of Tera; +X is the
|
||||
@@ -8,7 +8,8 @@
|
||||
* sparse because this car is normally read from a corridor or chase camera.
|
||||
*
|
||||
* Build one rig and clone it. Clones share geometry and materials while their
|
||||
* wheel and steering joints remain independent. Dispose the original only
|
||||
* wheel and steering joints remain independent. Legacy Model-X API names are
|
||||
* retained as serialized/realtime compatibility contracts, not design intent.
|
||||
* after every clone has left the scene.
|
||||
*/
|
||||
|
||||
@@ -26,6 +27,9 @@ export const MODEL_X_METRICS = {
|
||||
maxSteeringAngle: 0.62,
|
||||
} as const;
|
||||
|
||||
/** Preferred product-facing name; MODEL_X_METRICS remains a compatibility alias. */
|
||||
export const LUMBRIDGE_EV_METRICS = MODEL_X_METRICS;
|
||||
|
||||
export type ModelXDetail = "corridor" | "follow";
|
||||
|
||||
export interface ModelXMaterials {
|
||||
@@ -87,30 +91,32 @@ const UNIT_PLANE = new THREE.PlaneGeometry(1, 1);
|
||||
|
||||
/** Default materials are intentionally untextured: no binary art and no UV dependency. */
|
||||
export function createModelXMaterials(
|
||||
paint: THREE.ColorRepresentation = 0x050607,
|
||||
paint: THREE.ColorRepresentation = 0x465157,
|
||||
): ModelXMaterials {
|
||||
const body = new THREE.MeshPhysicalMaterial({
|
||||
name: "model-x.paint",
|
||||
color: paint,
|
||||
metalness: 0.72,
|
||||
roughness: 0.22,
|
||||
roughness: 0.26,
|
||||
clearcoat: 1,
|
||||
clearcoatRoughness: 0.12,
|
||||
emissive: 0x11191e,
|
||||
emissiveIntensity: 0.72,
|
||||
});
|
||||
return {
|
||||
paint: body,
|
||||
glass: new THREE.MeshPhysicalMaterial({
|
||||
name: "model-x.glass",
|
||||
color: 0x101b22,
|
||||
color: 0x193746,
|
||||
metalness: 0.12,
|
||||
roughness: 0.12,
|
||||
transparent: true,
|
||||
opacity: 0.86,
|
||||
opacity: 0.78,
|
||||
side: THREE.DoubleSide,
|
||||
}),
|
||||
trim: new THREE.MeshStandardMaterial({
|
||||
name: "model-x.trim",
|
||||
color: 0x101214,
|
||||
color: 0x272d31,
|
||||
metalness: 0.68,
|
||||
roughness: 0.3,
|
||||
}),
|
||||
@@ -122,9 +128,11 @@ export function createModelXMaterials(
|
||||
}),
|
||||
wheel: new THREE.MeshStandardMaterial({
|
||||
name: "model-x.wheel",
|
||||
color: 0x2d3135,
|
||||
color: 0x515960,
|
||||
metalness: 0.88,
|
||||
roughness: 0.25,
|
||||
emissive: 0x111519,
|
||||
emissiveIntensity: 0.45,
|
||||
}),
|
||||
brake: new THREE.MeshStandardMaterial({
|
||||
name: "model-x.brake",
|
||||
@@ -136,14 +144,14 @@ export function createModelXMaterials(
|
||||
name: "model-x.headlight",
|
||||
color: 0xd9f4ff,
|
||||
emissive: 0xb8eaff,
|
||||
emissiveIntensity: 2.4,
|
||||
emissiveIntensity: 3.2,
|
||||
roughness: 0.16,
|
||||
}),
|
||||
tailLight: new THREE.MeshStandardMaterial({
|
||||
name: "model-x.tail-light",
|
||||
color: 0xff2433,
|
||||
emissive: 0xd70918,
|
||||
emissiveIntensity: 1.9,
|
||||
emissiveIntensity: 2.8,
|
||||
roughness: 0.2,
|
||||
}),
|
||||
};
|
||||
@@ -376,6 +384,44 @@ function buildBody(materials: ModelXMaterials, detail: ModelXDetail): THREE.Grou
|
||||
});
|
||||
}
|
||||
|
||||
// EV-01 signature: continuous light blades and satin aero rails remain
|
||||
// readable at corridor LOD without a badge or a borrowed grille shape.
|
||||
batch.box(materials.headlight, {
|
||||
position: [0, 0.89, -2.43],
|
||||
scale: [1.28, 0.035, 0.028],
|
||||
});
|
||||
batch.box(materials.tailLight, {
|
||||
position: [0, 0.98, 2.39],
|
||||
scale: [1.34, 0.032, 0.026],
|
||||
});
|
||||
for (const side of [-1, 1]) {
|
||||
batch.box(materials.wheel, {
|
||||
position: [side * 0.985, 0.47, 0.12],
|
||||
scale: [0.025, 0.06, 3.45],
|
||||
});
|
||||
}
|
||||
|
||||
if (detail === "follow") {
|
||||
// A small real interior reads through the panoramic glazing in chase and
|
||||
// driver cameras. It deliberately uses the pooled trim surface.
|
||||
batch.box(materials.trim, {
|
||||
position: [0, 1.05, -0.58],
|
||||
scale: [1.45, 0.12, 0.34],
|
||||
rotation: [-0.12, 0, 0],
|
||||
});
|
||||
for (const side of [-1, 1]) {
|
||||
batch.box(materials.trim, {
|
||||
position: [side * 0.38, 1.02, 0.08],
|
||||
scale: [0.42, 0.52, 0.46],
|
||||
rotation: [0.12, 0, 0],
|
||||
});
|
||||
batch.box(materials.trim, {
|
||||
position: [side * 0.38, 1.33, 0.17],
|
||||
scale: [0.32, 0.28, 0.2],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Lower aero surfaces stop the black shell dissolving into the road.
|
||||
batch.box(materials.trim, {
|
||||
position: [0, 0.39, -1.92],
|
||||
@@ -502,6 +548,9 @@ export function buildModelX(options: ModelXBuildOptions = {}): ModelXRig {
|
||||
root.name = "model-x";
|
||||
root.userData.kind = "vehicle";
|
||||
root.userData.vehicleModel = "model-x";
|
||||
root.userData.design = "lumbridge-ev-01";
|
||||
root.userData.lod = detail;
|
||||
root.userData.unbranded = true;
|
||||
root.userData.forwardAxis = "-Z";
|
||||
|
||||
root.add(buildBody(materials, detail));
|
||||
@@ -518,6 +567,11 @@ export function buildModelX(options: ModelXBuildOptions = {}): ModelXRig {
|
||||
return { root, body: root.getObjectByName("model-x.body") as THREE.Group, wheels, ownsMaterials: !options.materials };
|
||||
}
|
||||
|
||||
/** Preferred builder for the original Lumbridge EV visual. */
|
||||
export function buildLumbridgeEV(options: ModelXBuildOptions = {}): ModelXRig {
|
||||
return buildModelX(options);
|
||||
}
|
||||
|
||||
/** Clone the hierarchy while sharing all immutable geometry and material resources. */
|
||||
export function cloneModelX(source: ModelXRig): ModelXRig {
|
||||
return resolveRig(source.root.clone(true), false);
|
||||
|
||||
Reference in New Issue
Block a user