3D Cameras with sweep and animation

This commit is contained in:
Marco 2025-06-27 15:06:33 +02:00
commit 7e76edc153
48 changed files with 3211 additions and 1511 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,9 @@
[gd_scene load_steps=8 format=3 uid="uid://cpx5yjfg3a2hw"]
[gd_scene load_steps=9 format=3 uid="uid://cpx5yjfg3a2hw"]
[ext_resource type="Script" uid="uid://dxhf1twoxf4j0" path="res://Scripts/Interactables/AlarmBox3D.cs" id="1_4ysgp"]
[ext_resource type="AudioStream" uid="uid://bjvklk7qmlivd" path="res://SFX/288963__littlerobotsoundfactory__click_electronic_14.wav" id="3_5t8l3"]
[ext_resource type="PackedScene" uid="uid://s4yb3i3td11j" path="res://3D/BlockbenchModels/AlarmBox/Alarm_Box.gltf" id="4_4ysgp"]
[ext_resource type="Script" uid="uid://0g2sdu48c2x8" path="res://Scripts/Actors/3D/AlarmSoundPlayer3D.cs" id="4_t4kxh"]
[sub_resource type="SphereShape3D" id="SphereShape3D_itd0i"]
radius = 0.419927
@ -55,7 +56,7 @@ metadata/_edit_group_ = true
shape = SubResource("SphereShape3D_itd0i")
[node name="ActivationSound" type="AudioStreamPlayer3D" parent="."]
visible = false
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.197789)
stream = ExtResource("3_5t8l3")
bus = &"Effects"
@ -66,4 +67,13 @@ libraries = {
&"": SubResource("AnimationLibrary_t4kxh")
}
[node name="AlarmSound" type="AudioStreamPlayer3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.198581)
volume_db = -5.551
max_distance = 25.08
bus = &"Effects"
script = ExtResource("4_t4kxh")
[connection signal="PlayActivationSound" from="." to="ActivationSound" method="play"]
[editable path="blockbench_export"]

View file

@ -1,20 +1,48 @@
[gd_scene load_steps=5 format=3 uid="uid://b0rhxqjs52fsv"]
[gd_scene load_steps=6 format=3 uid="uid://b0rhxqjs52fsv"]
[ext_resource type="Script" uid="uid://ccxnvbthsvka3" path="res://Scripts/Actors/Destructible3D.cs" id="1_ix4p0"]
[ext_resource type="Script" uid="uid://dg3aho2ulfngs" path="res://Scripts/Actors/3D/SecurityCamera3D.cs" id="1_tkk1t"]
[ext_resource type="Resource" uid="uid://bes254wblt1lm" path="res://Resources/Bullets/Explosion_Harmless_Small_3D.tres" id="2_1t1m8"]
[ext_resource type="PackedScene" uid="uid://bt5weyaiyhtrl" path="res://3D/BlockbenchModels/Camera/Camera.gltf" id="2_ix4p0"]
[ext_resource type="Script" uid="uid://cmgcelj1qxg8o" path="res://addons/tattomoosa.vision_cone_3d/src/VisionCone3D.gd" id="4_f1teg"]
[sub_resource type="BoxShape3D" id="BoxShape3D_hsg1w"]
size = Vector3(0.763428, 0.469452, 0.77832)
[sub_resource type="CylinderShape3D" id="CylinderShape3D_f1teg"]
[node name="Camera" type="StaticBody3D" groups=["Destroyable"]]
collision_layer = 16
collision_layer = 64
collision_mask = 0
script = ExtResource("1_ix4p0")
script = ExtResource("1_tkk1t")
Health = 6.0
ExplosionData = ExtResource("2_1t1m8")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00549316, 0.0253752, -0.000488281)
shape = SubResource("BoxShape3D_hsg1w")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00549316, -0.419623, -0.000488281)
shape = SubResource("CylinderShape3D_f1teg")
[node name="blockbench_export" parent="." instance=ExtResource("2_ix4p0")]
[node name="Camera" parent="blockbench_export/Node/Model" index="0"]
transform = Transform3D(0.819152, 0.148453, -0.554032, 0, 0.965926, 0.258819, 0.573576, -0.212012, 0.79124, 0, 0.25, -0.0625)
[node name="RemoteTransform3D" type="RemoteTransform3D" parent="blockbench_export/Node/Model/Camera" index="2"]
transform = Transform3D(1, -1.86265e-09, 0, 1.86265e-09, 1, 1.49012e-08, 0, -4.47035e-08, 1, -0.0399265, 0.00923264, -0.611748)
remote_path = NodePath("../../../../../VisionCone3D")
update_scale = false
[node name="VisionCone3D" type="Area3D" parent="."]
transform = Transform3D(0.819145, 0.148452, -0.554027, 1.79916e-09, 0.965925, 0.258817, 0.573572, -0.212012, 0.791233, 0.307593, 0.100586, -0.571398)
collision_layer = 32
collision_mask = 2
script = ExtResource("4_f1teg")
range = 5.62515
angle = 63.7926
debug_draw = true
vision_test_mode = 0
collision_layer_ = 32
collision_mask_ = 2
collision_environment_mask = 17
metadata/_custom_type_script = "uid://cmgcelj1qxg8o"
[connection signal="AnimationStart" from="." to="blockbench_export/AnimationPlayer" method="play"]
[connection signal="body_sighted" from="VisionCone3D" to="." method="OnBodySighted"]
[editable path="blockbench_export"]

View file

@ -0,0 +1,31 @@
[gd_resource type="Resource" script_class="FuncGodotFGDModelPointClass" load_steps=4 format=3 uid="uid://m0xahs14etiy"]
[ext_resource type="Resource" uid="uid://5bc1qysixhmh" path="res://3D/TrenchBroom/EntityDefinitions/base/actor_base.tres" id="1_yxg7b"]
[ext_resource type="PackedScene" uid="uid://b0rhxqjs52fsv" path="res://3D/Scenes/Props/Camera_3D.tscn" id="2_yxg7b"]
[ext_resource type="Script" uid="uid://dkmyelig23ub5" path="res://addons/func_godot/src/fgd/func_godot_fgd_model_point_class.gd" id="3_mqd4f"]
[resource]
script = ExtResource("3_mqd4f")
target_map_editor = 1
models_sub_folder = ""
scale_expression = ""
generate_size_property = false
rotation_offset = Vector3(0, 180, 0)
generate_gd_ignore_file = false
scene_file = ExtResource("2_yxg7b")
apply_rotation_on_map_build = true
apply_scale_on_map_build = false
classname = "actor_securitycamera"
description = "Security Camera"
func_godot_internal = false
base_classes = Array[Resource]([ExtResource("1_yxg7b")])
class_properties = {}
class_property_descriptions = {}
auto_apply_to_matching_node_properties = false
meta_properties = {
"model": "\"3D/MapModels/actor_securitycamera.glb\"",
"size": AABB(-4, -4, -4, 4, 4, 4)
}
node_class = ""
name_property = ""
metadata/_custom_type_script = "uid://c83r7t467hm4m"

View file

@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="FuncGodotFGDFile" load_steps=36 format=3 uid="uid://b700sa4be6dfa"]
[gd_resource type="Resource" script_class="FuncGodotFGDFile" load_steps=37 format=3 uid="uid://b700sa4be6dfa"]
[ext_resource type="Resource" uid="uid://ia1t5p4mhom3" path="res://3D/TrenchBroom/EntityDefinitions/point/lights/light_omni.tres" id="1_7vcj1"]
[ext_resource type="Script" uid="uid://cknmd0lgmorx2" path="res://addons/func_godot/src/fgd/func_godot_fgd_file.gd" id="1_lykim"]
@ -35,6 +35,7 @@
[ext_resource type="Resource" uid="uid://bjqfsr0ws3kbi" path="res://3D/TrenchBroom/EntityDefinitions/point/actors/Actor_Teleporter.tres" id="32_elv2e"]
[ext_resource type="Resource" uid="uid://yw4443572o6u" path="res://3D/TrenchBroom/EntityDefinitions/point/actors/Actor_Furniture_Triangle.tres" id="33_aawmv"]
[ext_resource type="Resource" uid="uid://cr3xqfglolihb" path="res://3D/TrenchBroom/EntityDefinitions/point/actors/Actor_Furniture_ComputerTower.tres" id="34_wy0ht"]
[ext_resource type="Resource" uid="uid://m0xahs14etiy" path="res://3D/TrenchBroom/EntityDefinitions/point/actors/Actor_SecurityCamera.tres" id="35_3jqmy"]
[resource]
script = ExtResource("1_lykim")
@ -42,5 +43,5 @@ export_file = false
target_map_editor = 1
fgd_name = "FuncGodot"
base_fgd_files = Array[Resource]([])
entity_definitions = Array[Resource]([ExtResource("1_7vcj1"), ExtResource("2_u7uvb"), ExtResource("3_u7uvb"), ExtResource("4_1nmxk"), ExtResource("5_ljb2x"), ExtResource("6_seuvu"), ExtResource("7_fmwex"), ExtResource("8_c8m8m"), ExtResource("9_14sjf"), ExtResource("10_xik3w"), ExtResource("11_dgiqm"), ExtResource("12_1teik"), ExtResource("13_f0hti"), ExtResource("14_6ld6e"), ExtResource("15_g8j5j"), ExtResource("16_7h4of"), ExtResource("17_m3sbm"), ExtResource("18_m3sbm"), ExtResource("19_hvhb8"), ExtResource("20_3v5us"), ExtResource("21_g67cj"), ExtResource("22_iy67w"), ExtResource("23_b5lmu"), ExtResource("24_ndas4"), ExtResource("25_n7wsi"), ExtResource("26_v48k5"), ExtResource("27_ombvk"), ExtResource("28_31lma"), ExtResource("29_louv0"), ExtResource("30_wfgfu"), ExtResource("31_elv2e"), ExtResource("32_elv2e"), ExtResource("33_aawmv"), ExtResource("34_wy0ht")])
entity_definitions = Array[Resource]([ExtResource("1_7vcj1"), ExtResource("2_u7uvb"), ExtResource("3_u7uvb"), ExtResource("4_1nmxk"), ExtResource("5_ljb2x"), ExtResource("6_seuvu"), ExtResource("7_fmwex"), ExtResource("8_c8m8m"), ExtResource("9_14sjf"), ExtResource("10_xik3w"), ExtResource("11_dgiqm"), ExtResource("12_1teik"), ExtResource("13_f0hti"), ExtResource("14_6ld6e"), ExtResource("15_g8j5j"), ExtResource("16_7h4of"), ExtResource("17_m3sbm"), ExtResource("18_m3sbm"), ExtResource("19_hvhb8"), ExtResource("20_3v5us"), ExtResource("21_g67cj"), ExtResource("22_iy67w"), ExtResource("23_b5lmu"), ExtResource("24_ndas4"), ExtResource("25_n7wsi"), ExtResource("26_v48k5"), ExtResource("27_ombvk"), ExtResource("28_31lma"), ExtResource("29_louv0"), ExtResource("30_wfgfu"), ExtResource("31_elv2e"), ExtResource("32_elv2e"), ExtResource("33_aawmv"), ExtResource("34_wy0ht"), ExtResource("35_3jqmy")])
metadata/_custom_type_script = "uid://cknmd0lgmorx2"

View file

@ -1,19 +1,12 @@
[gd_scene load_steps=60 format=3 uid="uid://ec4m3geediis"]
[gd_scene load_steps=14 format=3 uid="uid://ec4m3geediis"]
[ext_resource type="Script" uid="uid://cvisn0b641od4" path="res://addons/cyclops_level_builder/nodes/cyclops_block.gd" id="1_18fbr"]
[ext_resource type="Script" uid="uid://ba0tf7ihw4hpp" path="res://Scripts/Misc/CameraController3D.cs" id="1_g4gcm"]
[ext_resource type="Script" uid="uid://b8g8mflgsr5dc" path="res://Scripts/GameController.cs" id="1_joeuf"]
[ext_resource type="PackedScene" uid="uid://dkwi1hu1bixoe" path="res://Scenes/HUD/HUD.tscn" id="2_itd0i"]
[ext_resource type="Script" uid="uid://djeq3sxhsep3c" path="res://addons/cyclops_level_builder/resources/data_vector_byte.gd" id="2_kler0"]
[ext_resource type="Resource" uid="uid://bq2o78engsuyt" path="res://Resources/Maps/IsoMapTest2.tres" id="2_l7bgk"]
[ext_resource type="Script" uid="uid://c5nxsq3tyxcx6" path="res://Scripts/InventoryManager.cs" id="3_itd0i"]
[ext_resource type="Script" uid="uid://civ3w78ahacnu" path="res://addons/cyclops_level_builder/resources/data_vector_int.gd" id="3_k6bah"]
[ext_resource type="Script" uid="uid://db41w3h28c2la" path="res://addons/cyclops_level_builder/resources/data_vector_float.gd" id="4_01bfr"]
[ext_resource type="Script" uid="uid://c43o57os2lmc3" path="res://addons/cyclops_level_builder/resources/mesh_vector_data.gd" id="5_hmj6t"]
[ext_resource type="Material" uid="uid://dc88uealq5q85" path="res://textures/Walls/Wall0.tres" id="6_k6bah"]
[ext_resource type="Material" uid="uid://y1ovvv727tky" path="res://textures/Floors/Floor0.tres" id="7_01bfr"]
[ext_resource type="PackedScene" uid="uid://bh0ye0m85cyb1" path="res://3D/box.tscn" id="9_hmj6t"]
[ext_resource type="PackedScene" uid="uid://b5qj2iaprortr" path="res://3D/Mainframe3D.tscn" id="10_lkbo1"]
[ext_resource type="Script" uid="uid://cnkipcolyj61w" path="res://Scripts/AlarmManager.cs" id="6_w4wji"]
[ext_resource type="AudioStream" uid="uid://blohh20jktoyo" path="res://SFX/270641__phantastonia__alarm2.wav" id="7_5vm3d"]
[ext_resource type="Texture2D" uid="uid://du8xcvbnf30o2" path="res://ExternalMaterial/Barrel/Barrels.png" id="11_id3mo"]
[ext_resource type="PackedScene" uid="uid://rimplblbptcd" path="res://Scenes/Actors/IsoPlayer_FSM.tscn" id="12_g4gcm"]
[ext_resource type="PackedScene" uid="uid://c8gtrjf2xeue7" path="res://3D/MapScenes/TestLevel.tscn" id="12_g83w3"]
@ -21,399 +14,6 @@
[ext_resource type="PackedScene" uid="uid://cupulrjeeivxm" path="res://3D/MapScenes/TestLevel2.tscn" id="18_e2nai"]
[ext_resource type="PackedScene" uid="uid://diveeuoyyp7n" path="res://3D/MapScenes/RebelBase3D.tscn" id="20_1dvih"]
[sub_resource type="Resource" id="Resource_id3mo"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_mgr2t"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
name = &"color"
category = ""
data_type = 4
stride = 4
[sub_resource type="Resource" id="Resource_g83w3"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 0, 0, 0, 0, 0)
name = &"material_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_joeuf"]
script = ExtResource("2_kler0")
data = PackedByteArray(1, 1, 1, 1, 1, 1)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_lhprj"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
name = &"uv_transform"
category = ""
data_type = 8
stride = 6
[sub_resource type="Resource" id="Resource_g4gcm"]
script = ExtResource("2_kler0")
data = PackedByteArray(1, 1, 1, 1, 1, 1)
name = &"visible"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_l7bgk"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
name = &"color"
category = ""
data_type = 4
stride = 4
[sub_resource type="Resource" id="Resource_itd0i"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
name = &"face_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_1dvih"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
name = &"normal"
category = ""
data_type = 6
stride = 3
[sub_resource type="Resource" id="Resource_e2nai"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
name = &"vertex_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_ogy4l"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(0, 3, -3, 0, 3, 4, 0, 0, 4, 0, 0, -3, 1, 3, -3, 1, 3, 4, 1, 0, 4, 1, 0, -3)
name = &"position"
category = ""
data_type = 6
stride = 3
[sub_resource type="Resource" id="Resource_uusmx"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_xevt1"]
script = ExtResource("5_hmj6t")
num_vertices = 8
num_edges = 12
num_faces = 6
num_face_vertices = 24
active_vertex = -1
active_edge = -1
active_face = 0
active_face_vertex = -1
edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
vertex_data = {
&"position": SubResource("Resource_ogy4l"),
&"selected": SubResource("Resource_uusmx")
}
edge_data = {
&"selected": SubResource("Resource_id3mo")
}
face_data = {
&"color": SubResource("Resource_mgr2t"),
&"material_index": SubResource("Resource_g83w3"),
&"selected": SubResource("Resource_joeuf"),
&"uv_transform": SubResource("Resource_lhprj"),
&"visible": SubResource("Resource_g4gcm")
}
face_vertex_data = {
&"color": SubResource("Resource_l7bgk"),
&"face_index": SubResource("Resource_itd0i"),
&"normal": SubResource("Resource_1dvih"),
&"vertex_index": SubResource("Resource_e2nai")
}
[sub_resource type="Resource" id="Resource_ye808"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_8okcc"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
name = &"color"
category = ""
data_type = 4
stride = 4
[sub_resource type="Resource" id="Resource_w4wji"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 0, 0, 0, 0, 0)
name = &"material_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_ed6ow"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_eoyv0"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
name = &"uv_transform"
category = ""
data_type = 8
stride = 6
[sub_resource type="Resource" id="Resource_5vm3d"]
script = ExtResource("2_kler0")
data = PackedByteArray(1, 1, 1, 1, 1, 1)
name = &"visible"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_x1so1"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
name = &"color"
category = ""
data_type = 4
stride = 4
[sub_resource type="Resource" id="Resource_jj2wv"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
name = &"face_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_kdjrl"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
name = &"normal"
category = ""
data_type = 6
stride = 3
[sub_resource type="Resource" id="Resource_nxryr"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
name = &"vertex_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_bsjmx"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(0, 1, 0, 0, 1, 7, 0, 0, 7, 0, 0, 0, 8, 1, 0, 8, 1, 7, 8, 0, 7, 8, 0, 0)
name = &"position"
category = ""
data_type = 6
stride = 3
[sub_resource type="Resource" id="Resource_0dw8u"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_5p6ad"]
script = ExtResource("5_hmj6t")
num_vertices = 8
num_edges = 12
num_faces = 6
num_face_vertices = 24
active_vertex = -1
active_edge = -1
active_face = -1
active_face_vertex = -1
edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
vertex_data = {
&"position": SubResource("Resource_bsjmx"),
&"selected": SubResource("Resource_0dw8u")
}
edge_data = {
&"selected": SubResource("Resource_ye808")
}
face_data = {
&"color": SubResource("Resource_8okcc"),
&"material_index": SubResource("Resource_w4wji"),
&"selected": SubResource("Resource_ed6ow"),
&"uv_transform": SubResource("Resource_eoyv0"),
&"visible": SubResource("Resource_5vm3d")
}
face_vertex_data = {
&"color": SubResource("Resource_x1so1"),
&"face_index": SubResource("Resource_jj2wv"),
&"normal": SubResource("Resource_kdjrl"),
&"vertex_index": SubResource("Resource_nxryr")
}
[sub_resource type="Resource" id="Resource_37r6a"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_064cf"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
name = &"color"
category = ""
data_type = 4
stride = 4
[sub_resource type="Resource" id="Resource_4r06m"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 0, 0, 0, 0, 0)
name = &"material_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_lxss8"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_f0u18"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
name = &"uv_transform"
category = ""
data_type = 8
stride = 6
[sub_resource type="Resource" id="Resource_8r0yo"]
script = ExtResource("2_kler0")
data = PackedByteArray(1, 1, 1, 1, 1, 1)
name = &"visible"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_2b1tp"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
name = &"color"
category = ""
data_type = 4
stride = 4
[sub_resource type="Resource" id="Resource_43spg"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
name = &"face_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_yc3ep"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
name = &"normal"
category = ""
data_type = 6
stride = 3
[sub_resource type="Resource" id="Resource_7onea"]
script = ExtResource("3_k6bah")
data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
name = &"vertex_index"
category = ""
data_type = 1
stride = 1
[sub_resource type="Resource" id="Resource_631xa"]
script = ExtResource("4_01bfr")
data = PackedFloat32Array(-3, 3, 0, -3, 3, 1, -3, 0, 1, -3, 0, 0, 6, 3, 0, 6, 3, 1, 6, 0, 1, 6, 0, 0)
name = &"position"
category = ""
data_type = 6
stride = 3
[sub_resource type="Resource" id="Resource_fqk4s"]
script = ExtResource("2_kler0")
data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
name = &"selected"
category = ""
data_type = 0
stride = 1
[sub_resource type="Resource" id="Resource_xk7ib"]
script = ExtResource("5_hmj6t")
num_vertices = 8
num_edges = 12
num_faces = 6
num_face_vertices = 24
active_vertex = -1
active_edge = -1
active_face = -1
active_face_vertex = -1
edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
vertex_data = {
&"position": SubResource("Resource_631xa"),
&"selected": SubResource("Resource_fqk4s")
}
edge_data = {
&"selected": SubResource("Resource_37r6a")
}
face_data = {
&"color": SubResource("Resource_064cf"),
&"material_index": SubResource("Resource_4r06m"),
&"selected": SubResource("Resource_lxss8"),
&"uv_transform": SubResource("Resource_f0u18"),
&"visible": SubResource("Resource_8r0yo")
}
face_vertex_data = {
&"color": SubResource("Resource_2b1tp"),
&"face_index": SubResource("Resource_43spg"),
&"normal": SubResource("Resource_yc3ep"),
&"vertex_index": SubResource("Resource_7onea")
}
[node name="IsoMapTest2" type="Node3D"]
[node name="GameController" type="Node" parent="." node_paths=PackedStringArray("_cameraTarget", "PlayerParentNode")]
@ -431,17 +31,9 @@ PlayerParentNode = NodePath("..")
[node name="InventoryManager" type="Node" parent="GameController"]
script = ExtResource("3_itd0i")
[node name="block0" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 0)
script = ExtResource("1_18fbr")
mesh_vector_data = SubResource("Resource_xevt1")
materials = Array[Material]([ExtResource("6_k6bah")])
[node name="Block_0" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, -3)
script = ExtResource("1_18fbr")
mesh_vector_data = SubResource("Resource_5p6ad")
materials = Array[Material]([ExtResource("7_01bfr")])
[node name="AlarmManager" type="Node" parent="GameController"]
script = ExtResource("6_w4wji")
AlarmSound = ExtResource("7_5vm3d")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.442606, -0.744379, 0.5, 0.287606, 0.645974, 0.707107, -0.849343, -0.169166, 0.5, 30.2584, 5.82742, 20.7297)
@ -456,24 +48,12 @@ light_energy = 0.75
light_bake_mode = 1
shadow_enabled = true
[node name="block1" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -4)
script = ExtResource("1_18fbr")
mesh_vector_data = SubResource("Resource_xk7ib")
materials = Array[Material]([ExtResource("6_k6bah")])
[node name="Box" parent="." instance=ExtResource("9_hmj6t")]
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, -0.762422, 1.5, -1.73161)
[node name="OmniLight3D" type="OmniLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.24217, 2.81518, 1.21091)
light_energy = 1.796
omni_range = 6.367
omni_attenuation = 0.86
[node name="Mainframe3d" parent="." instance=ExtResource("10_lkbo1")]
transform = Transform3D(0.25, 0, 0, 0, 0.25, 0, 0, 0, 0.25, -1.33755, 1.30461, 0.470264)
[node name="Barrel" type="Sprite3D" parent="."]
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 1.8865, 1.30887, -1.63081)
pixel_size = 0.05
@ -485,7 +65,7 @@ hframes = 4
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.84862, 0, -4.8932)
[node name="StartPosition" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.2092, 1.57535, 4.23742)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 20.9212, 1.57535, 4.20806)
[node name="CameraTarget" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.0389, 2.33215, 3.16925)

View file

@ -0,0 +1,27 @@
using Godot;
namespace Cirno.Scripts.Actors._3D;
public partial class AlarmSoundPlayer3D : AudioStreamPlayer3D
{
public override void _Ready()
{
if (AlarmManager.Instance is not null)
{
this.Stream = AlarmManager.Instance.AlarmSound;
AlarmManager.Instance.AlarmEnabled3D += InstanceOnAlarmEnabled3D;
AlarmManager.Instance.AlarmDisabled += InstanceOnAlarmDisabled;
}
}
private void InstanceOnAlarmDisabled()
{
this.Stop();
}
private void InstanceOnAlarmEnabled3D(Vector3 location)
{
this.Play();
}
}

View file

@ -0,0 +1 @@
uid://0g2sdu48c2x8

View file

@ -0,0 +1,24 @@
using Godot;
namespace Cirno.Scripts.Actors._3D;
public partial class SecurityCamera3D : Destructible3D
{
[Export] public StringName SweepAnimation { get; private set; } = "SweepLoop";
[Signal]
public delegate void AnimationStartEventHandler(string animationName);
public override void _Ready()
{
if (Engine.IsEditorHint()) return;
EmitSignalAnimationStart(SweepAnimation);
}
public void OnBodySighted(Node3D body)
{
if (Engine.IsEditorHint()) return;
GD.Print($"{body.Name} Sighted!");
AlarmManager.Instance?.SoundAlarm(this.GlobalPosition);
}
}

View file

@ -0,0 +1 @@
uid://dg3aho2ulfngs

View file

@ -74,42 +74,6 @@ public partial class Destructible3D : StaticBody3D, IDestructible
explosion.Initialize(ExplosionData.MakeBullet(new Vector2(this.GlobalPosition.X, this.GlobalPosition.Y)));
}
// private void ApplyExplosionDamage()
// {
// var spaceState = GetWorld2D().DirectSpaceState;
// var query = new PhysicsShapeQueryParameters2D();
// var shape = new CircleShape2D { Radius = ExplosionRadius };
//
// query.SetShape(shape);
// query.Transform = new Transform2D(0, GlobalPosition);
// query.CollideWithBodies = false;
// query.CollideWithAreas = true;
// //query.CollisionMask = ;
//
// var results = spaceState.IntersectShape(query);
//
// GD.Print($"Shapes in range: {results.Count}");
//
// foreach (var result in results)
// {
// if (result.TryGetValue("collider", out var colliderObj))
// {
//
// var collider = colliderObj.As<Node2D>();
//
//
// if (collider.GetParent<Area2D>() is IDestructible destructible)
// {
// GD.Print($"HITTING {collider.Name}");
// destructible.Hit(ExplosionDamage);
// }
// else {
// GD.Print($"Collider {collider.Name} was not idestructible");
// }
// }
// }
// }
private void CreateDebris()
{
if (DebrisScene == null) return;

View file

@ -23,20 +23,25 @@ public partial class AlarmManager : Node
[Signal]
public delegate void AlarmDisabledEventHandler();
private AudioStreamPlayer2D _player;
//private AudioStreamPlayer _player;
public override void _Ready()
{
Instance = this;
if (AlarmSound is not null)
{
var player = new AudioStreamPlayer2D();
player.Stream = AlarmSound;
this.CallDeferred("add_child", player);
_player = player;
}
// if (AlarmSound is not null)
// {
// var player = GetNodeOrNull<AudioStreamPlayer>("AlarmSoundPlayer");
// if (player is not null)
// {
// player.Stream = AlarmSound;
// _player = player;
// }
//
// //this.CallDeferred("add_child", player);
//
//
// }
}
public void SoundAlarm(Vector2 location)
@ -47,7 +52,7 @@ public partial class AlarmManager : Node
EmitSignalAlarmEnabled(location);
GD.Print($"Alarm sounded at {location}");
_player?.Play();
//_player?.Play();
}
public void SoundAlarm(Vector3 location)
@ -58,7 +63,7 @@ public partial class AlarmManager : Node
EmitSignalAlarmEnabled3D(location);
GD.Print($"Alarm sounded at {location}");
_player?.Play();
//_player?.Play();
}
public void SoundSilentAlarm(Vector2 location)
@ -71,6 +76,6 @@ public partial class AlarmManager : Node
{
IsAlarmOn = false;
EmitSignal(nameof(AlarmDisabled));
_player?.Stop();
//_player?.Stop();
}
}

View file

@ -5,35 +5,53 @@ namespace Cirno.Scripts.Interactables;
[Tool]
public partial class AlarmBox3D : Interactable3D
{
private AudioStreamPlayer _activationSound;
private readonly string _activationSoundName = "ActivationSound";
private AnimationPlayer _animationPlayer;
public StringName FlashAnimationName { get; private set; } = "Flash";
[Signal] public delegate void OnActivatedEventHandler(ActivationType activationType);
[Signal]
public delegate void PlayActivationSoundEventHandler();
public override void _Ready()
{
if (Engine.IsEditorHint()) return;
_activationSound = GetNodeOrNull<AudioStreamPlayer>(_activationSoundName);
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
//CallDeferred(MethodName.InitDeferred);
InitDeferred();
}
private void InitDeferred()
{
if (AlarmManager.Instance is not null)
{
AlarmManager.Instance.AlarmEnabled3D += OnAlarmEnabled3D;
AlarmManager.Instance.AlarmDisabled += InstanceOnAlarmDisabled;
}
}
private void InstanceOnAlarmDisabled()
{
_animationPlayer.Stop();
}
private void OnAlarmEnabled3D(Vector3 location)
{
// TODO: set animation, make blinky
GD.Print($"Enabled alarm in box {this.Name}");
_animationPlayer.Play(FlashAnimationName);
}
public override bool Activate(ActivationType activationType = ActivationType.Toggle)
{
EmitSignal(SignalName.OnActivated, (int)activationType);
EmitSignalPlayActivationSound();
if (AlarmManager.Instance is not null)
{
_activationSound.Play();
//_activationSound?.Play();
AlarmManager.Instance.DisableAlarm();
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2024 Matt O'Tousa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,126 @@
<div align="center">
<br/>
<br/>
<img src="addons/tattomoosa.vision_cone_3d/icons/VisionCone3D.svg" width="100"/>
<br/>
<h1>
VisionCone3D
<br/>
<sub>
<sub>
<sub>
Simple but configurable 3D vision cone node for <a href="https://godotengine.org/">Godot</a>
</sub>
</sub>
</sub>
<br/>
<br/>
<br/>
</h1>
<br/>
<br/>
<img src="./readme_images/demo.png" height="140">
<img src="./readme_images/stress_test.png" height="140">
<img src="./readme_images/editor_view.png" height="140">
<br/>
<br/>
</div>
> Compatible with Godot 4.4 - see 4.3 branch for Godot 4.3 compatible version
Adds VisionCone3D, which tracks whether or not objects within its cone shape can be "seen".
This can be used to let objects in your game "see" multiple objects efficiently.
Default configuration should work for most use-cases out of the box.
## Features
* Edit range/angle of cone via 3D viewport editor gizmo
* Debug visualization to easily diagnose any issues
* Works with complex objects that have many collision shapes
* Configurable vision probe settings allow tuning effectiveness and performance to your use-case
* Ignore some physics bodies (eg the parent body)
* Separate masks for bodies that can be seen and bodies that can only occlude other objects
* Includes general-purpose ConeShape3D
## Installation
Install via the AssetLib tab within Godot by searching for VisionCone3D
## Usage
Add the VisionCone3D node to your scene. Turn on debug draw to see it working. Then you can...
### Connect to the body visible signals
These signals fire when a body is newly visible or newly hidden.
```python
func _ready():
vision_cone.body_sighted.connect(_on_body_sighted)
vision_cone.body_hidden.connect(_on_body_hidden)
func _on_body_sighted(body: Node3D):
print("body sighted: ", body.name)
func _on_body_hidden(body: Node3D):
print("body hidden: ", body.name)
```
### Poll the currently visible bodies
```python
func _process(): # doesn't need to be during a physics frame
print("bodies visible: ", vision_cone.get_visible_bodies())
```
## Performance Tuning
### Vision Test Mode
#### Center
Samples only the center point (position) of the CollisionShape. Most efficient, but least effective
as if the center of a shape is obscured it won't be seen.
```python
vision_cone.vision_test_mode = VisionCone3D.VisionTestMode.SAMPLE_CENTER
```
#### Sample Random Vertices
Uses CollisionShape's `get_debug_mesh` to get a mesh representation of the CollisionShape,
then samples random vertex points from that mesh.
Effectiveness determined by the max body count and max probe per shape count
```python
vision_cone.vision_test_mode = VisionCone3D.VisionTestMode.SAMPLE_RANDOM_VERTICES
vision_cone.vision_test_max_body_count = 50 # Bodies probed, per-frame
vision_cone.vision_test_shape_max_probe_count = 5 # Probes per hidden shape
```
### Collision Masks
VisionCone3D has 2 collision masks, one used for bodies that can be seen by the cone and one for an environment,
which can occlude seen bodies but is not itself probed for visibility.
For example, add the level collision layer to `collision_environment_mask` and the player/enemy/object collision layer to the `collision_mask`.
The player/enemy/object can then hide behind the level, but no processing/probing will occur on the level collision geometry itself.
## The Future
This asset is still in development. I have some ideas for further performance tuning options, and I'm open to feedback on the usability and how to improve documentation or workflows.
### 2D Support?
I am open to adding a 2D version of this addon if there is sufficient interest.
See if [VisionCone2D](https://github.com/d-bucur/godot-vision-cone) meets your needs in the meantime. No relation.
## Upgrading
### 0.1.0 -> 0.2.0
v0.2.0 has significant performance improvements. Probably should have waited a few days before publishing. It probably doesn't have any users yet, but just in case...
* Use "Change Type..." on your VisionCone3Ds and select Area3D.
* Use new ConeShape3D for all your cone-y collision needs

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,71 @@
extends PanelContainer
@export var vision_cone : VisionCone3D
@export var cameras : Array[Camera3D] = []
@onready var vision_test_center_checkbox : CheckBox = %VisionTestCenterCheckBox
@onready var vision_test_scatter_checkbox : CheckBox = %VisionTestScatterCheckBox
@onready var raycast_count_slider : Slider = %RaycastsPerFrameSlider
@onready var angle_slider : Slider = %AngleSlider
@onready var range_slider : Slider = %RangeSlider
@onready var rotation_slider : Slider = %ObserverRotationSlider
@onready var position_slider : Slider = %ObserverPositionSlider
@onready var max_bodies_slider : Slider = %MaxBodiesSlider
@onready var switch_camera_button : Button = %SwitchCameraButton
func _ready():
vision_test_center_checkbox.toggled.connect(_set_center)
vision_test_scatter_checkbox.toggled.connect(_set_scatter)
raycast_count_slider.value_changed.connect(func(value: float): vision_cone.vision_test_shape_max_probe_count = value)
angle_slider.value_changed.connect(func(value: float): vision_cone.angle = value)
range_slider.value_changed.connect(func(value: float): vision_cone.range = value)
rotation_slider.value_changed.connect(func(value: float): vision_cone.get_parent().rotation_degrees.y = -value)
position_slider.value_changed.connect(func(value: float): vision_cone.get_parent().position.x = value)
max_bodies_slider.value_changed.connect(func(value: float): vision_cone.vision_test_max_body_count = value)
if cameras.is_empty():
switch_camera_button.hide()
else:
cameras[0].current = true
switch_camera_button.text = "Current Camera: " + cameras[0].name
switch_camera_button.pressed.connect(
func():
for i in cameras.size():
var cam := cameras[i]
if cam.current:
cam.current = false
var next_cam : Camera3D
if (i + 1) < cameras.size():
next_cam = cameras[i + 1]
else:
next_cam = cameras[0]
next_cam.current = true
switch_camera_button.text = "Current Camera: " + next_cam.name
return
)
angle_slider.value = vision_cone.angle
range_slider.value = vision_cone.range
max_bodies_slider.value = vision_cone.vision_test_max_body_count
raycast_count_slider.value = vision_cone.vision_test_shape_max_probe_count
if vision_cone.get_parent() is CharacterBody3D:
vision_cone.get_parent().rotation_degrees.y = -rotation_slider.value
vision_cone.get_parent().position.x = position_slider.value
else:
rotation_slider.get_parent().hide()
position_slider.get_parent().hide()
_set_center(vision_test_center_checkbox.button_pressed)
_set_scatter(vision_test_scatter_checkbox.button_pressed)
size.y = 0
func _set_center(value: bool):
if !value:
return
vision_cone.vision_test_mode = VisionCone3D.VisionTestMode.SAMPLE_CENTER
func _set_scatter(value: bool):
if !value:
return
vision_cone.vision_test_mode = VisionCone3D.VisionTestMode.SAMPLE_RANDOM_VERTICES

View file

@ -0,0 +1 @@
uid://dof1xi7gcbq7s

View file

@ -0,0 +1,16 @@
[gd_scene load_steps=3 format=3 uid="uid://mqvpirrmppob"]
[sub_resource type="BoxShape3D" id="BoxShape3D_da376"]
size = Vector3(1, 3, 1)
[sub_resource type="BoxMesh" id="BoxMesh_mb6rt"]
size = Vector3(1, 3, 1)
[node name="Blocker" type="StaticBody3D"]
collision_layer = 2
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_da376")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_mb6rt")

View file

@ -0,0 +1,39 @@
[gd_scene load_steps=5 format=3 uid="uid://batptfh4cwpfb"]
[ext_resource type="PackedScene" uid="uid://cmgl8607thxgg" path="res://addons/tattomoosa.vision_cone_3d/examples/example_src/observable.tscn" id="1_p0bro"]
[sub_resource type="Animation" id="Animation_c3q4w"]
length = 0.001
tracks/0/type = "position_3d"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = PackedFloat32Array(0, 1, -0.195181, 0, -1.77211)
[sub_resource type="Animation" id="Animation_jctf1"]
resource_name = "back_and_forth"
length = 8.0
loop_mode = 1
tracks/0/type = "position_3d"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = PackedFloat32Array(0, 1, 0, 0, 0, 2.03333, 1, 4, 0, 0, 6.03333, 1, -4, 0, 0, 8, 1, 0, 0, 0)
[sub_resource type="AnimationLibrary" id="AnimationLibrary_al81f"]
_data = {
&"RESET": SubResource("Animation_c3q4w"),
&"back_and_forth": SubResource("Animation_jctf1")
}
[node name="MovingObservable" instance=ExtResource("1_p0bro")]
transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, -0.195181, 0, -1.77211)
[node name="AnimationPlayer" type="AnimationPlayer" parent="." index="2"]
libraries = {
"": SubResource("AnimationLibrary_al81f")
}

View file

@ -0,0 +1,31 @@
[gd_scene load_steps=6 format=3 uid="uid://cmgl8607thxgg"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dnr2f"]
albedo_color = Color(0, 0, 0, 1)
[sub_resource type="CapsuleMesh" id="CapsuleMesh_nj3xo"]
material = SubResource("StandardMaterial3D_dnr2f")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ecabg"]
albedo_color = Color(1, 0.635294, 1, 1)
[sub_resource type="CapsuleMesh" id="CapsuleMesh_5i4ah"]
material = SubResource("StandardMaterial3D_ecabg")
radius = 0.25
height = 0.75
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cf0b5"]
[node name="Observable" type="CharacterBody3D"]
transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0, 0, 0)
metadata/_edit_group_ = true
[node name="Body" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_nj3xo")
[node name="Face" type="MeshInstance3D" parent="Body"]
transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 0, 0.386695, -0.364832)
mesh = SubResource("CapsuleMesh_5i4ah")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_cf0b5")

View file

@ -0,0 +1,34 @@
[gd_scene load_steps=6 format=3 uid="uid://brqivkckug8uc"]
[ext_resource type="Script" uid="uid://cmgcelj1qxg8o" path="res://addons/tattomoosa.vision_cone_3d/src/VisionCone3D.gd" id="1_t4wly"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_td2ym"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ecabg"]
albedo_color = Color(1, 0.635294, 1, 1)
[sub_resource type="CapsuleMesh" id="CapsuleMesh_5i4ah"]
material = SubResource("StandardMaterial3D_ecabg")
radius = 0.25
height = 0.75
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cf0b5"]
[node name="Observer" type="CharacterBody3D"]
metadata/_edit_group_ = true
[node name="VisionCone3D" type="Area3D" parent="." node_paths=PackedStringArray("vision_test_ignore_bodies")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.41062, -0.023833)
script = ExtResource("1_t4wly")
debug_draw = true
vision_test_ignore_bodies = [NodePath("..")]
[node name="Body" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_td2ym")
[node name="Face" type="MeshInstance3D" parent="Body"]
transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 0, 0.386695, -0.364832)
mesh = SubResource("CapsuleMesh_5i4ah")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_cf0b5")

View file

@ -0,0 +1,10 @@
extends Label
@export var range_control : Range
func _ready():
range_control.value_changed.connect(_set_displayed_value)
_set_displayed_value(range_control.value)
func _set_displayed_value(_value: float):
text = str(_value)

View file

@ -0,0 +1 @@
uid://be67a8jv24c5o

View file

@ -0,0 +1,186 @@
[gd_scene load_steps=4 format=3 uid="uid://cdbsstpvtrvhd"]
[ext_resource type="Script" uid="uid://dof1xi7gcbq7s" path="res://addons/tattomoosa.vision_cone_3d/examples/example_src/VisionConeDemoControls.gd" id="1_vf8s6"]
[ext_resource type="Script" uid="uid://be67a8jv24c5o" path="res://addons/tattomoosa.vision_cone_3d/examples/example_src/ui/ValueLabel.gd" id="2_8lgvy"]
[sub_resource type="ButtonGroup" id="ButtonGroup_dqtte"]
[node name="VisionConeControls" type="PanelContainer"]
offset_left = 13.0
offset_top = 14.0
offset_right = 308.0
offset_bottom = 24.0
script = ExtResource("1_vf8s6")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Range" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/Range"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/Range/HBoxContainer2"]
layout_mode = 2
text = "Range:"
[node name="RangeValueLabel" type="Label" parent="VBoxContainer/Range/HBoxContainer2" node_paths=PackedStringArray("range_control")]
layout_mode = 2
script = ExtResource("2_8lgvy")
range_control = NodePath("../../RangeSlider")
[node name="RangeSlider" type="HSlider" parent="VBoxContainer/Range"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 40.0
step = 0.5
value = 20.0
[node name="Angle" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/Angle"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/Angle/HBoxContainer2"]
layout_mode = 2
text = "Angle:"
[node name="AngleValueLabel" type="Label" parent="VBoxContainer/Angle/HBoxContainer2" node_paths=PackedStringArray("range_control")]
layout_mode = 2
script = ExtResource("2_8lgvy")
range_control = NodePath("../../AngleSlider")
[node name="AngleSlider" type="HSlider" parent="VBoxContainer/Angle"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 0.1
max_value = 90.0
step = 0.1
value = 45.0
[node name="VisionTestModeControls" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label2" type="Label" parent="VBoxContainer/VisionTestModeControls"]
layout_mode = 2
text = "Vision Test Mode"
[node name="VisionTestMode" type="HBoxContainer" parent="VBoxContainer/VisionTestModeControls"]
layout_mode = 2
[node name="VisionTestCenterCheckBox" type="CheckBox" parent="VBoxContainer/VisionTestModeControls/VisionTestMode"]
unique_name_in_owner = true
layout_mode = 2
button_group = SubResource("ButtonGroup_dqtte")
text = "Center"
[node name="VisionTestScatterCheckBox" type="CheckBox" parent="VBoxContainer/VisionTestModeControls/VisionTestMode"]
unique_name_in_owner = true
layout_mode = 2
button_pressed = true
button_group = SubResource("ButtonGroup_dqtte")
text = "Scatter"
[node name="ProbesPerFrame" type="VBoxContainer" parent="VBoxContainer/VisionTestModeControls"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/VisionTestModeControls/ProbesPerFrame"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/VisionTestModeControls/ProbesPerFrame/HBoxContainer2"]
layout_mode = 2
text = "Max Probes Per Shape:"
[node name="RaycastPerFrameValueLabel" type="Label" parent="VBoxContainer/VisionTestModeControls/ProbesPerFrame/HBoxContainer2" node_paths=PackedStringArray("range_control")]
layout_mode = 2
script = ExtResource("2_8lgvy")
range_control = NodePath("../../RaycastsPerFrameSlider")
[node name="RaycastsPerFrameSlider" type="HSlider" parent="VBoxContainer/VisionTestModeControls/ProbesPerFrame"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
value = 10.0
[node name="MaxBodies" type="VBoxContainer" parent="VBoxContainer/VisionTestModeControls"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/VisionTestModeControls/MaxBodies"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/VisionTestModeControls/MaxBodies/HBoxContainer2"]
layout_mode = 2
text = "Max Bodies Per Frame:"
[node name="MaxBodiesFrameValueLabel" type="Label" parent="VBoxContainer/VisionTestModeControls/MaxBodies/HBoxContainer2" node_paths=PackedStringArray("range_control")]
layout_mode = 2
script = ExtResource("2_8lgvy")
range_control = NodePath("../../MaxBodiesSlider")
[node name="MaxBodiesSlider" type="HSlider" parent="VBoxContainer/VisionTestModeControls/MaxBodies"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 500.0
value = 11.0
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="SwitchCameraButton" type="Button" parent="VBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Switch Camera"
[node name="ObserverRotation" type="VBoxContainer" parent="VBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/ObserverRotation"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/VBoxContainer/ObserverRotation/HBoxContainer2"]
layout_mode = 2
text = "Observer Rotation:"
[node name="ObserverRotationValueLabel" type="Label" parent="VBoxContainer/VBoxContainer/ObserverRotation/HBoxContainer2" node_paths=PackedStringArray("range_control")]
layout_mode = 2
script = ExtResource("2_8lgvy")
range_control = NodePath("../../ObserverRotationSlider")
[node name="ObserverRotationSlider" type="HSlider" parent="VBoxContainer/VBoxContainer/ObserverRotation"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = -90.0
max_value = 90.0
[node name="ObserverPosition" type="VBoxContainer" parent="VBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/ObserverPosition"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/VBoxContainer/ObserverPosition/HBoxContainer2"]
layout_mode = 2
text = "Observer Position:"
[node name="ObserverPositionValueLabel" type="Label" parent="VBoxContainer/VBoxContainer/ObserverPosition/HBoxContainer2" node_paths=PackedStringArray("range_control")]
layout_mode = 2
script = ExtResource("2_8lgvy")
range_control = NodePath("../../ObserverPositionSlider")
[node name="ObserverPositionSlider" type="HSlider" parent="VBoxContainer/VBoxContainer/ObserverPosition"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = -8.0
max_value = 8.0
step = 0.1

View file

@ -0,0 +1,26 @@
[gd_scene load_steps=4 format=3 uid="uid://doqrupj5l86sr"]
[ext_resource type="Script" uid="uid://cmgcelj1qxg8o" path="res://addons/tattomoosa.vision_cone_3d/src/VisionCone3D.gd" id="1_78jke"]
[sub_resource type="BoxMesh" id="BoxMesh_si8ep"]
[sub_resource type="BoxShape3D" id="BoxShape3D_bebrf"]
[node name="Node3D" type="Node3D"]
[node name="VisionCone3D" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.00271)
script = ExtResource("1_78jke")
debug_draw = true
[node name="StaticBody3D" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -2.66644)
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D"]
mesh = SubResource("BoxMesh_si8ep")
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
shape = SubResource("BoxShape3D_bebrf")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 8.38814, 0)

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;fill-opacity:0.294;}
.st2{display:inline;fill:#FFFFFF;}
.st3{fill:#FFFFFF;}
.st4{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st5{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st6{fill:#231F20;}
.st7{fill:none;stroke:#000000;stroke-width:4;stroke-miterlimit:10;}
</style>
<g id="Layer_1_00000012455106065017520430000009640333213232038794_" class="st0">
<path class="st1" d="M52,4c-6.6,0-12,5.4-12,12v26.6C27.7,49.2,20,62,20,76v4h28.6c2.3,8.5,11.1,13.5,19.7,11.2
c5.4-1.5,9.7-5.7,11.2-11.2H108v-4c0-14-7.7-26.8-20-33.4V16c0-6.6-5.4-12-12-12H52z M40.3,82c-1.6-0.1-3.2,0.3-4.6,1.1l-10.4,6
c-3.8,2.2-5.1,7.1-2.9,10.9s7.1,5.1,10.9,2.9l10.4-6c3.8-2.2,5.1-7.1,2.9-10.9C45.4,83.7,43,82.2,40.3,82z M87.7,82
c-4.4,0.3-7.7,4.1-7.5,8.5c0.2,2.7,1.7,5.1,4,6.4l10.4,6c3.8,2.2,8.7,0.9,10.9-2.9s0.9-8.7-2.9-10.9l-10.4-6
C90.9,82.3,89.3,81.9,87.7,82L87.7,82z M64,96c-4.4,0-8,3.6-8,8v12c0,4.4,3.6,8,8,8s8-3.6,8-8v-12C72,99.6,68.4,96,64,96z"/>
<path class="st2" d="M52,8c-4.4,0-8,3.6-8,8v28.9C31.6,51.3,24,63.2,24,76h28c0,6.6,5.4,12,12,12s12-5.4,12-12h28
c0-12.8-7.6-24.7-20-31.1V16c0-4.4-3.6-8-8-8H52z M40,86c-0.8-0.1-1.6,0.1-2.3,0.5l-10.4,6c-1.9,1.1-2.6,3.6-1.5,5.5
s3.6,2.6,5.5,1.5l10.4-6c1.9-1.1,2.6-3.6,1.5-5.5C42.5,86.8,41.4,86.1,40,86L40,86z M88,86c-2.2,0.1-3.9,2-3.7,4.3
c0.1,1.3,0.8,2.5,2,3.2l10.4,6c1.9,1.1,4.4,0.4,5.5-1.5s0.4-4.4-1.5-5.5l-10.4-6C89.6,86.1,88.8,86,88,86L88,86z M64,100
c-2.2,0-4,1.8-4,4v12c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4v-12C68,101.8,66.2,100,64,100z"/>
</g>
<path class="st3" d="M6.2,66.2l85.1,57.5L88,112.5c-8.8-30.2-8.8-62.4,0-92.6l3.3-11.2L6.2,66.2z"/>
<path class="st4" d="M6.2,66.2l85.1,57.5L88,112.5c-8.8-30.2-8.8-62.4,0-92.6l3.3-11.2L6.2,66.2z"/>
<ellipse class="st3" cx="107" cy="66.2" rx="15.7" ry="57.5"/>
<ellipse class="st5" cx="107" cy="66.2" rx="15.7" ry="57.5"/>
<ellipse class="st6" cx="98.3" cy="66.2" rx="3.6" ry="19.3"/>
<ellipse class="st7" cx="99.3" cy="66.2" rx="3.6" ry="19.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://o1pkx5p4gmem"
path.s3tc="res://.godot/imported/GizmoVisionCone.svg-ea8f76460bf35030690548bcee239273.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/tattomoosa.vision_cone_3d/icons/GizmoVisionCone.svg"
dest_files=["res://.godot/imported/GizmoVisionCone.svg-ea8f76460bf35030690548bcee239273.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
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/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=0
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#FC7F7F;stroke-width:2;stroke-linecap:round;}
.st1{fill:none;stroke:#FC7F7F;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
</style>
<polyline class="st0" points="10.6,1.7 2.7,8 10.6,14.3 "/>
<ellipse class="st1" cx="11.6" cy="8" rx="1.4" ry="3.7"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://pgp4cqq8ov3o"
path="res://.godot/imported/VisionCone3D.svg-2598d1ee3dd911c502082c26b2e835ec.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/tattomoosa.vision_cone_3d/icons/VisionCone3D.svg"
dest_files=["res://.godot/imported/VisionCone3D.svg-2598d1ee3dd911c502082c26b2e835ec.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=true
editor/convert_colors_with_editor_theme=true

View file

@ -0,0 +1,7 @@
[plugin]
name="VisionCone3D"
description=""
author="Tattomoosa"
version="0.2.0"
script="plugin.gd"

View file

@ -0,0 +1,21 @@
@tool
extends EditorPlugin
const DEBUG_DRAW_TOOL := "Set Vision Cone Debug Draw Visibility"
const VisionCone3DGizmoPlugin := preload ("./src/editor/VisionCone3DGizmoPlugin.gd")
var gizmo : VisionCone3DGizmoPlugin = VisionCone3DGizmoPlugin.new()
func _enter_tree() -> void:
gizmo.undo_redo = get_undo_redo()
add_node_3d_gizmo_plugin(gizmo)
# add_tool_menu_item(
# DEBUG_DRAW_TOOL,
# func():
# VisionCone3D.debug_draw_all = !VisionCone3D.debug_draw_all
# )
func _exit_tree() -> void:
remove_node_3d_gizmo_plugin(gizmo)
# remove_tool_menu_item(DEBUG_DRAW_TOOL)

View file

@ -0,0 +1 @@
uid://yxw0yuks1mme

View file

@ -0,0 +1,50 @@
@tool
class_name ConeShape3D
extends ConvexPolygonShape3D
## The height of the cone
@export var height : float = 2.0:
set(value):
height = value
_request_resize()
## The radius of the bottom of the cone
@export var radius : float = 0.5:
set(value):
radius = value
_request_resize()
## The number of radial segments of the cone
@export var resolution : int = 8:
set(value):
resolution = value
_request_resize()
# Resize requested
var pending_resize := false
# Update size to initial state
func _init() -> void:
_request_resize()
# Will only resize once per frame, during idle time
func _request_resize() -> void:
if !pending_resize:
_update_size.call_deferred()
pending_resize = true
# Updates shape size
func _update_size() -> void:
points = _make_cone_polygon_points()
pending_resize = false
# Makes a cone polygon
@warning_ignore("return_value_discarded")
func _make_cone_polygon_points() -> PackedVector3Array:
var pts : PackedVector3Array = []
var top : Vector3 = Vector3(0, height / 2, 0)
for i in resolution:
var angle := float(i) * TAU / resolution
var x := cos(angle) * radius
var y := sin(angle) * radius
pts.push_back(Vector3(x, -height/2, y))
pts.push_back(top)
return pts

View file

@ -0,0 +1 @@
uid://bc32kei4pauw1

View file

@ -0,0 +1,421 @@
@tool
@icon("../icons/VisionCone3D.svg")
class_name VisionCone3D
extends Area3D
## Provides a "Vision Cone", a cone-shaped area where objects are then
## probed for visibility via ray casts
## Emitted when a body is newly visible
signal body_sighted(body: Node3D)
## Emitted when a body is newly not visible
signal body_hidden(body: Node3D)
## Emitted when the cone shape changes
signal shape_changed
#region VisionCone3D
#region members
## Determines how visibility is probed for bodies within the cone area
##
enum VisionTestMode{
## Samples the center of each CollisionShape. Maximum performance, least reliability
SAMPLE_CENTER,
## Samples random vertices of each CollisionShape, up to `vision_test_shape_max_probe_count`
## for hidden objects
## If shape was visible at last frame, tests last successful probe position first
SAMPLE_RANDOM_VERTICES,
## Gets a list of points where shapes collide from the physics engine
# SAMPLE_COLLIDE_SHAPE,
}
const VisionConeDebugVisualizer3D := preload("./debug/VisionConeDebugVisualizer3D.gd")
## Distance that can be seen (the height of the vision cone)
@warning_ignore("shadowed_global_identifier")
@export var range := 20.0:
set(v): range = v; _update_shape()
## Angle of the vision cone
@export_range(0, 150) var angle := 45.0:
set(v): angle = v; _update_shape()
## Whether or not to draw debug information
@export var debug_draw := false:
set(v):
debug_draw = v
if debug_draw and !_debug_visualizer:
_debug_visualizer = VisionConeDebugVisualizer3D.new()
add_child(_debug_visualizer)
elif !debug_draw and _debug_visualizer:
_debug_visualizer.queue_free()
@export_group("Vision Test", "vision_test_")
## Which VisionTestMode to use to determine if a shape is visible
@export var vision_test_mode : VisionTestMode = VisionTestMode.SAMPLE_RANDOM_VERTICES
## List of bodies to ignore in vision probing
##
## Useful for eg the VisionCone3D's parent body
@export var vision_test_ignore_bodies : Array[PhysicsBody3D]
@export_subgroup("Per-frame probe settings")
## Maximum amount of shape probes (per shape, per frame)
@export var vision_test_shape_max_probe_count : int = 5
## Maximum number of bodies to check, per-frame
##
## All bodies will still be evaluated as it will cycle through them
## frame by frame, but `body_sighted` or `body_hidden` may lag behind
## by some frames.
@export var vision_test_max_body_count : int = 10
@export_group("Collision", "collision_")
## Collision layer "hoisted" up from Area3D for convenience
@export_flags_3d_physics var collision_layer_ : int = 1:
get:
return collision_layer
set(value):
collision_layer = value
## Collision mask "hoisted" up from Area3D for convenience
##
## This represents what can be "seen" and notified against. Generally useful for characters
@export_flags_3d_physics var collision_mask_ : int = 1:
get:
return collision_mask
set(value):
collision_mask = value
## Collision mask of what objects can obscure visible objects (but don't need to
## be tracked and probed to determine visibility)
##
## Generally useful for the environment but any node where you don't care
## if its seen or not can be on this layer.
## This layer only affects raycasts, which can collide with any layer
## in either `collision_mask` or `collision_environment_mask`
@export_flags_3d_physics var collision_environment_mask : int = 1
## Radius at the wide end of the vision cone
var end_radius: float:
get: return _get_end_radius()
# { Node3D "body" : Node3D "shape" }
var _body_probe_data : Dictionary = {
# Node3D "body" : [
# "prober": VisionTestProber
# ]
}
var _last_probed_index : int = -1
var _debug_visualizer : VisionConeDebugVisualizer3D
var _collision_shape := CollisionShape3D.new()
var _cone_shape := ConeShape3D.new()
#endregion members
## Returns a list of intersecting PhysicsBody3Ds. The overlapping body's
## CollisionObject3D.collision_layer must be part of this area's CollisionObject3D.collision_mask
## in order to be detected.
func get_visible_bodies() -> Array[PhysicsBody3D]:
var bodies : Array[PhysicsBody3D] = []
for prober: VisionTestProber in _body_probe_data.values():
bodies.push_back(prober.body)
return bodies
## Whether or not a given point in global space is within the cone's
## angle.
func point_within_angle(global_point: Vector3) -> bool:
var body_pos := -global_basis.z
var pos := global_point - global_position
var angle_to := pos.angle_to(body_pos)
var angle_deg := rad_to_deg(angle_to)
return angle_deg <= (angle / 2)
## Whether or not a given point in global space is within the cone
func point_within_cone(global_point: Vector3) -> bool:
var local_point := to_local(global_point)
var z_distance : float = abs(local_point.z)
if z_distance < 0 or z_distance > range:
return false
return point_within_angle(global_point)
func _init() -> void:
add_child(_collision_shape)
_collision_shape.shape = _cone_shape
_collision_shape.rotation_degrees.x = 90
_update_shape()
# only true when copied
if _debug_visualizer:
_debug_visualizer.vision_cone = self
var err := body_shape_entered.connect(_on_body_shape_entered)
if err != OK:
push_warning("VisionCone3D body_shape_entered: ", error_string(err))
err = body_shape_exited.connect(_on_body_shape_exited)
if err != OK:
push_warning("VisionCone3D body_shape_exited: ", error_string(err))
@warning_ignore("return_value_discarded")
func _physics_process(_delta: float) -> void:
if Engine.is_editor_hint():
return
if !monitoring:
return
var bodies_to_probe := _get_bodies_to_probe_this_frame()
for body: CollisionObject3D in bodies_to_probe:
if !is_instance_valid(body):
push_warning("erasing invalid body")
_body_probe_data.erase(body)
continue
_probe_body(body)
func _get_bodies_to_probe_this_frame() -> Array: # Array[CollisionObject3D]:
var all_bodies := _body_probe_data.keys()
if all_bodies.is_empty():
return []
if all_bodies.size() < vision_test_max_body_count:
_last_probed_index = -1
return all_bodies
var start_index := _last_probed_index + 1
var to_end := all_bodies.slice(start_index, start_index + vision_test_max_body_count)
var counted := to_end.size()
var end_index : int = min(vision_test_max_body_count - counted, start_index)
var from_start := all_bodies.slice(0, end_index)
_last_probed_index = from_start.size() - 1 if from_start.size() > 0 else start_index + counted - 1
return (to_end + from_start)
func _probe_body(body: CollisionObject3D) -> void:
var body_was_visible_last_frame := false
var body_is_visible := false
var body_probes : Array[VisionTestProber]
body_probes.assign(_body_probe_data[body])
for prober in body_probes:
if prober.visible:
body_was_visible_last_frame = true
prober.probe()
if prober.visible:
body_is_visible = true
var body_visibility_changed := body_is_visible != body_was_visible_last_frame
if body_visibility_changed:
if body_is_visible:
body_sighted.emit(body)
else:
body_hidden.emit(body)
func _update_shape() -> void:
_cone_shape.height = range
_cone_shape.radius = end_radius
_collision_shape.position.z = -range / 2
update_gizmos()
shape_changed.emit()
func _get_collision_shape_node_in_body(body: PhysicsBody3D, body_shape_index: int) -> Node3D:
if !body:
return null
var body_shape_owner : int = body.shape_find_owner(body_shape_index)
return body.shape_owner_get_owner(body_shape_owner)
func _get_end_radius() -> float:
var angle_rad := deg_to_rad(angle / 2)
return range * tan(angle_rad)
func _get_prober_for_shape(shape: CollisionShape3D, body: CollisionObject3D) -> VisionTestProber:
for prober: VisionTestProber in _body_probe_data[body]:
if prober.collision_shape == shape:
return prober
return null
@warning_ignore("return_value_discarded")
func _on_body_shape_entered(
_body_rid: RID,
body: Node3D,
body_shape_index: int,
_local_shape_index: int,
) -> void:
# # weird!
if !is_instance_valid(body):
if _body_probe_data.has(body):
_body_probe_data.erase(body)
return
var shape := _get_collision_shape_node_in_body(body, body_shape_index)
var body_probes : Array[VisionTestProber] = _body_probe_data.get_or_add(
body, [] as Array[VisionTestProber]
)
var has_prober := _get_prober_for_shape(shape, body)
if !has_prober:
body_probes.push_back(VisionTestProber.new(self, shape, body))
else:
push_warning("Already has prober")
@warning_ignore("return_value_discarded")
func _on_body_shape_exited(
_body_rid: RID,
body: Node3D,
body_shape_index: int,
_local_shape_index: int,
) -> void:
if !body:
return
var shape := _get_collision_shape_node_in_body(body, body_shape_index)
var prober := _get_prober_for_shape(shape, body)
var body_probers : Array[VisionTestProber]= _body_probe_data[body]
body_probers.erase(prober)
if body_probers.is_empty():
_body_probe_data.erase(body)
#endregion VisionCone3D
class VisionTestProber:
## Useful for debugging probes
const CONTINUE_PROBING_ON_SUCCESS := false
## Vision cone to probe for
var vision_cone : VisionCone3D
## Collision shape to probe
var collision_shape: CollisionShape3D
## Collision shape mesh representation
var shape_probe_mesh: ArrayMesh
## Collision shape's owning body
var body : PhysicsBody3D
## Whether the probe found the shape to be visible
var visible: bool = false
## All probe results, for debugging
var probe_results: Array[ProbeResult]
static var _rng := RandomNumberGenerator.new()
func _init(
p_vision_cone: VisionCone3D,
p_collision_shape: CollisionShape3D,
p_body: PhysicsBody3D
) -> void:
vision_cone = p_vision_cone
collision_shape = p_collision_shape
body = p_body
func _probe_position(to: Vector3, shape_local_target: Vector3) -> ProbeResult:
# Collide with bodies OR the environment
var raycast_collision_mask := vision_cone.collision_mask | vision_cone.collision_environment_mask
# can store reference to this?
var space_state := vision_cone.get_world_3d().direct_space_state
var from := vision_cone.global_position
var exclude_bodies := vision_cone.vision_test_ignore_bodies\
.filter(func(x: PhysicsBody3D) -> bool: return is_instance_valid(x))\
.map(func(x: PhysicsBody3D) -> RID: return x.get_rid())
var query := PhysicsRayQueryParameters3D.create(
from,
to,
raycast_collision_mask,
exclude_bodies
)
var result := space_state.intersect_ray(query)
return ProbeResult.new(
from,
to,
shape_local_target,
result.collider if result.has("collider") else null
)
func _get_last_visible_point_on_shape() -> Vector3:
if probe_results.is_empty():
push_warning("Shape was not determined to be visible during last _probe_position")
return Vector3.ZERO
return probe_results[-1].shape_local_target
func _random_points_on_probe_mesh(count: int) -> PackedVector3Array:
if !shape_probe_mesh:
shape_probe_mesh = collision_shape.shape.get_debug_mesh()
var vertices : PackedVector3Array = shape_probe_mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
var points : PackedVector3Array = []
for point in count:
points.push_back(vertices[_rng.randi_range(0, vertices.size() - 1)])
return points
func _get_scatter_points(count: int) -> PackedVector3Array:
var sample_points : PackedVector3Array = []
# var random_point_count := vision_cone.vision_test_shape_max_probe_count
sample_points.append_array(_random_points_on_probe_mesh(count))
return sample_points
func _get_collide_points(count: int) -> PackedVector3Array:
var sample_points : PackedVector3Array = []
# var cone_shape := vision_cone._collision_shape.shape
var observable_shape := collision_shape.shape
var query := PhysicsShapeQueryParameters3D.new()
query.collide_with_areas = true
query.collide_with_bodies = false
query.collision_mask = vision_cone.collision_layer
query.shape_rid = observable_shape.get_rid()
var world_space := vision_cone.get_world_3d().direct_space_state
var result := world_space.collide_shape(query)
for i in result.size():
if i % 2 == 1:
continue
sample_points.push_back(result[i])
if sample_points.size() >= count:
break
return sample_points
func probe() -> void:
var sample_points : PackedVector3Array = []
var max_count := vision_cone.vision_test_shape_max_probe_count
if visible:
sample_points.append(_get_last_visible_point_on_shape())
max_count -= 1
match vision_cone.vision_test_mode:
VisionTestMode.SAMPLE_CENTER:
if !visible:
sample_points.push_back(Vector3.ZERO)
VisionTestMode.SAMPLE_RANDOM_VERTICES:
sample_points.append_array(_get_scatter_points(max_count))
probe_results = []
visible = false
for shape_local_point in sample_points:
var global_point := collision_shape.global_position +\
(collision_shape.global_basis * shape_local_point)
if !vision_cone.point_within_cone(global_point):
continue
var probe_result := _probe_position(global_point, shape_local_point)
probe_results.push_back(probe_result)
# found body we were looking for
if probe_result.collider == body:
probe_result.visible = true
visible = true
if CONTINUE_PROBING_ON_SUCCESS:
print_debug("visible - continuing to _probe_position")
continue
return
class ProbeResult:
var start : Vector3
var end : Vector3
var shape_local_target : Vector3
var collider : Node3D
var visible : bool = false
func _init(
p_start: Vector3,
p_end: Vector3,
p_shape_local_target: Vector3,
p_collider: Node3D
) -> void:
start = p_start
end = p_end
shape_local_target = p_shape_local_target
collider = p_collider

View file

@ -0,0 +1 @@
uid://cmgcelj1qxg8o

View file

@ -0,0 +1,99 @@
extends Node3D
const ProbeResult := VisionCone3D.VisionTestProber.ProbeResult
# TODO should be modifiable via EditorSettings
const DEBUG_VISION_CONE_COLOR := Color(1, 1, 0, 0.02)
# TODO should be modifiable via EditorSettings
const DEBUG_RAY_COLOR_IS_VISIBLE := Color(Color.GREEN, 0.8)
# TODO should be modifiable via EditorSettings
const DEBUG_RAY_COLOR_IS_OBSTRUCTED := Color(Color.RED, 0.4)
@export var vision_cone : VisionCone3D
var debug_vision_cone_color := DEBUG_VISION_CONE_COLOR
var debug_ray_color_is_visible := DEBUG_RAY_COLOR_IS_VISIBLE
var debug_ray_color_in_cone := DEBUG_RAY_COLOR_IS_OBSTRUCTED
var _bounds_renderer : MeshInstance3D
var _probe_renderer : DebugProbeLineRenderer
func _init() -> void:
# create cone renderer
_bounds_renderer = MeshInstance3D.new()
var mesh := CylinderMesh.new()
mesh.material = make_visualizer_material()
_bounds_renderer.mesh = mesh
add_child(_bounds_renderer, false, INTERNAL_MODE_BACK)
_probe_renderer = DebugProbeLineRenderer.new()
_probe_renderer.probe_success_material = make_visualizer_material(debug_ray_color_is_visible)
_probe_renderer.probe_failure_material = make_visualizer_material(debug_ray_color_in_cone)
add_child(_probe_renderer)
@warning_ignore("return_value_discarded")
func _ready() -> void:
vision_cone = get_parent()
_probe_renderer.body_probe_data = vision_cone._body_probe_data
vision_cone.shape_changed.connect(update_cone_shape)
update_cone_shape()
func make_visualizer_material(albedo_color: Color = debug_vision_cone_color) -> StandardMaterial3D:
var mat := StandardMaterial3D.new()
mat.albedo_color = albedo_color
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
return mat
func update_cone_shape() -> void:
var m : CylinderMesh = _bounds_renderer.mesh
m.top_radius = 0
m.bottom_radius = vision_cone.end_radius
m.height = vision_cone.range
_bounds_renderer.rotation_degrees = Vector3(90, 0, 0)
_bounds_renderer.position.z = -vision_cone.range / 2
class DebugProbeLineRenderer extends MeshInstance3D:
var body_probe_data: Dictionary
var probe_success_material : StandardMaterial3D
var probe_failure_material : StandardMaterial3D
func _init() -> void:
mesh = ImmediateMesh.new()
func _process(_delta: float) -> void:
if Engine.is_editor_hint():
return
(mesh as ImmediateMesh).clear_surfaces()
if body_probe_data.is_empty():
return
var successful : Array[ProbeResult] = []
var failed : Array[ProbeResult] = []
for prober_list : Array[VisionCone3D.VisionTestProber] in body_probe_data.values():
for prober in prober_list:
for probe in prober.probe_results:
if probe.visible:
successful.push_back(probe)
else:
failed.push_back(probe)
var material_index := 0
if !successful.is_empty():
_add_probe_lines_surface(successful)
mesh.surface_set_material(material_index, probe_success_material)
material_index += 1
if !failed.is_empty():
_add_probe_lines_surface(failed)
mesh.surface_set_material(material_index, probe_failure_material)
material_index += 1
func _add_probe_lines_surface(probes: Array[ProbeResult]) -> void:
var imesh := mesh as ImmediateMesh
imesh.surface_begin(Mesh.PRIMITIVE_LINES)
for probe in probes:
imesh.surface_add_vertex(to_local(probe.start))
imesh.surface_add_vertex(to_local(probe.end))
imesh.surface_end()

View file

@ -0,0 +1 @@
uid://8mi2so0m8lyx

View file

@ -0,0 +1,182 @@
extends EditorNode3DGizmoPlugin
var texture : Texture2D = preload("../../icons/GizmoVisionCone.svg")
var undo_redo: EditorUndoRedoManager
var _start_drag_mouse_world_position : Vector3
var _start_drag_range: float
var _start_drag_angle : float
func _init() -> void:
create_material("cone_preview", Color(1, 1, 0), false)
create_handle_material("handles")
create_icon_material(
"icon",
texture,
)
func _get_gizmo_name() -> String:
return "VisionCone3D"
func _get_handle_name(
_gizmo: EditorNode3DGizmo,
handle_id: int,
_secondary: bool
) -> String:
match handle_id:
0: return "Range"
1: return "Angle"
_: return ""
func _get_handle_value(
gizmo: EditorNode3DGizmo,
handle_id: int,
_secondary: bool
) -> Variant:
var vc : VisionCone3D = gizmo.get_node_3d()
match handle_id:
0: return vc.range
1: return vc.angle
_: return null
func _begin_handle_action(
gizmo: EditorNode3DGizmo,
handle_id: int,
_secondary: bool
) -> void:
var vc : VisionCone3D = gizmo.get_node_3d()
_start_drag_mouse_world_position = vc.global_position + (-vc.global_basis.z * vc.range)
match handle_id:
0: # range
_start_drag_range = vc.range
1: # angle
_start_drag_angle = vc.angle
func _commit_handle(
gizmo: EditorNode3DGizmo,
handle_id: int,
_secondary: bool,
_restore: Variant,
# TODO use cancel
_cancel: bool
) -> void:
var vc : VisionCone3D = gizmo.get_node_3d()
match handle_id:
0: # range
undo_redo.create_action("Set range")
undo_redo.add_do_property(vc, "range", vc.range)
undo_redo.add_undo_property(vc, "range", _start_drag_range)
1: # angle
undo_redo.create_action("Set angle")
undo_redo.add_do_property(vc, "angle", vc.angle)
undo_redo.add_undo_property(vc, "angle", _start_drag_angle)
undo_redo.commit_action()
func _set_handle(
gizmo: EditorNode3DGizmo,
handle_id: int,
_secondary: bool,
camera: Camera3D,
_screen_pos: Vector2,
) -> void:
var vc : VisionCone3D = gizmo.get_node_3d()
match handle_id:
0: # range
# TODO this mostly works but not if camera.y is near vc.y
var world_pos := _calculate_mouse_world_position(
camera,
vc.global_position.y,
Vector3.UP
)
var local_pos := vc.to_local(world_pos)
var new_range := -local_pos.z
if new_range > 0:
vc.range = new_range
1: # angle
# var local_end_range_pos := Vector3(0, 0, -vc.range)
# TODO this mostly works but not if camera.y is near vc.y
var world_pos := _calculate_mouse_world_position(
camera,
vc.global_position.y,
Vector3.UP
)
var local_pos := vc.to_local(world_pos)
var radius := local_pos.x
vc.angle = abs(rad_to_deg(atan(radius / vc.range))) * 2
gizmo.get_node_3d().update_gizmos()
func _has_gizmo(node: Node3D) -> bool:
return node is VisionCone3D
func _redraw(gizmo: EditorNode3DGizmo) -> void:
gizmo.clear()
var vc : VisionCone3D = gizmo.get_node_3d()
gizmo.add_unscaled_billboard(get_material("icon", gizmo), 0.04)
var lines := _make_cone_lines(360, 6, vc.end_radius, vc.range)
# var cylinder_mesh := CylinderMesh.new()
var handles := PackedVector3Array([
Vector3(0, 0, -vc.range),
Vector3(vc.end_radius, 0, -vc.range)
])
var cone_alpha := 1.0
if EditorInterface.get_selection().get_selected_nodes().has(vc):
gizmo.add_lines(lines, get_material("cone_preview", gizmo), false, Color(1, 1, 1, cone_alpha))
gizmo.add_handles(handles, get_material("handles", gizmo), [])
@warning_ignore("shadowed_global_identifier")
@warning_ignore("return_value_discarded")
@warning_ignore("integer_division")
func _make_cone_lines(
resolution: int,
support_resolution: int,
end_radius: float,
range: float
) -> PackedVector3Array:
var points: PackedVector3Array = []
var support_every := resolution / support_resolution
var start : Vector3
for i in resolution:
# circle logic
var angle := float(i) * TAU / resolution
var x := cos(angle) * end_radius
var y := sin(angle) * end_radius
var point := Vector3(x, y, -range)
points.append(point)
if i % support_every == 0:
points.append(Vector3.ZERO)
points.append(point)
if i == 0:
start = point
else:
points.append(point)
points.append(start)
points.append(Vector3.ZERO)
points.append(Vector3(0, 0, -range))
return points
static func _calculate_mouse_world_position(
camera: Camera3D,
# world position along plane normal, could use a better name
intersection_point: float,
plane_normal: Vector3 = Vector3.UP
) -> Vector3:
var position := camera.get_viewport().get_mouse_position()
var camera_from := camera.project_ray_origin(position)
var camera_to := camera.project_ray_normal(position)
var n := plane_normal # plane normal
var p := camera_from # ray origin
var v := camera_to # ray direction
var d := intersection_point # distance of the plane from origin
var t := -(n.dot(p) - d) / n.dot(v) # solving for plain/ray intersection
return p + t * v

View file

@ -0,0 +1 @@
uid://b3snj0jopsyp1

View file

@ -164,7 +164,7 @@ movie_writer/movie_file="D:/Maddo/Recordings/Capture.avi"
[editor_plugins]
enabled=PackedStringArray("res://addons/cyclops_level_builder/plugin.cfg", "res://addons/dialogic/plugin.cfg", "res://addons/func_godot/plugin.cfg", "res://addons/godot_test_scene/plugin.cfg", "res://addons/resources_spreadsheet_view/plugin.cfg", "res://addons/scene_palette/plugin.cfg", "res://addons/smoothing/plugin.cfg")
enabled=PackedStringArray("res://addons/cyclops_level_builder/plugin.cfg", "res://addons/dialogic/plugin.cfg", "res://addons/func_godot/plugin.cfg", "res://addons/godot_test_scene/plugin.cfg", "res://addons/resources_spreadsheet_view/plugin.cfg", "res://addons/scene_palette/plugin.cfg", "res://addons/smoothing/plugin.cfg", "res://addons/tattomoosa.vision_cone_3d/plugin.cfg")
[global_group]

BIN
textures/special/trigger.png (Stored with Git LFS)

Binary file not shown.