mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 10:35:34 +00:00
Updated func_godot
This commit is contained in:
parent
d8d348640c
commit
01a852de9b
170 changed files with 1705 additions and 2296 deletions
21
addons/func_godot/LICENSE
Normal file
21
addons/func_godot/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 func-godot
|
||||
|
||||
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.
|
||||
15
addons/func_godot/fgd/cull_interior_faces.tres
Normal file
15
addons/func_godot/fgd/cull_interior_faces.tres
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDBaseClass" load_steps=2 format=3 uid="uid://bcdsueg5pysfq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ck575aqs1sbrb" path="res://addons/func_godot/src/fgd/func_godot_fgd_base_class.gd" id="1_21jph"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_21jph")
|
||||
classname = "CullInteriorFaces"
|
||||
description = "Cull interior faces option for SolidClass Geometry"
|
||||
class_properties = Dictionary[String, Variant]({
|
||||
"_cull_interior_faces": false
|
||||
})
|
||||
class_property_descriptions = Dictionary[String, Variant]({
|
||||
"_cull_interior_faces": "If true, cull interior faces with matching vertices or faces that are flush within a larger face. Note: This has a performance impact that scales with how many brushes are in the brush entity."
|
||||
})
|
||||
metadata/_custom_type_script = "uid://cgkrrgcimlr8y"
|
||||
|
|
@ -1,39 +1,18 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://cxy7jnh6d7msn"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=5 format=3 uid="uid://cxy7jnh6d7msn"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="1_0fsmp"]
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_c3bns"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_c03gr"]
|
||||
[ext_resource type="Resource" uid="uid://bcdsueg5pysfq" path="res://addons/func_godot/fgd/cull_interior_faces.tres" id="3_wuxhx"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_0fsmp")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = false
|
||||
render_layers = 1
|
||||
collision_shape_type = 2
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_detail"
|
||||
description = "Static collidable geometry. Builds a StaticBody3D with a MeshInstance3D and a single concave CollisionShape3D. Does not occlude other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_c3bns"), ExtResource("2_c03gr")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
base_classes = Array[Resource]([ExtResource("1_c3bns"), ExtResource("2_c03gr"), ExtResource("3_wuxhx")])
|
||||
meta_properties = Dictionary[String, Variant]({
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
})
|
||||
node_class = "StaticBody3D"
|
||||
name_property = ""
|
||||
|
|
|
|||
|
|
@ -1,39 +1,17 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://ch3e0dix85uhb"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=5 format=3 uid="uid://ch3e0dix85uhb"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_ar63x"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_j7vgq"]
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="2_lhb87"]
|
||||
[ext_resource type="Resource" uid="uid://bcdsueg5pysfq" path="res://addons/func_godot/fgd/cull_interior_faces.tres" id="3_1mhrv"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_lhb87")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = false
|
||||
render_layers = 1
|
||||
collision_shape_type = 0
|
||||
collision_layer = 1
|
||||
collision_mask = 1
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_detail_illusionary"
|
||||
description = "Static geometry with no collision. Builds a Node3D with a MeshInstance3D. Does not occlude other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_ar63x"), ExtResource("2_j7vgq")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
base_classes = Array[Resource]([ExtResource("1_ar63x"), ExtResource("2_j7vgq"), ExtResource("3_1mhrv")])
|
||||
meta_properties = Dictionary[String, Variant]({
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
})
|
||||
node_class = "Node3D"
|
||||
name_property = ""
|
||||
|
|
|
|||
|
|
@ -1,39 +1,19 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://b70vf4t5dc70t"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=5 format=3 uid="uid://b70vf4t5dc70t"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_5mwee"]
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="2_8o081"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_bp8pb"]
|
||||
[ext_resource type="Resource" uid="uid://bcdsueg5pysfq" path="res://addons/func_godot/fgd/cull_interior_faces.tres" id="3_xnsya"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_8o081")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = true
|
||||
render_layers = 1
|
||||
collision_shape_type = 2
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_geo"
|
||||
description = "Static collidable geometry. Builds a StaticBody3D with a MeshInstance3D, a single concave CollisionShape3D, and an OccluderInstance3D."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_5mwee"), ExtResource("2_bp8pb")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
base_classes = Array[Resource]([ExtResource("1_5mwee"), ExtResource("2_bp8pb"), ExtResource("3_xnsya")])
|
||||
meta_properties = Dictionary[String, Variant]({
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
})
|
||||
node_class = "StaticBody3D"
|
||||
name_property = ""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDFile" load_steps=9 format=3 uid="uid://crgpdahjaj"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDFile" load_steps=10 format=3 uid="uid://crgpdahjaj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://drlmgulwbjwqu" path="res://addons/func_godot/src/fgd/func_godot_fgd_file.gd" id="1_axt3h"]
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_ehab8"]
|
||||
|
|
@ -6,12 +6,10 @@
|
|||
[ext_resource type="Resource" uid="uid://bdji3873bg32h" path="res://addons/func_godot/fgd/worldspawn.tres" id="2_ri2rx"]
|
||||
[ext_resource type="Resource" uid="uid://b70vf4t5dc70t" path="res://addons/func_godot/fgd/func_geo.tres" id="3_7jigp"]
|
||||
[ext_resource type="Resource" uid="uid://cxy7jnh6d7msn" path="res://addons/func_godot/fgd/func_detail.tres" id="3_fqfww"]
|
||||
[ext_resource type="Resource" uid="uid://bcdsueg5pysfq" path="res://addons/func_godot/fgd/cull_interior_faces.tres" id="3_h5cmk"]
|
||||
[ext_resource type="Resource" uid="uid://dg5x44cc7flew" path="res://addons/func_godot/fgd/func_illusionary.tres" id="4_c4ucw"]
|
||||
[ext_resource type="Resource" uid="uid://ch3e0dix85uhb" path="res://addons/func_godot/fgd/func_detail_illusionary.tres" id="5_b2q3p"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_axt3h")
|
||||
target_map_editor = 1
|
||||
fgd_name = "FuncGodot"
|
||||
base_fgd_files = Array[Resource]([])
|
||||
entity_definitions = Array[Resource]([ExtResource("1_ehab8"), ExtResource("2_7jebp"), ExtResource("2_ri2rx"), ExtResource("3_7jigp"), ExtResource("3_fqfww"), ExtResource("5_b2q3p"), ExtResource("4_c4ucw")])
|
||||
entity_definitions = Array[Resource]([ExtResource("1_ehab8"), ExtResource("2_7jebp"), ExtResource("3_h5cmk"), ExtResource("2_ri2rx"), ExtResource("3_7jigp"), ExtResource("3_fqfww"), ExtResource("5_b2q3p"), ExtResource("4_c4ucw")])
|
||||
|
|
|
|||
|
|
@ -1,39 +1,18 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://dg5x44cc7flew"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=5 format=3 uid="uid://dg5x44cc7flew"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_kv0mq"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_hovr4"]
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="2_uffhi"]
|
||||
[ext_resource type="Resource" uid="uid://bcdsueg5pysfq" path="res://addons/func_godot/fgd/cull_interior_faces.tres" id="3_woywv"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_uffhi")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = true
|
||||
render_layers = 1
|
||||
collision_shape_type = 0
|
||||
collision_layer = 1
|
||||
collision_mask = 1
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_illusionary"
|
||||
description = "Static geometry with no collision. Builds a Node3D with a MeshInstance3D and an Occluder3D to aid in render culling of other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_kv0mq"), ExtResource("2_hovr4")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
base_classes = Array[Resource]([ExtResource("1_kv0mq"), ExtResource("2_hovr4"), ExtResource("3_woywv")])
|
||||
meta_properties = Dictionary[String, Variant]({
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
})
|
||||
node_class = "Node3D"
|
||||
name_property = ""
|
||||
|
|
|
|||
|
|
@ -6,23 +6,14 @@
|
|||
script = ExtResource("1_04y3n")
|
||||
classname = "Phong"
|
||||
description = "Phong shading options for SolidClass geometry."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([])
|
||||
class_properties = {
|
||||
class_properties = Dictionary[String, Variant]({
|
||||
"_phong": {
|
||||
"Disabled": 0,
|
||||
"Smooth shading": 1
|
||||
},
|
||||
"_phong_angle": 89.0
|
||||
}
|
||||
class_property_descriptions = {
|
||||
})
|
||||
class_property_descriptions = Dictionary[String, Variant]({
|
||||
"_phong": ["Phong shading", 0],
|
||||
"_phong_angle": "Phong smoothing angle"
|
||||
}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1),
|
||||
"size": AABB(-8, -8, -8, 8, 8, 8)
|
||||
}
|
||||
node_class = ""
|
||||
name_property = ""
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,19 +6,10 @@
|
|||
script = ExtResource("1_h3atm")
|
||||
classname = "VertexMergeDistance"
|
||||
description = "Adjustable value to snap vertices to on map build. This can reduce instances of seams between polygons."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([])
|
||||
class_properties = {
|
||||
"_vertex_merge_distance": 0.008
|
||||
}
|
||||
class_property_descriptions = {
|
||||
class_properties = Dictionary[String, Variant]({
|
||||
"_vertex_merge_distance": 0.03125
|
||||
})
|
||||
class_property_descriptions = Dictionary[String, Variant]({
|
||||
"_vertex_merge_distance": "Adjustable value to snap vertices to on map build. This can reduce instances of seams between polygons."
|
||||
}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1),
|
||||
"size": AABB(-8, -8, -8, 8, 8, 8)
|
||||
}
|
||||
node_class = ""
|
||||
name_property = ""
|
||||
})
|
||||
metadata/_custom_type_script = "uid://ck575aqs1sbrb"
|
||||
|
|
|
|||
|
|
@ -1,38 +1,18 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=3 format=3 uid="uid://bdji3873bg32h"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://bdji3873bg32h"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="1_62t8m"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="1_h1046"]
|
||||
[ext_resource type="Resource" uid="uid://bcdsueg5pysfq" path="res://addons/func_godot/fgd/cull_interior_faces.tres" id="2_ky6lr"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_62t8m")
|
||||
spawn_type = 0
|
||||
origin_type = 1
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = false
|
||||
render_layers = 1
|
||||
collision_shape_type = 1
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "worldspawn"
|
||||
description = "Default static world geometry. Builds a StaticBody3D with a single MeshInstance3D and a single convex CollisionShape3D shape. Also builds Occluder3D to aid in render culling of other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_h1046")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
description = "Default static world geometry. Builds a StaticBody3D with a single MeshInstance3D and a single convex CollisionShape3D shape."
|
||||
base_classes = Array[Resource]([ExtResource("1_h1046"), ExtResource("2_ky6lr")])
|
||||
meta_properties = Dictionary[String, Variant]({
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
})
|
||||
node_class = "StaticBody3D"
|
||||
name_property = ""
|
||||
|
|
|
|||
|
|
@ -1,31 +1,9 @@
|
|||
[gd_resource type="Resource" script_class="FuncGodotMapSettings" load_steps=4 format=3 uid="uid://bkhxcqsquw1yg"]
|
||||
[gd_resource type="Resource" script_class="FuncGodotMapSettings" load_steps=5 format=3 uid="uid://bkhxcqsquw1yg"]
|
||||
|
||||
[ext_resource type="Material" uid="uid://cvex6toty8yn7" path="res://addons/func_godot/textures/default_material.tres" id="1_8l5wm"]
|
||||
[ext_resource type="Script" uid="uid://bctwech0sq0kh" path="res://addons/func_godot/src/map/func_godot_map_settings.gd" id="1_dlf23"]
|
||||
[ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="1_hd7se"]
|
||||
[ext_resource type="Script" uid="uid://38q6k0ctahjn" path="res://addons/func_godot/src/map/func_godot_map_settings.gd" id="1_dlf23"]
|
||||
[ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="2_hf4oi"]
|
||||
[ext_resource type="Script" uid="uid://cij36hpqc46c" path="res://addons/func_godot/src/import/quake_wad_file.gd" id="4_576s4"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_dlf23")
|
||||
inverse_scale_factor = 16.0
|
||||
entity_fgd = ExtResource("1_hd7se")
|
||||
entity_name_property = ""
|
||||
base_texture_dir = "res://textures"
|
||||
texture_file_extensions = Array[String](["png", "jpg", "jpeg", "bmp", "tga", "webp"])
|
||||
clip_texture = "special/clip"
|
||||
skip_texture = "special/skip"
|
||||
origin_texture = "special/origin"
|
||||
texture_wads = Array[Resource]([])
|
||||
material_file_extension = "tres"
|
||||
default_material = ExtResource("1_8l5wm")
|
||||
default_material_albedo_uniform = ""
|
||||
albedo_map_pattern = "%s_albedo.%s"
|
||||
normal_map_pattern = "%s_normal.%s"
|
||||
metallic_map_pattern = "%s_metallic.%s"
|
||||
roughness_map_pattern = "%s_roughness.%s"
|
||||
emission_map_pattern = "%s_emission.%s"
|
||||
ao_map_pattern = "%s_ao.%s"
|
||||
height_map_pattern = "%s_height.%s"
|
||||
orm_map_pattern = "%s_orm.%s"
|
||||
save_generated_materials = true
|
||||
uv_unwrap_texel_size = 2.0
|
||||
use_trenchbroom_groups_hierarchy = false
|
||||
|
|
|
|||
|
|
@ -8,17 +8,6 @@
|
|||
|
||||
[resource]
|
||||
script = ExtResource("2_en8ro")
|
||||
gamepack_name = "func_godot"
|
||||
game_name = "FuncGodot"
|
||||
base_game_path = ""
|
||||
fgd_file = ExtResource("1_gct4v")
|
||||
netradiant_custom_shaders = Array[Resource]([ExtResource("2_w7psh"), ExtResource("3_6gpk8"), ExtResource("4_8rl60")])
|
||||
texture_types = PackedStringArray("png", "jpg", "jpeg", "bmp", "tga")
|
||||
model_types = PackedStringArray("glb", "gltf", "obj")
|
||||
sound_types = PackedStringArray("wav", "ogg")
|
||||
default_scale = "1.0"
|
||||
clip_texture = "textures/special/clip"
|
||||
skip_texture = "textures/special/skip"
|
||||
map_type = 1
|
||||
default_build_menu_variables = {}
|
||||
default_build_menu_commands = {}
|
||||
texture_types = PackedStringArray("png", "jpg", "jpeg", "bmp", "tga")
|
||||
|
|
|
|||
|
|
@ -4,5 +4,4 @@
|
|||
|
||||
[resource]
|
||||
script = ExtResource("1_cuylw")
|
||||
texture_path = "textures/special/clip"
|
||||
shader_attributes = Array[String](["qer_trans 0.4"])
|
||||
texture_path = "textures/clip"
|
||||
|
|
|
|||
|
|
@ -4,5 +4,4 @@
|
|||
|
||||
[resource]
|
||||
script = ExtResource("1_ah2cp")
|
||||
texture_path = "textures/special/origin"
|
||||
shader_attributes = Array[String](["qer_trans 0.4"])
|
||||
texture_path = "textures/origin"
|
||||
|
|
|
|||
|
|
@ -4,5 +4,4 @@
|
|||
|
||||
[resource]
|
||||
script = ExtResource("1_4ja6h")
|
||||
texture_path = "textures/special/skip"
|
||||
shader_attributes = Array[String](["qer_trans 0.4"])
|
||||
texture_path = "textures/skip"
|
||||
|
|
|
|||
|
|
@ -9,26 +9,3 @@
|
|||
|
||||
[resource]
|
||||
script = ExtResource("2_ns6ah")
|
||||
game_name = "FuncGodot"
|
||||
icon = ExtResource("6_tex5j")
|
||||
map_formats = Array[Dictionary]([{
|
||||
"format": "Valve",
|
||||
"initialmap": "initial_valve.map"
|
||||
}, {
|
||||
"format": "Standard",
|
||||
"initialmap": "initial_standard.map"
|
||||
}, {
|
||||
"format": "Quake2",
|
||||
"initialmap": "initial_quake2.map"
|
||||
}, {
|
||||
"format": "Quake3"
|
||||
}])
|
||||
textures_root_folder = "textures"
|
||||
texture_exclusion_patterns = Array[String](["*_albedo", "*_ao", "*_emission", "*_height", "*_metallic", "*_normal", "*_orm", "*_roughness", "*_sss"])
|
||||
palette_path = "textures/palette.lmp"
|
||||
fgd_file = ExtResource("1_8u1vq")
|
||||
entity_scale = "32"
|
||||
brush_tags = Array[Resource]([])
|
||||
brushface_tags = Array[Resource]([ExtResource("1_rsp20"), ExtResource("2_166i2"), ExtResource("3_stisi")])
|
||||
default_uv_scale = Vector2(1, 1)
|
||||
game_config_version = 0
|
||||
|
|
|
|||
|
|
@ -8,4 +8,3 @@ tag_name = "Func"
|
|||
tag_attributes = Array[String]([])
|
||||
tag_match_type = 1
|
||||
tag_pattern = "func*"
|
||||
texture_name = ""
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
[resource]
|
||||
script = ExtResource("1_msqpk")
|
||||
tag_name = "Trigger"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 1
|
||||
tag_pattern = "trigger*"
|
||||
texture_name = "special/trigger"
|
||||
texture_name = "trigger"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,4 @@
|
|||
[resource]
|
||||
script = ExtResource("1_7td58")
|
||||
tag_name = "Clip"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 0
|
||||
tag_pattern = "clip"
|
||||
texture_name = ""
|
||||
|
|
|
|||
|
|
@ -5,7 +5,4 @@
|
|||
[resource]
|
||||
script = ExtResource("1_enkfc")
|
||||
tag_name = "Origin"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 0
|
||||
tag_pattern = "origin"
|
||||
texture_name = ""
|
||||
|
|
|
|||
|
|
@ -5,7 +5,4 @@
|
|||
[resource]
|
||||
script = ExtResource("1_2teqe")
|
||||
tag_name = "Skip"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 0
|
||||
tag_pattern = "skip"
|
||||
texture_name = ""
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.png-6db43b6a52df1ce3744a82f15cbdbbea.cte
|
|||
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
|
||||
|
|
@ -25,6 +27,10 @@ mipmaps/generate=false
|
|||
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
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon32.png-7025e2d95a64a3066b7947e1900b4daf.c
|
|||
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
|
||||
|
|
@ -25,6 +27,10 @@ mipmaps/generate=false
|
|||
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
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
name="FuncGodot"
|
||||
description="Quake .map and Half-Life .vmf file support for Godot."
|
||||
author="Josh Palmer, Hannah Crawford, Emberlynn Bland, Tim Maccabe, Vera Lux"
|
||||
version="2025.9"
|
||||
author="Josh Palmer, Hannah Crawford, Emberlynn Bland, Tim Maccabe, Vera Lux, func_godot Community"
|
||||
version="2025.12"
|
||||
script="src/func_godot_plugin.gd"
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ class GroupData extends RefCounted:
|
|||
class EntityData extends RefCounted:
|
||||
## All of the entity's key value pairs from the map file, retrieved during parsing.
|
||||
## The func_godot_properties dictionary generated at the end of entity assembly is derived from this.
|
||||
var properties: Dictionary = {}
|
||||
var properties: Dictionary[String, Variant] = {}
|
||||
## The entity's brush data collected during the parsing stage. If the entity's FGD resource cannot be found,
|
||||
## the presence of a single brush determines this entity to be a Solid Entity.
|
||||
var brushes: Array[BrushData] = []
|
||||
|
|
@ -154,6 +154,12 @@ class EntityData extends RefCounted:
|
|||
return (definition
|
||||
and definition is FuncGodotFGDSolidClass
|
||||
and definition.build_visuals)
|
||||
|
||||
func is_gi_enabled() -> bool:
|
||||
return (definition
|
||||
and definition is FuncGodotFGDSolidClass
|
||||
and definition.global_illumination_mode
|
||||
)
|
||||
|
||||
## Checks the entity's FGD resource definition, returning whether the Solid Class CollisionShapeType is set to Convex.
|
||||
func is_collision_convex() -> bool:
|
||||
|
|
@ -172,12 +178,12 @@ class EntityData extends RefCounted:
|
|||
## Determines if the entity's mesh should be processed for normal smoothing.
|
||||
## The smoothing property can be retrieved from [member FuncGodotMapSettings.entity_smoothing_property].
|
||||
func is_smooth_shaded(smoothing_property: String = "_phong") -> bool:
|
||||
return properties.get(smoothing_property, "0").to_int()
|
||||
return properties.get(smoothing_property, 0)
|
||||
|
||||
## Retrieves the entity's smoothing angle to determine if the face should be smoothed.
|
||||
## The smoothing angle property can be retrieved from [member FuncGodotMapSettings.entity_smoothing_angle_property].
|
||||
func get_smoothing_angle(smoothing_angle_property: String = "_phong_angle") -> float:
|
||||
return properties.get(smoothing_angle_property, "89.0").to_float()
|
||||
return properties.get(smoothing_angle_property, 89.0)
|
||||
|
||||
class VertexGroupData:
|
||||
## Faces this vertex appears in.
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ func generate_solid_entity_node(node: Node, node_name: String, data: _EntityData
|
|||
node = ClassDB.instantiate(definition.node_class)
|
||||
else:
|
||||
var script: Script = get_script_by_class_name(definition.node_class)
|
||||
if script is GDScript:
|
||||
node = (script as GDScript).new()
|
||||
if script is Script:
|
||||
node = script.new()
|
||||
else:
|
||||
node = Node3D.new()
|
||||
|
||||
node.name = node_name
|
||||
node_name = node_name.trim_suffix(definition.classname).trim_suffix("_")
|
||||
var properties: Dictionary = data.properties
|
||||
var properties: Dictionary[String, Variant] = data.properties
|
||||
|
||||
# Mesh Instance generation
|
||||
if data.mesh:
|
||||
|
|
@ -95,9 +95,17 @@ func generate_solid_entity_node(node: Node, node_name: String, data: _EntityData
|
|||
node.add_child(occluder_instance)
|
||||
data.occluder_instance = occluder_instance
|
||||
|
||||
# NOTE: Currently occuring in EntityAssembler until the appropriate method in GeometryGenerator is resolved
|
||||
# For now, smooth entire mesh, then unwrap for lightmap if needed
|
||||
if not (build_flags & FuncGodotMap.BuildFlags.DISABLE_SMOOTHING) and data.is_smooth_shaded(map_settings.entity_smoothing_property):
|
||||
mesh_instance.mesh = FuncGodotUtil.smooth_mesh_by_angle(data.mesh, data.get_smoothing_angle(map_settings.entity_smoothing_angle_property))
|
||||
|
||||
if data.is_gi_enabled() and (build_flags & FuncGodotMap.BuildFlags.UNWRAP_UV2):
|
||||
mesh_instance.mesh.lightmap_unwrap(
|
||||
Transform3D.IDENTITY,
|
||||
map_settings.uv_unwrap_texel_size * map_settings.scale_factor
|
||||
)
|
||||
|
||||
# Collision generation
|
||||
if data.shapes.size() and node is CollisionObject3D:
|
||||
node.collision_layer = definition.collision_layer
|
||||
|
|
@ -131,7 +139,9 @@ func generate_solid_entity_node(node: Node, node_name: String, data: _EntityData
|
|||
|
||||
if "position" in node:
|
||||
if node.position is Vector3:
|
||||
node.position = FuncGodotUtil.id_to_opengl(data.origin) * map_settings.scale_factor
|
||||
node.position = FuncGodotUtil.id_to_opengl(data.origin)
|
||||
elif node.position is Vector2:
|
||||
node.position = Vector2(data.origin.z, -data.origin.y) * map_settings.inverse_scale_factor
|
||||
|
||||
if not data.mesh_metadata.is_empty():
|
||||
node.set_meta("func_godot_mesh_data", data.mesh_metadata)
|
||||
|
|
@ -152,9 +162,9 @@ func generate_point_entity_node(node: Node, node_name: String, properties: Dicti
|
|||
node = ClassDB.instantiate(definition.node_class)
|
||||
else:
|
||||
var script: Script = get_script_by_class_name(definition.node_class)
|
||||
if script is GDScript:
|
||||
node = (script as GDScript).new()
|
||||
else:
|
||||
if script is Script:
|
||||
node = script.new()
|
||||
if not node:
|
||||
node = Node3D.new()
|
||||
|
||||
node.name = node_name
|
||||
|
|
@ -164,22 +174,28 @@ func generate_point_entity_node(node: Node, node_name: String, properties: Dicti
|
|||
if "angles" in properties or "mangle" in properties:
|
||||
var key := "angles" if "angles" in properties else "mangle"
|
||||
var angles_raw = properties[key]
|
||||
if not angles_raw is Vector3:
|
||||
if angles_raw is String:
|
||||
angles_raw = angles_raw.split_floats(' ')
|
||||
if angles_raw.size() > 2:
|
||||
if angles_raw.size() < 3:
|
||||
push_error("Invalid vector format for \"" + key + "\" in entity \"" + classname + "\"")
|
||||
angles_raw = null
|
||||
if angles_raw:
|
||||
angles = Vector3(-angles_raw[0], angles_raw[1], -angles_raw[2])
|
||||
if key == "mangle":
|
||||
if definition.classname.begins_with("light"):
|
||||
angles = Vector3(angles_raw[1], angles_raw[0], -angles_raw[2])
|
||||
elif definition.classname == "info_intermission":
|
||||
angles = Vector3(angles_raw[0], angles_raw[1], -angles_raw[2])
|
||||
else:
|
||||
push_error("Invalid vector format for \"" + key + "\" in entity \"" + classname + "\"")
|
||||
elif "angle" in properties:
|
||||
var angle = properties["angle"]
|
||||
if not angle is float:
|
||||
angle = float(angle)
|
||||
angles.y += angle
|
||||
if is_equal_approx(angle, -1):
|
||||
angles.x = 90
|
||||
elif is_equal_approx(angle, -2):
|
||||
angles.x = -90
|
||||
else:
|
||||
angles.y += angle
|
||||
angles.y += 180
|
||||
node.rotation_degrees = angles
|
||||
|
||||
|
|
@ -203,11 +219,18 @@ func generate_point_entity_node(node: Node, node_name: String, properties: Dicti
|
|||
|
||||
if "origin" in properties:
|
||||
var origin_vec: Vector3 = Vector3.ZERO
|
||||
var origin_comps: PackedFloat64Array = properties['origin'].split_floats(' ')
|
||||
if origin_comps.size() > 2:
|
||||
origin_vec = Vector3(origin_comps[1], origin_comps[2], origin_comps[0])
|
||||
var origin_prop = properties['origin']
|
||||
if origin_prop is Vector3:
|
||||
origin_vec = Vector3(origin_prop.y, origin_prop.z, origin_prop.x)
|
||||
elif origin_prop is String:
|
||||
var origin_comps: PackedFloat64Array = properties['origin'].split_floats(' ')
|
||||
if origin_comps.size() > 2:
|
||||
origin_vec = Vector3(origin_comps[1], origin_comps[2], origin_comps[0])
|
||||
else:
|
||||
push_error("Invalid vector format for \"origin\" in " + node_name)
|
||||
else:
|
||||
push_error("Invalid vector format for \"origin\" in " + node_name)
|
||||
|
||||
if "position" in node:
|
||||
if node.position is Vector3:
|
||||
node.position = origin_vec * map_settings.scale_factor
|
||||
|
|
@ -220,127 +243,15 @@ func generate_point_entity_node(node: Node, node_name: String, properties: Dicti
|
|||
## based upon the [FuncGodotFGDEntity]'s class properties, then attempts to send those properties to a [code]func_godot_properties[/code] [Dictionary]
|
||||
## and an [code]_func_godot_apply_properties(properties: Dictionary)[/code] method on the node. A deferred call to [code]_func_godot_build_complete()[/code] is also made.
|
||||
func apply_entity_properties(node: Node, data: _EntityData) -> void:
|
||||
var properties: Dictionary = data.properties
|
||||
var properties: Dictionary[String, Variant] = data.properties
|
||||
|
||||
if data.definition:
|
||||
var def := data.definition
|
||||
for property in properties:
|
||||
var prop_string = properties[property]
|
||||
if property in def.class_properties:
|
||||
var prop_default: Variant = def.class_properties[property]
|
||||
|
||||
match typeof(prop_default):
|
||||
TYPE_INT:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_FLOAT:
|
||||
properties[property] = prop_string.to_float()
|
||||
TYPE_BOOL:
|
||||
properties[property] = bool(prop_string.to_int())
|
||||
TYPE_VECTOR3:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 2:
|
||||
properties[property] = Vector3(prop_comps[0], prop_comps[1], prop_comps[2])
|
||||
else:
|
||||
push_error("Invalid Vector3 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR3I:
|
||||
var prop_vec: Vector3i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 2:
|
||||
for i in 3:
|
||||
prop_vec[i] = prop_comps[i].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector3i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_COLOR:
|
||||
var prop_color: Color = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 2:
|
||||
prop_color.r8 = prop_comps[0].to_int()
|
||||
prop_color.g8 = prop_comps[1].to_int()
|
||||
prop_color.b8 = prop_comps[2].to_int()
|
||||
prop_color.a = 1.0
|
||||
else:
|
||||
push_error("Invalid Color format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_color
|
||||
TYPE_DICTIONARY:
|
||||
var prop_desc = def.class_property_descriptions[property]
|
||||
if prop_desc is Array and prop_desc.size() > 1 and prop_desc[1] is int:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_ARRAY:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_VECTOR2:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 1:
|
||||
properties[property] = Vector2(prop_comps[0], prop_comps[1])
|
||||
else:
|
||||
push_error("Invalid Vector2 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR2I:
|
||||
var prop_vec: Vector2i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 1:
|
||||
for i in 2:
|
||||
prop_vec[i] = prop_comps[i].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector2i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_VECTOR4:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 3:
|
||||
properties[property] = Vector4(prop_comps[0], prop_comps[1], prop_comps[2], prop_comps[3])
|
||||
else:
|
||||
push_error("Invalid Vector4 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR4I:
|
||||
var prop_vec: Vector4i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 3:
|
||||
for i in 4:
|
||||
prop_vec[i] = prop_comps[i].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector4i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_STRING_NAME:
|
||||
properties[property] = StringName(prop_string)
|
||||
TYPE_NODE_PATH:
|
||||
properties[property] = prop_string
|
||||
TYPE_OBJECT:
|
||||
properties[property] = prop_string
|
||||
|
||||
# Assign properties not defined with defaults from the entity definition
|
||||
for property in def.class_properties:
|
||||
if not property in properties:
|
||||
var prop_default: Variant = def.class_properties[property]
|
||||
# Flags
|
||||
if prop_default is Array:
|
||||
var prop_flags_sum := 0
|
||||
for prop_flag in prop_default:
|
||||
if prop_flag is Array and prop_flag.size() > 2:
|
||||
if prop_flag[2] and prop_flag[1] is int:
|
||||
prop_flags_sum += prop_flag[1]
|
||||
properties[property] = prop_flags_sum
|
||||
# Choices
|
||||
elif prop_default is Dictionary:
|
||||
var prop_desc = def.class_property_descriptions.get(property, "")
|
||||
if prop_desc is Array and prop_desc.size() > 1 and (prop_desc[1] is int or prop_desc[1] is String):
|
||||
properties[property] = prop_desc[1]
|
||||
elif prop_default.size():
|
||||
properties[property] = prop_default[prop_default.keys().front()]
|
||||
else:
|
||||
properties[property] = 0
|
||||
# Materials, Shaders, and Sounds
|
||||
elif prop_default is Resource:
|
||||
properties[property] = prop_default.resource_path
|
||||
# Target Destination and Target Source
|
||||
elif prop_default is NodePath or prop_default is Object or prop_default == null:
|
||||
properties[property] = ""
|
||||
# Everything else
|
||||
else:
|
||||
properties[property] = prop_default
|
||||
|
||||
if def.auto_apply_to_matching_node_properties:
|
||||
for property in properties:
|
||||
if property == 'scale' and def is FuncGodotFGDPointClass and def.apply_scale_on_map_build:
|
||||
# scale has already been applied
|
||||
continue
|
||||
if property in node:
|
||||
if typeof(node.get(property)) == typeof(properties[property]):
|
||||
node.set(property, properties[property])
|
||||
|
|
@ -361,49 +272,44 @@ func apply_entity_properties(node: Node, data: _EntityData) -> void:
|
|||
func generate_entity_node(entity_data: _EntityData, entity_index: int) -> Node:
|
||||
var node: Node = null
|
||||
var node_name: String = "entity_%s" % entity_index
|
||||
var properties: Dictionary = entity_data.properties
|
||||
var properties: Dictionary[String, Variant] = entity_data.properties
|
||||
var entity_def: FuncGodotFGDEntityClass = entity_data.definition
|
||||
|
||||
if "classname" in entity_data.properties:
|
||||
var classname: String = properties["classname"]
|
||||
|
||||
node_name += "_" + properties["classname"]
|
||||
var default_point_def := FuncGodotFGDPointClass.new()
|
||||
var default_solid_def := FuncGodotFGDSolidClass.new()
|
||||
default_solid_def.collision_shape_type = FuncGodotFGDSolidClass.CollisionShapeType.NONE
|
||||
|
||||
if entity_def:
|
||||
var name_prop: String
|
||||
if entity_def.name_property in properties:
|
||||
name_prop = str(properties[entity_def.name_property])
|
||||
elif map_settings.entity_name_property in properties:
|
||||
name_prop = str(properties[map_settings.entity_name_property])
|
||||
if not name_prop.is_empty():
|
||||
node_name = "entity_" + name_prop
|
||||
|
||||
if entity_def is FuncGodotFGDSolidClass:
|
||||
node = generate_solid_entity_node(node, node_name, entity_data, entity_def)
|
||||
elif entity_def is FuncGodotFGDPointClass:
|
||||
node = generate_point_entity_node(node, node_name, properties, entity_def)
|
||||
else:
|
||||
push_error("Invalid entity definition for \"" + node_name + "\". Entity definition must be Solid Class or Point Class.")
|
||||
node = generate_point_entity_node(node, node_name, properties, default_point_def)
|
||||
|
||||
if node and entity_def.script_class:
|
||||
var name_prop: String
|
||||
if entity_def.name_property in properties:
|
||||
name_prop = str(properties[entity_def.name_property])
|
||||
elif map_settings.entity_name_property in properties:
|
||||
name_prop = str(properties[map_settings.entity_name_property])
|
||||
if not name_prop.is_empty():
|
||||
node_name = "entity_" + name_prop
|
||||
|
||||
if entity_def is FuncGodotFGDSolidClass:
|
||||
node = generate_solid_entity_node(node, node_name, entity_data, entity_def)
|
||||
elif entity_def is FuncGodotFGDPointClass:
|
||||
node = generate_point_entity_node(node, node_name, properties, entity_def)
|
||||
|
||||
if node:
|
||||
if entity_def.script_class:
|
||||
node.set_script(entity_def.script_class)
|
||||
else:
|
||||
push_error("No entity definition found for \"" + node_name + "\"")
|
||||
if entity_data.brushes.size():
|
||||
node = generate_solid_entity_node(node, node_name, entity_data, default_solid_def)
|
||||
else:
|
||||
node = generate_point_entity_node(node, node_name, properties, default_point_def)
|
||||
|
||||
var node_groups: Array[String] = map_settings.entity_node_groups.duplicate()
|
||||
node_groups.append_array(entity_def.node_groups)
|
||||
for node_group in node_groups:
|
||||
if node_group.is_empty():
|
||||
continue
|
||||
node.add_to_group(node_group, true)
|
||||
|
||||
return node
|
||||
|
||||
## Main entity assembly process called by [FuncGodotMap]. Generates and sorts group nodes in the [SceneTree] first,
|
||||
## then generates and assembles [Node]s based upon the provided [FuncGodotData.EntityData] and adds them to the [SceneTree].
|
||||
func build(map_node: FuncGodotMap, entities: Array[_EntityData], groups: Array[_GroupData]) -> void:
|
||||
var scene_root := map_node.get_tree().edited_scene_root if map_node.get_tree() else map_node
|
||||
var scene_root := map_node.get_tree().edited_scene_root if map_node.is_inside_tree() else map_node
|
||||
build_flags = map_node.build_flags
|
||||
|
||||
if map_settings.use_groups_hierarchy:
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
class_name FuncGodot extends RefCounted
|
||||
|
||||
var map_data:= FuncGodotMapData.new()
|
||||
var map_parser:= FuncGodotMapParser.new(map_data)
|
||||
var geo_generator = preload("res://addons/func_godot/src/core/func_godot_geo_generator.gd").new(map_data)
|
||||
var map_settings: FuncGodotMapSettings = null:
|
||||
set(new):
|
||||
if not new or new == map_settings: return
|
||||
surface_gatherer.map_settings = new
|
||||
map_settings = new
|
||||
var surface_gatherer:= FuncGodotSurfaceGatherer.new(map_data, map_settings)
|
||||
|
||||
func load_map(filename: String, keep_tb_groups: bool) -> void:
|
||||
map_parser.load_map(filename, keep_tb_groups)
|
||||
|
||||
func get_texture_list() -> PackedStringArray:
|
||||
var g_textures: PackedStringArray
|
||||
var tex_count: int = map_data.textures.size()
|
||||
|
||||
g_textures.resize(tex_count)
|
||||
for i in range(tex_count):
|
||||
g_textures.set(i, map_data.textures[i].name)
|
||||
|
||||
return g_textures
|
||||
|
||||
func set_entity_definitions(entity_defs: Dictionary) -> void:
|
||||
for i in range(entity_defs.size()):
|
||||
var classname: String = entity_defs.keys()[i]
|
||||
var spawn_type: int = entity_defs.values()[i].get("spawn_type", FuncGodotMapData.FuncGodotEntitySpawnType.ENTITY)
|
||||
var origin_type: int = entity_defs.values()[i].get("origin_type", FuncGodotMapData.FuncGodotEntityOriginType.BOUNDS_CENTER)
|
||||
var metadata_inclusion_flags: int = entity_defs.values()[i].get("metadata_inclusion_flags", FuncGodotMapData.FuncGodotEntityMetadataInclusionFlags.NONE)
|
||||
map_data.set_entity_types_by_classname(classname, spawn_type, origin_type, metadata_inclusion_flags)
|
||||
|
||||
func get_texture_info(texture_name: String) -> FuncGodotMapData.FuncGodotTextureType:
|
||||
if texture_name == map_settings.origin_texture:
|
||||
return FuncGodotMapData.FuncGodotTextureType.ORIGIN
|
||||
return FuncGodotMapData.FuncGodotTextureType.NORMAL
|
||||
|
||||
func generate_geometry(texture_dict: Dictionary) -> void:
|
||||
var keys: Array = texture_dict.keys()
|
||||
for key in keys:
|
||||
var val: Vector2 = texture_dict[key]
|
||||
map_data.set_texture_info(key, val.x, val.y, get_texture_info(key))
|
||||
geo_generator.run()
|
||||
|
||||
func get_entity_dicts() -> Array:
|
||||
var ent_dicts: Array
|
||||
for entity in map_data.entities:
|
||||
var dict: Dictionary
|
||||
dict["brush_count"] = entity.brushes.size()
|
||||
|
||||
# TODO: This is a horrible remnant of the worldspawn layer system, remove it.
|
||||
var brush_indices: PackedInt64Array
|
||||
brush_indices.resize(entity.brushes.size())
|
||||
for b in range(entity.brushes.size()):
|
||||
brush_indices[b] = b
|
||||
|
||||
dict["brush_indices"] = brush_indices
|
||||
dict["center"] = Vector3(entity.center.y, entity.center.z, entity.center.x)
|
||||
dict["properties"] = entity.properties
|
||||
|
||||
ent_dicts.append(dict)
|
||||
|
||||
return ent_dicts
|
||||
|
||||
func gather_texture_surfaces(texture_name: String) -> Dictionary:
|
||||
var sg: FuncGodotSurfaceGatherer = FuncGodotSurfaceGatherer.new(map_data, map_settings)
|
||||
sg.reset_params()
|
||||
sg.split_type = FuncGodotSurfaceGatherer.SurfaceSplitType.ENTITY
|
||||
const MFlags = FuncGodotMapData.FuncGodotEntityMetadataInclusionFlags
|
||||
sg.metadata_skip_flags = MFlags.TEXTURES | MFlags.COLLISION_SHAPE_TO_FACE_RANGE_MAP
|
||||
sg.set_texture_filter(texture_name)
|
||||
sg.set_clip_filter_texture(map_settings.clip_texture)
|
||||
sg.set_skip_filter_texture(map_settings.skip_texture)
|
||||
sg.set_origin_filter_texture(map_settings.origin_texture)
|
||||
sg.run()
|
||||
return {
|
||||
surfaces = fetch_surfaces(sg),
|
||||
metadata = sg.out_metadata,
|
||||
}
|
||||
|
||||
func gather_entity_convex_collision_surfaces(entity_idx: int) -> void:
|
||||
surface_gatherer.reset_params()
|
||||
surface_gatherer.split_type = FuncGodotSurfaceGatherer.SurfaceSplitType.BRUSH
|
||||
surface_gatherer.entity_filter_idx = entity_idx
|
||||
surface_gatherer.set_origin_filter_texture(map_settings.origin_texture)
|
||||
surface_gatherer.run()
|
||||
|
||||
func gather_entity_concave_collision_surfaces(entity_idx: int) -> void:
|
||||
surface_gatherer.reset_params()
|
||||
surface_gatherer.split_type = FuncGodotSurfaceGatherer.SurfaceSplitType.NONE
|
||||
surface_gatherer.entity_filter_idx = entity_idx
|
||||
const MFlags = FuncGodotMapData.FuncGodotEntityMetadataInclusionFlags
|
||||
surface_gatherer.metadata_skip_flags |= MFlags.COLLISION_SHAPE_TO_FACE_RANGE_MAP
|
||||
surface_gatherer.set_skip_filter_texture(map_settings.skip_texture)
|
||||
surface_gatherer.set_origin_filter_texture(map_settings.origin_texture)
|
||||
surface_gatherer.run()
|
||||
|
||||
func fetch_surfaces(sg: FuncGodotSurfaceGatherer) -> Array:
|
||||
var surfs: Array[FuncGodotMapData.FuncGodotFaceGeometry] = sg.out_surfaces
|
||||
var surf_array: Array
|
||||
|
||||
for surf in surfs:
|
||||
if surf == null or surf.vertices.size() == 0:
|
||||
surf_array.append(null)
|
||||
continue
|
||||
|
||||
var vertices: PackedVector3Array
|
||||
var normals: PackedVector3Array
|
||||
var tangents: PackedFloat64Array
|
||||
var uvs: PackedVector2Array
|
||||
for v in surf.vertices:
|
||||
vertices.append(Vector3(v.vertex.y, v.vertex.z, v.vertex.x) * map_settings.scale_factor)
|
||||
normals.append(Vector3(v.normal.y, v.normal.z, v.normal.x))
|
||||
tangents.append(v.tangent.y)
|
||||
tangents.append(v.tangent.z)
|
||||
tangents.append(v.tangent.x)
|
||||
tangents.append(v.tangent.w)
|
||||
uvs.append(Vector2(v.uv.x, v.uv.y))
|
||||
|
||||
var indices: PackedInt32Array
|
||||
if surf.indicies.size() > 0:
|
||||
indices.append_array(surf.indicies)
|
||||
|
||||
var brush_array: Array
|
||||
brush_array.resize(Mesh.ARRAY_MAX)
|
||||
|
||||
brush_array[Mesh.ARRAY_VERTEX] = vertices
|
||||
brush_array[Mesh.ARRAY_NORMAL] = normals
|
||||
brush_array[Mesh.ARRAY_TANGENT] = tangents
|
||||
brush_array[Mesh.ARRAY_TEX_UV] = uvs
|
||||
brush_array[Mesh.ARRAY_INDEX] = indices
|
||||
|
||||
surf_array.append(brush_array)
|
||||
|
||||
return surf_array
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bvstd30rkrap
|
||||
|
|
@ -1,381 +0,0 @@
|
|||
extends RefCounted
|
||||
|
||||
# Min distance between two verts in a brush before they're merged. Higher values fix angled brushes near extents.
|
||||
const CMP_EPSILON:= 0.008
|
||||
|
||||
const UP_VECTOR:= Vector3(0.0, 0.0, 1.0)
|
||||
const RIGHT_VECTOR:= Vector3(0.0, 1.0, 0.0)
|
||||
const FORWARD_VECTOR:= Vector3(1.0, 0.0, 0.0)
|
||||
|
||||
var map_data: FuncGodotMapData
|
||||
|
||||
var wind_entity_idx: int = 0
|
||||
var wind_brush_idx: int = 0
|
||||
var wind_face_idx: int = 0
|
||||
var wind_face_center: Vector3
|
||||
var wind_face_basis: Vector3
|
||||
var wind_face_normal: Vector3
|
||||
|
||||
func _init(in_map_data: FuncGodotMapData) -> void:
|
||||
map_data = in_map_data
|
||||
|
||||
func sort_vertices_by_winding(a: FuncGodotMapData.FuncGodotFaceVertex, b: FuncGodotMapData.FuncGodotFaceVertex) -> bool:
|
||||
var face: FuncGodotMapData.FuncGodotFace = map_data.entities[wind_entity_idx].brushes[wind_brush_idx].faces[wind_face_idx]
|
||||
var face_geo: FuncGodotMapData.FuncGodotFaceGeometry = map_data.entity_geo[wind_entity_idx].brushes[wind_brush_idx].faces[wind_face_idx]
|
||||
|
||||
var u: Vector3 = wind_face_basis.normalized()
|
||||
var v: Vector3 = u.cross(wind_face_normal).normalized()
|
||||
|
||||
var loc_a: Vector3 = a.vertex - wind_face_center
|
||||
var a_pu: float = loc_a.dot(u)
|
||||
var a_pv: float = loc_a.dot(v)
|
||||
|
||||
var loc_b: Vector3 = b.vertex - wind_face_center
|
||||
var b_pu: float = loc_b.dot(u)
|
||||
var b_pv: float = loc_b.dot(v)
|
||||
|
||||
var a_angle: float = atan2(a_pv, a_pu)
|
||||
var b_angle: float = atan2(b_pv, b_pu)
|
||||
|
||||
return a_angle < b_angle
|
||||
|
||||
# returns null if no intersection, else intersection vertex.
|
||||
func intersect_face(f0: FuncGodotMapData.FuncGodotFace, f1: FuncGodotMapData.FuncGodotFace, f2: FuncGodotMapData.FuncGodotFace) -> Variant:
|
||||
var n0:= f0.plane_normal
|
||||
var n1:= f1.plane_normal
|
||||
var n2:= f2.plane_normal
|
||||
var denom: float = n0.cross(n1).dot(n2)
|
||||
if denom > 0.0:
|
||||
return (n1.cross(n2) * f0.plane_dist + n2.cross(n0) * f1.plane_dist + n0.cross(n1) * f2.plane_dist) / denom
|
||||
return null
|
||||
|
||||
func vertex_in_hull(faces: Array[FuncGodotMapData.FuncGodotFace], vertex: Vector3) -> bool:
|
||||
for face in faces:
|
||||
var proj: float = face.plane_normal.dot(vertex)
|
||||
if proj > face.plane_dist and absf(face.plane_dist - proj) > CMP_EPSILON:
|
||||
return false
|
||||
return true
|
||||
|
||||
func get_standard_uv(vertex: Vector3, face: FuncGodotMapData.FuncGodotFace, texture_width: int, texture_height: int) -> Vector2:
|
||||
var uv_out: Vector2
|
||||
var du:= absf(face.plane_normal.dot(UP_VECTOR))
|
||||
var dr:= absf(face.plane_normal.dot(RIGHT_VECTOR))
|
||||
var df:= absf(face.plane_normal.dot(FORWARD_VECTOR))
|
||||
|
||||
if du >= dr and du >= df:
|
||||
uv_out = Vector2(vertex.x, -vertex.y)
|
||||
elif dr >= du and dr >= df:
|
||||
uv_out = Vector2(vertex.x, -vertex.z)
|
||||
elif df >= du and df >= dr:
|
||||
uv_out = Vector2(vertex.y, -vertex.z)
|
||||
|
||||
var angle: float = deg_to_rad(face.uv_extra.rot)
|
||||
uv_out = Vector2(
|
||||
uv_out.x * cos(angle) - uv_out.y * sin(angle),
|
||||
uv_out.x * sin(angle) + uv_out.y * cos(angle))
|
||||
|
||||
uv_out.x /= texture_width
|
||||
uv_out.y /= texture_height
|
||||
|
||||
uv_out.x /= face.uv_extra.scale_x
|
||||
uv_out.y /= face.uv_extra.scale_y
|
||||
|
||||
uv_out.x += face.uv_standard.x / texture_width
|
||||
uv_out.y += face.uv_standard.y / texture_height
|
||||
|
||||
return uv_out
|
||||
|
||||
func get_valve_uv(vertex: Vector3, face: FuncGodotMapData.FuncGodotFace, texture_width: int, texture_height: int) -> Vector2:
|
||||
var uv_out: Vector2
|
||||
var u_axis:= face.uv_valve.u.axis
|
||||
var v_axis:= face.uv_valve.v.axis
|
||||
var u_shift:= face.uv_valve.u.offset
|
||||
var v_shift:= face.uv_valve.v.offset
|
||||
|
||||
uv_out.x = u_axis.dot(vertex);
|
||||
uv_out.y = v_axis.dot(vertex);
|
||||
|
||||
uv_out.x /= texture_width;
|
||||
uv_out.y /= texture_height;
|
||||
|
||||
uv_out.x /= face.uv_extra.scale_x;
|
||||
uv_out.y /= face.uv_extra.scale_y;
|
||||
|
||||
uv_out.x += u_shift / texture_width;
|
||||
uv_out.y += v_shift / texture_height;
|
||||
|
||||
return uv_out
|
||||
|
||||
func get_standard_tangent(face: FuncGodotMapData.FuncGodotFace) -> Vector4:
|
||||
var du:= face.plane_normal.dot(UP_VECTOR)
|
||||
var dr:= face.plane_normal.dot(RIGHT_VECTOR)
|
||||
var df:= face.plane_normal.dot(FORWARD_VECTOR)
|
||||
var dua:= absf(du)
|
||||
var dra:= absf(dr)
|
||||
var dfa:= absf(df)
|
||||
|
||||
var u_axis: Vector3
|
||||
var v_sign: float = 0.0
|
||||
|
||||
if dua >= dra and dua >= dfa:
|
||||
u_axis = FORWARD_VECTOR
|
||||
v_sign = signf(du)
|
||||
elif dra >= dua and dra >= dfa:
|
||||
u_axis = FORWARD_VECTOR
|
||||
v_sign = -signf(dr)
|
||||
elif dfa >= dua and dfa >= dra:
|
||||
u_axis = RIGHT_VECTOR
|
||||
v_sign = signf(df)
|
||||
|
||||
v_sign *= signf(face.uv_extra.scale_y);
|
||||
u_axis = u_axis.rotated(face.plane_normal, deg_to_rad(-face.uv_extra.rot) * v_sign)
|
||||
|
||||
return Vector4(u_axis.x, u_axis.y, u_axis.z, v_sign)
|
||||
|
||||
func get_valve_tangent(face: FuncGodotMapData.FuncGodotFace) -> Vector4:
|
||||
var u_axis:= face.uv_valve.u.axis.normalized()
|
||||
var v_axis:= face.uv_valve.v.axis.normalized()
|
||||
var v_sign = -signf(face.plane_normal.cross(u_axis).dot(v_axis))
|
||||
return Vector4(u_axis.x, u_axis.y, u_axis.z, v_sign)
|
||||
|
||||
func generate_brush_vertices(entity_idx: int, brush_idx: int) -> void:
|
||||
var entity: FuncGodotMapData.FuncGodotEntity = map_data.entities[entity_idx]
|
||||
var brush: FuncGodotMapData.FuncGodotBrush = entity.brushes[brush_idx]
|
||||
var face_count: int = brush.faces.size()
|
||||
|
||||
var entity_geo: FuncGodotMapData.FuncGodotEntityGeometry = map_data.entity_geo[entity_idx]
|
||||
var brush_geo: FuncGodotMapData.FuncGodotBrushGeometry = entity_geo.brushes[brush_idx]
|
||||
|
||||
var phong: bool = entity.properties.get("_phong", "0") == "1"
|
||||
var phong_angle_str: String = entity.properties.get("_phong_angle", "89")
|
||||
var phong_angle: float = float(phong_angle_str) if phong_angle_str.is_valid_float() else 89.0
|
||||
|
||||
for f0 in range(face_count):
|
||||
var face: FuncGodotMapData.FuncGodotFace = brush.faces[f0]
|
||||
var face_geo: FuncGodotMapData.FuncGodotFaceGeometry = brush_geo.faces[f0]
|
||||
var texture: FuncGodotMapData.FuncGodotTextureData = map_data.textures[face.texture_idx]
|
||||
|
||||
for f1 in range(face_count):
|
||||
for f2 in range(face_count):
|
||||
var vertex = intersect_face(brush.faces[f0], brush.faces[f1], brush.faces[f2])
|
||||
if not vertex is Vector3:
|
||||
continue
|
||||
if not vertex_in_hull(brush.faces, vertex):
|
||||
continue
|
||||
|
||||
var merged: bool = false
|
||||
for f3 in range(f0):
|
||||
var other_face_geo : FuncGodotMapData.FuncGodotFaceGeometry = brush_geo.faces[f3]
|
||||
for i in range(len(other_face_geo.vertices)):
|
||||
if other_face_geo.vertices[i].vertex.distance_to(vertex) < CMP_EPSILON:
|
||||
vertex = other_face_geo.vertices[i].vertex
|
||||
merged = true;
|
||||
break
|
||||
|
||||
if merged:
|
||||
break
|
||||
|
||||
var normal: Vector3 = face.plane_normal
|
||||
if phong:
|
||||
var threshold:= cos((phong_angle + 0.01) * 0.0174533)
|
||||
if face.plane_normal.dot(brush.faces[f1].plane_normal) > threshold:
|
||||
normal += brush.faces[f1].plane_normal
|
||||
if face.plane_normal.dot(brush.faces[f2].plane_normal) > threshold:
|
||||
normal += brush.faces[f2].plane_normal
|
||||
normal = normal.normalized()
|
||||
|
||||
var uv: Vector2
|
||||
var tangent: Vector4
|
||||
if face.is_valve_uv:
|
||||
uv = get_valve_uv(vertex, face, texture.width, texture.height)
|
||||
tangent = get_valve_tangent(face)
|
||||
else:
|
||||
uv = get_standard_uv(vertex, face, texture.width, texture.height)
|
||||
tangent = get_standard_tangent(face)
|
||||
|
||||
# Check for a duplicate vertex in the current face.
|
||||
var duplicate_idx: int = -1
|
||||
for i in range(face_geo.vertices.size()):
|
||||
if face_geo.vertices[i].vertex == vertex:
|
||||
duplicate_idx = i
|
||||
break
|
||||
|
||||
if duplicate_idx < 0:
|
||||
var new_face_vert:= FuncGodotMapData.FuncGodotFaceVertex.new()
|
||||
new_face_vert.vertex = vertex
|
||||
new_face_vert.normal = normal
|
||||
new_face_vert.tangent = tangent
|
||||
new_face_vert.uv = uv
|
||||
face_geo.vertices.append(new_face_vert)
|
||||
elif phong:
|
||||
face_geo.vertices[duplicate_idx].normal += normal
|
||||
|
||||
# maybe optimisable?
|
||||
for face_geo in brush_geo.faces:
|
||||
for i in range(face_geo.vertices.size()):
|
||||
face_geo.vertices[i].normal = face_geo.vertices[i].normal.normalized()
|
||||
|
||||
func run() -> void:
|
||||
map_data.entity_geo.resize(map_data.entities.size())
|
||||
for i in range(map_data.entity_geo.size()):
|
||||
map_data.entity_geo[i] = FuncGodotMapData.FuncGodotEntityGeometry.new()
|
||||
|
||||
for e in range(map_data.entities.size()):
|
||||
var entity: FuncGodotMapData.FuncGodotEntity = map_data.entities[e]
|
||||
var entity_geo: FuncGodotMapData.FuncGodotEntityGeometry = map_data.entity_geo[e]
|
||||
entity_geo.brushes.resize(entity.brushes.size())
|
||||
for i in range(entity_geo.brushes.size()):
|
||||
entity_geo.brushes[i] = FuncGodotMapData.FuncGodotBrushGeometry.new()
|
||||
|
||||
for b in range(entity.brushes.size()):
|
||||
var brush: FuncGodotMapData.FuncGodotBrush = entity.brushes[b]
|
||||
var brush_geo: FuncGodotMapData.FuncGodotBrushGeometry = entity_geo.brushes[b]
|
||||
brush_geo.faces.resize(brush.faces.size())
|
||||
for i in range(brush_geo.faces.size()):
|
||||
brush_geo.faces[i] = FuncGodotMapData.FuncGodotFaceGeometry.new()
|
||||
|
||||
var generate_vertices_task = func(e):
|
||||
var entity: FuncGodotMapData.FuncGodotEntity = map_data.entities[e]
|
||||
var entity_geo: FuncGodotMapData.FuncGodotEntityGeometry = map_data.entity_geo[e]
|
||||
var entity_mins: Vector3 = Vector3.INF
|
||||
var entity_maxs: Vector3 = Vector3.INF
|
||||
var origin_mins: Vector3 = Vector3.INF
|
||||
var origin_maxs: Vector3 = -Vector3.INF
|
||||
|
||||
entity.center = Vector3.ZERO
|
||||
|
||||
for b in range(entity.brushes.size()):
|
||||
var brush: FuncGodotMapData.FuncGodotBrush = entity.brushes[b]
|
||||
brush.center = Vector3.ZERO
|
||||
var vert_count: int = 0
|
||||
|
||||
# Check if this is a special brush (eg: origin)
|
||||
var brush_texture_type: FuncGodotMapData.FuncGodotTextureType = FuncGodotMapData.FuncGodotTextureType.NORMAL
|
||||
if brush.faces.size() > 0:
|
||||
brush_texture_type = map_data.textures[brush.faces[0].texture_idx].type
|
||||
|
||||
# Check that all the faces match the same type
|
||||
for face_idx in range(1,brush.faces.size()):
|
||||
if map_data.textures[brush.faces[face_idx].texture_idx].type != brush_texture_type:
|
||||
brush_texture_type = FuncGodotMapData.FuncGodotTextureType.NORMAL # Reset face type if it doesn't match
|
||||
break
|
||||
|
||||
generate_brush_vertices(e, b)
|
||||
|
||||
var brush_geo: FuncGodotMapData.FuncGodotBrushGeometry = map_data.entity_geo[e].brushes[b]
|
||||
for face in brush_geo.faces:
|
||||
for vert in face.vertices:
|
||||
if entity_mins != Vector3.INF:
|
||||
entity_mins = entity_mins.min(vert.vertex)
|
||||
else:
|
||||
entity_mins = vert.vertex
|
||||
if entity_maxs != Vector3.INF:
|
||||
entity_maxs = entity_maxs.max(vert.vertex)
|
||||
else:
|
||||
entity_maxs = vert.vertex
|
||||
|
||||
if brush_texture_type == FuncGodotMapData.FuncGodotTextureType.ORIGIN:
|
||||
if origin_mins != Vector3.INF:
|
||||
origin_mins = origin_mins.min(vert.vertex)
|
||||
else:
|
||||
origin_mins = vert.vertex
|
||||
if origin_maxs != Vector3.INF:
|
||||
origin_maxs = origin_maxs.max(vert.vertex)
|
||||
else:
|
||||
origin_maxs = vert.vertex
|
||||
|
||||
brush.center += vert.vertex
|
||||
vert_count += 1
|
||||
|
||||
if vert_count > 0:
|
||||
brush.center /= float(vert_count)
|
||||
|
||||
# Default origin type is BOUNDS_CENTER
|
||||
if entity_maxs != Vector3.INF and entity_mins != Vector3.INF:
|
||||
entity.center = entity_maxs - ((entity_maxs - entity_mins) * 0.5)
|
||||
|
||||
if entity.origin_type != FuncGodotMapData.FuncGodotEntityOriginType.BOUNDS_CENTER and entity.brushes.size() > 0:
|
||||
match entity.origin_type:
|
||||
FuncGodotMapData.FuncGodotEntityOriginType.ABSOLUTE, FuncGodotMapData.FuncGodotEntityOriginType.RELATIVE:
|
||||
if 'origin' in entity.properties:
|
||||
var origin_comps: PackedFloat64Array = entity.properties['origin'].split_floats(' ')
|
||||
if origin_comps.size() > 2:
|
||||
if entity.origin_type == FuncGodotMapData.FuncGodotEntityOriginType.ABSOLUTE:
|
||||
entity.center = Vector3(origin_comps[0], origin_comps[1], origin_comps[2])
|
||||
else: # OriginType.RELATIVE
|
||||
entity.center += Vector3(origin_comps[0], origin_comps[1], origin_comps[2])
|
||||
|
||||
FuncGodotMapData.FuncGodotEntityOriginType.BRUSH:
|
||||
if origin_mins != Vector3.INF:
|
||||
entity.center = origin_maxs - ((origin_maxs - origin_mins) * 0.5)
|
||||
|
||||
FuncGodotMapData.FuncGodotEntityOriginType.BOUNDS_MINS:
|
||||
entity.center = entity_mins
|
||||
|
||||
FuncGodotMapData.FuncGodotEntityOriginType.BOUNDS_MAXS:
|
||||
entity.center = entity_maxs
|
||||
|
||||
FuncGodotMapData.FuncGodotEntityOriginType.AVERAGED:
|
||||
entity.center = Vector3.ZERO
|
||||
for b in range(entity.brushes.size()):
|
||||
entity.center += entity.brushes[b].center
|
||||
entity.center /= float(entity.brushes.size())
|
||||
|
||||
var generate_vertices_task_id:= WorkerThreadPool.add_group_task(generate_vertices_task, map_data.entities.size(), 4, true)
|
||||
WorkerThreadPool.wait_for_group_task_completion(generate_vertices_task_id)
|
||||
|
||||
# wind face vertices
|
||||
for e in range(map_data.entities.size()):
|
||||
var entity: FuncGodotMapData.FuncGodotEntity = map_data.entities[e]
|
||||
var entity_geo: FuncGodotMapData.FuncGodotEntityGeometry = map_data.entity_geo[e]
|
||||
|
||||
for b in range(entity.brushes.size()):
|
||||
var brush: FuncGodotMapData.FuncGodotBrush = entity.brushes[b]
|
||||
var brush_geo: FuncGodotMapData.FuncGodotBrushGeometry = entity_geo.brushes[b]
|
||||
|
||||
for f in range(brush.faces.size()):
|
||||
var face: FuncGodotMapData.FuncGodotFace = brush.faces[f]
|
||||
var face_geo: FuncGodotMapData.FuncGodotFaceGeometry = brush_geo.faces[f]
|
||||
|
||||
if face_geo.vertices.size() < 3:
|
||||
continue
|
||||
|
||||
wind_entity_idx = e
|
||||
wind_brush_idx = b
|
||||
wind_face_idx = f
|
||||
|
||||
wind_face_basis = face_geo.vertices[1].vertex - face_geo.vertices[0].vertex
|
||||
wind_face_center = Vector3.ZERO
|
||||
wind_face_normal = face.plane_normal
|
||||
|
||||
for v in face_geo.vertices:
|
||||
wind_face_center += v.vertex
|
||||
|
||||
wind_face_center /= face_geo.vertices.size()
|
||||
|
||||
face_geo.vertices.sort_custom(sort_vertices_by_winding)
|
||||
wind_entity_idx = 0
|
||||
|
||||
# index face vertices
|
||||
var index_faces_task:= func(e):
|
||||
var entity_geo: FuncGodotMapData.FuncGodotEntityGeometry = map_data.entity_geo[e]
|
||||
|
||||
for b in range(entity_geo.brushes.size()):
|
||||
var brush_geo: FuncGodotMapData.FuncGodotBrushGeometry = entity_geo.brushes[b]
|
||||
|
||||
for f in range(brush_geo.faces.size()):
|
||||
var face_geo: FuncGodotMapData.FuncGodotFaceGeometry = brush_geo.faces[f]
|
||||
|
||||
if face_geo.vertices.size() < 3:
|
||||
continue
|
||||
|
||||
var i_count: int = 0
|
||||
face_geo.indicies.resize((face_geo.vertices.size() - 2) * 3)
|
||||
for i in range(face_geo.vertices.size() - 2):
|
||||
face_geo.indicies[i_count] = 0
|
||||
face_geo.indicies[i_count + 1] = i + 1
|
||||
face_geo.indicies[i_count + 2] = i + 2
|
||||
i_count += 3
|
||||
|
||||
var index_faces_task_id:= WorkerThreadPool.add_group_task(index_faces_task, map_data.entities.size(), 4, true)
|
||||
WorkerThreadPool.wait_for_group_task_completion(index_faces_task_id)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cb0c2fn35hqov
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
class_name FuncGodotMapData extends RefCounted
|
||||
|
||||
var entities: Array[FuncGodotMapData.FuncGodotEntity]
|
||||
var entity_geo: Array[FuncGodotMapData.FuncGodotEntityGeometry]
|
||||
var textures: Array[FuncGodotMapData.FuncGodotTextureData]
|
||||
|
||||
func register_texture(name: String) -> int:
|
||||
for i in range(textures.size()):
|
||||
if textures[i].name == name:
|
||||
return i
|
||||
|
||||
textures.append(FuncGodotTextureData.new(name))
|
||||
return textures.size() - 1
|
||||
|
||||
func set_texture_info(name: String, width: int, height: int, type: FuncGodotTextureType) -> void:
|
||||
for i in range(textures.size()):
|
||||
if textures[i].name == name:
|
||||
textures[i].width = width
|
||||
textures[i].height = height
|
||||
textures[i].type = type
|
||||
return
|
||||
|
||||
func find_texture(texture_name: String) -> int:
|
||||
for i in range(textures.size()):
|
||||
if textures[i].name == texture_name:
|
||||
return i
|
||||
return -1
|
||||
|
||||
func set_entity_types_by_classname(classname: String, spawn_type: int, origin_type: int, meta_flags: int) -> void:
|
||||
for entity in entities:
|
||||
if entity.properties.has("classname") and entity.properties["classname"] == classname:
|
||||
entity.metadata_inclusion_flags = meta_flags as FuncGodotMapData.FuncGodotEntityMetadataInclusionFlags
|
||||
entity.spawn_type = spawn_type as FuncGodotMapData.FuncGodotEntitySpawnType
|
||||
if entity.spawn_type == FuncGodotMapData.FuncGodotEntitySpawnType.ENTITY:
|
||||
entity.origin_type = origin_type as FuncGodotMapData.FuncGodotEntityOriginType
|
||||
else:
|
||||
entity.origin_type = FuncGodotMapData.FuncGodotEntityOriginType.AVERAGED
|
||||
|
||||
func clear() -> void:
|
||||
entities.clear()
|
||||
entity_geo.clear()
|
||||
textures.clear()
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
# Nested Types
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
enum FuncGodotEntitySpawnType {
|
||||
WORLDSPAWN = 0,
|
||||
MERGE_WORLDSPAWN = 1,
|
||||
ENTITY = 2
|
||||
}
|
||||
|
||||
enum FuncGodotEntityOriginType {
|
||||
AVERAGED = 0,
|
||||
ABSOLUTE = 1,
|
||||
RELATIVE = 2,
|
||||
BRUSH = 3,
|
||||
BOUNDS_CENTER = 4,
|
||||
BOUNDS_MINS = 5,
|
||||
BOUNDS_MAXS = 6,
|
||||
}
|
||||
|
||||
enum FuncGodotEntityMetadataInclusionFlags {
|
||||
NONE = 0,
|
||||
ENTITY_INDEX_RANGES = 1,
|
||||
TEXTURES = 2,
|
||||
VERTEX = 4,
|
||||
FACE_POSITION = 8,
|
||||
FACE_NORMAL = 16,
|
||||
COLLISION_SHAPE_TO_FACE_RANGE_MAP = 32,
|
||||
}
|
||||
|
||||
enum FuncGodotTextureType {
|
||||
NORMAL = 0,
|
||||
ORIGIN = 1
|
||||
}
|
||||
|
||||
class FuncGodotFacePoints:
|
||||
var v0: Vector3
|
||||
var v1: Vector3
|
||||
var v2: Vector3
|
||||
|
||||
class FuncGodotValveTextureAxis:
|
||||
var axis: Vector3
|
||||
var offset: float
|
||||
|
||||
class FuncGodotValveUV:
|
||||
var u: FuncGodotValveTextureAxis
|
||||
var v: FuncGodotValveTextureAxis
|
||||
|
||||
func _init() -> void:
|
||||
u = FuncGodotValveTextureAxis.new()
|
||||
v = FuncGodotValveTextureAxis.new()
|
||||
|
||||
class FuncGodotFaceUVExtra:
|
||||
var rot: float
|
||||
var scale_x: float
|
||||
var scale_y: float
|
||||
|
||||
class FuncGodotFace:
|
||||
var plane_points: FuncGodotFacePoints
|
||||
var plane_normal: Vector3
|
||||
var plane_dist: float
|
||||
var texture_idx: int
|
||||
var is_valve_uv: bool
|
||||
var uv_standard: Vector2
|
||||
var uv_valve: FuncGodotValveUV
|
||||
var uv_extra: FuncGodotFaceUVExtra
|
||||
|
||||
func _init() -> void:
|
||||
plane_points = FuncGodotFacePoints.new()
|
||||
uv_valve = FuncGodotValveUV.new()
|
||||
uv_extra = FuncGodotFaceUVExtra.new()
|
||||
|
||||
class FuncGodotBrush:
|
||||
var faces: Array[FuncGodotFace]
|
||||
var center: Vector3
|
||||
|
||||
class FuncGodotEntity:
|
||||
var properties: Dictionary
|
||||
var brushes: Array[FuncGodotBrush]
|
||||
var center: Vector3
|
||||
var spawn_type: FuncGodotEntitySpawnType
|
||||
var origin_type: FuncGodotEntityOriginType
|
||||
var metadata_inclusion_flags: FuncGodotEntityMetadataInclusionFlags
|
||||
|
||||
class FuncGodotFaceVertex:
|
||||
var vertex: Vector3
|
||||
var normal: Vector3
|
||||
var uv: Vector2
|
||||
var tangent: Vector4
|
||||
|
||||
func duplicate() -> FuncGodotFaceVertex:
|
||||
var new_vert := FuncGodotFaceVertex.new()
|
||||
new_vert.vertex = vertex
|
||||
new_vert.normal = normal
|
||||
new_vert.uv = uv
|
||||
new_vert.tangent = tangent
|
||||
return new_vert
|
||||
|
||||
class FuncGodotFaceGeometry:
|
||||
var vertices: Array[FuncGodotFaceVertex]
|
||||
var indicies: Array[int]
|
||||
|
||||
class FuncGodotBrushGeometry:
|
||||
var faces: Array[FuncGodotFaceGeometry]
|
||||
|
||||
class FuncGodotEntityGeometry:
|
||||
var brushes: Array[FuncGodotBrushGeometry]
|
||||
|
||||
class FuncGodotTextureData:
|
||||
var name: String
|
||||
var width: int
|
||||
var height: int
|
||||
var type: FuncGodotTextureType
|
||||
|
||||
func _init(in_name: String):
|
||||
name = in_name
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://ct3rx5npjd00s
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
class_name FuncGodotMapParser extends RefCounted
|
||||
|
||||
var scope:= FuncGodotMapParser.ParseScope.FILE
|
||||
var comment: bool = false
|
||||
var entity_idx: int = -1
|
||||
var brush_idx: int = -1
|
||||
var face_idx: int = -1
|
||||
var component_idx: int = 0
|
||||
var prop_key: String = ""
|
||||
var current_property: String = ""
|
||||
var valve_uvs: bool = false
|
||||
|
||||
var current_face: FuncGodotMapData.FuncGodotFace
|
||||
var current_brush: FuncGodotMapData.FuncGodotBrush
|
||||
var current_entity: FuncGodotMapData.FuncGodotEntity
|
||||
|
||||
var map_data: FuncGodotMapData
|
||||
var _keep_tb_groups: bool = false
|
||||
|
||||
func _init(in_map_data: FuncGodotMapData) -> void:
|
||||
map_data = in_map_data
|
||||
|
||||
func load_map(map_file: String, keep_tb_groups: bool) -> bool:
|
||||
current_face = FuncGodotMapData.FuncGodotFace.new()
|
||||
current_brush = FuncGodotMapData.FuncGodotBrush.new()
|
||||
current_entity = FuncGodotMapData.FuncGodotEntity.new()
|
||||
|
||||
scope = FuncGodotMapParser.ParseScope.FILE
|
||||
comment = false
|
||||
entity_idx = -1
|
||||
brush_idx = -1
|
||||
face_idx = -1
|
||||
component_idx = 0
|
||||
valve_uvs = false
|
||||
_keep_tb_groups = keep_tb_groups
|
||||
|
||||
var lines: PackedStringArray = []
|
||||
|
||||
var map: FileAccess = FileAccess.open(map_file, FileAccess.READ)
|
||||
|
||||
if map == null:
|
||||
printerr("Error: Failed to open map file (" + map_file + ")")
|
||||
return false
|
||||
|
||||
if map_file.ends_with(".import"):
|
||||
while not map.eof_reached():
|
||||
var line: String = map.get_line()
|
||||
if line.begins_with("path"):
|
||||
map.close()
|
||||
line = line.replace("path=", "");
|
||||
line = line.replace('"', '')
|
||||
var map_data: String = (load(line) as QuakeMapFile).map_data
|
||||
if map_data.is_empty():
|
||||
printerr("Error: Failed to open map file (" + line + ")")
|
||||
return false
|
||||
lines = map_data.split("\n")
|
||||
break
|
||||
else:
|
||||
while not map.eof_reached():
|
||||
var line: String = map.get_line()
|
||||
lines.append(line)
|
||||
|
||||
for line in lines:
|
||||
if comment:
|
||||
comment = false
|
||||
var tokens := split_string(line, [" ", "\t"], true)
|
||||
for s in tokens:
|
||||
token(s)
|
||||
|
||||
return true
|
||||
|
||||
func split_string(s: String, delimeters: Array[String], allow_empty: bool = true) -> Array[String]:
|
||||
var parts: Array[String] = []
|
||||
|
||||
var start := 0
|
||||
var i := 0
|
||||
|
||||
while i < s.length():
|
||||
if s[i] in delimeters:
|
||||
if allow_empty or start < i:
|
||||
parts.push_back(s.substr(start, i - start))
|
||||
start = i + 1
|
||||
i += 1
|
||||
|
||||
if allow_empty or start < i:
|
||||
parts.push_back(s.substr(start, i - start))
|
||||
|
||||
return parts
|
||||
|
||||
func set_scope(new_scope: FuncGodotMapParser.ParseScope) -> void:
|
||||
"""
|
||||
match new_scope:
|
||||
ParseScope.FILE:
|
||||
print("Switching to file scope.")
|
||||
ParseScope.ENTITY:
|
||||
print("Switching to entity " + str(entity_idx) + "scope")
|
||||
ParseScope.PROPERTY_VALUE:
|
||||
print("Switching to property value scope")
|
||||
ParseScope.BRUSH:
|
||||
print("Switching to brush " + str(brush_idx) + " scope")
|
||||
ParseScope.PLANE_0:
|
||||
print("Switching to face " + str(face_idx) + " plane 0 scope")
|
||||
ParseScope.PLANE_1:
|
||||
print("Switching to face " + str(face_idx) + " plane 1 scope")
|
||||
ParseScope.PLANE_2:
|
||||
print("Switching to face " + str(face_idx) + " plane 2 scope")
|
||||
ParseScope.TEXTURE:
|
||||
print("Switching to texture scope")
|
||||
ParseScope.U:
|
||||
print("Switching to U scope")
|
||||
ParseScope.V:
|
||||
print("Switching to V scope")
|
||||
ParseScope.VALVE_U:
|
||||
print("Switching to Valve U scope")
|
||||
ParseScope.VALVE_V:
|
||||
print("Switching to Valve V scope")
|
||||
ParseScope.ROT:
|
||||
print("Switching to rotation scope")
|
||||
ParseScope.U_SCALE:
|
||||
print("Switching to U scale scope")
|
||||
ParseScope.V_SCALE:
|
||||
print("Switching to V scale scope")
|
||||
"""
|
||||
scope = new_scope
|
||||
|
||||
func token(buf_str: String) -> void:
|
||||
if comment:
|
||||
return
|
||||
elif buf_str == "//":
|
||||
comment = true
|
||||
return
|
||||
|
||||
match scope:
|
||||
FuncGodotMapParser.ParseScope.FILE:
|
||||
if buf_str == "{":
|
||||
entity_idx += 1
|
||||
brush_idx = -1
|
||||
set_scope(FuncGodotMapParser.ParseScope.ENTITY)
|
||||
FuncGodotMapParser.ParseScope.ENTITY:
|
||||
if buf_str.begins_with('"'):
|
||||
prop_key = buf_str.substr(1)
|
||||
if prop_key.ends_with('"'):
|
||||
prop_key = prop_key.left(-1)
|
||||
set_scope(FuncGodotMapParser.ParseScope.PROPERTY_VALUE)
|
||||
elif buf_str == "{":
|
||||
brush_idx += 1
|
||||
face_idx = -1
|
||||
set_scope(FuncGodotMapParser.ParseScope.BRUSH)
|
||||
elif buf_str == "}":
|
||||
commit_entity()
|
||||
set_scope(FuncGodotMapParser.ParseScope.FILE)
|
||||
FuncGodotMapParser.ParseScope.PROPERTY_VALUE:
|
||||
var is_first = buf_str[0] == '"'
|
||||
var is_last = buf_str.right(1) == '"'
|
||||
|
||||
if is_first:
|
||||
if current_property != "":
|
||||
current_property = ""
|
||||
|
||||
if not is_last:
|
||||
current_property += buf_str + " "
|
||||
else:
|
||||
current_property += buf_str
|
||||
|
||||
if is_last:
|
||||
current_entity.properties[prop_key] = current_property.substr(1, len(current_property) - 2)
|
||||
set_scope(FuncGodotMapParser.ParseScope.ENTITY)
|
||||
FuncGodotMapParser.ParseScope.BRUSH:
|
||||
if buf_str == "(":
|
||||
face_idx += 1
|
||||
component_idx = 0
|
||||
set_scope(FuncGodotMapParser.ParseScope.PLANE_0)
|
||||
elif buf_str == "}":
|
||||
commit_brush()
|
||||
set_scope(FuncGodotMapParser.ParseScope.ENTITY)
|
||||
FuncGodotMapParser.ParseScope.PLANE_0:
|
||||
if buf_str == ")":
|
||||
component_idx = 0
|
||||
set_scope(FuncGodotMapParser.ParseScope.PLANE_1)
|
||||
else:
|
||||
match component_idx:
|
||||
0:
|
||||
current_face.plane_points.v0.x = float(buf_str)
|
||||
1:
|
||||
current_face.plane_points.v0.y = float(buf_str)
|
||||
2:
|
||||
current_face.plane_points.v0.z = float(buf_str)
|
||||
|
||||
component_idx += 1
|
||||
FuncGodotMapParser.ParseScope.PLANE_1:
|
||||
if buf_str != "(":
|
||||
if buf_str == ")":
|
||||
component_idx = 0
|
||||
set_scope(FuncGodotMapParser.ParseScope.PLANE_2)
|
||||
else:
|
||||
match component_idx:
|
||||
0:
|
||||
current_face.plane_points.v1.x = float(buf_str)
|
||||
1:
|
||||
current_face.plane_points.v1.y = float(buf_str)
|
||||
2:
|
||||
current_face.plane_points.v1.z = float(buf_str)
|
||||
|
||||
component_idx += 1
|
||||
FuncGodotMapParser.ParseScope.PLANE_2:
|
||||
if buf_str != "(":
|
||||
if buf_str == ")":
|
||||
component_idx = 0
|
||||
set_scope(FuncGodotMapParser.ParseScope.TEXTURE)
|
||||
else:
|
||||
match component_idx:
|
||||
0:
|
||||
current_face.plane_points.v2.x = float(buf_str)
|
||||
1:
|
||||
current_face.plane_points.v2.y = float(buf_str)
|
||||
2:
|
||||
current_face.plane_points.v2.z = float(buf_str)
|
||||
|
||||
component_idx += 1
|
||||
FuncGodotMapParser.ParseScope.TEXTURE:
|
||||
current_face.texture_idx = map_data.register_texture(buf_str)
|
||||
set_scope(FuncGodotMapParser.ParseScope.U)
|
||||
FuncGodotMapParser.ParseScope.U:
|
||||
if buf_str == "[":
|
||||
valve_uvs = true
|
||||
component_idx = 0
|
||||
set_scope(FuncGodotMapParser.ParseScope.VALVE_U)
|
||||
else:
|
||||
valve_uvs = false
|
||||
current_face.uv_standard.x = float(buf_str)
|
||||
set_scope(FuncGodotMapParser.ParseScope.V)
|
||||
FuncGodotMapParser.ParseScope.V:
|
||||
current_face.uv_standard.y = float(buf_str)
|
||||
set_scope(FuncGodotMapParser.ParseScope.ROT)
|
||||
FuncGodotMapParser.ParseScope.VALVE_U:
|
||||
if buf_str == "]":
|
||||
component_idx = 0
|
||||
set_scope(FuncGodotMapParser.ParseScope.VALVE_V)
|
||||
else:
|
||||
match component_idx:
|
||||
0:
|
||||
current_face.uv_valve.u.axis.x = float(buf_str)
|
||||
1:
|
||||
current_face.uv_valve.u.axis.y = float(buf_str)
|
||||
2:
|
||||
current_face.uv_valve.u.axis.z = float(buf_str)
|
||||
3:
|
||||
current_face.uv_valve.u.offset = float(buf_str)
|
||||
|
||||
component_idx += 1
|
||||
FuncGodotMapParser.ParseScope.VALVE_V:
|
||||
if buf_str != "[":
|
||||
if buf_str == "]":
|
||||
set_scope(FuncGodotMapParser.ParseScope.ROT)
|
||||
else:
|
||||
match component_idx:
|
||||
0:
|
||||
current_face.uv_valve.v.axis.x = float(buf_str)
|
||||
1:
|
||||
current_face.uv_valve.v.axis.y = float(buf_str)
|
||||
2:
|
||||
current_face.uv_valve.v.axis.z = float(buf_str)
|
||||
3:
|
||||
current_face.uv_valve.v.offset = float(buf_str)
|
||||
|
||||
component_idx += 1
|
||||
FuncGodotMapParser.ParseScope.ROT:
|
||||
current_face.uv_extra.rot = float(buf_str)
|
||||
set_scope(FuncGodotMapParser.ParseScope.U_SCALE)
|
||||
FuncGodotMapParser.ParseScope.U_SCALE:
|
||||
current_face.uv_extra.scale_x = float(buf_str)
|
||||
set_scope(FuncGodotMapParser.ParseScope.V_SCALE)
|
||||
FuncGodotMapParser.ParseScope.V_SCALE:
|
||||
current_face.uv_extra.scale_y = float(buf_str)
|
||||
commit_face()
|
||||
set_scope(FuncGodotMapParser.ParseScope.BRUSH)
|
||||
|
||||
func commit_entity() -> void:
|
||||
if current_entity.properties.has('_tb_type') and map_data.entities.size() > 0:
|
||||
map_data.entities[0].brushes.append_array(current_entity.brushes)
|
||||
current_entity.brushes.clear()
|
||||
if !_keep_tb_groups:
|
||||
current_entity = FuncGodotMapData.FuncGodotEntity.new()
|
||||
return
|
||||
|
||||
var new_entity:= FuncGodotMapData.FuncGodotEntity.new()
|
||||
new_entity.spawn_type = FuncGodotMapData.FuncGodotEntitySpawnType.ENTITY
|
||||
new_entity.properties = current_entity.properties
|
||||
new_entity.brushes = current_entity.brushes
|
||||
map_data.entities.append(new_entity)
|
||||
|
||||
current_entity = FuncGodotMapData.FuncGodotEntity.new()
|
||||
|
||||
func commit_brush() -> void:
|
||||
current_entity.brushes.append(current_brush)
|
||||
current_brush = FuncGodotMapData.FuncGodotBrush.new()
|
||||
|
||||
func commit_face() -> void:
|
||||
var v0v1: Vector3 = current_face.plane_points.v1 - current_face.plane_points.v0
|
||||
var v1v2: Vector3 = current_face.plane_points.v2 - current_face.plane_points.v1
|
||||
current_face.plane_normal = v1v2.cross(v0v1).normalized()
|
||||
current_face.plane_dist = current_face.plane_normal.dot(current_face.plane_points.v0)
|
||||
current_face.is_valve_uv = valve_uvs
|
||||
|
||||
current_brush.faces.append(current_face)
|
||||
current_face = FuncGodotMapData.FuncGodotFace.new()
|
||||
|
||||
# Nested
|
||||
enum ParseScope{
|
||||
FILE,
|
||||
COMMENT,
|
||||
ENTITY,
|
||||
PROPERTY_VALUE,
|
||||
BRUSH,
|
||||
PLANE_0,
|
||||
PLANE_1,
|
||||
PLANE_2,
|
||||
TEXTURE,
|
||||
U,
|
||||
V,
|
||||
VALVE_U,
|
||||
VALVE_V,
|
||||
ROT,
|
||||
U_SCALE,
|
||||
V_SCALE
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cg2iiom3svtw0
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
class_name FuncGodotSurfaceGatherer extends RefCounted
|
||||
|
||||
var map_data: FuncGodotMapData
|
||||
var map_settings: FuncGodotMapSettings
|
||||
var split_type: SurfaceSplitType = SurfaceSplitType.NONE
|
||||
var entity_filter_idx: int = -1
|
||||
var texture_filter_idx: int = -1
|
||||
var clip_filter_texture_idx: int
|
||||
var skip_filter_texture_idx: int
|
||||
var origin_filter_texture_idx: int
|
||||
var metadata_skip_flags: int
|
||||
|
||||
var out_surfaces: Array[FuncGodotMapData.FuncGodotFaceGeometry]
|
||||
var out_metadata: Dictionary
|
||||
|
||||
func _init(in_map_data: FuncGodotMapData, in_map_settings: FuncGodotMapSettings) -> void:
|
||||
map_data = in_map_data
|
||||
map_settings = in_map_settings
|
||||
|
||||
func set_texture_filter(texture_name: String) -> void:
|
||||
texture_filter_idx = map_data.find_texture(texture_name)
|
||||
|
||||
func set_clip_filter_texture(texture_name: String) -> void:
|
||||
clip_filter_texture_idx = map_data.find_texture(texture_name)
|
||||
|
||||
func set_skip_filter_texture(texture_name: String) -> void:
|
||||
skip_filter_texture_idx = map_data.find_texture(texture_name)
|
||||
|
||||
func set_origin_filter_texture(texture_name: String) -> void:
|
||||
origin_filter_texture_idx = map_data.find_texture(texture_name)
|
||||
|
||||
func filter_entity(entity_idx: int) -> bool:
|
||||
if entity_filter_idx != -1 and entity_idx != entity_filter_idx:
|
||||
return true
|
||||
return false
|
||||
|
||||
func filter_face(entity_idx: int, brush_idx: int, face_idx: int) -> bool:
|
||||
var face: FuncGodotMapData.FuncGodotFace = map_data.entities[entity_idx].brushes[brush_idx].faces[face_idx]
|
||||
var face_geo: FuncGodotMapData.FuncGodotFaceGeometry = map_data.entity_geo[entity_idx].brushes[brush_idx].faces[face_idx]
|
||||
|
||||
if face_geo.vertices.size() < 3:
|
||||
return true
|
||||
|
||||
# Omit faces textured with Clip
|
||||
if clip_filter_texture_idx != -1 and face.texture_idx == clip_filter_texture_idx:
|
||||
return true
|
||||
|
||||
# Omit faces textured with Skip
|
||||
if skip_filter_texture_idx != -1 and face.texture_idx == skip_filter_texture_idx:
|
||||
return true
|
||||
|
||||
# Omit faces textured with Origin
|
||||
if origin_filter_texture_idx != -1 and face.texture_idx == origin_filter_texture_idx:
|
||||
return true
|
||||
|
||||
# Omit filtered texture indices
|
||||
if texture_filter_idx != -1 and face.texture_idx != texture_filter_idx:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
func run() -> void:
|
||||
out_surfaces.clear()
|
||||
var texture_names: Array[StringName] = []
|
||||
var textures: PackedInt32Array = []
|
||||
var vertices: PackedVector3Array = []
|
||||
var positions: PackedVector3Array = []
|
||||
var normals: PackedVector3Array = []
|
||||
var shape_index_ranges: Array[Vector2i] = []
|
||||
var entity_index_ranges: Array[Vector2i] = []
|
||||
|
||||
var index_offset: int = 0
|
||||
var entity_face_range: Vector2i = Vector2i.ZERO
|
||||
const MFlags = FuncGodotMapData.FuncGodotEntityMetadataInclusionFlags
|
||||
var build_entity_index_ranges: bool = not metadata_skip_flags & MFlags.ENTITY_INDEX_RANGES
|
||||
var surf: FuncGodotMapData.FuncGodotFaceGeometry
|
||||
|
||||
if split_type == SurfaceSplitType.NONE:
|
||||
surf = add_surface()
|
||||
index_offset = len(out_surfaces) - 1
|
||||
|
||||
for e in range(map_data.entities.size()):
|
||||
var entity:= map_data.entities[e]
|
||||
var entity_geo:= map_data.entity_geo[e]
|
||||
var shape_face_range := Vector2i.ZERO
|
||||
var total_entity_tris := 0
|
||||
var include_normals_metadata: bool = not metadata_skip_flags & MFlags.FACE_NORMAL and entity.metadata_inclusion_flags & MFlags.FACE_NORMAL
|
||||
var include_vertices_metadata: bool = not metadata_skip_flags & MFlags.VERTEX and entity.metadata_inclusion_flags & MFlags.VERTEX
|
||||
var include_textures_metadata: bool = not metadata_skip_flags & MFlags.TEXTURES and entity.metadata_inclusion_flags & MFlags.TEXTURES
|
||||
var include_positions_metadata: bool = not metadata_skip_flags & MFlags.FACE_POSITION and entity.metadata_inclusion_flags & MFlags.FACE_POSITION
|
||||
var include_shape_range_metadata: bool = not metadata_skip_flags & MFlags.COLLISION_SHAPE_TO_FACE_RANGE_MAP and entity.metadata_inclusion_flags & MFlags.COLLISION_SHAPE_TO_FACE_RANGE_MAP
|
||||
|
||||
if filter_entity(e):
|
||||
continue
|
||||
|
||||
if split_type == SurfaceSplitType.ENTITY:
|
||||
if entity.spawn_type == FuncGodotMapData.FuncGodotEntitySpawnType.MERGE_WORLDSPAWN:
|
||||
add_surface()
|
||||
surf = out_surfaces[0]
|
||||
index_offset = surf.vertices.size()
|
||||
else:
|
||||
surf = add_surface()
|
||||
index_offset = surf.vertices.size()
|
||||
|
||||
for b in range(entity.brushes.size()):
|
||||
var brush:= entity.brushes[b]
|
||||
var brush_geo:= entity_geo.brushes[b]
|
||||
var total_brush_tris:= 0
|
||||
|
||||
if split_type == SurfaceSplitType.BRUSH:
|
||||
index_offset = 0
|
||||
surf = add_surface()
|
||||
|
||||
for f in range(brush.faces.size()):
|
||||
var face_geo: FuncGodotMapData.FuncGodotFaceGeometry = brush_geo.faces[f]
|
||||
var face: FuncGodotMapData.FuncGodotFace = brush.faces[f]
|
||||
var num_tris = face_geo.vertices.size() - 2
|
||||
|
||||
if filter_face(e, b, f):
|
||||
continue
|
||||
|
||||
for v in range(face_geo.vertices.size()):
|
||||
var vert: FuncGodotMapData.FuncGodotFaceVertex = face_geo.vertices[v].duplicate()
|
||||
|
||||
if entity.spawn_type == FuncGodotMapData.FuncGodotEntitySpawnType.ENTITY:
|
||||
vert.vertex -= entity.center
|
||||
|
||||
surf.vertices.append(vert)
|
||||
|
||||
if include_normals_metadata:
|
||||
var normal := Vector3(face.plane_normal.y, face.plane_normal.z, face.plane_normal.x)
|
||||
for i in num_tris:
|
||||
normals.append(normal)
|
||||
if include_shape_range_metadata or build_entity_index_ranges:
|
||||
total_brush_tris += num_tris
|
||||
if include_textures_metadata:
|
||||
var texname := StringName(map_data.textures[face.texture_idx].name)
|
||||
var index: int
|
||||
if texture_names.is_empty():
|
||||
texture_names.append(texname)
|
||||
index = 0
|
||||
elif texture_names.back() == texname:
|
||||
# Common case, faces with textures are next to each other
|
||||
index = texture_names.size() - 1
|
||||
else:
|
||||
var texture_name_index: int = texture_names.find(texname)
|
||||
if texture_name_index == -1:
|
||||
index = texture_names.size()
|
||||
texture_names.append(texname)
|
||||
else:
|
||||
index = texture_name_index
|
||||
# Metadata addresses triangles, so we have to duplicate the info for each tri
|
||||
for i in num_tris:
|
||||
textures.append(index)
|
||||
|
||||
var avg_vertex_pos := Vector3.ZERO
|
||||
var avg_vertex_pos_ct: int = 0
|
||||
for i in range(num_tris * 3):
|
||||
surf.indicies.append(face_geo.indicies[i] + index_offset)
|
||||
var vertex: Vector3 = surf.vertices[surf.indicies.back()].vertex
|
||||
vertex = Vector3(vertex.y, vertex.z, vertex.x) * map_settings.scale_factor
|
||||
if include_vertices_metadata:
|
||||
vertices.append(vertex)
|
||||
if include_positions_metadata:
|
||||
avg_vertex_pos_ct += 1
|
||||
avg_vertex_pos += vertex
|
||||
if avg_vertex_pos_ct == 3:
|
||||
avg_vertex_pos /= 3
|
||||
positions.append(avg_vertex_pos)
|
||||
avg_vertex_pos = Vector3.ZERO
|
||||
avg_vertex_pos_ct = 0
|
||||
|
||||
index_offset += face_geo.vertices.size()
|
||||
|
||||
if include_shape_range_metadata:
|
||||
shape_face_range.x = shape_face_range.y
|
||||
shape_face_range.y = shape_face_range.x + total_brush_tris
|
||||
shape_index_ranges.append(shape_face_range)
|
||||
|
||||
if build_entity_index_ranges:
|
||||
total_entity_tris += total_brush_tris
|
||||
|
||||
if build_entity_index_ranges:
|
||||
entity_face_range.x = entity_face_range.y
|
||||
entity_face_range.y = entity_face_range.x + total_entity_tris
|
||||
entity_index_ranges.append(entity_face_range)
|
||||
|
||||
out_metadata = {
|
||||
textures = textures,
|
||||
texture_names = texture_names,
|
||||
normals = normals,
|
||||
vertices = vertices,
|
||||
positions = positions,
|
||||
shape_index_ranges = shape_index_ranges,
|
||||
}
|
||||
if build_entity_index_ranges:
|
||||
out_metadata["entity_index_ranges"] = entity_index_ranges
|
||||
|
||||
func add_surface() -> FuncGodotMapData.FuncGodotFaceGeometry:
|
||||
var surf:= FuncGodotMapData.FuncGodotFaceGeometry.new()
|
||||
out_surfaces.append(surf)
|
||||
return surf
|
||||
|
||||
func reset_params() -> void:
|
||||
split_type = SurfaceSplitType.NONE
|
||||
entity_filter_idx = -1
|
||||
texture_filter_idx = -1
|
||||
clip_filter_texture_idx = -1
|
||||
skip_filter_texture_idx = -1
|
||||
metadata_skip_flags = FuncGodotMapData.FuncGodotEntityMetadataInclusionFlags.ENTITY_INDEX_RANGES
|
||||
|
||||
# nested
|
||||
enum SurfaceSplitType{
|
||||
NONE,
|
||||
ENTITY,
|
||||
BRUSH
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://df8y3hiimomt5
|
||||
|
|
@ -8,8 +8,6 @@ const _SIGNATURE: String = "[GEO]"
|
|||
const _VERTEX_EPSILON := FuncGodotUtil._VERTEX_EPSILON
|
||||
const _VERTEX_EPSILON2 := _VERTEX_EPSILON * _VERTEX_EPSILON
|
||||
|
||||
const _HYPERPLANE_SIZE := 65355.0
|
||||
|
||||
const _OriginType := FuncGodotFGDSolidClass.OriginType
|
||||
|
||||
const _GroupData := FuncGodotData.GroupData
|
||||
|
|
@ -21,6 +19,7 @@ const _VertexGroupData := FuncGodotData.VertexGroupData
|
|||
|
||||
# Class members
|
||||
var map_settings: FuncGodotMapSettings = null
|
||||
var hyperplane_size: float = 512.0
|
||||
var entity_data: Array[_EntityData]
|
||||
var texture_materials: Dictionary[String, Material]
|
||||
var texture_sizes: Dictionary[String, Vector2]
|
||||
|
|
@ -30,8 +29,9 @@ var texture_sizes: Dictionary[String, Vector2]
|
|||
## Emitted when beginning a new step of the generation process.
|
||||
signal declare_step(step: String)
|
||||
|
||||
func _init(settings: FuncGodotMapSettings = null) -> void:
|
||||
func _init(settings: FuncGodotMapSettings = null, hplane_size: float = 512.0) -> void:
|
||||
map_settings = settings
|
||||
hyperplane_size = hplane_size
|
||||
|
||||
#region TOOLS
|
||||
func is_skip(face: _FaceData) -> bool:
|
||||
|
|
@ -99,10 +99,11 @@ func generate_base_winding(plane: Plane) -> PackedVector3Array:
|
|||
|
||||
# construct oversized square on the plane to clip against
|
||||
var winding := PackedVector3Array()
|
||||
winding.append(centroid + (right * _HYPERPLANE_SIZE) + (forward * _HYPERPLANE_SIZE))
|
||||
winding.append(centroid + (right * -_HYPERPLANE_SIZE) + (forward * _HYPERPLANE_SIZE))
|
||||
winding.append(centroid + (right * -_HYPERPLANE_SIZE) + (forward * -_HYPERPLANE_SIZE))
|
||||
winding.append(centroid + (right * _HYPERPLANE_SIZE) + (forward * -_HYPERPLANE_SIZE))
|
||||
var h: float = hyperplane_size
|
||||
winding.append(centroid + (right * h) + (forward * h))
|
||||
winding.append(centroid + (right * -h) + (forward * h))
|
||||
winding.append(centroid + (right * -h) + (forward * -h))
|
||||
winding.append(centroid + (right * h) + (forward * -h))
|
||||
return winding
|
||||
|
||||
func generate_face_vertices(brush: _BrushData, face_index: int, vertex_merge_distance: float = 0.0) -> PackedVector3Array:
|
||||
|
|
@ -121,9 +122,17 @@ func generate_face_vertices(brush: _BrushData, face_index: int, vertex_merge_dis
|
|||
if winding.is_empty():
|
||||
break
|
||||
|
||||
# Reduce seams between vertices
|
||||
for i in winding.size():
|
||||
winding.set(i, winding.get(i).snappedf(vertex_merge_distance))
|
||||
# Perform rounding and merge adjacent vertices that are equivalent
|
||||
if vertex_merge_distance > 0:
|
||||
var merged_winding : PackedVector3Array = PackedVector3Array()
|
||||
var prev_vtx : Vector3 = winding[0].snappedf(vertex_merge_distance)
|
||||
merged_winding.append(prev_vtx)
|
||||
for i in range(1, winding.size()):
|
||||
var cur_vtx : Vector3 = winding[i].snappedf(vertex_merge_distance)
|
||||
if prev_vtx != cur_vtx:
|
||||
merged_winding.append(cur_vtx)
|
||||
prev_vtx = cur_vtx
|
||||
winding = merged_winding
|
||||
|
||||
return winding
|
||||
|
||||
|
|
@ -206,9 +215,9 @@ func determine_entity_origins(entity_index: int) -> void:
|
|||
var origin_comps: PackedFloat64Array = entity.properties["origin"].split_floats(" ")
|
||||
if origin_comps.size() > 2:
|
||||
if entity.origin_type == _OriginType.ABSOLUTE:
|
||||
entity.origin = Vector3(origin_comps[0], origin_comps[1], origin_comps[2])
|
||||
entity.origin = Vector3(origin_comps[0], origin_comps[1], origin_comps[2]) * map_settings.scale_factor
|
||||
else: # _OriginType.RELATIVE
|
||||
entity.origin += Vector3(origin_comps[0], origin_comps[1], origin_comps[2])
|
||||
entity.origin += Vector3(origin_comps[0], origin_comps[1], origin_comps[2]) * map_settings.scale_factor
|
||||
|
||||
_OriginType.BRUSH:
|
||||
if origin_mins != Vector3.INF:
|
||||
|
|
@ -312,7 +321,7 @@ func generate_entity_surfaces(entity_index: int) -> void:
|
|||
def = entity.definition
|
||||
|
||||
var op_entity_ogl_xf: Callable = func(v: Vector3) -> Vector3:
|
||||
return (FuncGodotUtil.id_to_opengl(v - entity.origin) * map_settings.scale_factor)
|
||||
return (FuncGodotUtil.id_to_opengl(v - entity.origin))
|
||||
|
||||
# Surface groupings <texture_name, Array[Face]>
|
||||
var surfaces: Dictionary[String, Array] = {}
|
||||
|
|
@ -372,13 +381,70 @@ func generate_entity_surfaces(entity_index: int) -> void:
|
|||
# Begin fresh index offset for this subarray
|
||||
var index_offset: int = 0
|
||||
|
||||
for face in faces:
|
||||
for face: _FaceData in faces:
|
||||
# FACE SCOPE BEGIN
|
||||
|
||||
# Reject invalid faces
|
||||
if face.vertices.size() < 3 or is_skip(face) or is_origin(face):
|
||||
continue
|
||||
|
||||
#region Reject interior faces only if desired
|
||||
if entity.properties.get(map_settings.cull_interior_faces_property, false):
|
||||
var remove_face := false
|
||||
for face2: _FaceData in faces:
|
||||
if face == face2:
|
||||
continue
|
||||
# Are the planes aligned?
|
||||
if !face2.plane.has_point(face.plane.get_center()):
|
||||
continue
|
||||
# Opposite planes
|
||||
if !(face.plane.normal*-1.0).is_equal_approx(face2.plane.normal):
|
||||
continue;
|
||||
|
||||
# Check for faces that share all their vertices.
|
||||
var all_verts_in_face := true
|
||||
for vert in face.vertices:
|
||||
if !face2.vertices.has(vert):
|
||||
all_verts_in_face = false
|
||||
break;
|
||||
if all_verts_in_face:
|
||||
remove_face = true
|
||||
break
|
||||
|
||||
# Check if all vertices of Face1 intersect with any triangle of face 2
|
||||
# If they do, then Face 1 is entirely overlapped on Face 2 and we can remove Face 1
|
||||
var all_verts_in_face2 := true
|
||||
for vert in face.vertices:
|
||||
var vert_in_any_tri := false
|
||||
var from := vert - face2.plane.normal*0.001
|
||||
var to := face2.plane.normal*0.001
|
||||
|
||||
# Loop over all triangles in face 2 and see if the vert intersects any of them
|
||||
for i in ((face2.indices.size()/3)):
|
||||
var intersect = Geometry3D.ray_intersects_triangle(
|
||||
from,
|
||||
to,
|
||||
face2.vertices[face2.indices[i*3]],
|
||||
face2.vertices[face2.indices[i*3 + 1]],
|
||||
face2.vertices[face2.indices[i*3 + 2]]
|
||||
)
|
||||
if !intersect:
|
||||
continue
|
||||
if intersect:
|
||||
vert_in_any_tri = true
|
||||
break;
|
||||
# This vert didn't show up any triangle, can't remove this face
|
||||
if !vert_in_any_tri:
|
||||
all_verts_in_face2 = false
|
||||
break
|
||||
# All verts of face 1 are in face 2, so we can safely remove that face
|
||||
if all_verts_in_face2:
|
||||
remove_face = true
|
||||
break;
|
||||
if remove_face:
|
||||
continue;
|
||||
#endregion
|
||||
|
||||
# Create trimesh points regardless of texture
|
||||
if build_concave:
|
||||
var tris: PackedVector3Array
|
||||
|
|
@ -519,9 +585,11 @@ func generate_entity_surfaces(entity_index: int) -> void:
|
|||
|
||||
func unwrap_uv2s(entity_index: int, texel_size: float) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
if entity.mesh:
|
||||
if (entity.definition as FuncGodotFGDSolidClass).global_illumination_mode:
|
||||
entity.mesh.lightmap_unwrap(Transform3D.IDENTITY, texel_size)
|
||||
# NOTE: This skips smoothed meshes as they need to be unwrapped after smoothing.
|
||||
# Ideally smoothing will be performed here in GeoGen before this process.
|
||||
# For now, since it occurs in EntityAssembler, skip it.
|
||||
if entity.mesh and entity.is_gi_enabled() and not entity.is_smooth_shaded(map_settings.entity_smoothing_property):
|
||||
entity.mesh.lightmap_unwrap(Transform3D.IDENTITY, texel_size)
|
||||
|
||||
# Main build process
|
||||
func build(build_flags: int, entities: Array[_EntityData]) -> Error:
|
||||
|
|
|
|||
|
|
@ -81,8 +81,22 @@ func parse_map_data(map_file: String, map_settings: FuncGodotMapSettings) -> _Pa
|
|||
|
||||
var entities_data: Array[_EntityData] = parse_data.entities
|
||||
var entity_defs: Dictionary[String, FuncGodotFGDEntityClass] = map_settings.entity_fgd.get_entity_definitions()
|
||||
var missing_defs: PackedStringArray = []
|
||||
|
||||
declare_step.emit("Checking entity omission and definition status")
|
||||
var default_point_class := FuncGodotFGDPointClass.new()
|
||||
default_point_class.node_class = "Marker3D"
|
||||
|
||||
var default_solid_class := FuncGodotFGDSolidClass.new()
|
||||
default_solid_class.spawn_type = FuncGodotFGDSolidClass.SpawnType.ENTITY
|
||||
default_solid_class.build_occlusion = false
|
||||
default_solid_class.collision_shape_type = FuncGodotFGDSolidClass.CollisionShapeType.NONE
|
||||
default_solid_class.origin_type = FuncGodotFGDSolidClass.OriginType.BRUSH
|
||||
|
||||
declare_step.emit("Checking entity omission, definition status, and property types")
|
||||
|
||||
# Cache retrieved class property defaults. Format is Dictionary[Classname, Properties].
|
||||
var prop_defaults_cache: Dictionary[String, Dictionary] = {}
|
||||
var prop_descriptions_cache: Dictionary[String, Dictionary] = {}
|
||||
|
||||
for i in range(entities_data.size() - 1, -1, -1):
|
||||
var entity: _EntityData = entities_data[i]
|
||||
|
|
@ -98,6 +112,145 @@ func parse_map_data(map_file: String, map_settings: FuncGodotMapSettings) -> _Pa
|
|||
var classname: String = entity.properties["classname"]
|
||||
if classname in entity_defs:
|
||||
entity.definition = entity_defs[classname]
|
||||
if not entity.definition is FuncGodotFGDSolidClass and not entity.definition is FuncGodotFGDPointClass:
|
||||
if missing_defs.find(classname) < 0:
|
||||
push_error("Invalid entity definition for \"" + classname + "\". Entity definition must be Solid Class or Point Class.")
|
||||
missing_defs.append(classname)
|
||||
entity.definition = null
|
||||
elif missing_defs.find(classname) < 0:
|
||||
push_error("No entity definition found for \"" + classname + "\"")
|
||||
missing_defs.append(classname)
|
||||
|
||||
# Make sure we have a default definition to build entities from
|
||||
# This will make sure nothing goes wrong in the build processes
|
||||
if not entity.definition:
|
||||
if entity.brushes.is_empty():
|
||||
entity.definition = default_point_class
|
||||
else:
|
||||
entity.definition = default_solid_class
|
||||
|
||||
# Convert the string values of the entity's properties Dictionary to various
|
||||
# Variant formats based on the entity definition's class property defaults.
|
||||
var def := entity.definition
|
||||
var properties: Dictionary = entity.properties
|
||||
for property in properties:
|
||||
var prop_string = entity.properties[property]
|
||||
if property in def.class_properties:
|
||||
var prop_default: Variant = def.class_properties[property]
|
||||
|
||||
match typeof(prop_default):
|
||||
TYPE_INT:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_FLOAT:
|
||||
properties[property] = prop_string.to_float()
|
||||
TYPE_BOOL:
|
||||
properties[property] = bool(prop_string.to_int())
|
||||
TYPE_VECTOR3:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 2:
|
||||
properties[property] = Vector3(prop_comps[0], prop_comps[1], prop_comps[2])
|
||||
else:
|
||||
push_error("Invalid Vector3 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR3I:
|
||||
var prop_vec: Vector3i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 2:
|
||||
for v in 3:
|
||||
prop_vec[v] = prop_comps[v].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector3i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_COLOR:
|
||||
var prop_color: Color = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 2:
|
||||
prop_color.r8 = prop_comps[0].to_int()
|
||||
prop_color.g8 = prop_comps[1].to_int()
|
||||
prop_color.b8 = prop_comps[2].to_int()
|
||||
prop_color.a = 1.0
|
||||
else:
|
||||
push_error("Invalid Color format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_color
|
||||
TYPE_DICTIONARY:
|
||||
var prop_desc = def.class_property_descriptions[property]
|
||||
if prop_desc is Array and prop_desc.size() > 1 and prop_desc[1] is int:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_ARRAY:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_VECTOR2:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 1:
|
||||
properties[property] = Vector2(prop_comps[0], prop_comps[1])
|
||||
else:
|
||||
push_error("Invalid Vector2 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR2I:
|
||||
var prop_vec: Vector2i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 1:
|
||||
for v in 2:
|
||||
prop_vec[v] = prop_comps[v].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector2i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_VECTOR4:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 3:
|
||||
properties[property] = Vector4(prop_comps[0], prop_comps[1], prop_comps[2], prop_comps[3])
|
||||
else:
|
||||
push_error("Invalid Vector4 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR4I:
|
||||
var prop_vec: Vector4i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 3:
|
||||
for v in 4:
|
||||
prop_vec[v] = prop_comps[v].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector4i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_STRING_NAME:
|
||||
properties[property] = StringName(prop_string)
|
||||
TYPE_NODE_PATH:
|
||||
properties[property] = prop_string
|
||||
TYPE_OBJECT:
|
||||
properties[property] = prop_string
|
||||
|
||||
# Retrieve default properties.
|
||||
var def_properties: Dictionary[String, Variant] = prop_defaults_cache.get(def.classname, def.retrieve_all_class_properties())
|
||||
var def_descriptions: Dictionary[String, Variant] = prop_descriptions_cache.get(def.classname, def.retrieve_all_class_property_descriptions())
|
||||
|
||||
# Assign properties not defined with defaults from the entity definition
|
||||
for property in def_properties:
|
||||
if not property in properties:
|
||||
var prop_default: Variant = def_properties[property]
|
||||
# Flags
|
||||
if prop_default is Array:
|
||||
var prop_flags_sum := 0
|
||||
for prop_flag in prop_default:
|
||||
if prop_flag is Array and prop_flag.size() > 2:
|
||||
if prop_flag[2] and prop_flag[1] is int:
|
||||
prop_flags_sum += prop_flag[1]
|
||||
properties[property] = prop_flags_sum
|
||||
# Choices
|
||||
elif prop_default is Dictionary:
|
||||
var prop_desc = def_descriptions.get(property, "")
|
||||
if prop_desc is Array and prop_desc.size() > 1 and (prop_desc[1] is int or prop_desc[1] is String):
|
||||
properties[property] = prop_desc[1]
|
||||
elif prop_default.size():
|
||||
properties[property] = prop_default[prop_default.keys().front()]
|
||||
else:
|
||||
properties[property] = 0
|
||||
# Materials, Shaders, and Sounds
|
||||
elif prop_default is Resource:
|
||||
properties[property] = prop_default.resource_path
|
||||
# Target Destination and Target Source
|
||||
elif prop_default is NodePath or prop_default is Object or prop_default == null:
|
||||
properties[property] = ""
|
||||
# Everything else
|
||||
else:
|
||||
properties[property] = prop_default
|
||||
|
||||
# Delete omitted groups
|
||||
declare_step.emit("Removing omitted layers and groups")
|
||||
|
|
@ -203,7 +356,7 @@ func _parse_quake_map(map_data: PackedStringArray, map_settings: FuncGodotMapSet
|
|||
for i in 3:
|
||||
tokens[i] = tokens[i].trim_prefix("(")
|
||||
var pts: PackedFloat64Array = tokens[i].split_floats(" ", false)
|
||||
var point := Vector3(pts[0], pts[1], pts[2])
|
||||
var point := Vector3(pts[0], pts[1], pts[2]) * map_settings.scale_factor
|
||||
points[i] = point
|
||||
|
||||
var plane := Plane(points[0], points[1], points[2])
|
||||
|
|
@ -244,8 +397,8 @@ func _parse_quake_map(map_data: PackedStringArray, map_settings: FuncGodotMapSet
|
|||
|
||||
coords = tokens[2].split_floats(" ", false)
|
||||
# UV scale factor stored in basis
|
||||
face.uv.x = Vector2(coords[1], 0.0)
|
||||
face.uv.y = Vector2(0.0, coords[2])
|
||||
face.uv.x = Vector2(coords[1], 0.0) * map_settings.scale_factor
|
||||
face.uv.y = Vector2(0.0, coords[2]) * map_settings.scale_factor
|
||||
|
||||
# Quake Standard: texname offsetX offsetY rotation scaleX scaleY
|
||||
else:
|
||||
|
|
@ -253,8 +406,8 @@ func _parse_quake_map(map_data: PackedStringArray, map_settings: FuncGodotMapSet
|
|||
face.uv.origin = Vector2(coords[0], coords[1])
|
||||
|
||||
var r: float = deg_to_rad(coords[2])
|
||||
face.uv.x = Vector2(cos(r), -sin(r)) * coords[3]
|
||||
face.uv.y = Vector2(sin(r), cos(r)) * coords[4]
|
||||
face.uv.x = Vector2(cos(r), -sin(r)) * coords[3] * map_settings.scale_factor
|
||||
face.uv.y = Vector2(sin(r), cos(r)) * coords[4] * map_settings.scale_factor
|
||||
|
||||
brush.faces.append(face)
|
||||
continue
|
||||
|
|
@ -374,7 +527,7 @@ func _parse_vmf(map_data: PackedStringArray, map_settings: FuncGodotMapSettings,
|
|||
for i in 3:
|
||||
tokens[i] = tokens[i].trim_prefix("(")
|
||||
var pts: PackedFloat64Array = tokens[i].split_floats(" ", false)
|
||||
var point: Vector3 = Vector3(pts[0], pts[1], pts[2])
|
||||
var point: Vector3 = Vector3(pts[0], pts[1], pts[2]) * map_settings.scale_factor
|
||||
points[i] = point
|
||||
brush.planes.append(Plane(points[0], points[1], points[2]))
|
||||
brush.faces.append(_FaceData.new())
|
||||
|
|
@ -399,10 +552,10 @@ func _parse_vmf(map_data: PackedStringArray, map_settings: FuncGodotMapSettings,
|
|||
face.uv_axes.append(Vector3(vals[0], vals[1], vals[2]))
|
||||
if key.begins_with("u"):
|
||||
face.uv.origin.x = vals[3] # Offset
|
||||
face.uv.x *= vals[4] # Scale
|
||||
face.uv.x *= vals[4] * map_settings.scale_factor # Scale
|
||||
else:
|
||||
face.uv.origin.y = vals[3] # Offset
|
||||
face.uv.y *= vals[4] # Scale
|
||||
face.uv.y *= vals[4] * map_settings.scale_factor # Scale
|
||||
continue
|
||||
"rotation":
|
||||
# Rotation isn't used in Valve 220 mapping and VMFs are 220 exclusive
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotFGDEntityClass extends Resource
|
||||
@abstract class_name FuncGodotFGDEntityClass extends Resource
|
||||
## Entity definition template. WARNING! Not to be used directly! Use [FuncGodotFGDBaseClass], [FuncGodotFGDSolidClass], or [FuncGodotFGDPointClass] instead.
|
||||
##
|
||||
## Entity definition template. It holds all of the common entity class properties shared between [FuncGodotFGDBaseClass], [FuncGodotFGDSolidClass], or [FuncGodotFGDPointClass].
|
||||
|
|
@ -28,24 +28,24 @@ var prefix: String = ""
|
|||
|
||||
## Key value pair properties that will appear in the map editor. After building the [FuncGodotMap] in Godot, these properties will be added to a [Dictionary]
|
||||
## that gets applied to the generated node, as long as that node is a tool script with an exported `func_godot_properties` Dictionary.
|
||||
@export var class_properties : Dictionary = {}
|
||||
@export var class_properties : Dictionary[String, Variant] = {}
|
||||
|
||||
## Map editor descriptions for previously defined key value pair properties. Optional but recommended.
|
||||
@export var class_property_descriptions : Dictionary = {}
|
||||
@export var class_property_descriptions : Dictionary[String, Variant] = {}
|
||||
|
||||
## Automatically applies entity class properties to matching properties in the generated node.
|
||||
## When using this feature, class properties need to be the correct type or you may run into errors on map build.
|
||||
@export var auto_apply_to_matching_node_properties : bool = false
|
||||
|
||||
## Appearance properties for the map editor. See the Valve Developer Wiki and TrenchBroom documentation for more information.
|
||||
@export var meta_properties : Dictionary = {
|
||||
@export var meta_properties : Dictionary[String, Variant] = {
|
||||
"size": AABB(Vector3(-8, -8, -8), Vector3(8, 8, 8)),
|
||||
"color": Color(0.8, 0.8, 0.8)
|
||||
}
|
||||
|
||||
@export_group("Node Generation")
|
||||
|
||||
## Node to generate on map build. This can be a built-in Godot class, a GDScript class, or a GDExtension class.
|
||||
## Node to generate on map build. This can be a built-in Godot class, a Script class, or a GDExtension class.
|
||||
## For Point Class entities that use Scene File instantiation leave this blank.
|
||||
@export var node_class := ""
|
||||
|
||||
|
|
@ -54,6 +54,9 @@ var prefix: String = ""
|
|||
## Nodes will be named `"entity_" + name_property`. An entity's name should be unique, otherwise you may run into unexpected behavior.
|
||||
@export var name_property := ""
|
||||
|
||||
## Optional array of node groups to add the generated node to.
|
||||
@export var node_groups : Array[String] = []
|
||||
|
||||
## Parses the definition and outputs it into the FGD format.
|
||||
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
# Class prefix
|
||||
|
|
@ -231,3 +234,17 @@ func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors =
|
|||
res += "]" + FuncGodotUtil.newline()
|
||||
|
||||
return res
|
||||
|
||||
func retrieve_all_class_properties(properties: Dictionary[String, Variant] = {}) -> Dictionary[String, Variant]:
|
||||
for key in class_properties.keys():
|
||||
properties[key] = class_properties[key]
|
||||
for b in base_classes:
|
||||
properties = b.retrieve_all_class_properties(properties)
|
||||
return properties
|
||||
|
||||
func retrieve_all_class_property_descriptions(descriptions: Dictionary[String, Variant] = {}) -> Dictionary[String, Variant]:
|
||||
for key in class_property_descriptions.keys():
|
||||
descriptions[key] = class_property_descriptions[key]
|
||||
for b in base_classes:
|
||||
descriptions = b.retrieve_all_class_property_descriptions(descriptions)
|
||||
return descriptions
|
||||
|
|
|
|||
|
|
@ -23,28 +23,25 @@ func export_button() -> void:
|
|||
do_export_file(target_map_editor)
|
||||
|
||||
func do_export_file(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM, fgd_output_folder: String = "") -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if fgd_output_folder.is_empty():
|
||||
fgd_output_folder = FuncGodotLocalConfig.get_setting(FuncGodotLocalConfig.PROPERTY.FGD_OUTPUT_FOLDER) as String
|
||||
if fgd_output_folder.is_empty():
|
||||
print("Skipping export: No game config folder")
|
||||
printerr("Skipping export: No game config folder")
|
||||
return
|
||||
|
||||
if fgd_name == "":
|
||||
print("Skipping export: Empty FGD name")
|
||||
printerr("Skipping export: Empty FGD name")
|
||||
|
||||
if not DirAccess.dir_exists_absolute(fgd_output_folder):
|
||||
if DirAccess.make_dir_recursive_absolute(fgd_output_folder) != OK:
|
||||
print("Skipping export: Failed to create directory")
|
||||
printerr("Skipping export: Failed to create directory")
|
||||
return
|
||||
|
||||
var fgd_file = fgd_output_folder.path_join(fgd_name + ".fgd")
|
||||
|
||||
var file_obj := FileAccess.open(fgd_file, FileAccess.WRITE)
|
||||
if not file_obj:
|
||||
print("Failed to open file for writing: ", fgd_file)
|
||||
printerr("Failed to open file for writing: ", fgd_file)
|
||||
return
|
||||
|
||||
print("Exporting FGD to ", fgd_file)
|
||||
|
|
@ -76,6 +73,9 @@ func do_export_file(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMa
|
|||
## Array of resources that inherit from [FuncGodotFGDEntityClass]. This array defines the entities that will be added to the exported FGD file and the nodes that will be generated in a [FuncGodotMap].
|
||||
@export var entity_definitions: Array[Resource] = []
|
||||
|
||||
## Toggles whether [FuncGodotFGDModelPointClass] resources will generate models from their [PackedScene] files.
|
||||
@export var generate_model_point_class_models: bool = true
|
||||
|
||||
func build_class_text(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
var res : String = ""
|
||||
|
||||
|
|
@ -91,6 +91,8 @@ func build_class_text(target_editor: FuncGodotTargetMapEditors = FuncGodotTarget
|
|||
continue
|
||||
if ent.func_godot_internal:
|
||||
continue
|
||||
if ent is FuncGodotFGDModelPointClass:
|
||||
ent._model_generation_enabled = generate_model_point_class_models
|
||||
|
||||
var ent_text = ent.build_def_text(target_editor)
|
||||
res += ent_text
|
||||
|
|
@ -127,9 +129,9 @@ func get_entity_definitions() -> Dictionary[String, FuncGodotFGDEntityClass]:
|
|||
|
||||
if ent is FuncGodotFGDPointClass or ent is FuncGodotFGDSolidClass:
|
||||
var entity_def = ent.duplicate()
|
||||
var meta_properties := {}
|
||||
var class_properties := {}
|
||||
var class_property_descriptions := {}
|
||||
var meta_properties: Dictionary[String, Variant] = {}
|
||||
var class_properties: Dictionary[String, Variant] = {}
|
||||
var class_property_descriptions: Dictionary[String, Variant] = {}
|
||||
|
||||
for base_class in _generate_base_class_list(entity_def):
|
||||
for meta_property in base_class.meta_properties:
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ enum TargetMapEditor {
|
|||
## Creates a .gdignore file in the model export folder to prevent Godot importing the display models. Only needs to be generated once.
|
||||
@export_tool_button("Generate GD Ignore File", "FileAccess") var generate_gd_ignore_file : Callable = _generate_gd_ignore_file
|
||||
|
||||
var _model_generation_enabled: bool = false
|
||||
|
||||
func _generate_gd_ignore_file() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
var path: String = _get_game_path().path_join(_get_model_folder())
|
||||
|
|
@ -45,7 +47,9 @@ func _generate_gd_ignore_file() -> void:
|
|||
|
||||
## Builds and saves the display model into the specified destination, then parses the definition and outputs it into the FGD format.
|
||||
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
_generate_model()
|
||||
if _model_generation_enabled:
|
||||
_generate_model()
|
||||
_model_generation_enabled = false
|
||||
return super()
|
||||
|
||||
func _generate_model() -> void:
|
||||
|
|
@ -65,7 +69,10 @@ func _generate_model() -> void:
|
|||
if target_map_editor == TargetMapEditor.TRENCHBROOM:
|
||||
const model_key: String = "model"
|
||||
if scale_expression.is_empty():
|
||||
meta_properties[model_key] = '"%s"' % _get_local_path()
|
||||
meta_properties[model_key] = '{"path": "%s", "scale": %s }' % [
|
||||
_get_local_path(),
|
||||
ProjectSettings.get_setting("func_godot/default_inverse_scale_factor", 32.0) as float
|
||||
]
|
||||
else:
|
||||
meta_properties[model_key] = '{"path": "%s", "scale": %s }' % [
|
||||
_get_local_path(),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class_name FuncGodotFGDPointClass extends FuncGodotFGDEntityClass
|
||||
## FGD PointClass entity definition.
|
||||
##
|
||||
## A resource used to define an FGD PointClass entity. PointClass entities can use either the [member FuncGodotFGDEntityClass.node_class]
|
||||
## A resource used to define an FGD Point Class entity. PointClass entities can use either the [member FuncGodotFGDEntityClass.node_class]
|
||||
## or the [member scene_file] property to tell [FuncGodotMap] what to generate on map build.
|
||||
##
|
||||
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
|
||||
|
|
@ -17,15 +17,12 @@ class_name FuncGodotFGDPointClass extends FuncGodotFGDEntityClass
|
|||
func _init() -> void:
|
||||
prefix = "@PointClass"
|
||||
|
||||
@export_group ("Scene")
|
||||
## An optional [PackedScene] file to instantiate on map build. Overrides [member FuncGodotFGDEntityClass.node_class] and [member script_class].
|
||||
@export var scene_file: PackedScene
|
||||
|
||||
@export_group ("Scripting")
|
||||
## An optional [Script] resource to attach to the node generated on map build. Ignored if [member scene_file] is specified.
|
||||
@export var script_class: Script
|
||||
|
||||
@export_group("Build")
|
||||
## Toggles whether entity will use `angles`, `mangle`, or `angle` to determine rotations on [FuncGodotMap] build, prioritizing the key value pairs in that order.
|
||||
## Set to [code]false[/code] if you would like to define how the generated node is rotated yourself.
|
||||
@export var apply_rotation_on_map_build : bool = true
|
||||
|
|
@ -33,3 +30,91 @@ func _init() -> void:
|
|||
## Toggles whether entity will use `scale` to determine the generated node or scene's scale. This is performed on the top level node.
|
||||
## The property can be a [float], [Vector3], or [Vector2]. Set to [code]false[/code] if you would like to define how the generated node is scaled yourself.
|
||||
@export var apply_scale_on_map_build: bool = true
|
||||
|
||||
## An optional [Array] of [FuncGodotFGDPointClassDisplayDescriptor] that describes how this Point Entity should appear in the map editor.
|
||||
## When using multiple display descriptors, only the first element found without [member FuncGodotFGDPointClassDisplayDescriptor.conditional]
|
||||
## will be used as the default display asset. If no descriptor is found without a condition, the last descriptor will become the default.[br][br]
|
||||
## Conditional display descriptors will be written to the FGD in the order set in the array.[br][br]
|
||||
## [color=orange]WARNING:[/color] Multiple descriptors are only supported by TrenchBroom! They will be omitted on export when
|
||||
## [member FuncGodotFGDFile.target_map_editor] is not set to [enum FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM].
|
||||
@export var display_descriptors: Array[FuncGodotFGDPointClassDisplayDescriptor] = []
|
||||
|
||||
func _build_model_branch_text(descriptor: FuncGodotFGDPointClassDisplayDescriptor) -> String:
|
||||
if not descriptor:
|
||||
return ''
|
||||
|
||||
var model_string: String = ''
|
||||
var uses_options: bool = false
|
||||
|
||||
if not descriptor.scale.is_empty() or not descriptor.skin.is_empty() or not descriptor.frame.is_empty():
|
||||
uses_options = true
|
||||
|
||||
if not uses_options:
|
||||
return descriptor.display_asset_path
|
||||
|
||||
model_string = '{ \"path\": %s' % descriptor.display_asset_path
|
||||
|
||||
if not descriptor.skin.is_empty():
|
||||
model_string += ', \"skin\": %s' % descriptor.skin
|
||||
if not descriptor.frame.is_empty():
|
||||
model_string += ', \"frame\": %s' % descriptor.frame
|
||||
if not descriptor.scale.is_empty():
|
||||
model_string += ', \"scale\": %s' % descriptor.scale
|
||||
|
||||
model_string += " }"
|
||||
|
||||
return model_string
|
||||
|
||||
func _build_model_text() -> String:
|
||||
var model_string: String = ''
|
||||
|
||||
if display_descriptors.is_empty():
|
||||
return model_string
|
||||
|
||||
if display_descriptors.size() == 1:
|
||||
return _build_model_branch_text(display_descriptors[0])
|
||||
|
||||
model_string = '{{'
|
||||
var default_display: FuncGodotFGDPointClassDisplayDescriptor
|
||||
for i in display_descriptors.size():
|
||||
var d: FuncGodotFGDPointClassDisplayDescriptor = display_descriptors[i]
|
||||
|
||||
# Only set the first discovered descriptor without a condition to the default, which must be the last option in a list.
|
||||
# If a conditional is not set, skip it.
|
||||
if d.conditional.is_empty():
|
||||
if not default_display:
|
||||
default_display = d
|
||||
else:
|
||||
printerr(classname + " has a Point Class Display Descriptor without required conditionals set. Must have only 1 conditionless Display Descriptor!")
|
||||
continue
|
||||
|
||||
model_string += '%s -> %s, ' % [d.conditional, _build_model_branch_text(d)]
|
||||
|
||||
if default_display:
|
||||
model_string += '%s }}' % _build_model_branch_text(default_display)
|
||||
else:
|
||||
model_string = model_string.trim_suffix(', ')
|
||||
model_string += ' }}'
|
||||
|
||||
return model_string
|
||||
|
||||
func _build_studio_text() -> String:
|
||||
var display_string = ""
|
||||
for d in display_descriptors:
|
||||
if d.display_asset_path.find('\"') != -1:
|
||||
display_string = d.display_asset_path
|
||||
else:
|
||||
printerr(classname + " attempting to set an invalid value to @studio format during FGD export. Only relative file paths encapsulated by quotations are valid.")
|
||||
return display_string
|
||||
|
||||
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
if not display_descriptors.is_empty():
|
||||
if target_editor == FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM:
|
||||
var display_string: String = _build_model_text()
|
||||
if not display_string.is_empty():
|
||||
meta_properties["model"] = display_string
|
||||
else:
|
||||
var display_string: String = _build_studio_text()
|
||||
if not display_string.is_empty():
|
||||
meta_properties["studio"] = display_string
|
||||
return super(target_editor)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_godambler3d.svg")
|
||||
class_name FuncGodotFGDPointClassDisplayDescriptor extends Resource
|
||||
## Resource that describes how to display an FGD Point Class entity.
|
||||
##
|
||||
## A resource for [FuncGodotFGDPointClass] that describes how to display a point entity in a map editor.
|
||||
## Values entered into the different options are taken literally: paths should be enclosed within quotation marks,
|
||||
## while class property keys and integer values should omit them.[br][br]
|
||||
##
|
||||
## Most editors only support the [member display_asset] option. Exporting an FGD compatible with these editors will
|
||||
## automatically omit the unsupported options introduced by TrenchBroom when exporting from their respective game configuration resources
|
||||
## or setting [member FuncGodotFGDFile.target_map_editor] away from [enum FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM].
|
||||
##
|
||||
## The extra options are considered advanced features and are unable to be evaluated by FuncGodot to ensure they were input correctly.
|
||||
## Exercise caution, care, and patience when attempting to use these, especially the [member conditional] option.
|
||||
##
|
||||
## @tutorial(Level Design Book: Display Models for Entities): https://book.leveldesignbook.com/appendix/resources/formats/fgd#display-models-for-entities
|
||||
## @tutorial(Valve Developer Wiki FGD Article: Entity Description Section): https://developer.valvesoftware.com/wiki/FGD#Entity_Description
|
||||
## @tutorial(TrenchBroom Manual: Display Models for Entities): https://trenchbroom.github.io/manual/latest/#display-models-for-entities
|
||||
## @tutorial(TrenchBroom Manual: Expression Language): https://trenchbroom.github.io/manual/latest/#expression_language
|
||||
|
||||
## Either a file path to the asset that will be displayed for this point entity, relative to the map editor's game path,
|
||||
## or a class property key that can contain the path.[br][br]
|
||||
## For paths, you must surround the path with quotes, e.g: [code]"models/marsfrog.glb"[/code].
|
||||
## For properties, you must omit the quotes, e.g: [code]display_model_path[/code].[br][br]
|
||||
## Different editors support different file types: common ones include MDL, GLB, SPR, and PNG.
|
||||
@export var display_asset_path: String = ""
|
||||
|
||||
@export_group("TrenchBroom Options")
|
||||
## Optional string that determines the scale of the display asset. This can be a number, a class property key, or
|
||||
## a scale expression in accordance with TrenchBroom's Expression Language. Leave blank to use the game configuration's default scale expression.[br][br]
|
||||
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
|
||||
@export var scale: String = ""
|
||||
|
||||
## Optional string that determines which skin the display asset should use. This can be either a number or a class property key.[br][br]
|
||||
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
|
||||
@export var skin: String = ""
|
||||
|
||||
## Optional string that determines the appearance of a display asset based on its file type. This can be either a number or a class property key.[br][br]
|
||||
## Traditional Quake MDL files will set the display to that frame of its animations (all animations in a Quake MDL are compiled into a single animation).
|
||||
## GLBs meanwhile seem to set themselves to the animation assigned to an index that matches the [code]frame[/code] value.[br][br]
|
||||
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
|
||||
@export var frame: String = ""
|
||||
|
||||
## Optional evaluation string that, when true, will force the Point Class to display the asset defined by [member display_asset_path].
|
||||
## Format should be [code]property == value[/code] or some other valid expression in accordance with TrenchBroom's Expression Language.[br][br]
|
||||
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
|
||||
@export var conditional: String = ""
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://d1nwwgcrner8b
|
||||
|
|
@ -38,6 +38,6 @@ func _import(source_file, save_path, options, r_platform_variants, r_gen_files)
|
|||
map_resource.revision += 1
|
||||
else:
|
||||
map_resource = QuakeMapFile.new()
|
||||
map_resource.map_data = FileAccess.open(source_file, FileAccess.READ).get_as_text(true)
|
||||
map_resource.map_data = FileAccess.open(source_file, FileAccess.READ).get_as_text()
|
||||
|
||||
return ResourceSaver.save(map_resource, save_path_str)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func _import(source_file, save_path, options, r_platform_variants, r_gen_files)
|
|||
var file = FileAccess.open(source_file, FileAccess.READ)
|
||||
if file == null:
|
||||
var err = FileAccess.get_open_error()
|
||||
print(['Error opening super.lmp file: ', err])
|
||||
printerr(['Error opening super.lmp file: ', err])
|
||||
return err
|
||||
|
||||
var colors := PackedColorArray()
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ class_name QuakeWadFile extends Resource
|
|||
## Collection of [ImageTexture] imported from the WAD file.
|
||||
@export var textures: Dictionary[String, ImageTexture]
|
||||
|
||||
func _init(textures: Dictionary = Dictionary()):
|
||||
func _init(textures: Dictionary[String, ImageTexture] = {}):
|
||||
self.textures = textures
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func _import(source_file, save_path, options, r_platform_variants, r_gen_files)
|
|||
var file = FileAccess.open(source_file, FileAccess.READ)
|
||||
if file == null:
|
||||
var err = FileAccess.get_open_error()
|
||||
print(['Error opening super.wad file: ', err])
|
||||
printerr(['Error opening super.wad file: ', err])
|
||||
return err
|
||||
|
||||
# Read WAD header
|
||||
|
|
@ -81,13 +81,13 @@ func _import(source_file, save_path, options, r_platform_variants, r_gen_files)
|
|||
if magic_string == 'WAD3':
|
||||
wad_format = WadFormat.HalfLife
|
||||
elif magic_string != 'WAD2':
|
||||
print('Error: Invalid WAD magic')
|
||||
printerr('Error: Invalid WAD magic')
|
||||
return ERR_INVALID_DATA
|
||||
|
||||
var palette_path : String = options['palette_file']
|
||||
var palette_file : QuakePaletteFile = load(palette_path) as QuakePaletteFile
|
||||
if wad_format == WadFormat.Quake and not palette_file:
|
||||
print('Error: Invalid Quake palette file')
|
||||
printerr('Error: Invalid Quake palette file')
|
||||
file.close()
|
||||
return ERR_CANT_ACQUIRE_RESOURCE
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ var _map_file_internal: String = ""
|
|||
## [enum BuildFlags] that can affect certain aspects of the build process.
|
||||
@export_flags("Unwrap UV2:1", "Show Profiling Info:2", "Disable Smooth Shading:4") var build_flags: int = 0
|
||||
|
||||
## The hyperplane is an initial plane that all geometry faces are cut from, like a large sheet of marble before a sculptor begins chiseling.
|
||||
## The hyperplane size would need to be able to cover your map's potential total area.
|
||||
## Smaller values can minimize floating point errors, reducing the effect of gaps between polygon seams.
|
||||
## Measured in Godot units, not Quake units.
|
||||
@export_range(256.0, 2048.0, 128.0) var hyperplane_size: float = 512.0
|
||||
|
||||
## Map build failure handler. Displays error message and emits [signal build_failed] signal.
|
||||
func fail_build(reason: String, notify: bool = false) -> void:
|
||||
push_error(_SIGNATURE, " ", reason)
|
||||
|
|
@ -117,7 +123,7 @@ func build() -> void:
|
|||
parser = null
|
||||
|
||||
# Retrieve geometry
|
||||
var generator := FuncGodotGeometryGenerator.new(map_settings)
|
||||
var generator := FuncGodotGeometryGenerator.new(map_settings, hyperplane_size)
|
||||
if build_flags & BuildFlags.SHOW_PROFILE_INFO:
|
||||
print("\nGEOMETRY GENERATOR")
|
||||
generator.declare_step.connect(FuncGodotUtil.print_profile_info.bind(generator._SIGNATURE))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ class_name FuncGodotMapSettings extends Resource
|
|||
## Reusable map settings configuration for [FuncGodotMap] nodes.
|
||||
|
||||
#region BUILD
|
||||
@export_category("Build Settings")
|
||||
@export_group("Build Settings")
|
||||
|
||||
## Set automatically when [member inverse_scale_factor] is changed. Used primarily during the build process.
|
||||
var scale_factor: float = 0.03125
|
||||
|
||||
|
|
@ -21,30 +22,51 @@ var scale_factor: float = 0.03125
|
|||
## [FuncGodotFGDFile] that translates map file classnames into Godot nodes and packed scenes.
|
||||
@export var entity_fgd: FuncGodotFGDFile = preload("res://addons/func_godot/fgd/func_godot_fgd.tres")
|
||||
|
||||
## Default class property to use in naming generated nodes. This setting is overridden by [member FuncGodotFGDEntityClass.name_property].
|
||||
## Naming occurs before adding to the [SceneTree] and applying properties.
|
||||
## Nodes will be named `"entity_" + name_property`. An entity's name should be unique, otherwise you may run into unexpected behavior.
|
||||
@export var entity_name_property: String = ""
|
||||
|
||||
## Class property that determines whether the [FuncGodotFGDSolidClass] entity performs mesh smoothing operations.
|
||||
@export var entity_smoothing_property: String = "_phong"
|
||||
|
||||
## Class property that contains the angular threshold that determines when a [FuncGodotFGDSolidClass] entity's mesh vertices are smoothed.
|
||||
@export var entity_smoothing_angle_property: String = "_phong_angle"
|
||||
|
||||
## If true, will organize [SceneTree] using TrenchBroom Layers and Groups or Hammer Visgroups. Groups will be generated as [Node3D] nodes.
|
||||
## All non-entity structural brushes will be moved out of their groups and merged into the `Worldspawn` entity.
|
||||
## Any Layers toggled to be omitted from export in TrenchBroom and their child entities and groups will not be built.
|
||||
@export var use_groups_hierarchy: bool = false
|
||||
|
||||
## Class property that contains the snapping epsilon for generated vertices of [FuncGodotFGDSolidClass] entities.
|
||||
## Utilizing this property can help reduce instances of seams between polygons.
|
||||
@export var vertex_merge_distance_property: String = "_vertex_merge_distance"
|
||||
## Texel size for UV2 unwrapping.
|
||||
## Actual texel size is uv_unwrap_texel_size / [member inverse_scale_factor]. A ratio of 1/16 is usually a good place to start with
|
||||
## (if inverse_scale_factor is 32, start with a uv_unwrap_texel_size of 2).
|
||||
## Larger values will produce less detailed lightmaps. To conserve memory and filesize, use the largest value that still looks good.
|
||||
@export var uv_unwrap_texel_size: float = 2.0
|
||||
|
||||
#endregion
|
||||
|
||||
#region ENTITY
|
||||
@export_group("Entity Settings")
|
||||
|
||||
## Optional array of node groups to add all generated nodes to.
|
||||
@export var entity_node_groups: Array[String] = []
|
||||
|
||||
@export_subgroup("Entity Property Names")
|
||||
## Default class property to use in naming generated nodes. This setting is overridden by [member FuncGodotFGDEntityClass.name_property].
|
||||
## Naming occurs before adding to the [SceneTree] and applying properties.
|
||||
## Nodes will be named `"entity_" + name_property`. An entity's name should be unique, otherwise you may run into unexpected behavior.
|
||||
@export var entity_name_property: String = ""
|
||||
|
||||
## Entity class property that determines whether the [FuncGodotFGDSolidClass] entity performs mesh smoothing operations.
|
||||
@export var entity_smoothing_property: String = "_phong"
|
||||
|
||||
## Entity class property that contains the angular threshold that determines when a [FuncGodotFGDSolidClass] entity's mesh vertices are smoothed.
|
||||
@export var entity_smoothing_angle_property: String = "_phong_angle"
|
||||
|
||||
## Entity class property that contains the snapping epsilon for generated vertices of [FuncGodotFGDSolidClass] entities.
|
||||
## Utilizing this property can help reduce instances of seams between polygons.
|
||||
@export var vertex_merge_distance_property: String = "_vertex_merge_distance"
|
||||
|
||||
## Entity class property that tells whether interior faces should be culled for that brush entity.
|
||||
## Interior faces are faces with matching vertices or are flush within a larger face.
|
||||
## Note that this has a performance impact that scales with how many brushes are in the entity.
|
||||
@export var cull_interior_faces_property: String = "_cull_interior_faces"
|
||||
|
||||
@export_subgroup("")
|
||||
#endregion
|
||||
|
||||
#region TEXTURES
|
||||
@export_category("Textures")
|
||||
@export_group("Textures")
|
||||
|
||||
## Base directory for textures. When building materials, FuncGodot will search this directory for texture files with matching names to the textures assigned to map brush faces.
|
||||
@export_dir var base_texture_dir: String = "res://textures"
|
||||
|
|
@ -52,25 +74,27 @@ var scale_factor: float = 0.03125
|
|||
## File extensions to search for texture data.
|
||||
@export var texture_file_extensions: Array[String] = ["png", "jpg", "jpeg", "bmp", "tga", "webp"]
|
||||
|
||||
@export_subgroup("Hint Textures")
|
||||
## Optional path for the clip texture, relative to [member base_texture_dir].
|
||||
## Brush faces textured with the clip texture will have those faces removed from the generated [Mesh] but not the generated [Shape3D].
|
||||
@export var clip_texture: String = "special/clip":
|
||||
@export var clip_texture: String = "clip":
|
||||
set(tex):
|
||||
clip_texture = tex.to_lower()
|
||||
|
||||
## Optional path for the skip texture, relative to [member base_texture_dir].
|
||||
## Brush faces textured with the skip texture will have those faces removed from the generated [Mesh].
|
||||
## If [member FuncGodotFGDSolidClass.collision_shape_type] is set to concave then it will also remove collision from those faces in the generated [Shape3D].
|
||||
@export var skip_texture: String = "special/skip":
|
||||
@export var skip_texture: String = "skip":
|
||||
set(tex):
|
||||
skip_texture = tex.to_lower()
|
||||
|
||||
## Optional path for the origin texture, relative to [member base_texture_dir].
|
||||
## Brush faces textured with the origin texture will have those faces removed from the generated [Mesh] and [Shape3D].
|
||||
## The bounds of these faces will be used to calculate the origin point of the entity.
|
||||
@export var origin_texture: String = "special/origin":
|
||||
@export var origin_texture: String = "origin":
|
||||
set(tex):
|
||||
origin_texture = tex.to_lower()
|
||||
@export_subgroup("")
|
||||
|
||||
## Optional [QuakeWadFile] resources to apply textures from. See the [Quake Wiki](https://quakewiki.org/wiki/Texture_Wad) for more information on Quake Texture WADs.
|
||||
@export var texture_wads: Array[QuakeWadFile] = []
|
||||
|
|
@ -78,7 +102,7 @@ var scale_factor: float = 0.03125
|
|||
#endregion
|
||||
|
||||
#region MATERIALS
|
||||
@export_category("Materials")
|
||||
@export_group("Materials")
|
||||
|
||||
## Base directory for loading and saving materials. When building materials, FuncGodot will search this directory for material resources
|
||||
## with matching names to the textures assigned to map brush faces. If not found, will fall back to [member base_texture_dir].
|
||||
|
|
@ -93,33 +117,33 @@ var scale_factor: float = 0.03125
|
|||
## Sampler2D uniform that supplies the Albedo in a custom shader when [member default_material] is a [ShaderMaterial].
|
||||
@export var default_material_albedo_uniform: String = ""
|
||||
|
||||
## Automatic [ShaderMaterial] generation mapping patterns. Only used when [member default_material] is a ShaderMaterial.
|
||||
## Keys should be the names of the shader uniforms while the values should be the suffixes for the texture maps.
|
||||
## Patterns only use one replacement String: the texture name, ex: [code]"%s_normal"[/code].
|
||||
@export var shader_material_uniform_map_patterns: Dictionary[String, String] = {}
|
||||
|
||||
@export_subgroup("BaseMaterial3D Map Patterns")
|
||||
## Automatic PBR material generation albedo map pattern.
|
||||
@export var albedo_map_pattern: String = "%s_albedo.%s"
|
||||
@export var albedo_map_pattern: String = "%s_albedo"
|
||||
## Automatic PBR material generation normal map pattern.
|
||||
@export var normal_map_pattern: String = "%s_normal.%s"
|
||||
@export var normal_map_pattern: String = "%s_normal"
|
||||
## Automatic PBR material generation metallic map pattern
|
||||
@export var metallic_map_pattern: String = "%s_metallic.%s"
|
||||
@export var metallic_map_pattern: String = "%s_metallic"
|
||||
## Automatic PBR material generation roughness map pattern
|
||||
@export var roughness_map_pattern: String = "%s_roughness.%s"
|
||||
@export var roughness_map_pattern: String = "%s_roughness"
|
||||
## Automatic PBR material generation emission map pattern
|
||||
@export var emission_map_pattern: String = "%s_emission.%s"
|
||||
@export var emission_map_pattern: String = "%s_emission"
|
||||
## Automatic PBR material generation ambient occlusion map pattern
|
||||
@export var ao_map_pattern: String = "%s_ao.%s"
|
||||
@export var ao_map_pattern: String = "%s_ao"
|
||||
## Automatic PBR material generation height map pattern
|
||||
@export var height_map_pattern: String = "%s_height.%s"
|
||||
@export var height_map_pattern: String = "%s_height"
|
||||
## Automatic PBR material generation ORM map pattern
|
||||
@export var orm_map_pattern: String = "%s_orm.%s"
|
||||
@export var orm_map_pattern: String = "%s_orm"
|
||||
@export_subgroup("")
|
||||
|
||||
## Save automatically generated materials to disk, allowing reuse across [FuncGodotMap] nodes.
|
||||
## [i]NOTE: Materials do not use the [member default_material] settings after saving.[/i]
|
||||
@export var save_generated_materials: bool = true
|
||||
@export_group("")
|
||||
|
||||
#endregion
|
||||
|
||||
@export_category("UV Unwrap")
|
||||
|
||||
## Texel size for UV2 unwrapping.
|
||||
## Actual texel size is uv_unwrap_texel_size / [member inverse_scale_factor]. A ratio of 1/16 is usually a good place to start with
|
||||
## (if inverse_scale_factor is 32, start with a uv_unwrap_texel_size of 2).
|
||||
## Larger values will produce less detailed lightmaps. To conserve memory and filesize, use the largest value that still looks good.
|
||||
@export var uv_unwrap_texel_size: float = 2.0
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ enum NetRadiantCustomMapType {
|
|||
## this should be the master FGD that contains them in [member FuncGodotFGDFile.base_fgd_files].
|
||||
@export var fgd_file : FuncGodotFGDFile = preload("res://addons/func_godot/fgd/func_godot_fgd.tres")
|
||||
|
||||
## Toggles whether [FuncGodotFGDModelPointClass] resources will generate models from their [PackedScene] files.
|
||||
@export var generate_model_point_class_models: bool = true
|
||||
|
||||
## Collection of [NetRadiantCustomShader] resources for shader file generation.
|
||||
@export var netradiant_custom_shaders : Array[Resource] = [
|
||||
preload("res://addons/func_godot/game_config/netradiant_custom/netradiant_custom_shader_clip.tres"),
|
||||
|
|
@ -34,28 +37,30 @@ enum NetRadiantCustomMapType {
|
|||
preload("res://addons/func_godot/game_config/netradiant_custom/netradiant_custom_shader_origin.tres")
|
||||
]
|
||||
|
||||
## Supported texture file types.
|
||||
@export var texture_types : PackedStringArray = ["png", "jpg", "jpeg", "bmp", "tga"]
|
||||
|
||||
## Supported model file types.
|
||||
@export var model_types : PackedStringArray = ["glb", "gltf", "obj"]
|
||||
|
||||
## Supported audio file types.
|
||||
@export var sound_types : PackedStringArray = ["wav", "ogg"]
|
||||
|
||||
## Quake map type NetRadiant will filter the map for, determining whether PatchDef entries are saved.
|
||||
## [color=red][b]WARNING![/b][/color] Toggling this option may be destructive!
|
||||
@export var map_type: NetRadiantCustomMapType = NetRadiantCustomMapType.QUAKE_3
|
||||
|
||||
@export_group("Textures")
|
||||
## Supported texture file types.
|
||||
@export var texture_types : PackedStringArray = ["png", "jpg", "jpeg", "bmp", "tga"]
|
||||
|
||||
## Default scale of textures in NetRadiant Custom.
|
||||
@export var default_scale : String = "1.0"
|
||||
|
||||
## Clip texture path that gets applied to [i]weapclip[/i] and [i]nodraw[/i] shaders.
|
||||
@export var clip_texture: String = "textures/special/clip"
|
||||
@export var clip_texture: String = "textures/clip"
|
||||
|
||||
## Skip texture path that gets applied to [i]caulk[/i] and [i]nodrawnonsolid[/i] shaders.
|
||||
@export var skip_texture: String = "textures/special/skip"
|
||||
|
||||
## Quake map type NetRadiant will filter the map for, determining whether PatchDef entries are saved.
|
||||
## [color=red][b]WARNING![/b][/color] Toggling this option may be destructive!
|
||||
@export var map_type: NetRadiantCustomMapType = NetRadiantCustomMapType.QUAKE_1
|
||||
@export var skip_texture: String = "textures/skip"
|
||||
|
||||
@export_group("Build Menu")
|
||||
## Variables to include in the exported gamepack's [code]default_build_menu.xml[/code].[br][br]
|
||||
## Each [String] key defines a variable name, and its corresponding [String] value as the literal command-line string
|
||||
## to execute in place of this variable identifier[br][br]
|
||||
|
|
@ -312,5 +317,6 @@ func export_file() -> void:
|
|||
|
||||
# FGD
|
||||
var export_fgd : FuncGodotFGDFile = fgd_file.duplicate()
|
||||
export_fgd.generate_model_point_class_models = generate_model_point_class_models
|
||||
export_fgd.do_export_file(FuncGodotFGDFile.FuncGodotTargetMapEditors.NET_RADIANT_CUSTOM, gamepacks_folder + "/" + gamepack_name + ".game/" + base_game_path)
|
||||
print("NetRadiant Custom Gamepack export complete\n")
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ enum GameConfigVersion {
|
|||
{ "format": "Quake3" }
|
||||
]
|
||||
|
||||
@export_category("Textures")
|
||||
@export_group("Textures")
|
||||
|
||||
## Path to top level textures folder relative to the game path. Also referred to as materials in the latest versions of TrenchBroom.
|
||||
@export var textures_root_folder: String = "textures"
|
||||
|
|
@ -42,7 +42,7 @@ enum GameConfigVersion {
|
|||
## Palette path relative to your Game Path. Only needed for Quake WAD2 files. Half-Life WAD3 files contain the palettes within the texture information.
|
||||
@export var palette_path: String = "textures/palette.lmp"
|
||||
|
||||
@export_category("Entities")
|
||||
@export_group("Entities")
|
||||
|
||||
## [FuncGodotFGDFile] resource to include with this game. If using multiple FGD File resources,
|
||||
## this should be the master FGD File that contains them in [member FuncGodotFGDFile.base_fgd_files].
|
||||
|
|
@ -52,8 +52,11 @@ enum GameConfigVersion {
|
|||
## See [url="https://trenchbroom.github.io/manual/latest/#game_configuration_files_entities"]TrenchBroom Manual Entity Configuration Information[/url] for more information.
|
||||
@export var entity_scale: String = "32"
|
||||
|
||||
## Toggles whether [FuncGodotFGDModelPointClass] resources will generate models from their [PackedScene] files.
|
||||
@export var generate_model_point_class_models: bool = true
|
||||
|
||||
## Arrays containing the [TrenchbroomTag] resource type.
|
||||
@export_category("Tags")
|
||||
@export_group("Tags")
|
||||
|
||||
## [TrenchbroomTag] resources that apply to brush entities.
|
||||
@export var brush_tags : Array[Resource] = []
|
||||
|
|
@ -65,12 +68,12 @@ enum GameConfigVersion {
|
|||
preload("res://addons/func_godot/game_config/trenchbroom/tb_face_tag_origin.tres")
|
||||
]
|
||||
|
||||
@export_category("Face Attributes")
|
||||
@export_group("Face Attributes")
|
||||
|
||||
## Default scale of textures on new brushes and when UV scale is reset.
|
||||
@export var default_uv_scale : Vector2 = Vector2(1, 1)
|
||||
|
||||
@export_category("Compatibility")
|
||||
@export_group("Compatibility")
|
||||
|
||||
## Game configuration format compatible with the version of TrenchBroom being used.
|
||||
@export var game_config_version: GameConfigVersion = GameConfigVersion.Latest
|
||||
|
|
@ -229,6 +232,7 @@ func export_file() -> void:
|
|||
|
||||
# FGD
|
||||
var export_fgd : FuncGodotFGDFile = fgd_file.duplicate()
|
||||
export_fgd.generate_model_point_class_models = generate_model_point_class_models
|
||||
export_fgd.do_export_file(FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM, config_folder)
|
||||
print("TrenchBroom Game Config export complete\n")
|
||||
|
||||
|
|
|
|||
|
|
@ -55,18 +55,6 @@ const _CONFIG_PROPERTIES: Array[Dictionary] = [
|
|||
"hint": PROPERTY_HINT_GLOBAL_DIR,
|
||||
"func_godot_type": PROPERTY.MAP_EDITOR_GAME_PATH
|
||||
},
|
||||
#{
|
||||
#"name": "game_path_models_folder",
|
||||
#"usage": PROPERTY_USAGE_EDITOR,
|
||||
#"type": TYPE_STRING,
|
||||
#"func_godot_type": PROPERTY.GAME_PATH_MODELS_FOLDER
|
||||
#},
|
||||
#{
|
||||
#"name": "default_inverse_scale_factor",
|
||||
#"usage": PROPERTY_USAGE_EDITOR,
|
||||
#"type": TYPE_FLOAT,
|
||||
#"func_godot_type": PROPERTY.DEFAULT_INVERSE_SCALE
|
||||
#}
|
||||
]
|
||||
|
||||
var _settings_dict: Dictionary
|
||||
|
|
@ -117,9 +105,13 @@ func _get_config_property(name: StringName) -> Variant:
|
|||
## Reload this system's configuration settings into the Local Config resource.
|
||||
func reload_func_godot_settings() -> void:
|
||||
_loaded = true
|
||||
var path = _get_path()
|
||||
var path = "user://func_godot_config.json"
|
||||
if not FileAccess.file_exists(path):
|
||||
return
|
||||
var application_name: String = ProjectSettings.get('application/config/name')
|
||||
application_name = application_name.replace(" ", "_")
|
||||
path = "user://" + application_name + "_FuncGodotConfig.json"
|
||||
if not FileAccess.file_exists(path):
|
||||
return
|
||||
var settings = FileAccess.get_file_as_string(path)
|
||||
_settings_dict = {}
|
||||
if not settings or settings.is_empty():
|
||||
|
|
@ -137,14 +129,9 @@ func _try_loading() -> void:
|
|||
func export_func_godot_settings() -> void:
|
||||
if _settings_dict.size() == 0:
|
||||
return
|
||||
var path = _get_path()
|
||||
var path = "user://func_godot_config.json"
|
||||
var file = FileAccess.open(path, FileAccess.WRITE)
|
||||
var json = JSON.stringify(_settings_dict)
|
||||
file.store_line(json)
|
||||
_loaded = false
|
||||
print("Saved settings to ", path)
|
||||
|
||||
func _get_path() -> String:
|
||||
var application_name: String = ProjectSettings.get('application/config/name')
|
||||
application_name = application_name.replace(" ", "_")
|
||||
return 'user://' + application_name + '_FuncGodotConfig.json'
|
||||
print("Saved settings to ", file.get_path_absolute())
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
class_name FuncGodotTextureLoader
|
||||
|
||||
enum PBRSuffix {
|
||||
ALBEDO,
|
||||
NORMAL,
|
||||
METALLIC,
|
||||
ROUGHNESS,
|
||||
EMISSION,
|
||||
AO,
|
||||
HEIGHT,
|
||||
ORM
|
||||
}
|
||||
|
||||
# Suffix string / Godot enum / StandardMaterial3D property
|
||||
const PBR_SUFFIX_NAMES: Dictionary = {
|
||||
PBRSuffix.ALBEDO: 'albedo',
|
||||
PBRSuffix.NORMAL: 'normal',
|
||||
PBRSuffix.METALLIC: 'metallic',
|
||||
PBRSuffix.ROUGHNESS: 'roughness',
|
||||
PBRSuffix.EMISSION: 'emission',
|
||||
PBRSuffix.AO: 'ao',
|
||||
PBRSuffix.HEIGHT: 'height',
|
||||
PBRSuffix.ORM: 'orm'
|
||||
}
|
||||
|
||||
const PBR_SUFFIX_PATTERNS: Dictionary = {
|
||||
PBRSuffix.ALBEDO: '%s_albedo.%s',
|
||||
PBRSuffix.NORMAL: '%s_normal.%s',
|
||||
PBRSuffix.METALLIC: '%s_metallic.%s',
|
||||
PBRSuffix.ROUGHNESS: '%s_roughness.%s',
|
||||
PBRSuffix.EMISSION: '%s_emission.%s',
|
||||
PBRSuffix.AO: '%s_ao.%s',
|
||||
PBRSuffix.HEIGHT: '%s_height.%s',
|
||||
PBRSuffix.ORM: '%s_orm.%s'
|
||||
}
|
||||
|
||||
var PBR_SUFFIX_TEXTURES: Dictionary = {
|
||||
PBRSuffix.ALBEDO: StandardMaterial3D.TEXTURE_ALBEDO,
|
||||
PBRSuffix.NORMAL: StandardMaterial3D.TEXTURE_NORMAL,
|
||||
PBRSuffix.METALLIC: StandardMaterial3D.TEXTURE_METALLIC,
|
||||
PBRSuffix.ROUGHNESS: StandardMaterial3D.TEXTURE_ROUGHNESS,
|
||||
PBRSuffix.EMISSION: StandardMaterial3D.TEXTURE_EMISSION,
|
||||
PBRSuffix.AO: StandardMaterial3D.TEXTURE_AMBIENT_OCCLUSION,
|
||||
PBRSuffix.HEIGHT: StandardMaterial3D.TEXTURE_HEIGHTMAP,
|
||||
PBRSuffix.ORM: ORMMaterial3D.TEXTURE_ORM
|
||||
}
|
||||
|
||||
const PBR_SUFFIX_PROPERTIES: Dictionary = {
|
||||
PBRSuffix.NORMAL: 'normal_enabled',
|
||||
PBRSuffix.EMISSION: 'emission_enabled',
|
||||
PBRSuffix.AO: 'ao_enabled',
|
||||
PBRSuffix.HEIGHT: 'heightmap_enabled',
|
||||
}
|
||||
|
||||
var map_settings: FuncGodotMapSettings = FuncGodotMapSettings.new()
|
||||
var texture_wad_resources: Array = []
|
||||
|
||||
# Overrides
|
||||
func _init(new_map_settings: FuncGodotMapSettings) -> void:
|
||||
map_settings = new_map_settings
|
||||
load_texture_wad_resources()
|
||||
|
||||
# Business Logic
|
||||
func load_texture_wad_resources() -> void:
|
||||
texture_wad_resources.clear()
|
||||
for texture_wad in map_settings.texture_wads:
|
||||
if texture_wad and not texture_wad in texture_wad_resources:
|
||||
texture_wad_resources.append(texture_wad)
|
||||
|
||||
func load_textures(texture_list: Array) -> Dictionary:
|
||||
var texture_dict: Dictionary = {}
|
||||
for texture_name in texture_list:
|
||||
texture_dict[texture_name] = load_texture(texture_name)
|
||||
return texture_dict
|
||||
|
||||
func load_texture(texture_name: String) -> Texture2D:
|
||||
# Load albedo texture if it exists
|
||||
for texture_extension in map_settings.texture_file_extensions:
|
||||
var texture_path: String = "%s/%s.%s" % [map_settings.base_texture_dir, texture_name, texture_extension]
|
||||
if ResourceLoader.exists(texture_path, "Texture2D") or ResourceLoader.exists(texture_path + ".import", "Texture2D"):
|
||||
return load(texture_path) as Texture2D
|
||||
|
||||
var texture_name_lower: String = texture_name.to_lower()
|
||||
for texture_wad in texture_wad_resources:
|
||||
if texture_name_lower in texture_wad.textures:
|
||||
return texture_wad.textures[texture_name_lower]
|
||||
|
||||
return load("res://addons/func_godot/textures/default_texture.png") as Texture2D
|
||||
|
||||
func create_materials(texture_list: Array) -> Dictionary:
|
||||
var texture_materials: Dictionary = {}
|
||||
#prints("TEXLI", texture_list)
|
||||
for texture in texture_list:
|
||||
texture_materials[texture] = create_material(texture)
|
||||
return texture_materials
|
||||
|
||||
func create_material(texture_name: String) -> Material:
|
||||
# Autoload material if it exists
|
||||
var material_dict: Dictionary = {}
|
||||
|
||||
var material_path: String = "%s/%s.%s" % [map_settings.base_texture_dir, texture_name, map_settings.material_file_extension]
|
||||
if not material_path in material_dict and (FileAccess.file_exists(material_path) or FileAccess.file_exists(material_path + ".remap")):
|
||||
var loaded_material: Material = load(material_path)
|
||||
if loaded_material:
|
||||
material_dict[material_path] = loaded_material
|
||||
|
||||
# If material already exists, use it
|
||||
if material_path in material_dict:
|
||||
return material_dict[material_path]
|
||||
|
||||
var material: Material = null
|
||||
|
||||
if map_settings.default_material:
|
||||
material = map_settings.default_material.duplicate()
|
||||
else:
|
||||
material = StandardMaterial3D.new()
|
||||
var texture: Texture2D = load_texture(texture_name)
|
||||
if not texture:
|
||||
return material
|
||||
|
||||
if material is StandardMaterial3D:
|
||||
material.set_texture(StandardMaterial3D.TEXTURE_ALBEDO, texture)
|
||||
elif material is ShaderMaterial && map_settings.default_material_albedo_uniform != "":
|
||||
material.set_shader_parameter(map_settings.default_material_albedo_uniform, texture)
|
||||
elif material is ORMMaterial3D:
|
||||
material.set_texture(ORMMaterial3D.TEXTURE_ALBEDO, texture)
|
||||
|
||||
var pbr_textures : Dictionary = get_pbr_textures(texture_name)
|
||||
|
||||
for pbr_suffix in PBRSuffix.values():
|
||||
var suffix: int = pbr_suffix
|
||||
var tex: Texture2D = pbr_textures[suffix]
|
||||
if tex:
|
||||
if material is ShaderMaterial:
|
||||
material = StandardMaterial3D.new()
|
||||
material.set_texture(StandardMaterial3D.TEXTURE_ALBEDO, texture)
|
||||
var enable_prop: String = PBR_SUFFIX_PROPERTIES[suffix] if suffix in PBR_SUFFIX_PROPERTIES else ""
|
||||
if(enable_prop != ""):
|
||||
material.set(enable_prop, true)
|
||||
material.set_texture(PBR_SUFFIX_TEXTURES[suffix], tex)
|
||||
|
||||
material_dict[material_path] = material
|
||||
|
||||
if (map_settings.save_generated_materials and material
|
||||
and texture_name != map_settings.clip_texture
|
||||
and texture_name != map_settings.skip_texture
|
||||
and texture_name != map_settings.origin_texture
|
||||
and texture.resource_path != "res://addons/func_godot/textures/default_texture.png"):
|
||||
ResourceSaver.save(material, material_path)
|
||||
|
||||
return material
|
||||
|
||||
# PBR texture fetching
|
||||
func get_pbr_suffix_pattern(suffix: int) -> String:
|
||||
if not suffix in PBR_SUFFIX_NAMES:
|
||||
return ''
|
||||
|
||||
var pattern_setting: String = "%s_map_pattern" % [PBR_SUFFIX_NAMES[suffix]]
|
||||
if pattern_setting in map_settings:
|
||||
return map_settings.get(pattern_setting)
|
||||
|
||||
return PBR_SUFFIX_PATTERNS[suffix]
|
||||
|
||||
func get_pbr_texture(texture: String, suffix: PBRSuffix) -> Texture2D:
|
||||
var texture_comps: PackedStringArray = texture.split('/')
|
||||
if texture_comps.size() == 0:
|
||||
return null
|
||||
|
||||
for texture_extension in map_settings.texture_file_extensions:
|
||||
var path: String = "%s/%s/%s" % [
|
||||
map_settings.base_texture_dir,
|
||||
'/'.join(texture_comps),
|
||||
get_pbr_suffix_pattern(suffix) % [
|
||||
texture_comps[-1],
|
||||
texture_extension
|
||||
]
|
||||
]
|
||||
|
||||
if(FileAccess.file_exists(path)):
|
||||
return load(path) as Texture2D
|
||||
|
||||
return null
|
||||
|
||||
func get_pbr_textures(texture_name: String) -> Dictionary:
|
||||
var pbr_textures: Dictionary = {}
|
||||
for pbr_suffix in PBRSuffix.values():
|
||||
pbr_textures[pbr_suffix] = get_pbr_texture(texture_name, pbr_suffix)
|
||||
return pbr_textures
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://c0r8ajf4k061i
|
||||
|
|
@ -80,16 +80,33 @@ const _pbr_textures: PackedInt32Array = [
|
|||
StandardMaterial3D.TEXTURE_EMISSION,
|
||||
StandardMaterial3D.TEXTURE_AMBIENT_OCCLUSION,
|
||||
StandardMaterial3D.TEXTURE_HEIGHTMAP,
|
||||
ORMMaterial3D.TEXTURE_ORM
|
||||
ORMMaterial3D.TEXTURE_ORM,
|
||||
]
|
||||
|
||||
# Used during auto-PBR processing. Must match the _pbr_textures order.
|
||||
# -1 means the feature is permanantly enabled.
|
||||
const _pbr_features: PackedInt32Array = [
|
||||
-1,
|
||||
BaseMaterial3D.FEATURE_NORMAL_MAPPING,
|
||||
-1,
|
||||
-1,
|
||||
BaseMaterial3D.FEATURE_EMISSION,
|
||||
BaseMaterial3D.FEATURE_AMBIENT_OCCLUSION,
|
||||
BaseMaterial3D.FEATURE_HEIGHT_MAPPING,
|
||||
-1,
|
||||
]
|
||||
|
||||
## Searches for a Texture2D within the base texture directory or the WAD files added to map settings.
|
||||
## If not found, a default texture is returned.
|
||||
static func load_texture(texture_name: String, wad_resources: Array[QuakeWadFile], map_settings: FuncGodotMapSettings) -> Texture2D:
|
||||
for texture_file_extension in map_settings.texture_file_extensions:
|
||||
var texture_path: String = map_settings.base_texture_dir.path_join(texture_name + "." + texture_file_extension)
|
||||
if ResourceLoader.exists(texture_path):
|
||||
return load(texture_path)
|
||||
var texture_file = load(texture_path)
|
||||
if texture_file is Texture2D:
|
||||
return texture_file
|
||||
else:
|
||||
printerr("Error: Texture load failed! (%s) not a valid Texture2D resource", texture_path)
|
||||
|
||||
var texture_name_lower: String = texture_name.to_lower()
|
||||
for wad in wad_resources:
|
||||
|
|
@ -131,8 +148,8 @@ static func filter_face(texture: String, map_settings: FuncGodotMapSettings) ->
|
|||
static func build_base_material(map_settings: FuncGodotMapSettings, material: BaseMaterial3D, texture: String) -> void:
|
||||
var path: String = map_settings.base_texture_dir.path_join(texture)
|
||||
# Check if there is a subfolder with our PBR textures
|
||||
if DirAccess.open(path.path_join(path)):
|
||||
path = path.path_join(path)
|
||||
if DirAccess.open(path):
|
||||
path = path.path_join(texture)
|
||||
|
||||
var pbr_suffixes: PackedStringArray = [
|
||||
map_settings.albedo_map_pattern,
|
||||
|
|
@ -145,12 +162,33 @@ static func build_base_material(map_settings: FuncGodotMapSettings, material: Ba
|
|||
map_settings.orm_map_pattern,
|
||||
]
|
||||
|
||||
for texture_file_extension in map_settings.texture_file_extensions:
|
||||
for i in pbr_suffixes.size():
|
||||
if not pbr_suffixes[i].is_empty():
|
||||
var pbr: String = pbr_suffixes[i] % [path, texture_file_extension]
|
||||
if ResourceLoader.exists(pbr):
|
||||
material.set_texture(_pbr_textures[i], load(pbr))
|
||||
for i in pbr_suffixes.size():
|
||||
if not pbr_suffixes[i].is_empty():
|
||||
var pbr: String = pbr_suffixes[i]
|
||||
var token: int = pbr.find("%s", 0)
|
||||
if token != -1:
|
||||
if pbr.find("%s", token + 1) != -1:
|
||||
token = 2
|
||||
else:
|
||||
token = 1
|
||||
|
||||
if token < 1:
|
||||
printerr("No string replacement tokens found in auto-PBR pattern \'" + pbr + "\'! Must have at least one instance of \'%s\' per pattern.")
|
||||
continue
|
||||
|
||||
if token > 0:
|
||||
for texture_file_extension in map_settings.texture_file_extensions:
|
||||
if token > 1:
|
||||
pbr = pbr_suffixes[i] % [path, texture_file_extension]
|
||||
else:
|
||||
pbr = pbr_suffixes[i] % [path]
|
||||
pbr += "." + texture_file_extension
|
||||
if ResourceLoader.exists(pbr):
|
||||
print(pbr)
|
||||
if _pbr_features[i] > -1:
|
||||
material.set_feature(_pbr_features[i], true)
|
||||
material.set_texture(_pbr_textures[i], load(pbr))
|
||||
break
|
||||
|
||||
## Builds both materials and sizes dictionaries for use in the geometry generation step of the build process.
|
||||
## Both dictionaries use texture names as keys. The materials dictionary uses [Material] as values,
|
||||
|
|
@ -202,7 +240,7 @@ static func build_texture_map(entity_data: Array[FuncGodotData.EntityData], map_
|
|||
|
||||
# Material generation
|
||||
elif map_settings.default_material:
|
||||
var material = map_settings.default_material.duplicate(true)
|
||||
var material = map_settings.default_material.duplicate(false)
|
||||
var texture: Texture2D = load_texture(texture_name, wad_resources, map_settings)
|
||||
texture_sizes[texture_name] = texture.get_size()
|
||||
|
||||
|
|
@ -211,12 +249,29 @@ static func build_texture_map(entity_data: Array[FuncGodotData.EntityData], map_
|
|||
build_base_material(map_settings, material, texture_name)
|
||||
elif material is ShaderMaterial:
|
||||
material.set_shader_parameter(map_settings.default_material_albedo_uniform, texture)
|
||||
var path: String = map_settings.base_texture_dir
|
||||
for uniform in map_settings.shader_material_uniform_map_patterns.keys():
|
||||
if map_settings.shader_material_uniform_map_patterns[uniform].find("%s") < 0:
|
||||
printerr("No string replacement tokens fuond in ShaderMaterial uniform map pattern \'" + map_settings.shader_material_uniform_map_patterns[uniform] + "\'! Must have one instance of \'%s\' per pattern.")
|
||||
continue
|
||||
for texture_file_extension in map_settings.texture_file_extensions:
|
||||
var uniform_texture_path: String = map_settings.shader_material_uniform_map_patterns[uniform] % [texture_name] + "." + texture_file_extension
|
||||
uniform_texture_path = path.path_join(uniform_texture_path)
|
||||
if ResourceLoader.exists(uniform_texture_path):
|
||||
material.set_shader_parameter(uniform, load(uniform_texture_path))
|
||||
break
|
||||
|
||||
if (map_settings.save_generated_materials and material
|
||||
and texture_name != map_settings.clip_texture
|
||||
and texture_name != map_settings.skip_texture
|
||||
and texture_name != map_settings.origin_texture
|
||||
and texture.resource_path != default_texture_path):
|
||||
# Make sure our material directory exists
|
||||
var dir := DirAccess.open(material_path.get_base_dir())
|
||||
if not dir:
|
||||
dir = DirAccess.open("res://")
|
||||
dir.make_dir_recursive(material_path.get_base_dir().trim_prefix("res://"))
|
||||
# Save the new material
|
||||
ResourceSaver.save(material, material_path)
|
||||
|
||||
texture_materials[texture_name] = material
|
||||
|
|
@ -232,30 +287,30 @@ static func build_texture_map(entity_data: Array[FuncGodotData.EntityData], map_
|
|||
## Returns UV coordinate calculated from the Valve 220 UV format.
|
||||
static func get_valve_uv(vertex: Vector3, u_axis: Vector3, v_axis: Vector3, uv_basis := Transform2D.IDENTITY, texture_size := Vector2.ONE) -> Vector2:
|
||||
var uv := Vector2(u_axis.dot(vertex), v_axis.dot(vertex))
|
||||
uv += (uv_basis.origin * uv_basis.get_scale())
|
||||
uv.x /= uv_basis.x.x
|
||||
uv.y /= uv_basis.y.y
|
||||
var scale := Vector2(uv_basis.x.x, uv_basis.y.y)
|
||||
uv += (uv_basis.origin * scale)
|
||||
uv /= scale;
|
||||
uv.x /= texture_size.x
|
||||
uv.y /= texture_size.y
|
||||
return uv
|
||||
|
||||
## Returns UV coordinate calculated from the original id Standard UV format.
|
||||
static func get_quake_uv(vertex: Vector3, normal: Vector3, uv_basis := Transform2D.IDENTITY, texture_size := Vector2.ONE) -> Vector2:
|
||||
static func get_quake_uv(vertex: Vector3, normal: Vector3, uv_in := Transform2D.IDENTITY, texture_size := Vector2.ONE) -> Vector2:
|
||||
var uv_out: Vector2
|
||||
var nx := absf(normal.dot(Vector3.RIGHT))
|
||||
var ny := absf(normal.dot(Vector3.UP))
|
||||
var nz := absf(normal.dot(Vector3.FORWARD))
|
||||
var uv: Vector2
|
||||
|
||||
if ny >= nx and ny >= nz:
|
||||
uv = Vector2(vertex.x, -vertex.z)
|
||||
uv_out = Vector2(vertex.x, -vertex.z)
|
||||
elif nx >= ny and nx >= nz:
|
||||
uv = Vector2(vertex.y, -vertex.z)
|
||||
uv_out = Vector2(vertex.y, -vertex.z)
|
||||
else:
|
||||
uv = Vector2(vertex.x, vertex.y)
|
||||
uv_out = Vector2(vertex.x, vertex.y)
|
||||
|
||||
var uv_out := uv.rotated(uv_basis.get_rotation())
|
||||
uv_out /= uv_basis.get_scale()
|
||||
uv_out += uv_basis.origin
|
||||
uv_out = uv_out.rotated(uv_in.get_rotation())
|
||||
uv_out /= uv_in.get_scale()
|
||||
uv_out += uv_in.origin
|
||||
uv_out /= texture_size
|
||||
return uv_out
|
||||
|
||||
|
|
@ -322,10 +377,10 @@ static func get_face_tangent(face: FuncGodotData.FaceData) -> PackedFloat32Array
|
|||
|
||||
#region MESH
|
||||
|
||||
static func smooth_mesh_by_angle(mesh: ArrayMesh, angle_deg: float = 89.0) -> Mesh:
|
||||
static func smooth_mesh_by_angle(mesh: ArrayMesh, angle_deg: float = 89.0) -> ArrayMesh:
|
||||
if not mesh:
|
||||
push_error("Need a source mesh to smooth")
|
||||
return
|
||||
return null
|
||||
|
||||
var angle: float = deg_to_rad(clampf(angle_deg, 0.0, 360.0))
|
||||
|
||||
|
|
|
|||
|
|
@ -3,21 +3,23 @@
|
|||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dhmu0toe1itnr"
|
||||
path="res://.godot/imported/clip.png-508a86fa3876d8467d5c9af6188a34df.ctex"
|
||||
path="res://.godot/imported/clip.png-747f19bfc6fe499e254d63eeccc3bbe4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/textures/special/clip.png"
|
||||
dest_files=["res://.godot/imported/clip.png-508a86fa3876d8467d5c9af6188a34df.ctex"]
|
||||
source_file="res://addons/func_godot/textures/clip.png"
|
||||
dest_files=["res://.godot/imported/clip.png-747f19bfc6fe499e254d63eeccc3bbe4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=3
|
||||
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
|
||||
|
|
@ -25,6 +27,10 @@ mipmaps/generate=false
|
|||
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
|
||||
|
|
@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/default_texture.png-145fbd5fef7f63ace60797fec
|
|||
compress/mode=3
|
||||
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
|
||||
|
|
@ -25,6 +27,10 @@ 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
|
||||
|
|
|
|||
|
|
@ -3,21 +3,23 @@
|
|||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dutip72dl002r"
|
||||
path="res://.godot/imported/origin.png-85b62dd151467f05fa8f98ed6d2927d0.ctex"
|
||||
path="res://.godot/imported/origin.png-501c9242087fbbeb03d876a682f0832c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/textures/special/origin.png"
|
||||
dest_files=["res://.godot/imported/origin.png-85b62dd151467f05fa8f98ed6d2927d0.ctex"]
|
||||
source_file="res://addons/func_godot/textures/origin.png"
|
||||
dest_files=["res://.godot/imported/origin.png-501c9242087fbbeb03d876a682f0832c.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
|
||||
|
|
@ -25,6 +27,10 @@ mipmaps/generate=false
|
|||
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
|
||||
|
|
@ -3,21 +3,23 @@
|
|||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bk5oo263y3u7w"
|
||||
path="res://.godot/imported/skip.png-d741e3eb75a5e289907774cb73d93931.ctex"
|
||||
path="res://.godot/imported/skip.png-b8ce29eeb9f4be76c300a81fd41322fa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/textures/special/skip.png"
|
||||
dest_files=["res://.godot/imported/skip.png-d741e3eb75a5e289907774cb73d93931.ctex"]
|
||||
source_file="res://addons/func_godot/textures/skip.png"
|
||||
dest_files=["res://.godot/imported/skip.png-b8ce29eeb9f4be76c300a81fd41322fa.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=3
|
||||
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
|
||||
|
|
@ -25,6 +27,10 @@ mipmaps/generate=false
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue