M0' scaffold: Godot 4.7 + GDExtension + libgr2 wired up (macOS)
- extension/: godot-cpp submodule (master, API 4.7), Metin2Model node registered, links xrender-poc/libgr2 via sibling-dir reference. - project/: Godot 4.7 project — orbit camera + DirectionalLight3D (shadows) + WorldEnvironment/procedural sky + placeholder cube; main.gd probes the extension and can run libgr2 on a real .gr2 (MTGODOT_PROBE_GR2). - build.sh one-shot build into project/bin/. - docs/GODOT-POC-PLAN.md (phased: Phase 1 macOS -> M0'/M1/M2/M2.5). Verified on this machine: [mtgodot] Metin2Model registered OK — libgr2 linked probe_gr2(warrior_cheongrin.gr2) -> gr2 OK: format_version=6 sections=8 Metal Forward+ renders the scene (test/golden/m0-scaffold.png). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
extends Node3D
|
||||
## mtgodot-poc — M0' harness.
|
||||
##
|
||||
## Builds the minimal scene the plan asks for (orbit camera + DirectionalLight3D
|
||||
## with shadows + WorldEnvironment/sky + placeholder cube + an empty Metin2Model),
|
||||
## then reports whether the GDExtension loaded.
|
||||
##
|
||||
## M1 will replace the cube with Metin2Model.load_gr2(...) output.
|
||||
|
||||
const ORBIT_SPEED := 0.01
|
||||
const ZOOM_STEP := 0.3
|
||||
const PITCH_LIMIT := 1.45
|
||||
|
||||
var _cam: Camera3D
|
||||
var _yaw := 0.6
|
||||
var _pitch := 0.45
|
||||
var _dist := 4.0
|
||||
var _target := Vector3(0.0, 1.0, 0.0)
|
||||
var _dragging := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_build_environment()
|
||||
_build_sun()
|
||||
_build_camera()
|
||||
_build_placeholder()
|
||||
_probe_extension()
|
||||
print("[mtgodot] M0' harness ready — drag: orbit, wheel: zoom, F2: screenshot")
|
||||
# Batch capture: MTGODOT_AUTOSHOT=/abs/path.png godot --path project
|
||||
var autoshot := OS.get_environment("MTGODOT_AUTOSHOT")
|
||||
if autoshot != "":
|
||||
await get_tree().create_timer(0.8).timeout
|
||||
var img := get_viewport().get_texture().get_image()
|
||||
img.save_png(autoshot)
|
||||
print("[mtgodot] autoshot -> ", autoshot)
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func _build_environment() -> void:
|
||||
var we := WorldEnvironment.new()
|
||||
var env := Environment.new()
|
||||
var sky := Sky.new()
|
||||
sky.sky_material = ProceduralSkyMaterial.new()
|
||||
env.background_mode = Environment.BG_SKY
|
||||
env.sky = sky
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
|
||||
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
|
||||
|
||||
func _build_sun() -> void:
|
||||
var sun := DirectionalLight3D.new()
|
||||
sun.rotation_degrees = Vector3(-45.0, -35.0, 0.0)
|
||||
sun.shadow_enabled = true
|
||||
add_child(sun)
|
||||
|
||||
|
||||
func _build_camera() -> void:
|
||||
_cam = Camera3D.new()
|
||||
_cam.current = true
|
||||
_cam.fov = 55.0
|
||||
add_child(_cam)
|
||||
_update_camera()
|
||||
|
||||
|
||||
func _build_placeholder() -> void:
|
||||
var cube := MeshInstance3D.new()
|
||||
cube.name = "Placeholder"
|
||||
cube.mesh = BoxMesh.new()
|
||||
cube.position = _target
|
||||
add_child(cube)
|
||||
|
||||
|
||||
func _probe_extension() -> void:
|
||||
if not ClassDB.class_exists("Metin2Model"):
|
||||
push_error("[mtgodot] Metin2Model NOT registered — GDExtension failed to load")
|
||||
return
|
||||
var m := ClassDB.instantiate("Metin2Model") as Node3D
|
||||
m.name = "Metin2Model"
|
||||
add_child(m)
|
||||
print("[mtgodot] Metin2Model registered OK — ", m.call("libgr2_info"))
|
||||
# Optional: point MTGODOT_PROBE_GR2 at a real .gr2 to exercise libgr2 end to end.
|
||||
var probe := OS.get_environment("MTGODOT_PROBE_GR2")
|
||||
if probe != "":
|
||||
print("[mtgodot] probe_gr2(", probe, ") -> ", m.call("probe_gr2", probe))
|
||||
|
||||
|
||||
func _update_camera() -> void:
|
||||
var offset := Vector3(
|
||||
cos(_pitch) * sin(_yaw),
|
||||
sin(_pitch),
|
||||
cos(_pitch) * cos(_yaw)) * _dist
|
||||
_cam.position = _target + offset
|
||||
_cam.look_at(_target, Vector3.UP)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
var mb := event as InputEventMouseButton
|
||||
match mb.button_index:
|
||||
MOUSE_BUTTON_LEFT:
|
||||
_dragging = mb.pressed
|
||||
MOUSE_BUTTON_WHEEL_UP:
|
||||
_dist = maxf(0.5, _dist - ZOOM_STEP)
|
||||
_update_camera()
|
||||
MOUSE_BUTTON_WHEEL_DOWN:
|
||||
_dist = minf(50.0, _dist + ZOOM_STEP)
|
||||
_update_camera()
|
||||
elif event is InputEventMouseMotion and _dragging:
|
||||
var mm := event as InputEventMouseMotion
|
||||
_yaw -= mm.relative.x * ORBIT_SPEED
|
||||
_pitch = clampf(_pitch + mm.relative.y * ORBIT_SPEED, -PITCH_LIMIT, PITCH_LIMIT)
|
||||
_update_camera()
|
||||
elif event is InputEventKey:
|
||||
var k := event as InputEventKey
|
||||
if k.pressed and k.keycode == KEY_F2:
|
||||
_screenshot()
|
||||
|
||||
|
||||
func _screenshot() -> void:
|
||||
var img := get_viewport().get_texture().get_image()
|
||||
var path := "user://shot_%d.png" % Time.get_ticks_msec()
|
||||
img.save_png(path)
|
||||
print("[mtgodot] screenshot -> ", ProjectSettings.globalize_path(path))
|
||||
Reference in New Issue
Block a user