OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / ptex_material_test.gd

141 lines · gd ref:
@tool
extends Node3D
## Load a .ptex using MM's engine, render textures, apply to sphere.

@export var load_material: bool = false:
	set(v):
		if v:
			_load_material()
			load_material = false

@export_file("*.ptex") var ptex_path: String = "/storage/projects/code/godot/blender-bridge/test/fixtures/infected_rock.ptex"
@export var texture_size: int = 512

var _mesh_instance: MeshInstance3D

# Material Maker's engine, which this project does not currently ship. These
# were referenced without ever being declared, so the whole script failed to
# PARSE -- which is a louder failure than the missing dependency deserves and
# hid it behind a syntax error. Declared here so the script loads and says what
# is actually missing.
var mm_loader: Node = null
var mm_renderer: Node = null

func _ready() -> void:
	if not Engine.is_editor_hint():
		call_deferred("_load_material")

func _load_material() -> void:
	print("[PtexTest] Loading: %s" % ptex_path)

	if mm_loader == null or mm_renderer == null:
		push_error("[PtexTest] Material Maker's engine (mm_loader / mm_renderer) "
				+ "is not available in this project -- nothing to load with.")
		return

	var gen = await mm_loader.load_gen(ptex_path)
	if gen == null:
		push_error("[PtexTest] Failed to load ptex")
		return

	mm_renderer.add_child(gen)
	print("[PtexTest] Loaded graph: %d children" % gen.get_child_count())

	for i in range(5):
		await get_tree().process_frame

	# Find the Material node
	var mat_node = null
	for c in gen.get_children():
		if c.has_method("get_export_profiles"):
			mat_node = c
			break

	if mat_node == null:
		push_error("[PtexTest] No material node found")
		gen.queue_free()
		return

	# Give MM's renderer time to initialize its rendering thread + device
	print("[PtexTest] Waiting for MM renderer to initialize...")
	for i in range(30):
		await get_tree().process_frame
	print("[PtexTest] mm_renderer in tree: %s" % str(mm_renderer.is_inside_tree()))
	print("[PtexTest] mm_renderer rendering_device: %s" % str(mm_renderer.rendering_device))
	print("[PtexTest] Rendering textures at %dx%d..." % [texture_size, texture_size])

	# Render each PBR channel directly using MM's compute shader pipeline
	# Material input ports: 0=albedo, 1=metallic, 2=roughness, 3=emission, 4=normal, 5=ao
	var size = Vector2i(texture_size, texture_size)
	var channels = {
		"albedo": 0,
		"metallic": 1,
		"roughness": 2,
		"emission": 3,
		"normal": 4,
		"ao": 5
	}

	var textures = {}
	for channel_name in channels:
		var port = channels[channel_name]
		if mat_node.get_source(port) != null:
			print("[PtexTest]   Rendering %s (port %d)..." % [channel_name, port])
			var image = await mat_node.render_output(port, size)
			if image:
				textures[channel_name] = ImageTexture.create_from_image(image)
				print("[PtexTest]   %s: %dx%d" % [channel_name, image.get_width(), image.get_height()])
				# Also save as PNG
				var dir = "res://materials/infected_rock/"
				DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(dir))
				image.save_png(ProjectSettings.globalize_path(dir + channel_name + ".png"))
			else:
				print("[PtexTest]   %s: render returned null" % channel_name)
		else:
			print("[PtexTest]   %s: not connected" % channel_name)

	# Build StandardMaterial3D from rendered textures
	var mat = StandardMaterial3D.new()
	if textures.has("albedo"):
		mat.albedo_texture = textures["albedo"]
	if textures.has("metallic"):
		mat.metallic = 1.0
		mat.metallic_texture = textures["metallic"]
	if textures.has("roughness"):
		mat.roughness_texture = textures["roughness"]
	if textures.has("emission"):
		mat.emission_enabled = true
		mat.emission_texture = textures["emission"]
		mat.emission_energy_multiplier = 0.2
	if textures.has("normal"):
		mat.normal_enabled = true
		mat.normal_texture = textures["normal"]
	if textures.has("ao"):
		mat.ao_enabled = true
		mat.ao_texture = textures["ao"]

	_ensure_sphere()
	if _mesh_instance:
		_mesh_instance.set_surface_override_material(0, mat)
		print("[PtexTest] Applied material with %d textures" % textures.size())

	print("[PtexTest] Done")

func _ensure_sphere() -> void:
	for child in get_children():
		if child is MeshInstance3D:
			_mesh_instance = child
			return

	_mesh_instance = MeshInstance3D.new()
	var sphere := SphereMesh.new()
	sphere.radius = 1.0
	sphere.height = 2.0
	sphere.radial_segments = 64
	sphere.rings = 32
	_mesh_instance.mesh = sphere
	_mesh_instance.name = "PtexSphere"
	add_child(_mesh_instance)
	if Engine.is_editor_hint():
		_mesh_instance.owner = get_tree().edited_scene_root