[gd_scene load_steps=8 format=3 uid="uid://dp478jyugrn7o"]

[ext_resource type="Script" path="res://Player.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://d0ypm0v45pwdv" path="res://art/player.glb" id="2"]

[sub_resource type="SphereShape3D" id="1"]
radius = 0.792278

[sub_resource type="CylinderShape3D" id="2"]
height = 0.1438
radius = 0.907607

[sub_resource type="CylinderShape3D" id="CylinderShape3D_76fa1"]
height = 0.144
radius = 0.2

[sub_resource type="Animation" id="3"]
length = 1.2
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Pivot/Character:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.22, 0.7, 1.18),
"transitions": PackedFloat32Array(0.435275, 2.21914, 1),
"update": 0,
"values": [Vector3(0, 0.329753, 0), Vector3(0, 0.660351, 0), Vector3(0, 0.349734, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Pivot/Character:rotation_degrees")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0.1, 0.46, 1.18),
"transitions": PackedFloat32Array(0.482968, 0.535887, 1),
"update": 0,
"values": [Vector3(-5.0326, 0, 0), Vector3(10, 0, 0), Vector3(-10, 0, 0)]
}

[sub_resource type="AnimationLibrary" id="AnimationLibrary_aq6tr"]
_data = {
"float": SubResource("3")
}

[node name="Player" type="CharacterBody3D"]
collision_mask = 2147483654
script = ExtResource("1")

[node name="Pivot" type="Node3D" parent="."]

[node name="Character" parent="Pivot" instance=ExtResource("2")]
transform = Transform3D(1, 0, 0, 0, 0.984808, 0.173648, 0, -0.173648, 0.984808, 0, 0.349734, 0)

[node name="CollisionShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.771765, 0)
shape = SubResource("1")

[node name="MobDetector" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 2
monitorable = false

[node name="CollisionShape" type="CollisionShape3D" parent="MobDetector"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.06491, 0)
shape = SubResource("2")

[node name="CollisionShape2" type="CollisionShape3D" parent="MobDetector"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.455268, 0.95, 1.1423)
shape = SubResource("CylinderShape3D_76fa1")

[node name="CollisionShape3" type="CollisionShape3D" parent="MobDetector"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.716365, 0.95, 1.71889)
shape = SubResource("CylinderShape3D_76fa1")

[node name="CollisionShape4" type="CollisionShape3D" parent="MobDetector"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.730546, 0.95, 1.69713)
shape = SubResource("CylinderShape3D_76fa1")

[node name="CollisionShape5" type="CollisionShape3D" parent="MobDetector"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.45857, 0.95, 1.15318)
shape = SubResource("CylinderShape3D_76fa1")

[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_aq6tr")
}
autoplay = "float"

[connection signal="body_entered" from="MobDetector" to="." method="_on_MobDetector_body_entered"]


extends CharacterBody3D

signal hit

## How fast the player moves in meters per second.
@export var speed = 14
## Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20
## Vertical impulse applied to the character upon bouncing over a mob in meters per second.
@export var bounce_impulse = 16
## The downward acceleration when in the air, in meters per second.
@export var fall_acceleration = 75

func _physics_process(delta):
	var direction = Vector3.ZERO
	if Input.is_action_pressed("move_right"):
		direction.x += 1
	if Input.is_action_pressed("move_left"):
		direction.x -= 1
	if Input.is_action_pressed("move_back"):
		direction.z += 1
	if Input.is_action_pressed("move_forward"):
		direction.z -= 1

	if direction != Vector3.ZERO:
		# In the lines below, we turn the character when moving and make the animation play faster.
		direction = direction.normalized()
		# Setting the basis property will affect the rotation of the node.
		basis = Basis.looking_at(direction)
		$AnimationPlayer.speed_scale = 4
	else:
		$AnimationPlayer.speed_scale = 1

	velocity.x = direction.x * speed
	velocity.z = direction.z * speed

	# Jumping.
	if is_on_floor() and Input.is_action_just_pressed("jump"):
		velocity.y += jump_impulse

	# We apply gravity every frame so the character always collides with the ground when moving.
	# This is necessary for the is_on_floor() function to work as a body can always detect
	# the floor, walls, etc. when a collision happens the same frame.
	velocity.y -= fall_acceleration * delta
	move_and_slide()

	# Here, we check if we landed on top of a mob and if so, we kill it and bounce.
	# With move_and_slide(), Godot makes the body move sometimes multiple times in a row to
	# smooth out the character's motion. So we have to loop over all collisions that may have
	# happened.
	# If there are no "slides" this frame, the loop below won't run.
	for index in range(get_slide_collision_count()):
		var collision = get_slide_collision(index)
		if collision.get_collider().is_in_group("mob"):
			var mob = collision.get_collider()
			if Vector3.UP.dot(collision.get_normal()) > 0.1:
				mob.squash()
				velocity.y = bounce_impulse
				# Prevent this block from running more than once,
				# which would award the player more than 1 point for squashing a single mob.
				break

	# This makes the character follow a nice arc when jumping
	rotation.x = PI / 6 * velocity.y / jump_impulse

func die():
	hit.emit()
	queue_free()

func _on_MobDetector_body_entered(_body):
	die()


extends CharacterBody3D

# Emitted when the player jumped on the mob.
signal squashed

## Minimum speed of the mob in meters per second.
@export var min_speed = 10
## Maximum speed of the mob in meters per second.
@export var max_speed = 18

func _physics_process(_delta):
	move_and_slide()

func initialize(start_position, player_position):
	look_at_from_position(start_position, player_position, Vector3.UP)
	rotate_y(randf_range(-PI / 4, PI / 4))

	var random_speed = randf_range(min_speed, max_speed)
	# We calculate a forward velocity first, which represents the speed.
	velocity = Vector3.FORWARD * random_speed
	# We then rotate the vector based on the mob's Y rotation to move in the direction it's looking.
	velocity = velocity.rotated(Vector3.UP, rotation.y)

	$AnimationPlayer.speed_scale = random_speed / min_speed

func squash():
	squashed.emit()
	queue_free()

func _on_visible_on_screen_notifier_screen_exited():
	queue_free()


extends Camera3D

const MOUSE_SENSITIVITY = 0.002
const MOVE_SPEED = 1.5

var rot := Vector3()
var velocity := Vector3()

func _ready() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event: InputEvent) -> void:
	# Mouse look (only if the mouse is captured).
	if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
		# Horizontal mouse look.
		rot.y -= event.relative.x * MOUSE_SENSITIVITY
		# Vertical mouse look.
		rot.x = clamp(rot.x - event.relative.y * MOUSE_SENSITIVITY, -1.57, 1.57)
		transform.basis = Basis.from_euler(rot)

	if event.is_action_pressed("toggle_mouse_capture"):
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
		else:
			Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _process(delta: float) -> void:
	var motion := Vector3(
			Input.get_axis(&"move_left", &"move_right"),
			0,
			Input.get_axis(&"move_forward", &"move_back")
	)

	# Normalize motion to prevent diagonal movement from being
	# `sqrt(2)` times faster than straight movement.
	motion = motion.normalized()

	velocity += MOVE_SPEED * delta * (transform.basis * motion)
	velocity *= 0.85
	position += velocity


extends CharacterBody3D

const MAX_SPEED = 3.5
const JUMP_SPEED = 6.5
const ACCELERATION = 4
const DECELERATION = 4

@onready var camera: Camera3D = $Target/Camera3D
@onready var gravity := float(-ProjectSettings.get_setting("physics/3d/default_gravity"))
@onready var start_position := position

func _physics_process(delta: float) -> void:
	if Input.is_action_just_pressed(&"exit"):
		get_tree().quit()
	if Input.is_action_just_pressed(&"reset_position") or global_position.y < - 6:
		# Pressed the reset key or fell off the ground.
		position = start_position
		velocity = Vector3.ZERO

	var dir := Vector3()
	dir.x = Input.get_axis(&"move_left", &"move_right")
	dir.z = Input.get_axis(&"move_forward", &"move_back")

	# Get the camera's transform basis, but remove the X rotation such
	# that the Y axis is up and Z is horizontal.
	var cam_basis := camera.global_transform.basis
	cam_basis = cam_basis.rotated(cam_basis.x, -cam_basis.get_euler().x)
	dir = cam_basis * dir

	# Limit the input to a length of 1. `length_squared()` is faster to check
	# than `length()`.
	if dir.length_squared() > 1:
		dir /= dir.length()

	# Apply gravity.
	velocity.y += delta * gravity

	# Using only the horizontal velocity, interpolate towards the input.
	var hvel := velocity
	hvel.y = 0

	var target := dir * MAX_SPEED
	var acceleration := 0.0
	if dir.dot(hvel) > 0:
		acceleration = ACCELERATION
	else:
		acceleration = DECELERATION

	hvel = hvel.lerp(target, acceleration * delta)

	# Assign hvel's values back to velocity, and then move.
	velocity.x = hvel.x
	velocity.z = hvel.z
	# TODO: This information should be set to the CharacterBody properties instead of arguments: , Vector3.UP
	# TODO: Rename velocity to linear_velocity in the rest of the script.
	move_and_slide()

	# TODO: This information should be set to the CharacterBody properties instead of arguments.
	# Jumping code. is_on_floor() must come after move_and_slide().
	if is_on_floor() and Input.is_action_pressed(&"jump"):
		velocity.y = JUMP_SPEED

func _on_tcube_body_entered(body: PhysicsBody3D) -> void:
	if body == self:
		$WinText.show()


extends Camera3D

@export var min_distance := 0.5
@export var max_distance := 3.0
@export var angle_v_adjust := 0.0

var collision_exception := []
var max_height := 2.0
var min_height := 0

@onready var target_node: Node3D = get_parent()

func _ready() -> void:
	collision_exception.append(target_node.get_parent().get_rid())
	# Detaches the camera transform from the parent spatial node.
	top_level = true

func _physics_process(_delta: float) -> void:
	var target_pos := target_node.global_transform.origin
	var camera_pos := global_transform.origin

	var delta_pos := camera_pos - target_pos

	# Regular delta follow.

	# Check ranges.
	if delta_pos.length() < min_distance:
		delta_pos = delta_pos.normalized() * min_distance
	elif delta_pos.length() > max_distance:
		delta_pos = delta_pos.normalized() * max_distance

	# Check upper and lower height.
	if delta_pos.y > max_height:
		delta_pos.y = max_height
	if delta_pos.y < min_height:
		delta_pos.y = min_height

	camera_pos = target_pos + delta_pos

	look_at_from_position(camera_pos, target_pos, Vector3.UP)

	# Turn a little up or down.
	var t := transform
	t.basis = Basis(t.basis[0], deg_to_rad(angle_v_adjust)) * t.basis
	transform = t


extends Button

@export_file var scene_to_change_to: String = ""

func _ready():
	pressed.connect(change_scene)

func change_scene():
	if not scene_to_change_to.is_empty():
		get_tree().change_scene_to_file(scene_to_change_to)


@tool
extends Node3D

# A FABRIK IK chain with a middle joint helper.

# The delta/tolerance for the bone chain (how do the bones need to be before it is considered satisfactory)
const CHAIN_TOLERANCE = 0.01
# The amount of interations the bone chain will go through in an attempt to get to the target position
const CHAIN_MAX_ITER = 10

@export var skeleton_path: NodePath:
	set(value):
		skeleton_path = value
		# Because get_node doesn't work in the first call, we just want to assign instead
		if first_call:
			return

		if skeleton_path == null:
			if debug_messages:
				printerr(name, " - IK_FABRIK: No Nodepath selected for skeleton_path!")
			return

		var temp = get_node(skeleton_path)
		if temp != null:
			# If it has the method "get_bone_global_pose" it is likely a Skeleton3D
			if temp.has_method("get_bone_global_pose"):
				skeleton = temp
				bone_IDs = {}

				# (Delete all of the old bone nodes and) Make all of the bone nodes for each bone in the IK chain
				_make_bone_nodes()

				if debug_messages:
					printerr(name, " - IK_FABRIK: Attached to a new skeleton")
			# If not, then it's (likely) not a Skeleton3D node
			else:
				skeleton = null
				if debug_messages:
					printerr(name, " - IK_FABRIK: skeleton_path does not point to a skeleton!")
		else:
			if debug_messages:
				printerr(name, " - IK_FABRIK: No Nodepath selected for skeleton_path!")

@export var bones_in_chain: PackedStringArray:
	set(value):
		bones_in_chain = value
		_make_bone_nodes()

@export var bones_in_chain_lengths: PackedFloat32Array:
	set(value):
		bones_in_chain_lengths = value
		total_length = INF

@export_enum("_process", "_physics_process", "_notification", "none") var update_mode: int = 0:
	set(value):
		update_mode = value

		set_process(false)
		set_physics_process(false)
		set_notify_transform(false)

		if update_mode == 0:
			set_process(true)
		elif update_mode == 1:
			set_process(true)
		elif update_mode == 2:
			set_notify_transform(true)
		else:
			if debug_messages:
				printerr(name, " - IK_FABRIK: Unknown update mode. NOT updating skeleton")
			return

var target: Node3D = null

var skeleton: Skeleton3D

# A dictionary holding all of the bone IDs (from the skeleton) and a dictionary holding
# all of the bone helper nodes
var bone_IDs = {}
var bone_nodes = {}

# The position of the origin
var chain_origin = Vector3()
# The combined length of every bone in the bone chain
var total_length = INF
# The amount of iterations we've been through, and whether or not we want to limit our solver to CHAIN_MAX_ITER
# amounts of interations.
@export var chain_iterations: int = 0
@export var limit_chain_iterations: bool = true
# Should we reset chain_iterations on movement during our update method?
@export var reset_iterations_on_update: bool = false

# A boolean to track whether or not we want to move the middle joint towards middle joint target.
@export var use_middle_joint_target: bool = false
var middle_joint_target: Node3D = null

# Have we called _set_skeleton_path or not already. Due to some issues using exported NodePaths,
# we need to ignore the first _set_skeleton_path call.
var first_call = true

# A boolean to track whether or not we want to print debug messages
var debug_messages = false

func _ready():
	if target == null:
		# NOTE: You MUST have a node called Target as a child of this node!
		# So we create one if one doesn't already exist.
		if not has_node("Target"):
			target = Node3D.new()
			add_child(target)

			if Engine.is_editor_hint():
				if get_tree() != null:
					if get_tree().edited_scene_root != null:
						target.set_owner(get_tree().edited_scene_root)

			target.name = &"Target"
		else:
			target = $Target

		# If we are in the editor, we want to make a sphere at this node
		if Engine.is_editor_hint():
			_make_editor_sphere_at_node(target, Color.MAGENTA)

	if middle_joint_target == null:
		if not has_node("MiddleJoint"):
			middle_joint_target = Node3D.new()
			add_child(middle_joint_target)

			if Engine.is_editor_hint():
				if get_tree() != null:
					if get_tree().edited_scene_root != null:
						middle_joint_target.set_owner(get_tree().edited_scene_root)

			middle_joint_target.name = &"MiddleJoint"
		else:
			middle_joint_target = get_node(^"MiddleJoint")

		# If we are in the editor, we want to make a sphere at this node
		if Engine.is_editor_hint():
			_make_editor_sphere_at_node(middle_joint_target, Color(1, 0.24, 1, 1))

	# Make all of the bone nodes for each bone in the IK chain
	_make_bone_nodes()

	# Make sure we're using the right update mode
	update_mode = update_mode

# Various upate methods
func _process(_delta):
	if reset_iterations_on_update:
		chain_iterations = 0
	update_skeleton()

func _physics_process(_delta):
	if reset_iterations_on_update:
		chain_iterations = 0
	update_skeleton()

func _notification(what):
	if what == NOTIFICATION_TRANSFORM_CHANGED:
		if reset_iterations_on_update:
			chain_iterations = 0
		update_skeleton()

############# IK SOLVER RELATED FUNCTIONS #############

func update_skeleton():
	#### ERROR CHECKING conditions
	if first_call:
		skeleton_path = skeleton_path
		first_call = false

		if skeleton == null:
			skeleton_path = skeleton_path

		return

	if bones_in_chain == null:
		if debug_messages:
			printerr(name, " - IK_FABRIK: No Bones in IK chain defined!")
		return
	if bones_in_chain_lengths == null:
		if debug_messages:
			printerr(name, " - IK_FABRIK: No Bone3D lengths in IK chain defined!")
		return

	if bones_in_chain.size() != bones_in_chain_lengths.size():
		if debug_messages:
			printerr(name, " - IK_FABRIK: bones_in_chain and bones_in_chain_lengths!")
		return

	################################

	# Set all of the bone IDs in bone_IDs, if they are not already made
	var i = 0
	if bone_IDs.size() <= 0:
		for bone_name in bones_in_chain:
			bone_IDs[bone_name] = skeleton.find_bone(bone_name)

			# Set the bone node to the currect bone position
			bone_nodes[i].global_transform = get_bone_transform(i)
			# If this is not the last bone in the bone chain, make it look at the next bone in the bone chain
			if i < bone_IDs.size()-1:
				bone_nodes[i].look_at(get_bone_transform(i+1).origin + skeleton.global_transform.origin, Vector3.UP)

			i += 1

	# Set the total length of the bone chain, if it is not already set
	if total_length == INF:
		total_length = 0
		for bone_length in bones_in_chain_lengths:
			total_length += bone_length

	# Solve the bone chain
	solve_chain()


@tool
extends Node3D

@export var skeleton_path: NodePath:
	set(value):
		# Assign skeleton_path to whatever value is passed.
		skeleton_path = value
		# Because get_node doesn't work in the first call, we just want to assign instead.
		# This is to get around a issue with NodePaths exposed to the editor.
		if first_call:
			return
		_setup_skeleton_path()
@export var bone_name: String = ""
@export_enum("_process", "_physics_process", "_notification", "none") var update_mode: int = 0:
	set(value):
		update_mode = value

		# Set all of our processes to false.
		set_process(false)
		set_physics_process(false)
		set_notify_transform(false)

		# Based on the value of passed to update, enable the correct process.
		if update_mode == 0:
			set_process(true)
			if debug_messages:
				print(name, " - IK_LookAt: updating skeleton using _process...")
		elif update_mode == 1:
			set_physics_process(true)
			if debug_messages:
				print(name, " - IK_LookAt: updating skeleton using _physics_process...")
		elif update_mode == 2:
			set_notify_transform(true)
			if debug_messages:
				print(name, " - IK_LookAt: updating skeleton using _notification...")
		else:
			if debug_messages:
				print(name, " - IK_LookAt: NOT updating skeleton due to unknown update method...")

@export_enum("X-up", "Y-up", "Z-up") var look_at_axis: int = 1
@export_range(0.0, 1.0, 0.001) var interpolation: float = 1.0
@export var use_our_rotation_x: bool = false
@export var use_our_rotation_y: bool = false
@export var use_our_rotation_z: bool = false
@export var use_negative_our_rot: bool = false
@export var additional_rotation: Vector3 = Vector3()
@export var position_using_additional_bone: bool = false
@export var additional_bone_name: String = ""
@export var additional_bone_length: float = 1
@export var debug_messages: bool = false

var skeleton_to_use: Skeleton3D = null
var first_call: bool = true
var _editor_indicator: Node3D = null

func _ready():
	set_process(false)
	set_physics_process(false)
	set_notify_transform(false)

	if update_mode == 0:
		set_process(true)
	elif update_mode == 1:
		set_physics_process(true)
	elif update_mode == 2:
		set_notify_transform(true)
	else:
		if debug_messages:
			print(name, " - IK_LookAt: Unknown update mode. NOT updating skeleton")

	if Engine.is_editor_hint():
		_setup_for_editor()

func _process(_delta):
	update_skeleton()

func _physics_process(_delta):
	update_skeleton()

func _notification(what):
	if what == NOTIFICATION_TRANSFORM_CHANGED:
		update_skeleton()

func update_skeleton():
	# NOTE: Because get_node doesn't work in _ready, we need to skip
	# a call before doing anything.
	if first_call:
		first_call = false
		if skeleton_to_use == null:
			_setup_skeleton_path()

	# If we do not have a skeleton and/or we're not supposed to update, then return.
	if skeleton_to_use == null:
		return
	if update_mode >= 3:
		return

	# Get the bone index.
	var bone: int = skeleton_to_use.find_bone(bone_name)

	# If no bone is found (-1), then return and optionally printan error.
	if bone == -1:
		if debug_messages:
			print(name, " - IK_LookAt: No bone in skeleton found with name [", bone_name, "]!")
		return

	# get the bone's global transform pose.
	var rest = skeleton_to_use.get_bone_global_pose(bone)

	# Convert our position relative to the skeleton's transform.
	var target_pos = global_transform.origin * skeleton_to_use.global_transform

	# Call helper's look_at function with the chosen up axis.
	if look_at_axis == 0:
		rest = rest.looking_at(target_pos, Vector3.RIGHT)
	elif look_at_axis == 1:
		rest = rest.looking_at(target_pos, Vector3.UP)
	elif look_at_axis == 2:
		rest = rest.looking_at(target_pos, Vector3.FORWARD)
	else:
		rest = rest.looking_at(target_pos, Vector3.UP)
		if debug_messages:
			print(name, " - IK_LookAt: Unknown look_at_axis value!")

	# Get the rotation euler of the bone and of this node.
	var rest_euler = rest.basis.get_euler()
	var self_euler = global_transform.basis.orthonormalized().get_euler()

	# Flip the rotation euler if using negative rotation.
	if use_negative_our_rot:
		self_euler = -self_euler

	# Apply this node's rotation euler on each axis, if wanted/required.
	if use_our_rotation_x:
		rest_euler.x = self_euler.x
	if use_our_rotation_y:
		rest_euler.y = self_euler.y
	if use_our_rotation_z:
		rest_euler.z = self_euler.z

	# Make a new basis with the, potentially, changed euler angles.
	rest.basis = Basis.from_euler(rest_euler)

	# Apply additional rotation stored in additional_rotation to the bone.
	if additional_rotation != Vector3.ZERO:
		rest.basis = rest.basis.rotated(rest.basis.x, deg_to_rad(additional_rotation.x))
		rest.basis = rest.basis.rotated(rest.basis.y, deg_to_rad(additional_rotation.y))
		rest.basis = rest.basis.rotated(rest.basis.z, deg_to_rad(additional_rotation.z))

	# If the position is set using an additional bone, then set the origin
	# based on that bone and its length.
	if position_using_additional_bone:
		var additional_bone_id = skeleton_to_use.find_bone(additional_bone_name)
		var additional_bone_pos = skeleton_to_use.get_bone_global_pose(additional_bone_id)
		rest.origin = (
			additional_bone_pos.origin
			- additional_bone_pos.basis.z.normalized() * additional_bone_length
		)

	# Finally, apply the new rotation to the bone in the skeleton.
	skeleton_to_use.set_bone_global_pose_override(bone, rest, interpolation, true)

func _setup_for_editor():
	# To see the target in the editor, let's create a MeshInstance3D,
	# add it as a child of this node, and name it.
	_editor_indicator = MeshInstance3D.new()
	add_child(_editor_indicator)
	_editor_indicator.name = &"(EditorOnly) Visual indicator"

	# Make a sphere mesh for the MeshInstance3D
	var indicator_mesh = SphereMesh.new()
	indicator_mesh.radius = 0.1
	indicator_mesh.height = 0.2
	indicator_mesh.radial_segments = 8
	indicator_mesh.rings = 4

	# Create a new StandardMaterial3D for the sphere and give it the editor
	# gizmo texture so it is textured.
	var indicator_material = StandardMaterial3D.new()
	indicator_material.flags_unshaded = true
	indicator_material.albedo_texture = preload("editor_gizmo_texture.png")
	indicator_material.albedo_color = Color(1, 0.5, 0, 1)

	# Assign the material and mesh to the MeshInstance3D.
	indicator_mesh.material = indicator_material
	_editor_indicator.mesh = indicator_mesh

func _setup_skeleton_path():
	if skeleton_path == null:
		if debug_messages:
			print(name, " - IK_LookAt: No Nodepath selected for skeleton_path!")
		return

	# Get the node at that location, if there is one.
	var temp = get_node(skeleton_path)
	if temp != null:
		if temp is Skeleton3D:
			skeleton_to_use = temp
			if debug_messages:
				print(name, " - IK_LookAt: attached to (new) skeleton")
		else:
			skeleton_to_use = null
			if debug_messages:
				print(name, " - IK_LookAt: skeleton_path does not point to a skeleton!")
	else:
		if debug_messages:
			print(name, " - IK_LookAt: No Nodepath selected for skeleton_path!")


[gd_scene load_steps=6 format=3 uid="uid://d36uvu4r5qtpi"]

[ext_resource type="Script" path="res://fps/simple_bullet.gd" id="1"]

[sub_resource type="PhysicsMaterial" id="1"]
bounce = 0.5

[sub_resource type="SphereMesh" id="2"]

[sub_resource type="StandardMaterial3D" id="3"]
albedo_color = Color(0.769531, 0.486969, 0, 1)
emission_enabled = true
emission = Color(1, 0.445313, 0, 1)
emission_energy = 1.8

[sub_resource type="SphereShape3D" id="4"]
radius = 0.4

[node name="SimpleBullet" type="RigidBody3D"]
mass = 2.0
physics_material_override = SubResource( "1" )
gravity_scale = 3.0
continuous_cd = true
can_sleep = false
linear_damp = 0.4
script = ExtResource( "1" )

[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(0.4, 0, 0, 0, 0.4, 0, 0, 0, 0.4, 0, 0, 0)
cast_shadow = 0
mesh = SubResource( "2" )
surface_material_override/0 = SubResource( "3" )

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource( "4" )


extends RigidBody3D

const DESPAWN_TIME = 5

var timer = 0

func _ready():
	set_physics_process(true);

func _physics_process(delta):
	timer += delta
	if timer > DESPAWN_TIME:
		queue_free()
		timer = 0


[node name="Wall4" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(1, 0, -4.76837e-07, 0, 4, 0, 1.19209e-07, 0, 4, -19.9997, 8.00032, -21.9995)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall4"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall4/StaticBody3D"]
shape = SubResource("6")

[node name="Wall5" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(-1.62921e-07, 0, -4, 0, 4, 0, 1, 0, -6.51683e-07, -9.9997, 8.00032, -27.9995)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall5"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall5/StaticBody3D"]
shape = SubResource("6")

[node name="Wall6" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(-1, 0, 8.26528e-07, 0, 4, 0, -2.06632e-07, 0, -4, 0.000319004, 8.00032, -21.9995)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall6"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall6/StaticBody3D"]
shape = SubResource("6")

[node name="Wall7" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(-1.62921e-07, 0, -4, 0, 4, 0, 1, 0, -6.51683e-07, 10.0003, 8.00032, -15.9995)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall7"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall7/StaticBody3D"]
shape = SubResource("6")

[node name="Wall9" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(1, 0, -4.76837e-07, 0, 4, 0, 1.19209e-07, 0, 4, 25.0003, 8.00032, -25.9995)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall9"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall9/StaticBody3D"]
shape = SubResource("6")

[node name="Wall10" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(0.573577, 0, 3.27661, 0, 4, 0, -0.819152, 0, 2.29431, 23.0003, 8.00032, 3.00049)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall10"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall10/StaticBody3D"]
shape = SubResource("6")

[node name="Wall11" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(-0.819152, 0, 2.29431, 0, 4, 0, -0.573577, 0, -3.27661, 22.2126, 8.00032, 14.7123)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall11"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall11/StaticBody3D"]
shape = SubResource("6")

[node name="Wall12" type="MeshInstance3D" parent="Level/Walls"]
transform = Transform3D(-0.627507, 2.10616, 2.29431, 0.642788, 3.06418, 0, -0.439385, 1.47475, -3.27661, 14.8402, 8.00032, 9.55015)
mesh = SubResource("4")
surface_material_override/0 = SubResource("7")

[node name="StaticBody3D" type="StaticBody3D" parent="Level/Walls/Wall12"]

[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/Walls/Wall12/StaticBody3D"]
shape = SubResource("6")

[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.388878, -0.754027, 0.529355, 0, 0.574581, 0.818448, -0.921289, -0.318277, 0.223442, -9.77531, 11.5204, 11.766)
light_color = Color(1, 0.925598, 0.820313, 1)
shadow_enabled = true
directional_shadow_mode = 0

[node name="Control" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0

[node name="Panel" type="Panel" parent="Control"]
modulate = Color(1, 1, 1, 0.784314)
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -2.0
offset_top = -94.0
offset_right = 4.0
grow_horizontal = 2
grow_vertical = 0

[node name="Label" type="Label" parent="Control/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "F.A.B.R.I.K IK Example use case: Dynamic FPS Animations
Controls: WASD/Arrows to move, left click to fire, right click to look down sights, Q/E to lean left/right
Escape to free/lock mouse cursor"
horizontal_alignment = 1

[node name="ButtonPrev" type="Button" parent="Control"]
layout_mode = 0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = -60.0
offset_right = 129.0
offset_bottom = -10.0
text = "Previous scene"
script = ExtResource("2")
scene_to_change_to = "res://skeleton_ik.tscn"

[node name="Crosshair" type="Control" parent="Control"]
modulate = Color(1, 1, 1, 0.784314)
anchors_preset = 0
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 20.0

[node name="ColorRect" type="ColorRect" parent="Control/Crosshair"]
layout_mode = 0
offset_left = 19.0
offset_right = 21.0
offset_bottom = 40.0

[node name="ColorRect2" type="ColorRect" parent="Control/Crosshair"]
layout_mode = 0
offset_left = 40.0
offset_top = 18.0
offset_right = 42.0
offset_bottom = 58.0
rotation = 90.0

[node name="CharacterBody3D" type="CharacterBody3D" parent="."]
script = ExtResource("3")


extends CharacterBody3D

# Moves the player

@export_range(1, 2) var player_id := 1
@export var walk_speed := 2.0

func _physics_process(_delta: float) -> void:
	var move_direction := Input.get_vector(
			&"move_left_player" + str(player_id),
			&"move_right_player" + str(player_id),
			&"move_up_player" + str(player_id),
			&"move_down_player" + str(player_id),
	)
	velocity.x += move_direction.x * walk_speed
	velocity.z += move_direction.y * walk_speed

	# Apply friction.
	velocity *= 0.9

	move_and_slide()


extends Node3D
# Handle the motion of both player cameras as well as communication with the
# SplitScreen shader to achieve the dynamic split screen effet
#
# Cameras are place on the segment joining the two players, either in the middle
# if players are close enough or at a fixed distance if they are not.
# In the first case, both cameras being at the same location, only the view of
# the first one is used for the entire screen thus allowing the players to play
# on a unsplit screen.
# In the second case, the screen is split in two with a line perpendicular to the
# segement joining the two players.
#
# The points of customization are:
#   max_separation: the distance between players at which the view starts to split
#   split_line_thickness: the thickness of the split line in pixels
#   split_line_color: color of the split line
#   adaptive_split_line_thickness: if true, the split line thickness will vary
#       depending on the distance between players. If false, the thickness will
#       be constant and equal to split_line_thickness

@export var max_separation := 20.0
@export var split_line_thickness := 3.0
@export var split_line_color := Color.BLACK
@export var adaptive_split_line_thickness := true

@onready var player1: CharacterBody3D = $"../Player1"
@onready var player2: CharacterBody3D = $"../Player2"
@onready var view: TextureRect = $View
@onready var viewport1: SubViewport = $Viewport1
@onready var viewport2: SubViewport = $Viewport2
@onready var camera1: Camera3D = viewport1.get_node(^"Camera1")
@onready var camera2: Camera3D = viewport2.get_node(^"Camera2")

var viewport_base_height := int(ProjectSettings.get_setting("display/window/size/viewport_height"))

func _ready() -> void:
	_on_size_changed()
	_update_splitscreen()

	get_viewport().size_changed.connect(_on_size_changed)

	view.material.set_shader_parameter("viewport1", viewport1.get_texture())
	view.material.set_shader_parameter("viewport2", viewport2.get_texture())

func _process(_delta: float) -> void:
	_move_cameras()
	_update_splitscreen()

func _move_cameras() -> void:
	var position_difference := _get_position_difference_in_world()

	var distance := clampf(_get_horizontal_length(position_difference), 0, max_separation)

	position_difference = position_difference.normalized() * distance

	camera1.position.x = player1.position.x + position_difference.x / 2.0
	camera1.position.z = player1.position.z + position_difference.z / 2.0

	camera2.position.x = player2.position.x - position_difference.x / 2.0
	camera2.position.z = player2.position.z - position_difference.z / 2.0

func _update_splitscreen() -> void:
	var screen_size := get_viewport().get_visible_rect().size
	var player1_position := camera1.unproject_position(player1.position) / screen_size
	var player2_position := camera2.unproject_position(player2.position) / screen_size

	var thickness := 0.0
	if adaptive_split_line_thickness:
		var position_difference := _get_position_difference_in_world()
		var distance := _get_horizontal_length(position_difference)
		thickness = lerpf(0, split_line_thickness, (distance - max_separation) / max_separation)
		thickness = clampf(thickness, 0, split_line_thickness)
	else:
		thickness = split_line_thickness

	view.material.set_shader_parameter("split_active", _is_split_state())
	view.material.set_shader_parameter("player1_position", player1_position)
	view.material.set_shader_parameter("player2_position", player2_position)
	view.material.set_shader_parameter("split_line_thickness", thickness)
	view.material.set_shader_parameter("split_line_color", split_line_color)

## Returns `true` if split screen is active (which occurs when players are
## too far apart from each other), `false` otherwise.
## Only the horizontal components (x, z) are used for distance computation.
func _is_split_state() -> bool:
	var position_difference := _get_position_difference_in_world()
	var separation_distance := _get_horizontal_length(position_difference)
	return separation_distance > max_separation

func _on_size_changed() -> void:
	var screen_size := get_viewport().get_visible_rect().size

	$Viewport1.size = screen_size
	$Viewport2.size = screen_size

	view.material.set_shader_parameter("viewport_size", screen_size)

func _get_position_difference_in_world() -> Vector3:
	return player2.position - player1.position

func _get_horizontal_length(vec: Vector3) -> float:
	return Vector2(vec.x, vec.z).length()


[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group4/Wall5"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group4/Wall5" groups=["walls"]]
material_override = SubResource("33")
mesh = SubResource("11")

[node name="Wall6" type="StaticBody3D" parent="Walls/Group4"]
transform = Transform3D(0.155702, 0, -0.987804, 0, 1, 0, 0.987804, 0, 0.155702, -1.39999, 0.5, -3.24225)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group4/Wall6"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group4/Wall6" groups=["walls"]]
material_override = SubResource("34")
mesh = SubResource("11")

[node name="Group5" type="Node3D" parent="Walls"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 14)

[node name="Wall1" type="StaticBody3D" parent="Walls/Group5"]
transform = Transform3D(0.999549, 0, 0.0300306, 0, 1, 0, -0.0300306, 0, 0.999549, -1.87929, 0.5, -3.04373)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group5/Wall1"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group5/Wall1" groups=["walls"]]
material_override = SubResource("35")
mesh = SubResource("11")

[node name="Wall2" type="StaticBody3D" parent="Walls/Group5"]
transform = Transform3D(0.999549, 0, 0.0300306, 0, 1, 0, -0.0300306, 0, 0.999549, 6.39778, 0.5, -1.68458)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group5/Wall2"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group5/Wall2" groups=["walls"]]
material_override = SubResource("36")
mesh = SubResource("11")

[node name="Wall3" type="StaticBody3D" parent="Walls/Group5"]
transform = Transform3D(0.999549, 0, 0.0300306, 0, 1, 0, -0.0300306, 0, 0.999549, -4.44285, 0.5, -6.669)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group5/Wall3"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group5/Wall3" groups=["walls"]]
material_override = SubResource("37")
mesh = SubResource("11")

[node name="Wall4" type="StaticBody3D" parent="Walls/Group5"]
transform = Transform3D(0.999549, 0, 0.0300306, 0, 1, 0, -0.0300306, 0, 0.999549, -0.234326, 0.5, 2.05526)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group5/Wall4"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group5/Wall4" groups=["walls"]]
material_override = SubResource("38")
mesh = SubResource("11")

[node name="Wall5" type="StaticBody3D" parent="Walls/Group5"]
transform = Transform3D(0.999549, 0, 0.0300306, 0, 1, 0, -0.0300306, 0, 0.999549, 2.17761, 0.5, -4.76423)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group5/Wall5"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group5/Wall5" groups=["walls"]]
material_override = SubResource("39")
mesh = SubResource("11")

[node name="Wall6" type="StaticBody3D" parent="Walls/Group5"]
transform = Transform3D(0.999549, 0, 0.0300306, 0, 1, 0, -0.0300306, 0, 0.999549, -3.21803, 0.5, 0.834073)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group5/Wall6"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group5/Wall6" groups=["walls"]]
material_override = SubResource("40")
mesh = SubResource("11")

[node name="Group6" type="Node3D" parent="Walls"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 0, -7)

[node name="Wall1" type="StaticBody3D" parent="Walls/Group6"]
transform = Transform3D(0.613129, 0, -0.789983, 0, 1, 0, 0.789983, 0, 0.613129, 1.70841, 0.5, -3.54429)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group6/Wall1"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group6/Wall1" groups=["walls"]]
material_override = SubResource("41")
mesh = SubResource("11")

[node name="Wall2" type="StaticBody3D" parent="Walls/Group6"]
transform = Transform3D(0.613129, 0, -0.789983, 0, 1, 0, 0.789983, 0, 0.613129, 5.48642, 0.5, 3.94462)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group6/Wall2"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group6/Wall2" groups=["walls"]]
material_override = SubResource("42")
mesh = SubResource("11")

[node name="Wall3" type="StaticBody3D" parent="Walls/Group6"]
transform = Transform3D(0.613129, 0, -0.789983, 0, 1, 0, 0.789983, 0, 0.613129, 3.1275, 0.5, -7.7515)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group6/Wall3"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group6/Wall3" groups=["walls"]]
material_override = SubResource("43")
mesh = SubResource("11")

[node name="Wall4" type="StaticBody3D" parent="Walls/Group6"]
transform = Transform3D(0.613129, 0, -0.789983, 0, 1, 0, 0.789983, 0, 0.613129, -1.44268, 0.5, 0.788867)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group6/Wall4"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group6/Wall4" groups=["walls"]]
material_override = SubResource("44")
mesh = SubResource("11")


[node name="Wall5" type="StaticBody3D" parent="Walls/Group6"]
transform = Transform3D(0.613129, 0, -0.789983, 0, 1, 0, 0.789983, 0, 0.613129, 5.48868, 0.5, -1.27975)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group6/Wall5"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group6/Wall5" groups=["walls"]]
material_override = SubResource("45")
mesh = SubResource("11")

[node name="Wall6" type="StaticBody3D" parent="Walls/Group6"]
transform = Transform3D(0.613129, 0, -0.789983, 0, 1, 0, 0.789983, 0, 0.613129, -2.2137, 0.5, -2.34152)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group6/Wall6"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group6/Wall6" groups=["walls"]]
material_override = SubResource("46")
mesh = SubResource("11")

[node name="Group7" type="Node3D" parent="Walls"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, -16)

[node name="Wall1" type="StaticBody3D" parent="Walls/Group7"]
transform = Transform3D(-0.999329, 0, -0.0366257, 0, 1, 0, 0.0366257, 0, -0.999329, 1.73055, 0.5, 2.47421)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group7/Wall1"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group7/Wall1" groups=["walls"]]
material_override = SubResource("47")
mesh = SubResource("11")

[node name="Wall2" type="StaticBody3D" parent="Walls/Group7"]
transform = Transform3D(-0.999329, 0, -0.0366257, 0, 1, 0, 0.0366257, 0, -0.999329, -6.55531, 0.5, 1.16971)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group7/Wall2"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group7/Wall2" groups=["walls"]]
material_override = SubResource("48")
mesh = SubResource("11")

[node name="Wall3" type="StaticBody3D" parent="Walls/Group7"]
transform = Transform3D(-0.999329, 0, -0.0366257, 0, 1, 0, 0.0366257, 0, -0.999329, 4.31798, 0.5, 6.08249)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group7/Wall3"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group7/Wall3" groups=["walls"]]
material_override = SubResource("49")
mesh = SubResource("11")

[node name="Wall4" type="StaticBody3D" parent="Walls/Group7"]
transform = Transform3D(-0.999329, 0, -0.0366257, 0, 1, 0, 0.0366257, 0, -0.999329, 0.0519707, 0.5, -2.61381)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group7/Wall4"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group7/Wall4" groups=["walls"]]
material_override = SubResource("50")
mesh = SubResource("11")

[node name="Wall5" type="StaticBody3D" parent="Walls/Group7"]
transform = Transform3D(-0.999329, 0, -0.0366257, 0, 1, 0, 0.0366257, 0, -0.999329, -2.31492, 0.5, 4.22145)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group7/Wall5"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group7/Wall5" groups=["walls"]]
material_override = SubResource("51")
mesh = SubResource("11")

[node name="Wall6" type="StaticBody3D" parent="Walls/Group7"]
transform = Transform3D(-0.999329, 0, -0.0366257, 0, 1, 0, 0.0366257, 0, -0.999329, 3.04367, 0.5, -1.41234)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Walls/Group7/Wall6"]
shape = SubResource("9")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Walls/Group7/Wall6" groups=["walls"]]
material_override = SubResource("52")
mesh = SubResource("11")


extends Node2D

const PAD_SPEED = 150
const INITIAL_BALL_SPEED = 80.0

var ball_speed := INITIAL_BALL_SPEED
var screen_size := Vector2(640, 400)

# Default ball direction.
var direction := Vector2.LEFT
var pad_size := Vector2(8, 32)

@onready var ball: Sprite2D = $Ball
@onready var left_paddle: Sprite2D = $LeftPaddle
@onready var right_paddle: Sprite2D = $RightPaddle

func _ready() -> void:
	screen_size = get_viewport_rect().size  # Get actual size.
	pad_size = left_paddle.get_texture().get_size()

func _process(delta: float) -> void:
	# Get ball position and pad rectangles.
	var ball_pos := ball.get_position()
	var left_rect := Rect2(left_paddle.get_position() - pad_size * 0.5, pad_size)
	var right_rect := Rect2(right_paddle.get_position() - pad_size * 0.5, pad_size)

	# Integrate new ball postion.
	ball_pos += direction * ball_speed * delta

	# Flip when touching roof or floor.
	if (ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > screen_size.y and direction.y > 0):
		direction.y = -direction.y

	# Flip, change direction and increase speed when touching pads.
	if (left_rect.has_point(ball_pos) and direction.x < 0) or (right_rect.has_point(ball_pos) and direction.x > 0):
		direction.x = -direction.x
		ball_speed *= 1.1
		direction.y = randf() * 2.0 - 1
		direction = direction.normalized()

	# Check gameover.
	if ball_pos.x < 0 or ball_pos.x > screen_size.x:
		ball_pos = screen_size * 0.5
		ball_speed = INITIAL_BALL_SPEED
		direction = Vector2(-1, 0)

	ball.set_position(ball_pos)

	# Move left pad.
	var left_pos := left_paddle.get_position()

	if left_pos.y > 0 and Input.is_action_pressed(&"left_move_up"):
		left_pos.y += -PAD_SPEED * delta
	if left_pos.y < screen_size.y and Input.is_action_pressed(&"left_move_down"):
		left_pos.y += PAD_SPEED * delta

	left_paddle.set_position(left_pos)

	# Move right pad.
	var right_pos := right_paddle.get_position()
	if right_pos.y > 0 and Input.is_action_pressed(&"right_move_up"):
		right_pos.y += -PAD_SPEED * delta
	if right_pos.y < screen_size.y and Input.is_action_pressed(&"right_move_down"):
		right_pos.y += PAD_SPEED * delta

	right_paddle.set_position(right_pos)


# 2D in 3D

A demo showing how a 2D scene can be shown within a 3D scene using viewports.

Language: GDScript

Renderer: Compatibility

Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2803

## How does it work?

The Pong game is rendered to a custom
[`Viewport`](https://docs.godotengine.org/en/latest/classes/class_viewport.html)
node rather than the main Viewport. In the code,
`get_texture()` is called on the Viewport to get a
[`ViewportTexture`](https://docs.godotengine.org/en/latest/classes/class_viewporttexture.html),
which is then assigned to the quad's material's albedo texture.

## Screenshots

![Screenshot](screenshots/2d_in_3d.webp)


[gd_scene load_steps=11 format=3 uid="uid://xwkspajfdmd7"]

[ext_resource type="Script" path="res://3d_in_2d.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://q5bonn8iowbc" path="res://robot_demo.png" id="2"]
[ext_resource type="PackedScene" uid="uid://8nlipuu0ceal" path="res://robot_3d.tscn" id="3"]

[sub_resource type="ViewportTexture" id="ViewportTexture_2squv"]
viewport_path = NodePath("SubViewport")

[sub_resource type="AtlasTexture" id="2"]
atlas = ExtResource("2")
region = Rect2(0, 0, 64, 64)

[sub_resource type="AtlasTexture" id="3"]
atlas = ExtResource("2")
region = Rect2(64, 0, 64, 64)

[sub_resource type="AtlasTexture" id="4"]
atlas = ExtResource("2")
region = Rect2(128, 0, 64, 64)

[sub_resource type="AtlasTexture" id="5"]
atlas = ExtResource("2")
region = Rect2(192, 0, 64, 64)

[sub_resource type="AtlasTexture" id="6"]
atlas = ExtResource("2")
region = Rect2(256, 0, 64, 64)

[sub_resource type="SpriteFrames" id="7"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("2")
}, {
"duration": 1.0,
"texture": SubResource("3")
}, {
"duration": 1.0,
"texture": SubResource("4")
}, {
"duration": 1.0,
"texture": SubResource("5")
}, {
"duration": 1.0,
"texture": SubResource("6")
}],
"loop": true,
"name": &"default",
"speed": 5.0
}]

[node name="3Din2D" type="Node2D"]
script = ExtResource("1")

[node name="SubViewport" type="SubViewport" parent="."]
transparent_bg = true
msaa_3d = 2
size = Vector2i(300, 300)
render_target_update_mode = 4

[node name="Robot3D" parent="SubViewport" instance=ExtResource("3")]

[node name="ViewportSprite" type="Sprite2D" parent="."]
position = Vector2(650, 300)
texture = SubResource("ViewportTexture_2squv")

[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
texture_filter = 1
position = Vector2(350, 300)
scale = Vector2(3, 3)
sprite_frames = SubResource("7")

[node name="Camera2D" type="Camera2D" parent="."]
offset = Vector2(512, 300)


# 3D in 2D

A demo showing how a 3D scene can be shown within a 2D one using viewports.

Language: GDScript

Renderer: Compatibility

Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2804

## How does it work?

The 3D robot is rendered to a custom
[`Viewport`](https://docs.godotengine.org/en/latest/classes/class_viewport.html)
node rather than the main Viewport. In the code,
`get_texture()` is called on the Viewport to get a
[`ViewportTexture`](https://docs.godotengine.org/en/latest/classes/class_viewporttexture.html),
which is then assigned to the sprite's texture.

## Screenshots

![Screenshot](screenshots/3d_in_2d.webp)


extends Node2D

@onready var viewport: SubViewport = $SubViewport
@onready var viewport_initial_size: Vector2i = viewport.size
@onready var viewport_sprite: Sprite2D = $ViewportSprite

func _ready() -> void:
	$AnimatedSprite2D.play()
	get_viewport().size_changed.connect(_root_viewport_size_changed)

# Called when the root's viewport size changes (i.e. when the window is resized).
# This is done to handle multiple resolutions without losing quality.
func _root_viewport_size_changed() -> void:
	# The viewport is resized depending on the window height.
	# To compensate for the larger resolution, the viewport sprite is scaled down.
	viewport.size = Vector2.ONE * get_viewport().size.y
	viewport_sprite.scale = Vector2.ONE * viewport_initial_size.y / get_viewport().size.y


extends Node3D

## Used for checking if the mouse is inside the Area3D.
var is_mouse_inside := false

## The last processed input touch/mouse event. Used to calculate relative movement.
var last_event_pos2D := Vector2()

## The time of the last event in seconds since engine start.
var last_event_time := -1.0

@onready var node_viewport: SubViewport = $SubViewport
@onready var node_quad: MeshInstance3D = $Quad
@onready var node_area: Area3D = $Quad/Area3D

func _ready() -> void:
	node_area.mouse_entered.connect(_mouse_entered_area)
	node_area.mouse_exited.connect(_mouse_exited_area)
	node_area.input_event.connect(_mouse_input_event)

	# If the material is NOT set to use billboard settings, then avoid running billboard specific code
	if node_quad.get_surface_override_material(0).billboard_mode == BaseMaterial3D.BillboardMode.BILLBOARD_DISABLED:
		set_process(false)

func _process(_delta: float) -> void:
	# NOTE: Remove this function if you don't plan on using billboard settings.
	rotate_area_to_billboard()

func _mouse_entered_area() -> void:
	is_mouse_inside = true

func _mouse_exited_area() -> void:
	is_mouse_inside = false

func _unhandled_input(event: InputEvent) -> void:
	# Check if the event is a non-mouse/non-touch event
	for mouse_event in [InputEventMouseButton, InputEventMouseMotion, InputEventScreenDrag, InputEventScreenTouch]:
		if is_instance_of(event, mouse_event):
			# If the event is a mouse/touch event, then we can ignore it here, because it will be
			# handled via Physics Picking.
			return
	node_viewport.push_input(event)

func _mouse_input_event(_camera: Camera3D, event: InputEvent, event_position: Vector3, _normal: Vector3, _shape_idx: int) -> void:
	# Get mesh size to detect edges and make conversions. This code only supports PlaneMesh and QuadMesh.
	var quad_mesh_size: Vector2 = node_quad.mesh.size

	# Event position in Area3D in world coordinate space.
	var event_pos3D := event_position

	# Current time in seconds since engine start.
	var now := Time.get_ticks_msec() / 1000.0

	# Convert position to a coordinate space relative to the Area3D node.
	# NOTE: `affine_inverse()` accounts for the Area3D node's scale, rotation, and position in the scene!
	event_pos3D = node_quad.global_transform.affine_inverse() * event_pos3D

	# TODO: Adapt to bilboard mode or avoid completely.

	var event_pos2D := Vector2()

	if is_mouse_inside:
		# Convert the relative event position from 3D to 2D.
		event_pos2D = Vector2(event_pos3D.x, -event_pos3D.y)

		# Right now the event position's range is the following: (-quad_size/2) -> (quad_size/2)
		# We need to convert it into the following range: -0.5 -> 0.5
		event_pos2D.x = event_pos2D.x / quad_mesh_size.x
		event_pos2D.y = event_pos2D.y / quad_mesh_size.y
		# Then we need to convert it into the following range: 0 -> 1
		event_pos2D.x += 0.5
		event_pos2D.y += 0.5

		# Finally, we convert the position to the following range: 0 -> viewport.size
		event_pos2D.x *= node_viewport.size.x
		event_pos2D.y *= node_viewport.size.y
		# We need to do these conversions so the event's position is in the viewport's coordinate system.

	elif last_event_pos2D != null:
		# Fall back to the last known event position.
		event_pos2D = last_event_pos2D

	# Set the event's position and global position.
	event.position = event_pos2D
	if event is InputEventMouse:
		event.global_position = event_pos2D

	# Calculate the relative event distance.
	if event is InputEventMouseMotion or event is InputEventScreenDrag:
		# If there is not a stored previous position, then we'll assume there is no relative motion.
		if last_event_pos2D == null:
			event.relative = Vector2(0, 0)
		# If there is a stored previous position, then we'll calculate the relative position by subtracting
		# the previous position from the new position. This will give us the distance the event traveled from prev_pos.
		else:
			event.relative = event_pos2D - last_event_pos2D
			event.velocity = event.relative / (now - last_event_time)

	# Update last_event_pos2D with the position we just calculated.
	last_event_pos2D = event_pos2D

	# Update last_event_time to current time.
	last_event_time = now

	# Finally, send the processed input event to the viewport.
	node_viewport.push_input(event)

func rotate_area_to_billboard() -> void:
	var billboard_mode: BaseMaterial3D.BillboardMode = node_quad.get_surface_override_material(0).billboard_mode

	# Try to match the area with the material's billboard setting, if enabled.
	if billboard_mode > 0:
		# Get the camera.
		var camera := get_viewport().get_camera_3d()
		# Look in the same direction as the camera.
		var look := camera.to_global(Vector3(0, 0, -100)) - camera.global_transform.origin
		look = node_area.position + look

		# Y-Billboard: Lock Y rotation, but gives bad results if the camera is tilted.
		if billboard_mode == 2:
			look = Vector3(look.x, 0, look.z)

		node_area.look_at(look, Vector3.UP)

		# Rotate in the Z axis to compensate camera tilt.
		node_area.rotate_object_local(Vector3.BACK, camera.rotation.z)


extends VBoxContainer

func _ready() -> void:
	# Don't allow loading files that don't exist yet.
	($SaveLoad/LoadConfigFile as Button).disabled = not FileAccess.file_exists("user://save_config_file.ini")
	($SaveLoad/LoadJSON as Button).disabled = not FileAccess.file_exists("user://save_json.json")

func _on_open_user_data_folder_pressed() -> void:
	OS.shell_open(ProjectSettings.globalize_path("user://"))


extends Button
# This script shows how to save data using Godot's custom ConfigFile format.
# ConfigFile can store any Variant type except Signal or Callable.
# It can even store Objects, but be extra careful where you deserialize them
# from, because they can include (potentially malicious) scripts.

const SAVE_PATH = "user://save_config_file.ini"

## The root game node (so we can get and instance enemies).
@export var game_node: NodePath
## The player node (so we can set/get its health and position).
@export var player_node: NodePath

func save_game() -> void:
	var config := ConfigFile.new()

	var player := get_node(player_node) as Player
	config.set_value("player", "position", player.position)
	config.set_value("player", "health", player.health)
	config.set_value("player", "rotation", player.sprite.rotation)

	var enemies := []
	for enemy in get_tree().get_nodes_in_group(&"enemy"):
		enemies.push_back({
			position = enemy.position,
		})
	config.set_value("enemies", "enemies", enemies)

	config.save(SAVE_PATH)

	($"../LoadConfigFile" as Button).disabled = false

func load_game() -> void:
	var config := ConfigFile.new()
	config.load(SAVE_PATH)

	var player := get_node(player_node) as Player
	player.position = config.get_value("player", "position")
	player.health = config.get_value("player", "health")
	player.sprite.rotation = config.get_value("player", "rotation")

	# Remove existing enemies before adding new ones.
	get_tree().call_group("enemy", "queue_free")

	var enemies: Array = config.get_value("enemies", "enemies")
	var game := get_node(game_node)

	for enemy_config: Dictionary in enemies:
		var enemy := preload("res://enemy.tscn").instantiate() as Enemy
		enemy.position = enemy_config.position
		game.add_child(enemy)


extends Panel

func _on_goto_scene_pressed() -> void:
	# Change the scene to the one located at the given path.
	get_tree().change_scene_to_file("res://scene_b.tscn")


extends Panel

func _on_goto_scene_pressed() -> void:
	# Change the scene to the given PackedScene.
	# Though it usually takes more code, this can have advantages, such as letting you load the
	# scene in another thread, or use a scene that isn't saved to a file.
	var scene: PackedScene = load("res://scene_a.tscn")
	get_tree().change_scene_to_packed(scene)


; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
;   [section] ; section goes between []
;   param=value ; assign values to parameters

config_version=5

[application]

config/name="Scene Changer"
config/description="This uses functions in SceneTree to switch between two scenes."
config/tags=PackedStringArray("demo", "official")
run/main_scene="res://scene_a.tscn"
config/features=PackedStringArray("4.2")
run/low_processor_mode=true
config/icon="res://icon.svg"

[debug]

gdscript/warnings/untyped_declaration=1

[display]

window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
window/vsync/vsync_mode=0

[rendering]

renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"


extends Control

var thread: Thread

func _on_load_pressed() -> void:
	if is_instance_valid(thread) and thread.is_started():
		# If a thread is already running, let it finish before we start another.
		thread.wait_to_finish()
	thread = Thread.new()
	print_rich("[b]Starting thread.")
	# Our method needs an argument, so we pass it using bind().
	thread.start(_bg_load.bind("res://mona.png"))

func _bg_load(path: String) -> Texture2D:
	print("Calling thread function.")
	var tex := load(path)
	# call_deferred() tells the main thread to call a method during idle time.
	# Our method operates on nodes currently in the tree, so it isn't safe to
	# call directly from another thread.
	_bg_load_done.call_deferred()
	return tex

func _bg_load_done() -> void:
	# Wait for the thread to complete, and get the returned value.
	var tex: Texture2D = thread.wait_to_finish()
	print_rich("[b][i]Thread finished.\n")
	$TextureRect.texture = tex
	# We're done with the thread now, so we can free it.
	# Threads are reference counted, so this is how we free them.
	thread = null

func _exit_tree() -> void:
	# You should always wait for a thread to finish before letting it get freed!
	# It might not clean up correctly if you don't.
	if is_instance_valid(thread) and thread.is_started():
		thread.wait_to_finish()
		thread = null


extends Node
# Changing scenes is most easily done using the `change_scene_to_file()` and
# `change_scene_to_packed()` methods of SceneTree. This script demonstrates
# how to change scenes without those helpers.

func goto_scene(path: String) -> void:
	# This function will usually be called from a signal callback,
	# or some other function from the running scene.
	# Deleting the current scene at this point might be
	# a bad idea, because it may be inside of a callback or function of it.
	# The worst case will be a crash or unexpected behavior.

	# The way around this is deferring the load to a later time, when
	# it is ensured that no code from the current scene is running:
	_deferred_goto_scene.call_deferred(path)

func _deferred_goto_scene(path: String) -> void:
	# Immediately free the current scene. There is no risk here because the
	# call to this method is already deferred.
	get_tree().current_scene.free()

	var packed_scene: PackedScene = ResourceLoader.load(path)

	var instanced_scene := packed_scene.instantiate()

	# Add it to the scene tree, as direct child of root
	get_tree().root.add_child(instanced_scene)

	# Set it as the current scene, only after it has been added to the tree
	get_tree().current_scene = instanced_scene


extends Control

@onready var client: Node = $Client
@onready var host: LineEdit = $VBoxContainer/Connect/Host
@onready var room: LineEdit = $VBoxContainer/Connect/RoomSecret
@onready var mesh: CheckBox = $VBoxContainer/Connect/Mesh

func _ready() -> void:
	client.lobby_joined.connect(_lobby_joined)
	client.lobby_sealed.connect(_lobby_sealed)
	client.connected.connect(_connected)
	client.disconnected.connect(_disconnected)

	multiplayer.connected_to_server.connect(_mp_server_connected)
	multiplayer.connection_failed.connect(_mp_server_disconnect)
	multiplayer.server_disconnected.connect(_mp_server_disconnect)
	multiplayer.peer_connected.connect(_mp_peer_connected)
	multiplayer.peer_disconnected.connect(_mp_peer_disconnected)

@rpc("any_peer", "call_local")
func ping(argument: float) -> void:
	_log("[Multiplayer] Ping from peer %d: arg: %f" % [multiplayer.get_remote_sender_id(), argument])

func _mp_server_connected() -> void:
	_log("[Multiplayer] Server connected (I am %d)" % client.rtc_mp.get_unique_id())

func _mp_server_disconnect() -> void:
	_log("[Multiplayer] Server disconnected (I am %d)" % client.rtc_mp.get_unique_id())

func _mp_peer_connected(id: int) -> void:
	_log("[Multiplayer] Peer %d connected" % id)

func _mp_peer_disconnected(id: int) -> void:
	_log("[Multiplayer] Peer %d disconnected" % id)

func _connected(id: int, use_mesh: bool) -> void:
	_log("[Signaling] Server connected with ID: %d. Mesh: %s" % [id, use_mesh])

func _disconnected() -> void:
	_log("[Signaling] Server disconnected: %d - %s" % [client.code, client.reason])

func _lobby_joined(lobby: String) -> void:
	_log("[Signaling] Joined lobby %s" % lobby)

func _lobby_sealed() -> void:
	_log("[Signaling] Lobby has been sealed")

func _log(msg: String) -> void:
	print(msg)
	$VBoxContainer/TextEdit.text += str(msg) + "\n"

func _on_peers_pressed() -> void:
	_log(str(multiplayer.get_peers()))

func _on_ping_pressed() -> void:
	ping.rpc(randf())

func _on_seal_pressed() -> void:
	client.seal_lobby()

func _on_start_pressed() -> void:
	client.start(host.text, room.text, mesh.button_pressed)

func _on_stop_pressed() -> void:
	client.stop()


[gd_scene load_steps=3 format=3 uid="uid://c5m3rogpaglk1"]

[ext_resource type="Texture2D" uid="uid://bdomqql6y50po" path="res://brickfloor.png" id="1"]

[sub_resource type="RectangleShape2D" id="1"]
size = Vector2(48, 48)

[node name="TileScene" type="Node2D"]

[node name="Wall" type="Sprite2D" parent="."]
position = Vector2(24, 24)
texture = ExtResource("1")
region_rect = Rect2(0, 0, 48, 48)

[node name="StaticBody2D" type="StaticBody2D" parent="Wall"]

[node name="CollisionShape2D" type="CollisionShape2D" parent="Wall/StaticBody2D"]
shape = SubResource("1")

[node name="Floor" type="Sprite2D" parent="."]
position = Vector2(72, 24)
texture = ExtResource("1")
region_rect = Rect2(48, 0, 48, 48)


extends CharacterBody3D

# Settings to control the character.
@export var rotation_speed := 1.0
@export var movement_speed := 5.0
@export var movement_acceleration := 5.0

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity := float(ProjectSettings.get_setting("physics/3d/default_gravity"))

# Helper variables to keep our code readable.
@onready var origin_node: XROrigin3D = $XROrigin3D
@onready var camera_node: XRCamera3D = $XROrigin3D/XRCamera3D
@onready var neck_position_node: Node3D = $XROrigin3D/XRCamera3D/Neck
@onready var black_out: Node3D = $XROrigin3D/XRCamera3D/BlackOut

## Called when the user has requested their view to be recentered.
func recenter() -> void:
	# The code here assumes the player has walked into an area they shouldn't be
	# and we return the player back to the character body.
	# But other strategies can be applied here as well such as returning the player
	# to a starting position or a checkpoint.

	# Calculate where our camera should be, we start with our global transform.
	var new_camera_transform : Transform3D = global_transform

	# Set to the height of our neck joint.
	new_camera_transform.origin.y = neck_position_node.global_position.y

	# Apply transform our our next position to get our desired camera transform.
	new_camera_transform = new_camera_transform * neck_position_node.transform.inverse()

	# Remove tilt from camera transform.
	var camera_transform : Transform3D = camera_node.transform
	var forward_dir : Vector3 = camera_transform.basis.z
	forward_dir.y = 0.0
	camera_transform = camera_transform.looking_at(camera_transform.origin + forward_dir.normalized(), Vector3.UP, true)

	# Update our XR location.
	origin_node.global_transform = new_camera_transform * camera_transform.inverse()

# Returns our move input by querying the move action on each controller.
func _get_movement_input() -> Vector2:
	var movement := Vector2()

	# If move is not bound to one of our controllers,
	# that controller will return `Vector2.ZERO`.
	movement += $XROrigin3D/LeftHand.get_vector2("move")
	movement += $XROrigin3D/RightHand.get_vector2("move")

	return movement

# `_process_on_physical_movement()` handles the physical movement of the player
# adjusting our character body position to "catch up to" the player.
# If the character body encounters an obstruction our view will black out
# and we will stop further character movement until the player physically
# moves back.
func _process_on_physical_movement(delta: float) -> bool:
	# Remember our current velocity, as we'll apply that later.
	var current_velocity := velocity

	# Start by rotating the player to face the same way our real player is.
	var camera_basis: Basis = origin_node.transform.basis * camera_node.transform.basis
	var forward: Vector2 = Vector2(camera_basis.z.x, camera_basis.z.z)
	var angle: float = forward.angle_to(Vector2(0.0, 1.0))

	# Rotate our character body.
	transform.basis = transform.basis.rotated(Vector3.UP, angle)

	# Reverse this rotation our origin node.
	origin_node.transform = Transform3D().rotated(Vector3.UP, -angle) * origin_node.transform

	# Now apply movement, first move our player body to the right location.
	var org_player_body: Vector3 = global_transform.origin
	var player_body_location: Vector3 = origin_node.transform * camera_node.transform * neck_position_node.transform.origin
	player_body_location.y = 0.0
	player_body_location = global_transform * player_body_location

	velocity = (player_body_location - org_player_body) / delta
	move_and_slide()

	# Now move our XROrigin back.
	var delta_movement := global_transform.origin - org_player_body
	origin_node.global_transform.origin -= delta_movement

	# Negate any height change in local space due to player hitting ramps, etc.
	origin_node.transform.origin.y = 0.0

	# Return our value.
	velocity = current_velocity

	# Check if we managed to move where we wanted to.
	var location_offset := (player_body_location - global_transform.origin).length()
	if location_offset > 0.1:
		# We couldn't go where we wanted to, black out our screen.
		black_out.fade = clampf((location_offset - 0.1) / 0.1, 0.0, 1.0)
		return true
	else:
		black_out.fade = 0.0
		return false

# `_process_movement_on_input()` handles movement through controller input.
# We first handle rotating the player and then apply movement.
# We also apply the effects of gravity at this point.
func _process_movement_on_input(is_colliding: bool, delta: float) -> void:
	if not is_colliding:
		# Only handle input if we've not physically moved somewhere we shouldn't.
		var movement_input := _get_movement_input()

		# First handle rotation, to keep this example simple we are implementing
		# "smooth" rotation here. This can lead to motion sickness.
		# Adding a comfort option with "stepped" rotation is good practice but
		# falls outside of the scope of this demonstration.
		rotation.y += -movement_input.x * delta * rotation_speed

		# Now handle forward/backwards movement.
		# Straffing can be added by using the movement_input.x input
		# and using a different input for rotational control.
		# Straffing is more prone to motion sickness.
		var direction := global_transform.basis * Vector3(0.0, 0.0, -movement_input.y) * movement_speed
		if direction:
			velocity.x = move_toward(velocity.x, direction.x, delta * movement_acceleration)
			velocity.z = move_toward(velocity.z, direction.z, delta * movement_acceleration)
		else:
			velocity.x = move_toward(velocity.x, 0, delta * movement_acceleration)
			velocity.z = move_toward(velocity.z, 0, delta * movement_acceleration)

	# Always handle gravity
	velocity.y -= gravity * delta

	move_and_slide()

# `_physics_process()` handles our player movement.
func _physics_process(delta: float) -> void:
	var is_colliding := _process_on_physical_movement(delta)
	_process_movement_on_input(is_colliding, delta)


extends XROrigin3D

# Settings to control the character.
@export var rotation_speed := 1.0
@export var movement_speed := 5.0
@export var movement_acceleration := 5.0

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity := float(ProjectSettings.get_setting("physics/3d/default_gravity"))

# Helper variables to keep our code readable.
@onready var character_body : CharacterBody3D = $CharacterBody3D
@onready var camera_node : XRCamera3D = $XRCamera3D
@onready var neck_position_node : Node3D = $XRCamera3D/Neck
@onready var black_out : Node3D = $XRCamera3D/BlackOut

## Called when the user has requested their view to be recentered.
func recenter() -> void:
	# The code here assumes the player has walked into an area they shouldn't be
	# and we return the player back to the character body.
	# But other strategies can be applied here as well such as returning the player
	# to a starting position or a checkpoint.

	# Calculate where our camera should be, we start with our global transform.
	var new_camera_transform: Transform3D = character_body.global_transform

	# Set to the height of our neck joint.
	new_camera_transform.origin.y = neck_position_node.global_position.y

	# Apply transform our our next position to get our desired camera transform.
	new_camera_transform = new_camera_transform * neck_position_node.transform.inverse()

	# Remove tilt from camera transform.
	var camera_transform: Transform3D = camera_node.transform
	var forward_dir: Vector3 = camera_transform.basis.z
	forward_dir.y = 0.0
	camera_transform = camera_transform.looking_at(camera_transform.origin + forward_dir.normalized(), Vector3.UP, true)

	# Update our XR location.
	global_transform = new_camera_transform * camera_transform.inverse()

	# Recenter character body.
	character_body.transform = Transform3D()

# `_get_movement_input()` returns our move input by querying the move action on each controller.
func _get_movement_input() -> Vector2:
	var movement : Vector2 = Vector2()

	# If move is not bound to one of our controllers,
	# that controller will return `Vector2.ZERO`.
	movement += $LeftHand.get_vector2("move")
	movement += $RightHand.get_vector2("move")

	return movement

# `_process_on_physical_movement` handles the physical movement of the player
# adjusting our character body position to "catch up to" the player.
# If the character body encounters an obstruction our view will black out
# and we will stop further character movement until the player physically
# moves back.
func _process_on_physical_movement(delta: float) -> bool:
	# Remember our current velocity, as we'll apply that later.
	var current_velocity := character_body.velocity

	# Remember where our player body currently is.
	var org_player_body: Vector3 = character_body.global_transform.origin

	# Determine where our player body should be.
	var player_body_location: Vector3 = camera_node.transform * neck_position_node.transform.origin
	player_body_location.y = 0.0
	player_body_location = global_transform * player_body_location

	# Attempt to move our character.
	character_body.velocity = (player_body_location - org_player_body) / delta
	character_body.move_and_slide()

	# Set back to our current value.
	character_body.velocity = current_velocity

	# Check if we managed to move all the way, ignoring height change.
	var movement_left := player_body_location - character_body.global_transform.origin
	movement_left.y = 0.0

	# Check if we managed to move where we wanted to.
	var location_offset := movement_left.length()
	if location_offset > 0.1:
		# We couldn't go where we wanted to, black out our screen.
		black_out.fade = clamp((location_offset - 0.1) / 0.1, 0.0, 1.0)

		return true
	else:
		black_out.fade = 0.0
		return false

func _copy_player_rotation_to_character_body() -> void:
	# We only copy our forward direction to our character body, we ignore tilt.
	var camera_forward := -camera_node.global_transform.basis.z
	var body_forward := Vector3(camera_forward.x, 0.0, camera_forward.z)

	character_body.global_transform.basis = Basis.looking_at(body_forward, Vector3.UP)

# `_process_movement_on_input` handles movement through controller input.
# We first handle rotating the player and then apply movement.
# We also apply the effects of gravity at this point.
func _process_movement_on_input(is_colliding: bool, delta: float) -> void:
	# Remember where our player body currently is.
	var org_player_body: Vector3 = character_body.global_transform.origin

	if not is_colliding:
		# Only handle input if we've not physically moved somewhere we shouldn't.
		var movement_input := _get_movement_input()

		# First handle rotation, to keep this example simple we are implementing
		# "smooth" rotation here. This can lead to motion sickness.
		# Adding a comfort option with "stepped" rotation is good practice but
		# falls outside of the scope of this demonstration.

		var t1 := Transform3D()
		var t2 := Transform3D()
		var rot := Transform3D()

		# We are going to rotate the origin around the player.
		var player_position := character_body.global_transform.origin - global_transform.origin

		t1.origin = -player_position
		t2.origin = player_position
		rot = rot.rotated(Vector3(0.0, 1.0, 0.0), -movement_input.x * delta * rotation_speed)
		global_transform = (global_transform * t2 * rot * t1).orthonormalized()

		# Now ensure our player body is facing the correct way as well.
		_copy_player_rotation_to_character_body()

		# Now handle forward/backwards movement.
		# Straffing can be added by using the movement_input.x input
		# and using a different input for rotational control.
		# Straffing is more prone to motion sickness.
		var direction: Vector3 = (character_body.global_transform.basis * Vector3(0.0, 0.0, -movement_input.y)) * movement_speed
		if direction:
			character_body.velocity.x = move_toward(character_body.velocity.x, direction.x, delta * movement_acceleration)
			character_body.velocity.z = move_toward(character_body.velocity.z, direction.z, delta * movement_acceleration)
		else:
			character_body.velocity.x = move_toward(character_body.velocity.x, 0, delta * movement_acceleration)
			character_body.velocity.z = move_toward(character_body.velocity.z, 0, delta * movement_acceleration)

	# Always handle gravity.
	character_body.velocity.y -= gravity * delta

	# Attempt to move our player.
	character_body.move_and_slide()

	# And now apply the actual movement to our origin.
	global_transform.origin += character_body.global_transform.origin - org_player_body

# _physics_process handles our player movement.
func _physics_process(delta: float) -> void:
	var is_colliding := _process_on_physical_movement(delta)
	_process_movement_on_input(is_colliding, delta)


extends RigidBody3D
class_name PickupAbleBody3D

var highlight_material : Material = preload("res://shaders/highlight_material.tres")
var picked_up_by : Area3D
var closest_areas : Array

var original_parent : Node3D
var tween : Tween

# Called when this object becomes the closest body in an area
func add_is_closest(area : Area3D) -> void:
	if not closest_areas.has(area):
		closest_areas.push_back(area)

	_update_highlight()

# Called when this object becomes the closest body in an area
func remove_is_closest(area : Area3D) -> void:
	if closest_areas.has(area):
		closest_areas.erase(area)

	_update_highlight()

# Returns whether we have been picked up.
func is_picked_up() -> bool:
	# If we have a valid picked up by object,
	# we've been picked up
	if picked_up_by:
		return true

	return false

# Pick this object up.
func pick_up(pick_up_by) -> void:
	# Already picked up? Can't pick up twice.
	if picked_up_by:
		if picked_up_by == pick_up_by:
			return

		let_go()

	# Remember some state we want to reapply on release.
	original_parent = get_parent()
	var current_transform = global_transform

	# Remove us from our old parent.
	original_parent.remove_child(self)

	# Process our pickup.
	picked_up_by = pick_up_by
	picked_up_by.add_child(self)
	global_transform = current_transform
	freeze = true

	# Kill any existing tween and create a new one.
	if tween:
		tween.kill()
	tween = create_tween()

	# Snap the object to this transform.
	var snap_to : Transform3D

	# Add code here to determine snap position and orientation.

	# Now tween
	tween.tween_property(self, "transform", snap_to, 0.1)

# Let this object go.
func let_go() -> void:
	# Ignore if we haven't been picked up.
	if not picked_up_by:
		return

	# Cancel any ongoing tween
	if tween:
		tween.kill()
		tween = null

	# Remember our current transform.
	var current_transform = global_transform

	# Remove us from what picked us up.
	picked_up_by.remove_child(self)
	picked_up_by = null

	# Reset some state.
	original_parent.add_child(self)
	global_transform = current_transform
	freeze = false

# Update our highlight to show that we can be picked up
func _update_highlight() -> void:
	if not picked_up_by and not closest_areas.is_empty():
		# add highlight
		for child in get_children():
			if child is MeshInstance3D:
				var mesh_instance : MeshInstance3D = child
				mesh_instance.material_overlay = highlight_material
	else:
		# remove highlight
		for child in get_children():
			if child is MeshInstance3D:
				var mesh_instance : MeshInstance3D = child
				mesh_instance.material_overlay = null


[gd_scene load_steps=3 format=3 uid="uid://byif52d1xkl3u"]

[ext_resource type="Script" path="res://pickup/pickup_handler.gd" id="1_5qec3"]

[sub_resource type="SphereShape3D" id="SphereShape3D_i5on0"]
resource_local_to_scene = true
margin = 0.001
radius = 0.3

[node name="PickupHandler" type="Area3D"]
script = ExtResource("1_5qec3")
pickup_action = null

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("SphereShape3D_i5on0")

[connection signal="body_entered" from="." to="." method="_on_body_entered"]
[connection signal="body_exited" from="." to="." method="_on_body_exited"]


@tool
extends Area3D
class_name PickupHandler3D

# This area3D class detects all physics bodys based on
# PickupAbleBody3D within range and handles the logic
# for selecting the closest one and allowing pickup
# of that object.

# Detect range specifies within what radius we detect
# objects we can pick up.
@export var detect_range : float = 0.3:
	set(value):
		detect_range = value
		if is_inside_tree():
			_update_detect_range()
			_update_closest_body()

# Pickup Action specifies the action in the OpenXR
# action map that triggers our pickup function.
@export var pickup_action : String = "pickup"

var closest_body : PickupAbleBody3D
var picked_up_body: PickupAbleBody3D
var was_pickup_pressed : bool = false

# Update our detection range.
func _update_detect_range() -> void:
	var shape : SphereShape3D = $CollisionShape3D.shape
	if shape:
		shape.radius = detect_range

# Update our closest body.
func _update_closest_body() -> void:
	# Do not do this when we're in the editor.
	if Engine.is_editor_hint():
		return

	# Do not check this if we've picked something up.
	if picked_up_body:
		if closest_body:
			closest_body.remove_is_closest(self)
			closest_body = null

		return

	# Find the body that is currently the closest.
	var new_closest_body : PickupAbleBody3D
	var closest_distance : float = 1000000.0

	for body in get_overlapping_bodies():
		if body is PickupAbleBody3D and not body.is_picked_up():
			var distance_squared = (body.global_position - global_position).length_squared()
			if distance_squared < closest_distance:
				new_closest_body = body
				closest_distance = distance_squared

	# Unchanged? Just exit
	if closest_body == new_closest_body:
		return

	# We had a closest body
	if closest_body:
		closest_body.remove_is_closest(self)

	closest_body = new_closest_body
	if closest_body:
		closest_body.add_is_closest(self)

# Get our controller that we are a child of
func _get_parent_controller() -> XRController3D:
	var parent : Node = get_parent()
	while parent:
		if parent is XRController3D:
			return parent

		parent = parent.get_parent()

	return null

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	_update_detect_range()
	_update_closest_body()

# Called every physics frame
func _physics_process(delta) -> void:
	# As we move our hands we need to check if the closest body
	# has changed.
	_update_closest_body()

	# Check if our pickup action is true
	var pickup_pressed = false
	var controller : XRController3D = _get_parent_controller()
	if controller:
		# While OpenXR can return this as a boolean, there is a lot of
		# difference in handling thresholds between platforms.
		# So we implement our own logic here.
		var pickup_value : float = controller.get_float(pickup_action)
		var threshold : float = 0.4 if was_pickup_pressed else 0.6
		pickup_pressed = pickup_value > threshold

	# Do we need to let go?
	if picked_up_body and not pickup_pressed:
		picked_up_body.let_go()
		picked_up_body = null

	# Do we need to pick something up
	if not picked_up_body and not was_pickup_pressed and pickup_pressed and closest_body:
		picked_up_body = closest_body
		picked_up_body.pick_up(self)

	# Remember our state for the next frame
	was_pickup_pressed = pickup_pressed


[gd_resource type="Shader" format=3 uid="uid://cs1jlvgrhd4ac"]

[resource]
code = "// NOTE: Shader automatically converted from Godot Engine 4.3.beta1's StandardMaterial3D.

shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_front, diffuse_burley, specular_schlick_ggx, unshaded;

uniform vec4 albedo : source_color;
uniform float grow : hint_range(-16.0, 16.0, 0.001);

uniform float specular : hint_range(0.0, 1.0, 0.01);
uniform float metallic : hint_range(0.0, 1.0, 0.01);
uniform float roughness : hint_range(0.0, 1.0);

void vertex() {
	// Standard grow along the normal will create seams
	VERTEX += normalize(VERTEX) * grow;
}

void fragment() {
	ALBEDO = albedo.rgb;

	METALLIC = metallic;
	SPECULAR = specular;
	ROUGHNESS = roughness;
}
"


[gd_scene load_steps=5 format=3 uid="uid://cenb0bfok13vx"]

[ext_resource type="Script" path="res://ui.gd" id="1_wnf2v"]
[ext_resource type="Shader" path="res://cursor.gdshader" id="2_hngl5"]

[sub_resource type="LabelSettings" id="LabelSettings_cnxo1"]
font_size = 64

[sub_resource type="ShaderMaterial" id="ShaderMaterial_84eui"]
shader = ExtResource("2_hngl5")
shader_parameter/color = Color(1, 1, 1, 1)

[node name="UI" type="Control"]
custom_minimum_size = Vector2(1024, 512)
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_wnf2v")

[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.638554, 0.499437, 0.164002, 0.384314)

[node name="TopLabel" type="Label" parent="."]
layout_mode = 0
offset_left = 25.0
offset_top = 17.0
offset_right = 125.0
offset_bottom = 40.0
text = "This is a test!"
label_settings = SubResource("LabelSettings_cnxo1")

[node name="Button" type="Button" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -249.0
offset_top = -48.0
offset_right = 249.0
offset_bottom = 48.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 64
text = "Press this button"

[node name="CountLabel" type="Label" parent="."]
layout_mode = 0
offset_left = 39.0
offset_top = 316.0
offset_right = 965.0
offset_bottom = 495.0
text = "The button has not been pressed."
label_settings = SubResource("LabelSettings_cnxo1")
horizontal_alignment = 1
autowrap_mode = 3

[node name="Cursor" type="ColorRect" parent="."]
process_mode = 4
material = SubResource("ShaderMaterial_84eui")
layout_mode = 0
offset_right = 32.0
offset_bottom = 32.0
mouse_filter = 2

[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]


@tool
extends Area3D

############################################################################
# Water ripple effect shader - Bastiaan Olij
#
# This is an example of how to implement a more complex compute shader
# in Godot and making use of the new Custom Texture RD API added to
# the RenderingServer.
#
# If thread model is set to Multi-Threaded, the code related to compute will
# run on the render thread. This is needed as we want to add our logic to
# the normal rendering pipeline for this thread.
#
# The effect itself is an implementation of the classic ripple effect
# that has been around since the 90s, but in a compute shader.
# If someone knows if the original author ever published a paper I could
# quote, please let me know :)

@export var rain_size: float = 3.0
@export var mouse_size: float = 5.0
@export var texture_size: Vector2i = Vector2i(512, 512)
@export_range(1.0, 10.0, 0.1) var damp: float = 1.0

var t := 0.0
var max_t := 0.1

var texture: Texture2DRD
var next_texture: int = 0

var add_wave_point: Vector4
var mouse_pos: Vector2
var mouse_pressed: bool = false

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	# In case we're running stuff on the rendering thread
	# we need to do our initialisation on that thread.
	RenderingServer.call_on_render_thread(_initialize_compute_code.bind(texture_size))

	# Get our texture from our material so we set our RID.
	var material: ShaderMaterial = $MeshInstance3D.material_override
	if material:
		material.set_shader_parameter("effect_texture_size", texture_size)

		# Get our texture object.
		texture = material.get_shader_parameter("effect_texture")

func _exit_tree() -> void:
	# Make sure we clean up!
	if texture:
		texture.texture_rd_rid = RID()

	RenderingServer.call_on_render_thread(_free_compute_resources)

func _unhandled_input(event: InputEvent) -> void:
	# If tool enabled, we don't want to handle our input in the editor.
	if Engine.is_editor_hint():
		return

	if event is InputEventMouseMotion or event is InputEventMouseButton:
		mouse_pos = event.global_position

	if event is InputEventMouseButton and event.button_index == MouseButton.MOUSE_BUTTON_LEFT:
		mouse_pressed = event.pressed

func _check_mouse_pos() -> void:
	# This is a mouse event, do a raycast.
	var camera := get_viewport().get_camera_3d()

	var parameters := PhysicsRayQueryParameters3D.new()
	parameters.from = camera.project_ray_origin(mouse_pos)
	parameters.to = parameters.from + camera.project_ray_normal(mouse_pos) * 100.0
	parameters.collision_mask = 1
	parameters.collide_with_bodies = false
	parameters.collide_with_areas = true

	var result := get_world_3d().direct_space_state.intersect_ray(parameters)
	if not result.is_empty():
		# Transform our intersection point.
		var pos: Vector3 = global_transform.affine_inverse() * result.position
		add_wave_point.x = clamp(pos.x / 5.0, -0.5, 0.5) * texture_size.x + 0.5 * texture_size.x
		add_wave_point.y = clamp(pos.z / 5.0, -0.5, 0.5) * texture_size.y + 0.5 * texture_size.y
		# We have w left over so we use it to indicate mouse is over our water plane.
		add_wave_point.w = 1.0
	else:
		add_wave_point.x = 0.0
		add_wave_point.y = 0.0
		add_wave_point.w = 0.0

func _process(delta: float) -> void:
	# If tool is enabled, ignore mouse input.
	if Engine.is_editor_hint():
		add_wave_point.w = 0.0
	else:
		# Check where our mouse intersects our area, can change if things move.
		_check_mouse_pos()

	# If we're not using the mouse, animate water drops, we (ab)used our W for this.
	if add_wave_point.w == 0.0:
		t += delta
		if t > max_t:
			t = 0
			add_wave_point.x = randi_range(0, texture_size.x)
			add_wave_point.y = randi_range(0, texture_size.y)
			add_wave_point.z = rain_size
		else:
			add_wave_point.z = 0.0
	else:
		add_wave_point.z = mouse_size if mouse_pressed else 0.0

	# Increase our next texture index.
	next_texture = (next_texture + 1) % 3

	# Update our texture to show our next result (we are about to create).
	# Note that `_initialize_compute_code` may not have run yet so the first
	# frame this my be an empty RID.
	if texture:
		texture.texture_rd_rid = texture_rds[next_texture]

	# While our render_process may run on the render thread it will run before our texture
	# is used and thus our next_rd will be populated with our next result.
	# It's probably overkill to sent texture_size and damp as parameters as these are static
	# but we sent add_wave_point as it may be modified while process runs in parallel.
	RenderingServer.call_on_render_thread(_render_process.bind(next_texture, add_wave_point, texture_size, damp))

###############################################################################
# Everything after this point is designed to run on our rendering thread.

var rd: RenderingDevice

var shader: RID
var pipeline: RID

# We use 3 textures:
# - One to render into
# - One that contains the last frame rendered
# - One for the frame before that
var texture_rds: Array[RID] = [RID(), RID(), RID()]
var texture_sets: Array[RID] = [RID(), RID(), RID()]

func _create_uniform_set(texture_rd: RID) -> RID:
	var uniform := RDUniform.new()
	uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
	uniform.binding = 0
	uniform.add_id(texture_rd)
	# Even though we're using 3 sets, they are identical, so we're kinda cheating.
	return rd.uniform_set_create([uniform], shader, 0)

func _initialize_compute_code(init_with_texture_size: Vector2i) -> void:
	# As this becomes part of our normal frame rendering,
	# we use our main rendering device here.
	rd = RenderingServer.get_rendering_device()

	# Create our shader.
	var shader_file := load("res://water_plane/water_compute.glsl")
	var shader_spirv: RDShaderSPIRV = shader_file.get_spirv()
	shader = rd.shader_create_from_spirv(shader_spirv)
	pipeline = rd.compute_pipeline_create(shader)

	# Create our textures to manage our wave.
	var tf: RDTextureFormat = RDTextureFormat.new()
	tf.format = RenderingDevice.DATA_FORMAT_R32_SFLOAT
	tf.texture_type = RenderingDevice.TEXTURE_TYPE_2D
	tf.width = init_with_texture_size.x
	tf.height = init_with_texture_size.y
	tf.depth = 1
	tf.array_layers = 1
	tf.mipmaps = 1
	tf.usage_bits = (
			RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT |
			RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT |
			RenderingDevice.TEXTURE_USAGE_STORAGE_BIT |
			RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT |
			RenderingDevice.TEXTURE_USAGE_CAN_COPY_TO_BIT
	)

	for i in 3:
		# Create our texture.
		texture_rds[i] = rd.texture_create(tf, RDTextureView.new(), [])

		# Make sure our textures are cleared.
		rd.texture_clear(texture_rds[i], Color(0, 0, 0, 0), 0, 1, 0, 1)

		# Now create our uniform set so we can use these textures in our shader.
		texture_sets[i] = _create_uniform_set(texture_rds[i])


shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx;

uniform vec3 albedo : source_color;
uniform float metalic : hint_range(0.0, 1.0, 0.1) = 0.8;
uniform float roughness : hint_range(0.0, 1.0, 0.1) = 0.2;
uniform sampler2D effect_texture;
uniform vec2 effect_texture_size;

varying vec2 uv_tangent;
varying vec2 uv_binormal;

void vertex() {
	vec2 pixel_size = vec2(1.0, 1.0) / effect_texture_size;

	uv_tangent = UV + vec2(pixel_size.x, 0.0);
	uv_binormal = UV + vec2(0.0, pixel_size.y);
}

void fragment() {
	float f1 = texture(effect_texture, UV).r;
	float f2 = texture(effect_texture, uv_tangent).r;
	float f3 = texture(effect_texture, uv_binormal).r;

	vec3 tangent = normalize(vec3(1.0, 0.0, f2 - f1));
	vec3 binormal = normalize(vec3(0.0, 1.0, f3 - f1));
	NORMAL_MAP = normalize(cross(binormal, tangent)) * 0.5 + 0.5;

	ALBEDO = albedo.rgb;
	METALLIC = metalic;
	ROUGHNESS = roughness;
	SPECULAR = 0.5;
}


[gd_scene load_steps=4 format=3 uid="uid://csyb8ij530w1l"]

[sub_resource type="StandardMaterial3D" id="1"]
diffuse_mode = 1
albedo_color = Color(0.149414, 0.796875, 0.569252, 1)
roughness = 0.0

[sub_resource type="BoxMesh" id="2"]

[sub_resource type="Environment" id="Environment_wokxq"]
ambient_light_source = 2
ambient_light_color = Color(0.305882, 0.360784, 0.537255, 1)

[node name="Node3D" type="Node3D"]

[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(0.707107, 0, -0.707107, -0.353553, 0.866025, -0.353553, 0.612372, 0.5, 0.612372, 0, 0, 0)
material_override = SubResource("1")
mesh = SubResource("2")

[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.926535, 0.11439, -0.358396, 0.199614, 0.658013, 0.726067, 0.318884, -0.744267, 0.586839, 0, 0, 0)

[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.08165e-12, 2.08165e-12, 3)
fov = 60.0
near = 0.1

[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_wokxq")