196 lines
5.6 KiB
GDScript3
196 lines
5.6 KiB
GDScript3
|
@tool
|
||
|
class_name XRToolsVignette
|
||
|
extends Node3D
|
||
|
|
||
|
@export var radius : float = 1.0: set = set_radius
|
||
|
@export var fade : float = 0.05: set = set_fade
|
||
|
@export var steps : int = 32: set = set_steps
|
||
|
|
||
|
@export var auto_adjust : bool = true: set = set_auto_adjust
|
||
|
@export var auto_inner_radius : float = 0.35
|
||
|
@export var auto_fade_out_factor : float = 1.5
|
||
|
@export var auto_fade_delay : float = 1.0
|
||
|
@export var auto_rotation_limit : float = 20.0: set = set_auto_rotation_limit
|
||
|
@export var auto_velocity_limit : float = 10.0
|
||
|
|
||
|
var material : ShaderMaterial = preload("res://addons/godot-xr-tools/effects/vignette.tres")
|
||
|
|
||
|
var auto_first = true
|
||
|
var fade_delay = 0.0
|
||
|
var origin_node = null
|
||
|
var last_origin_basis : Basis
|
||
|
var last_location : Vector3
|
||
|
@onready var auto_rotation_limit_rad = deg_to_rad(auto_rotation_limit)
|
||
|
|
||
|
func set_radius(new_radius : float) -> void:
|
||
|
radius = new_radius
|
||
|
if is_inside_tree():
|
||
|
_update_radius()
|
||
|
|
||
|
func _update_radius() -> void:
|
||
|
if radius < 1.0:
|
||
|
if material:
|
||
|
material.set_shader_parameter("radius", radius * sqrt(2))
|
||
|
$Mesh.visible = true
|
||
|
else:
|
||
|
$Mesh.visible = false
|
||
|
|
||
|
func set_fade(new_fade : float) -> void:
|
||
|
fade = new_fade
|
||
|
if is_inside_tree():
|
||
|
_update_fade()
|
||
|
|
||
|
func _update_fade() -> void:
|
||
|
if material:
|
||
|
material.set_shader_parameter("fade", fade)
|
||
|
|
||
|
|
||
|
func set_steps(new_steps : int) -> void:
|
||
|
steps = new_steps
|
||
|
if is_inside_tree():
|
||
|
_update_mesh()
|
||
|
|
||
|
func _update_mesh() -> void:
|
||
|
var vertices : PackedVector3Array
|
||
|
var indices : PackedInt32Array
|
||
|
|
||
|
vertices.resize(2 * steps)
|
||
|
indices.resize(6 * steps)
|
||
|
for i in steps:
|
||
|
var v : Vector3 = Vector3.RIGHT.rotated(Vector3.FORWARD, deg_to_rad((360.0 * i) / steps))
|
||
|
vertices[i] = v
|
||
|
vertices[steps+i] = v * 2.0
|
||
|
|
||
|
var off = i * 6
|
||
|
var i2 = ((i + 1) % steps)
|
||
|
indices[off + 0] = steps + i
|
||
|
indices[off + 1] = steps + i2
|
||
|
indices[off + 2] = i2
|
||
|
indices[off + 3] = steps + i
|
||
|
indices[off + 4] = i2
|
||
|
indices[off + 5] = i
|
||
|
|
||
|
# update our mesh
|
||
|
var arr_mesh = ArrayMesh.new()
|
||
|
var arr : Array
|
||
|
arr.resize(ArrayMesh.ARRAY_MAX)
|
||
|
arr[ArrayMesh.ARRAY_VERTEX] = vertices
|
||
|
arr[ArrayMesh.ARRAY_INDEX] = indices
|
||
|
arr_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr)
|
||
|
arr_mesh.custom_aabb = AABB(Vector3(-1.0, -1.0, -1.0), Vector3(1.0, 1.0, 1.0))
|
||
|
|
||
|
$Mesh.mesh = arr_mesh
|
||
|
$Mesh.set_surface_override_material(0, material)
|
||
|
|
||
|
func set_auto_adjust(new_auto_adjust : bool) -> void:
|
||
|
auto_adjust = new_auto_adjust
|
||
|
if is_inside_tree() and !Engine.is_editor_hint():
|
||
|
_update_auto_adjust()
|
||
|
|
||
|
func _update_auto_adjust() -> void:
|
||
|
# Turn process on if auto adjust is true.
|
||
|
# Note we don't turn it off here, we want to finish fading out the vignette if needed
|
||
|
if auto_adjust:
|
||
|
set_process(true)
|
||
|
|
||
|
func set_auto_rotation_limit(new_auto_rotation_limit : float) -> void:
|
||
|
auto_rotation_limit = new_auto_rotation_limit
|
||
|
auto_rotation_limit_rad = deg_to_rad(auto_rotation_limit)
|
||
|
|
||
|
|
||
|
# Add support for is_xr_class on XRTools classes
|
||
|
func is_xr_class(name : String) -> bool:
|
||
|
return name == "XRToolsVignette"
|
||
|
|
||
|
|
||
|
# Called when the node enters the scene tree for the first time.
|
||
|
func _ready():
|
||
|
if !Engine.is_editor_hint():
|
||
|
origin_node = XRHelpers.get_xr_origin(self)
|
||
|
_update_mesh()
|
||
|
_update_radius()
|
||
|
_update_fade()
|
||
|
_update_auto_adjust()
|
||
|
else:
|
||
|
set_process(false)
|
||
|
|
||
|
# Called on process
|
||
|
func _process(delta):
|
||
|
if Engine.is_editor_hint():
|
||
|
return
|
||
|
|
||
|
if !origin_node:
|
||
|
return
|
||
|
|
||
|
if !auto_adjust:
|
||
|
# set to true for next time this is enabled
|
||
|
auto_first = true
|
||
|
|
||
|
# We are done, turn off process
|
||
|
set_process(false)
|
||
|
|
||
|
return
|
||
|
|
||
|
if auto_first:
|
||
|
# first time we run process since starting, just record transform
|
||
|
last_origin_basis = origin_node.global_transform.basis
|
||
|
last_location = global_transform.origin
|
||
|
auto_first = false
|
||
|
return
|
||
|
|
||
|
# Get our delta transform
|
||
|
var delta_b = origin_node.global_transform.basis * last_origin_basis.inverse()
|
||
|
var delta_v = global_transform.origin - last_location
|
||
|
|
||
|
# Adjust radius based on rotation speed of our origin point (not of head movement).
|
||
|
# We convert our delta rotation to a quaterion.
|
||
|
# A quaternion represents a rotation around an angle.
|
||
|
var q = delta_b.get_rotation_quaternion()
|
||
|
|
||
|
# We get our angle from our w component and then adjust to get a
|
||
|
# rotation speed per second by dividing by delta
|
||
|
var angle = (2 * acos(q.w)) / delta
|
||
|
|
||
|
# Calculate what our radius should be for our rotation speed
|
||
|
var target_radius = 1.0
|
||
|
if auto_rotation_limit > 0:
|
||
|
target_radius = 1.0 - (
|
||
|
clamp(angle / auto_rotation_limit_rad, 0.0, 1.0) * (1.0 - auto_inner_radius))
|
||
|
|
||
|
# Now do the same for speed, this includes players physical speed but there
|
||
|
# isn't much we can do there.
|
||
|
if auto_velocity_limit > 0:
|
||
|
var velocity = delta_v.length() / delta
|
||
|
target_radius = min(target_radius, 1.0 - (
|
||
|
clamp(velocity / auto_velocity_limit, 0.0, 1.0) * (1.0 - auto_inner_radius)))
|
||
|
|
||
|
# if our radius is small then our current we apply it
|
||
|
if target_radius < radius:
|
||
|
set_radius(target_radius)
|
||
|
fade_delay = auto_fade_delay
|
||
|
elif fade_delay > 0.0:
|
||
|
fade_delay -= delta
|
||
|
else:
|
||
|
set_radius(clamp(radius + delta / auto_fade_out_factor, 0.0, 1.0))
|
||
|
|
||
|
last_origin_basis = origin_node.global_transform.basis
|
||
|
last_location = global_transform.origin
|
||
|
|
||
|
# This method verifies the vignette has a valid configuration.
|
||
|
# Specifically it checks the following:
|
||
|
# - XROrigin3D is a parent
|
||
|
# - XRCamera3D is our parent
|
||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||
|
var warnings := PackedStringArray()
|
||
|
|
||
|
# Check the origin node
|
||
|
if !XRHelpers.get_xr_origin(self):
|
||
|
warnings.append("Parent node must be in a branch from XROrigin3D")
|
||
|
|
||
|
# check camera node
|
||
|
var parent = get_parent()
|
||
|
if !parent or !parent is XRCamera3D:
|
||
|
warnings.append("Parent node must be an XRCamera3D")
|
||
|
|
||
|
return warnings
|