From 4a883bc582e361d462fccb40c5ca8cac02a20ea1 Mon Sep 17 00:00:00 2001 From: Fat Mimir Date: Tue, 30 May 2023 03:11:15 -0600 Subject: [PATCH] solution1: Follow target with natural interpolation curve but without error correction --- .gitattributes | 2 + .gitignore | 15 +++++ assets/floor.gdshader | 127 +++++++++++++++++++++++++++++++++++ assets/transparent_cursor.jpg.import | 34 ++++++++++ assets/transparent_cursor.png | Bin 0 -> 99 bytes assets/transparent_cursor.png.import | 34 ++++++++++ camera.gd | 23 +++++++ follower.gd | 55 +++++++++++++++ icon.svg | 1 + icon.svg.import | 37 ++++++++++ main.gd | 13 ++++ main.tscn | 86 ++++++++++++++++++++++++ project.godot | 49 ++++++++++++++ second_order_dynamics.gd | 26 +++++++ 14 files changed, 502 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 assets/floor.gdshader create mode 100644 assets/transparent_cursor.jpg.import create mode 100644 assets/transparent_cursor.png create mode 100644 assets/transparent_cursor.png.import create mode 100644 camera.gd create mode 100644 follower.gd create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 main.gd create mode 100644 main.tscn create mode 100644 project.godot create mode 100644 second_order_dynamics.gd 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 0000000000000000000000000000000000000000..5be06761208c3feeb9877c10523b6bb4985c2b18 GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@})do977^n o-yURS1oD~`{;$vTZ~!sivk53LFuF4?0I6c|boFyt=akR{0G==v1poj5 literal 0 HcmV?d00001 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