├── .editorconfig
├── .gitattributes
├── .gitignore
├── Autoloads
└── PortalSignals.cs
├── ExtensionMethods
├── Polygon2DExtensionMethods.cs
└── VectorExtensionMethods.cs
├── LICENSE
├── Maps
├── Assets
│ ├── MapConstructor.cs
│ ├── PortalableSurface.cs
│ └── PortalableSurface.tscn
├── TestChamber04
│ ├── Map.blend
│ ├── Map.glb
│ ├── Map.glb.import
│ ├── Map.gltf.import
│ ├── Map_FloorCeiling.png
│ ├── Map_FloorCeiling.png.import
│ ├── Map_Wall.png
│ ├── Map_Wall.png.import
│ └── TestChamber04.tscn
└── Textures
│ └── Walls
│ ├── FloorCeiling.png
│ ├── FloorCeiling.png.import
│ ├── Wall.png
│ └── Wall.png.import
├── Objects
├── Player
│ ├── Player.cs
│ └── Player.tscn
├── PortalManager
│ ├── PortalManager.cs
│ └── PortalManager.tscn
├── PortalObject
│ ├── Mesh
│ │ ├── PortalMesh.blend
│ │ ├── PortalMesh.glb
│ │ ├── PortalMesh.glb.import
│ │ └── PortalMeshShape.tres
│ ├── PortalMaterial.tres
│ ├── PortalObject.cs
│ ├── PortalObject.tscn
│ ├── PortalShader.tres
│ ├── UnlinkedMaterial.tres
│ ├── UnlinkedTexture.png
│ └── UnlinkedTexture.png.import
└── PortalTraveller
│ └── IPortalTraveller.cs
├── Portal.csproj
├── Portal.sln
├── Properties
└── launchSettings.json
├── README.md
├── icon.svg
├── icon.svg.import
└── project.godot
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Version: 4.1.1 (Using https://semver.org/)
2 | # Updated: 2022-05-23
3 | # See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
4 | # See https://github.com/RehanSaeed/EditorConfig for updates to this file.
5 | # See http://EditorConfig.org for more information about .editorconfig files.
6 |
7 | ##########################################
8 | # Common Settings
9 | ##########################################
10 |
11 | # This file is the top-most EditorConfig file
12 | root = true
13 |
14 | # All Files
15 | [*]
16 | charset = utf-8
17 | indent_style = space
18 | indent_size = 4
19 | insert_final_newline = true
20 | trim_trailing_whitespace = true
21 |
22 | ##########################################
23 | # File Extension Settings
24 | ##########################################
25 |
26 | # Visual Studio Solution Files
27 | [*.sln]
28 | indent_style = tab
29 |
30 | # Visual Studio XML Project Files
31 | [*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
32 | indent_size = 2
33 |
34 | # XML Configuration Files
35 | [*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
36 | indent_size = 2
37 |
38 | # JSON Files
39 | [*.{json,json5,webmanifest}]
40 | indent_size = 2
41 |
42 | # YAML Files
43 | [*.{yml,yaml}]
44 | indent_size = 2
45 |
46 | # Markdown Files
47 | [*.{md,mdx}]
48 | trim_trailing_whitespace = false
49 |
50 | # Web Files
51 | [*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}]
52 | indent_size = 2
53 |
54 | # Batch Files
55 | [*.{cmd,bat}]
56 | end_of_line = crlf
57 |
58 | # Bash Files
59 | [*.sh]
60 | end_of_line = lf
61 |
62 | # Makefiles
63 | [Makefile]
64 | indent_style = tab
65 |
66 | ##########################################
67 | # Default .NET Code Style Severities
68 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope
69 | ##########################################
70 |
71 | [*.{cs,csx,cake,vb,vbx}]
72 | # Default Severity for all .NET Code Style rules below
73 | dotnet_analyzer_diagnostic.severity = warning
74 |
75 | ##########################################
76 | # Language Rules
77 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules
78 | ##########################################
79 |
80 | # .NET Style Rules
81 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules
82 | [*.{cs,csx,cake,vb,vbx}]
83 | # "this." and "Me." qualifiers
84 | dotnet_style_qualification_for_field = false:warning
85 | dotnet_style_qualification_for_property = false:warning
86 | dotnet_style_qualification_for_method = false:warning
87 | dotnet_style_qualification_for_event = false:warning
88 | # Language keywords instead of framework type names for type references
89 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
90 | dotnet_style_predefined_type_for_member_access = true:warning
91 | # Modifier preferences
92 | dotnet_style_require_accessibility_modifiers = always:warning
93 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
94 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning
95 | dotnet_style_readonly_field = true:warning
96 | # Parentheses preferences
97 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:warning
98 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:warning
99 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:warning
100 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning
101 | # Expression-level preferences
102 | dotnet_style_object_initializer = true:warning
103 | dotnet_style_collection_initializer = true:warning
104 | dotnet_style_explicit_tuple_names = true:warning
105 | dotnet_style_prefer_inferred_tuple_names = true:warning
106 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
107 | dotnet_style_prefer_auto_properties = true:warning
108 | dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
109 | dotnet_diagnostic.IDE0045.severity = suggestion
110 | dotnet_style_prefer_conditional_expression_over_return = false:suggestion
111 | dotnet_diagnostic.IDE0046.severity = suggestion
112 | dotnet_style_prefer_compound_assignment = true:warning
113 | dotnet_style_prefer_simplified_interpolation = true:warning
114 | dotnet_style_prefer_simplified_boolean_expressions = true:warning
115 | # Null-checking preferences
116 | dotnet_style_coalesce_expression = true:warning
117 | dotnet_style_null_propagation = true:warning
118 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
119 | # File header preferences
120 | # file_header_template = \n© PROJECT-AUTHOR\n
121 | # If you use StyleCop, you'll need to disable SA1636: File header copyright text should match.
122 | # dotnet_diagnostic.SA1636.severity = none
123 | # Undocumented
124 | dotnet_style_operator_placement_when_wrapping = end_of_line:warning
125 | csharp_style_prefer_null_check_over_type_check = true:warning
126 |
127 | # C# Style Rules
128 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
129 | [*.{cs,csx,cake}]
130 | # 'var' preferences
131 | csharp_style_var_for_built_in_types = true:warning
132 | csharp_style_var_when_type_is_apparent = true:warning
133 | csharp_style_var_elsewhere = true:warning
134 | # Expression-bodied members
135 | csharp_style_expression_bodied_methods = false:warning
136 | csharp_style_expression_bodied_constructors = false:warning
137 | csharp_style_expression_bodied_operators = true:warning
138 | csharp_style_expression_bodied_properties = true:warning
139 | csharp_style_expression_bodied_indexers = true:warning
140 | csharp_style_expression_bodied_accessors = true:warning
141 | csharp_style_expression_bodied_lambdas = true:warning
142 | csharp_style_expression_bodied_local_functions = true:warning
143 | # Pattern matching preferences
144 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
145 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
146 | csharp_style_prefer_switch_expression = true:warning
147 | csharp_style_prefer_pattern_matching = true:warning
148 | csharp_style_prefer_not_pattern = true:warning
149 | # Expression-level preferences
150 | csharp_style_inlined_variable_declaration = true:warning
151 | csharp_prefer_simple_default_expression = true:warning
152 | csharp_style_pattern_local_over_anonymous_function = true:warning
153 | csharp_style_deconstructed_variable_declaration = true:warning
154 | csharp_style_prefer_index_operator = true:warning
155 | csharp_style_prefer_range_operator = true:warning
156 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
157 | # "Null" checking preferences
158 | csharp_style_throw_expression = true:warning
159 | csharp_style_conditional_delegate_call = true:warning
160 | # Code block preferences
161 | csharp_prefer_braces = false:warning
162 | csharp_prefer_simple_using_statement = true:suggestion
163 | dotnet_diagnostic.IDE0063.severity = suggestion
164 | # 'using' directive preferences
165 | csharp_using_directive_placement = inside_namespace:warning
166 | # Modifier preferences
167 | csharp_prefer_static_local_function = true:warning
168 |
169 | ##########################################
170 | # Unnecessary Code Rules
171 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules
172 | ##########################################
173 |
174 | # .NET Unnecessary code rules
175 | [*.{cs,csx,cake,vb,vbx}]
176 | dotnet_code_quality_unused_parameters = all:warning
177 | dotnet_remove_unnecessary_suppression_exclusions = none:warning
178 |
179 | # C# Unnecessary code rules
180 | [*.{cs,csx,cake}]
181 | csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
182 | dotnet_diagnostic.IDE0058.severity = suggestion
183 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
184 | dotnet_diagnostic.IDE0059.severity = suggestion
185 |
186 | ##########################################
187 | # Formatting Rules
188 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules
189 | ##########################################
190 |
191 | # .NET formatting rules
192 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#net-formatting-rules
193 | [*.{cs,csx,cake,vb,vbx}]
194 | # Organize using directives
195 | dotnet_sort_system_directives_first = true
196 | dotnet_separate_import_directive_groups = false
197 | # Dotnet namespace options
198 | dotnet_style_namespace_match_folder = true:suggestion
199 | dotnet_diagnostic.IDE0130.severity = suggestion
200 |
201 | # C# formatting rules
202 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules
203 | [*.{cs,csx,cake}]
204 | # Newline options
205 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#new-line-options
206 | csharp_new_line_before_open_brace = all
207 | csharp_new_line_before_else = true
208 | csharp_new_line_before_catch = true
209 | csharp_new_line_before_finally = true
210 | csharp_new_line_before_members_in_object_initializers = true
211 | csharp_new_line_before_members_in_anonymous_types = true
212 | csharp_new_line_between_query_expression_clauses = true
213 | # Indentation options
214 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#indentation-options
215 | csharp_indent_case_contents = true
216 | csharp_indent_switch_labels = true
217 | csharp_indent_labels = no_change
218 | csharp_indent_block_contents = true
219 | csharp_indent_braces = false
220 | csharp_indent_case_contents_when_block = false
221 | # Spacing options
222 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#spacing-options
223 | csharp_space_after_cast = false
224 | csharp_space_after_keywords_in_control_flow_statements = true
225 | csharp_space_between_parentheses = false
226 | csharp_space_before_colon_in_inheritance_clause = true
227 | csharp_space_after_colon_in_inheritance_clause = true
228 | csharp_space_around_binary_operators = before_and_after
229 | csharp_space_between_method_declaration_parameter_list_parentheses = false
230 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
231 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
232 | csharp_space_between_method_call_parameter_list_parentheses = false
233 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
234 | csharp_space_between_method_call_name_and_opening_parenthesis = false
235 | csharp_space_after_comma = true
236 | csharp_space_before_comma = false
237 | csharp_space_after_dot = false
238 | csharp_space_before_dot = false
239 | csharp_space_after_semicolon_in_for_statement = true
240 | csharp_space_before_semicolon_in_for_statement = false
241 | csharp_space_around_declaration_statements = false
242 | csharp_space_before_open_square_brackets = false
243 | csharp_space_between_empty_square_brackets = false
244 | csharp_space_between_square_brackets = false
245 | # Wrap options
246 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#wrap-options
247 | csharp_preserve_single_line_statements = false
248 | csharp_preserve_single_line_blocks = true
249 | # Namespace options
250 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#namespace-options
251 | csharp_style_namespace_declarations = file_scoped:warning
252 |
253 | ##########################################
254 | # .NET Naming Rules
255 | # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules
256 | ##########################################
257 |
258 | [*.{cs,csx,cake,vb,vbx}]
259 |
260 | ##########################################
261 | # Styles
262 | ##########################################
263 |
264 | # camel_case_style - Define the camelCase style
265 | dotnet_naming_style.camel_case_style.capitalization = camel_case
266 | # pascal_case_style - Define the PascalCase style
267 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
268 | # first_upper_style - The first character must start with an upper-case character
269 | dotnet_naming_style.first_upper_style.capitalization = first_word_upper
270 | # prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I'
271 | dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case
272 | dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I
273 | # prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T'
274 | dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case
275 | dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
276 | # disallowed_style - Anything that has this style applied is marked as disallowed
277 | dotnet_naming_style.disallowed_style.capitalization = pascal_case
278 | dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
279 | dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
280 | # internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file
281 | dotnet_naming_style.internal_error_style.capitalization = pascal_case
282 | dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____
283 | dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____
284 |
285 | ##########################################
286 | # .NET Design Guideline Field Naming Rules
287 | # Naming rules for fields follow the .NET Framework design guidelines
288 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/index
289 | ##########################################
290 |
291 | # All public/protected/protected_internal constant fields must be PascalCase
292 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field
293 | dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal
294 | dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const
295 | dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field
296 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group
297 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style
298 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning
299 |
300 | # All public/protected/protected_internal static readonly fields must be PascalCase
301 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field
302 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal
303 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly
304 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field
305 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group
306 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style
307 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning
308 |
309 | # No other public/protected/protected_internal fields are allowed
310 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field
311 | dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal
312 | dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field
313 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group
314 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style
315 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error
316 |
317 | ##########################################
318 | # StyleCop Field Naming Rules
319 | # Naming rules for fields follow the StyleCop analyzers
320 | # This does not override any rules using disallowed_style above
321 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers
322 | ##########################################
323 |
324 | # All constant fields must be PascalCase
325 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md
326 | dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
327 | dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const
328 | dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field
329 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group
330 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style
331 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning
332 |
333 | # All static readonly fields must be PascalCase
334 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md
335 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
336 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly
337 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field
338 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group
339 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style
340 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning
341 |
342 | # No non-private instance fields are allowed
343 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md
344 | dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected
345 | dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field
346 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group
347 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style
348 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error
349 |
350 | # Private fields must be camelCase
351 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md
352 | dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private
353 | dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field
354 | dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group
355 | dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style
356 | dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning
357 |
358 | # Local variables must be camelCase
359 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md
360 | dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local
361 | dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local
362 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group
363 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style
364 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent
365 |
366 | # This rule should never fire. However, it's included for at least two purposes:
367 | # First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers.
368 | # Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#).
369 | dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = *
370 | dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field
371 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group
372 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style
373 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error
374 |
375 |
376 | ##########################################
377 | # Other Naming Rules
378 | ##########################################
379 |
380 | # All of the following must be PascalCase:
381 | # - Namespaces
382 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces
383 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
384 | # - Classes and Enumerations
385 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
386 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
387 | # - Delegates
388 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types
389 | # - Constructors, Properties, Events, Methods
390 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members
391 | dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property
392 | dotnet_naming_rule.element_rule.symbols = element_group
393 | dotnet_naming_rule.element_rule.style = pascal_case_style
394 | dotnet_naming_rule.element_rule.severity = warning
395 |
396 | # Interfaces use PascalCase and are prefixed with uppercase 'I'
397 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
398 | dotnet_naming_symbols.interface_group.applicable_kinds = interface
399 | dotnet_naming_rule.interface_rule.symbols = interface_group
400 | dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style
401 | dotnet_naming_rule.interface_rule.severity = warning
402 |
403 | # Generics Type Parameters use PascalCase and are prefixed with uppercase 'T'
404 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
405 | dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter
406 | dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group
407 | dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style
408 | dotnet_naming_rule.type_parameter_rule.severity = warning
409 |
410 | # Function parameters use camelCase
411 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters
412 | dotnet_naming_symbols.parameters_group.applicable_kinds = parameter
413 | dotnet_naming_rule.parameters_rule.symbols = parameters_group
414 | dotnet_naming_rule.parameters_rule.style = camel_case_style
415 | dotnet_naming_rule.parameters_rule.severity = warning
416 |
417 | ##########################################
418 | # License
419 | ##########################################
420 | # The following applies as to the .editorconfig file ONLY, and is
421 | # included below for reference, per the requirements of the license
422 | # corresponding to this .editorconfig file.
423 | # See: https://github.com/RehanSaeed/EditorConfig
424 | #
425 | # MIT License
426 | #
427 | # Copyright (c) 2017-2019 Muhammad Rehan Saeed
428 | # Copyright (c) 2019 Henry Gabryjelski
429 | #
430 | # Permission is hereby granted, free of charge, to any
431 | # person obtaining a copy of this software and associated
432 | # documentation files (the "Software"), to deal in the
433 | # Software without restriction, including without limitation
434 | # the rights to use, copy, modify, merge, publish, distribute,
435 | # sublicense, and/or sell copies of the Software, and to permit
436 | # persons to whom the Software is furnished to do so, subject
437 | # to the following conditions:
438 | #
439 | # The above copyright notice and this permission notice shall be
440 | # included in all copies or substantial portions of the Software.
441 | #
442 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
443 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
444 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
445 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
446 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
447 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
448 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
449 | # OTHER DEALINGS IN THE SOFTWARE.
450 | ##########################################
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 | *.old
4 |
5 | # Blender specific ignores
6 | *.blend?
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 | ##
11 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
12 |
13 | # User-specific files
14 | *.rsuser
15 | *.suo
16 | *.user
17 | *.userosscache
18 | *.sln.docstates
19 |
20 | # User-specific files (MonoDevelop/Xamarin Studio)
21 | *.userprefs
22 |
23 | # Mono auto generated files
24 | mono_crash.*
25 |
26 | # Build results
27 | [Dd]ebug/
28 | [Dd]ebugPublic/
29 | [Rr]elease/
30 | [Rr]eleases/
31 | x64/
32 | x86/
33 | [Ww][Ii][Nn]32/
34 | [Aa][Rr][Mm]/
35 | [Aa][Rr][Mm]64/
36 | bld/
37 | [Bb]in/
38 | [Oo]bj/
39 | [Ll]og/
40 | [Ll]ogs/
41 |
42 | # Visual Studio 2015/2017 cache/options directory
43 | .vs/
44 | # Uncomment if you have tasks that create the project's static files in wwwroot
45 | #wwwroot/
46 |
47 | # Visual Studio 2017 auto generated files
48 | Generated\ Files/
49 |
50 | # MSTest test Results
51 | [Tt]est[Rr]esult*/
52 | [Bb]uild[Ll]og.*
53 |
54 | # NUnit
55 | *.VisualState.xml
56 | TestResult.xml
57 | nunit-*.xml
58 |
59 | # Build Results of an ATL Project
60 | [Dd]ebugPS/
61 | [Rr]eleasePS/
62 | dlldata.c
63 |
64 | # Benchmark Results
65 | BenchmarkDotNet.Artifacts/
66 |
67 | # .NET Core
68 | project.lock.json
69 | project.fragment.lock.json
70 | artifacts/
71 |
72 | # ASP.NET Scaffolding
73 | ScaffoldingReadMe.txt
74 |
75 | # StyleCop
76 | StyleCopReport.xml
77 |
78 | # Files built by Visual Studio
79 | *_i.c
80 | *_p.c
81 | *_h.h
82 | *.ilk
83 | *.meta
84 | *.obj
85 | *.iobj
86 | *.pch
87 | *.pdb
88 | *.ipdb
89 | *.pgc
90 | *.pgd
91 | *.rsp
92 | *.sbr
93 | *.tlb
94 | *.tli
95 | *.tlh
96 | *.tmp
97 | *.tmp_proj
98 | *_wpftmp.csproj
99 | *.log
100 | *.tlog
101 | *.vspscc
102 | *.vssscc
103 | .builds
104 | *.pidb
105 | *.svclog
106 | *.scc
107 |
108 | # Chutzpah Test files
109 | _Chutzpah*
110 |
111 | # Visual C++ cache files
112 | ipch/
113 | *.aps
114 | *.ncb
115 | *.opendb
116 | *.opensdf
117 | *.sdf
118 | *.cachefile
119 | *.VC.db
120 | *.VC.VC.opendb
121 |
122 | # Visual Studio profiler
123 | *.psess
124 | *.vsp
125 | *.vspx
126 | *.sap
127 |
128 | # Visual Studio Trace Files
129 | *.e2e
130 |
131 | # TFS 2012 Local Workspace
132 | $tf/
133 |
134 | # Guidance Automation Toolkit
135 | *.gpState
136 |
137 | # ReSharper is a .NET coding add-in
138 | _ReSharper*/
139 | *.[Rr]e[Ss]harper
140 | *.DotSettings.user
141 |
142 | # TeamCity is a build add-in
143 | _TeamCity*
144 |
145 | # DotCover is a Code Coverage Tool
146 | *.dotCover
147 |
148 | # AxoCover is a Code Coverage Tool
149 | .axoCover/*
150 | !.axoCover/settings.json
151 |
152 | # Coverlet is a free, cross platform Code Coverage Tool
153 | coverage*.json
154 | coverage*.xml
155 | coverage*.info
156 |
157 | # Visual Studio code coverage results
158 | *.coverage
159 | *.coveragexml
160 |
161 | # NCrunch
162 | _NCrunch_*
163 | .*crunch*.local.xml
164 | nCrunchTemp_*
165 |
166 | # MightyMoose
167 | *.mm.*
168 | AutoTest.Net/
169 |
170 | # Web workbench (sass)
171 | .sass-cache/
172 |
173 | # Installshield output folder
174 | [Ee]xpress/
175 |
176 | # DocProject is a documentation generator add-in
177 | DocProject/buildhelp/
178 | DocProject/Help/*.HxT
179 | DocProject/Help/*.HxC
180 | DocProject/Help/*.hhc
181 | DocProject/Help/*.hhk
182 | DocProject/Help/*.hhp
183 | DocProject/Help/Html2
184 | DocProject/Help/html
185 |
186 | # Click-Once directory
187 | publish/
188 |
189 | # Publish Web Output
190 | *.[Pp]ublish.xml
191 | *.azurePubxml
192 | # Note: Comment the next line if you want to checkin your web deploy settings,
193 | # but database connection strings (with potential passwords) will be unencrypted
194 | *.pubxml
195 | *.publishproj
196 |
197 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
198 | # checkin your Azure Web App publish settings, but sensitive information contained
199 | # in these scripts will be unencrypted
200 | PublishScripts/
201 |
202 | # NuGet Packages
203 | *.nupkg
204 | # NuGet Symbol Packages
205 | *.snupkg
206 | # The packages folder can be ignored because of Package Restore
207 | **/[Pp]ackages/*
208 | # except build/, which is used as an MSBuild target.
209 | !**/[Pp]ackages/build/
210 | # Uncomment if necessary however generally it will be regenerated when needed
211 | #!**/[Pp]ackages/repositories.config
212 | # NuGet v3's project.json files produces more ignorable files
213 | *.nuget.props
214 | *.nuget.targets
215 |
216 | # Microsoft Azure Build Output
217 | csx/
218 | *.build.csdef
219 |
220 | # Microsoft Azure Emulator
221 | ecf/
222 | rcf/
223 |
224 | # Windows Store app package directories and files
225 | AppPackages/
226 | BundleArtifacts/
227 | Package.StoreAssociation.xml
228 | _pkginfo.txt
229 | *.appx
230 | *.appxbundle
231 | *.appxupload
232 |
233 | # Visual Studio cache files
234 | # files ending in .cache can be ignored
235 | *.[Cc]ache
236 | # but keep track of directories ending in .cache
237 | !?*.[Cc]ache/
238 |
239 | # Others
240 | ClientBin/
241 | ~$*
242 | *~
243 | *.dbmdl
244 | *.dbproj.schemaview
245 | *.jfm
246 | *.pfx
247 | *.publishsettings
248 | orleans.codegen.cs
249 |
250 | # Including strong name files can present a security risk
251 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
252 | #*.snk
253 |
254 | # Since there are multiple workflows, uncomment next line to ignore bower_components
255 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
256 | #bower_components/
257 |
258 | # RIA/Silverlight projects
259 | Generated_Code/
260 |
261 | # Backup & report files from converting an old project file
262 | # to a newer Visual Studio version. Backup files are not needed,
263 | # because we have git ;-)
264 | _UpgradeReport_Files/
265 | Backup*/
266 | UpgradeLog*.XML
267 | UpgradeLog*.htm
268 | ServiceFabricBackup/
269 | *.rptproj.bak
270 |
271 | # SQL Server files
272 | *.mdf
273 | *.ldf
274 | *.ndf
275 |
276 | # Business Intelligence projects
277 | *.rdl.data
278 | *.bim.layout
279 | *.bim_*.settings
280 | *.rptproj.rsuser
281 | *- [Bb]ackup.rdl
282 | *- [Bb]ackup ([0-9]).rdl
283 | *- [Bb]ackup ([0-9][0-9]).rdl
284 |
285 | # Microsoft Fakes
286 | FakesAssemblies/
287 |
288 | # GhostDoc plugin setting file
289 | *.GhostDoc.xml
290 |
291 | # Node.js Tools for Visual Studio
292 | .ntvs_analysis.dat
293 | node_modules/
294 |
295 | # Visual Studio 6 build log
296 | *.plg
297 |
298 | # Visual Studio 6 workspace options file
299 | *.opt
300 |
301 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
302 | *.vbw
303 |
304 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
305 | *.vbp
306 |
307 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
308 | *.dsw
309 | *.dsp
310 |
311 | # Visual Studio 6 technical files
312 | *.ncb
313 | *.aps
314 |
315 | # Visual Studio LightSwitch build output
316 | **/*.HTMLClient/GeneratedArtifacts
317 | **/*.DesktopClient/GeneratedArtifacts
318 | **/*.DesktopClient/ModelManifest.xml
319 | **/*.Server/GeneratedArtifacts
320 | **/*.Server/ModelManifest.xml
321 | _Pvt_Extensions
322 |
323 | # Paket dependency manager
324 | .paket/paket.exe
325 | paket-files/
326 |
327 | # FAKE - F# Make
328 | .fake/
329 |
330 | # CodeRush personal settings
331 | .cr/personal
332 |
333 | # Python Tools for Visual Studio (PTVS)
334 | __pycache__/
335 | *.pyc
336 |
337 | # Cake - Uncomment if you are using it
338 | # tools/**
339 | # !tools/packages.config
340 |
341 | # Tabs Studio
342 | *.tss
343 |
344 | # Telerik's JustMock configuration file
345 | *.jmconfig
346 |
347 | # BizTalk build output
348 | *.btp.cs
349 | *.btm.cs
350 | *.odx.cs
351 | *.xsd.cs
352 |
353 | # OpenCover UI analysis results
354 | OpenCover/
355 |
356 | # Azure Stream Analytics local run output
357 | ASALocalRun/
358 |
359 | # MSBuild Binary and Structured Log
360 | *.binlog
361 |
362 | # NVidia Nsight GPU debugger configuration file
363 | *.nvuser
364 |
365 | # MFractors (Xamarin productivity tool) working folder
366 | .mfractor/
367 |
368 | # Local History for Visual Studio
369 | .localhistory/
370 |
371 | # Visual Studio History (VSHistory) files
372 | .vshistory/
373 |
374 | # BeatPulse healthcheck temp database
375 | healthchecksdb
376 |
377 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
378 | MigrationBackup/
379 |
380 | # Ionide (cross platform F# VS Code tools) working folder
381 | .ionide/
382 |
383 | # Fody - auto-generated XML schema
384 | FodyWeavers.xsd
385 |
386 | # VS Code files for those working on multiple tools
387 | .vscode/*
388 | !.vscode/settings.json
389 | !.vscode/tasks.json
390 | !.vscode/launch.json
391 | !.vscode/extensions.json
392 | *.code-workspace
393 |
394 | # Local History for Visual Studio Code
395 | .history/
396 |
397 | # Windows Installer files from build outputs
398 | *.cab
399 | *.msi
400 | *.msix
401 | *.msm
402 | *.msp
403 |
404 | # JetBrains Rider
405 | *.sln.iml
406 |
--------------------------------------------------------------------------------
/Autoloads/PortalSignals.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Autoloads;
2 |
3 | using System;
4 | using Godot;
5 | using Portal.Maps.Assets;
6 | using Portal.Objects;
7 |
8 | public partial class PortalSignals : Node
9 | {
10 | public Action PortalShot { get; set; }
11 | public Action PortalRemoved { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/ExtensionMethods/Polygon2DExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.ExtensionMethods;
2 |
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Clipper2Lib;
6 | using Godot;
7 | using Poly2Tri.Triangulation.Polygon;
8 | using Poly2Tri;
9 |
10 | internal static class Polygon2DExtensionMethods
11 | {
12 | public static IEnumerable ToPolygon(this IEnumerable triangles)
13 | {
14 | var trianglePaths = triangles.ToPath();
15 | var polygons = Clipper.Union(trianglePaths, FillRule.NonZero);
16 |
17 | return polygons.Select(n => n.ToPolygon());
18 | }
19 |
20 | public static Polygon2D ToPolygon(this PathD path)
21 | {
22 | return new Polygon2D()
23 | {
24 | Polygon = path.Select(n => new Vector2((float)n.x, (float)n.y)).ToArray()
25 | };
26 | }
27 |
28 | public static IEnumerable ToPolygon(this PathsD paths)
29 | {
30 | return paths.Select(n => n.ToPolygon());
31 | }
32 |
33 | public static Polygon2D ToPolgyon(this IEnumerable points)
34 | {
35 | return new Polygon2D() { Polygon = points.ToArray() };
36 | }
37 |
38 | public static IEnumerable Clip(
39 | this IEnumerable polygonA,
40 | IEnumerable polygonB
41 | )
42 | {
43 | var pathA = polygonA.ToPath();
44 | var pathB = polygonB.ToPath();
45 |
46 | return Clipper.Difference(pathA, pathB, FillRule.NonZero).ToPolygon();
47 | }
48 |
49 | public static IEnumerable Triangulate(this IEnumerable polygons)
50 | {
51 | var positivePolygons = polygons.Where(n => Clipper.IsPositive(n.ToPath()));
52 | var negativePolygons = polygons.Where(n => !Clipper.IsPositive(n.ToPath()));
53 |
54 | var triangles = new List();
55 | foreach (var positivePolygon in positivePolygons)
56 | {
57 | var polygon = new Polygon(
58 | positivePolygon.Polygon.Select(point => new PolygonPoint(point.X, point.Y))
59 | );
60 |
61 | foreach (var negativePolygon in negativePolygons)
62 | if (
63 | negativePolygon
64 | .ToPath()
65 | .ToPathsD()
66 | .IsApproximatelyEntirelyContainedBy(
67 | positivePolygon.ToPath().ToPathsD(),
68 | 0.05f
69 | )
70 | )
71 | {
72 | polygon.AddHole(
73 | new Polygon(
74 | negativePolygon.Polygon.Select(
75 | point => new PolygonPoint(point.X, point.Y)
76 | )
77 | )
78 | );
79 | }
80 |
81 | P2T.Triangulate(polygon);
82 | triangles.AddRange(
83 | polygon.Triangles.Select(
84 | n =>
85 | new Polygon2D()
86 | {
87 | Polygon = n.Points
88 | .Select(p => new Vector2((float)p.X, (float)p.Y))
89 | .ToArray()
90 | }
91 | )
92 | );
93 | }
94 |
95 | return triangles;
96 | }
97 |
98 | public static Polygon2D Inflate(this Polygon2D polygon, float delta)
99 | {
100 | var path = new PathsD { polygon.ToPath() };
101 | return Clipper
102 | .InflatePaths(path, delta, JoinType.Square, EndType.Polygon)
103 | .ToPolygon()
104 | .First();
105 | }
106 |
107 | public static IEnumerable Inflate(this IEnumerable polygons, float delta)
108 | {
109 | var paths = polygons.ToPath();
110 | return Clipper.InflatePaths(paths, delta, JoinType.Square, EndType.Polygon).ToPolygon();
111 | }
112 |
113 | public static PathD ToPath(this Polygon2D polygon)
114 | {
115 | return new PathD(polygon.Polygon.Select(n => new PointD(n.X, n.Y)));
116 | }
117 |
118 | public static PathsD ToPath(this IEnumerable polygons)
119 | {
120 | return new PathsD(polygons.Select(n => n.ToPath()));
121 | }
122 |
123 | public static bool IsApproximatelyEqualTo(this PathsD left, PathsD right, double areaDelta)
124 | {
125 | var difference = Clipper.Difference(left, right, FillRule.NonZero);
126 | return Clipper.Area(difference) < areaDelta;
127 | }
128 |
129 | public static bool IsEqualTo(this PathsD left, PathsD right)
130 | {
131 | return left.IsApproximatelyEqualTo(right, 0);
132 | }
133 |
134 | public static bool IsEntirelyContainedBy(this PathsD inner, PathsD outer)
135 | {
136 | return Clipper.Union(inner, outer, FillRule.NonZero).IsEqualTo(outer);
137 | }
138 |
139 | public static bool IsApproximatelyEntirelyContainedBy(
140 | this PathsD inner,
141 | PathsD outer,
142 | double areaDelta
143 | )
144 | {
145 | return Clipper
146 | .Union(inner, outer, FillRule.NonZero)
147 | .IsApproximatelyEqualTo(outer, areaDelta);
148 | }
149 |
150 | public static PathsD ToPathsD(this PathD path)
151 | {
152 | return new PathsD(new List() { path });
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/ExtensionMethods/VectorExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.ExtensionMethods;
2 |
3 | using System;
4 | using Godot;
5 | using System.Linq;
6 | using System.Collections.Generic;
7 |
8 | public static class VectorExtensionMethods
9 | {
10 | public static IEnumerable RemoveAxis(
11 | this IEnumerable vertices,
12 | Vector3.Axis axisToRemove
13 | )
14 | {
15 | return vertices.Select(n => RemoveAxis(n, axisToRemove));
16 | }
17 |
18 | public static Vector2 RemoveAxis(this Vector3 vertex, Vector3.Axis axisToRemove)
19 | {
20 | return axisToRemove switch
21 | {
22 | Vector3.Axis.X => new Vector2(vertex.Y, vertex.Z),
23 | Vector3.Axis.Y => new Vector2(vertex.X, vertex.Z),
24 | Vector3.Axis.Z => new Vector2(vertex.X, vertex.Y),
25 | _ => throw new ArgumentOutOfRangeException(nameof(axisToRemove)),
26 | };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Django
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Maps/Assets/MapConstructor.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Maps.Assets;
2 |
3 | using Godot;
4 | using Portal.Objects;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 |
9 | public partial class MapConstructor : Node3D
10 | {
11 | private List meshInstances = new();
12 |
13 | public override void _Ready()
14 | {
15 | GetChildMeshInstances();
16 |
17 | // TODO: Neaten this and check for prefixes
18 | foreach (var mesh in meshInstances.Select(n => n.Mesh))
19 | {
20 | //var points = mesh.GetFaces();
21 | var triangles = mesh.GetFaces()
22 | .Select((s, i) => new { Value = s, Index = i })
23 | .GroupBy(x => x.Index / 3)
24 | .Select(grp => grp.Select(x => x.Value).ToArray())
25 | .ToList();
26 |
27 | // Remove triangle slithers
28 | triangles.RemoveAll(n => !IsValidTriangle(n));
29 |
30 | var coplanarTriangles = GetCoplanarTriangles(triangles.ToArray());
31 |
32 | var portalableSurfaceScene = GD.Load(
33 | "res://Maps/Assets/PortalableSurface.tscn"
34 | );
35 |
36 | foreach (var face in coplanarTriangles)
37 | {
38 | var transform = ResetFaceTransform(face);
39 |
40 | var portalableSurface = portalableSurfaceScene.Instantiate() as PortalableSurface;
41 | portalableSurface.Triangles = face;
42 | portalableSurface.Transform = transform;
43 | AddChild(portalableSurface);
44 | }
45 | }
46 | }
47 |
48 | private static bool IsValidTriangle(IEnumerable triangle)
49 | {
50 | var triangleArray = triangle.ToArray();
51 |
52 | if (triangleArray.Length != 3)
53 | return false;
54 |
55 | // Any points are equal
56 | if (
57 | triangleArray[0].IsEqualApprox(triangleArray[1])
58 | || triangleArray[0].IsEqualApprox(triangleArray[2])
59 | || triangleArray[1].IsEqualApprox(triangleArray[2])
60 | )
61 | return false;
62 |
63 | // In straight line
64 | // TODO
65 |
66 | return true;
67 | }
68 |
69 | private static Transform3D ResetFaceTransform(Vector3[][] triangles)
70 | {
71 | var normal = GetNormal(triangles[0]);
72 | var minimumDistanceToOrigin = normal.Dot(triangles[0][0]);
73 | var originalTransform = Transform3D.Identity;
74 |
75 | if (normal.Cross(Vector3.Up).Length() < 0.01) // Floor/Cieling
76 | originalTransform = originalTransform.LookingAt(normal * 1000000, Vector3.Forward);
77 | else
78 | originalTransform = originalTransform.LookingAt(normal * 1000000, Vector3.Up);
79 |
80 | originalTransform = originalTransform.Translated(minimumDistanceToOrigin * normal);
81 |
82 | foreach (var triangle in triangles)
83 | {
84 | for (var i = 0; i < triangle.Length; i++)
85 | {
86 | triangle[i] = originalTransform.Inverse() * triangle[i];
87 | }
88 | }
89 |
90 | return originalTransform;
91 | }
92 |
93 | private static Vector3 GetNormal(IEnumerable triangle)
94 | {
95 | var triangleArray = triangle.ToArray();
96 |
97 | var line1 = triangleArray[1] - triangleArray[0];
98 | var line2 = triangleArray[2] - triangleArray[0];
99 |
100 | return line2.Cross(line1).Normalized();
101 | }
102 |
103 | private static Vector3[][][] GetCoplanarTriangles(Vector3[][] triangles)
104 | {
105 | var coplanarFaces = new Dictionary>(); // Dictionary: keys define plane, values are all triangles on that plane
106 |
107 | foreach (var triangle in triangles)
108 | {
109 | var normal = GetNormal(triangle);
110 | var d = normal.Dot(triangle[0]);
111 | var plane = new Vector4(normal.X, normal.Y, normal.Z, d);
112 |
113 | if (coplanarFaces.Keys.Any(key => key.IsEqualApprox(plane)))
114 | {
115 | var key = coplanarFaces.Keys.Single(key => key.IsEqualApprox(plane));
116 | coplanarFaces[key].Add(triangle);
117 | }
118 | else
119 | {
120 | coplanarFaces.Add(plane, new List { triangle });
121 | }
122 | }
123 |
124 | return coplanarFaces.Values.Select(n => n.ToArray()).ToArray();
125 | }
126 |
127 | private void GetChildMeshInstances()
128 | {
129 | foreach (var child in GetChildren())
130 | {
131 | if (child is MeshInstance3D mChild)
132 | {
133 | meshInstances.Add(mChild);
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Maps/Assets/PortalableSurface.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Maps.Assets;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using Clipper2Lib;
7 | using Godot;
8 | using Portal.Autoloads;
9 | using Portal.ExtensionMethods;
10 | using Portal.Objects;
11 |
12 | public partial class PortalableSurface : Node3D
13 | {
14 | public List AttachedPortals { get; set; } = new();
15 |
16 | // TODO: It's quite jank having this exposed.
17 | public Polygon2D[] CollisionPolygon
18 | {
19 | get => collisionPolygon;
20 | set
21 | {
22 | collisionPolygon = value;
23 | SetCollisionShapeFromPolygon(value);
24 | }
25 | }
26 | private Polygon2D[] collisionPolygon;
27 |
28 | public Vector3[][] Triangles
29 | {
30 | get => triangles;
31 | set
32 | {
33 | triangles = value;
34 |
35 | // Used for calculating updated collision with placedPortals cut out.
36 | CollisionPolygon = GetPolygonFromTriangles(triangles, Vector3.Axis.Z);
37 | // For portal raycast detection, remains unchanged.
38 | AddChild(GetCollisionShapeFromTriangles(Triangles));
39 |
40 | // For player collision. Changes when placedPortals are added
41 | var staticBody = new StaticBody3D();
42 | staticBody.AddChild(currentCollisionShape);
43 | AddChild(staticBody);
44 | }
45 | }
46 | private Vector3[][] triangles;
47 |
48 | private PortalSignals portalSignals;
49 | private CollisionShape3D currentCollisionShape = new();
50 |
51 | public override void _Ready() { }
52 |
53 | public void RemovePortal(PortalObject portal)
54 | {
55 | if (!AttachedPortals.Contains(portal))
56 | return;
57 |
58 | AttachedPortals.Remove(portal);
59 |
60 | CollisionPolygon = GetPolygonFromTriangles(Triangles, Vector3.Axis.Z);
61 | foreach (var attachedPortal in AttachedPortals)
62 | {
63 | AddPortalHoleToCollisionPolygon(attachedPortal.Position.RemoveAxis(Vector3.Axis.Z));
64 | }
65 | }
66 |
67 | public PortalObject PlacePortal(Vector3 suggestedGlobalPortalPosition)
68 | {
69 | var suggestedPortalPosition = GlobalTransform.Inverse() * suggestedGlobalPortalPosition;
70 | var portalPositionNullable = FindValidPortalPosition(
71 | suggestedPortalPosition.RemoveAxis(Vector3.Axis.Z)
72 | );
73 |
74 | if (portalPositionNullable == null)
75 | return null;
76 |
77 | var portalPosition2 = portalPositionNullable.Value;
78 | var wallOffset = -0.151f;
79 | var portalPosition3 = new Vector3(portalPosition2.X, portalPosition2.Y, wallOffset);
80 |
81 | var portal = AddNewPortal(portalPosition3);
82 | AddPortalHoleToCollisionPolygon(portalPosition2);
83 |
84 | return portal;
85 | }
86 |
87 | private void AddPortalHoleToCollisionPolygon(Vector2 portalPosition)
88 | {
89 | var offSetPortalPolygon = PortalObject
90 | .GetPortalPolygon()
91 | .Polygon.Select(n => n + portalPosition)
92 | .ToPolgyon();
93 |
94 | CollisionPolygon = CollisionPolygon
95 | .Clip(new List() { offSetPortalPolygon })
96 | .ToArray();
97 | }
98 |
99 | private void SetCollisionShapeFromPolygon(IEnumerable polygon)
100 | {
101 | var triangulatedPolygon = polygon.Triangulate();
102 |
103 | // Set new collisionshape
104 | var data = triangulatedPolygon
105 | .SelectMany(n => n.Polygon.Select(m => new Vector3(m.X, m.Y, 0)))
106 | .ToArray();
107 |
108 | var collisionShape = new ConcavePolygonShape3D { Data = data, BackfaceCollision = true };
109 | currentCollisionShape.Shape = collisionShape;
110 | }
111 |
112 | private PortalObject AddNewPortal(Vector3 portalPosition)
113 | {
114 | var portalScene = GD.Load("res://Objects/PortalObject/PortalObject.tscn");
115 | var portal = portalScene.Instantiate() as PortalObject;
116 | portal.Position = portalPosition;
117 | AttachedPortals.Add(portal);
118 | portal.AttachedSurface = this;
119 |
120 | AddChild(portal);
121 |
122 | return portal;
123 | }
124 |
125 | private static CollisionShape3D GetCollisionShapeFromTriangles(Vector3[][] triangles)
126 | {
127 | return new CollisionShape3D()
128 | {
129 | Shape = new ConcavePolygonShape3D()
130 | {
131 | Data = triangles.SelectMany(n => n.Select(m => m)).ToArray()
132 | }
133 | };
134 | }
135 |
136 | private static Polygon2D[] GetPolygonFromTriangles(
137 | Vector3[][] triangles,
138 | Vector3.Axis axisToRemove
139 | )
140 | {
141 | var triangles2 = triangles.SelectMany(
142 | n => triangles.Select(m => m.RemoveAxis(axisToRemove))
143 | );
144 | return triangles2
145 | .Select(n => new Polygon2D() { Polygon = n.ToArray() })
146 | .ToPolygon()
147 | .ToArray();
148 | }
149 |
150 | private Vector2? FindValidPortalPosition(Vector2 suggestedPortalPosition)
151 | {
152 | var margin = 0.05f;
153 | var portalPolygon = PortalObject
154 | .GetPortalPolygon()
155 | .Inflate(margin)
156 | .Polygon.Select(n => n + suggestedPortalPosition)
157 | .ToPolgyon();
158 |
159 | var portalPositionOffset = FindClosestValidPortalPositionOffset(
160 | collisionPolygon,
161 | portalPolygon
162 | );
163 |
164 | return suggestedPortalPosition + portalPositionOffset;
165 | }
166 |
167 | private static Vector2? FindClosestValidPortalPositionOffset(
168 | Polygon2D[] baseCollisionPolygons,
169 | Polygon2D portalPolygon
170 | )
171 | {
172 | var directionNum = 32;
173 | var layerNum = 100;
174 | var maxMovement = 2.0f;
175 | var layerWidth = maxMovement / layerNum;
176 |
177 | var offsetGuessDirections = new List();
178 |
179 | for (var i = 0; i < directionNum; i++)
180 | {
181 | offsetGuessDirections.Add(
182 | new Vector2(1, 0).Rotated((float)(2 * i * Math.PI / directionNum))
183 | );
184 | }
185 |
186 | var offsetGuesses = new List() { new Vector2(0, 0) };
187 |
188 | for (var i = 0; i < layerNum; i++)
189 | {
190 | offsetGuesses.AddRange(offsetGuessDirections.Select(n => n * layerWidth * (i + 1)));
191 | }
192 |
193 | var baseCollisionPaths = baseCollisionPolygons.ToPath();
194 | var portalPath = portalPolygon.ToPath();
195 |
196 | foreach (var offsetGuess in offsetGuesses)
197 | {
198 | var offSetPortalPath = new PathsD(
199 | new List
200 | {
201 | new PathD(
202 | portalPath.Select(n => new PointD(n.x + offsetGuess.X, n.y + offsetGuess.Y))
203 | )
204 | }
205 | );
206 |
207 | if (offSetPortalPath.IsApproximatelyEntirelyContainedBy(baseCollisionPaths, 0.05))
208 | return offsetGuess;
209 | }
210 |
211 | return null;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/Maps/Assets/PortalableSurface.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://dhn56m3wu1n4o"]
2 |
3 | [ext_resource type="Script" path="res://Maps/Assets/PortalableSurface.cs" id="1_kjdct"]
4 |
5 | [node name="PortalableSurface" type="Area3D"]
6 | collision_layer = 4
7 | collision_mask = 0
8 | input_ray_pickable = false
9 | monitoring = false
10 | script = ExtResource("1_kjdct")
11 |
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Maps/TestChamber04/Map.blend
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Maps/TestChamber04/Map.glb
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map.glb.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://cybu1x3qjp3yr"
7 | path="res://.godot/imported/Map.glb-c094885e15356fa6929e77508e1e51ad.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://Maps/TestChamber04/Map.glb"
12 | dest_files=["res://.godot/imported/Map.glb-c094885e15356fa6929e77508e1e51ad.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type="Node3D"
17 | nodes/root_name="Scene Root"
18 | nodes/apply_root_scale=true
19 | nodes/root_scale=1.0
20 | meshes/ensure_tangents=true
21 | meshes/generate_lods=true
22 | meshes/create_shadow_meshes=true
23 | meshes/light_baking=1
24 | meshes/lightmap_texel_size=0.2
25 | skins/use_named_skins=true
26 | animation/import=true
27 | animation/fps=30
28 | animation/trimming=false
29 | animation/remove_immutable_tracks=true
30 | import_script/path=""
31 | _subresources={}
32 | gltf/embedded_image_handling=1
33 |
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map.gltf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://bd6wxnhyx35tg"
7 | path="res://.godot/imported/Map.gltf-abf418bd850a58dce8834e655ad92c8b.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://Maps/TestChamber04/Map.gltf"
12 | dest_files=["res://.godot/imported/Map.gltf-abf418bd850a58dce8834e655ad92c8b.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type="Node3D"
17 | nodes/root_name="Scene Root"
18 | nodes/apply_root_scale=true
19 | nodes/root_scale=1.0
20 | meshes/ensure_tangents=true
21 | meshes/generate_lods=true
22 | meshes/create_shadow_meshes=true
23 | meshes/light_baking=1
24 | meshes/lightmap_texel_size=0.2
25 | skins/use_named_skins=true
26 | animation/import=true
27 | animation/fps=30
28 | animation/trimming=false
29 | animation/remove_immutable_tracks=true
30 | import_script/path=""
31 | _subresources={}
32 | gltf/embedded_image_handling=1
33 |
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map_FloorCeiling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Maps/TestChamber04/Map_FloorCeiling.png
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map_FloorCeiling.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://ck4xr7qol4l04"
6 | path="res://.godot/imported/Map_FloorCeiling.png-109ae8fc542b4eb6723615bd68e78689.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://Maps/TestChamber04/Map_FloorCeiling.png"
14 | dest_files=["res://.godot/imported/Map_FloorCeiling.png-109ae8fc542b4eb6723615bd68e78689.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=1
23 | compress/channel_pack=0
24 | mipmaps/generate=true
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=0
35 |
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map_Wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Maps/TestChamber04/Map_Wall.png
--------------------------------------------------------------------------------
/Maps/TestChamber04/Map_Wall.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bjmlvnswdr75"
6 | path="res://.godot/imported/Map_Wall.png-e98b7a92c47c24450f69d69a737c60aa.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://Maps/TestChamber04/Map_Wall.png"
14 | dest_files=["res://.godot/imported/Map_Wall.png-e98b7a92c47c24450f69d69a737c60aa.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=1
23 | compress/channel_pack=0
24 | mipmaps/generate=true
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=0
35 |
--------------------------------------------------------------------------------
/Maps/TestChamber04/TestChamber04.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=6 format=3 uid="uid://fofc0pvmks4a"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://cyuxx15xsw13v" path="res://Objects/PortalManager/PortalManager.tscn" id="1_dy7sy"]
4 | [ext_resource type="PackedScene" uid="uid://bjgf83cp40r5u" path="res://Objects/Player/Player.tscn" id="2_h05wb"]
5 | [ext_resource type="PackedScene" uid="uid://cybu1x3qjp3yr" path="res://Maps/TestChamber04/Map.glb" id="3_4qs0t"]
6 | [ext_resource type="Script" path="res://Maps/Assets/MapConstructor.cs" id="4_sabgw"]
7 |
8 | [sub_resource type="Environment" id="Environment_qddy7"]
9 | background_mode = 1
10 | background_color = Color(0.572549, 0.529412, 0.533333, 1)
11 | background_energy_multiplier = 2.7
12 | ssao_enabled = true
13 | ssao_radius = 5.0
14 |
15 | [node name="TestChamber04" type="Node3D"]
16 |
17 | [node name="Map" parent="." instance=ExtResource("3_4qs0t")]
18 | script = ExtResource("4_sabgw")
19 |
20 | [node name="PortalManager" parent="." node_paths=PackedStringArray("player") instance=ExtResource("1_dy7sy")]
21 | player = NodePath("../Player")
22 |
23 | [node name="Player" parent="." node_paths=PackedStringArray("portalManager") instance=ExtResource("2_h05wb")]
24 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.24044, 2.56379, 0)
25 | wall_min_slide_angle = 0.261799
26 | portalManager = NodePath("../PortalManager")
27 |
28 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
29 | environment = SubResource("Environment_qddy7")
30 |
31 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
32 | transform = Transform3D(0.41739, 0.167309, -0.893193, 0.374436, 0.863923, 0.3368, 0.828, -0.47502, 0.297946, -13.6362, 11.7903, 13.3014)
33 |
34 | [node name="SpotLight3D" type="SpotLight3D" parent="."]
35 | transform = Transform3D(-2.98023e-08, 0, -1, 0, 1, 0, 1, 0, -2.98023e-08, -6.79618, 5, -17)
36 | light_color = Color(0.690196, 0.690196, 0.690196, 1)
37 | light_volumetric_fog_energy = 0.0
38 | light_specular = 0.0
39 | distance_fade_begin = 0.0
40 | spot_range = 12.0
41 | spot_attenuation = 0.466517
42 | spot_angle = 66.65
43 |
--------------------------------------------------------------------------------
/Maps/Textures/Walls/FloorCeiling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Maps/Textures/Walls/FloorCeiling.png
--------------------------------------------------------------------------------
/Maps/Textures/Walls/FloorCeiling.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://daycs06ku04kr"
6 | path="res://.godot/imported/FloorCeiling.png-b6260f946987713115f56db321b45db6.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://Maps/Textures/Walls/FloorCeiling.png"
14 | dest_files=["res://.godot/imported/FloorCeiling.png-b6260f946987713115f56db321b45db6.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/Maps/Textures/Walls/Wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Maps/Textures/Walls/Wall.png
--------------------------------------------------------------------------------
/Maps/Textures/Walls/Wall.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cfkt7f6hjgvhy"
6 | path="res://.godot/imported/Wall.png-667057f8587322b88bb9adab9d687a34.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://Maps/Textures/Walls/Wall.png"
14 | dest_files=["res://.godot/imported/Wall.png-667057f8587322b88bb9adab9d687a34.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/Objects/Player/Player.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Objects;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using Godot;
7 | using Portal.Autoloads;
8 | using Portal.Maps.Assets;
9 | using Portal.Objects.PortalTraveller;
10 |
11 | public partial class Player : CharacterBody3D, IPortalTraveller
12 | {
13 | [Export]
14 | private PortalManager portalManager;
15 |
16 | public Vector3 PreviousOffsetFromPortal { get; set; }
17 |
18 | private PortalSignals events;
19 |
20 | private Node3D head;
21 | private Camera3D camera;
22 | private RayCast3D rayCast;
23 |
24 | private const float Speed = 5;
25 | private const float GroundFriction = 0.5f;
26 | private const float AirFriction = 0.005f;
27 | private const float Gravity = 15;
28 | private const float MaxGravityVelocity = 35;
29 | private const float JumpVelocity = 6.5f;
30 | private const float MouseSensitivity = 0.01f;
31 |
32 | private Signal shootPortal;
33 |
34 | public override void _Ready()
35 | {
36 | events = GetNode("/root/PortalSignals");
37 |
38 | head = GetNode("Head");
39 | camera = GetNode("Head/Camera");
40 | rayCast = GetNode("Head/RayCast");
41 |
42 | Input.MouseMode = Input.MouseModeEnum.Captured;
43 |
44 | FloorStopOnSlope = true;
45 | FloorSnapLength = 0.2f;
46 | }
47 |
48 | public override void _Input(InputEvent @event)
49 | {
50 | if (@event is InputEventMouseMotion mouseMotion)
51 | FpsHeadMovement(mouseMotion);
52 | }
53 |
54 | public override void _PhysicsProcess(double delta)
55 | {
56 | var direction = GetMovementDirection();
57 | UpdateVelocity(direction, (float)delta);
58 |
59 | MoveAndSlide();
60 |
61 | HandlePortalShooting();
62 |
63 | var distances = new List();
64 | foreach (var portal in portalManager.PlacedPortals)
65 | {
66 | distances.Add((portal.GlobalPosition - GlobalPosition).Length());
67 | }
68 |
69 | var minDistance = float.MaxValue;
70 | if (distances.Count != 0)
71 | minDistance = distances.Min();
72 |
73 | if (minDistance < 0.5)
74 | camera.Near = 0.001f;
75 | else
76 | camera.Near = 0.05f;
77 | }
78 |
79 | private void HandlePortalShooting()
80 | {
81 | if (Input.IsActionJustPressed("ShootA"))
82 | OnShoot(PortalType.A);
83 | if (Input.IsActionJustPressed("ShootB"))
84 | OnShoot(PortalType.B);
85 | }
86 |
87 | private void OnShoot(PortalType portalType)
88 | {
89 | var collider = rayCast.GetCollider();
90 | if (collider is PortalableSurface surface)
91 | {
92 | // Get portal rotation
93 | //var portalRotation =
94 | // rayCast.GetCollisionNormal().Cross(Vector3.Up).Length() < 0.01 ? Rotation.Y : 0;
95 |
96 | events.PortalShot(surface, rayCast.GetCollisionPoint(), portalType);
97 | }
98 | }
99 |
100 | private Vector3 GetMovementDirection()
101 | {
102 | var rotation = GlobalTransform.Basis.GetEuler().Y;
103 | var forwardInput =
104 | Input.GetActionStrength("MoveBackward") - Input.GetActionStrength("MoveForward");
105 | var horizontalInput =
106 | Input.GetActionStrength("MoveRight") - Input.GetActionStrength("MoveLeft");
107 | return new Vector3(horizontalInput, 0, forwardInput)
108 | .Rotated(Vector3.Up, rotation)
109 | .Normalized();
110 | }
111 |
112 | private void UpdateVelocity(Vector3 direction, float delta)
113 | {
114 | var friction = IsOnFloor() ? GroundFriction : AirFriction;
115 | Velocity = new Vector3(
116 | Velocity.X * (1 - friction),
117 | Velocity.Y,
118 | Velocity.Z * (1 - friction)
119 | );
120 |
121 | if (Velocity.Length() < Speed + 0.1)
122 | Velocity = new Vector3(direction.X * Speed, Velocity.Y, direction.Z * Speed);
123 |
124 | if (IsOnFloor() && Input.IsActionJustPressed("Jump"))
125 | Velocity = new Vector3(Velocity.X, JumpVelocity, Velocity.Z);
126 |
127 | if (!IsOnFloor() && Velocity.Y > -MaxGravityVelocity)
128 | Velocity += new Vector3(0, -Gravity * delta, 0);
129 | }
130 |
131 | private void FpsHeadMovement(InputEventMouseMotion mouseMotion)
132 | {
133 | RotateY(-mouseMotion.Relative.X * MouseSensitivity);
134 | head.RotateX(-mouseMotion.Relative.Y * MouseSensitivity);
135 | head.Rotation = new Vector3(
136 | x: (float)Math.Clamp(head.Rotation.X, (-Math.PI / 2) + 0.1, (Math.PI / 2) - 0.1),
137 | y: head.Rotation.Y,
138 | z: head.Rotation.Z
139 | );
140 | }
141 |
142 | public void Travel(Transform3D relativeTransform)
143 | {
144 | GD.Print("Travelling through portal");
145 |
146 | GlobalTransform = relativeTransform * GlobalTransform;
147 | Velocity = relativeTransform.Basis * Velocity;
148 |
149 | OrientPlayerUpwards(0.25f);
150 | }
151 |
152 | private void OrientPlayerUpwards(float duration)
153 | {
154 | var previousHeadRotation = head.GlobalRotation;
155 | Quaternion = new Quaternion(0, Quaternion.Y, 0, Quaternion.W); // Puts player on feet
156 | head.GlobalRotation = previousHeadRotation; // Maintain global head rotation
157 |
158 | // Want the player body, not the head to rotate around Y
159 | Rotation += new Vector3(0, head.Rotation.Y, 0);
160 | head.Rotation = new Vector3(head.Rotation.X, 0, head.Rotation.Z);
161 |
162 | // Tween the head z rotation, as it must be zero, but looks janky if you do it in one frame
163 | var tween = CreateTween().SetTrans(Tween.TransitionType.Quad);
164 | tween.TweenProperty(head, "rotation:z", 0, duration);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/Objects/Player/Player.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://bjgf83cp40r5u"]
2 |
3 | [ext_resource type="Script" path="res://Objects/Player/Player.cs" id="1_o0jvl"]
4 |
5 | [sub_resource type="CapsuleMesh" id="CapsuleMesh_unipv"]
6 |
7 | [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xhisy"]
8 |
9 | [node name="Player" type="CharacterBody3D"]
10 | collision_layer = 2
11 | wall_min_slide_angle = 0.872665
12 | script = ExtResource("1_o0jvl")
13 |
14 | [node name="Head" type="Node3D" parent="."]
15 |
16 | [node name="Camera" type="Camera3D" parent="Head"]
17 | far = 1000.0
18 |
19 | [node name="RayCast" type="RayCast3D" parent="Head"]
20 | target_position = Vector3(0, 0, -100)
21 | collision_mask = 4
22 | collide_with_areas = true
23 | collide_with_bodies = false
24 |
25 | [node name="Mesh" type="MeshInstance3D" parent="."]
26 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
27 | mesh = SubResource("CapsuleMesh_unipv")
28 |
29 | [node name="CollisionShape" type="CollisionShape3D" parent="."]
30 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
31 | shape = SubResource("CapsuleShape3D_xhisy")
32 |
--------------------------------------------------------------------------------
/Objects/PortalManager/PortalManager.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Objects;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using Godot;
7 | using Portal.Autoloads;
8 | using Portal.Maps.Assets;
9 |
10 | public partial class PortalManager : Node
11 | {
12 | public List PlacedPortals { get; set; } = new();
13 |
14 | [Export]
15 | private Player player;
16 | private Camera3D playerCamera;
17 | private PortalSignals portalSignals;
18 |
19 | public override void _Ready()
20 | {
21 | // TODO: Remove tight coupling to player here. Perhaps search through entire scene
22 | playerCamera = player.GetNode("Head/Camera");
23 |
24 | portalSignals = GetNode("/root/PortalSignals");
25 | portalSignals.PortalShot += OnPortalShot;
26 | }
27 |
28 | private void OnPortalShot(
29 | PortalableSurface surface,
30 | Vector3 suggestedGlobalPosition,
31 | PortalType portalType
32 | )
33 | {
34 | foreach (var placedPortal in PlacedPortals.Where(n => n.PortalType == portalType)) // Should only have 0 or 1
35 | {
36 | placedPortal.AttachedSurface.RemovePortal(placedPortal);
37 | placedPortal.QueueFree();
38 | }
39 | PlacedPortals.RemoveAll(n => n.PortalType == portalType);
40 |
41 | var portal = surface.PlacePortal(suggestedGlobalPosition);
42 |
43 | if (portal == null)
44 | {
45 | LinkPlacedPortals();
46 | return;
47 | }
48 |
49 | portal.PortalType = portalType;
50 | portal.PlayerCamera = playerCamera;
51 | PlacedPortals.Add(portal);
52 |
53 | if (portalType == PortalType.B)
54 | portal.RotateY((float)Math.PI);
55 |
56 | LinkPlacedPortals();
57 | }
58 |
59 | private void LinkPlacedPortals()
60 | {
61 | if (PlacedPortals.Count != 2)
62 | {
63 | foreach (var placedPortal in PlacedPortals)
64 | placedPortal.LinkedPortal = null;
65 |
66 | return;
67 | }
68 |
69 | PlacedPortals[0].LinkedPortal = PlacedPortals[1];
70 | PlacedPortals[1].LinkedPortal = PlacedPortals[0];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Objects/PortalManager/PortalManager.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://cyuxx15xsw13v"]
2 |
3 | [ext_resource type="Script" path="res://Objects/PortalManager/PortalManager.cs" id="1_mw1vu"]
4 |
5 | [node name="PortalManager" type="Node3D"]
6 | script = ExtResource("1_mw1vu")
7 |
--------------------------------------------------------------------------------
/Objects/PortalObject/Mesh/PortalMesh.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Objects/PortalObject/Mesh/PortalMesh.blend
--------------------------------------------------------------------------------
/Objects/PortalObject/Mesh/PortalMesh.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Objects/PortalObject/Mesh/PortalMesh.glb
--------------------------------------------------------------------------------
/Objects/PortalObject/Mesh/PortalMesh.glb.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://83vki4arpu01"
7 | path="res://.godot/imported/PortalMesh.glb-461c4a961cc808da04557eb2351e45b2.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://Objects/PortalObject/Mesh/PortalMesh.glb"
12 | dest_files=["res://.godot/imported/PortalMesh.glb-461c4a961cc808da04557eb2351e45b2.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type="Node3D"
17 | nodes/root_name="Scene Root"
18 | nodes/apply_root_scale=true
19 | nodes/root_scale=1.0
20 | meshes/ensure_tangents=true
21 | meshes/generate_lods=true
22 | meshes/create_shadow_meshes=true
23 | meshes/light_baking=1
24 | meshes/lightmap_texel_size=0.2
25 | skins/use_named_skins=true
26 | animation/import=true
27 | animation/fps=30
28 | animation/trimming=false
29 | animation/remove_immutable_tracks=true
30 | import_script/path=""
31 | _subresources={}
32 | gltf/embedded_image_handling=1
33 |
--------------------------------------------------------------------------------
/Objects/PortalObject/Mesh/PortalMeshShape.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="ArrayMesh" load_steps=3 format=3 uid="uid://bga62l8t1viai"]
2 |
3 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1wqf8"]
4 |
5 | [sub_resource type="ArrayMesh" id="ArrayMesh_82616"]
6 | _surfaces = [{
7 | "aabb": AABB(-0.870758, -1.43343, -1.0919e-07, 1.74152, 2.86687, 1e-05),
8 | "format": 4097,
9 | "index_count": 90,
10 | "index_data": PackedByteArray(0, 0, 2, 0, 1, 0, 2, 0, 4, 0, 3, 0, 4, 0, 6, 0, 5, 0, 6, 0, 8, 0, 7, 0, 8, 0, 10, 0, 9, 0, 10, 0, 12, 0, 11, 0, 12, 0, 14, 0, 13, 0, 14, 0, 16, 0, 15, 0, 16, 0, 18, 0, 17, 0, 18, 0, 20, 0, 19, 0, 20, 0, 22, 0, 21, 0, 22, 0, 24, 0, 23, 0, 24, 0, 26, 0, 25, 0, 26, 0, 28, 0, 27, 0, 28, 0, 30, 0, 29, 0, 30, 0, 0, 0, 31, 0, 0, 0, 4, 0, 2, 0, 4, 0, 8, 0, 6, 0, 8, 0, 12, 0, 10, 0, 12, 0, 16, 0, 14, 0, 16, 0, 20, 0, 18, 0, 20, 0, 24, 0, 22, 0, 24, 0, 28, 0, 26, 0, 28, 0, 0, 0, 30, 0, 0, 0, 8, 0, 4, 0, 8, 0, 16, 0, 12, 0, 16, 0, 24, 0, 20, 0, 24, 0, 0, 0, 28, 0, 0, 0, 16, 0, 8, 0, 16, 0, 0, 0, 24, 0),
11 | "primitive": 3,
12 | "vertex_count": 32,
13 | "vertex_data": PackedByteArray(32, 26, 111, 176, 150, 122, 183, 63, 202, 123, 234, 179, 28, 244, 45, 190, 14, 244, 179, 63, 95, 250, 229, 179, 112, 156, 170, 190, 41, 131, 169, 63, 113, 162, 216, 179, 90, 176, 247, 190, 157, 142, 152, 63, 72, 247, 194, 179, 194, 159, 29, 191, 56, 189, 129, 63, 14, 206, 165, 179, 166, 88, 57, 191, 235, 222, 75, 63, 167, 69, 130, 179, 30, 242, 77, 191, 186, 109, 12, 63, 71, 119, 51, 179, 128, 161, 90, 191, 7, 46, 143, 62, 94, 251, 182, 178, 2, 234, 94, 191, 160, 131, 139, 179, 134, 139, 182, 39, 128, 161, 90, 191, 7, 46, 143, 190, 94, 251, 182, 50, 30, 242, 77, 191, 186, 109, 12, 191, 71, 119, 51, 51, 168, 88, 57, 191, 234, 222, 75, 191, 167, 69, 130, 51, 194, 159, 29, 191, 56, 189, 129, 191, 15, 206, 165, 51, 89, 176, 247, 190, 158, 142, 152, 191, 73, 247, 194, 51, 113, 156, 170, 190, 41, 131, 169, 191, 113, 162, 216, 51, 27, 244, 45, 190, 15, 244, 179, 191, 96, 250, 229, 51, 118, 155, 161, 51, 150, 122, 183, 191, 202, 123, 234, 51, 23, 244, 45, 62, 15, 244, 179, 191, 96, 250, 229, 51, 111, 156, 170, 62, 41, 131, 169, 191, 113, 162, 216, 51, 92, 176, 247, 62, 156, 142, 152, 191, 71, 247, 194, 51, 196, 159, 29, 63, 56, 189, 129, 191, 16, 206, 165, 51, 166, 88, 57, 63, 239, 222, 75, 191, 170, 69, 130, 51, 30, 242, 77, 63, 190, 109, 12, 191, 76, 119, 51, 51, 130, 161, 90, 63, 9, 46, 143, 190, 96, 251, 182, 50, 4, 234, 94, 63, 228, 253, 125, 50, 216, 78, 145, 166, 130, 161, 90, 63, 11, 46, 143, 62, 98, 251, 182, 178, 30, 242, 77, 63, 191, 109, 12, 63, 77, 119, 51, 179, 170, 88, 57, 63, 231, 222, 75, 63, 165, 69, 130, 179, 198, 159, 29, 63, 55, 189, 129, 63, 14, 206, 165, 179, 92, 176, 247, 62, 157, 142, 152, 63, 72, 247, 194, 179, 111, 156, 170, 62, 41, 131, 169, 63, 114, 162, 216, 179, 22, 244, 45, 62, 15, 244, 179, 63, 96, 250, 229, 179)
14 | }]
15 | blend_shape_mode = 0
16 |
17 | [resource]
18 | resource_name = "PortalMesh_Circle"
19 | _surfaces = [{
20 | "aabb": AABB(-0.870758, -1.43343, -1.0919e-07, 1.74152, 2.86687, 1e-05),
21 | "attribute_data": PackedByteArray(206, 106, 1, 63, 64, 202, 18, 60, 109, 118, 209, 62, 0, 95, 148, 60, 16, 233, 161, 62, 160, 134, 59, 61, 160, 2, 108, 62, 144, 46, 186, 61, 94, 222, 30, 62, 8, 102, 27, 62, 28, 184, 190, 61, 20, 105, 103, 62, 128, 178, 63, 61, 66, 26, 159, 62, 128, 21, 147, 60, 64, 57, 206, 62, 64, 26, 252, 59, 240, 65, 255, 62, 144, 115, 134, 60, 248, 40, 24, 63, 152, 78, 51, 61, 72, 195, 47, 63, 204, 185, 181, 61, 181, 135, 69, 63, 54, 37, 25, 62, 26, 160, 88, 63, 214, 71, 101, 62, 153, 80, 104, 63, 230, 43, 158, 62, 221, 254, 115, 63, 75, 126, 205, 62, 246, 55, 123, 63, 140, 201, 254, 62, 215, 180, 125, 63, 90, 20, 24, 63, 7, 93, 123, 63, 4, 219, 47, 63, 149, 71, 116, 63, 226, 206, 69, 63, 44, 186, 104, 63, 244, 23, 89, 63, 124, 38, 89, 63, 134, 248, 104, 63, 187, 37, 70, 63, 102, 212, 116, 63, 223, 114, 48, 63, 226, 54, 124, 63, 95, 227, 24, 63, 92, 215, 126, 63, 6, 95, 0, 63, 242, 155, 124, 63, 12, 174, 207, 62, 158, 154, 117, 63, 100, 121, 160, 62, 78, 24, 106, 63, 24, 225, 105, 62, 62, 134, 90, 63, 160, 127, 29, 62, 150, 125, 71, 63, 40, 123, 189, 61, 152, 185, 49, 63, 32, 18, 64, 61, 104, 16, 26, 63, 0, 1, 153, 60),
22 | "format": 4119,
23 | "index_count": 90,
24 | "index_data": PackedByteArray(0, 0, 2, 0, 1, 0, 2, 0, 4, 0, 3, 0, 4, 0, 6, 0, 5, 0, 6, 0, 8, 0, 7, 0, 8, 0, 10, 0, 9, 0, 10, 0, 12, 0, 11, 0, 12, 0, 14, 0, 13, 0, 14, 0, 16, 0, 15, 0, 16, 0, 18, 0, 17, 0, 18, 0, 20, 0, 19, 0, 20, 0, 22, 0, 21, 0, 22, 0, 24, 0, 23, 0, 24, 0, 26, 0, 25, 0, 26, 0, 28, 0, 27, 0, 28, 0, 30, 0, 29, 0, 30, 0, 0, 0, 31, 0, 0, 0, 4, 0, 2, 0, 4, 0, 8, 0, 6, 0, 8, 0, 12, 0, 10, 0, 12, 0, 16, 0, 14, 0, 16, 0, 20, 0, 18, 0, 20, 0, 24, 0, 22, 0, 24, 0, 28, 0, 26, 0, 28, 0, 0, 0, 30, 0, 0, 0, 8, 0, 4, 0, 8, 0, 16, 0, 12, 0, 16, 0, 24, 0, 20, 0, 24, 0, 0, 0, 28, 0, 0, 0, 16, 0, 8, 0, 16, 0, 0, 0, 24, 0),
25 | "material": SubResource("StandardMaterial3D_1wqf8"),
26 | "primitive": 3,
27 | "vertex_count": 32,
28 | "vertex_data": PackedByteArray(32, 26, 111, 176, 150, 122, 183, 63, 202, 123, 234, 179, 255, 127, 255, 127, 96, 255, 78, 192, 28, 244, 45, 190, 14, 244, 179, 63, 95, 250, 229, 179, 255, 127, 255, 127, 96, 255, 78, 192, 112, 156, 170, 190, 41, 131, 169, 63, 113, 162, 216, 179, 255, 127, 255, 127, 96, 255, 78, 192, 90, 176, 247, 190, 157, 142, 152, 63, 72, 247, 194, 179, 255, 127, 255, 127, 96, 255, 78, 192, 194, 159, 29, 191, 56, 189, 129, 63, 14, 206, 165, 179, 255, 127, 255, 127, 96, 255, 78, 192, 166, 88, 57, 191, 235, 222, 75, 63, 167, 69, 130, 179, 255, 127, 255, 127, 96, 255, 78, 192, 30, 242, 77, 191, 186, 109, 12, 63, 71, 119, 51, 179, 255, 127, 255, 127, 96, 255, 78, 192, 128, 161, 90, 191, 7, 46, 143, 62, 94, 251, 182, 178, 255, 127, 255, 127, 96, 255, 78, 192, 2, 234, 94, 191, 160, 131, 139, 179, 134, 139, 182, 39, 255, 127, 255, 127, 96, 255, 78, 192, 128, 161, 90, 191, 7, 46, 143, 190, 94, 251, 182, 50, 255, 127, 255, 127, 96, 255, 78, 192, 30, 242, 77, 191, 186, 109, 12, 191, 71, 119, 51, 51, 255, 127, 255, 127, 96, 255, 78, 192, 168, 88, 57, 191, 234, 222, 75, 191, 167, 69, 130, 51, 255, 127, 255, 127, 96, 255, 78, 192, 194, 159, 29, 191, 56, 189, 129, 191, 15, 206, 165, 51, 255, 127, 255, 127, 96, 255, 78, 192, 89, 176, 247, 190, 158, 142, 152, 191, 73, 247, 194, 51, 255, 127, 255, 127, 96, 255, 78, 192, 113, 156, 170, 190, 41, 131, 169, 191, 113, 162, 216, 51, 255, 127, 255, 127, 96, 255, 78, 192, 27, 244, 45, 190, 15, 244, 179, 191, 96, 250, 229, 51, 255, 127, 255, 127, 96, 255, 78, 192, 118, 155, 161, 51, 150, 122, 183, 191, 202, 123, 234, 51, 255, 127, 255, 127, 96, 255, 78, 192, 23, 244, 45, 62, 15, 244, 179, 191, 96, 250, 229, 51, 255, 127, 255, 127, 96, 255, 78, 192, 111, 156, 170, 62, 41, 131, 169, 191, 113, 162, 216, 51, 255, 127, 255, 127, 96, 255, 78, 192, 92, 176, 247, 62, 156, 142, 152, 191, 71, 247, 194, 51, 255, 127, 255, 127, 96, 255, 78, 192, 196, 159, 29, 63, 56, 189, 129, 191, 16, 206, 165, 51, 255, 127, 255, 127, 96, 255, 78, 192, 166, 88, 57, 63, 239, 222, 75, 191, 170, 69, 130, 51, 255, 127, 255, 127, 96, 255, 78, 192, 30, 242, 77, 63, 190, 109, 12, 191, 76, 119, 51, 51, 255, 127, 255, 127, 96, 255, 78, 192, 130, 161, 90, 63, 9, 46, 143, 190, 96, 251, 182, 50, 255, 127, 255, 127, 96, 255, 78, 192, 4, 234, 94, 63, 228, 253, 125, 50, 216, 78, 145, 166, 255, 127, 255, 127, 96, 255, 78, 192, 130, 161, 90, 63, 11, 46, 143, 62, 98, 251, 182, 178, 255, 127, 255, 127, 95, 255, 78, 192, 30, 242, 77, 63, 191, 109, 12, 63, 77, 119, 51, 179, 255, 127, 255, 127, 96, 255, 78, 192, 170, 88, 57, 63, 231, 222, 75, 63, 165, 69, 130, 179, 255, 127, 255, 127, 97, 255, 77, 192, 198, 159, 29, 63, 55, 189, 129, 63, 14, 206, 165, 179, 255, 127, 255, 127, 96, 255, 78, 192, 92, 176, 247, 62, 157, 142, 152, 63, 72, 247, 194, 179, 255, 127, 255, 127, 96, 255, 78, 192, 111, 156, 170, 62, 41, 131, 169, 63, 114, 162, 216, 179, 255, 127, 255, 127, 96, 255, 78, 192, 22, 244, 45, 62, 15, 244, 179, 63, 96, 250, 229, 179, 255, 127, 255, 127, 96, 255, 78, 192)
29 | }]
30 | blend_shape_mode = 0
31 | shadow_mesh = SubResource("ArrayMesh_82616")
32 |
--------------------------------------------------------------------------------
/Objects/PortalObject/PortalMaterial.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://cn0ib6mticlv8"]
2 |
3 | [ext_resource type="Shader" uid="uid://dt44x5qn6ygkx" path="res://Objects/PortalObject/PortalShader.tres" id="1_ve5jw"]
4 |
5 | [resource]
6 | render_priority = 0
7 | shader = ExtResource("1_ve5jw")
8 |
--------------------------------------------------------------------------------
/Objects/PortalObject/PortalObject.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Objects;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using Godot;
6 | using Portal.Maps.Assets;
7 | using Portal.Objects.PortalTraveller;
8 |
9 | public partial class PortalObject : Node3D
10 | {
11 | private PortalObject linkedPortal;
12 | public PortalObject LinkedPortal
13 | {
14 | get => linkedPortal;
15 | set
16 | {
17 | linkedPortal = value;
18 |
19 | // Don't allow player through portal if there is not a linked portal
20 | // Switch out between portal and unconnected materials as well.
21 | if (value == null)
22 | {
23 | portalBlocker.CollisionLayer = 1;
24 | portalMesh.MaterialOverride = unlinkedMaterial;
25 | }
26 | else
27 | {
28 | portalBlocker.CollisionLayer = 0;
29 | portalMesh.MaterialOverride = portalMaterial;
30 | }
31 | }
32 | }
33 |
34 | private PortalType portalType;
35 | public PortalType PortalType
36 | {
37 | get => portalType;
38 | set
39 | {
40 | portalType = value;
41 | if (portalType == PortalType.B)
42 | {
43 | portalMesh.Position = new Vector3(
44 | portalMesh.Position.X,
45 | portalMesh.Position.Y,
46 | -portalMesh.Position.Z
47 | );
48 | }
49 | UpdatePortalColour();
50 | }
51 | }
52 |
53 | public Camera3D PlayerCamera { get; set; }
54 | public PortalableSurface AttachedSurface { get; set; }
55 |
56 | private SubViewport portalViewport;
57 | private Camera3D portalCamera;
58 | private MeshInstance3D portalMesh;
59 | private StaticBody3D portalBlocker;
60 | private ShaderMaterial portalMaterial;
61 | private BaseMaterial3D unlinkedMaterial;
62 |
63 | private readonly List trackedPortalTravellers = new();
64 |
65 | public override void _Ready()
66 | {
67 | portalViewport = GetNode("PortalViewport");
68 | portalCamera = portalViewport.GetNode("PortalCamera");
69 | portalMesh = GetNode("PortalMesh");
70 | portalBlocker = GetNode("PortalBlocker");
71 |
72 | portalMaterial = (ShaderMaterial)
73 | GD.Load("res://Objects/PortalObject/PortalMaterial.tres").Duplicate();
74 | portalMaterial.SetShaderParameter("TextureAlbedo", portalViewport.GetTexture());
75 | unlinkedMaterial = (BaseMaterial3D)
76 | GD.Load("res://Objects/PortalObject/UnlinkedMaterial.tres");
77 | }
78 |
79 | public override void _PhysicsProcess(double delta)
80 | {
81 | UpdateViewPortSize();
82 | UpdatePortalCameraTransform();
83 | //UpdatePortalCameraCulling();
84 |
85 | DetectPortalUse();
86 | }
87 |
88 | private void UpdatePortalColour()
89 | {
90 | if (PortalType == PortalType.A)
91 | unlinkedMaterial.AlbedoColor = Colors.Blue;
92 | else
93 | unlinkedMaterial.AlbedoColor = Colors.Red;
94 | }
95 |
96 | private void DetectPortalUse()
97 | {
98 | var portalNormal = GlobalTransform.Basis * Vector3.Forward;
99 | foreach (var portalTraveller in trackedPortalTravellers)
100 | {
101 | var nPortalTraveller = portalTraveller as Node3D;
102 |
103 | var offsetFromPortal = nPortalTraveller.GlobalPosition - GlobalPosition;
104 | var currentPortalSide = Math.Sign(offsetFromPortal.Dot(portalNormal));
105 | var previousPortalSide = Math.Sign(
106 | portalTraveller.PreviousOffsetFromPortal.Dot(portalNormal)
107 | );
108 |
109 | if (currentPortalSide != previousPortalSide)
110 | {
111 | portalTraveller.Travel(GetRelativeTransformToLinkedPortal());
112 | portalTraveller.PreviousOffsetFromPortal =
113 | nPortalTraveller.GlobalPosition - GlobalPosition;
114 | }
115 | else
116 | {
117 | portalTraveller.PreviousOffsetFromPortal = offsetFromPortal;
118 | }
119 | }
120 | }
121 |
122 | // TODO: Bind this to a signal generated when size changes
123 | private void UpdateViewPortSize()
124 | {
125 | portalViewport.Size = (GetViewport() as Window).Size;
126 | }
127 |
128 | private void UpdatePortalCameraTransform()
129 | {
130 | if (LinkedPortal == null)
131 | return;
132 |
133 | portalCamera.GlobalTransform =
134 | GetRelativeTransformToLinkedPortal() * PlayerCamera.GlobalTransform;
135 | }
136 |
137 | private Transform3D GetRelativeTransformToLinkedPortal()
138 | {
139 | return LinkedPortal.GlobalTransform * GlobalTransform.Inverse();
140 | }
141 |
142 | private void UpdatePortalCameraCulling()
143 | {
144 | // TODO: Implment oblique
145 | throw new NotImplementedException();
146 | }
147 |
148 | // Linked to body_entered signal from Area3D
149 | private void OnBodyEntered(Node3D body)
150 | {
151 | if (body is IPortalTraveller portalTraveller)
152 | {
153 | trackedPortalTravellers.Add(portalTraveller);
154 | portalTraveller.PreviousOffsetFromPortal = body.GlobalPosition - GlobalPosition;
155 | }
156 | }
157 |
158 | // Linked to body_exited signal from Area3D
159 | private void OnBodyExited(Node3D body)
160 | {
161 | trackedPortalTravellers.Remove(body as IPortalTraveller);
162 | }
163 |
164 | public static Polygon2D GetPortalPolygon()
165 | {
166 | var width = 1.8f;
167 | var height = 3f;
168 |
169 | return new Polygon2D()
170 | {
171 | Polygon = new Vector2[]
172 | {
173 | // TODO: Make this not hard coded
174 | new Vector2(width / 2, height / 2),
175 | new Vector2(-width / 2, height / 2),
176 | new Vector2(-width / 2, -height / 2),
177 | new Vector2(width / 2, -height / 2)
178 | }
179 | };
180 | }
181 | }
182 |
183 | public enum PortalType
184 | {
185 | A,
186 | B
187 | }
188 |
--------------------------------------------------------------------------------
/Objects/PortalObject/PortalObject.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=5 format=3 uid="uid://dwdr21k6a1t3c"]
2 |
3 | [ext_resource type="Script" path="res://Objects/PortalObject/PortalObject.cs" id="1_kumex"]
4 | [ext_resource type="ArrayMesh" uid="uid://bga62l8t1viai" path="res://Objects/PortalObject/Mesh/PortalMeshShape.tres" id="2_anpjw"]
5 |
6 | [sub_resource type="BoxShape3D" id="BoxShape3D_7wb2n"]
7 | size = Vector3(1.8, 3, 0.1)
8 |
9 | [sub_resource type="BoxShape3D" id="BoxShape3D_qxx0g"]
10 | size = Vector3(1.8, 3, 0)
11 |
12 | [node name="PortalObject" type="Area3D"]
13 | collision_layer = 0
14 | collision_mask = 2
15 | monitorable = false
16 | script = ExtResource("1_kumex")
17 |
18 | [node name="PortalMesh" type="MeshInstance3D" parent="."]
19 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.15)
20 | layers = 2
21 | cast_shadow = 0
22 | ignore_occlusion_culling = true
23 | mesh = ExtResource("2_anpjw")
24 |
25 | [node name="PortalViewport" type="SubViewport" parent="."]
26 | render_target_clear_mode = 2
27 | render_target_update_mode = 4
28 |
29 | [node name="PortalCamera" type="Camera3D" parent="PortalViewport"]
30 | cull_mask = 1048573
31 |
32 | [node name="TrackTravellerArea" type="CollisionShape3D" parent="."]
33 | shape = SubResource("BoxShape3D_7wb2n")
34 |
35 | [node name="PortalBlocker" type="StaticBody3D" parent="."]
36 | collision_mask = 0
37 | input_ray_pickable = false
38 |
39 | [node name="CollisionShape3D" type="CollisionShape3D" parent="PortalBlocker"]
40 | shape = SubResource("BoxShape3D_qxx0g")
41 |
42 | [connection signal="body_entered" from="." to="." method="OnBodyEntered"]
43 | [connection signal="body_exited" from="." to="." method="OnBodyExited"]
44 |
--------------------------------------------------------------------------------
/Objects/PortalObject/PortalShader.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="VisualShader" load_steps=5 format=3 uid="uid://dt44x5qn6ygkx"]
2 |
3 | [sub_resource type="VisualShaderNodeTexture2DParameter" id="VisualShaderNodeTexture2DParameter_pouwu"]
4 | parameter_name = "TextureAlbedo"
5 | texture_type = 1
6 | texture_filter = 2
7 |
8 | [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_1xn7a"]
9 |
10 | [sub_resource type="VisualShaderNodeTexture" id="VisualShaderNodeTexture_gwcxx"]
11 | source = 5
12 | texture = SubResource("PlaceholderTexture2D_1xn7a")
13 | texture_type = 1
14 |
15 | [sub_resource type="VisualShaderNodeInput" id="VisualShaderNodeInput_eewbs"]
16 | input_name = "screen_uv"
17 |
18 | [resource]
19 | code = "shader_type spatial;
20 | render_mode cull_disabled, unshaded;
21 |
22 | uniform sampler2D TextureAlbedo : source_color, filter_linear;
23 |
24 |
25 |
26 | void fragment() {
27 | // Input:6
28 | vec2 n_out6p0 = SCREEN_UV;
29 |
30 |
31 | vec4 n_out5p0;
32 | // Texture2D:5
33 | n_out5p0 = texture(TextureAlbedo, n_out6p0);
34 |
35 |
36 | // Output:0
37 | ALBEDO = vec3(n_out5p0.xyz);
38 |
39 |
40 | }
41 | "
42 | graph_offset = Vector2(-623.057, -126.725)
43 | modes/cull = 2
44 | flags/unshaded = true
45 | nodes/fragment/0/position = Vector2(120, -220)
46 | nodes/fragment/4/node = SubResource("VisualShaderNodeTexture2DParameter_pouwu")
47 | nodes/fragment/4/position = Vector2(-600, -100)
48 | nodes/fragment/5/node = SubResource("VisualShaderNodeTexture_gwcxx")
49 | nodes/fragment/5/position = Vector2(-200, -100)
50 | nodes/fragment/6/node = SubResource("VisualShaderNodeInput_eewbs")
51 | nodes/fragment/6/position = Vector2(-600, -200)
52 | nodes/fragment/connections = PackedInt32Array(4, 0, 5, 2, 6, 0, 5, 0, 5, 0, 0, 0)
53 |
--------------------------------------------------------------------------------
/Objects/PortalObject/UnlinkedMaterial.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://bte1k8ectpfdj"]
2 |
3 | [ext_resource type="Texture2D" uid="uid://bl6xb1wlhp5my" path="res://Objects/PortalObject/UnlinkedTexture.png" id="1_o2e5y"]
4 |
5 | [resource]
6 | cull_mode = 2
7 | albedo_texture = ExtResource("1_o2e5y")
8 |
--------------------------------------------------------------------------------
/Objects/PortalObject/UnlinkedTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-s/Portal/bc4184d18a144cef19fd2309a870958863ca5580/Objects/PortalObject/UnlinkedTexture.png
--------------------------------------------------------------------------------
/Objects/PortalObject/UnlinkedTexture.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bl6xb1wlhp5my"
6 | path.s3tc="res://.godot/imported/UnlinkedTexture.png-98da1f55df7f64a2a3ff659fba53560d.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://Objects/PortalObject/UnlinkedTexture.png"
15 | dest_files=["res://.godot/imported/UnlinkedTexture.png-98da1f55df7f64a2a3ff659fba53560d.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/Objects/PortalTraveller/IPortalTraveller.cs:
--------------------------------------------------------------------------------
1 | namespace Portal.Objects.PortalTraveller;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Godot;
8 |
9 | internal interface IPortalTraveller
10 | {
11 | public Vector3 PreviousOffsetFromPortal { get; set; }
12 | public void Travel(Transform3D relativeTransform);
13 | }
14 |
--------------------------------------------------------------------------------
/Portal.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | true
5 |
6 |
7 | False
8 |
9 |
10 | False
11 |
12 |
13 | False
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Portal.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 2012
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portal", "Portal.csproj", "{DB757665-0C05-467A-BE98-98E7772DAFE2}"
4 | EndProject
5 | Global
6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
7 | Debug|Any CPU = Debug|Any CPU
8 | ExportDebug|Any CPU = ExportDebug|Any CPU
9 | ExportRelease|Any CPU = ExportRelease|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {DB757665-0C05-467A-BE98-98E7772DAFE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {DB757665-0C05-467A-BE98-98E7772DAFE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {DB757665-0C05-467A-BE98-98E7772DAFE2}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
15 | {DB757665-0C05-467A-BE98-98E7772DAFE2}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
16 | {DB757665-0C05-467A-BE98-98E7772DAFE2}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
17 | {DB757665-0C05-467A-BE98-98E7772DAFE2}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
18 | EndGlobalSection
19 | EndGlobal
20 |
--------------------------------------------------------------------------------
/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Portal": {
4 | "commandName": "Executable",
5 | "commandLineArgs": "--path.--verbose",
6 | "workingDirectory": ".",
7 | "nativeDebugging": true
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Portal
2 | A Portal remake made in godot.
3 |
4 | # Current Features
5 | 1) Place portals
6 | 2) Move through portals
7 | 3) Load a map from blender
8 |
9 | # Features to include
10 | 1) Allow physics objects (cubes) to pass through portals
11 | 2) Pick up physics object
12 | 3) Classic button/cube puzzle implementation
13 | 4) Oblique camera projection to fix visual glitch - currently can't be done in Godot without modifying source
14 | 5) Portal recursion (easier to do once oblique cameras are done)
15 |
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://b2p0e41ek4f26"
6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="Portal"
14 | run/main_scene="res://Maps/TestChamber04/TestChamber04.tscn"
15 | config/features=PackedStringArray("4.0", "C#", "Forward Plus")
16 | run/max_fps=60
17 | config/icon="res://icon.svg"
18 |
19 | [autoload]
20 |
21 | PortalSignals="*res://Autoloads/PortalSignals.cs"
22 |
23 | [dotnet]
24 |
25 | project/assembly_name="Portal"
26 |
27 | [editor]
28 |
29 | movie_writer/fps=30
30 |
31 | [input]
32 |
33 | MoveForward={
34 | "deadzone": 0.5,
35 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null)
36 | ]
37 | }
38 | MoveBackward={
39 | "deadzone": 0.5,
40 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
41 | ]
42 | }
43 | MoveLeft={
44 | "deadzone": 0.5,
45 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null)
46 | ]
47 | }
48 | MoveRight={
49 | "deadzone": 0.5,
50 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
51 | ]
52 | }
53 | Jump={
54 | "deadzone": 0.5,
55 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null)
56 | ]
57 | }
58 | ShootA={
59 | "deadzone": 0.5,
60 | "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"pressed":false,"double_click":false,"script":null)
61 | ]
62 | }
63 | ShootB={
64 | "deadzone": 0.5,
65 | "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"pressed":false,"double_click":false,"script":null)
66 | ]
67 | }
68 |
69 | [layer_names]
70 |
71 | 3d_physics/layer_1="Environment"
72 | 3d_physics/layer_2="Player"
73 | 3d_physics/layer_3="PortalableSurface"
74 |
75 | [physics]
76 |
77 | common/physics_ticks_per_second=240
78 |
79 | [rendering]
80 |
81 | anti_aliasing/quality/msaa_2d=2
82 | anti_aliasing/quality/msaa_3d=2
83 |
--------------------------------------------------------------------------------