Isometric test

This commit is contained in:
Marco 2025-06-09 18:57:53 +02:00
commit 1434f7aa6d
806 changed files with 18966 additions and 15 deletions

View file

@ -0,0 +1,136 @@
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

View file

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

View file

@ -0,0 +1,381 @@
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)

View file

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

View file

@ -0,0 +1,158 @@
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

View file

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

View file

@ -0,0 +1,326 @@
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
}

View file

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

View file

@ -0,0 +1,217 @@
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
}

View file

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