@tool
extends EditorScript

# Auto-generate OccluderInstance3D nodes from CSG wall pieces.
#
# Scans the current scene for CSGBox3D nodes tagged as structural
# (walls, floors, pillars) and creates matching OccluderInstance3D
# boxes so Godot's occlusion culling system can use them.
#
# Usage:
#   1. Open target scene in Godot editor
#   2. Run: Scene > Run Script (select this file)
#   3. Or run from command line:
#      godot --script scripts/generate_occluders.gd --scene res://path/to/scene.tscn
#
# Generated occluders are parented under an "Occluders" node.

const OCCLUDER_PARENT_NAME := "Occluders"

# Minimum CSG size (longest axis) to generate an occluder.
# Very small pieces don't contribute meaningful occlusion.
const MIN_OCCLUDER_SIZE := 0.5

func _run():
	var scene_root: Node
	
	if Engine.is_editor_hint():
		scene_root = get_scene() as Node
		if not scene_root:
			printerr("No scene is currently open in the editor.")
			return
	else:
		var args = OS.get_cmdline_args()
		var scene_path := ""
		for i in range(args.size()):
			if args[i] == "--scene" and i + 1 < args.size():
				scene_path = args[i + 1]
		if scene_path.is_empty():
			printerr("Usage: godot --script generate_occluders.gd --scene res://path/to/scene.tscn")
			return
		
		var packed = ResourceLoader.load(scene_path)
		if not packed:
			printerr("Failed to load scene: ", scene_path)
			return
		scene_root = packed.instantiate()
	
	print("=== Occluder Generator ===")
	print("Scene: ", scene_root.name if scene_root else "unknown")
	
	# Find or create the Occluders parent node
	var occluder_parent: Node = _find_or_create_parent(scene_root)
	var generated_count := 0
	var skipped_count := 0
	
	# Walk all CSGBox3D nodes in the scene
	_generate_from_node(scene_root, occluder_parent, generated_count, skipped_count)
	
	print("Generated: ", generated_count, " occluders")
	print("Skipped (too small): ", skipped_count)
	print("Parent: ", occluder_parent.get_path())
	print("")
	print("IMPORTANT: Occlusion culling requires the following project settings:")
	print("  rendering/occlusion_culling/occlusion_rays_per_octant = 6")
	print("  rendering/occlusion_culling/occlusion_culler_size = 128")
	print("")
	print("=== Occluder generation complete ===")

func _generate_from_node(node: Node, parent: Node, generated: int, skipped: int):
	# Check if this is a wall/pillar CSGBox3D (exclude subtraction/hole pieces)
	if node is CSGBox3D:
		var csg := node as CSGBox3D
		# Skip subtraction pieces (operation = 1 = Subtraction)
		# Skip pieces that are too small to occlude
		if csg.operation != CSGShape3D.OPERATION_SUBTRACTION:
			var size := csg.size
			var longest = max(size.x, max(size.y, size.z))
			if longest >= MIN_OCCLUDER_SIZE:
				_create_occluder(csg, parent)
				generated += 1
			else:
				skipped += 1
		else:
			skipped += 1
	
	# Recurse into children
	for child in node.get_children():
		_generate_from_node(child, parent, generated, skipped)

func _find_or_create_parent(root: Node) -> Node:
	for child in root.get_children():
		if child.name == OCCLUDER_PARENT_NAME:
			print("Found existing Occluders node.")
			return child
	
	var parent := Node3D.new()
	parent.name = OCCLUDER_PARENT_NAME
	parent.set_script(null)  # No script needed
	
	# Add as first child so it's visually at the top in the scene tree
	root.add_child(parent, true)
	if Engine.is_editor_hint():
		parent.set_owner(root)
	
	print("Created Occluders node.")
	return parent

func _create_occluder(csg: CSGBox3D, parent: Node) -> void:
	var occluder := OccluderInstance3D.new()
	occluder.name = "Occluder_" + csg.name
	
	# Create a box-shaped occluder matching the CSG piece
	var shape := OccluderShape3D.new()
	shape.set_as_vertices_and_indices(
		PackedVector3Array([]),  # auto-generated from AABB
		PackedInt32Array([])
	)
	
	# Use a box occluder matching the CSG box dimensions
	var box_shape := OccluderShapeBox3D.new()
	box_shape.size = csg.size
	occluder.set_occluder_shape(box_shape)
	
	# Match the CSG node's global transform
	var global_transform := csg.global_transform
	occluder.global_transform = global_transform
	
	# Bake to static mode (not dynamic) for best performance
	occluder.bake_mask = 1  # Layer 1 only
	occluder.bake_id = 0
	
	parent.add_child(occluder, true)
	if Engine.is_editor_hint():
		occluder.set_owner(parent.owner)
	
	# Apply voxel resolution
	# Godot 4's occlusion is computed from a signed-distance field
	# at the configured culler size (128^3 by budget spec)
