# Tattoo viewer: Quaternius Regular Male with six baked tattoo marks. # Saves verification shots to shots/, then hands over to manual control: # left-drag orbits the camera around the model, scroll wheel zooms in/out. extends Node3D const GLTF_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/Regular_Male_FullBody.gltf" const BAKED_PATH := "C:/Users/CAN/tinqs-ltd/tattoo-test/baked_body.png" const NORMAL_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/T_Regular_Male_Normal_png.png" const EYE_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/T_Eye_Brown.png" const HAIR_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/T_Hair_1_BaseColor.png" const SHOT_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/shots/" const TAKE_SHOTS := true var model: Node3D var skel: Skeleton3D var cam: Camera3D # manual orbit state (active once shots are done) var interactive := false var dragging := false var orbit_yaw := 0.0 var orbit_pitch := 0.08 var orbit_dist := 3.4 var orbit_target := Vector3(0, 1.0, 0) func _ready() -> void: DirAccess.make_dir_recursive_absolute(SHOT_DIR) var env := Environment.new() env.background_mode = Environment.BG_COLOR env.background_color = Color(0.12, 0.13, 0.16) env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_color = Color(0.75, 0.78, 0.85) env.ambient_light_energy = 0.7 env.tonemap_mode = Environment.TONE_MAPPER_FILMIC var we := WorldEnvironment.new() we.environment = env add_child(we) var sun := DirectionalLight3D.new() sun.rotation_degrees = Vector3(-40, -35, 0) sun.light_energy = 1.5 add_child(sun) var fill := DirectionalLight3D.new() fill.rotation_degrees = Vector3(-15, 140, 0) fill.light_energy = 0.6 add_child(fill) var under := DirectionalLight3D.new() under.rotation_degrees = Vector3(35, 0, 0) under.light_energy = 0.35 add_child(under) var doc := GLTFDocument.new() var state := GLTFState.new() var err := doc.append_from_file(GLTF_PATH, state) if err != OK: push_error("GLTF load failed") return model = doc.generate_scene(state) add_child(model) var baked := ImageTexture.create_from_image(Image.load_from_file(BAKED_PATH)) var body_normal := ImageTexture.create_from_image(Image.load_from_file(NORMAL_PATH)) var eye_tex := ImageTexture.create_from_image(Image.load_from_file(EYE_PATH)) var hair_tex := ImageTexture.create_from_image(Image.load_from_file(HAIR_PATH)) var stack: Array = [model] while not stack.is_empty(): var n: Node = stack.pop_back() if n is Skeleton3D and skel == null: skel = n if n is MeshInstance3D: var lname := String(n.name).to_lower() var mat := StandardMaterial3D.new() mat.roughness = 0.85 if lname.contains("regularmale"): mat.albedo_texture = baked mat.normal_enabled = true mat.normal_texture = body_normal elif lname.contains("eye") and not lname.contains("brow"): mat.albedo_texture = eye_tex else: mat.albedo_texture = hair_tex for s in n.mesh.get_surface_count(): n.set_surface_override_material(s, mat) for c in n.get_children(): stack.push_back(c) cam = Camera3D.new() cam.fov = 50 add_child(cam) cam.current = true if TAKE_SHOTS: await _take_shots() print("SHOTS_DONE") # hand over to manual orbit model.rotation.y = 0.0 interactive = true _update_cam() # ── manual orbit: left-drag rotates, scroll wheel zooms ───────────────────── func _unhandled_input(event: InputEvent) -> void: if not interactive: return if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: dragging = event.pressed elif event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_UP: orbit_dist = clampf(orbit_dist * 0.90, 0.35, 8.0) _update_cam() elif event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_DOWN: orbit_dist = clampf(orbit_dist / 0.90, 0.35, 8.0) _update_cam() elif event is InputEventMouseMotion and dragging: orbit_yaw -= event.relative.x * 0.008 orbit_pitch = clampf(orbit_pitch + event.relative.y * 0.008, -1.3, 1.3) _update_cam() func _update_cam() -> void: var dir := Vector3( sin(orbit_yaw) * cos(orbit_pitch), sin(orbit_pitch), cos(orbit_yaw) * cos(orbit_pitch)) cam.look_at_from_position(orbit_target + dir * orbit_dist, orbit_target) # ── verification shots ─────────────────────────────────────────────────────── func _bone_point(bone: String) -> Vector3: return (skel.global_transform * skel.get_bone_global_rest(skel.find_bone(bone)).origin) func _limb_mid(a: String, b: String) -> Vector3: return (_bone_point(a) + _bone_point(b)) * 0.5 func _snap(shot_name: String) -> void: await get_tree().process_frame await get_tree().process_frame await RenderingServer.frame_post_draw var img := get_viewport().get_texture().get_image() img.save_png(SHOT_DIR + shot_name + ".png") print("shot saved: ", SHOT_DIR + shot_name + ".png") func _take_shots() -> void: # full body front / back model.rotation.y = 0.0 cam.look_at_from_position(Vector3(0, 1.1, 3.4), Vector3(0, 1.0, 0)) await _snap("01_full_front") model.rotation.y = PI await get_tree().process_frame await _snap("02_full_back") # torso closeups model.rotation.y = 0.0 await get_tree().process_frame cam.look_at_from_position(Vector3(0, 1.3, 0.9), Vector3(0, 1.3, 0)) await _snap("03_chest_star_eye") model.rotation.y = PI await get_tree().process_frame cam.look_at_from_position(Vector3(0, 1.25, 0.9), Vector3(0, 1.25, 0)) await _snap("04_back_hearth_warmth") # arms (front view of each arm) model.rotation.y = 0.0 await get_tree().process_frame var mid_r := _limb_mid("upperarm_r", "hand_r") cam.look_at_from_position(mid_r + Vector3(0, 0.12, 1.0), mid_r) await _snap("05_arm_r_chiefs_mark") var mid_l := _limb_mid("upperarm_l", "hand_l") cam.look_at_from_position(mid_l + Vector3(0, 0.12, 1.0), mid_l) await _snap("06_arm_l_iron_spine") # legs (front + outer side of each leg) var leg_r := _limb_mid("thigh_r", "foot_r") cam.look_at_from_position(leg_r + Vector3(0, 0.05, 1.1), leg_r) await _snap("07_leg_r_voyagers_front") cam.look_at_from_position(leg_r + Vector3(-1.0, 0.05, 0.3), leg_r) await _snap("08_leg_r_voyagers_outer") var leg_l := _limb_mid("thigh_l", "foot_l") cam.look_at_from_position(leg_l + Vector3(0, 0.05, 1.1), leg_l) await _snap("09_leg_l_storm_front") cam.look_at_from_position(leg_l + Vector3(1.0, 0.05, 0.3), leg_l) await _snap("10_leg_l_storm_outer") model.rotation.y = PI await get_tree().process_frame cam.look_at_from_position(leg_r + Vector3(0, 0.05, 1.1), leg_r) await _snap("11_legs_back") model.rotation.y = 0.0