tools/build_level_osm.py extrudes OSM buildings (-col), lays road strips, Spawn + RacePath empties; osm_level_args.py computes a building-clear spawn on a named way and race-loop corners from street intersection nodes. game.gd builds Path3D from GLB RacePath empties, sanitizes spawn bases, and places bodies BEFORE add_child. Three real bugs fixed: mall canopies (building=roof) extruded as solid blocks, underground busways rendered at surface, and the km-wide ground trimesh breaking suspension raycasts (now -convcol). Cars get continuous_cd for thin building shells. tests/probe.gd is the physics raycast debugger that found it. Data (c) OpenStreetMap contributors, ODbL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
64 lines
2.7 KiB
GDScript
64 lines
2.7 KiB
GDScript
extends SceneTree
|
|
## Physics probe: boot a level headless, raycast forward from the spawn to see
|
|
## which colliders exist ahead. Usage:
|
|
## Godot --headless --path . --script tests/probe.gd -- level=queen_st_mall
|
|
|
|
func _initialize() -> void:
|
|
_run()
|
|
|
|
func _run() -> void:
|
|
var opts := {}
|
|
for a in OS.get_cmdline_user_args():
|
|
var kv := a.split("=")
|
|
if kv.size() == 2:
|
|
opts[kv[0]] = kv[1]
|
|
Game.car_path = Registry.cars().values()[0]
|
|
var levels := Registry.levels()
|
|
Game.level_path = levels.get(opts.get("level", ""), levels.values()[0])
|
|
Game.mode = "cruise"
|
|
var game: Game = (load("res://src/game.tscn") as PackedScene).instantiate()
|
|
root.add_child(game)
|
|
await process_frame
|
|
var sp := game.get_child(0).find_child("Spawn", true, false)
|
|
print("Spawn node: ", sp, " class=", sp.get_class() if sp else "nil",
|
|
" global=", (sp as Node3D).global_position if sp is Node3D else Vector3.INF)
|
|
print("car right after ready: ", game.car.global_position)
|
|
for i in 10:
|
|
var p: Vector3 = game.car.global_position
|
|
print("tick %d: y=%.3f vy=%.2f" % [i, p.y, game.car.linear_velocity.y])
|
|
await physics_frame
|
|
var space := game.car.get_world_3d().direct_space_state
|
|
var from := game.car.global_position + Vector3.UP * 0.5
|
|
var fwd := -game.car.global_basis.z
|
|
print("spawn at ", game.car.global_position, " facing ", fwd)
|
|
for dist in [5.0, 200.0]:
|
|
var q := PhysicsRayQueryParameters3D.create(from, from + fwd * dist)
|
|
q.exclude = [game.car.get_rid()]
|
|
var hit := space.intersect_ray(q)
|
|
if hit:
|
|
print("ray %dm: hit %s at %.1fm" % [dist, hit.collider.get_parent().name if hit.collider.get_parent() else hit.collider.name, from.distance_to(hit.position)])
|
|
else:
|
|
print("ray %dm: clear" % dist)
|
|
for off in [Vector3.ZERO, Vector3(20, 0, -18)]:
|
|
var down := PhysicsRayQueryParameters3D.create(from + off, from + off + Vector3.DOWN * 100.0)
|
|
down.exclude = [game.car.get_rid()]
|
|
var dh := space.intersect_ray(down)
|
|
if dh:
|
|
print("down@%s: hit %s at y=%.2f" % [off, dh.collider.get_parent().name if dh.collider.get_parent() else dh.collider.name, dh.position.y])
|
|
else:
|
|
print("down@%s: NOTHING within 100m" % off)
|
|
var bodies := 0
|
|
for n in game.find_children("*", "StaticBody3D", true, false):
|
|
bodies += 1
|
|
print("static bodies in level: ", bodies)
|
|
var car := game.car
|
|
print("basis.y = ", car.global_basis.y, " basis.z = ", car.global_basis.z)
|
|
for w: Node3D in car.wheels:
|
|
var wf := w.global_position
|
|
var q2 := PhysicsRayQueryParameters3D.create(wf, wf - car.global_basis.y * Car.REST)
|
|
q2.exclude = [car.get_rid()]
|
|
var h2 := space.intersect_ray(q2)
|
|
print("wheel %s from y=%.2f: %s" % [w.name, wf.y,
|
|
("hit %s y=%.2f" % [h2.collider.get_parent().name, h2.position.y]) if h2 else "MISS"])
|
|
quit(0)
|