diff --git a/art/exported/items/equipment/net.glb b/art/exported/items/equipment/net.glb new file mode 100644 index 0000000..db4fa34 Binary files /dev/null and b/art/exported/items/equipment/net.glb differ diff --git a/art/exported/items/equipment/net.glb.import b/art/exported/items/equipment/net.glb.import new file mode 100644 index 0000000..28667d6 --- /dev/null +++ b/art/exported/items/equipment/net.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dsa6yss1sxfcy" +path="res://.godot/imported/net.glb-cbbc53905aec6a50c4a3322ca7a93b1e.scn" + +[deps] + +source_file="res://art/exported/items/equipment/net.glb" +dest_files=["res://.godot/imported/net.glb-cbbc53905aec6a50c4a3322ca7a93b1e.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/art/exported/items/equipment/net_Material.png b/art/exported/items/equipment/net_Material.png new file mode 100644 index 0000000..bd5eff2 Binary files /dev/null and b/art/exported/items/equipment/net_Material.png differ diff --git a/art/exported/items/equipment/net_Material.png.import b/art/exported/items/equipment/net_Material.png.import new file mode 100644 index 0000000..531b99b --- /dev/null +++ b/art/exported/items/equipment/net_Material.png.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dprm50tbiulog" +path="res://.godot/imported/net_Material.png-f4c4b0400561bc2751fe719b6b1a1e1a.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "ef2c2ae657c9460c90eb808ba5217f39" +} + +[deps] + +source_file="res://art/exported/items/equipment/net_Material.png" +dest_files=["res://.godot/imported/net_Material.png-f4c4b0400561bc2751fe719b6b1a1e1a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/art/exported/items/equipment/net_Plane Base Color.png b/art/exported/items/equipment/net_Plane Base Color.png new file mode 100644 index 0000000..795d0a3 Binary files /dev/null and b/art/exported/items/equipment/net_Plane Base Color.png differ diff --git a/art/exported/items/equipment/net_Plane Base Color.png.import b/art/exported/items/equipment/net_Plane Base Color.png.import new file mode 100644 index 0000000..7f40afc --- /dev/null +++ b/art/exported/items/equipment/net_Plane Base Color.png.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://seq5b31mwcqr" +path="res://.godot/imported/net_Plane Base Color.png-3875ae3dd186ddf319c55aabb13e663f.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "a6001c381d4c8e16bd2d64ea74ccff82" +} + +[deps] + +source_file="res://art/exported/items/equipment/net_Plane Base Color.png" +dest_files=["res://.godot/imported/net_Plane Base Color.png-3875ae3dd186ddf319c55aabb13e663f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/art/source/items/equipment/net.blend b/art/source/items/equipment/net.blend new file mode 100644 index 0000000..0a7ba4c Binary files /dev/null and b/art/source/items/equipment/net.blend differ diff --git a/player/net_attachment.gd b/player/net_attachment.gd index 8938a13..3c7843a 100644 --- a/player/net_attachment.gd +++ b/player/net_attachment.gd @@ -1,7 +1,88 @@ extends "res://player/tool_socket_attachment.gd" -@onready var _impact_marker: Marker3D = %ImpactMarker +const TextureSamplingPolicyScript := preload( + "res://main/texture_sampling_policy.gd" +) +const SPRING_ROOT_BONE: StringName = &"net_mid" +const SPRING_END_BONE: StringName = &"net_tip" +const IMPACT_BONE: StringName = &"net_rim" + +@export_range(0.0, 10.0, 0.05) var spring_stiffness: float = 1.8 +@export_range(0.0, 1.0, 0.01) var spring_drag: float = 0.68 +@export_range(0.0, 10.0, 0.05) var spring_gravity: float = 0.3 +@export_range(0.01, 0.5, 0.01) var spring_end_length: float = 0.1 + +@onready var _catching_net: Node3D = %CatchingNet +@onready var _net_model: Node3D = %NetModel + +var _spring: SpringBoneSimulator3D +var _spring_was_visible: bool = false +var _net_skeleton: Skeleton3D + + +func _ready() -> void: + super._ready() + TextureSamplingPolicyScript.enforce_subtree(_net_model) + _configure_spring() + _sync_spring_activity() + + +func _process(_delta: float) -> void: + _sync_spring_activity() + + +func _configure_spring() -> void: + _net_skeleton = _net_model.find_child( + "Skeleton3D", true, false + ) as Skeleton3D + if _net_skeleton == null: + push_error("Imported net model is missing its Skeleton3D.") + return + if _net_skeleton.find_bone(SPRING_ROOT_BONE) < 0: + push_error("Imported net model is missing bone %s." % SPRING_ROOT_BONE) + return + if _net_skeleton.find_bone(SPRING_END_BONE) < 0: + push_error("Imported net model is missing bone %s." % SPRING_END_BONE) + return + if _net_skeleton.find_bone(IMPACT_BONE) < 0: + push_error("Imported net model is missing bone %s." % IMPACT_BONE) + return + + _spring = SpringBoneSimulator3D.new() + _spring.name = "NetSpring" + _spring.set_setting_count(1) + _spring.set_root_bone_name(0, SPRING_ROOT_BONE) + _spring.set_end_bone_name(0, SPRING_END_BONE) + _spring.set_extend_end_bone(0, true) + _spring.set_end_bone_direction( + 0, SkeletonModifier3D.BONE_DIRECTION_FROM_PARENT + ) + _spring.set_end_bone_length(0, spring_end_length) + _spring.set_stiffness(0, spring_stiffness) + _spring.set_drag(0, spring_drag) + _spring.set_gravity(0, spring_gravity) + _spring.set_gravity_direction(0, Vector3.DOWN) + _net_skeleton.add_child(_spring) func get_impact_world_position() -> Vector3: - return _impact_marker.global_position + if _net_skeleton == null: + return _catching_net.global_position + var impact_bone_index := _net_skeleton.find_bone(IMPACT_BONE) + if impact_bone_index < 0: + return _catching_net.global_position + return _net_skeleton.global_transform * ( + _net_skeleton.get_bone_global_pose(impact_bone_index).origin + ) + + +func _sync_spring_activity() -> void: + if _spring == null: + return + var is_visible := _catching_net.is_visible_in_tree() + if is_visible and not _spring_was_visible: + _spring.active = true + _spring.reset() + elif not is_visible: + _spring.active = false + _spring_was_visible = is_visible diff --git a/player/net_attachment.tscn b/player/net_attachment.tscn index 246e71c..da49a88 100644 --- a/player/net_attachment.tscn +++ b/player/net_attachment.tscn @@ -1,42 +1,7 @@ -[gd_scene load_steps=8 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://player/net_attachment.gd" id="1_script"] - -[sub_resource type="StandardMaterial3D" id="HandleMaterial"] -albedo_color = Color(0.25, 0.13, 0.055, 1) -roughness = 0.9 - -[sub_resource type="CylinderMesh" id="HandleMesh"] -material = SubResource("HandleMaterial") -top_radius = 0.025 -bottom_radius = 0.035 -height = 1.25 - -[sub_resource type="StandardMaterial3D" id="HoopMaterial"] -albedo_color = Color(0.34, 0.39, 0.4, 1) -metallic = 0.25 -roughness = 0.65 - -[sub_resource type="TorusMesh" id="HoopMesh"] -material = SubResource("HoopMaterial") -inner_radius = 0.24 -outer_radius = 0.29 -rings = 12 -ring_segments = 18 - -[sub_resource type="StandardMaterial3D" id="NetMaterial"] -transparency = 1 -albedo_color = Color(0.65, 0.78, 0.75, 0.42) -shading_mode = 0 -cull_mode = 2 - -[sub_resource type="CylinderMesh" id="NetMesh"] -material = SubResource("NetMaterial") -top_radius = 0.24 -bottom_radius = 0.08 -height = 0.34 -radial_segments = 12 -rings = 1 +[ext_resource type="PackedScene" path="res://art/exported/items/equipment/net.glb" id="2_model"] [node name="NetAttachment" type="BoneAttachment3D"] bone_name = "rod_socket" @@ -47,18 +12,9 @@ aligned_mount_path = NodePath("CatchingNet") unique_name_in_owner = true visible = false -[node name="Handle" type="MeshInstance3D" parent="CatchingNet"] -position = Vector3(0, 0.55, 0) -mesh = SubResource("HandleMesh") +[node name="ModelMount" type="Node3D" parent="CatchingNet"] +position = Vector3(-0.000217, -0.098636, -0.020218) +rotation = Vector3(0, 3.1415927, 0) -[node name="Hoop" type="MeshInstance3D" parent="CatchingNet"] -position = Vector3(0, 1.17, 0) -mesh = SubResource("HoopMesh") - -[node name="Bag" type="MeshInstance3D" parent="CatchingNet"] -position = Vector3(0, 1, 0) -mesh = SubResource("NetMesh") - -[node name="ImpactMarker" type="Marker3D" parent="CatchingNet"] +[node name="NetModel" parent="CatchingNet/ModelMount" unique_id=1627767240 instance=ExtResource("2_model")] unique_name_in_owner = true -position = Vector3(0, 1.17, 0) diff --git a/tests/net_attachment_validation.gd b/tests/net_attachment_validation.gd index 15b8b7d..6b53c08 100644 --- a/tests/net_attachment_validation.gd +++ b/tests/net_attachment_validation.gd @@ -57,23 +57,73 @@ func _run() -> void: "Net rear face did not follow the socket direction guide.", ) - var handle := catching_net.get_node_or_null("Handle") as MeshInstance3D - var hoop := catching_net.get_node_or_null("Hoop") as MeshInstance3D - var bag := catching_net.get_node_or_null("Bag") as MeshInstance3D - var impact_marker := ( - catching_net.get_node_or_null("ImpactMarker") as Marker3D + var model := catching_net.get_node_or_null("ModelMount/NetModel") as Node3D + _check(model != null, "Imported net model is missing.") + if model == null: + _finish(host_skeleton) + return + var skeleton := model.find_child("Skeleton3D", true, false) as Skeleton3D + _check(skeleton != null, "Imported net Skeleton3D is missing.") + if skeleton == null: + _finish(host_skeleton) + return + _check( + skeleton.find_bone("net_root") >= 0, + "Imported net_root bone is missing.", ) - _check(handle != null and handle.mesh != null, "Placeholder handle is missing.") - _check(hoop != null and hoop.mesh != null, "Placeholder hoop is missing.") - _check(bag != null and bag.mesh != null, "Placeholder net bag is missing.") - _check(impact_marker != null, "Net impact marker is missing.") - if impact_marker != null: + _check( + skeleton.find_bone("net_rim") >= 0, + "Imported net_rim bone is missing.", + ) + _check( + skeleton.find_bone("net_mid") >= 0, + "Imported net_mid bone is missing.", + ) + _check( + skeleton.find_bone("net_tip") >= 0, + "Imported net_tip bone is missing.", + ) + + var spring := skeleton.get_node_or_null("NetSpring") as SpringBoneSimulator3D + _check(spring != null, "Net spring simulator was not created.") + if spring != null: + _check(spring.get_setting_count() == 1, "Net spring settings changed.") _check( - attachment.get_impact_world_position().is_equal_approx( - impact_marker.global_position - ), - "Net impact position moved away from its marker.", + spring.get_root_bone_name(0) == &"net_mid", + "Net spring root must remain net_mid.", ) + _check( + spring.get_end_bone_name(0) == &"net_tip", + "Net spring end must remain net_tip.", + ) + _check(not spring.active, "Hidden net spring must be suspended.") + catching_net.visible = true + await process_frame + await process_frame + _check(spring.active, "Visible net spring must be active.") + _check(spring.get_joint_count(0) == 2, "Net spring chain is incomplete.") + _check( + not attachment.get_impact_world_position().is_equal_approx( + catching_net.global_position + ), + "Net impact position did not follow the authored rim bone.", + ) + catching_net.visible = false + await process_frame + _check(not spring.active, "Hidden net spring did not suspend.") + + for child: Node in model.find_children("*", "MeshInstance3D", true, false): + var mesh_instance := child as MeshInstance3D + if mesh_instance.mesh == null: + continue + for surface_index: int in mesh_instance.mesh.get_surface_count(): + var material := mesh_instance.mesh.surface_get_material(surface_index) + if material is BaseMaterial3D: + _check( + (material as BaseMaterial3D).texture_filter + == BaseMaterial3D.TEXTURE_FILTER_NEAREST, + "Net material does not use nearest texture sampling.", + ) var rod_attachment := ( FishingRodAttachmentScene.instantiate() as BoneAttachment3D