commit 4a883bc582e361d462fccb40c5ca8cac02a20ea1 Author: Fat Mimir Date: Tue May 30 03:11:15 2023 -0600 solution1: Follow target with natural interpolation curve but without error correction diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9aac21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Imported translations (automatically generated from CSV files) +*.translation + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json diff --git a/assets/floor.gdshader b/assets/floor.gdshader new file mode 100644 index 0000000..d131b9f --- /dev/null +++ b/assets/floor.gdshader @@ -0,0 +1,127 @@ +shader_type spatial; +render_mode blend_mix, depth_draw_always, cull_back, diffuse_burley, specular_schlick_ggx; + +uniform vec4 albedo : source_color = vec4(0.6f, 0.6f, 0.6f, 1.0f); +uniform sampler2D detail_texture; +uniform vec2 detail_texture_size = vec2(256.0f, 256.0f); +uniform float specular; +uniform float metallic : hint_range(0, 1) = 0.0f; +uniform float roughness : hint_range(0, 1) = 0.5f; +uniform float point_size : hint_range(0, 128); + +varying vec3 Position; +varying vec3 Normal; + +void vertex() +{ + Position = VERTEX; + Normal = NORMAL; +} + +float SquareAntialiased(float coord, float dd) // antialiased square wave +{ + coord += dd * 0.5f; + + dd = max(dd, 0.0001f); + float invdd = 1.0f / dd; + + coord = mod(coord, 1.0f); + float c = min(coord * invdd, 0.5f - (coord - 0.5f) / dd + 0.5f); + c = clamp(c, 0.0f, 1.0f); + + return c; +} + +float SXOR(float a, float b) // "smooth" xor, used to combine checker patterns +{ + return abs(a - b); +} + +vec4 SXOR4(vec4 a, vec4 b) +{ + return abs(a - b); +} + +float Checkers(vec3 coord, vec3 ddxyz, vec3 tripCoeff) +{ + vec3 fade = clamp(1.0f / (ddxyz * 2.0f) - 1.0f, 0.0f, 1.0f); + + float cx = SquareAntialiased(coord.x, ddxyz.x); + float cy = SquareAntialiased(coord.y, ddxyz.y); + float cz = SquareAntialiased(coord.z, ddxyz.z); + + vec3 filter = clamp(1.0f / ddxyz * 0.5f - 0.5f, 0.0f, 1.0f); // smooth, manual filtering + + float c = SXOR(cy * filter.y, cz * filter.z) * tripCoeff.x; + c += SXOR(cz * filter.z, cx * filter.x) * tripCoeff.y; + c += SXOR(cx * filter.x, cy * filter.y) * tripCoeff.z; + c += 1.0f - dot(filter, vec3(0.3333f)); + + return c; +} + +vec4 texturePointSmooth(sampler2D smp, vec2 uv, vec2 tex_size, vec2 filter_width) +{ + float fade = clamp(max(1.0f / filter_width.x, 1.0f / filter_width.y) - 1.0f, 0.0f, 1.0f); + filter_width = max(filter_width, vec2(1.0f)); + vec2 uv_pixels = uv * tex_size; + + vec2 uv_pixels_floor = round(uv_pixels) - vec2(0.5f); + vec2 uv_dxy_pixels = uv_pixels - uv_pixels_floor; + + uv_dxy_pixels = clamp((uv_dxy_pixels - vec2(0.5f)) * filter_width + vec2(0.5f), 0.0f, 1.0f); + + uv = uv_pixels_floor / tex_size; + + return mix(textureLod(smp, uv + uv_dxy_pixels / tex_size, 0.0f), vec4(0.5f, 0.5f, 0.5f, 1.0f), fade); +} + +vec4 TextureTriplanar(sampler2D smp, vec2 texSize, vec3 coord, vec3 ddxyz, vec3 tripCoeff) +{ + vec4 cx = texturePointSmooth(smp, coord.yz, texSize, 1.0f / ddxyz.yz / texSize); + vec4 cy = texturePointSmooth(smp, coord.zx, texSize, 1.0f / ddxyz.zx / texSize); + vec4 cz = texturePointSmooth(smp, coord.xy, texSize, 1.0f / ddxyz.xy / texSize); + + vec3 filter = clamp(1.0f / ddxyz * 0.5f - 0.5f, 0.0f, 1.0f); // smooth, manual filtering + + vec4 c = cx * tripCoeff.x; + c += cy * tripCoeff.y; + c += cz * tripCoeff.z; + + return c; +} + +void fragment() +{ + ALBEDO = albedo.rgb; + + vec3 coord = Position.xyz * 4.0f; + vec3 ddxyz = fwidth(coord) * 1.0f; + + vec3 tripCoeff = max((Normal * Normal * 2.0f - 0.5f), 0.001f); + tripCoeff /= (tripCoeff.x + tripCoeff.y + tripCoeff.z); + float checkers = Checkers(coord, ddxyz, tripCoeff); + + coord = Position.xyz; + ddxyz = fwidth(coord); + float checkersL = 1.0f - Checkers(coord, ddxyz, tripCoeff); + + vec3 detailCoord = Position.xyz * 0.5f; // Position.xyz * 2.0f + vec4 detail = TextureTriplanar(detail_texture, detail_texture_size, detailCoord, fwidth(detailCoord), tripCoeff); + + checkers = mix(checkers, checkersL, 0.7f); + checkers = clamp(checkers * 0.8f + detail.r * 0.2f - 0.025f, 0.0f, 1.0f); + + ALBEDO = mix(ALBEDO * 0.3f, ALBEDO, mix(checkers, 1.0f, metallic * roughness)); + ROUGHNESS = mix(1.0f, 0.5f, checkers * (1.0f - roughness * 0.333f)) * roughness; + + /* when using rgb noise as a texture, this can give some variation in roughness and normals + { + detail.rgb = detail.rgb * mat3(WORLD_MATRIX[0].xyz, WORLD_MATRIX[1].xyz, WORLD_MATRIX[2].xyz); + NORMAL = normalize(NORMAL + (detail.rgb - 0.5f) * 0.08f * roughness); + } + */ + + METALLIC = metallic; + SPECULAR = specular; +} \ No newline at end of file diff --git a/assets/transparent_cursor.jpg.import b/assets/transparent_cursor.jpg.import new file mode 100644 index 0000000..f2df30f --- /dev/null +++ b/assets/transparent_cursor.jpg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6oh1e3s28ngo" +path="res://.godot/imported/transparent_cursor.jpg-86c75b56e2c380b7e1b94c2bd2d0a993.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/transparent_cursor.jpg" +dest_files=["res://.godot/imported/transparent_cursor.jpg-86c75b56e2c380b7e1b94c2bd2d0a993.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/transparent_cursor.png b/assets/transparent_cursor.png new file mode 100644 index 0000000..5be0676 Binary files /dev/null and b/assets/transparent_cursor.png differ diff --git a/assets/transparent_cursor.png.import b/assets/transparent_cursor.png.import new file mode 100644 index 0000000..ed0f773 --- /dev/null +++ b/assets/transparent_cursor.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1gqwb316f5a7" +path="res://.godot/imported/transparent_cursor.png-ace4e0eb38b0048c2aad890e0663d1a9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/transparent_cursor.png" +dest_files=["res://.godot/imported/transparent_cursor.png-ace4e0eb38b0048c2aad890e0663d1a9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/camera.gd b/camera.gd new file mode 100644 index 0000000..aea47db --- /dev/null +++ b/camera.gd @@ -0,0 +1,23 @@ +extends Camera3D + +@export var ray_length := 1000.0 +@export var target_path : NodePath +@export var height := 3.5 +@onready var target_node : MeshInstance3D = get_node(target_path) +var projected_point := Vector3.ZERO + +func _input(event): + if event is InputEventMouseMotion: + var from = project_ray_origin(event.position) + var to = from + project_ray_normal(event.position) * ray_length + var result = get_world_3d().direct_space_state.intersect_ray( + PhysicsRayQueryParameters3D.create(from, to) + ) + if result and result.get('position'): + projected_point = result.get('position') + +func _physics_process(_delta): + if projected_point: + target_node.global_position = ( + projected_point + projected_point.direction_to(global_position).normalized() * height + ) diff --git a/follower.gd b/follower.gd new file mode 100644 index 0000000..47dcd59 --- /dev/null +++ b/follower.gd @@ -0,0 +1,55 @@ +extends MeshInstance3D + +@export var f := 1.0 +@export var z := 0.5 +@export var r := 2.0 +@export var target_path: NodePath +@export var f_label_path: NodePath +@export var z_label_path: NodePath +@export var r_label_path: NodePath + +@onready var second_order_dynamics := SecondOrderDynamics.new(f, z, r, global_position) +@onready var target_node: Node3D = get_node(target_path) +@onready var f_label_node: Label = get_node(f_label_path) +@onready var z_label_node: Label = get_node(z_label_path) +@onready var r_label_node: Label = get_node(r_label_path) + +func _ready(): + process_mode = Node.PROCESS_MODE_ALWAYS + +func _input(event): + if event.is_action_pressed("inc_f"): + f += 0.1 + second_order_dynamics = SecondOrderDynamics.new(f, z, r, global_position) + + if event.is_action_pressed("dec_f"): + f -= 0.1 + second_order_dynamics = SecondOrderDynamics.new(f, z, r, global_position) + + if event.is_action_pressed("inc_z"): + z += 0.1 + second_order_dynamics = SecondOrderDynamics.new(f, z, r, global_position) + + if event.is_action_pressed("dec_z"): + z -= 0.1 + second_order_dynamics = SecondOrderDynamics.new(f, z, r, global_position) + + if event.is_action_pressed("inc_r"): + r += 0.1 + second_order_dynamics = SecondOrderDynamics.new(f, z, r, global_position) + + if event.is_action_pressed("dec_r"): + r -= 0.1 + second_order_dynamics = SecondOrderDynamics.new(f, z, r, global_position) + +func _process(delta): + global_position = second_order_dynamics.update(delta, target_node.global_position, Vector3.ZERO) + + if f_label_node: + f_label_node.text = "f = %.3f" % [f] + + if z_label_node: + z_label_node.text = "z = %.3f" % [z] + + if r_label_node: + r_label_node.text = "r = %.3f" % [r] diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..adc26df --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..dbbdcec --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csn6jebkkejps" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/main.gd b/main.gd new file mode 100644 index 0000000..07515b8 --- /dev/null +++ b/main.gd @@ -0,0 +1,13 @@ +extends Node3D + +@onready var cursor := load('res://assets/transparent_cursor.png') + +func _ready(): + Input.mouse_mode = Input.MOUSE_MODE_CONFINED + Input.set_custom_mouse_cursor(cursor) + +func _input(event): + if event.is_action_pressed("ui_cancel"): + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + elif event is InputEventMouseButton: + Input.mouse_mode = Input.MOUSE_MODE_CONFINED diff --git a/main.tscn b/main.tscn new file mode 100644 index 0000000..f045eba --- /dev/null +++ b/main.tscn @@ -0,0 +1,86 @@ +[gd_scene load_steps=9 format=3 uid="uid://3b5ij42n5jko"] + +[ext_resource type="Script" path="res://main.gd" id="1_1ywfr"] +[ext_resource type="Script" path="res://camera.gd" id="1_32slg"] +[ext_resource type="Shader" path="res://assets/floor.gdshader" id="2_snf6g"] +[ext_resource type="Script" path="res://follower.gd" id="3_4rocj"] + +[sub_resource type="SphereMesh" id="SphereMesh_w43c3"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_j7rh5"] +albedo_color = Color(0.992157, 0.415686, 1, 1) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_txxch"] +albedo_color = Color(0.407843, 0.721569, 0.980392, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_2gtpr"] +render_priority = 0 +shader = ExtResource("2_snf6g") +shader_parameter/albedo = Color(0.721569, 0.615686, 0.917647, 1) +shader_parameter/detail_texture_size = Vector2(256, 256) +shader_parameter/specular = null +shader_parameter/metallic = 0.0 +shader_parameter/roughness = 0.5 +shader_parameter/point_size = null + +[node name="Main" type="Node3D"] +script = ExtResource("1_1ywfr") + +[node name="Help" type="HBoxContainer" parent="."] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 156.0 +grow_horizontal = 2 +pivot_offset = Vector2(1.89, 0.201) + +[node name="Label" type="Label" parent="Help"] +layout_mode = 2 +text = "1 - Increment f +2 - Decrement f +3 - Increment z +4 - Decrement z +5 - Increment r +6 - Decrement z" + +[node name="FLabel" type="Label" parent="Help"] +layout_mode = 2 + +[node name="ZLabel" type="Label" parent="Help"] +layout_mode = 2 + +[node name="RLabel" type="Label" parent="Help"] +layout_mode = 2 + +[node name="Camera" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.807671, 0.589633, 0, -0.589633, 0.807671, 0, 22, 24) +projection = 1 +size = 7.0 +script = ExtResource("1_32slg") +target_path = NodePath("../Target") + +[node name="Target" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.5, 0) +mesh = SubResource("SphereMesh_w43c3") +surface_material_override/0 = SubResource("StandardMaterial3D_j7rh5") + +[node name="Follower" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.5, 0) +mesh = SubResource("SphereMesh_w43c3") +surface_material_override/0 = SubResource("StandardMaterial3D_txxch") +script = ExtResource("3_4rocj") +target_path = NodePath("../Target") +f_label_path = NodePath("../Help/FLabel") +z_label_path = NodePath("../Help/ZLabel") +r_label_path = NodePath("../Help/RLabel") + +[node name="Map" type="CSGCombiner3D" parent="."] +transform = Transform3D(0.707107, 0, 0.707107, 0, 1, 0, -0.707107, 0, 0.707107, 0, 0, 0) +use_collision = true + +[node name="Floor" type="CSGBox3D" parent="Map"] +size = Vector3(30, 0.01, 30) +material = SubResource("ShaderMaterial_2gtpr") + +[node name="Sun" type="DirectionalLight3D" parent="Map"] +transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 3.72719, 11.0332, 9.55047) +shadow_enabled = true diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..651b61e --- /dev/null +++ b/project.godot @@ -0,0 +1,49 @@ +; 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="Interpolation Curves" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.0", "Forward Plus") +config/icon="res://icon.svg" + +[input] + +inc_f={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"echo":false,"script":null) +] +} +dec_f={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"echo":false,"script":null) +] +} +inc_z={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"echo":false,"script":null) +] +} +dec_z={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"echo":false,"script":null) +] +} +inc_r={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":53,"echo":false,"script":null) +] +} +dec_r={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":54,"key_label":0,"unicode":54,"echo":false,"script":null) +] +} diff --git a/second_order_dynamics.gd b/second_order_dynamics.gd new file mode 100644 index 0000000..a9bfd80 --- /dev/null +++ b/second_order_dynamics.gd @@ -0,0 +1,26 @@ +class_name SecondOrderDynamics + +var k1: float +var k2: float +var k3: float + +var xp: Vector3 +var y: Vector3 +var yd: Vector3 + +func _init(f: float, z: float, r: float, x0: Vector3): + k1 = z / PI * f + k2 = 1 / ((2 * PI * f) * (2 * PI * f)) + k3 = r * z / (2 * PI * f) + xp = x0 + y = x0 + yd = Vector3.ZERO + +func update(t: float, x: Vector3, xd: Vector3) -> Vector3: + if xd.is_zero_approx(): + xd = (x - xp) / t + xp = x + + y = y + t * yd + yd = yd + t * (x + k3*xd - y - k1*yd) / k2 + return y