diff --git a/.gitignore b/.gitignore index d644401..4a82b33 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.import export.cfg export_presets.cfg +.godot/ # Mono-specific ignores .mono/ diff --git a/README.md b/README.md index 66d981f..7f323fc 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,6 @@ 4. Enable plugin in project settings `Project > Project Settings > Plugins` 5. Reload project (if necessary). -# Dependencies -[Godot Utils v0.3.1](https://github.com/addmix/godot_utils/releases/tag/v0.3.1) - # Usage Tutorial available [here](https://youtu.be/iI8SXQdaqDQ) 1. Add an `AeroBody` to your scene, and add one or more `AeroSurface` derived classes as children, adjust settings to change the characteristics. diff --git a/core/aero_body_3d.gd b/core/aero_body_3d.gd index dcaace1..e68809e 100644 --- a/core/aero_body_3d.gd +++ b/core/aero_body_3d.gd @@ -96,6 +96,9 @@ var inclination := 0.0 #debug +const Vector3D = preload("../utils/vector_3d/vector_3d.gd") +const Point3D = preload("../utils/point_3d/point_3d.gd") + var linear_velocity_vector : Vector3D var angular_velocity_vector : Vector3D diff --git a/core/aero_influencer_3d/aero_influencer_3d.gd b/core/aero_influencer_3d/aero_influencer_3d.gd index cd38bb2..0a578ff 100644 --- a/core/aero_influencer_3d/aero_influencer_3d.gd +++ b/core/aero_influencer_3d/aero_influencer_3d.gd @@ -26,6 +26,7 @@ var dynamic_pressure : float = 0.0 var _current_force : Vector3 = Vector3.ZERO var _current_torque : Vector3 = Vector3.ZERO +const Vector3D = preload("../../utils/vector_3d/vector_3d.gd") var force_debug_vector : Vector3D var torque_debug_vector : Vector3D diff --git a/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd b/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd index d643573..51cde42 100644 --- a/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd +++ b/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd @@ -2,14 +2,15 @@ extends Resource class_name ManualAeroSurfaceConfig -@export var min_lift_coefficient : float = -1.5 -@export var max_lift_coefficient : float = 1.5 +@export var min_lift_coefficient : float = -1.0 +@export var max_lift_coefficient : float = 1.0 @export var lift_aoa_curve : Curve -@export var min_drag_coefficient : float = 0.001 -@export var max_drag_coefficient : float = 0.5 - +@export var min_drag_coefficient : float = 0.0 +@export var max_drag_coefficient : float = 1.0 @export var drag_aoa_curve : Curve +const MathUtils = preload("../../../../utils/math_utils.gd") + func _init(_lift_aoa_curve : Curve = preload("../../../resources/default_lift_aoa_curve.tres").duplicate(), _drag_aoa_curve : Curve = preload("../../../resources/default_drag_aoa_curve.tres").duplicate()) -> void: lift_aoa_curve = _lift_aoa_curve drag_aoa_curve = _drag_aoa_curve diff --git a/core/singletons/aero_units.gd b/core/singletons/aero_units.gd index babded6..eea9fab 100644 --- a/core/singletons/aero_units.gd +++ b/core/singletons/aero_units.gd @@ -1,6 +1,8 @@ @tool extends Node +const NodeUtils = preload("../../utils/node_utils.gd") + #air is 1.402 @export var ratio_of_specific_heat : float = 1.402 @@ -24,10 +26,12 @@ extends Node @export var min_altitude : float = 0.0 @export var max_altitude : float = 80000.0 func get_altitude(node : Node) -> float: - var floating_origin = NodeUtils.get_first_parent_of_type(node, FloatingOrigin) - if floating_origin is FloatingOrigin: + # Need to find a better way to check for floating origin + + var floating_origin = NodeUtils.get_first_parent_of_type_with_string(node, "FloatingOrigin") + if floating_origin: return node.global_position.y + (float(floating_origin.current_offset.y) * floating_origin.shift_threshold) - return node.global_position.y + return node.position.y @export var min_mach : float = 0.0 @export var max_mach : float = 10.0 diff --git a/demo/aero_body_3d.gd b/demo/aero_body_3d.gd new file mode 100644 index 0000000..d3ada5d --- /dev/null +++ b/demo/aero_body_3d.gd @@ -0,0 +1,8 @@ +@tool +extends AeroBody3D + +func _physics_process(delta): + super._physics_process(delta) + $Elevator.rotation.x = Input.get_axis("ui_down", "ui_up") * 0.4 + $WingL.rotation.x = Input.get_axis("ui_left", "ui_right") * 0.1 + $WingR.rotation.x = -Input.get_axis("ui_left", "ui_right") * 0.1 diff --git a/demo/demo_plane.tscn b/demo/demo_plane.tscn new file mode 100644 index 0000000..98b425e --- /dev/null +++ b/demo/demo_plane.tscn @@ -0,0 +1,153 @@ +[gd_scene load_steps=18 format=3 uid="uid://dx0v1i14fyw8c"] + +[ext_resource type="Script" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_3d.gd" id="2_ygpq6"] +[ext_resource type="Script" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd" id="3_dhflf"] +[ext_resource type="Script" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_config.gd" id="4_ht3i8"] +[ext_resource type="Script" path="res://demo/thruster.gd" id="5_50cgi"] +[ext_resource type="Script" path="res://demo/aero_body_3d.gd" id="5_208xj"] + +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_swshj"] +friction = 0.0 + +[sub_resource type="BoxShape3D" id="BoxShape3D_nchkm"] +size = Vector3(5.34473, 0.454224, 5.40618) + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_6onr1"] +radius = 0.248054 + +[sub_resource type="Curve" id="Curve_v5kpe"] +_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, 1), 0.0, 0.0, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0, Vector2(0.75, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 5 + +[sub_resource type="Curve" id="Curve_cmcrd"] +min_value = -1.0 +_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, -0.571), 0.0, 0.0, 0, 0, Vector2(0.375, -0.4), 0.0, 0.0, 0, 0, Vector2(0.403, -1), 0.0, 0.0, 0, 0, Vector2(0.597, 1), 0.0, 0.0, 0, 0, Vector2(0.625, 0.4), 0.0, 0.0, 0, 0, Vector2(0.75, 0.571), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 8 + +[sub_resource type="Resource" id="Resource_raxm3"] +script = ExtResource("3_dhflf") +min_lift_coefficient = -1.5 +max_lift_coefficient = 1.5 +lift_aoa_curve = SubResource("Curve_cmcrd") +min_drag_coefficient = 0.001 +max_drag_coefficient = 0.5 +drag_aoa_curve = SubResource("Curve_v5kpe") + +[sub_resource type="Curve" id="Curve_8vcck"] + +[sub_resource type="Curve" id="Curve_26y5e"] +min_value = 1.0 +max_value = 1.69 +_data = [Vector2(0.07, 1), 0.0, 0.0, 0, 0, Vector2(0.088, 1.519), 0.0, 0.0, 0, 0, Vector2(0.119, 1), 0.0, 0.0, 0, 0] +point_count = 3 + +[sub_resource type="Curve" id="Curve_u1ll1"] +_data = [Vector2(0, 1), 0.0, -2.09824, 0, 0, Vector2(0.406542, 0.490909), -0.666575, -0.666575, 0, 0, Vector2(1, 0.290909), 0.0, 0.0, 0, 0] +point_count = 3 + +[sub_resource type="Resource" id="Resource_b87jk"] +script = ExtResource("4_ht3i8") +chord = 1.0 +span = 2.0 +skin_friction = 0.001 +auto_aspect_ratio = true +aspect_ratio = 2.0 +zero_lift_aoa = 0.0 +flap_fraction = 0.0 +is_control_surface = false +sweep_drag_multiplier_curve = SubResource("Curve_u1ll1") +drag_at_mach_multiplier_curve = SubResource("Curve_26y5e") +buffet_aoa_curve = SubResource("Curve_8vcck") + +[sub_resource type="Resource" id="Resource_7jeqs"] +script = ExtResource("4_ht3i8") +chord = 1.0 +span = 1.4 +skin_friction = 0.001 +auto_aspect_ratio = true +aspect_ratio = 1.4 +zero_lift_aoa = 0.0 +flap_fraction = 0.0 +is_control_surface = false +sweep_drag_multiplier_curve = SubResource("Curve_u1ll1") +drag_at_mach_multiplier_curve = SubResource("Curve_26y5e") +buffet_aoa_curve = SubResource("Curve_8vcck") + +[sub_resource type="Resource" id="Resource_658jb"] +script = ExtResource("4_ht3i8") +chord = 1.0 +span = 3.55 +skin_friction = 0.001 +auto_aspect_ratio = true +aspect_ratio = 3.55 +zero_lift_aoa = 0.0 +flap_fraction = 0.0 +is_control_surface = false +sweep_drag_multiplier_curve = SubResource("Curve_u1ll1") +drag_at_mach_multiplier_curve = SubResource("Curve_26y5e") +buffet_aoa_curve = SubResource("Curve_8vcck") + +[node name="AeroBody3D" type="VehicleBody3D"] +mass = 1000.0 +physics_material_override = SubResource("PhysicsMaterial_swshj") +center_of_mass_mode = 1 +linear_damp_mode = 1 +angular_damp_mode = 1 +script = ExtResource("5_208xj") +show_debug = true +update_debug = true + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0840454, 1.49654) +shape = SubResource("BoxShape3D_nchkm") + +[node name="CollisionShape3D2" type="CollisionShape3D" parent="."] +transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 0, -0.282653, -0.4323) +shape = SubResource("CapsuleShape3D_6onr1") + +[node name="Elevator" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3.57628e-07, 3.05994) +script = ExtResource("2_ygpq6") +manual_config = SubResource("Resource_raxm3") +wing_config = SubResource("Resource_b87jk") +show_debug = true + +[node name="Rudder" type="Node3D" parent="."] +transform = Transform3D(-4.37114e-08, -1, 0, 0.835693, -3.65293e-08, -0.549198, 0.549198, -2.40062e-08, 0.835693, -2.96035e-17, 0.742918, 3.32475) +script = ExtResource("2_ygpq6") +manual_config = SubResource("Resource_raxm3") +wing_config = SubResource("Resource_7jeqs") +show_debug = true + +[node name="WingL" type="Node3D" parent="."] +transform = Transform3D(1, 0, -5.96046e-08, 0, 1, 0, 5.96046e-08, 0, 1, -1.90776, 0, -0.473555) +script = ExtResource("2_ygpq6") +manual_config = SubResource("Resource_raxm3") +wing_config = SubResource("Resource_658jb") +show_debug = true + +[node name="WingR" type="Node3D" parent="."] +transform = Transform3D(1, 0, 5.96046e-08, 0, 1, 0, -5.96046e-08, 0, 1, 1.908, 0, -0.473945) +script = ExtResource("2_ygpq6") +manual_config = SubResource("Resource_raxm3") +wing_config = SubResource("Resource_658jb") +show_debug = true + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.55771, 9.0562) + +[node name="CSGBox3D" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.20421) +size = Vector3(7.34863, 0.113525, 1.14014) + +[node name="CSGBox3D2" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.35488) +size = Vector3(2.0332, 0.113525, 1.14014) + +[node name="CSGBox3D3" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.822998, -0.568045, 0, 0.568045, 0.822998, 0, 0.623825, 3.53358) +size = Vector3(0.141602, 1.41797, 1.14014) + +[node name="Marker3D" type="Marker3D" parent="."] +script = ExtResource("5_50cgi") +force = 600.0 diff --git a/demo/demo_scene.tscn b/demo/demo_scene.tscn new file mode 100644 index 0000000..154e3d7 --- /dev/null +++ b/demo/demo_scene.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=6 format=3 uid="uid://cpbp7en0cerjl"] + +[ext_resource type="PackedScene" uid="uid://dx0v1i14fyw8c" path="res://demo/demo_plane.tscn" id="1_bbdyo"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_lajrc"] +sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) +ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) + +[sub_resource type="Sky" id="Sky_dsdc3"] +sky_material = SubResource("ProceduralSkyMaterial_lajrc") + +[sub_resource type="Environment" id="Environment_bkiet"] +background_mode = 2 +sky = SubResource("Sky_dsdc3") +tonemap_mode = 2 +glow_enabled = true + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1sawy"] +albedo_color = Color(0, 0.490196, 0.0823529, 1) + +[node name="Main" type="Node3D"] + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0) +shadow_enabled = true + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_bkiet") + +[node name="AeroBody3D" parent="." instance=ExtResource("1_bbdyo")] +linear_velocity = Vector3(0, 0, -100) + +[node name="CSGBox3D" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.12099, -190.455) +use_collision = true +size = Vector3(10000, 1, 10000) +material = SubResource("StandardMaterial3D_1sawy") diff --git a/demo/thruster.gd b/demo/thruster.gd new file mode 100644 index 0000000..b476f6a --- /dev/null +++ b/demo/thruster.gd @@ -0,0 +1,8 @@ +extends Marker3D + +@export var enabled : bool = true +@export var force : float = 100.0 + +func _physics_process(delta : float) -> void: + if enabled: + get_parent().apply_force(-global_transform.basis.z * force, global_transform.basis * position) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..442b2ea Binary files /dev/null and b/icon.png differ diff --git a/plugin.gd b/plugin.gd index e3895fa..4164001 100644 --- a/plugin.gd +++ b/plugin.gd @@ -1,6 +1,8 @@ @tool extends EditorPlugin +const PluginUtils = preload("./utils/plugin_utils.gd") + var path := PluginUtils.get_plugin_path("Godot Aerodynamic Physics") #icons @@ -28,7 +30,7 @@ const manual_aero_surface_config = preload("./core/aero_influencer_3d/aero_surfa #const procedural_aero_surface_config = preload("./core/aero_influencer_3d/aero_surface_3d/procedural_aero_surface_3d/procedural_aero_surface_config.gd") func _enter_tree(): - SettingsUtils.ifndef("physics/3d/aerodynamics/substeps", 1) + ifndef("physics/3d/aerodynamics/substeps", 1) add_autoload_singleton("AeroUnits", path + "/core/singletons/aero_units.gd") add_node_3d_gizmo_plugin(gizmo_plugin_instance) @@ -57,3 +59,11 @@ func _exit_tree(): remove_autoload_singleton("AeroUnits") remove_node_3d_gizmo_plugin(gizmo_plugin_instance) + +static func ifndef(setting : String, default_value : Variant) -> Variant: + if not ProjectSettings.has_setting(setting): + ProjectSettings.set_setting(setting, default_value) + if not default_value == null: + ProjectSettings.set_initial_value(setting, default_value) + + return ProjectSettings.get_setting(setting) diff --git a/utils/math_utils.gd b/utils/math_utils.gd new file mode 100644 index 0000000..e808066 --- /dev/null +++ b/utils/math_utils.gd @@ -0,0 +1,147 @@ +# From https://github.com/addmix/godot_utils + +const E := 2.718281828459045 + + + +#type-less functions + + + +static func toggle(condition : bool, _true : Variant, _false : Variant) -> Variant: + return float(condition) * _true + float(!condition) * _false + + + +#float math functions + + + +static func bias(x : float, bias : float) -> float: + var f : float = 1 - bias + var k : float = f * f * f + return (x * k) / (x * k - x + 1) + +#branchlessly toggles a float between two values given a condition +static func float_toggle(condition : bool, _true : float, _false : float) -> float: + return float(condition) * _true + float(!condition) * _false + +static func log_with_base(value : float, base : float) -> float: + return log(value) / log(base) + +static func mix(a : float, b : float, amount : float) -> float: + return (a - amount) * a + amount * b + +#deprecated, use GDScript global move_toward() function +static func move_to(position : float, target : float, speed : float = 1.0) -> float: + var direction : float = sign(target - position) + var new_position = position + direction * speed + var new_direction : float = sign(target - new_position) + + return float_toggle(direction == new_direction, new_position, target) + +#smooth minimum +static func polynomial_smin(a : float, b : float, k : float =0.1) -> float: + var h = clamp(0.5 + 0.5 * (a - b) / k, 0.0, 1.0) + return mix(a, b, h) - k * h * (1.0 - h) + +static func sigmoid(x : float, e : float = E) -> float: + return pow(e, x) / pow(e, x) + 1.0 + + + + +#matrix math stuff, very inefficient stuff + + + +#unused +static func check_squareness(a : Array) -> void: + if a.size() != a[0].size(): + push_error("Matrix not square") + +static func identity_matrix(n : int) -> Array: + var matrix := [] + + for y in range(n): + var row := [] + for x in range(n): + row.append(int(y == x)) + matrix.append(row) + + return matrix + +#https://integratedmlai.com/matrixinverse/ +static func inverse(a : Array) -> Array: + var n := a.size() + var am := a.duplicate(true) + var I = identity_matrix(n) + var im = I.duplicate(true) + + for fd in range(n): + var fdScaler : float = 1.0 / am[fd][fd] + + for j in range(n): + am[fd][j] *= fdScaler + im[fd][j] *= fdScaler + + for i in range(n): + if i == fd: + continue + + var crScaler : float = am[i][fd] + for j in range(n): + am[i][j] = am[i][j] - crScaler * am[fd][j] + im[i][j] = im[i][j] - crScaler * im[fd][j] + + return im + +static func multiply(a : Array, b : Array) -> Array: + var matrix := zero_matrix(a.size(), b[0].size()) + + for i in range(a.size()): + for j in range(b[0].size()): + for k in range(a[0].size()): + matrix[i][j] = matrix[i][j] + a[i][k] * b[k][j] + return matrix + +#plane intersection fucntion +#http://tbirdal.blogspot.com/2016/10/a-better-approach-to-plane-intersection.html +static func ray_plane_intersection(p1 : Vector3, n1 : Vector3, p2 : Vector3, n2 : Vector3, p0 : Vector3) -> Array[Vector3]: + + var M := [ + [2, 0, 0, n1.x, n2.x], + [0, 2, 0, n1.y, n2.y], + [0, 0, 2, n1.z, n2.z], + [n1.x, n1.y, n1.z, 0, 0], + [n2.x, n2.y, n2.z, 0, 0]] + + var bx := p1 * n1 + var by := p2 * n2 + + var b4 := bx.x + bx.y + bx.z + var b5 := by.x + by.y + by.z + + var b = [ + [2*p0.x], + [2*p0.y], + [2*p0.z], + [b4], + [b5]] + +# warning-ignore:unused_variable + var x := multiply(inverse(M), b) + var p = Vector3(x[0][0], x[0][1], x[0][2]) + var n = n1.cross(n2) + return [p, n] + +#matrix multiplication funcs +#https://godotengine.org/qa/41768/matrix-matrix-vector-multiplication +static func zero_matrix(nX : int, nY : int) -> Array: + var matrix := [] + for x in range(nX): + matrix.append([]) +# warning-ignore:unused_variable + for y in range(nY): + matrix[x].append(0) + return matrix diff --git a/utils/node_utils.gd b/utils/node_utils.gd new file mode 100644 index 0000000..568062a --- /dev/null +++ b/utils/node_utils.gd @@ -0,0 +1,119 @@ +#returns signal connection error, if any. Mainly for plugin nodes +static func connect_signal_safe(node : Node, _signal : StringName, callable : Callable, flags : int = 0) -> int: + if not node.has_signal(_signal): + return node.connect(_signal, callable, flags) + #mimick godot's default error for doubly connected signals + push_warning("Signal already connected") + return ERR_INVALID_PARAMETER + + +static func get_descendants(node : Node, include_internal : bool = false) -> Array[Node]: + var children : Array[Node] = node.get_children(include_internal) + + #array for storing all descendants + var arr := [] + + for i in range(children.size()): + arr.append(children[i]) + #add child's array contents to self's array + arr += get_descendants(children[i]) + + return arr + +static func get_first_child_of_type(node : Node, type) -> Node: + for child in node.get_children(): + if is_instance_of(child, type): + return child + return null + +static func get_first_descandant_of_type(node : Node, type, include_internal : bool = false) -> Node: + if is_instance_of(node, type): + return node + + for child in node.get_children(include_internal): + #returns node or null + var descendant : Node = get_first_descandant_of_type(child, type, include_internal) + #if not null, we have found a match + if descendant: + return descendant + + return null + +static func get_descendants_of_type(node : Node, type, include_internal : bool = false) -> Array[Node]: + var descandants_of_type : Array[Node] = [] + for child in node.get_children(include_internal): + if is_instance_of(child, type): + descandants_of_type.append(child) + descandants_of_type = descandants_of_type + get_descendants_of_type(child, type) + return descandants_of_type + +static func has_node_of_type(node : Node, type) -> bool: + for child in node.get_children(): + if is_instance_of(child, type): + return true + return false + + + +static func get_first_parent_of_type(node : Node, type) -> Node: + var parent := node.get_parent() + if parent == null: + return null + elif is_instance_of(parent, type): + return parent + else: + return get_first_parent_of_type(parent, type) + +static func get_first_parent_of_type_with_string(node : Node, type : String) -> Node: + var parent := node.get_parent() + if parent == null: + return null + elif is_instance_of_string(parent, type): + return parent + else: + return get_first_parent_of_type_with_string(parent, type) + + +static func get_first_parent_with_name(node : Node, _name : String) -> Node: + var parent := node.get_parent() + if parent == null: + return null + elif parent.name == _name: + return parent + else: + return get_first_parent_with_name(parent, _name) + +static func is_instance_of_string(obj : Object, given_class_name : String) -> bool: + if ClassDB.class_exists(given_class_name): + # We have a build in class + return obj.is_class(given_class_name) + else: + # We don't have a build in class + # It must be a script class + var class_script : Script + # Assume it is a script path and try to load it + if ResourceLoader.exists(given_class_name): + class_script = load(given_class_name) as Script + + if class_script == null: + # Assume it is a class name and try to find it + for x in ProjectSettings.get_global_class_list(): + + if str(x["class"]) == given_class_name: + class_script = load(str(x["path"])) + break + + if class_script == null: + # Unknown class + return false + + # Get the script of the object and try to match it + var check_script : Script = obj.get_script() + while check_script != null: + if check_script == class_script: + return true + + check_script = check_script.get_base_script() + + # Match not found + return false diff --git a/utils/plugin_utils.gd b/utils/plugin_utils.gd new file mode 100644 index 0000000..5841d0a --- /dev/null +++ b/utils/plugin_utils.gd @@ -0,0 +1,37 @@ +# From https://github.com/addmix/godot_utils + +static func get_plugin_list() -> Array: + var plugins := [] + for directory in DirAccess.get_directories_at("res://addons"): + var path := "res://addons/%s/plugin.cfg" % directory + if FileAccess.file_exists(path): + var config := ConfigFile.new() + var err := config.load(path) + + plugins.append({ + "name": config.get_value("plugin", "name", ""), + "description": config.get_value("plugin", "description", ""), + "author": config.get_value("plugin", "author", ""), + "version": config.get_value("plugin", "version", ""), + "script": config.get_value("plugin", "script", ""), + "path": path.get_base_dir() + }) + + return plugins + +static func get_plugin_dictionary() -> Dictionary: + var plugin_dictionary := {} + + var list := get_plugin_list() + for plugin in list: + plugin_dictionary[plugin["name"]] = plugin + + return plugin_dictionary + +#very slow, use sparingly. +static func get_plugin_path(plugin_name : String) -> String: + var dictionary := get_plugin_dictionary() + if not dictionary.has(plugin_name): + push_error("Plugin name not found in plugin dictionary.") + return dictionary[plugin_name ]["path"] + diff --git a/utils/point_3d/point_3d.gd b/utils/point_3d/point_3d.gd new file mode 100644 index 0000000..912f740 --- /dev/null +++ b/utils/point_3d/point_3d.gd @@ -0,0 +1,100 @@ +# From https://github.com/addmix/godot_utils + +@tool +extends MeshInstance3D + +@export var color := Color(1, 1, 1): + set(x): + color = x + material.set_shader_parameter("_color", color) +@export var width := 0.1: + set(x): + width = x + material.set_shader_parameter("_width", width) +@export var checker := true: + set(x): + checker = x + material.set_shader_parameter("checker_pattern", checker) + +var material := ShaderMaterial.new() + +var _mesh : Array[Vector3] = [Vector3(0, -1, 0), Vector3(-1, 0, 0), Vector3(0, 1, 0), Vector3(1, 0, 0)] +var _verticies : Array[Vector3] = [Vector3(0, -1, 0), #index 0 bottom point +#go clockwise from -z position +Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(-1, 0, 0), #index 1-4 side poitns +#top +Vector3(0, 1, 0)] #index 5 top point + +# 5 +# +# 1 +# 4 2 +# 3 +# +# 0 + +#bottom triangle order +#021 +#032 +#043 +#014 + +#top triangle order +#512 +#523 +#534 +#541 + + + +func _init(_color : Color = Color(), _width : float = 0.1, _checker : bool = true) -> void: + material.shader = preload("./point_3d.gdshader") + material.render_priority = 100 + + color = _color + width = _width + checker = _checker + + var st := SurfaceTool.new() + + st.begin(Mesh.PRIMITIVE_TRIANGLES) + st.set_material(material) + #bottom 4 triangles + #northeast + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[2]) + st.add_vertex(_verticies[1]) + #southeast + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[3]) + st.add_vertex(_verticies[2]) + #southwest + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[4]) + st.add_vertex(_verticies[3]) + + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[1]) + st.add_vertex(_verticies[4]) + + #top 4 triangles + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[1]) + st.add_vertex(_verticies[2]) + + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[2]) + st.add_vertex(_verticies[3]) + + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[3]) + st.add_vertex(_verticies[4]) + + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[4]) + st.add_vertex(_verticies[1]) + + st.generate_normals() + mesh = st.commit() + + custom_aabb = AABB(Vector3.ZERO, Vector3(100, 100, 100)) diff --git a/utils/point_3d/point_3d.gdshader b/utils/point_3d/point_3d.gdshader new file mode 100644 index 0000000..b1e26d1 --- /dev/null +++ b/utils/point_3d/point_3d.gdshader @@ -0,0 +1,20 @@ +shader_type spatial; +render_mode unshaded, depth_test_disabled; + +uniform float _width = 1.0; +uniform vec3 _color : source_color = vec3(0, 0, 0); +uniform bool checker_pattern = false; + +void vertex() { + VERTEX = VERTEX * _width; + COLOR = vec4(VERTEX, 0); +} + +void fragment() { + vec3 output_color = _color; + if (checker_pattern) { + ALBEDO = _color * sign(COLOR.r) * sign(COLOR.g) * sign(COLOR.b); + } else { + ALBEDO = _color; + } +} \ No newline at end of file diff --git a/utils/vector_3d/vector_3d.gd b/utils/vector_3d/vector_3d.gd new file mode 100644 index 0000000..b1074de --- /dev/null +++ b/utils/vector_3d/vector_3d.gd @@ -0,0 +1,117 @@ +# From https://github.com/addmix/godot_utils + +@tool +extends MeshInstance3D + +@export var value := Vector3.ZERO: + set(x): + value = x + material.set_shader_parameter("_length", value.length()) + + #prevent error when value is 0 + var length_squared := value.length_squared() + if is_equal_approx(length_squared, 0.0): + #reset rotation + basis = Basis() + return + + var up = Vector3(0, 1, 0) + var dot := value.dot(up) + var dot_squared := dot * dot + + if is_equal_approx(dot_squared, length_squared): + up = Vector3(1, 0, 0) + + transform = transform.looking_at(value, up) +@export var color := Color(1, 1, 1): + set(x): + color = x + material.set_shader_parameter("_color", color) +@export var width := 0.1: + set(x): + width = x + material.set_shader_parameter("_width", width) +@export var checker := false: + set(x): + checker = x + material.set_shader_parameter("checker_pattern", checker) + +var material := ShaderMaterial.new() + +var _mesh : Array[Vector3] = [Vector3(0, -1, 0), Vector3(-1, 0, 0), Vector3(0, 1, 0), Vector3(1, 0, 0)] +var _verticies : Array[Vector3] = [Vector3(0, -1, 0), #index 0 bottom point +#go clockwise from -z position +Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(-1, 0, 0), #index 1-4 side poitns +#top +Vector3(0, 1, 0)] #index 5 top point + +# 5 +# +# 1 +# 4 2 +# 3 +# +# 0 + +#bottom triangle order +#021 +#032 +#043 +#014 + +#top triangle order +#512 +#523 +#534 +#541 + +func _init(_color : Color = Color(), _width : float = 0.1, _checker : bool = false) -> void: + material.shader = preload("./vector_3d.gdshader") + material.render_priority = 100 + + color = _color + width = _width + checker = _checker + + var st := SurfaceTool.new() + + st.begin(Mesh.PRIMITIVE_TRIANGLES) + st.set_material(material) + #bottom 4 triangles + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[2]) + st.add_vertex(_verticies[1]) + + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[3]) + st.add_vertex(_verticies[2]) + + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[4]) + st.add_vertex(_verticies[3]) + + st.add_vertex(_verticies[0]) + st.add_vertex(_verticies[1]) + st.add_vertex(_verticies[4]) + + #top 4 triangles + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[1]) + st.add_vertex(_verticies[2]) + + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[2]) + st.add_vertex(_verticies[3]) + + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[3]) + st.add_vertex(_verticies[4]) + + st.add_vertex(_verticies[5]) + st.add_vertex(_verticies[4]) + st.add_vertex(_verticies[1]) + + st.generate_normals() + mesh = st.commit() + + custom_aabb = AABB(Vector3.ZERO, Vector3(100, 100, 100)) diff --git a/utils/vector_3d/vector_3d.gdshader b/utils/vector_3d/vector_3d.gdshader new file mode 100644 index 0000000..33db361 --- /dev/null +++ b/utils/vector_3d/vector_3d.gdshader @@ -0,0 +1,22 @@ +shader_type spatial; +render_mode unshaded, depth_test_disabled; + +uniform float _length = 0.0; +uniform float _width = 1.0; +uniform vec3 _color = vec3(0, 0, 0); +uniform bool checker_pattern = false; + +void vertex() { + VERTEX = VERTEX * _width; + COLOR = vec4(VERTEX, 0); + VERTEX -= float(VERTEX.z < 0.0) * vec3(0, 0, _length); +} + +void fragment() { + vec3 output_color = _color; + if (checker_pattern) { + ALBEDO = _color * sign(COLOR.r) * sign(COLOR.g) * sign(COLOR.b); + } else { + ALBEDO = _color; + } +} \ No newline at end of file