feat: retire the gantry and billboard districts and light the rest as steel
This commit is contained in:
@@ -13,16 +13,21 @@ here = Path(__file__).resolve().parent
|
|||||||
root = here.parents[2]
|
root = here.parents[2]
|
||||||
scene = bpy.data.scenes['Roadside Districts']
|
scene = bpy.data.scenes['Roadside Districts']
|
||||||
output = root / 'src/assets/nightdrive-districts.glb'
|
output = root / 'src/assets/nightdrive-districts.glb'
|
||||||
|
# Retired in round 6: the overhead gantry and the sign truss now own the road,
|
||||||
|
# and the billboards' light strips read as garbled text. They stay authored in
|
||||||
|
# the .blend but are no longer exported.
|
||||||
|
RETIRED = {'gantry', 'billboard'}
|
||||||
|
exported = [o for o in scene.objects if o.name.rsplit('_', 1)[0] not in RETIRED]
|
||||||
|
|
||||||
for obj in scene.objects:
|
for obj in scene.objects:
|
||||||
obj.select_set(True)
|
obj.select_set(obj in exported)
|
||||||
bpy.ops.export_scene.gltf(
|
bpy.ops.export_scene.gltf(
|
||||||
filepath=str(output), export_format='GLB', use_selection=True, use_active_scene=True,
|
filepath=str(output), export_format='GLB', use_selection=True, use_active_scene=True,
|
||||||
export_apply=True, export_normals=False, export_texcoords=False, export_materials='NONE',
|
export_apply=True, export_normals=False, export_texcoords=False, export_materials='NONE',
|
||||||
export_animations=False, export_cameras=False, export_lights=False, export_yup=True,
|
export_animations=False, export_cameras=False, export_lights=False, export_yup=True,
|
||||||
)
|
)
|
||||||
triangles = 0
|
triangles = 0
|
||||||
for obj in scene.objects:
|
for obj in exported:
|
||||||
obj.data.calc_loop_triangles()
|
obj.data.calc_loop_triangles()
|
||||||
triangles += len(obj.data.loop_triangles)
|
triangles += len(obj.data.loop_triangles)
|
||||||
provenance = {
|
provenance = {
|
||||||
@@ -30,7 +35,8 @@ provenance = {
|
|||||||
'source': 'nightdrive-districts.blend',
|
'source': 'nightdrive-districts.blend',
|
||||||
'authorship': 'Original procedural modelling in Blender for this portfolio (author-districts.py); no external assets.',
|
'authorship': 'Original procedural modelling in Blender for this portfolio (author-districts.py); no external assets.',
|
||||||
'editable_scene': 'Roadside Districts',
|
'editable_scene': 'Roadside Districts',
|
||||||
'pieces': sorted({o.name.rsplit('_', 1)[0] for o in scene.objects}),
|
'pieces': sorted({o.name.rsplit('_', 1)[0] for o in exported}),
|
||||||
|
'retired': sorted(RETIRED),
|
||||||
'triangles': triangles,
|
'triangles': triangles,
|
||||||
'attributes': 'POSITION + indices; flat normals derived at runtime',
|
'attributes': 'POSITION + indices; flat normals derived at runtime',
|
||||||
'export': '../../../src/assets/nightdrive-districts.glb',
|
'export': '../../../src/assets/nightdrive-districts.glb',
|
||||||
|
|||||||
@@ -5,16 +5,18 @@
|
|||||||
"editable_scene": "Roadside Districts",
|
"editable_scene": "Roadside Districts",
|
||||||
"pieces": [
|
"pieces": [
|
||||||
"arch",
|
"arch",
|
||||||
"billboard",
|
|
||||||
"container",
|
"container",
|
||||||
"crane",
|
"crane",
|
||||||
"gantry",
|
|
||||||
"mast",
|
"mast",
|
||||||
"rack-row",
|
"rack-row",
|
||||||
"workshop"
|
"workshop"
|
||||||
],
|
],
|
||||||
"triangles": 5424,
|
"retired": [
|
||||||
|
"billboard",
|
||||||
|
"gantry"
|
||||||
|
],
|
||||||
|
"triangles": 4392,
|
||||||
"attributes": "POSITION + indices; flat normals derived at runtime",
|
"attributes": "POSITION + indices; flat normals derived at runtime",
|
||||||
"export": "../../../src/assets/nightdrive-districts.glb",
|
"export": "../../../src/assets/nightdrive-districts.glb",
|
||||||
"sha256": "428a212792ac4d8dbf86b9224e9788f49cfc22b3d0dd1b72dfaf33c89f08f189"
|
"sha256": "4fb4acbd90ccd1ed6cb56362c3c2fca3347e0d6f53d37f19e925478f1be4e252"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -18,8 +18,6 @@ import { WORLD_DEPTH, WORLD_FAR_Z } from './metrics';
|
|||||||
* like the expressway furniture. Districts at rest are hidden, so only the
|
* like the expressway furniture. Districts at rest are hidden, so only the
|
||||||
* current chapter's pieces cost draw calls. */
|
* current chapter's pieces cost draw calls. */
|
||||||
export type DistrictPiece =
|
export type DistrictPiece =
|
||||||
| 'gantry'
|
|
||||||
| 'billboard'
|
|
||||||
| 'rack-row'
|
| 'rack-row'
|
||||||
| 'mast'
|
| 'mast'
|
||||||
| 'workshop'
|
| 'workshop'
|
||||||
@@ -34,7 +32,7 @@ interface Placement {
|
|||||||
/** Yaw in radians, applied before mirroring. */
|
/** Yaw in radians, applied before mirroring. */
|
||||||
ry?: number;
|
ry?: number;
|
||||||
s?: number;
|
s?: number;
|
||||||
/** Extra vertical stretch (gantries clear the chase camera). */
|
/** Extra vertical stretch (the arch clears the chase camera). */
|
||||||
sy?: number;
|
sy?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,18 +40,10 @@ interface Placement {
|
|||||||
export const DISTRICT_PRESENCE: Readonly<
|
export const DISTRICT_PRESENCE: Readonly<
|
||||||
Record<DistrictPiece, Partial<Record<SectionId, number>>>
|
Record<DistrictPiece, Partial<Record<SectionId, number>>>
|
||||||
> = {
|
> = {
|
||||||
gantry: {
|
// Pieces rise fully or not at all: a part-raised piece is squashed flat.
|
||||||
profile: 1,
|
|
||||||
experience: 1,
|
|
||||||
infrastructure: 1,
|
|
||||||
'selected-builds': 1,
|
|
||||||
'additional-work': 1,
|
|
||||||
toolkit: 1,
|
|
||||||
},
|
|
||||||
billboard: { profile: 1, experience: 1 },
|
|
||||||
'rack-row': { infrastructure: 1 },
|
'rack-row': { infrastructure: 1 },
|
||||||
mast: { infrastructure: 0.75, toolkit: 1 },
|
mast: { infrastructure: 1, toolkit: 1 },
|
||||||
workshop: { 'selected-builds': 1, 'additional-work': 0.5 },
|
workshop: { 'selected-builds': 1 },
|
||||||
crane: { 'selected-builds': 1, 'additional-work': 1 },
|
crane: { 'selected-builds': 1, 'additional-work': 1 },
|
||||||
container: { 'additional-work': 1 },
|
container: { 'additional-work': 1 },
|
||||||
arch: { contact: 1 },
|
arch: { contact: 1 },
|
||||||
@@ -67,14 +57,6 @@ const span = (count: number, from: number, to: number) =>
|
|||||||
function placementsFor(piece: DistrictPiece, dense: boolean): Placement[] {
|
function placementsFor(piece: DistrictPiece, dense: boolean): Placement[] {
|
||||||
const n = (full: number) => (dense ? full : Math.ceil(full / 2));
|
const n = (full: number) => (dense ? full : Math.ceil(full / 2));
|
||||||
switch (piece) {
|
switch (piece) {
|
||||||
case 'gantry':
|
|
||||||
return span(n(3), -70, -70 - WORLD_DEPTH).map((z) => ({ x: 0, z, sy: 1.42 }));
|
|
||||||
case 'billboard':
|
|
||||||
return span(n(10), -40, -40 - WORLD_DEPTH).map((z, i) => ({
|
|
||||||
x: (i % 2 ? 1 : -1) * 16,
|
|
||||||
z,
|
|
||||||
ry: (i % 2 ? 1 : -1) * 0.28,
|
|
||||||
}));
|
|
||||||
case 'rack-row':
|
case 'rack-row':
|
||||||
return span(n(18), -26, -26 - WORLD_DEPTH).flatMap((z, i) => {
|
return span(n(18), -26, -26 - WORLD_DEPTH).flatMap((z, i) => {
|
||||||
const side = i % 2 ? 1 : -1;
|
const side = i % 2 ? 1 : -1;
|
||||||
@@ -194,17 +176,28 @@ export function createDistricts(
|
|||||||
varying float vHeight;
|
varying float vHeight;
|
||||||
void main() {
|
void main() {
|
||||||
${FADES}
|
${FADES}
|
||||||
|
// Opaque dark steel, like the sign truss: fades dissolve through a
|
||||||
|
// fixed screen-door pattern instead of alpha, so pieces never read
|
||||||
|
// as translucent sheets over the sky and the sun.
|
||||||
|
float cover = fade * smoothstep(0.0, 0.25, vRise);
|
||||||
|
vec2 cell = mod(floor(gl_FragCoord.xy), 4.0);
|
||||||
|
float threshold = (mod(cell.x + cell.y * 2.0, 4.0) * 4.0 + mod(cell.y, 2.0) * 2.0 + mod(cell.x, 2.0) + 0.5) / 16.0;
|
||||||
|
if (cover < threshold) discard;
|
||||||
vec3 n = normalize(cross(dFdx(vWorld), dFdy(vWorld)));
|
vec3 n = normalize(cross(dFdx(vWorld), dFdy(vWorld)));
|
||||||
float key = max(0.0, dot(n, normalize(vec3(-0.45, 0.85, 0.35))));
|
float key = max(0.0, dot(n, normalize(vec3(-0.45, 0.85, 0.35))));
|
||||||
float rim = pow(1.0 - abs(n.y), 3.0) * 0.5;
|
float fill = max(0.0, dot(n, normalize(vec3(0.6, 0.2, -0.4))));
|
||||||
vec3 color = vec3(0.03, 0.016, 0.05) + vec3(0.085, 0.06, 0.14) * key;
|
vec3 color = vec3(0.022, 0.018, 0.034) + vec3(0.07, 0.06, 0.1) * key + vec3(0.02, 0.015, 0.035) * fill;
|
||||||
color += uAccent * rim * 0.06 * (0.4 + 0.6 * smoothstep(0.0, 12.0, vHeight));
|
// Emissive edge: the silhouette catches the chapter colour, and
|
||||||
gl_FragColor = vec4(color, fade * smoothstep(0.0, 0.25, vRise));
|
// the tops of the pieces carry a thin lit band.
|
||||||
|
float rim = pow(1.0 - abs(dot(n, normalize(cameraPosition - vWorld))), 4.0);
|
||||||
|
float crown = smoothstep(0.92, 1.0, n.y) * smoothstep(1.0, 6.0, vHeight);
|
||||||
|
color += uAccent * (rim * 0.22 + crown * 0.05);
|
||||||
|
gl_FragColor = vec4(color, 1.0);
|
||||||
#include <tonemapping_fragment>
|
#include <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
transparent: true,
|
transparent: false,
|
||||||
depthWrite: true,
|
depthWrite: true,
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
}),
|
}),
|
||||||
@@ -253,7 +246,7 @@ export function createDistricts(
|
|||||||
const entry = state.get(piece);
|
const entry = state.get(piece);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
const placements = placementsFor(piece, quality.postEffects);
|
const placements = placementsFor(piece, quality.postEffects);
|
||||||
const accentMix = piece === 'gantry' || piece === 'arch' || piece === 'billboard' ? 1 : 0.35;
|
const accentMix = piece === 'arch' ? 1 : 0.35;
|
||||||
for (const [geometry, material] of [
|
for (const [geometry, material] of [
|
||||||
[body, bodyMaterial(entry.rise)],
|
[body, bodyMaterial(entry.rise)],
|
||||||
[light, lightMaterial(entry.rise, accentMix)],
|
[light, lightMaterial(entry.rise, accentMix)],
|
||||||
|
|||||||
Reference in New Issue
Block a user