FLOORED LAZZA 1.5: John's actual first car, '82 KA Laser hatch
Built from his photos: white 5-door wedge, black bumpers + belt-line rubbing strip, amber corner indicators, wide dark grille bezel (pale headlights vanish on white paint without it), and the crown jewel -- rear hatch louvres, five slats that shed one by one on impact like a dropped pie. New optional spec keys: louvres, indicators, side_strip, wide_grille; build_cars.py now takes `-- <id>` to rebuild a single car; screenshot harness gains cam=rear. Reversed-stats rule applied: 54 kW carby IRL -> 21.0 power / 860 kg / 7.8 grip here. Second-quickest thing in the game behind the Excel, and the chuckable-est. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
da63829e9e
commit
81ed13b1ff
@ -46,8 +46,10 @@ Model convention (any GLB works, hand-modelled or generated):
|
|||||||
- Node origin 0.75 m above ground level (the controller's ride height).
|
- Node origin 0.75 m above ground level (the controller's ride height).
|
||||||
- Objects named `det_*` are detachable — the game rips them off on impact
|
- Objects named `det_*` are detachable — the game rips them off on impact
|
||||||
(doors, mirrors, bumpers, bonnet, boot).
|
(doors, mirrors, bumpers, bonnet, boot).
|
||||||
- Current fleet: Datto 120Y, Gemmy TX, Kingswood HQ, VL Terbo, XF Fungoon, Excel Hilton.
|
- Current fleet: 30 shitboxes, from the Datto 120Y to the FLOORED LAZZA 1.5 ('82
|
||||||
Piss-take names on purpose: real model names and badges are trademarks.
|
Laser hatch, rear louvres that shed slat-by-slat — John's actual first car,
|
||||||
|
built from his photos). Piss-take names on purpose: real model names and
|
||||||
|
badges are trademarks.
|
||||||
|
|
||||||
To improve a silhouette: supply a side-on photo + front three-quarter photo, tweak the
|
To improve a silhouette: supply a side-on photo + front three-quarter photo, tweak the
|
||||||
profile points in the spec, rebuild, screenshot.
|
profile points in the spec, rebuild, screenshot.
|
||||||
|
|||||||
BIN
cars/lazza_ka/car.glb
Normal file
BIN
cars/lazza_ka/car.glb
Normal file
Binary file not shown.
45
cars/lazza_ka/car.glb.import
Normal file
45
cars/lazza_ka/car.glb.import
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bbottmecxqhu1"
|
||||||
|
path="res://.godot/imported/car.glb-edc9533c079c72a53d09ab51d6c7dcf4.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://cars/lazza_ka/car.glb"
|
||||||
|
dest_files=["res://.godot/imported/car.glb-edc9533c079c72a53d09ab51d6c7dcf4.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
13
cars/lazza_ka/stats.tres
Normal file
13
cars/lazza_ka/stats.tres
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[gd_resource type="Resource" script_class="CarStats" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://src/car_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
display_name = "FLOORED LAZZA 1.5"
|
||||||
|
model_path = "res://cars/lazza_ka/car.glb"
|
||||||
|
engine_power = 21.0
|
||||||
|
mass = 860.0
|
||||||
|
grip = 7.8
|
||||||
|
length = 3.96
|
||||||
|
width = 1.61
|
||||||
@ -35,6 +35,12 @@ func _run() -> void:
|
|||||||
game.cam.look_at(game.car.global_position + Vector3.UP * 0.4)
|
game.cam.look_at(game.car.global_position + Vector3.UP * 0.4)
|
||||||
await process_frame
|
await process_frame
|
||||||
await process_frame
|
await process_frame
|
||||||
|
elif opts.get("cam", "") == "rear":
|
||||||
|
game.set_process(false)
|
||||||
|
game.cam.global_position = game.car.global_position + game.car.global_basis * Vector3(2.6, 1.6, 6.5)
|
||||||
|
game.cam.look_at(game.car.global_position + Vector3.UP * 0.4)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
elif opts.get("cam", "") == "top":
|
elif opts.get("cam", "") == "top":
|
||||||
game.set_process(false)
|
game.set_process(false)
|
||||||
var alt := float(opts.get("alt", "150"))
|
var alt := float(opts.get("alt", "150"))
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"""Procedural Aussie shitbox factory.
|
"""Procedural Aussie shitbox factory.
|
||||||
|
|
||||||
Run: /Applications/Blender.app/Contents/MacOS/Blender -b -P tools/build_cars.py
|
Run: /Applications/Blender.app/Contents/MacOS/Blender -b -P tools/build_cars.py
|
||||||
|
(append `-- <id> [<id>...]` to rebuild only those cars)
|
||||||
|
|
||||||
Each car: side-silhouette profile extruded across the width (body + greenhouse),
|
Each car: side-silhouette profile extruded across the width (body + greenhouse),
|
||||||
cylinder wheels, and separate det_* panel objects (doors, mirrors, bumpers,
|
cylinder wheels, and separate det_* panel objects (doors, mirrors, bumpers,
|
||||||
@ -15,6 +16,7 @@ side-on photos per car when a shape needs to get closer.
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import math
|
import math
|
||||||
|
import sys
|
||||||
import bpy
|
import bpy
|
||||||
import bmesh
|
import bmesh
|
||||||
from mathutils import Matrix
|
from mathutils import Matrix
|
||||||
@ -30,7 +32,9 @@ LIGHT_R = (0.7, 0.1, 0.08, 1)
|
|||||||
|
|
||||||
# L, W, heights: nose/belt/tail, lengths: bonnet/boot, roof height + rear roof end,
|
# L, W, heights: nose/belt/tail, lengths: bonnet/boot, roof height + rear roof end,
|
||||||
# axle overhangs, wheel radius, paint, chrome bumpers, hatch rear.
|
# axle overhangs, wheel radius, paint, chrome bumpers, hatch rear.
|
||||||
# Optional: doors=2, bonnet_color (two-tone accent), roof_cap (vinyl roof), spoiler.
|
# Optional: doors=2, bonnet_color (two-tone accent), roof_cap (vinyl roof), spoiler,
|
||||||
|
# louvres (hatch-glass slat stack, each slat sheds on impact), indicators (amber
|
||||||
|
# corner blinkers), side_strip (black rubbing strip along the belt line).
|
||||||
CARS = {
|
CARS = {
|
||||||
"datto_120y": dict(L=3.92, W=1.50, nose=0.62, belt=0.78, tail=0.72,
|
"datto_120y": dict(L=3.92, W=1.50, nose=0.62, belt=0.78, tail=0.72,
|
||||||
bonnet=1.00, boot=0.85, roof=1.32, roof_rear=0.75,
|
bonnet=1.00, boot=0.85, roof=1.32, roof_rear=0.75,
|
||||||
@ -84,6 +88,13 @@ CARS = {
|
|||||||
ohang_f=0.85, ohang_r=0.95, wheel=0.29,
|
ohang_f=0.85, ohang_r=0.95, wheel=0.29,
|
||||||
paint=(0.70, 0.72, 0.58, 1), chrome_bumpers=True, hatch=False,
|
paint=(0.70, 0.72, 0.58, 1), chrome_bumpers=True, hatch=False,
|
||||||
doors=2, van=True),
|
doors=2, van=True),
|
||||||
|
# John's actual first car: '82 KA Laser 1.5 hatch, white, rear louvres.
|
||||||
|
# Gutless IRL (54 kW carby) -> reversed-stats rule makes it a weapon here.
|
||||||
|
"lazza_ka": dict(L=3.96, W=1.61, nose=0.58, belt=0.79, tail=0.90,
|
||||||
|
bonnet=1.02, boot=0.35, roof=1.37, roof_rear=0.72,
|
||||||
|
ohang_f=0.79, ohang_r=0.72, wheel=0.26,
|
||||||
|
paint=(0.92, 0.92, 0.89, 1), chrome_bumpers=False, hatch=True,
|
||||||
|
louvres=True, indicators=True, side_strip=True, wide_grille=True),
|
||||||
# mokes use the open-tub builder: style="moke"
|
# mokes use the open-tub builder: style="moke"
|
||||||
"mini_merk": dict(style="moke", L=3.05, W=1.42, nose=0.60, tub=0.66, canopy=1.38,
|
"mini_merk": dict(style="moke", L=3.05, W=1.42, nose=0.60, tub=0.66, canopy=1.38,
|
||||||
bonnet=0.85, ohang_f=0.55, ohang_r=0.55, wheel=0.26,
|
bonnet=0.85, ohang_f=0.55, ohang_r=0.55, wheel=0.26,
|
||||||
@ -215,6 +226,19 @@ def build(car_id, s):
|
|||||||
parts.append(box("det_bumper_f", (0, f + 0.05, bottom + 0.16), (W + 0.06, 0.15, 0.16), bumper_mat))
|
parts.append(box("det_bumper_f", (0, f + 0.05, bottom + 0.16), (W + 0.06, 0.15, 0.16), bumper_mat))
|
||||||
parts.append(box("det_bumper_r", (0, r - 0.05, bottom + 0.16), (W + 0.06, 0.15, 0.16), bumper_mat))
|
parts.append(box("det_bumper_r", (0, r - 0.05, bottom + 0.16), (W + 0.06, 0.15, 0.16), bumper_mat))
|
||||||
|
|
||||||
|
if s.get("louvres") and s["hatch"]:
|
||||||
|
# venetian-blind slat stack over the hatch glass; every slat is its own
|
||||||
|
# det_ part, so a rear-ender sheds them one by one like a dropped pie
|
||||||
|
ty, tz = boot_y + s["roof_rear"], s["roof"] # glass top edge
|
||||||
|
ly, lz = rr_y, rr_z # glass bottom edge
|
||||||
|
g_ang = math.atan2(tz - lz, ty - ly)
|
||||||
|
for i in range(5):
|
||||||
|
t = 0.14 + i * 0.18
|
||||||
|
sy = ty + (ly - ty) * t
|
||||||
|
sz = tz + (lz - tz) * t + 0.045
|
||||||
|
parts.append(box("det_louvre_%d" % i, (0, sy, sz),
|
||||||
|
(W * 0.80, 0.20, 0.022), dark, rot_x=-g_ang * 0.45))
|
||||||
|
|
||||||
if s.get("van"): # cargo box from cab rear to tail, plus rip-off tailgate
|
if s.get("van"): # cargo box from cab rear to tail, plus rip-off tailgate
|
||||||
parts.append(box("van_box", (0, (boot_y + r) / 2, (s["belt"] + s["roof"]) / 2),
|
parts.append(box("van_box", (0, (boot_y + r) / 2, (s["belt"] + s["roof"]) / 2),
|
||||||
(W * 0.94, boot_y - r - 0.05, s["roof"] - s["belt"]), paint))
|
(W * 0.94, boot_y - r - 0.05, s["roof"] - s["belt"]), paint))
|
||||||
@ -240,13 +264,23 @@ def build(car_id, s):
|
|||||||
(W * 0.86, cap_f - cap_r + 0.1, 0.035), material("vinyl", s["roof_cap"], rough=0.9)))
|
(W * 0.86, cap_f - cap_r + 0.1, 0.035), material("vinyl", s["roof_cap"], rough=0.9)))
|
||||||
if s.get("spoiler"): # bootlid ducktail
|
if s.get("spoiler"): # bootlid ducktail
|
||||||
trim.append(box("trim_spoiler", (0, r + 0.22, s["tail"] + 0.05), (W * 0.8, 0.28, 0.09), paint))
|
trim.append(box("trim_spoiler", (0, r + 0.22, s["tail"] + 0.05), (W * 0.8, 0.28, 0.09), paint))
|
||||||
|
grille_w = W * (0.78 if s.get("wide_grille") else 0.5) # wide = lights sit in a dark bezel band
|
||||||
trim += [
|
trim += [
|
||||||
box("trim_grille", (0, f + 0.01, s["nose"] - 0.14), (W * 0.5, 0.05, 0.12), dark),
|
box("trim_grille", (0, f + 0.01, s["nose"] - 0.14), (grille_w, 0.05, 0.14), dark),
|
||||||
box("trim_light_fl", (-W * 0.33, f + 0.02, s["nose"] - 0.13), (0.24, 0.05, 0.13), material("lf", LIGHT_F)),
|
box("trim_light_fl", (-W * 0.33, f + 0.02, s["nose"] - 0.13), (0.24, 0.05, 0.13), material("lf", LIGHT_F)),
|
||||||
box("trim_light_fr", (W * 0.33, f + 0.02, s["nose"] - 0.13), (0.24, 0.05, 0.13), material("lf2", LIGHT_F)),
|
box("trim_light_fr", (W * 0.33, f + 0.02, s["nose"] - 0.13), (0.24, 0.05, 0.13), material("lf2", LIGHT_F)),
|
||||||
box("trim_light_rl", (-W * 0.32, r - 0.02, s["tail"] - 0.14), (0.26, 0.05, 0.12), material("lr", LIGHT_R)),
|
box("trim_light_rl", (-W * 0.32, r - 0.02, s["tail"] - 0.14), (0.26, 0.05, 0.12), material("lr", LIGHT_R)),
|
||||||
box("trim_light_rr", (W * 0.32, r - 0.02, s["tail"] - 0.14), (0.26, 0.05, 0.12), material("lr2", LIGHT_R)),
|
box("trim_light_rr", (W * 0.32, r - 0.02, s["tail"] - 0.14), (0.26, 0.05, 0.12), material("lr2", LIGHT_R)),
|
||||||
]
|
]
|
||||||
|
if s.get("indicators"):
|
||||||
|
amber = material("amber", (0.95, 0.55, 0.05, 1))
|
||||||
|
trim.append(box("trim_ind_l", (-W * 0.44, f + 0.02, s["nose"] - 0.13), (0.11, 0.05, 0.12), amber))
|
||||||
|
trim.append(box("trim_ind_r", (W * 0.44, f + 0.02, s["nose"] - 0.13), (0.11, 0.05, 0.12), amber))
|
||||||
|
if s.get("side_strip"):
|
||||||
|
strip_len = L * 0.80
|
||||||
|
for sx in (-1, 1):
|
||||||
|
trim.append(box("trim_strip_%s" % ("l" if sx < 0 else "r"),
|
||||||
|
(sx * (hw + 0.015), -L * 0.02, 0.52), (0.03, strip_len, 0.055), dark))
|
||||||
|
|
||||||
ax_f, ax_r = f - s["ohang_f"], r + s["ohang_r"]
|
ax_f, ax_r = f - s["ohang_f"], r + s["ohang_r"]
|
||||||
wx = hw - 0.10
|
wx = hw - 0.10
|
||||||
@ -337,9 +371,14 @@ def build_moke(car_id, s):
|
|||||||
print("built", car_id, "->", path)
|
print("built", car_id, "->", path)
|
||||||
|
|
||||||
|
|
||||||
|
only = set(sys.argv[sys.argv.index("--") + 1:]) if "--" in sys.argv else set()
|
||||||
|
built = 0
|
||||||
for car_id, spec in CARS.items():
|
for car_id, spec in CARS.items():
|
||||||
|
if only and car_id not in only:
|
||||||
|
continue
|
||||||
if spec.get("style") == "moke":
|
if spec.get("style") == "moke":
|
||||||
build_moke(car_id, spec)
|
build_moke(car_id, spec)
|
||||||
else:
|
else:
|
||||||
build(car_id, spec)
|
build(car_id, spec)
|
||||||
print("done: %d cars" % len(CARS))
|
built += 1
|
||||||
|
print("done: %d cars" % built)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user