Repository: outobugi/Terrain3D Branch: main Commit: 2d732fb0e363 Files: 356 Total size: 2.2 MB Directory structure: gitextract_y8dts4rh/ ├── .clang-format ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── actions/ │ │ ├── base-deps/ │ │ │ └── action.yml │ │ └── build-cache/ │ │ └── action.yml │ └── workflows/ │ ├── android.yml │ ├── build.yml │ ├── ios.yml │ ├── linux.yml │ ├── macos.yml │ ├── web.yml │ └── windows.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── AUTHORS.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── SConstruct ├── Terrain3D.sln ├── Terrain3D.vcxproj ├── Terrain3D.vcxproj.filters ├── Terrain3D.vcxproj.user ├── doc/ │ ├── Makefile │ ├── _static/ │ │ └── theme_overrides.css │ ├── api/ │ │ ├── class_terrain3d.rst │ │ ├── class_terrain3dassets.rst │ │ ├── class_terrain3dcollision.rst │ │ ├── class_terrain3ddata.rst │ │ ├── class_terrain3deditor.rst │ │ ├── class_terrain3dinstancer.rst │ │ ├── class_terrain3dmaterial.rst │ │ ├── class_terrain3dmeshasset.rst │ │ ├── class_terrain3dregion.rst │ │ ├── class_terrain3dtextureasset.rst │ │ ├── class_terrain3dutil.rst │ │ └── index.rst │ ├── build_docs.sh │ ├── conf.py │ ├── doc_classes/ │ │ ├── Terrain3D.xml │ │ ├── Terrain3DAssets.xml │ │ ├── Terrain3DCollision.xml │ │ ├── Terrain3DData.xml │ │ ├── Terrain3DEditor.xml │ │ ├── Terrain3DInstancer.xml │ │ ├── Terrain3DMaterial.xml │ │ ├── Terrain3DMeshAsset.xml │ │ ├── Terrain3DRegion.xml │ │ ├── Terrain3DTextureAsset.xml │ │ └── Terrain3DUtil.xml │ ├── docs/ │ │ ├── authors.rst │ │ ├── building_from_source.md │ │ ├── collision.md │ │ ├── contributing.rst │ │ ├── controlmap_format.md │ │ ├── data_format.md │ │ ├── displacement.md │ │ ├── double_precision.md │ │ ├── games.md │ │ ├── generating_csharp_bindings.md │ │ ├── getting_help.md │ │ ├── heightmaps.md │ │ ├── import_export.md │ │ ├── installation.md │ │ ├── instancer.md │ │ ├── introduction.md │ │ ├── keyboard_shortcuts.md │ │ ├── license.rst │ │ ├── navigation.md │ │ ├── nightly_builds.md │ │ ├── occlusion_culling.md │ │ ├── platforms.md │ │ ├── press.md │ │ ├── programming_languages.rst │ │ ├── shader_design.md │ │ ├── system_architecture.md │ │ ├── texture_painting.md │ │ ├── texture_prep.md │ │ ├── tips_environment.md │ │ ├── tips_technical.md │ │ ├── troubleshooting.md │ │ ├── tutorial_videos.md │ │ └── user_interface.md │ ├── dump_contributors.py │ ├── index.rst │ ├── make.bat │ └── requirements.txt ├── project/ │ ├── Terrain3D.csproj │ ├── Terrain3D.sln │ ├── addons/ │ │ └── terrain_3d/ │ │ ├── brushes/ │ │ │ ├── .gdignore │ │ │ ├── acrylic1.exr │ │ │ ├── circle0.exr │ │ │ ├── circle1.exr │ │ │ ├── circle2.exr │ │ │ ├── circle3.exr │ │ │ ├── circle4.exr │ │ │ ├── hill1.exr │ │ │ ├── hill2.exr │ │ │ ├── mountain1.exr │ │ │ ├── mountain2.exr │ │ │ ├── mountain3.exr │ │ │ ├── mountain4.exr │ │ │ ├── peak1.exr │ │ │ ├── peak2.exr │ │ │ ├── peak3.exr │ │ │ ├── ring1.exr │ │ │ ├── smoke.exr │ │ │ ├── square1.exr │ │ │ ├── square2.exr │ │ │ ├── square3.exr │ │ │ ├── square4.exr │ │ │ ├── square5.exr │ │ │ ├── stones.exr │ │ │ ├── terrain1.exr │ │ │ ├── terrain2.exr │ │ │ ├── terrain3.exr │ │ │ ├── terrain4.exr │ │ │ ├── terrain5.exr │ │ │ ├── terrain6.exr │ │ │ ├── texture1.exr │ │ │ ├── texture2.exr │ │ │ ├── texture3.exr │ │ │ ├── texture4.exr │ │ │ ├── texture5.exr │ │ │ └── vegetation1.exr │ │ ├── csharp/ │ │ │ ├── Terrain3D.cs │ │ │ ├── Terrain3D.cs.uid │ │ │ ├── Terrain3DAssets.cs │ │ │ ├── Terrain3DAssets.cs.uid │ │ │ ├── Terrain3DCollision.cs │ │ │ ├── Terrain3DCollision.cs.uid │ │ │ ├── Terrain3DData.cs │ │ │ ├── Terrain3DData.cs.uid │ │ │ ├── Terrain3DEditor.cs │ │ │ ├── Terrain3DEditor.cs.uid │ │ │ ├── Terrain3DInstancer.cs │ │ │ ├── Terrain3DInstancer.cs.uid │ │ │ ├── Terrain3DMaterial.cs │ │ │ ├── Terrain3DMaterial.cs.uid │ │ │ ├── Terrain3DMeshAsset.cs │ │ │ ├── Terrain3DMeshAsset.cs.uid │ │ │ ├── Terrain3DRegion.cs │ │ │ ├── Terrain3DRegion.cs.uid │ │ │ ├── Terrain3DTextureAsset.cs │ │ │ ├── Terrain3DTextureAsset.cs.uid │ │ │ ├── Terrain3DUtil.cs │ │ │ └── Terrain3DUtil.cs.uid │ │ ├── extras/ │ │ │ ├── 3rd_party/ │ │ │ │ ├── import_sgt.gd │ │ │ │ ├── import_sgt.gd.uid │ │ │ │ ├── project_on_terrain3d.gd │ │ │ │ └── project_on_terrain3d.gd.uid │ │ │ ├── particle_example/ │ │ │ │ ├── Terrain3DParticles.tscn │ │ │ │ ├── grass.gdshader │ │ │ │ ├── grass.gdshader.uid │ │ │ │ ├── grass_material.tres │ │ │ │ ├── particles.gdshader │ │ │ │ ├── particles.gdshader.uid │ │ │ │ ├── process_material.tres │ │ │ │ ├── terrain_3D_particles.gd │ │ │ │ └── terrain_3D_particles.gd.uid │ │ │ └── shaders/ │ │ │ ├── M_ocean.tres │ │ │ ├── hex_grid.gdshaderinc │ │ │ ├── hex_grid.gdshaderinc.uid │ │ │ ├── lightweight.gdshader │ │ │ ├── lightweight.gdshader.uid │ │ │ ├── minimum.gdshader │ │ │ ├── minimum.gdshader.uid │ │ │ ├── ocean_shader.gdshader │ │ │ └── ocean_shader.gdshader.uid │ │ ├── icons/ │ │ │ ├── autoshader.svg.import │ │ │ ├── color_paint.svg.import │ │ │ ├── height_add.svg.import │ │ │ ├── height_div.svg.import │ │ │ ├── height_flat.svg.import │ │ │ ├── height_mul.svg.import │ │ │ ├── height_slope.svg.import │ │ │ ├── height_smooth.svg.import │ │ │ ├── height_sub.svg.import │ │ │ ├── holes.svg.import │ │ │ ├── layers.svg.import │ │ │ ├── multimesh.svg.import │ │ │ ├── navigation.svg.import │ │ │ ├── picker_checked.svg.import │ │ │ ├── region_add.svg.import │ │ │ ├── region_remove.svg.import │ │ │ ├── terrain3d.svg.import │ │ │ ├── texture_paint.svg.import │ │ │ ├── texture_spray.svg.import │ │ │ └── wetness.svg.import │ │ ├── menu/ │ │ │ ├── bake_lod_dialog.gd │ │ │ ├── bake_lod_dialog.gd.uid │ │ │ ├── bake_lod_dialog.tscn │ │ │ ├── baker.gd │ │ │ ├── baker.gd.uid │ │ │ ├── channel_packer.gd │ │ │ ├── channel_packer.gd.uid │ │ │ ├── channel_packer.tscn │ │ │ ├── channel_packer_dragdrop.gd │ │ │ ├── channel_packer_dragdrop.gd.uid │ │ │ ├── channel_packer_import_template.txt │ │ │ ├── directory_setup.gd │ │ │ ├── directory_setup.gd.uid │ │ │ ├── directory_setup.tscn │ │ │ ├── terrain_menu.gd │ │ │ └── terrain_menu.gd.uid │ │ ├── plugin.cfg │ │ ├── src/ │ │ │ ├── asset_dock.gd │ │ │ ├── asset_dock.gd.uid │ │ │ ├── asset_dock.tscn │ │ │ ├── double_slider.gd │ │ │ ├── double_slider.gd.uid │ │ │ ├── editor_plugin.gd │ │ │ ├── editor_plugin.gd.uid │ │ │ ├── gradient_operation_builder.gd │ │ │ ├── gradient_operation_builder.gd.uid │ │ │ ├── multi_picker.gd │ │ │ ├── multi_picker.gd.uid │ │ │ ├── operation_builder.gd │ │ │ ├── operation_builder.gd.uid │ │ │ ├── tool_settings.gd │ │ │ ├── tool_settings.gd.uid │ │ │ ├── toolbar.gd │ │ │ ├── toolbar.gd.uid │ │ │ ├── ui.gd │ │ │ └── ui.gd.uid │ │ ├── terrain.gdextension │ │ ├── terrain.gdextension.uid │ │ ├── tools/ │ │ │ ├── importer.gd │ │ │ ├── importer.gd.uid │ │ │ ├── importer.tscn │ │ │ ├── region_mover.gd │ │ │ └── region_mover.gd.uid │ │ └── utils/ │ │ ├── terrain_3d_objects.gd │ │ ├── terrain_3d_objects.gd.uid │ │ ├── transform_changed_notifier.gd │ │ └── transform_changed_notifier.gd.uid │ ├── demo/ │ │ ├── CodeGeneratedDemo.tscn │ │ ├── Demo.tscn │ │ ├── NavigationDemo.tscn │ │ ├── assets/ │ │ │ ├── materials/ │ │ │ │ ├── M_crystal_blue.tres │ │ │ │ ├── M_crystal_purple.tres │ │ │ │ ├── M_crystal_red.tres │ │ │ │ ├── M_rock23_black_tp.tres │ │ │ │ └── M_rock23_tp.tres │ │ │ ├── models/ │ │ │ │ ├── CrystalC.tscn │ │ │ │ ├── LOD10Example.tscn │ │ │ │ ├── LOD5Example.tscn │ │ │ │ ├── RockA.glb │ │ │ │ ├── RockA.glb.import │ │ │ │ ├── RockA.tscn │ │ │ │ ├── RockB.glb │ │ │ │ ├── RockB.glb.import │ │ │ │ ├── RockB.tscn │ │ │ │ ├── RockC.glb │ │ │ │ ├── RockC.glb.import │ │ │ │ ├── RockC.tscn │ │ │ │ ├── Tunnel.glb │ │ │ │ ├── Tunnel.glb.import │ │ │ │ └── Tunnel.tscn │ │ │ └── textures/ │ │ │ ├── asset_licenses.txt │ │ │ ├── ground037_alb_ht.png.import │ │ │ ├── ground037_nrm_rgh.png.import │ │ │ ├── rock023_alb_ht.png.import │ │ │ └── rock023_nrm_rgh.png.import │ │ ├── components/ │ │ │ ├── DemoBenchmark.tscn │ │ │ ├── Enemy.tscn │ │ │ ├── Environment.tscn │ │ │ ├── Player.tscn │ │ │ ├── Tunnel.tscn │ │ │ └── UI.tscn │ │ ├── csharp/ │ │ │ ├── CodeGenerated.cs │ │ │ ├── CodeGenerated.cs.uid │ │ │ └── CodeGeneratedCSDemo.tscn │ │ ├── data/ │ │ │ ├── M_terrain.tres │ │ │ ├── assets.tres │ │ │ ├── nav_mesh.res │ │ │ ├── terrain3d_00-01.res │ │ │ ├── terrain3d_00-02.res │ │ │ └── terrain3d_00_00.res │ │ └── src/ │ │ ├── CameraManager.gd │ │ ├── CameraManager.gd.uid │ │ ├── CaveEntrance.gd │ │ ├── CaveEntrance.gd.uid │ │ ├── CodeGenerated.gd │ │ ├── CodeGenerated.gd.uid │ │ ├── DemoScene.gd │ │ ├── DemoScene.gd.uid │ │ ├── Enemy.gd │ │ ├── Enemy.gd.uid │ │ ├── Player.gd │ │ ├── Player.gd.uid │ │ ├── RuntimeNavigationBaker.gd │ │ ├── RuntimeNavigationBaker.gd.uid │ │ ├── UI.gd │ │ └── UI.gd.uid │ ├── icon.png.import │ └── project.godot ├── src/ │ ├── constants.h │ ├── generated_texture.cpp │ ├── generated_texture.h │ ├── logger.h │ ├── register_types.cpp │ ├── register_types.h │ ├── shaders/ │ │ ├── auto_shader.glsl │ │ ├── backgrounds.glsl │ │ ├── debug_views.glsl │ │ ├── displacement.glsl │ │ ├── displacement_buffer.glsl │ │ ├── dual_scaling.glsl │ │ ├── editor_functions.glsl │ │ ├── gpu_depth.glsl │ │ ├── macro_variation.glsl │ │ ├── main.glsl │ │ ├── overlays.glsl │ │ ├── pbr_views.glsl │ │ ├── projection.glsl │ │ └── samplers.glsl │ ├── target_node_3d.h │ ├── terrain_3d.cpp │ ├── terrain_3d.h │ ├── terrain_3d_asset_resource.h │ ├── terrain_3d_assets.cpp │ ├── terrain_3d_assets.h │ ├── terrain_3d_collision.cpp │ ├── terrain_3d_collision.h │ ├── terrain_3d_data.cpp │ ├── terrain_3d_data.h │ ├── terrain_3d_editor.cpp │ ├── terrain_3d_editor.h │ ├── terrain_3d_instancer.cpp │ ├── terrain_3d_instancer.h │ ├── terrain_3d_material.cpp │ ├── terrain_3d_material.h │ ├── terrain_3d_mesh_asset.cpp │ ├── terrain_3d_mesh_asset.h │ ├── terrain_3d_mesher.cpp │ ├── terrain_3d_mesher.h │ ├── terrain_3d_region.cpp │ ├── terrain_3d_region.h │ ├── terrain_3d_texture_asset.cpp │ ├── terrain_3d_texture_asset.h │ ├── terrain_3d_util.cpp │ ├── terrain_3d_util.h │ ├── unit_testing.cpp │ └── unit_testing.h └── tools/ ├── build_release.sh ├── hooks/ │ ├── asmessage.applescript │ ├── canonicalize_filename.sh │ ├── pre-commit │ ├── pre-commit-clang-format │ └── winmessage.ps1 └── install-hooks.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # Commented out parameters are those with the same value as base LLVM style. # We can uncomment them if we want to change their value, or enforce the # chosen value in case the base style changes (last sync: Clang 14.0). --- ### General config, applies to all languages ### BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign # AlignArrayOfStructures: None # AlignConsecutiveMacros: None # AlignConsecutiveAssignments: None # AlignConsecutiveBitFields: None # AlignConsecutiveDeclarations: None # AlignEscapedNewlines: Right AlignOperands: DontAlign AlignTrailingComments: false # AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: false # AllowShortEnumsOnASingleLine: true # AllowShortBlocksOnASingleLine: Never # AllowShortCaseLabelsOnASingleLine: false # AllowShortFunctionsOnASingleLine: All # AllowShortLambdasOnASingleLine: All # AllowShortIfStatementsOnASingleLine: Never # AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: None # AlwaysBreakAfterReturnType: None # AlwaysBreakBeforeMultilineStrings: false # AlwaysBreakTemplateDeclarations: MultiLine # AttributeMacros: # - __capability # BinPackArguments: true # BinPackParameters: true # BraceWrapping: # AfterCaseLabel: false # AfterClass: false # AfterControlStatement: Never # AfterEnum: false # AfterFunction: false # AfterNamespace: false # AfterObjCDeclaration: false # AfterStruct: false # AfterUnion: false # AfterExternBlock: false # BeforeCatch: false # BeforeElse: false # BeforeLambdaBody: false # BeforeWhile: false # IndentBraces: false # SplitEmptyFunction: true # SplitEmptyRecord: true # SplitEmptyNamespace: true # BreakBeforeBinaryOperators: None # BreakBeforeConceptDeclarations: true # BreakBeforeBraces: Attach # BreakBeforeInheritanceComma: false # BreakInheritanceList: BeforeColon # BreakBeforeTernaryOperators: true # BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: AfterColon # BreakStringLiterals: true ColumnLimit: 0 # CommentPragmas: '^ IWYU pragma:' # QualifierAlignment: Leave # CompactNamespaces: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false # DeriveLineEnding: true # DerivePointerAlignment: false # DisableFormat: false # EmptyLineAfterAccessModifier: Never # EmptyLineBeforeAccessModifier: LogicalBlock # ExperimentalAutoDetectBinPacking: false # PackConstructorInitializers: BinPack ConstructorInitializerAllOnOneLineOrOnePerLine: true # AllowAllConstructorInitializersOnNextLine: true # FixNamespaceComments: true # ForEachMacros: # - foreach # - Q_FOREACH # - BOOST_FOREACH # IfMacros: # - KJ_IF_MAYBE # IncludeBlocks: Preserve IncludeCategories: - Regex: '".*"' Priority: 1 - Regex: '^<.*\.h>' Priority: 2 - Regex: '^<.*' Priority: 3 # IncludeIsMainRegex: '(Test)?$' # IncludeIsMainSourceRegex: '' # IndentAccessModifiers: false IndentCaseLabels: true # IndentCaseBlocks: false # IndentGotoLabels: true # IndentPPDirectives: None # IndentExternBlock: AfterExternBlock # IndentRequires: false IndentWidth: 4 # IndentWrappedFunctionNames: false # InsertTrailingCommas: None # JavaScriptQuotes: Leave # JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false # LambdaBodyIndentation: Signature # MacroBlockBegin: '' # MacroBlockEnd: '' # MaxEmptyLinesToKeep: 1 # NamespaceIndentation: None # PenaltyBreakAssignment: 2 # PenaltyBreakBeforeFirstCallParameter: 19 # PenaltyBreakComment: 300 # PenaltyBreakFirstLessLess: 120 # PenaltyBreakOpenParenthesis: 0 # PenaltyBreakString: 1000 # PenaltyBreakTemplateDeclaration: 10 # PenaltyExcessCharacter: 1000000 # PenaltyReturnTypeOnItsOwnLine: 60 # PenaltyIndentedWhitespace: 0 # PointerAlignment: Right # PPIndentWidth: -1 # ReferenceAlignment: Pointer # ReflowComments: true # RemoveBracesLLVM: false # SeparateDefinitionBlocks: Leave # ShortNamespaceLines: 1 # SortIncludes: CaseSensitive # SortJavaStaticImport: Before # SortUsingDeclarations: true # SpaceAfterCStyleCast: false # SpaceAfterLogicalNot: false # SpaceAfterTemplateKeyword: true # SpaceBeforeAssignmentOperators: true # SpaceBeforeCaseColon: false # SpaceBeforeCpp11BracedList: false # SpaceBeforeCtorInitializerColon: true # SpaceBeforeInheritanceColon: true # SpaceBeforeParens: ControlStatements # SpaceBeforeParensOptions: # AfterControlStatements: true # AfterForeachMacros: true # AfterFunctionDefinitionName: false # AfterFunctionDeclarationName: false # AfterIfMacros: true # AfterOverloadedOperator: false # BeforeNonEmptyParentheses: false # SpaceAroundPointerQualifiers: Default # SpaceBeforeRangeBasedForLoopColon: true # SpaceInEmptyBlock: false # SpaceInEmptyParentheses: false # SpacesBeforeTrailingComments: 1 # SpacesInAngles: Never # SpacesInConditionalStatement: false # SpacesInContainerLiterals: true # SpacesInCStyleCastParentheses: false ## Godot TODO: We'll want to use a min of 1, but we need to see how to fix ## our comment capitalization at the same time. SpacesInLineCommentPrefix: Minimum: 0 Maximum: -1 # SpacesInParentheses: false # SpacesInSquareBrackets: false # SpaceBeforeSquareBrackets: false # BitFieldColonSpacing: Both # StatementAttributeLikeMacros: # - Q_EMIT # StatementMacros: # - Q_UNUSED # - QT_REQUIRE_VERSION TabWidth: 4 # UseCRLF: false UseTab: Always # WhitespaceSensitiveMacros: # - STRINGIZE # - PP_STRINGIZE # - BOOST_PP_STRINGIZE # - NS_SWIFT_NAME # - CF_SWIFT_NAME --- ### C++ specific config ### Language: Cpp Standard: c++17 --- ### ObjC specific config ### Language: ObjC # ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 4 # ObjCBreakBeforeNestedBlockParam: true # ObjCSpaceAfterProperty: false # ObjCSpaceBeforeProtocolList: true --- ### Java specific config ### Language: Java # BreakAfterJavaFieldAnnotations: false JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] ... ================================================ FILE: .gitattributes ================================================ # Normalize EOL for all files that Git considers text files. * text=auto eol=lf ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [TokisanGames] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username thanks_dev: # Replace with a single thanks.dev username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Report a technical issue description: Report a potential bug or technical issue in Terrain3D body: - type: markdown attributes: value: | Has your issue already been addressed in [Installation](https://terrain3d.readthedocs.io/en/latest/docs/installation.html), [Preparing textures](https://terrain3d.readthedocs.io/en/latest/docs/texture_prep.html), [Troubleshooting](https://terrain3d.readthedocs.io/en/latest/docs/troubleshooting.html), or [Tips](https://terrain3d.readthedocs.io/en/latest/docs/tips_technical.html)? - type: input attributes: label: Terrain3D version description: > Release version or the commit string of a development build. No -dev versions, provide the commit shown in `git log`. placeholder: v1.0.0 validations: required: true - type: input attributes: label: System information description: | - Specify the version of Godot and your OS, GPU and rendering backend (Vulkan Forward+, Mobile, Compatibility/WebGL). - You can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window. placeholder: Godot v4.1.3.stable - Windows 10/64 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3070 Laptop GPU (NVIDIA; 31.0.15.4633) validations: required: true - type: dropdown id: demo attributes: label: Is the issue reproducable in the demo? description: | Try to isolate the problem. E.g. If you're having issues with a texture, put it in the demo and see if it causes the same issue. options: - '' - 'Yes' - 'No' - 'Not applicable' validations: required: true - type: textarea attributes: label: Issue description description: | Briefly describe the issue. What doesn't work, what are you expecting, and what have you done to troubleshooting it? You can paste or drag in screenshots or videos. Format code with ``` before and after on their own lines. validations: required: true - type: textarea attributes: label: Logs description: | Terrain3D has [extensive logging](https://terrain3d.readthedocs.io/en/latest/docs/troubleshooting.html#debug-logs). Enable it and attach a text file, or copy the logs here. Format logs with ``` before and after on their own lines. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Documentation url: https://terrain3d.readthedocs.io/ about: Many questions and issues have already been answered, including status, tips, and troubleshooting. - name: Ask questions on Discord url: https://tokisan.com/discord about: Just have a question about features? Please use discord. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature request description: Suggest an idea for Terrain3D, or something else that isn't a technical issue body: - type: markdown attributes: value: | Have you reviewed the [status of current and pending features](https://terrain3d.readthedocs.io/en/latest/docs/tips_technical.html#are-certain-features-supported)? - type: textarea attributes: label: Description description: | Please describe the feature you would like to see in Terrain3D, how it might work, and why it's helpful. Include examples of other terrain systems, GDC talks, white papers, and links to blogs or code that will be good reference info. validations: required: true ================================================ FILE: .github/actions/base-deps/action.yml ================================================ name: Setup Base Dependencies description: Setup base dependencies inputs: platform: required: true description: Target platform. runs: using: "composite" steps: - name: Setup Python 3.x uses: actions/setup-python@v5 with: python-version: 3.x - name: Setup SCons 4.4 shell: bash run: | python -c "import sys; print(sys.version)" python -m pip install scons==4.4.0 scons --version - name: Setup Android Dependencies if: inputs.platform == 'android' uses: nttld/setup-ndk@v1 with: ndk-version: r28b link-to-sdk: true - name: Setup Windows Dependencies if: inputs.platform == 'windows' shell: sh run: | sudo apt-get install mingw-w64 sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - name: Setup Web Dependencies if: inputs.platform == 'web' uses: mymindstorm/setup-emsdk@v14 with: version: 3.1.64 no-cache: true - name: Verify Emscripten setup if: inputs.platform == 'web' shell: bash run: | emcc -v ================================================ FILE: .github/actions/build-cache/action.yml ================================================ name: Setup Build Cache description: Setup build cache. inputs: cache-name: description: The cache base name (job name by default). default: "${{ github.job }}" scons-cache: description: The scons cache path. default: "${{ github.workspace }}/.scons-cache/" runs: using: "composite" steps: # Upload cache on completion and check it out now - name: Load .scons_cache directory uses: actions/cache@v4 with: path: ${{ inputs.scons-cache }} key: ${{ inputs.cache-name }}-${{ github.ref }} # We try to match an existing cache to restore from it. Each potential key is checked against # all existing caches as a prefix. E.g. 'linux-template-minimal' would match any cache that # starts with "linux-template-minimal", such as "linux-template-minimal-master-refs/heads/master-6588a4a29af1621086feac0117d5d4d37af957fd". # # We check these prefixes in this order: # # 1. The exact match, including the base branch, the commit reference, and the SHA hash of the commit. # 2. A partial match for the same base branch and the same commit reference. # 3. A partial match for the same base branch and the base branch commit reference. # 4. A partial match for the same base branch only (not ideal, matches any PR with the same base branch). restore-keys: | ${{ inputs.cache-name }}-${{ github.ref }} ${{ inputs.cache-name }}-refs/heads/main ${{ inputs.cache-name }} ================================================ FILE: .github/workflows/android.yml ================================================ name: 🤖 Android Builds on: [ workflow_call, workflow_dispatch ] jobs: build: name: 🤖 Android ${{ matrix.arch }} ${{ matrix.target }} runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: platform: [android] target: [debug, release] arch: [arm64, arm32] steps: - name: Checkout Terrain3D uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' ARCH: '${{ matrix.arch }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' arch=$ARCH debug_symbols=no -j2 - name: Include Files shell: sh run: | ls -l project/addons/terrain_3d/bin/ cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .github/workflows/build.yml ================================================ # Modeled off of godot-cpp https://github.com/godotengine/godot-cpp/blob/master/.github/workflows/ci.yml name: 🛠️ Build All on: push: branches: [ main, 1.1-godot4.4 ] pull_request: paths: [ '**' ] workflow_dispatch: concurrency: group: ${{ github.ref }} cancel-in-progress: true jobs: build: name: ${{ matrix.name }} ${{ matrix.target }} runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: identifier: [linux, windows, macos, ios, android-arm32, android-arm64, web-nothreads] target: [debug, release] include: - identifier: linux platform: linux name: 🐧 Linux runner: ubuntu-22.04 flags: arch=x86_64 - identifier: windows platform: windows name: 🪟 Windows runner: ubuntu-22.04 flags: arch=x86_64 - identifier: macos platform: macos name: 🍎 macOS runner: macos-latest flags: arch=universal - identifier: ios platform: ios name: 🍏 iOS runner: macos-latest flags: arch=universal - identifier: android-arm32 platform: android name: 🤖 Android Arm32 runner: ubuntu-22.04 flags: arch=arm32 - identifier: android-arm64 platform: android name: 🤖 Android Arm64 runner: ubuntu-22.04 flags: arch=arm64 - identifier: web-nothreads platform: web name: 🌐 Web No-threads runner: ubuntu-22.04 flags: threads=no steps: - name: Checkout Terrain3D env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.identifier }}-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' ${{ matrix.flags }} debug_symbols=no -j2 - name: Strip Libraries (Windows/Linux) if: ${{ matrix.platform == 'windows' || matrix.platform == 'linux' }} shell: sh run: | ls -l project/addons/terrain_3d/bin/ strip project/addons/terrain_3d/bin/libterrain.* ls -l project/addons/terrain_3d/bin/ - name: Include Files shell: sh run: | cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.identifier }}-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .github/workflows/ios.yml ================================================ name: 🍏 iOS Builds on: [ workflow_call, workflow_dispatch ] jobs: build: name: 🍏 iOS ${{ matrix.arch }} ${{ matrix.target }} runs-on: macos-latest strategy: fail-fast: false matrix: platform: [ios] target: [debug, release] arch: [universal] steps: - name: Checkout Terrain3D uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.platform }}-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' ARCH: '${{ matrix.arch }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' arch=$ARCH debug_symbols=no -j2 - name: Include Files shell: sh run: | ls -l project/addons/terrain_3d/bin/* cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .github/workflows/linux.yml ================================================ name: 🐧 Linux Builds on: [ workflow_call, workflow_dispatch ] jobs: build: name: 🐧 Linux ${{ matrix.arch }} ${{ matrix.target }} runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: platform: [linux] target: [debug, release] arch: [x86_64] steps: - name: Checkout Terrain3D uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.platform }}-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' ARCH: '${{ matrix.arch }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' arch=$ARCH debug_symbols=no -j2 - name: Prepare Files shell: sh run: | ls -l project/addons/terrain_3d/bin/ strip project/addons/terrain_3d/bin/libterrain.* ls -l project/addons/terrain_3d/bin/ cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .github/workflows/macos.yml ================================================ name: 🍎 macOS Builds on: [ workflow_call, workflow_dispatch ] jobs: build: name: 🍎 macOS ${{ matrix.arch }} ${{ matrix.target }} runs-on: macos-latest strategy: fail-fast: false matrix: platform: [macos] target: [debug, release] arch: [universal] steps: - name: Checkout Terrain3D uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.platform }}-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' ARCH: '${{ matrix.arch }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' arch=$ARCH debug_symbols=no -j2 - name: Include Files shell: sh run: | ls -l project/addons/terrain_3d/bin/*/ cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .github/workflows/web.yml ================================================ name: 🌐 Web Builds on: [ workflow_call, workflow_dispatch ] jobs: build: name: 🌐 Web ${{ matrix.arch }} ${{ matrix.target }} runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: platform: [web] target: [debug, release] #threads: [yes, no] steps: - name: Checkout Terrain3D uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.platform }}-nothreads-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' threads=no debug_symbols=no -j2 - name: Prepare Files shell: sh run: | ls -l project/addons/terrain_3d/bin/ cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.platform }}-nothreads-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .github/workflows/windows.yml ================================================ name: 🪟 Windows Builds on: [ workflow_call, workflow_dispatch ] jobs: build: name: 🪟 Windows ${{ matrix.arch }} ${{ matrix.target }} runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: platform: [windows] target: [debug, release] arch: [x86_64] steps: - name: Checkout Terrain3D uses: actions/checkout@v4 with: submodules: recursive - name: Setup Base Dependencies uses: ./.github/actions/base-deps with: platform: ${{ matrix.platform }} - name: Setup Build Cache uses: ./.github/actions/build-cache with: cache-name: ${{ matrix.platform }}-${{ matrix.target }} continue-on-error: true - name: Build Terrain3D env: SCONS_CACHE: "${{ github.workspace }}/.scons-cache/" TARGET: 'template_${{ matrix.target }}' ARCH: '${{ matrix.arch }}' shell: sh run: | scons target=$TARGET platform='${{ matrix.platform }}' arch=$ARCH debug_symbols=no -j2 - name: Prepare Files shell: sh run: | ls -l project/addons/terrain_3d/bin/ strip project/addons/terrain_3d/bin/libterrain.* ls -l project/addons/terrain_3d/bin/ cp '${{ github.workspace }}/README.md' '${{ github.workspace }}/LICENSE.txt' ${{ github.workspace }}/project/addons/terrain_3d/ - name: Upload Package uses: actions/upload-artifact@v4 with: include-hidden-files: true name: t3d-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.target }} path: | ${{ github.workspace }}/project/ merge: runs-on: ubuntu-latest needs: build steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 with: include-hidden-files: true name: ${{ github.event.repository.name }} pattern: t3d-* delete-merged: true ================================================ FILE: .gitignore ================================================ # Terrain3D project/addons/terrain_3d/bin/ project/addons/explore-editor-theme/ project/_dev/ project/_export/ project/_tests/ project/test/ src/_archive/ src/gen/ _misc/ _patches/ *.zip # Docs doc/_build doc/art/ # Godot-specific ignores (Include script cache. See godot issue #75388) /project/.import/ /project/.godot/ export.cfg export_presets.cfg # Objects .scons-cache/ *.os *.obj *.o # SConstruct .sconf_temp .sconsign.dblite *.pyc # MacOS .DS_Store # Editors .vscode/ .vs/ ================================================ FILE: .gitmodules ================================================ [submodule "godot-cpp"] path = godot-cpp url = https://github.com/godotengine/godot-cpp.git branch = master ================================================ FILE: .readthedocs.yaml ================================================ # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: "ubuntu-22.04" tools: python: "3.11" python: install: - requirements: doc/requirements.txt sphinx: configuration: doc/conf.py # Possible options: htmlzip, pdf, epub formats: [] ================================================ FILE: AUTHORS.md ================================================ # Terrain3D Authors The creation of this plugin is thanks to the following contributors. ## Core Team * Cory Petkovsek [@TokisanGames](https://github.com/TokisanGames) * Roope Palmroos [@outobugi](https://github.com/outobugi) * Emerson Rowland [@Xtarsia](https://github.com/Xtarsia) ## MVPs * Tom Coxon [@tcoxon](https://github.com/tcoxon) * Aidan Davey [@aidandavey](https://github.com/aidandavey) ## Contributors * Lorenz [@lw64](https://github.com/lw64) * Loic Chen [@painfulexistence](https://github.com/painfulexistence) * [@Dekker3D](https://github.com/Dekker3D) * Laurent Senta [@laurentsenta](https://github.com/laurentsenta) * Ryan [@Ryan-000](https://github.com/Ryan-000) * Jacob Coughenour [@jacobcoughenour](https://github.com/jacobcoughenour) * Slashscreen [@SlashScreen](https://github.com/SlashScreen) * Roman Shapiro [@rds1983](https://github.com/rds1983) * Malido [@Malidos](https://github.com/Malidos) * Bruno Meneguello [@bkmeneguello](https://github.com/bkmeneguello) * xht [@xanhast](https://github.com/xanhast) * [@wenqiangwang](https://github.com/wenqiangwang) * [@k1r4n8](https://github.com/k1r4n8) * [@jesus-g20](https://github.com/jesus-g20) * [@jeffercize](https://github.com/jeffercize) * [@artoonu](https://github.com/artoonu) * [@Zennii](https://github.com/Zennii) * Sven Cannivy [@svencan](https://github.com/svencan) * StAkira [@stakira](https://github.com/stakira) * Sean Otto [@seanj29](https://github.com/seanj29) * Scott Davis [@scottdavis](https://github.com/scottdavis) * Rose [@az-raven](https://github.com/az-raven) * [@OzelotVanilla](https://github.com/OzelotVanilla) * Matt [@FishOfTheNorthStar](https://github.com/FishOfTheNorthStar) * L [@lfxu](https://github.com/lfxu) * Joyless [@Joy-less](https://github.com/Joy-less) * Johan Frohlander [@pew-jfrohlander](https://github.com/pew-jfrohlander) * [@GabrielPlante](https://github.com/GabrielPlante) * Furq [@Furqit](https://github.com/Furqit) * Feiyun Wang [@feiyunw](https://github.com/feiyunw) * Brian [@epitaque](https://github.com/epitaque) * Dissonant Void [@DissonantVoid](https://github.com/DissonantVoid) * [@directedchaossoftware](https://github.com/directedchaossoftware) * [@CrowhopTech](https://github.com/CrowhopTech) * Benjamin Wolff [@benjiwolff](https://github.com/benjiwolff) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing To Terrain3D We need your help to make this the best terrain plugin for Godot. Please see [System Architecture](https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html) to gain an understanding of how the system works. Then review the [roadmap](https://github.com/users/TokisanGames/projects/3) for priority of issues. If you wish to take on a major component, it's best to join our [discord server](https://tokisan.com/discord) and discuss your plans in #terrain3d-dev to make sure your efforts are aligned with other plans. **Table of Contents** * [Important Directories](#important-directories) * [Setup Your System](#setup-your-system) * [PR Workflow](#pr-workflow) * [Code Style](#code-style) * [Documentation](#documentation) * [Maintainers](#maintainers) ## Important Directories * `src` - C++ source for the library * `src/shaders` - GLSL source for the default shader * `doc/doc_classes` - XML docs for C++ classes * `doc/docs` - MD tutorial docs * `project/addons/terrain_3d` * `src` - GDScript for the editor plugin: the user interface for hand editing * `menu` - GDScript for the tools menu: bakers, channel packer * `tools` - GDScript for the importer, which will eventually be merged into the menu * `utils` - GDScript for other objects, eg. terrain_3d_objects.gd * `extras` - GDScript examples for users ## Setup Your System Make sure you are setup to [build the plugin from source](https://terrain3d.readthedocs.io/en/stable/docs/building_from_source.html). ### Install clang-format clang-format will adjust the style of your code to a consistent standard. Once you install it you can manually run it on all of your code to see or apply changes, and you can set it up to run automatically upon each commit. #### Installing clang-format binary onto your system. * Download version 13 or later * Make sure the LLVM binary directory where `clang-format` is stored gets added to the `PATH` during installation * Linux/OSX: Install the `clang-format` package, or all of `LLVM` or `clang` if your distribution doesn't provide the standalone tool * Windows: Download LLVM for Windows from #### Using clang-format automatically We use Godot's clang-format hooks that will format your code upon making a commit. Install the hooks into your repo after cloning. * Copy `tools/hooks/*` into `.git/hooks` or run `python tools/install-hooks.py` #### Using clang-format manually * View a formatted file, no changes on disk: `clang-format ` * See what changes would be made: `git-clang-format --diff ` * Change the files in place: `clang-format -i ` ## PR Workflow We use the standard [Godot PR workflow](https://contributing.godotengine.org/en/latest/organization/pull_requests/creating_pull_requests.html). Please submit PRs according to the same process Godot uses. This includes: * Creating a new branch (not main) before submitting the PR. * Never using git merge, or the `sync` button. Only fetch, push, pull. * To update your PR to the latest main, rebase it then force push into your branch. * `git pull --rebase upstream main` * `git push -f` Read the guide above for more details. ## Code Style ### GDScript In general, follow the [Godot GDScript style guidelines](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html). In addition: * All variables and functions are static typed, with a colon then space (eg. `var state: int = 3`) * Auto static typing can be used *only* when the type is specifically assigned (eg. `var point := Vector2(1, 1)`) * Two blank lines between functions ### GLSL * Similar to C++ formatting below, except use `float` and no clang-format * Private uniforms are prefaced with `_` and are hidden from the inspector and not accessible via set/get_shader_param() ### C++ In general, follow the [Godot C++ style guidelines](https://contributing.godotengine.org/en/latest/engine/guidelines/code_style.html). In addition: Use const correctness: * Function parameters that won't be changed (almost all) should be marked const. Exceptions are pointers, or where passing a variable the function is supposed to modify, eg. Terrain3D::_generate_triangles * Functions that won't change the object should be marked const (e.g. most get_ functions) Pass by reference: * Pass everything larger than 4 bytes by reference, including Ref<> and arrays, dictionaries, RIDs. e.g. `const Transform3D &xform` * Floats: * Use `real_t` instead of `float` * Format float literals like `0.0f` or `0.f` * Float literals and `real_t` variables can share operations (e.g. `mydouble += 1.0f`) unless the compiler complains. e.g. `Math::lerp(mydouble, real_t(0.0f), real_t(1.0f))` * Standard Library & Godot Functions: * Use `std::abs`, not `Math::abs` (same), and definitely not `abs` (broken on mingw) * Use `std::isnan`, not `Math::is_nan` (same) Braces: * Everything braced - no if/for one-liners. Including switch cases * One line setters/getters can go in the header file * Opening brace on the initial line (eg. `if (condition) {`), and ending brace at the same tab stop as the initial line Private & Public: * Private variables/functions prefaced with `_` * One initial public section for constants * Private/public/protected for members and functions in that order, in header and cpp files * Functions in h and cpp files in same order Other formatting: * One blank line between functions * All code passed through clang-format. See above ## Documentation All PRs that include new methods and features or changed functionality should include documentation updates. This could be in the form of a tutorial page for the user manual, or API changes to the XML Class Reference. ### User Manual Tutorials and usage documentation lives in [doc/docs](https://github.com/TokisanGames/Terrain3D/tree/main/doc/docs) and is written in Markdown (*.md). Images are stored in `images` and videos are stored [_static/video](https://github.com/TokisanGames/Terrain3D/tree/main/doc/_static/video). Pages also need to be included in the table of contents `doc/index.rst`. Readthedocs will then be able to find everything it needs to build the html documentation upon a commit. ### Class Reference The class reference documentation that contributors edit is stored in [XML files](https://github.com/TokisanGames/Terrain3D/tree/main/doc/classes). These files are used as the source for generated documentation. Edit the class reference according to the [Godot class reference primer](https://docs.godotengine.org/en/stable/engine_details/class_reference/index.html). Godot's doc-tool is used to extract or update the class structure from the compiled addon. See below for instructions. ### Using the Documentation Generation Tools This step isn't required for contributors. You may ask for help generating the XML class structure so you can edit it, or generating the resulting RST files. #### To setup your system 1. Use a bash shell available in linux, [gitforwindows](https://gitforwindows.org), or [Microsoft's WSL](https://learn.microsoft.com/en-us/windows/wsl/install). 2. Install the following modules using python's pip: `pip install docutils myst-parser sphinx sphinx-rtd-theme sphinx-rtd-dark-mode`. 3. Edit `doc/build_docs.sh` and adjust the paths to your Godot executable and `make_rst.py`, found in the Godot repository. #### To edit the documentation 1. Build Terrain3D with your updated code. 2. Within the `doc` folder, run `./build_docs.sh`. The following will occur: - The Godot executable dumps the XML structure for all classes, including those of installed addons. - Any existing XML files (eg Terrain3D*) will be updated with the new structure, leaving prior written documentation. - Sphinx RST files are generated from the XML files. - All non-Terrain3D XML files are removed. - A local html copy of the docs are generated from the Markdown and RST files, and a browser is open to view them. 3. Fill in the XML files with documentation of the new generated structure and make any other changes to the Markdown files. 4. Run the script again to update the RST files. This isn't necessary for Markdown updates, except to view the changes locally. 5. Push your updates to the Markdown, XML, and RST files to the repository. Due to the nature of generation scripts, carefully review the changes so you only push those you intend. 6. Readthedocs will detect commits to the main tree and will build the online html docs from the Markdown and RST files. Doc generation via Sphinx is configured by conf.py and requirements.txt. Readthedocs also reads .readthedocs.yaml. The website is configured to automatically build based on specifically chosen branches and tags. `latest` is `main`. `stable` is a tag that we must manually update to point to the latest stable commit. ## Maintainers There are various responsibilities and processes maintainers need to do to update Terrain3D. 1. Ensure PR builds are successful, and occasionally make changes to the build scripts when Github makes changes. 2. Ensure PRs are up to code standards and include XML documentation. You may need to generate the XML for them first. 3. [Update docs](#using-the-documentation-generation-tools) to generate the XML and RST files. Readthedocs will update automatically once PRs are merged. Though if it fails, you may need to log in and figure out why. It can be a bit finicky. They have automatic tags. 4. [Update C# bindings](generating_csharp_bindings.md) as the API changes. 5. Update versions and tags as indicated below. ## Updating New Versions and Releases of Terrain3D Edit the following files on new releases and versions. ### New Terrain3D Release Version * Set src/terrain_3d.h : _version * Set project/addons/terrain_3d/plugin.cfg : version * Set doc/conf.py : version * Rebuild the docs with doc/build_docs.sh * Review minimum version in terrain.gdextension * Create a new tag for github * Create a new branch for new milestones (1.0) so readthedocs will create a new version. You may need to enable it on their website. * Reassign the `stable` tag for readthedocs to update that doc build. `latest` automatically builds off of `main`. ### New Terrain3DRegion Data Format Version * Update src/terrain_3d_data.h : CURRENT_DATA_VERSION * Update docs/data_format.md ### New Year: * Update Copyright header in all source files and conf.py ## Maintaining multiple versions Occasionally we might maintain two builds of the same version, such as `1.1-godot4.4` and `1.1` for Godot 4.5+. In this case the difference was the former used the godot-cpp 4.4 API, the latter used the 4.5 API. There was a minor but important difference in our code. I wanted all commits from one branch to be in the other branch, except for the few that changed the godot-cpp API. Here's how that process worked. 1. At the time, `main` was 1.1-dev and I had made a separate `1.1-godot4.4` branch. I made a commit changing godot-cpp to the 4.5 API. 2. Then after some commits, I cherry-picked all of the new ones from `main` into `1.1-godot4.4`. 3. On `main`, I created a tag called `_last-cherry-pick` so that when I periodically updated the 4.4 branch I knew where I left off. 4. Bulk cherry-picking is easy to do with the following: ``` git checkout 1.1-godot4.4 # Start in the destination branch git cherry-pick --no-merges _last-cherry-pick..main # Use any two hashes or tags git diff main # Ensure the only difference is the 4.5 API change in this example git push # Upload all bulk cherry-picked commits ``` ================================================ FILE: LICENSE.txt ================================================ MIT License Copyright (c) 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![Terrain3D Logo](/doc/docs/images/terrain3d.jpg) # Terrain3D A high performance, editable terrain system for Godot 4. ## Features * Written in C++ as a GDExtension addon, which works with official builds of Godot Engine * [Can be accessed](https://terrain3d.readthedocs.io/en/stable/docs/programming_languages.html) by GDScript, C#, and any language Godot supports * Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in non-contiguous and variable sized regions * Up to 32 textures * Up to 10 levels of detail for the terrain mesh * Foliage instancing, with up to 10 levels of detail, and a shadow impostor * Sculpting, holes, texture painting, texture detiling, painting colors and wetness * Imports heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), Gaea, World Creator, World Machine, Unity, Unreal and any tool that can export a heightmap. See [heightmaps](https://terrain3d.readthedocs.io/en/stable/docs/heightmaps.html) ## Games Using Terrain3D Please see the [featured games using Terrain3D](https://terrain3d.readthedocs.io/en/latest/docs/games.html) for examples of what it can do. ## Getting Started 1. Read the [Introduction](https://terrain3d.readthedocs.io/en/stable/docs/introduction.html) to understand how this terrain system works. 2. Read the [Installation & Upgrade](https://terrain3d.readthedocs.io/en/stable/docs/installation.html) instructions. 3. Watch the [tutorial videos](https://terrain3d.readthedocs.io/en/stable/docs/tutorial_videos.html) and read through the documentation. 4. For support, read [Getting Help](https://terrain3d.readthedocs.io/en/stable/docs/getting_help.html) and join our [Discord server](https://tokisan.com/discord). ## Credit Developed for the Godot community by: ||| |--|--| | **Cory Petkovsek, Tokisan Games** | [](https://twitter.com/TokisanGames) [](https://github.com/TokisanGames) [](https://tokisan.com/) [](https://tokisan.com/discord) [](https://www.youtube.com/@TokisanGames)| | **Roope Palmroos, Outobugi Games** | [](https://twitter.com/outobugi) [](https://github.com/outobugi) [](https://outobugi.com/) [](https://www.youtube.com/@outobugi)| And the contribution team in [AUTHORS.md](https://terrain3d.readthedocs.io/en/stable/docs/authors.html) and on the right of the github page. ## Contributing Please see [CONTRIBUTING.md](https://github.com/TokisanGames/Terrain3D/blob/main/CONTRIBUTING.md) if you would like to help make Terrain3D the best terrain system for Godot. ## License This addon has been released under the [MIT License](https://github.com/TokisanGames/Terrain3D/blob/main/LICENSE.txt). ================================================ FILE: SConstruct ================================================ #!/usr/bin/env python from glob import glob from pathlib import Path import os # TODO: Do not copy environment after godot-cpp/test is updated . env = SConscript("godot-cpp/SConstruct") # Add source files. env.Append(CPPPATH=["src/"]) sources = Glob("src/*.cpp") # Find gdextension path even if the directory or extension is renamed (e.g. project/addons/example/example.gdextension). (extension_path,) = glob("project/addons/terrain_3d/*.gdextension") # Find the addon path (e.g. project/addons/example). addon_path = Path(extension_path).parent # Find the project name from the gdextension file (e.g. example). project_name = Path(extension_path).stem scons_cache_path = os.environ.get("SCONS_CACHE") if scons_cache_path != None: CacheDir(scons_cache_path) print("Scons cache enabled... (path: '" + scons_cache_path + "')") # Embed documentation into the engine if env["target"] in ["editor", "template_debug"]: doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc/doc_classes/*.xml")) sources.append(doc_data) # Create the library target (e.g. libexample.linux.debug.x86_64.so). debug_or_release = "release" if env["target"] == "template_release" else "debug" if env["platform"] == "macos": library = env.SharedLibrary( "{0}/bin/lib{1}.{2}.{3}.framework/{1}.{2}.{3}".format( addon_path, project_name, env["platform"], debug_or_release, ), source=sources, ) else: library = env.SharedLibrary( "{}/bin/lib{}.{}.{}.{}{}".format( addon_path, project_name, env["platform"], debug_or_release, env["arch"], env["SHLIBSUFFIX"], ), source=sources, ) ## Option to use C++20 for this extension by replacing CXXFLAGS #if env.get("is_msvc", False): # env.Replace(CXXFLAGS=["/std:c++20"]) #else: # env.Replace(CXXFLAGS=["-std=c++20"]) ## Reenable CXXFLAGS removed by the above from godot-cpp/tools/godotcpp.py # Disable exception handling. Godot doesn't use exceptions anywhere, and this # saves around 20% of binary size and very significant build time. #if env["disable_exceptions"]: # if env.get("is_msvc", False): # env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)]) # else: # env.Append(CXXFLAGS=["-fno-exceptions"]) #elif env.get("is_msvc", False): # env.Append(CXXFLAGS=["/EHsc"]) Default(library) ================================================ FILE: Terrain3D.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.34024.191 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Terrain3D", "Terrain3D.vcxproj", "{B8850C81-3339-46A9-9668-CAA004E84629}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B8850C81-3339-46A9-9668-CAA004E84629}.Debug|x64.ActiveCfg = Debug|x64 {B8850C81-3339-46A9-9668-CAA004E84629}.Debug|x64.Build.0 = Debug|x64 {B8850C81-3339-46A9-9668-CAA004E84629}.Debug|x86.ActiveCfg = Debug|Win32 {B8850C81-3339-46A9-9668-CAA004E84629}.Debug|x86.Build.0 = Debug|Win32 {B8850C81-3339-46A9-9668-CAA004E84629}.Release|x64.ActiveCfg = Release|x64 {B8850C81-3339-46A9-9668-CAA004E84629}.Release|x64.Build.0 = Release|x64 {B8850C81-3339-46A9-9668-CAA004E84629}.Release|x86.ActiveCfg = Release|Win32 {B8850C81-3339-46A9-9668-CAA004E84629}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {119F9825-B9FE-4F51-9225-7EA96BF5BF7F} EndGlobalSection EndGlobal ================================================ FILE: Terrain3D.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 17.0 Win32Proj {b8850c81-3339-46a9-9668-caa004e84629} Terrain3D 10.0 Application true v143 Unicode Application false v143 true Unicode Makefile true v143 Unicode Application false v143 true Unicode scons dev_build=yes scons dev_build=yes scons --clean $(SolutionDir)\src;$(SolutionDir)\godot-cpp\gdextension;$(SolutionDir)\godot-cpp\gen\include;$(SolutionDir)\godot-cpp\include /std:c++17 .vs .vs GDEXTENSION Level3 true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true true true Level3 true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true .vs/Terrain3D-build.log Level3 true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true true true Document ================================================ FILE: Terrain3D.vcxproj.filters ================================================  {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms {cddadac5-d2a5-45b0-947d-d3e2b055c87c} {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd {fa4f3a1c-e2a4-4421-9b19-e15c14fce184} {0d771ba6-8e4a-4985-895c-d9b8eec8b3fd} 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 5. Headers 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 6. C++ 1. Project Files 1. Project Files 1. Project Files 1. Project Files 1. Project Files 1. Project Files 1. Project Files 1. Project Files 1. Project Files 4. Shaders 4. Shaders 4. Shaders 1. Project Files 1. Project Files 2. Docs 2. Docs 2. Docs 2. Docs 4. Shaders 4. Shaders 4. Shaders 4. Shaders 2. Docs 1. Project Files 2. Docs 2. Docs 1. Project Files 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 1. Project Files 1. Project Files 1. Project Files 2. Docs 2. Docs 2. Docs 1. Project Files 4. Shaders 2. Docs 2. Docs 4. Shaders 2. Docs 4. Shaders 2. Docs 2. Docs 4. Shaders 4. Shaders 4. Shaders 2. Docs 4. Shaders 4. Shaders 1. Project Files 4. Shaders 1. Project Files 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 2. Docs 1. Project Files 2. Docs 2. Docs 4. Shaders 1. Project Files 3. XML 3. XML 3. XML 3. XML 3. XML 3. XML 3. XML 3. XML 3. XML 3. XML 3. XML ================================================ FILE: Terrain3D.vcxproj.user ================================================  false c:\gd\bin\Godot_v4.5.1-stable_win64.exe -e project.godot project WindowsLocalDebugger ================================================ FILE: doc/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) install: python -m venv .venv && \ . .venv/bin/activate && \ pip install -r requirements.txt && \ echo "Setup complete. Remember to run 'source .venv/bin/activate' to activate the virtual environment." serve: source ./.venv/bin/activate && \ make html && \ python -m http.server --directory _build/html ================================================ FILE: doc/_static/theme_overrides.css ================================================ /* override table width restrictions */ .wy-table-responsive table td, .wy-table-responsive table th { white-space: normal; } .wy-table-responsive { margin-bottom: 24px; max-width: 100%; overflow: visible; } ================================================ FILE: doc/api/class_terrain3d.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3D.xml. .. _class_Terrain3D: Terrain3D ========= **Inherits:** ``Node3D`` .. rst-class:: classref-introduction-group Description ----------- Terrain3D is a high performance, editable terrain system for Godot 4. It provides a clipmap based terrain that supports terrains from 64x64m up to 65.5x65.5km with multiple LODs, 32 textures, and editor tools for importing or creating terrains. This class handles mesh generation, and management of the whole system. See `System Architecture `__ for design details. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`Terrain3DAssets` | :ref:`assets` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``Shader`` | :ref:`buffer_shader_override` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`buffer_shader_override_enabled` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | RenderingServer.ShadowCastingSetting | :ref:`cast_shadows` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``Node3D`` | :ref:`clipmap_target` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`Terrain3DCollision` | :ref:`collision` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`collision_layer` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`collision_mask` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`CollisionMode` | :ref:`collision_mode` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`collision_priority` | ``1.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`collision_radius` | ``64`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`collision_shape_size` | ``16`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``Node3D`` | :ref:`collision_target` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`cull_margin` | ``0.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`Terrain3DData` | :ref:`data` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``String`` | :ref:`data_directory` | ``""`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`DebugLevel` | :ref:`debug_level` | ``0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`displacement_scale` | ``1.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`displacement_sharpness` | ``0.25`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`free_editor_textures` | ``true`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | GeometryInstance3D.GIMode | :ref:`gi_mode` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`Terrain3DInstancer` | :ref:`instancer` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`InstancerMode` | :ref:`instancer_mode` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`label_distance` | ``0.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`label_size` | ``48`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`Terrain3DMaterial` | :ref:`material` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`mesh_lods` | ``7`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`mesh_size` | ``48`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`mouse_layer` | ``32`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | RenderingServer.ShadowCastingSetting | :ref:`ocean_cast_shadows` | ``0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`ocean_cull_margin` | ``20.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`ocean_enabled` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | GeometryInstance3D.GIMode | :ref:`ocean_gi_mode` | ``0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``Node3D`` | :ref:`ocean_light_target` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``Material`` | :ref:`ocean_material` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`ocean_mesh_lods` | ``7`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`ocean_mesh_size` | ``32`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`ocean_render_layers` | ``1`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`ocean_tessellation_level` | ``0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`ocean_vertex_spacing` | ``4.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``PhysicsMaterial`` | :ref:`physics_material` | | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | :ref:`RegionSize` | :ref:`region_size` | ``256`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`render_layers` | ``2147483649`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`save_16_bit` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_autoshader` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_checkered` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_colormap` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_contours` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_control_angle` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_control_blend` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_control_scale` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_control_texture` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_displacement_buffer` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_grey` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_grid` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_heightmap` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_instancer_grid` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_jaggedness` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_navigation` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_region_grid` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_roughmap` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_texture_albedo` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_texture_ao` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_texture_height` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_texture_normal` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_texture_rough` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``bool`` | :ref:`show_vertex_grid` | ``false`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``int`` | :ref:`tessellation_level` | ``0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``String`` | :ref:`version` | ``"1.1.0-dev"`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ | ``float`` | :ref:`vertex_spacing` | ``1.0`` | +-------------------------------------------------------------+------------------------------------------------------------------------------------------------+-----------------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Mesh`` | :ref:`bake_mesh`\ (\ lod\: ``int``, filter\: :ref:`HeightFilter` = 0\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedVector3Array`` | :ref:`generate_nav_mesh_source_geometry`\ (\ global_aabb\: ``AABB``, require_nav\: ``bool`` = true\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Camera3D`` | :ref:`get_camera`\ (\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector3`` | :ref:`get_clipmap_target_position`\ (\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector3`` | :ref:`get_collision_target_position`\ (\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DEditor` | :ref:`get_editor`\ (\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector3`` | :ref:`get_intersection`\ (\ src_pos\: ``Vector3``, direction\: ``Vector3``, gpu_mode\: ``bool`` = false\ ) | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Object`` | :ref:`get_plugin`\ (\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Dictionary`` | :ref:`get_raycast_result`\ (\ src_pos\: ``Vector3``, direction\: ``Vector3``, collision_mask\: ``int`` = 4294967295, exclude_terrain\: ``bool`` = false\ ) |const| | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_camera`\ (\ camera\: ``Camera3D``\ ) | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_editor`\ (\ editor\: :ref:`Terrain3DEditor`\ ) | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_plugin`\ (\ plugin\: ``Object``\ ) | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`snap`\ (\ ) | +-----------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Signals ------- .. _class_Terrain3D_signal_assets_changed: .. rst-class:: classref-signal **assets_changed**\ (\ ) :ref:`🔗` Emitted when :ref:`assets` is changed. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_signal_material_changed: .. rst-class:: classref-signal **material_changed**\ (\ ) :ref:`🔗` Emitted when :ref:`material` is changed. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3D_DebugLevel: .. rst-class:: classref-enumeration enum **DebugLevel**: :ref:`🔗` .. _class_Terrain3D_constant_ERROR: .. rst-class:: classref-enumeration-constant :ref:`DebugLevel` **ERROR** = ``0`` Errors and warnings always print. .. _class_Terrain3D_constant_INFO: .. rst-class:: classref-enumeration-constant :ref:`DebugLevel` **INFO** = ``1`` Typically every function call and other important informational messages. .. _class_Terrain3D_constant_DEBUG: .. rst-class:: classref-enumeration-constant :ref:`DebugLevel` **DEBUG** = ``2`` Detailed steps within functions. .. _class_Terrain3D_constant_EXTREME: .. rst-class:: classref-enumeration-constant :ref:`DebugLevel` **EXTREME** = ``3`` Messages for continuous operations like snapping and editing. .. rst-class:: classref-item-separator ---- .. _enum_Terrain3D_RegionSize: .. rst-class:: classref-enumeration enum **RegionSize**: :ref:`🔗` .. _class_Terrain3D_constant_SIZE_64: .. rst-class:: classref-enumeration-constant :ref:`RegionSize` **SIZE_64** = ``64`` The region size is 64 x 64 meters, vertices, and pixels on Image maps. .. _class_Terrain3D_constant_SIZE_128: .. rst-class:: classref-enumeration-constant :ref:`RegionSize` **SIZE_128** = ``128`` The region size is 128 x 128 meters, vertices, and pixels on Image maps. .. _class_Terrain3D_constant_SIZE_256: .. rst-class:: classref-enumeration-constant :ref:`RegionSize` **SIZE_256** = ``256`` The region size is 256 x 256 meters, vertices, and pixels on Image maps. (default) .. _class_Terrain3D_constant_SIZE_512: .. rst-class:: classref-enumeration-constant :ref:`RegionSize` **SIZE_512** = ``512`` The region size is 512 x 512 meters, vertices, and pixels on Image maps. .. _class_Terrain3D_constant_SIZE_1024: .. rst-class:: classref-enumeration-constant :ref:`RegionSize` **SIZE_1024** = ``1024`` The region size is 1024 x 1024 meters, vertices, and pixels on Image maps. .. _class_Terrain3D_constant_SIZE_2048: .. rst-class:: classref-enumeration-constant :ref:`RegionSize` **SIZE_2048** = ``2048`` The region size is 2048 x 2048 meters, vertices, and pixels on Image maps. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3D_property_assets: .. rst-class:: classref-property :ref:`Terrain3DAssets` **assets** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_assets**\ (\ value\: :ref:`Terrain3DAssets`\ ) - :ref:`Terrain3DAssets` **get_assets**\ (\ ) The list of texture and mesh assets used by Terrain3D. You can optionally save this as an external ``.tres`` text file if you wish to share it with Terrain3D nodes in other scenes. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_buffer_shader_override: .. rst-class:: classref-property ``Shader`` **buffer_shader_override** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_buffer_shader_override**\ (\ value\: ``Shader``\ ) - ``Shader`` **get_buffer_shader_override**\ (\ ) .. container:: contribute There is currently no description for this property. Please help us by `contributing one `__! .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_buffer_shader_override_enabled: .. rst-class:: classref-property ``bool`` **buffer_shader_override_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_buffer_shader_override_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **is_buffer_shader_override_enabled**\ (\ ) .. container:: contribute There is currently no description for this property. Please help us by `contributing one `__! .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_cast_shadows: .. rst-class:: classref-property RenderingServer.ShadowCastingSetting **cast_shadows** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_cast_shadows**\ (\ value\: RenderingServer.ShadowCastingSetting\ ) - RenderingServer.ShadowCastingSetting **get_cast_shadows**\ (\ ) Tells the renderer how to cast shadows from the terrain onto other objects. This sets ``GeometryInstance3D.ShadowCastingSetting`` in the engine. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_clipmap_target: .. rst-class:: classref-property ``Node3D`` **clipmap_target** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_clipmap_target**\ (\ value\: ``Node3D``\ ) - ``Node3D`` **get_clipmap_target**\ (\ ) The terrain clipmap mesh and lods will center itself at the position of this node. If null, or if in the editor, it will fall back to the camera position. See :ref:`set_camera()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision: .. rst-class:: classref-property :ref:`Terrain3DCollision` **collision** :ref:`🔗` .. rst-class:: classref-property-setget - :ref:`Terrain3DCollision` **get_collision**\ (\ ) The active :ref:`Terrain3DCollision` object. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_layer: .. rst-class:: classref-property ``int`` **collision_layer** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_layer**\ (\ value\: ``int``\ ) - ``int`` **get_collision_layer**\ (\ ) The physics layers the terrain lives on. Sets ``CollisionObject3D.collision_layer``. Alias for :ref:`Terrain3DCollision.layer`. Also see :ref:`collision_mask`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_mask: .. rst-class:: classref-property ``int`` **collision_mask** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_mask**\ (\ value\: ``int``\ ) - ``int`` **get_collision_mask**\ (\ ) The physics layers the physics body scans for colliding objects. Sets ``CollisionObject3D.collision_mask``. Alias for :ref:`Terrain3DCollision.mask`. Also see :ref:`collision_layer`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_mode: .. rst-class:: classref-property :ref:`CollisionMode` **collision_mode** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_mode**\ (\ value\: :ref:`CollisionMode`\ ) - :ref:`CollisionMode` **get_collision_mode**\ (\ ) The selected mode determines if collision is generated and how. See :ref:`CollisionMode` for details. Alias for :ref:`Terrain3DCollision.mode`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_priority: .. rst-class:: classref-property ``float`` **collision_priority** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_priority**\ (\ value\: ``float``\ ) - ``float`` **get_collision_priority**\ (\ ) The priority with which the physics server uses to solve collisions. The higher the priority, the lower the penetration of a colliding object. Sets ``CollisionObject3D.collision_priority``. Alias for :ref:`Terrain3DCollision.priority`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_radius: .. rst-class:: classref-property ``int`` **collision_radius** = ``64`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_radius**\ (\ value\: ``int``\ ) - ``int`` **get_collision_radius**\ (\ ) If :ref:`collision_mode` is Dynamic, this is the distance range within which collision shapes will be generated. Alias for :ref:`Terrain3DCollision.radius`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_shape_size: .. rst-class:: classref-property ``int`` **collision_shape_size** = ``16`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_shape_size**\ (\ value\: ``int``\ ) - ``int`` **get_collision_shape_size**\ (\ ) If :ref:`collision_mode` is Dynamic, this is the size of each collision shape. Alias for :ref:`Terrain3DCollision.shape_size`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_collision_target: .. rst-class:: classref-property ``Node3D`` **collision_target** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_collision_target**\ (\ value\: ``Node3D``\ ) - ``Node3D`` **get_collision_target**\ (\ ) In dynamic mode, the terrain collision will center itself at the position of this node. If null, it will fall back to the :ref:`clipmap_target` position and failing that will use the camera position. The camera is always used in the editor. See :ref:`set_camera()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_cull_margin: .. rst-class:: classref-property ``float`` **cull_margin** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_cull_margin**\ (\ value\: ``float``\ ) - ``float`` **get_cull_margin**\ (\ ) This margin is added to the vertical component of the terrain mesh bounding boxes (AABB). The terrain already sets its AABB from :ref:`Terrain3DData.get_height_range()`, which is calculated while sculpting. This setting only needs to be used if the shader has expanded the terrain beyond the AABB and the terrain meshes are being culled at certain viewing angles. This might happen from using :ref:`Terrain3DMaterial.world_background` with NOISE and a height value larger than the terrain heights. This setting is similar to ``GeometryInstance3D.extra_cull_margin``, but it only affects the Y axis. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_data: .. rst-class:: classref-property :ref:`Terrain3DData` **data** :ref:`🔗` .. rst-class:: classref-property-setget - :ref:`Terrain3DData` **get_data**\ (\ ) This class manages loading, saving, adding, and removing of Terrain3DRegions and access to their content. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_data_directory: .. rst-class:: classref-property ``String`` **data_directory** = ``""`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_data_directory**\ (\ value\: ``String``\ ) - ``String`` **get_data_directory**\ (\ ) The directory where terrain data will be saved to and loaded from. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_debug_level: .. rst-class:: classref-property :ref:`DebugLevel` **debug_level** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_debug_level**\ (\ value\: :ref:`DebugLevel`\ ) - :ref:`DebugLevel` **get_debug_level**\ (\ ) The verbosity of debug messages printed to the console. Errors and warnings are always printed. This can also be set via command line using ``--terrain3d-debug=LEVEL`` where ``LEVEL`` is one of ``ERROR, INFO, DEBUG, EXTREME``. The last includes continuously recurring messages like position updates for the mesh as the camera moves around. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_displacement_scale: .. rst-class:: classref-property ``float`` **displacement_scale** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_displacement_scale**\ (\ value\: ``float``\ ) - ``float`` **get_displacement_scale**\ (\ ) A global multiplier for all displaced textures. This is the maximum distance that 2 adjacent verticies can be vertically seperated by. Setting this 1.0 would mean a maximum of + 0.5m, and -0.5m deviation from the collision mesh. Alias for :ref:`Terrain3DMaterial.displacement_scale`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_displacement_sharpness: .. rst-class:: classref-property ``float`` **displacement_sharpness** = ``0.25`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_displacement_sharpness**\ (\ value\: ``float``\ ) - ``float`` **get_displacement_sharpness**\ (\ ) Adjusts the transition between textures. When set at `1.0`, the blending of displacment between textures will match the aldebo/normal blend sharpness exactly. Lower values will have a softer transition, avoiding harsh shapes, without compromising the abldeo and normal blend sharpness. If set at or very near to `0.0`, it is possible that more displaced textures can affect less displaced textures at low blend values even if not visible. Alias for :ref:`Terrain3DMaterial.displacement_sharpness`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_free_editor_textures: .. rst-class:: classref-property ``bool`` **free_editor_textures** = ``true`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_free_editor_textures**\ (\ value\: ``bool``\ ) - ``bool`` **get_free_editor_textures**\ (\ ) Frees ground textures used for editing in _ready(). These textures are used to generate the TextureArrays, so if you don't change any :ref:`Terrain3DTextureAsset` settings in game, this can be enabled. Also reloads the texture asset list in _enter_tree() in case you load scenes via code and need the textures again. Calls :ref:`Terrain3DAssets.clear_textures()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_gi_mode: .. rst-class:: classref-property GeometryInstance3D.GIMode **gi_mode** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_gi_mode**\ (\ value\: GeometryInstance3D.GIMode\ ) - GeometryInstance3D.GIMode **get_gi_mode**\ (\ ) Tells the renderer which global illumination mode to use for the terrain mesh. This sets ``GeometryInstance3D.gi_mode`` in the engine. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_instancer: .. rst-class:: classref-property :ref:`Terrain3DInstancer` **instancer** :ref:`🔗` .. rst-class:: classref-property-setget - :ref:`Terrain3DInstancer` **get_instancer**\ (\ ) The active :ref:`Terrain3DInstancer` object. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_instancer_mode: .. rst-class:: classref-property :ref:`InstancerMode` **instancer_mode** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_instancer_mode**\ (\ value\: :ref:`InstancerMode`\ ) - :ref:`InstancerMode` **get_instancer_mode**\ (\ ) Normal - Generates MultiMeshInstance3Ds and renders all instances as normal. Disabled - prevents the instancer from creating any MultiMeshInstance3Ds. Alias for :ref:`Terrain3DInstancer.mode`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_label_distance: .. rst-class:: classref-property ``float`` **label_distance** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_label_distance**\ (\ value\: ``float``\ ) - ``float`` **get_label_distance**\ (\ ) If label_distance is non-zero (try 1024-4096) it will generate and display region coordinates in the viewport so you can identify the exact region files you are editing. This setting is the visible distance of the labels. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_label_size: .. rst-class:: classref-property ``int`` **label_size** = ``48`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_label_size**\ (\ value\: ``int``\ ) - ``int`` **get_label_size**\ (\ ) Sets the font size for region labels. See :ref:`label_distance`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_material: .. rst-class:: classref-property :ref:`Terrain3DMaterial` **material** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_material**\ (\ value\: :ref:`Terrain3DMaterial`\ ) - :ref:`Terrain3DMaterial` **get_material**\ (\ ) A custom material for Terrain3D. You can optionally save this as an external ``.tres`` text file if you wish to share it with instances of Terrain3D in other scenes. See :ref:`Terrain3DMaterial`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_mesh_lods: .. rst-class:: classref-property ``int`` **mesh_lods** = ``7`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mesh_lods**\ (\ value\: ``int``\ ) - ``int`` **get_mesh_lods**\ (\ ) The number of lods generated for the terrain meshes. Enable wireframe mode in the viewport to see them. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_mesh_size: .. rst-class:: classref-property ``int`` **mesh_size** = ``48`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mesh_size**\ (\ value\: ``int``\ ) - ``int`` **get_mesh_size**\ (\ ) The correlated size of the terrain meshes. Lod0 has ``4*mesh_size + 2`` quads per side. E.g. when mesh_size=8, lod0 has 34 quads to a side, including 2 quads for seams. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_mouse_layer: .. rst-class:: classref-property ``int`` **mouse_layer** = ``32`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mouse_layer**\ (\ value\: ``int``\ ) - ``int`` **get_mouse_layer**\ (\ ) Godot supports 32 render layers. For most objects, only layers 1-20 are available for selection in the inspector. 21-32 are settable via code, and are considered reserved for editor plugins. This variable sets the editor render layer (21-32) to be used by ``get_intersection``, which the mouse cursor uses. You may place other objects on this layer, however ``get_intersection`` will report intersections with them. So either dedicate this layer to Terrain3D, or if you must use all 32 layers, dedicate this one during editing or when using ``get_intersection``, and then you can use it during game play. See :ref:`get_intersection()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_cast_shadows: .. rst-class:: classref-property RenderingServer.ShadowCastingSetting **ocean_cast_shadows** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_cast_shadows**\ (\ value\: RenderingServer.ShadowCastingSetting\ ) - RenderingServer.ShadowCastingSetting **get_ocean_cast_shadows**\ (\ ) Tells the renderer how to cast shadows from the ocean onto other objects. This sets ``GeometryInstance3D.ShadowCastingSetting`` in the engine. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_cull_margin: .. rst-class:: classref-property ``float`` **ocean_cull_margin** = ``20.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_cull_margin**\ (\ value\: ``float``\ ) - ``float`` **get_ocean_cull_margin**\ (\ ) This margin is added to the vertical component of the ocean mesh bounding boxes (AABB). When you set the height of your waves in the shader, this margin should be adjusted. If it's too small, the meshes will clip at certain camera angles. If you set it too large, the renderer may have slightly more work to do. This setting is similar to ``GeometryInstance3D.extra_cull_margin``, but it only affects the Y axis. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_enabled: .. rst-class:: classref-property ``bool`` **ocean_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **is_ocean_enabled**\ (\ ) Generates another clipmap mesh, which you can apply an ocean shader to and configure independently of the terrain mesh. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_gi_mode: .. rst-class:: classref-property GeometryInstance3D.GIMode **ocean_gi_mode** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_gi_mode**\ (\ value\: GeometryInstance3D.GIMode\ ) - GeometryInstance3D.GIMode **get_ocean_gi_mode**\ (\ ) Tells the renderer which global illumination mode to use for the ocean mesh. This sets ``GeometryInstance3D.gi_mode`` in the engine. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_light_target: .. rst-class:: classref-property ``Node3D`` **ocean_light_target** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_light_target**\ (\ value\: ``Node3D``\ ) - ``Node3D`` **get_ocean_light_target**\ (\ ) This sets the _light_direction and _light_color uniforms in the ocean shader, if they are present. You can use this for light scattering, detecting if the light is above the horizon, albedo coloring, etc. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_material: .. rst-class:: classref-property ``Material`` **ocean_material** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_material**\ (\ value\: ``Material``\ ) - ``Material`` **get_ocean_material**\ (\ ) You can assign a ``StandardMaterial`` here for testing, but you really need a ``ShaderMaterial``. Start with the example in ``addons/terrain_3d/extras/shaders/M_ocean.tres``, which you can build on. Or use any of the ocean shaders available around the internet, provided you set `skip_vertex_transform` and copy the geomorphing code from our `vertex()` shader, which will properly handle the LOD transitions on the clipmap. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_mesh_lods: .. rst-class:: classref-property ``int`` **ocean_mesh_lods** = ``7`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_mesh_lods**\ (\ value\: ``int``\ ) - ``int`` **get_ocean_mesh_lods**\ (\ ) The number of lods generated for the ocean meshes. Enable wireframe mode in the viewport to see them. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_mesh_size: .. rst-class:: classref-property ``int`` **ocean_mesh_size** = ``32`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_mesh_size**\ (\ value\: ``int``\ ) - ``int`` **get_ocean_mesh_size**\ (\ ) The correlated size of the ocean meshes. Lod0 has ``4*ocean_mesh_size + 2`` quads per side. E.g. when ocean_mesh_size=8, lod0 has 34 quads to a side, including 2 quads for seams. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_render_layers: .. rst-class:: classref-property ``int`` **ocean_render_layers** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_render_layers**\ (\ value\: ``int``\ ) - ``int`` **get_ocean_render_layers**\ (\ ) The render layers the ocean is drawn on. This sets ``VisualInstance3D.layers`` in the engine. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_tessellation_level: .. rst-class:: classref-property ``int`` **ocean_tessellation_level** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_tessellation_level**\ (\ value\: ``int``\ ) - ``int`` **get_ocean_tessellation_level**\ (\ ) This setting creates up to 6 additional subdivisions of the ocean mesh below LOD0, which provides more vertices for the vertex shader if desired. You can see it in wireframe mode. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_ocean_vertex_spacing: .. rst-class:: classref-property ``float`` **ocean_vertex_spacing** = ``4.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ocean_vertex_spacing**\ (\ value\: ``float``\ ) - ``float`` **get_ocean_vertex_spacing**\ (\ ) The distance between vertices, settable up to 100. Godot units are typically considered to be meters. This laterally scales the ocean vertices on X and Z axes, but does not scale wave height. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_physics_material: .. rst-class:: classref-property ``PhysicsMaterial`` **physics_material** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_physics_material**\ (\ value\: ``PhysicsMaterial``\ ) - ``PhysicsMaterial`` **get_physics_material**\ (\ ) Applies a ``PhysicsMaterial`` override to the entire terrain StaticBody. Alias for :ref:`Terrain3DCollision.physics_material` See that entry for details. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_region_size: .. rst-class:: classref-property :ref:`RegionSize` **region_size** = ``256`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **change_region_size**\ (\ value\: :ref:`RegionSize`\ ) - :ref:`RegionSize` **get_region_size**\ (\ ) The number of vertices in each region, and the number of pixels for each map in :ref:`Terrain3DRegion`. 1 pixel always corresponds to 1 vertex. :ref:`vertex_spacing` laterally scales regions, but does not change the number of vertices or pixels in each. There is no undo for this operation. However you can apply it again to reslice, as long as your data doesn't hit the maximum boundaries. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_render_layers: .. rst-class:: classref-property ``int`` **render_layers** = ``2147483649`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_render_layers**\ (\ value\: ``int``\ ) - ``int`` **get_render_layers**\ (\ ) The render layers the terrain is drawn on. This sets ``VisualInstance3D.layers`` in the engine. The defaults is layer 1 and 32 (for the mouse cursor). When you set this via code, make sure the layer for :ref:`mouse_layer` is included, or set that variable again after this so that the mouse cursor and :ref:`get_intersection()` work. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_save_16_bit: .. rst-class:: classref-property ``bool`` **save_16_bit** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_save_16_bit**\ (\ value\: ``bool``\ ) - ``bool`` **get_save_16_bit**\ (\ ) If enabled, heightmaps are saved as 16-bit half-precision to reduce file size. Files are always loaded in 32-bit for editing. Upon save, a copy of the heightmap is converted to 16-bit for writing. It does not change what is currently in memory. This process is lossy. 16-bit precision gets increasingly worse with every power of 2. At a height of 256m, the precision interval is .25m. At 512m it is .5m. At 1024m it is 1m. Saving a height of 1024.4m will be rounded down to 1024m. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_autoshader: .. rst-class:: classref-property ``bool`` **show_autoshader** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_autoshader**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_autoshader**\ (\ ) Displays the area designated for use by the autoshader, which shows materials based upon slope. Alias for :ref:`Terrain3DMaterial.show_autoshader`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_checkered: .. rst-class:: classref-property ``bool`` **show_checkered** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_checkered**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_checkered**\ (\ ) Shows a checkerboard display using a shader rendered pattern. This is turned on if the Texture List is empty. Note that when a blank texture slot is created, a 1k checkerboard texture is generated and stored in the texture slot. That takes VRAM. The two patterns have a slightly different scale. Alias for :ref:`Terrain3DMaterial.show_checkered`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_colormap: .. rst-class:: classref-property ``bool`` **show_colormap** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_colormap**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_colormap**\ (\ ) Shows the color map in the albedo channel. Alias for :ref:`Terrain3DMaterial.show_colormap`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_contours: .. rst-class:: classref-property ``bool`` **show_contours** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_contours**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_contours**\ (\ ) Overlays contour lines on the terrain. Customize the options in the material when enabled. Press `4` with the mouse in the viewport to toggle. Alias for :ref:`Terrain3DMaterial.show_contours`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_control_angle: .. rst-class:: classref-property ``bool`` **show_control_angle** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_angle**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_angle**\ (\ ) Albedo shows the painted angle. Orange means 0°, Yellow 270°, Cyan 180°, Violet 90°. Or warm colors towards -Z, cool colors +Z, greens/yellows +X, reds/blues -X. Draw all angles coming from the center of a circle for a better understanding. Alias for :ref:`Terrain3DMaterial.show_control_angle`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_control_blend: .. rst-class:: classref-property ``bool`` **show_control_blend** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_blend**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_blend**\ (\ ) Displays the values used to blend the textures. Blue shows the autoshader blending, red shows manually painted blending. Alias for :ref:`Terrain3DMaterial.show_control_blend`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_control_scale: .. rst-class:: classref-property ``bool`` **show_control_scale** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_scale**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_scale**\ (\ ) Albedo shows the painted scale. Larger scales are more red, smaller scales are more blue. 0.5 middle grey is the default 100% scale. Alias for :ref:`Terrain3DMaterial.show_control_scale`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_control_texture: .. rst-class:: classref-property ``bool`` **show_control_texture** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_texture**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_texture**\ (\ ) Albedo shows the base and overlay texture indices defined by the control map. Red pixels indicate the base texture, with brightness showing texture ids 0 to 31. Green pixels indicate the overlay texture. Yellow indicates both. Alias for :ref:`Terrain3DMaterial.show_control_texture`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_displacement_buffer: .. rst-class:: classref-property ``bool`` **show_displacement_buffer** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_displacement_buffer**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_displacement_buffer**\ (\ ) Alias for :ref:`Terrain3DMaterial.show_displacement_buffer`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_grey: .. rst-class:: classref-property ``bool`` **show_grey** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_grey**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_grey**\ (\ ) Albedo is set to 0.2 grey. Alias for :ref:`Terrain3DMaterial.show_grey`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_grid: .. rst-class:: classref-property ``bool`` **show_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_region_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_region_grid**\ (\ ) Alias for :ref:`Terrain3DMaterial.show_region_grid`. Press `1` with the mouse in the viewport to toggle. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_heightmap: .. rst-class:: classref-property ``bool`` **show_heightmap** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_heightmap**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_heightmap**\ (\ ) Albedo is a white to black gradient depending on height. The gradient is scaled to a height of 300, so above that or far below 0 will be all white or black. Alias for :ref:`Terrain3DMaterial.show_heightmap`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_instancer_grid: .. rst-class:: classref-property ``bool`` **show_instancer_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_instancer_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_instancer_grid**\ (\ ) Overlays the 32x32m instancer grid on the terrain, which shows how the instancer data is partitioned. Press `2` with the mouse in the viewport to toggle. Alias for :ref:`Terrain3DMaterial.show_instancer_grid`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_jaggedness: .. rst-class:: classref-property ``bool`` **show_jaggedness** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_jaggedness**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_jaggedness**\ (\ ) Highlights non-smooth areas of the terrain. Jagged peaks, troughs, or edges that are a bit rough with sharp angles between vertices. Alias for :ref:`Terrain3DMaterial.show_jaggedness`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_navigation: .. rst-class:: classref-property ``bool`` **show_navigation** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_navigation**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_navigation**\ (\ ) Displays the area designated for generating the navigation mesh. Alias for :ref:`Terrain3DMaterial.show_navigation`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_region_grid: .. rst-class:: classref-property ``bool`` **show_region_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_region_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_region_grid**\ (\ ) Overlays the region grid on the terrain. This is more accurate than the region grid gizmo for determining where the region border is when editing. Press `1` with the mouse in the viewport to toggle. Alias for :ref:`Terrain3DMaterial.show_region_grid`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_roughmap: .. rst-class:: classref-property ``bool`` **show_roughmap** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_roughmap**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_roughmap**\ (\ ) Albedo is set to the roughness modification map as grey scale. Middle grey, 0.5 means no roughness modification. Black would be high gloss while white is very rough. Alias for :ref:`Terrain3DMaterial.show_roughmap`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_texture_albedo: .. rst-class:: classref-property ``bool`` **show_texture_albedo** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_albedo**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_albedo**\ (\ ) Albedo textures are shown only. Other channels are excluded. Alias for :ref:`Terrain3DMaterial.show_texture_albedo`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_texture_ao: .. rst-class:: classref-property ``bool`` **show_texture_ao** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_ao**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_ao**\ (\ ) Albedo is set to the painted Ambient Occlusion textures. Alias for :ref:`Terrain3DMaterial.show_texture_ao`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_texture_height: .. rst-class:: classref-property ``bool`` **show_texture_height** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_height**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_height**\ (\ ) Albedo is set to the painted Height textures. Alias for :ref:`Terrain3DMaterial.show_texture_height`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_texture_normal: .. rst-class:: classref-property ``bool`` **show_texture_normal** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_normal**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_normal**\ (\ ) Albedo is set to the painted Normal textures. Alias for :ref:`Terrain3DMaterial.show_texture_normal`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_texture_rough: .. rst-class:: classref-property ``bool`` **show_texture_rough** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_rough**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_rough**\ (\ ) Albedo is set to the painted Roughness textures. This is different from the roughness modification map above. Alias for :ref:`Terrain3DMaterial.show_texture_rough`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_show_vertex_grid: .. rst-class:: classref-property ``bool`` **show_vertex_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_vertex_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_vertex_grid**\ (\ ) Overlays the vertex grid on the terrain, showing where each vertex is. Press `3` with the mouse in the viewport to toggle. Alias for :ref:`Terrain3DMaterial.show_vertex_grid`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_tessellation_level: .. rst-class:: classref-property ``int`` **tessellation_level** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_tessellation_level**\ (\ value\: ``int``\ ) - ``int`` **get_tessellation_level**\ (\ ) Enables displacement using texture heights for additional mesh detail when set above 0. This creates up to 6 additional subdivisions of the terrain mesh below LOD0, and adds a displacement buffer configurable in the material. You can see this in wireframe mode. Set to 0 to disable displacement. See :ref:`Terrain3DMaterial.show_displacement_buffer` and look at the Displacement Buffer debug view. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_version: .. rst-class:: classref-property ``String`` **version** = ``"1.1.0-dev"`` :ref:`🔗` .. rst-class:: classref-property-setget - ``String`` **get_version**\ (\ ) The current version of Terrain3D. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_property_vertex_spacing: .. rst-class:: classref-property ``float`` **vertex_spacing** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_vertex_spacing**\ (\ value\: ``float``\ ) - ``float`` **get_vertex_spacing**\ (\ ) The distance between vertices, settable up to 100. Godot units are typically considered to be meters. This laterally scales the terrain on X and Z axes. This variable changes the global position of landscape features. A mountain peak might be at (512, 512), but with a vertex spacing of 2.0 it is now located at (1024, 1024). It retains the same height. All Terrain3D functions with a global_position expect an absolute global value. If you would normally use :ref:`Terrain3DData.import_images()` to import an image in the region at (-1024, -1024), with a vertex_spacing of 2, you'll need to import that image at (-2048, -2048) to place it in the same region. To scale heights, export the height map and reimport it with a new height scale. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3D_method_bake_mesh: .. rst-class:: classref-method ``Mesh`` **bake_mesh**\ (\ lod\: ``int``, filter\: :ref:`HeightFilter` = 0\ ) |const| :ref:`🔗` Generates a static ArrayMesh for the terrain. \ ``lod`` - Determines the granularity of the generated mesh. The range is 0-8. 4 is recommended. \ ``filter`` - Controls how vertex Y coordinates are generated from the height map. See :ref:`HeightFilter`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_generate_nav_mesh_source_geometry: .. rst-class:: classref-method ``PackedVector3Array`` **generate_nav_mesh_source_geometry**\ (\ global_aabb\: ``AABB``, require_nav\: ``bool`` = true\ ) |const| :ref:`🔗` Generates source geometry faces for input to nav mesh baking. Geometry is only generated where there are no holes and the terrain has been painted as navigable. \ ``global_aabb`` - If non-empty, geometry will be generated only within this AABB. If empty, geometry will be generated for the entire terrain. \ ``require_nav`` - If true, this function will only generate geometry for terrain marked navigable. Otherwise, geometry is generated for the entire terrain within the AABB (which can be useful for dynamic and/or runtime nav mesh baking). .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_camera: .. rst-class:: classref-method ``Camera3D`` **get_camera**\ (\ ) |const| :ref:`🔗` Returns the camera the terrain is currently tracking for position, if not overridden by :ref:`clipmap_target`. See :ref:`set_camera()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_clipmap_target_position: .. rst-class:: classref-method ``Vector3`` **get_clipmap_target_position**\ (\ ) |const| :ref:`🔗` Returns the position on which the terrain mesh is centered, which may be the camera or a target node. See :ref:`clipmap_target`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_collision_target_position: .. rst-class:: classref-method ``Vector3`` **get_collision_target_position**\ (\ ) |const| :ref:`🔗` Returns the position on which the terrain collision is centered, which may be the camera or a target node. See :ref:`collision_target`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_editor: .. rst-class:: classref-method :ref:`Terrain3DEditor` **get_editor**\ (\ ) |const| :ref:`🔗` Returns the current Terrain3DEditor instance, if it has been set. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_intersection: .. rst-class:: classref-method ``Vector3`` **get_intersection**\ (\ src_pos\: ``Vector3``, direction\: ``Vector3``, gpu_mode\: ``bool`` = false\ ) :ref:`🔗` Casts a ray from ``src_pos`` pointing towards ``direction``, attempting to intersect the terrain. This operation is does not use physics and is not a typical raycast, so enabling collision is unnecessary. This function likely won't work if src_pos is below the terrain. This function can operate in one of two modes selected by ``gpu_mode``: - If gpu_mode is disabled (default), it raymarches from src_pos until the terrain is intersected, up to 4000m away. This works with one function call, and can only intersect the terrain where regions exist. It is slower than gpu_mode and gets increasingly slower the farther away the terrain is, though you may not notice. - If gpu_mode is enabled, it uses the GPU to detect the mouse. This works wherever the terrain is visible, even outside of regions, but may need to be called twice. GPU mode places a camera at the specified point and "looks" at the terrain. It uses the depth texture to determine how far away the intersection point is. It requires the use of an editor render layer, (default 32, set with :ref:`mouse_layer`) while using this function. The main caveats of using this mode is that the call to get_intersection() requests a viewport be drawn, but cannot wait for it to finish as there is no "await" in C++ and no force draw function in Godot. So the return value is one frame behind, and invalid on the first call. This also means the function cannot be used more than once per frame. This mode works well when used continuously, once per frame, where one frame of difference won't matter. The editor uses this mode to place the mouse cursor decal. This mode can also be used by your plugins and games, such as a space ship firing lasers at the terrain and causing an explosion at the hit point. However if the calls aren't continuous, eg driven by the mouse, you'll need to call once to capture the viewport image, wait for it to be drawn, then call again to get the result: :: var target_point = terrain.get_intersection(camera_pos, camera_dir, true) await RenderingServer.frame_post_draw target_point = terrain.get_intersection(camera_pos, camera_dir, true) Possible return values: - If the terrain is hit, the intersection point is returned. - If there is no intersection, eg. the ray points towards the sky, it returns the maximum double float value ``Vector3(3.402823466e+38F,...)``. You can check this case with this code: ``if point.z > 3.4e38:``\ - On error, it returns ``Vector3(NAN, NAN, NAN)`` and prints a message to the console. Also see :ref:`get_raycast_result()` and :ref:`Terrain3DData.get_height()` for alternative functions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_plugin: .. rst-class:: classref-method ``Object`` **get_plugin**\ (\ ) |const| :ref:`🔗` Returns the EditorPlugin Object connected to Terrain3D. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_get_raycast_result: .. rst-class:: classref-method ``Dictionary`` **get_raycast_result**\ (\ src_pos\: ``Vector3``, direction\: ``Vector3``, collision_mask\: ``int`` = 4294967295, exclude_terrain\: ``bool`` = false\ ) |const| :ref:`🔗` This is a helper function that creates a general physics-based raycast and returns the resulting dictionary; it's not limited to terrain use. Raycasts can only detect collision. It is used by our editor using the `on_collision` option to instance on non-terrain meshes. Direction is added to src_pos and includes magnitude. So to run a raycast from (100, 100, 100) to the ground 100m below, direction would be (0, -110, 0) with margin. Collision_mask has the physics layers the query will detect as a bitmask. By default, all collision layers are detected. See `PhysicsDirectSpaceState3D.intersect_ray `__ for how to interpret the resulting dictionary. Also see :ref:`get_intersection()` and :ref:`Terrain3DData.get_height()` for alternative functions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_set_camera: .. rst-class:: classref-method |void| **set_camera**\ (\ camera\: ``Camera3D``\ ) :ref:`🔗` Specifies the camera on which the terrain centers. It attempts to aquire the camera from the active viewport. If the camera is instanced in a sub scene or by code, Terrain3D might not be able to find it, will issue an error, and the terrain will center at (0,0,0) causing LODs to not update until a trackable node is set. Either specify the camera, or specify the clipmap and/or collision targets. It will use the targets first and fall back to the camera if they are null. See :ref:`clipmap_target` and :ref:`collision_target`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_set_editor: .. rst-class:: classref-method |void| **set_editor**\ (\ editor\: :ref:`Terrain3DEditor`\ ) :ref:`🔗` Sets the current Terrain3DEditor instance. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_set_plugin: .. rst-class:: classref-method |void| **set_plugin**\ (\ plugin\: ``Object``\ ) :ref:`🔗` Sets the EditorPlugin Object connected to Terrain3D. .. rst-class:: classref-item-separator ---- .. _class_Terrain3D_method_snap: .. rst-class:: classref-method |void| **snap**\ (\ ) :ref:`🔗` Queues the terrain mesh and collision to snap their positions to the target nodes on the next physics frame. Typically this only happens if the targets have moved sufficiently far. See :ref:`clipmap_target` and :ref:`collision_target`. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dassets.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DAssets.xml. .. _class_Terrain3DAssets: Terrain3DAssets =============== **Inherits:** ``Resource`` .. rst-class:: classref-introduction-group Description ----------- This class contains arrays of :ref:`Terrain3DTextureAsset` and :ref:`Terrain3DMeshAsset` resources. It is a savable resource, so you can save it to disk and use the same asset list in multiple scenes that use Terrain3D. The amount of data is small, so it can be saved as a git-friendly, text based .tres file or left within the scene file. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +----------------------------------------------------------------------------------------+------------------------------------------------------------------+--------+ | :ref:`Array`\[:ref:`Terrain3DMeshAsset`\] | :ref:`mesh_list` | ``[]`` | +----------------------------------------------------------------------------------------+------------------------------------------------------------------+--------+ | :ref:`Array`\[:ref:`Terrain3DTextureAsset`\] | :ref:`texture_list` | ``[]`` | +----------------------------------------------------------------------------------------+------------------------------------------------------------------+--------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear_textures`\ (\ update\: ``bool`` = false\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`create_mesh_thumbnails`\ (\ id\: ``int`` = -1, size\: ``Vector2i`` = Vector2i(512, 512), force\: ``bool`` = false\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_albedo_array_rid`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DMeshAsset` | :ref:`get_mesh_asset`\ (\ id\: ``int``\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_mesh_count`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_normal_array_rid`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedFloat32Array`` | :ref:`get_texture_ao_light_affects`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedFloat32Array`` | :ref:`get_texture_ao_strengths`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DTextureAsset` | :ref:`get_texture_asset`\ (\ id\: ``int``\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedColorArray`` | :ref:`get_texture_colors`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_texture_count`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedVector2Array`` | :ref:`get_texture_detiles`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedVector2Array`` | :ref:`get_texture_displacements`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedFloat32Array`` | :ref:`get_texture_normal_depths`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedFloat32Array`` | :ref:`get_texture_roughness_mods`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedFloat32Array`` | :ref:`get_texture_uv_scales`\ (\ ) |const| | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Error | :ref:`save`\ (\ path\: ``String`` = ""\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_mesh_asset`\ (\ id\: ``int``, mesh\: :ref:`Terrain3DMeshAsset`\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_texture_asset`\ (\ id\: ``int``, texture\: :ref:`Terrain3DTextureAsset`\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_mesh_list`\ (\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_texture_list`\ (\ ) | +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Signals ------- .. _class_Terrain3DAssets_signal_meshes_changed: .. rst-class:: classref-signal **meshes_changed**\ (\ ) :ref:`🔗` Emitted when the mesh list is updated, which happens as a result of a :ref:`Terrain3DMeshAsset` changing. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_signal_textures_changed: .. rst-class:: classref-signal **textures_changed**\ (\ ) :ref:`🔗` Emitted when this list is updated due to changes in the texture slots, or the files or settings of any :ref:`Terrain3DTextureAsset`. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DAssets_AssetType: .. rst-class:: classref-enumeration enum **AssetType**: :ref:`🔗` .. _class_Terrain3DAssets_constant_TYPE_TEXTURE: .. rst-class:: classref-enumeration-constant :ref:`AssetType` **TYPE_TEXTURE** = ``0`` Asset is type Terrain3DTextureAsset. .. _class_Terrain3DAssets_constant_TYPE_MESH: .. rst-class:: classref-enumeration-constant :ref:`AssetType` **TYPE_MESH** = ``1`` Asset is type Terrain3DMeshAsset. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Constants --------- .. _class_Terrain3DAssets_constant_MAX_TEXTURES: .. rst-class:: classref-constant **MAX_TEXTURES** = ``32`` :ref:`🔗` Hard coded maximum number of textures, with IDs in the range of 0-31. Cannot easily be expanded. .. _class_Terrain3DAssets_constant_MAX_MESHES: .. rst-class:: classref-constant **MAX_MESHES** = ``256`` :ref:`🔗` Limit of the maximum number of meshes. Arbitrary, easily expanded. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DAssets_property_mesh_list: .. rst-class:: classref-property :ref:`Array`\[:ref:`Terrain3DMeshAsset`\] **mesh_list** = ``[]`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mesh_list**\ (\ value\: :ref:`Array`\[:ref:`Terrain3DMeshAsset`\]\ ) - :ref:`Array`\[:ref:`Terrain3DMeshAsset`\] **get_mesh_list**\ (\ ) The list of mesh assets. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_property_texture_list: .. rst-class:: classref-property :ref:`Array`\[:ref:`Terrain3DTextureAsset`\] **texture_list** = ``[]`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_texture_list**\ (\ value\: :ref:`Array`\[:ref:`Terrain3DTextureAsset`\]\ ) - :ref:`Array`\[:ref:`Terrain3DTextureAsset`\] **get_texture_list**\ (\ ) The list of texture assets. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DAssets_method_clear_textures: .. rst-class:: classref-method |void| **clear_textures**\ (\ update\: ``bool`` = false\ ) :ref:`🔗` After textures are loaded, they are combined into a TextureArray. The originals remain in VRAM and are only used if the :ref:`Terrain3DTextureAsset` settings are changed and regenerating the TextureArrays are necessary. Use this function to clear the originals if not needed. It removes all textures from the asset list, freeing them if they are not referenced by other objects. Update will regenerate the texture arrays housing the textures drawn on the terrain. This will remove all textures and turn the terrain checkerboard. A similar ``clear_meshes`` is less useful so hasn't been included. However you can do the same thing with ``get_mesh_list().clear()``. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_create_mesh_thumbnails: .. rst-class:: classref-method |void| **create_mesh_thumbnails**\ (\ id\: ``int`` = -1, size\: ``Vector2i`` = Vector2i(512, 512), force\: ``bool`` = false\ ) :ref:`🔗` Generates mesh asset preview thumbnails for the asset dock, stored within each mesh asset. Specify id -1 to generate all. By default, mesh thumbnails are not recreated if they already exist. Specify ``force`` to regenerate existing thumbnails. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_albedo_array_rid: .. rst-class:: classref-method ``RID`` **get_albedo_array_rid**\ (\ ) |const| :ref:`🔗` Returns the resource ID of the TextureArray generated from combining all albedo and height textures. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_mesh_asset: .. rst-class:: classref-method :ref:`Terrain3DMeshAsset` **get_mesh_asset**\ (\ id\: ``int``\ ) |const| :ref:`🔗` Returns the specified Terrain3DMeshAsset resource. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_mesh_count: .. rst-class:: classref-method ``int`` **get_mesh_count**\ (\ ) |const| :ref:`🔗` Returns the number of mesh assets in the list. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_normal_array_rid: .. rst-class:: classref-method ``RID`` **get_normal_array_rid**\ (\ ) |const| :ref:`🔗` Returns the resource ID of the TextureArray generated from combining all normal and roughness textures. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_ao_light_affects: .. rst-class:: classref-method ``PackedFloat32Array`` **get_texture_ao_light_affects**\ (\ ) |const| :ref:`🔗` Returns the array of ao light affects values each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_ao_strengths: .. rst-class:: classref-method ``PackedFloat32Array`` **get_texture_ao_strengths**\ (\ ) |const| :ref:`🔗` Returns the array of AO strengths for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_asset: .. rst-class:: classref-method :ref:`Terrain3DTextureAsset` **get_texture_asset**\ (\ id\: ``int``\ ) |const| :ref:`🔗` Returns the Terrain3DTextureAsset with the specified ID. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_colors: .. rst-class:: classref-method ``PackedColorArray`` **get_texture_colors**\ (\ ) |const| :ref:`🔗` Returns the array of albedo tints for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_count: .. rst-class:: classref-method ``int`` **get_texture_count**\ (\ ) |const| :ref:`🔗` Returns the number of texture slots used. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_detiles: .. rst-class:: classref-method ``PackedVector2Array`` **get_texture_detiles**\ (\ ) |const| :ref:`🔗` Returns the array of detiling values for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_displacements: .. rst-class:: classref-method ``PackedVector2Array`` **get_texture_displacements**\ (\ ) |const| :ref:`🔗` Returns the array of displacement offset and scale values for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_normal_depths: .. rst-class:: classref-method ``PackedFloat32Array`` **get_texture_normal_depths**\ (\ ) |const| :ref:`🔗` Returns the array of normal strengths for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_roughness_mods: .. rst-class:: classref-method ``PackedFloat32Array`` **get_texture_roughness_mods**\ (\ ) |const| :ref:`🔗` Returns the array of roughness modification values for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_get_texture_uv_scales: .. rst-class:: classref-method ``PackedFloat32Array`` **get_texture_uv_scales**\ (\ ) |const| :ref:`🔗` Returns the array of uv scale values for each texture asset, indexed by asset id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_save: .. rst-class:: classref-method Error **save**\ (\ path\: ``String`` = ""\ ) :ref:`🔗` Saves this texture list resource to disk, if saved as an external ``.tres`` or ``.res`` resource file. path - specifies a directory and file name to use from now on. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_set_mesh_asset: .. rst-class:: classref-method |void| **set_mesh_asset**\ (\ id\: ``int``, mesh\: :ref:`Terrain3DMeshAsset`\ ) :ref:`🔗` Assigns the Terrain3DMeshAsset to the specified ID slot. It can be null to clear the slot. See :ref:`set_texture_asset()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_set_texture_asset: .. rst-class:: classref-method |void| **set_texture_asset**\ (\ id\: ``int``, texture\: :ref:`Terrain3DTextureAsset`\ ) :ref:`🔗` Adds a Terrain3DTextureAsset at the specified ID slot. The texture can be null to clear the slot, or remove it if its the last in the list. If the specified slot is full, it will be swapped with the source texture ID, or will find the next available ID. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_update_mesh_list: .. rst-class:: classref-method |void| **update_mesh_list**\ (\ ) :ref:`🔗` Updates the internal list of meshes used by the instancer. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DAssets_method_update_texture_list: .. rst-class:: classref-method |void| **update_texture_list**\ (\ ) :ref:`🔗` Regenerates the texture arrays from the list of texture assets, which is sent to the shader. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dcollision.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DCollision.xml. .. _class_Terrain3DCollision: Terrain3DCollision ================== **Inherits:** ``Object`` .. rst-class:: classref-introduction-group Description ----------- This class manages collision. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | ``int`` | :ref:`layer` | ``1`` | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | ``int`` | :ref:`mask` | ``1`` | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | :ref:`CollisionMode` | :ref:`mode` | ``1`` | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | ``PhysicsMaterial`` | :ref:`physics_material` | | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | ``float`` | :ref:`priority` | ``1.0`` | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | ``int`` | :ref:`radius` | ``64`` | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ | ``int`` | :ref:`shape_size` | ``16`` | +-------------------------------------------------------------+-----------------------------------------------------------------------------+---------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`build`\ (\ ) | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`destroy`\ (\ ) | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_rid`\ (\ ) |const| | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_dynamic_mode`\ (\ ) |const| | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_editor_mode`\ (\ ) |const| | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_enabled`\ (\ ) |const| | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update`\ (\ region_location\: ``Vector2i`` = Vector2i(2147483647, 2147483647), rebuild\: ``bool`` = false\ ) | +----------+------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DCollision_CollisionMode: .. rst-class:: classref-enumeration enum **CollisionMode**: :ref:`🔗` .. _class_Terrain3DCollision_constant_DISABLED: .. rst-class:: classref-enumeration-constant :ref:`CollisionMode` **DISABLED** = ``0`` No collision shapes will be generated. .. _class_Terrain3DCollision_constant_DYNAMIC_GAME: .. rst-class:: classref-enumeration-constant :ref:`CollisionMode` **DYNAMIC_GAME** = ``1`` Collision shapes are generated around the camera as it moves; in game only. .. _class_Terrain3DCollision_constant_DYNAMIC_EDITOR: .. rst-class:: classref-enumeration-constant :ref:`CollisionMode` **DYNAMIC_EDITOR** = ``2`` Collision shapes are generated around the camera as it moves; in the editor and in game. Enable ``View Gizmos`` in the viewport menu to see them. .. _class_Terrain3DCollision_constant_FULL_GAME: .. rst-class:: classref-enumeration-constant :ref:`CollisionMode` **FULL_GAME** = ``3`` Collision shapes are generated for all regions in game only. One shape per region. .. _class_Terrain3DCollision_constant_FULL_EDITOR: .. rst-class:: classref-enumeration-constant :ref:`CollisionMode` **FULL_EDITOR** = ``4`` Collision shapes are generated for all regions in the editor and in game. One shape per region. This mode is necessary for some 3rd party plugins to detect the terrain using collision. Enable ``View Gizmos`` in the viewport menu to see the collision mesh. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DCollision_property_layer: .. rst-class:: classref-property ``int`` **layer** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_layer**\ (\ value\: ``int``\ ) - ``int`` **get_layer**\ (\ ) The physics layers the terrain lives on. Sets ``CollisionObject3D.collision_layer``. Also see :ref:`mask`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_property_mask: .. rst-class:: classref-property ``int`` **mask** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mask**\ (\ value\: ``int``\ ) - ``int`` **get_mask**\ (\ ) The physics layers the physics body scans for colliding objects. Sets ``CollisionObject3D.collision_mask``. Also see :ref:`layer`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_property_mode: .. rst-class:: classref-property :ref:`CollisionMode` **mode** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mode**\ (\ value\: :ref:`CollisionMode`\ ) - :ref:`CollisionMode` **get_mode**\ (\ ) The selected mode determines if collision is generated and how. See :ref:`CollisionMode` for details. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_property_physics_material: .. rst-class:: classref-property ``PhysicsMaterial`` **physics_material** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_physics_material**\ (\ value\: ``PhysicsMaterial``\ ) - ``PhysicsMaterial`` **get_physics_material**\ (\ ) Applies a ``PhysicsMaterial`` override to the entire terrain StaticBody. There's no ability built into Godot to change physics material parameters based on texture or any other factor. However, it might be possible to extend `PhysicsMaterial` in order to inject code into the queries. It would need references to an object position and a terrain, and then it could run :ref:`Terrain3DData.get_texture_id()` based on the position and return different physics settings per texture. That would change the settings for the entire terrain for that moment. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_property_priority: .. rst-class:: classref-property ``float`` **priority** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_priority**\ (\ value\: ``float``\ ) - ``float`` **get_priority**\ (\ ) The priority with which the physics server uses to solve collisions. The higher the priority, the lower the penetration of a colliding object. Sets ``CollisionObject3D.collision_priority``. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_property_radius: .. rst-class:: classref-property ``int`` **radius** = ``64`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_radius**\ (\ value\: ``int``\ ) - ``int`` **get_radius**\ (\ ) If :ref:`mode` is Dynamic, this is the distance range within which collision shapes will be generated. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_property_shape_size: .. rst-class:: classref-property ``int`` **shape_size** = ``16`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_shape_size**\ (\ value\: ``int``\ ) - ``int`` **get_shape_size**\ (\ ) If :ref:`mode` is Dynamic, this is the size of each collision shape. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DCollision_method_build: .. rst-class:: classref-method |void| **build**\ (\ ) :ref:`🔗` Creates collision shapes and calls :ref:`update()` to shape them. Calls :ref:`destroy()` first, so it is safe to call this to fully rebuild collision any time. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_method_destroy: .. rst-class:: classref-method |void| **destroy**\ (\ ) :ref:`🔗` Removes all collision shapes and frees any memory used. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_method_get_rid: .. rst-class:: classref-method ``RID`` **get_rid**\ (\ ) |const| :ref:`🔗` Returns the RID of the active StaticBody. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_method_is_dynamic_mode: .. rst-class:: classref-method ``bool`` **is_dynamic_mode**\ (\ ) |const| :ref:`🔗` Returns true if :ref:`mode` is ``Dynamic / Editor`` or ``Dynamic / Game``. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_method_is_editor_mode: .. rst-class:: classref-method ``bool`` **is_editor_mode**\ (\ ) |const| :ref:`🔗` Returns true if :ref:`mode` is ``Full / Editor`` or ``Dynamic / Editor``. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_method_is_enabled: .. rst-class:: classref-method ``bool`` **is_enabled**\ (\ ) |const| :ref:`🔗` Returns true if :ref:`mode` is not ``Disabled``. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DCollision_method_update: .. rst-class:: classref-method |void| **update**\ (\ region_location\: ``Vector2i`` = Vector2i(2147483647, 2147483647), rebuild\: ``bool`` = false\ ) :ref:`🔗` - If :ref:`mode` is Full, recalculates the specified or all existing collision shapes. Specify a ``region_location`` to update only that region, or omit it or use `Vector2i.MAX` to update all regions. If regions have been added or removed, set ``rebuild`` to true or call :ref:`build()` instead. A full update can be slow. - If :ref:`mode` is Dynamic, repositions collision shapes around the camera and recalculates ones that moved. Set ``rebuild`` to true to recalculate all shapes within :ref:`radius`. ``region_location`` is ignored. This is very fast, and can be updated at 60fps for little cost. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3ddata.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DData.xml. .. _class_Terrain3DData: Terrain3DData ============= **Inherits:** ``Object`` .. rst-class:: classref-introduction-group Description ----------- Terrain3D divides all data into regions which fit on a grid in the world. These coordinates are called region locations. The map data are stored in instances of :ref:`Terrain3DRegion`, which are saved to individual files. This class manages region loading, unloading, data retreival and manipulation. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +-------------------------------------------+------------------------------------------------------------------------+--------+ | :ref:`Array`\[``Image``\] | :ref:`color_maps` | ``[]`` | +-------------------------------------------+------------------------------------------------------------------------+--------+ | :ref:`Array`\[``Image``\] | :ref:`control_maps` | ``[]`` | +-------------------------------------------+------------------------------------------------------------------------+--------+ | :ref:`Array`\[``Image``\] | :ref:`height_maps` | ``[]`` | +-------------------------------------------+------------------------------------------------------------------------+--------+ | :ref:`Array`\[``Vector2i``\] | :ref:`region_locations` | ``[]`` | +-------------------------------------------+------------------------------------------------------------------------+--------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Error | :ref:`add_region`\ (\ region\: :ref:`Terrain3DRegion`, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DRegion` | :ref:`add_region_blank`\ (\ region_location\: ``Vector2i``, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DRegion` | :ref:`add_region_blankp`\ (\ global_position\: ``Vector3``, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`calc_height_range`\ (\ recursive\: ``bool`` = false\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`change_region_size`\ (\ region_size\: ``int``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`do_for_regions`\ (\ area\: ``Rect2i``, callback\: ``Callable``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`dump`\ (\ verbose\: ``bool`` = false\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Error | :ref:`export_image`\ (\ file_name\: ``String``, map_type\: :ref:`MapType`\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Color`` | :ref:`get_color`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_color_maps_rid`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_control`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`get_control_angle`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`get_control_auto`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_control_base_id`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`get_control_blend`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`get_control_hole`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_control_maps_rid`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`get_control_navigation`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_control_overlay_id`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`get_control_scale`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`get_height`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_height_maps_rid`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector2`` | :ref:`get_height_range`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Array`\[``Image``\] | :ref:`get_maps`\ (\ map_type\: :ref:`MapType`\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector3`` | :ref:`get_mesh_vertex`\ (\ lod\: ``int``, filter\: :ref:`HeightFilter`, global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector3`` | :ref:`get_normal`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Color`` | :ref:`get_pixel`\ (\ map_type\: :ref:`MapType`, global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DRegion` | :ref:`get_region`\ (\ region_location\: ``Vector2i``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_region_count`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_region_id`\ (\ region_location\: ``Vector2i``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_region_idp`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector2i`` | :ref:`get_region_location`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``PackedInt32Array`` | :ref:`get_region_map`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_region_map_index`\ (\ region_location\: ``Vector2i``\ ) |static| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DRegion` | :ref:`get_regionp`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Array`\[:ref:`Terrain3DRegion`\] | :ref:`get_regions_active`\ (\ copy\: ``bool`` = false, deep\: ``bool`` = false\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Dictionary`` | :ref:`get_regions_all`\ (\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`get_roughness`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector3`` | :ref:`get_texture_id`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`has_region`\ (\ region_location\: ``Vector2i``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`has_regionp`\ (\ global_position\: ``Vector3``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`import_images`\ (\ images\: :ref:`Array`\[``Image``\], global_position\: ``Vector3`` = Vector3(0, 0, 0), offset\: ``float`` = 0.0, scale\: ``float`` = 1.0\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_in_slope`\ (\ global_position\: ``Vector3``, slope_range\: ``Vector2``, normal\: ``Vector3`` = Vector3(0, 0, 0)\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_region_deleted`\ (\ region_location\: ``Vector2i``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_region_modified`\ (\ region_location\: ``Vector2i``\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`layered_to_image`\ (\ map_type\: :ref:`MapType`\ ) |const| | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`load_directory`\ (\ directory\: ``String``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`load_region`\ (\ region_location\: ``Vector2i``, directory\: ``String``, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`remove_region`\ (\ region\: :ref:`Terrain3DRegion`, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`remove_regionl`\ (\ region_location\: ``Vector2i``, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`remove_regionp`\ (\ global_position\: ``Vector3``, update\: ``bool`` = true\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`save_directory`\ (\ directory\: ``String``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`save_region`\ (\ region_location\: ``Vector2i``, directory\: ``String``, save_16_bit\: ``bool`` = false\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_color`\ (\ global_position\: ``Vector3``, color\: ``Color``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control`\ (\ global_position\: ``Vector3``, control\: ``int``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_angle`\ (\ global_position\: ``Vector3``, degrees\: ``float``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_auto`\ (\ global_position\: ``Vector3``, enable\: ``bool``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_base_id`\ (\ global_position\: ``Vector3``, texture_id\: ``int``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_blend`\ (\ global_position\: ``Vector3``, blend_value\: ``float``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_hole`\ (\ global_position\: ``Vector3``, enable\: ``bool``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_navigation`\ (\ global_position\: ``Vector3``, enable\: ``bool``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_overlay_id`\ (\ global_position\: ``Vector3``, texture_id\: ``int``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_control_scale`\ (\ global_position\: ``Vector3``, percentage_modifier\: ``float``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_height`\ (\ global_position\: ``Vector3``, height\: ``float``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_pixel`\ (\ map_type\: :ref:`MapType`, global_position\: ``Vector3``, pixel\: ``Color``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_region_deleted`\ (\ region_location\: ``Vector2i``, deleted\: ``bool``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_region_modified`\ (\ region_location\: ``Vector2i``, modified\: ``bool``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_roughness`\ (\ global_position\: ``Vector3``, roughness\: ``float``\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_maps`\ (\ map_type\: :ref:`MapType` = 3, all_regions\: ``bool`` = true, generate_mipmaps\: ``bool`` = false\ ) | +----------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Signals ------- .. _class_Terrain3DData_signal_color_maps_changed: .. rst-class:: classref-signal **color_maps_changed**\ (\ ) :ref:`🔗` Emitted when the color maps array is regenerated. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_signal_control_maps_changed: .. rst-class:: classref-signal **control_maps_changed**\ (\ ) :ref:`🔗` Emitted when the control maps array is regenerated. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_signal_height_maps_changed: .. rst-class:: classref-signal **height_maps_changed**\ (\ ) :ref:`🔗` Emitted when the height maps array is regenerated. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_signal_maps_changed: .. rst-class:: classref-signal **maps_changed**\ (\ ) :ref:`🔗` Emitted when the region map or any map array has been regenerated. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_signal_maps_edited: .. rst-class:: classref-signal **maps_edited**\ (\ edited_area\: ``AABB``\ ) :ref:`🔗` This signal is emitted whenever the editor (:ref:`Terrain3DEditor`) is used to: - add or remove a region - alter a region map with a brush tool - undo or redo any of the above operations The parameter contains the axis-aligned bounding box of the area edited. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_signal_region_map_changed: .. rst-class:: classref-signal **region_map_changed**\ (\ ) :ref:`🔗` Emitted when the region map is regenerated. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DData_HeightFilter: .. rst-class:: classref-enumeration enum **HeightFilter**: :ref:`🔗` .. _class_Terrain3DData_constant_HEIGHT_FILTER_NEAREST: .. rst-class:: classref-enumeration-constant :ref:`HeightFilter` **HEIGHT_FILTER_NEAREST** = ``0`` Samples the height map at the exact coordinates given. .. _class_Terrain3DData_constant_HEIGHT_FILTER_MINIMUM: .. rst-class:: classref-enumeration-constant :ref:`HeightFilter` **HEIGHT_FILTER_MINIMUM** = ``1`` Samples (1 << lod) \* 2 heights around the given coordinates and returns the lowest. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Constants --------- .. _class_Terrain3DData_constant_REGION_MAP_SIZE: .. rst-class:: classref-constant **REGION_MAP_SIZE** = ``32`` :ref:`🔗` Hard coded number of regions on a side. The total number of regions is this squared. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DData_property_color_maps: .. rst-class:: classref-property :ref:`Array`\[``Image``\] **color_maps** = ``[]`` :ref:`🔗` .. rst-class:: classref-property-setget - :ref:`Array`\[``Image``\] **get_color_maps**\ (\ ) An Array\ ``Image`` containing references to all of the color maps in all regions. See :ref:`Terrain3DRegion.color_map`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_property_control_maps: .. rst-class:: classref-property :ref:`Array`\[``Image``\] **control_maps** = ``[]`` :ref:`🔗` .. rst-class:: classref-property-setget - :ref:`Array`\[``Image``\] **get_control_maps**\ (\ ) An Array\ ``Image`` containing references to all of the control maps in all regions. See :ref:`Terrain3DRegion.control_map`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_property_height_maps: .. rst-class:: classref-property :ref:`Array`\[``Image``\] **height_maps** = ``[]`` :ref:`🔗` .. rst-class:: classref-property-setget - :ref:`Array`\[``Image``\] **get_height_maps**\ (\ ) An Array\ ``Image`` containing references to all of the height maps in all regions. See :ref:`Terrain3DRegion.height_map`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_property_region_locations: .. rst-class:: classref-property :ref:`Array`\[``Vector2i``\] **region_locations** = ``[]`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_region_locations**\ (\ value\: :ref:`Array`\[``Vector2i``\]\ ) - :ref:`Array`\[``Vector2i``\] **get_region_locations**\ (\ ) The array of all active region locations; those not marked for deletion. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DData_method_add_region: .. rst-class:: classref-method Error **add_region**\ (\ region\: :ref:`Terrain3DRegion`, update\: ``bool`` = true\ ) :ref:`🔗` Adds a region for sculpting and painting. The region should already be configured with the desired location and maps before sending to this function. Upon saving, this region will be written to a data file stored in :ref:`Terrain3D.data_directory`. - update - regenerates the texture arrays if true. Set to false if bulk adding many regions, then true on the last one or use :ref:`update_maps()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_add_region_blank: .. rst-class:: classref-method :ref:`Terrain3DRegion` **add_region_blank**\ (\ region_location\: ``Vector2i``, update\: ``bool`` = true\ ) :ref:`🔗` Creates and adds a blank region at the specified location. See :ref:`add_region()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_add_region_blankp: .. rst-class:: classref-method :ref:`Terrain3DRegion` **add_region_blankp**\ (\ global_position\: ``Vector3``, update\: ``bool`` = true\ ) :ref:`🔗` Creates and adds a blank region at a region location encompassing the specified global position. See :ref:`add_region()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_calc_height_range: .. rst-class:: classref-method |void| **calc_height_range**\ (\ recursive\: ``bool`` = false\ ) :ref:`🔗` Recalculates the master height range for the whole terrain by summing the height ranges of all active regions. Recursive mode does the same, but has each region recalculate heights from each heightmap pixel. See :ref:`Terrain3DRegion.calc_height_range()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_change_region_size: .. rst-class:: classref-method |void| **change_region_size**\ (\ region_size\: ``int``\ ) :ref:`🔗` Reslices terrain data to fit the new region size. This is a destructive process for which there is no undo. However Godot does make an undo entry, which will reslice in reverse. Files on disk are not added or removed until the scene is saved. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_do_for_regions: .. rst-class:: classref-method |void| **do_for_regions**\ (\ area\: ``Rect2i``, callback\: ``Callable``\ ) :ref:`🔗` Calls the callback function for every region within the given area. If using vertex_spacing, area values should be descaled. The callable receives: source Terrain3DRegion, source Rect2i, dest Rect2i, (bindings) You may wish to append .bind() to the callback to pass along variables. For instance internally this function is called when changing region size. We bind the destination Terrain3DRegion, then use do_for_regions to copy segments of source regions to segments of destination regions. See the code for change_region_size() for more. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_dump: .. rst-class:: classref-method |void| **dump**\ (\ verbose\: ``bool`` = false\ ) |const| :ref:`🔗` Calls :ref:`Terrain3DRegion.dump()` for all regions loaded, active and inactive. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_export_image: .. rst-class:: classref-method Error **export_image**\ (\ file_name\: ``String``, map_type\: :ref:`MapType`\ ) |const| :ref:`🔗` Exports the specified map type as one of r16/raw, exr, jpg, png, webp, res, tres. R16 or exr are recommended for roundtrip external editing. R16 can be edited by Krita, however you must know the dimensions and min/max before reimporting. This information is printed to the console. Res/tres stores in Godot's native data format. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_color: .. rst-class:: classref-method ``Color`` **get_color**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the associated pixel on the color map at the requested position. Returns ``Color(NAN, NAN, NAN, NAN)`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_color_maps_rid: .. rst-class:: classref-method ``RID`` **get_color_maps_rid**\ (\ ) |const| :ref:`🔗` Returns the resource ID of the generated height map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See `Tips `__ for an example. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control: .. rst-class:: classref-method ``int`` **get_control**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the associated pixel on the control map at the requested position. Returns ``4,294,967,295`` aka ``UINT32_MAX`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_angle: .. rst-class:: classref-method ``float`` **get_control_angle**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the angle, aka uv rotation, on the control map at the requested position. Values are fixed to 22.5 degree intervals, for a maximum of 16 angles. 360 / 16 = 22.5. Returns ``NAN`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_auto: .. rst-class:: classref-method ``bool`` **get_control_auto**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns whether the autoshader is enabled on the control map at the requested position. Returns ``false`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_base_id: .. rst-class:: classref-method ``int`` **get_control_base_id**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the base texture ID on the control map at the requested position. Values are 0 - 31, which matches the ID of the texture asset in the asset dock. Returns ``4,294,967,295`` aka ``UINT32_MAX`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_blend: .. rst-class:: classref-method ``float`` **get_control_blend**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the blend value between the base texture ID and the overlay texture ID. The value is clamped between 0.0 - 1.0 where 0.0 shows only the base texture, and 1.0 shows only the overlay texture. Returns ``NAN`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_hole: .. rst-class:: classref-method ``bool`` **get_control_hole**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns whether there is a hole on the control map at the requested position. Returns ``false`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_maps_rid: .. rst-class:: classref-method ``RID`` **get_control_maps_rid**\ (\ ) |const| :ref:`🔗` Returns the resource ID of the generated control map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See `Tips `__ for an example. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_navigation: .. rst-class:: classref-method ``bool`` **get_control_navigation**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns whether navigation is enabled on the control map at the requested position. Returns ``false`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_overlay_id: .. rst-class:: classref-method ``int`` **get_control_overlay_id**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the overlay texture ID on the control map at the requested position. Values are 0 - 31, which matches the ID of the texture asset in the asset dock. Returns ``4,294,967,295`` aka ``UINT32_MAX`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_control_scale: .. rst-class:: classref-method ``float`` **get_control_scale**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the uv scale on the control map at the requested position. The value is rounded to the nearest 20% difference from 100%, ranging between -60% to +80%. Eg. +20% or -40%. Returns ``NAN`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_height: .. rst-class:: classref-method ``float`` **get_height**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the height at the requested position. If the position is close to a vertex, the pixel height on the heightmap is returned. Otherwise the value is interpolated from the 4 vertices surrounding the position. Returns ``NAN`` if the requested position is a hole or outside of defined regions. Also see :ref:`Terrain3D.get_raycast_result()` and :ref:`Terrain3D.get_intersection()` for alternative functions .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_height_maps_rid: .. rst-class:: classref-method ``RID`` **get_height_maps_rid**\ (\ ) |const| :ref:`🔗` Returns the resource ID of the generated height map texture array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See `Tips `__ for an example. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_height_range: .. rst-class:: classref-method ``Vector2`` **get_height_range**\ (\ ) |const| :ref:`🔗` Returns the highest and lowest heights for the sculpted terrain used to set the world AABB. See :ref:`calc_height_range()`. Any :ref:`Terrain3DMaterial.world_background` used that extends the mesh outside of this range will not change this variable. You need to set :ref:`Terrain3D.cull_margin` or the renderer will clip meshes. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_maps: .. rst-class:: classref-method :ref:`Array`\[``Image``\] **get_maps**\ (\ map_type\: :ref:`MapType`\ ) |const| :ref:`🔗` Returns an Array of Images from all regions of the specified map type. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_mesh_vertex: .. rst-class:: classref-method ``Vector3`` **get_mesh_vertex**\ (\ lod\: ``int``, filter\: :ref:`HeightFilter`, global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the position of a terrain vertex at a certain LOD. If the position is outside of defined regions or there is a hole, it returns ``NAN`` in the vector's Y coordinate. \ ``lod`` - Determines how many heights around the given global position will be sampled. Range 0 - 8. \ ``filter`` - Specifies how samples are filtered. See :ref:`HeightFilter`. \ ``global_position`` - X and Z coordinates of the vertex. Heights will be sampled around these coordinates. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_normal: .. rst-class:: classref-method ``Vector3`` **get_normal**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the terrain normal at the specified position. This function uses :ref:`get_height()`. Returns ``Vector3(NAN, NAN, NAN)`` if the requested position is a hole or outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_pixel: .. rst-class:: classref-method ``Color`` **get_pixel**\ (\ map_type\: :ref:`MapType`, global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the pixel for the map type associated with the specified position. Returns ``Color(NAN, NAN, NAN, NAN)`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region: .. rst-class:: classref-method :ref:`Terrain3DRegion` **get_region**\ (\ region_location\: ``Vector2i``\ ) |const| :ref:`🔗` Return the :ref:`Terrain3DRegion` at the specified location. This will return inactive regions marked for deletion. Check with :ref:`Terrain3DRegion.deleted`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region_count: .. rst-class:: classref-method ``int`` **get_region_count**\ (\ ) |const| :ref:`🔗` Returns the number of active regions; those not marked for deletion. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region_id: .. rst-class:: classref-method ``int`` **get_region_id**\ (\ region_location\: ``Vector2i``\ ) |const| :ref:`🔗` Returns -1 if no region or out of bounds at the given location, otherwise returns the current region id. The region_id is the index into the TextureArrays sent to the shader, and can change at any time. Gamedevs should generally index regions by location. However, this function is useful to determine if the location is a valid region. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region_idp: .. rst-class:: classref-method ``int`` **get_region_idp**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the region id at a global position. See :ref:`get_region_id()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region_location: .. rst-class:: classref-method ``Vector2i`` **get_region_location**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the calculated region location for the given global position. This is just a calculation and does no bounds checking or verification that a region exists. See :ref:`get_region_map_index()` for bounds checking, or :ref:`has_region()` for checking existance. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region_map: .. rst-class:: classref-method ``PackedInt32Array`` **get_region_map**\ (\ ) |const| :ref:`🔗` Returns a fully populated 32 x 32 array. The array location contains the region id + 1, or 0, which means no region. See :ref:`get_region_map_index()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_region_map_index: .. rst-class:: classref-method ``int`` **get_region_map_index**\ (\ region_location\: ``Vector2i``\ ) |static| :ref:`🔗` Given a region location, returns the index into the region map array. See :ref:`get_region_map()`. You can use this function to quickly determine if a location is within the greater world bounds (-16,-16) to (15, 15). It returns -1 if not. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_regionp: .. rst-class:: classref-method :ref:`Terrain3DRegion` **get_regionp**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the region at the specified global position. This will return inactive regions marked for deletion. Check with :ref:`Terrain3DRegion.deleted`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_regions_active: .. rst-class:: classref-method :ref:`Array`\[:ref:`Terrain3DRegion`\] **get_regions_active**\ (\ copy\: ``bool`` = false, deep\: ``bool`` = false\ ) |const| :ref:`🔗` Returns an array of active regions not marked for deletion. Each region knows its own location. See :ref:`Terrain3DRegion.location`. - copy - returns a shallow copy of the regions; region map references are copied. - deep - returns a deep copy of the regions; region maps are full duplicates. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_regions_all: .. rst-class:: classref-method ``Dictionary`` **get_regions_all**\ (\ ) |const| :ref:`🔗` Returns all regions in a dictionary indexed by region location. Some regions may be marked for deletion. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_roughness: .. rst-class:: classref-method ``float`` **get_roughness**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the roughness modifier (wetness) on the color map alpha channel associated with the specified position. Returns ``Color(NAN, NAN, NAN, NAN)`` if the position is outside of defined regions. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_get_texture_id: .. rst-class:: classref-method ``Vector3`` **get_texture_id**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns ``Vector3(base texture id, overlay id, blend value)``. Returns ``Vector3(NAN, NAN, NAN)`` if the position is a hole or outside of defined regions. This is often used for playing sounds on footsteps. It's up to the gamedev to determine which is visually apparent based on shader settings. Due to blending, it won't be pixel perfect. Try having your player controller print this value while walking around to see how the blending values look. Perhaps you'll find that the overlay texture is visible starting at a blend value of .3 to .5, otherwise the base is visible. You can also observe the control blend debug view with :ref:`Terrain3DMaterial.show_control_blend`. Observing how this is done in The Witcher 3, there are only about 6 sounds used (snow, foliage, dirt, gravel, rock, wood), and except for wood, they are not pixel perfect. Wood is easy to do by detecting if the player is walking on wood meshes. The other 5 sounds are played when the player is in an area where the textures are blending. So it might play rock while over a dirt area. This shows pixel perfect accuracy is not important. It will still provide a seamless audio visual experience. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_has_region: .. rst-class:: classref-method ``bool`` **has_region**\ (\ region_location\: ``Vector2i``\ ) |const| :ref:`🔗` Returns true if the specified region location has an active region. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_has_regionp: .. rst-class:: classref-method ``bool`` **has_regionp**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns true if the specified global position has an active region. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_import_images: .. rst-class:: classref-method |void| **import_images**\ (\ images\: :ref:`Array`\[``Image``\], global_position\: ``Vector3`` = Vector3(0, 0, 0), offset\: ``float`` = 0.0, scale\: ``float`` = 1.0\ ) :ref:`🔗` Imports an Image set (Height, Control, Color) into this resource. It does NOT normalize values to 0-1. You must do that using get_min_max() and adjusting scale and offset. \ ``images`` - MapType.TYPE_MAX sized array of Images for Height, Control, Color. Images can be blank or null. \ ``global_position`` - X,0,Z position on the region map. Valid range is :ref:`Terrain3D.vertex_spacing` \* :ref:`Terrain3D.region_size` \* (+/-16, +/-16). \ ``offset`` - Add this factor to all height values, can be negative. \ ``scale`` - Scale all height values by this factor (applied after offset). .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_is_in_slope: .. rst-class:: classref-method ``bool`` **is_in_slope**\ (\ global_position\: ``Vector3``, slope_range\: ``Vector2``, normal\: ``Vector3`` = Vector3(0, 0, 0)\ ) |const| :ref:`🔗` Returns true if the slope of the terrain at the given position is within the slope range. If normal is provided it will use that instead of querying the terrain. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_is_region_deleted: .. rst-class:: classref-method ``bool`` **is_region_deleted**\ (\ region_location\: ``Vector2i``\ ) |const| :ref:`🔗` Returns true if the region at the location exists and is marked as deleted. Syntactic sugar for :ref:`Terrain3DRegion.deleted`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_is_region_modified: .. rst-class:: classref-method ``bool`` **is_region_modified**\ (\ region_location\: ``Vector2i``\ ) |const| :ref:`🔗` Returns true if the region at the location exists and is marked as modified. Syntactic sugar for :ref:`Terrain3DRegion.modified`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_layered_to_image: .. rst-class:: classref-method ``Image`` **layered_to_image**\ (\ map_type\: :ref:`MapType`\ ) |const| :ref:`🔗` Returns an Image of the given map type that contains all regions in one large image. If the world has multiple islands, this function will return an image large enough to encompass all used regions, with black areas in between the islands. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_load_directory: .. rst-class:: classref-method |void| **load_directory**\ (\ directory\: ``String``\ ) :ref:`🔗` Loads all of the Terrain3DRegion files found in the specified directory. Then it rebuilds all map arrays. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_load_region: .. rst-class:: classref-method |void| **load_region**\ (\ region_location\: ``Vector2i``, directory\: ``String``, update\: ``bool`` = true\ ) :ref:`🔗` Loads the specified region location file. - update - rebuild maps if true. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_remove_region: .. rst-class:: classref-method |void| **remove_region**\ (\ region\: :ref:`Terrain3DRegion`, update\: ``bool`` = true\ ) :ref:`🔗` Marks the specified region as deleted. This deactivates it so it won't render it on screen once maps are updated, unless marked not deleted. The file will be deleted from disk upon saving. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_remove_regionl: .. rst-class:: classref-method |void| **remove_regionl**\ (\ region_location\: ``Vector2i``, update\: ``bool`` = true\ ) :ref:`🔗` Removes the region at the specified location. See :ref:`remove_region()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_remove_regionp: .. rst-class:: classref-method |void| **remove_regionp**\ (\ global_position\: ``Vector3``, update\: ``bool`` = true\ ) :ref:`🔗` Removes the region at the specified global_position. See :ref:`remove_region()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_save_directory: .. rst-class:: classref-method |void| **save_directory**\ (\ directory\: ``String``\ ) :ref:`🔗` This saves all active regions into the specified directory. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_save_region: .. rst-class:: classref-method |void| **save_region**\ (\ region_location\: ``Vector2i``, directory\: ``String``, save_16_bit\: ``bool`` = false\ ) :ref:`🔗` Saves the specified active region to the directory. See :ref:`Terrain3DRegion.save()`. - region_location - the region to save. - 16_bit - converts the edited 32-bit heightmap to 16-bit. This is a lossy operation. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_color: .. rst-class:: classref-method |void| **set_color**\ (\ global_position\: ``Vector3``, color\: ``Color``\ ) :ref:`🔗` Sets the color on the color map pixel associated with the specified position. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control: .. rst-class:: classref-method |void| **set_control**\ (\ global_position\: ``Vector3``, control\: ``int``\ ) :ref:`🔗` Sets the value on the control map pixel associated with the specified position. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_angle: .. rst-class:: classref-method |void| **set_control_angle**\ (\ global_position\: ``Vector3``, degrees\: ``float``\ ) :ref:`🔗` Sets the angle, aka uv rotation, on the control map at the requested position. Values are rounded to the nearest 22.5 degree interval, for a maximum of 16 angles. 360 / 16 = 22.5. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_auto: .. rst-class:: classref-method |void| **set_control_auto**\ (\ global_position\: ``Vector3``, enable\: ``bool``\ ) :ref:`🔗` Sets if the material should render the autoshader or manual texturing on the control map at the requested position. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_base_id: .. rst-class:: classref-method |void| **set_control_base_id**\ (\ global_position\: ``Vector3``, texture_id\: ``int``\ ) :ref:`🔗` Sets the base texture ID on the control map at the requested position. Values are clamped to 0 - 31, matching the ID of the texture asset in the asset dock. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_blend: .. rst-class:: classref-method |void| **set_control_blend**\ (\ global_position\: ``Vector3``, blend_value\: ``float``\ ) :ref:`🔗` Sets the blend value between the base texture ID, and the overlay texture ID. The value is clamped between 0.0 - 1.0 where 0.0 shows only the base texture, and 1.0 shows only the overlay texture. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_hole: .. rst-class:: classref-method |void| **set_control_hole**\ (\ global_position\: ``Vector3``, enable\: ``bool``\ ) :ref:`🔗` Sets if a hole should be rendered on the control map at the requested position. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_navigation: .. rst-class:: classref-method |void| **set_control_navigation**\ (\ global_position\: ``Vector3``, enable\: ``bool``\ ) :ref:`🔗` Sets if navigation generation is enabled on the control map at the requested position. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_overlay_id: .. rst-class:: classref-method |void| **set_control_overlay_id**\ (\ global_position\: ``Vector3``, texture_id\: ``int``\ ) :ref:`🔗` Sets the overlay texture ID on the control map at the requested position. Values are clamped to 0 - 31, matching the ID of the texture asset in the asset dock. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_control_scale: .. rst-class:: classref-method |void| **set_control_scale**\ (\ global_position\: ``Vector3``, percentage_modifier\: ``float``\ ) :ref:`🔗` Sets the uv scale on the control map at the requested position. The value is rounded to the nearest 20% difference from 100%, ranging between -60% to +80%. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_height: .. rst-class:: classref-method |void| **set_height**\ (\ global_position\: ``Vector3``, height\: ``float``\ ) :ref:`🔗` Sets the height value on the heightmap pixel associated with the specified position. See :ref:`set_pixel()` for important information. Unlike :ref:`get_height()`, which interpolates between vertices, this function does not and will set the pixel at floored coordinates. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_pixel: .. rst-class:: classref-method |void| **set_pixel**\ (\ map_type\: :ref:`MapType`, global_position\: ``Vector3``, pixel\: ``Color``\ ) :ref:`🔗` Sets the pixel for the map type associated with the specified position. This method is fine for setting a few pixels, but if you wish to modify thousands of pixels quickly, you should get the region and use :ref:`Terrain3DRegion.get_map()`, then edit the images directly. After setting pixels you need to call :ref:`update_maps()`. You may also need to regenerate collision if you don't have dynamic collision enabled. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_region_deleted: .. rst-class:: classref-method |void| **set_region_deleted**\ (\ region_location\: ``Vector2i``, deleted\: ``bool``\ ) :ref:`🔗` Marks a region as deleted. It will stop displaying when maps are updated. The file will be removed on save. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_region_modified: .. rst-class:: classref-method |void| **set_region_modified**\ (\ region_location\: ``Vector2i``, modified\: ``bool``\ ) :ref:`🔗` Sets the region as modified. It will be written to disk when saved. Syntactic sugar for :ref:`Terrain3DRegion.modified`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_set_roughness: .. rst-class:: classref-method |void| **set_roughness**\ (\ global_position\: ``Vector3``, roughness\: ``float``\ ) :ref:`🔗` Sets the roughness modifier (wetness) on the color map alpha channel associated with the specified position. See :ref:`set_pixel()` for important information. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DData_method_update_maps: .. rst-class:: classref-method |void| **update_maps**\ (\ map_type\: :ref:`MapType` = 3, all_regions\: ``bool`` = true, generate_mipmaps\: ``bool`` = false\ ) :ref:`🔗` Regenerates the region map and the TextureArrays that combine the requested map types. This function needs to be called after editing any of the maps. By default, this function rebuilds all maps for all regions. - map_type - Regenerate only maps of this type. - all_regions - Regenerate all regions if true, otherwise only those marked with :ref:`Terrain3DRegion.edited`. - generate_mipmaps - Regenerate mipmaps if map_type is color or all (max), for the regions specified above. This can also be done on individual regions before calling this function with ``region.get_color_map().generate_mipmaps()``. For frequent editing, rather than enabling all_regions, it is more optimal to only update changed regions as follows: :: terrain.data.set_height(global_position, 10.0) var region:Terrain3DRegion = terrain.data.get_regionp(global_position) region.set_edited(true) terrain.data.update_maps(Terrain3DRegion.TYPE_HEIGHT, false) region.set_edited(false) .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3deditor.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DEditor.xml. .. _class_Terrain3DEditor: Terrain3DEditor =============== **Inherits:** ``Object`` .. rst-class:: classref-introduction-group Description ----------- This class handles all of the sculpting and painting operations for Terrain3D. .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`apply_undo`\ (\ data\: ``Dictionary``\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`backup_region`\ (\ region\: :ref:`Terrain3DRegion`\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Operation` | :ref:`get_operation`\ (\ ) |const| | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3D` | :ref:`get_terrain`\ (\ ) |const| | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Tool` | :ref:`get_tool`\ (\ ) |const| | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_operating`\ (\ ) |const| | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`operate`\ (\ position\: ``Vector3``, camera_direction\: ``float``\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_brush_data`\ (\ data\: ``Dictionary``\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_operation`\ (\ operation\: :ref:`Operation`\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_terrain`\ (\ terrain\: :ref:`Terrain3D`\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_tool`\ (\ tool\: :ref:`Tool`\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`start_operation`\ (\ position\: ``Vector3``\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`stop_operation`\ (\ ) | +--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DEditor_Operation: .. rst-class:: classref-enumeration enum **Operation**: :ref:`🔗` .. _class_Terrain3DEditor_constant_ADD: .. rst-class:: classref-enumeration-constant :ref:`Operation` **ADD** = ``0`` Additive operations. .. _class_Terrain3DEditor_constant_SUBTRACT: .. rst-class:: classref-enumeration-constant :ref:`Operation` **SUBTRACT** = ``1`` Subtractive operations. .. _class_Terrain3DEditor_constant_REPLACE: .. rst-class:: classref-enumeration-constant :ref:`Operation` **REPLACE** = ``2`` Replacing operations. .. _class_Terrain3DEditor_constant_AVERAGE: .. rst-class:: classref-enumeration-constant :ref:`Operation` **AVERAGE** = ``3`` Averaging operations. .. _class_Terrain3DEditor_constant_GRADIENT: .. rst-class:: classref-enumeration-constant :ref:`Operation` **GRADIENT** = ``4`` Gradient operations. .. _class_Terrain3DEditor_constant_OP_MAX: .. rst-class:: classref-enumeration-constant :ref:`Operation` **OP_MAX** = ``5`` The number of elements in this enum. .. rst-class:: classref-item-separator ---- .. _enum_Terrain3DEditor_Tool: .. rst-class:: classref-enumeration enum **Tool**: :ref:`🔗` .. _class_Terrain3DEditor_constant_SCULPT: .. rst-class:: classref-enumeration-constant :ref:`Tool` **SCULPT** = ``1`` .. container:: contribute There is currently no description for this enum. Please help us by `contributing one `__! .. _class_Terrain3DEditor_constant_HEIGHT: .. rst-class:: classref-enumeration-constant :ref:`Tool` **HEIGHT** = ``2`` Sculpt heights. .. _class_Terrain3DEditor_constant_TEXTURE: .. rst-class:: classref-enumeration-constant :ref:`Tool` **TEXTURE** = ``3`` Paint textures. .. _class_Terrain3DEditor_constant_COLOR: .. rst-class:: classref-enumeration-constant :ref:`Tool` **COLOR** = ``4`` Paint on the color map. .. _class_Terrain3DEditor_constant_ROUGHNESS: .. rst-class:: classref-enumeration-constant :ref:`Tool` **ROUGHNESS** = ``5`` Paint a roughness modifier, aka wetness. .. _class_Terrain3DEditor_constant_ANGLE: .. rst-class:: classref-enumeration-constant :ref:`Tool` **ANGLE** = ``10`` Paint textures rotated by an angle. .. _class_Terrain3DEditor_constant_SCALE: .. rst-class:: classref-enumeration-constant :ref:`Tool` **SCALE** = ``11`` Paint textures scaled by a value. .. _class_Terrain3DEditor_constant_AUTOSHADER: .. rst-class:: classref-enumeration-constant :ref:`Tool` **AUTOSHADER** = ``6`` Paint where the shader automatically textures. .. _class_Terrain3DEditor_constant_HOLES: .. rst-class:: classref-enumeration-constant :ref:`Tool` **HOLES** = ``7`` Paint where vertices will be invalidated to leave holes. .. _class_Terrain3DEditor_constant_NAVIGATION: .. rst-class:: classref-enumeration-constant :ref:`Tool` **NAVIGATION** = ``8`` Paint where navigation will be generated. .. _class_Terrain3DEditor_constant_INSTANCER: .. rst-class:: classref-enumeration-constant :ref:`Tool` **INSTANCER** = ``9`` Paint MultiMesh instances on the ground. .. _class_Terrain3DEditor_constant_REGION: .. rst-class:: classref-enumeration-constant :ref:`Tool` **REGION** = ``0`` Add/remove regions. .. _class_Terrain3DEditor_constant_TOOL_MAX: .. rst-class:: classref-enumeration-constant :ref:`Tool` **TOOL_MAX** = ``12`` The number of elements in this enum. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DEditor_method_apply_undo: .. rst-class:: classref-method |void| **apply_undo**\ (\ data\: ``Dictionary``\ ) :ref:`🔗` Undo the previous changes, with the provided data. Used by Godot, not gamedevs. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_backup_region: .. rst-class:: classref-method |void| **backup_region**\ (\ region\: :ref:`Terrain3DRegion`\ ) :ref:`🔗` Adds a region to the currently pending operation undo snapshot. :ref:`is_operating()` must be true. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_get_operation: .. rst-class:: classref-method :ref:`Operation` **get_operation**\ (\ ) |const| :ref:`🔗` Returns the current selected tool operation (eg. add, subtract). .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_get_terrain: .. rst-class:: classref-method :ref:`Terrain3D` **get_terrain**\ (\ ) |const| :ref:`🔗` Returns the instance of Terrain3D this class is conneced to. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_get_tool: .. rst-class:: classref-method :ref:`Tool` **get_tool**\ (\ ) |const| :ref:`🔗` Returns the current tool selected in the editor plugin. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_is_operating: .. rst-class:: classref-method ``bool`` **is_operating**\ (\ ) |const| :ref:`🔗` Returns true if currently in the middle of a brushing operation. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_operate: .. rst-class:: classref-method |void| **operate**\ (\ position\: ``Vector3``, camera_direction\: ``float``\ ) :ref:`🔗` Start brushing. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_set_brush_data: .. rst-class:: classref-method |void| **set_brush_data**\ (\ data\: ``Dictionary``\ ) :ref:`🔗` Sets all brush settings used in the editor plugin. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_set_operation: .. rst-class:: classref-method |void| **set_operation**\ (\ operation\: :ref:`Operation`\ ) :ref:`🔗` Sets the tool operation used in the editor plugin. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_set_terrain: .. rst-class:: classref-method |void| **set_terrain**\ (\ terrain\: :ref:`Terrain3D`\ ) :ref:`🔗` Sets the instance of Terrain3D this class is connected to. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_set_tool: .. rst-class:: classref-method |void| **set_tool**\ (\ tool\: :ref:`Tool`\ ) :ref:`🔗` Sets the tool selected in the editor plugin. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_start_operation: .. rst-class:: classref-method |void| **start_operation**\ (\ position\: ``Vector3``\ ) :ref:`🔗` Begin a sculpting or painting operation. Prepares to create an undo/redo commit. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DEditor_method_stop_operation: .. rst-class:: classref-method |void| **stop_operation**\ (\ ) :ref:`🔗` End a sculpting or painting operation. Commits any regions marked with :ref:`Terrain3DRegion.edited` in the undo/redo system and clears that flag. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dinstancer.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DInstancer.xml. .. _class_Terrain3DInstancer: Terrain3DInstancer ================== **Inherits:** ``Object`` .. rst-class:: classref-introduction-group Description ----------- This class places mesh instances defined in the Terrain3D asset dock into MultiMeshInstance3Ds on the ground. Data is currently stored in :ref:`Terrain3DRegion.instances` and loaded into MultiMeshInstances, which are attached to the scene tree and managed by this class. \ **The methods available for adding instances are:**\ - :ref:`add_transforms()` - Accepts your list of transforms and parses them by region and cell location and stores in our data storage. Recommended for general API instancing. - :ref:`add_multimesh()` - Pulls the transforms out of your MultiMesh and calls add_transforms. - :ref:`add_instances()` - A feature rich function designed for hand editing via Terrain3DEditor. - Creating your own instance data and inserting it directly into :ref:`Terrain3DRegion.instances`. It's not difficult to do this in GDScript, but a thorough understanding of the C++ code in this class is recommended. \ **The methods available for removing instances are:**\ - :ref:`remove_instances()` - Like add_instances, this is can be used procedurally but is designed for hand editing. - :ref:`clear_by_mesh()`, :ref:`clear_by_location()` - To erase large sections of instances. - Editing :ref:`Terrain3DRegion.instances` directly. After modifying region data, run :ref:`update_mmis()` to rebuild the MultiMeshInstance3Ds. \ **Read More:**\ - **Tutorial:** `Foliage Instancing `__\ - **Godot Reference:** `MultiMesh `__ .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +-------------------------------------------------------------+-----------------------------------------------------+-------+ | :ref:`InstancerMode` | :ref:`mode` | ``1`` | +-------------------------------------------------------------+-----------------------------------------------------+-------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`add_instances`\ (\ global_position\: ``Vector3``, params\: ``Dictionary``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`add_multimesh`\ (\ mesh_id\: ``int``, multimesh\: ``MultiMesh``, transform\: ``Transform3D`` = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0), update\: ``bool`` = true\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`add_transforms`\ (\ mesh_id\: ``int``, transforms\: :ref:`Array`\[``Transform3D``\], colors\: ``PackedColorArray`` = PackedColorArray(), update\: ``bool`` = true\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`append_location`\ (\ region_location\: ``Vector2i``, mesh_id\: ``int``, transforms\: :ref:`Array`\[``Transform3D``\], colors\: ``PackedColorArray``, update\: ``bool`` = true\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`append_region`\ (\ region\: :ref:`Terrain3DRegion`, mesh_id\: ``int``, transforms\: :ref:`Array`\[``Transform3D``\], colors\: ``PackedColorArray``, update\: ``bool`` = true\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear_by_location`\ (\ region_location\: ``Vector2i``, mesh_id\: ``int``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear_by_mesh`\ (\ mesh_id\: ``int``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear_by_region`\ (\ region\: :ref:`Terrain3DRegion`, mesh_id\: ``int``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_closest_mesh_id`\ (\ global_position\: ``Vector3``\ ) |const| | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_enabled`\ (\ ) |const| | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`remove_instances`\ (\ global_position\: ``Vector3``, params\: ``Dictionary``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`swap_ids`\ (\ src_id\: ``int``, dest_id\: ``int``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_mmis`\ (\ mesh_id\: ``int`` = -1, region_location\: ``Vector2i`` = Vector2i(2147483647, 2147483647), rebuild_all\: ``bool`` = false\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_transforms`\ (\ aabb\: ``AABB``\ ) | +----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DInstancer_InstancerMode: .. rst-class:: classref-enumeration enum **InstancerMode**: :ref:`🔗` .. _class_Terrain3DInstancer_constant_NORMAL: .. rst-class:: classref-enumeration-constant :ref:`InstancerMode` **NORMAL** = ``1`` Create MultiMeshInstance3Ds and render instances as normal. .. _class_Terrain3DInstancer_constant_DISABLED: .. rst-class:: classref-enumeration-constant :ref:`InstancerMode` **DISABLED** = ``0`` Disables creation of MultiMeshInstance3Ds and instances. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DInstancer_property_mode: .. rst-class:: classref-property :ref:`InstancerMode` **mode** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_mode**\ (\ value\: :ref:`InstancerMode`\ ) - :ref:`InstancerMode` **get_mode**\ (\ ) Normal - Generates MMIs and renders all instances as normal. Disabled - prevents the instancer from creating any MultiMeshInstance3Ds. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DInstancer_method_add_instances: .. rst-class:: classref-method |void| **add_instances**\ (\ global_position\: ``Vector3``, params\: ``Dictionary``\ ) :ref:`🔗` Used by Terrain3DEditor to place instances given many brush parameters. In addition to the brush position, it also uses the following parameters: asset_id:int, size:float, strength:float, fixed_scale:float, random_scale:float, fixed_spin:float, random_spin:float, fixed_tilt:float, random_tilt:float, align_to_normal:bool, height_offset:float, random_height:float, vertex_color:Color, random_hue:float, random_darken:float, slope:Vector2, on_collision:bool, raycast_height:float. All of these settings are set in the editor via tool_settings.gd. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_add_multimesh: .. rst-class:: classref-method |void| **add_multimesh**\ (\ mesh_id\: ``int``, multimesh\: ``MultiMesh``, transform\: ``Transform3D`` = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0), update\: ``bool`` = true\ ) :ref:`🔗` Allows procedural placement of meshes, or importing from another MultiMeshInstancer placement tool. The specified mesh_id should already be setup as a :ref:`Terrain3DMeshAsset` in the asset dock. This function extracts the instance transforms and colors from a multimesh and passes it to :ref:`add_transforms()`. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_add_transforms: .. rst-class:: classref-method |void| **add_transforms**\ (\ mesh_id\: ``int``, transforms\: :ref:`Array`\[``Transform3D``\], colors\: ``PackedColorArray`` = PackedColorArray(), update\: ``bool`` = true\ ) :ref:`🔗` Allows procedural placement of meshes. The mesh_id should already be setup as a :ref:`Terrain3DMeshAsset` in the asset dock. You provide the array of Transform3Ds and optional Colors, which will be parsed into our data storage. This function adds the :ref:`Terrain3DMeshAsset.height_offset` to the transform along its local Y axis. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_append_location: .. rst-class:: classref-method |void| **append_location**\ (\ region_location\: ``Vector2i``, mesh_id\: ``int``, transforms\: :ref:`Array`\[``Transform3D``\], colors\: ``PackedColorArray``, update\: ``bool`` = true\ ) :ref:`🔗` Appends new transforms to the existing data within a region location. The mesh_id should already be setup as a :ref:`Terrain3DMeshAsset` in the asset dock. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_append_region: .. rst-class:: classref-method |void| **append_region**\ (\ region\: :ref:`Terrain3DRegion`, mesh_id\: ``int``, transforms\: :ref:`Array`\[``Transform3D``\], colors\: ``PackedColorArray``, update\: ``bool`` = true\ ) :ref:`🔗` Appends new transforms to the existing data within a region location. The mesh_id should already be setup as a :ref:`Terrain3DMeshAsset` in the asset dock. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_clear_by_location: .. rst-class:: classref-method |void| **clear_by_location**\ (\ region_location\: ``Vector2i``, mesh_id\: ``int``\ ) :ref:`🔗` Removes all instancer data and MultiMeshInstance nodes attached to the tree for the specified region location and mesh id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_clear_by_mesh: .. rst-class:: classref-method |void| **clear_by_mesh**\ (\ mesh_id\: ``int``\ ) :ref:`🔗` Removes all instancer data and MultiMeshInstance nodes attached to the tree for all regions for the specified mesh id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_clear_by_region: .. rst-class:: classref-method |void| **clear_by_region**\ (\ region\: :ref:`Terrain3DRegion`, mesh_id\: ``int``\ ) :ref:`🔗` Removes all instancer data and MultiMeshInstance nodes attached to the tree for the specified region and mesh id. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_get_closest_mesh_id: .. rst-class:: classref-method ``int`` **get_closest_mesh_id**\ (\ global_position\: ``Vector3``\ ) |const| :ref:`🔗` Returns the mesh instance ID closest to the specified global position on the ground. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_is_enabled: .. rst-class:: classref-method ``bool`` **is_enabled**\ (\ ) |const| :ref:`🔗` Returns true if :ref:`mode` is not disabled. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_remove_instances: .. rst-class:: classref-method |void| **remove_instances**\ (\ global_position\: ``Vector3``, params\: ``Dictionary``\ ) :ref:`🔗` Uses parameters asset_id:int, size:float, strength:float, slope:Vector2, on_collision:bool, raycast_height:float to randomly remove instances within the indicated brush position and size. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_swap_ids: .. rst-class:: classref-method |void| **swap_ids**\ (\ src_id\: ``int``, dest_id\: ``int``\ ) :ref:`🔗` Swaps the ID of two meshes without changing the mesh instances on the ground. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_update_mmis: .. rst-class:: classref-method |void| **update_mmis**\ (\ mesh_id\: ``int`` = -1, region_location\: ``Vector2i`` = Vector2i(2147483647, 2147483647), rebuild_all\: ``bool`` = false\ ) :ref:`🔗` Queues a request to rebuild the MMIs for the specified IDs and regions on the next RenderingServer.frame_pre_draw signal. This is safe to call multiple times per frame and it will de-duplicate and do the most general requests. So if you first call it for region (0,0), mesh 52, then later in the frame ask for all regions, mesh 52, it will do only the latter. - mesh_id - rebuild MMIs for this mesh id. Use `-1` for all IDs. - region_location - rebuild MMIs for this region. Use `Vector2i.MAX` for all regions. - rebuild_all - destroy all MMIs first before rebuilding. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DInstancer_method_update_transforms: .. rst-class:: classref-method |void| **update_transforms**\ (\ aabb\: ``AABB``\ ) :ref:`🔗` Reviews all existing instance transforms within an AABB and adjusts their heights to match the terrain. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dmaterial.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DMaterial.xml. .. _class_Terrain3DMaterial: Terrain3DMaterial ================= **Inherits:** ``Resource`` A custom shader material resource for Terrain3D. .. rst-class:: classref-introduction-group Description ----------- This class handles options for both the built-in shader and any custom override shader. It collects compiled texture data from the other classes and sends all of it to the shader via the RenderingServer. It is a savable resource, so you can save it to disk and use the same material settings in multiple scenes that use Terrain3D. The amount of data is small, assuming you have saved your shader parameter textures to disk, so it can be saved as a git-friendly, text based .tres file or left within the scene file. While it does mimic some of the functionality of ShaderMaterial, it does not derive from any of the Godot Material classes. It will fail any ``is Material`` checks. It is a ``Resource``. Inspector settings above `Custom Shader` and :ref:`shader_override` are used to determine what code is used in the current shader. Inspector settings in `Shader Uniforms` are the public uniforms (not prefaced with `\_`) available in the current shader. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``Dictionary`` | :ref:`_shader_parameters` | ``{}`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`auto_shader_enabled` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``Shader`` | :ref:`buffer_shader_override` | | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`buffer_shader_override_enabled` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``float`` | :ref:`displacement_scale` | ``1.0`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``float`` | :ref:`displacement_sharpness` | ``0.5`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`dual_scaling_enabled` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`macro_variation_enabled` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`output_albedo` | ``true`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`output_ambient_occlusion` | ``true`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`output_normal_map` | ``true`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`output_roughness` | ``true`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`projection_enabled` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``Shader`` | :ref:`shader_override` | | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`shader_override_enabled` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_autoshader` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_checkered` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_colormap` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_contours` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_control_angle` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_control_blend` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_control_scale` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_control_texture` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_displacement_buffer` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_grey` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_heightmap` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_instancer_grid` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_jaggedness` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_navigation` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_region_grid` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_roughmap` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_texture_albedo` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_texture_ao` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_texture_height` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_texture_normal` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_texture_rough` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | ``bool`` | :ref:`show_vertex_grid` | ``false`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | :ref:`TextureFiltering` | :ref:`texture_filtering` | ``0`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ | :ref:`WorldBackground` | :ref:`world_background` | ``1`` | +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +-------------+----------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_buffer_material_rid`\ (\ ) |const| | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_buffer_shader_rid`\ (\ ) |const| | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_material_rid`\ (\ ) |const| | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | ``Variant`` | :ref:`get_shader_param`\ (\ name\: ``StringName``\ ) |const| | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | ``RID`` | :ref:`get_shader_rid`\ (\ ) |const| | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | Error | :ref:`save`\ (\ path\: ``String`` = ""\ ) | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_shader_param`\ (\ name\: ``StringName``, value\: ``Variant``\ ) | +-------------+----------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update`\ (\ flags\: ``int`` = 0\ ) | +-------------+----------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DMaterial_WorldBackground: .. rst-class:: classref-enumeration enum **WorldBackground**: :ref:`🔗` .. _class_Terrain3DMaterial_constant_NONE: .. rst-class:: classref-enumeration-constant :ref:`WorldBackground` **NONE** = ``0`` Outside of the defined regions, hide the mesh. .. _class_Terrain3DMaterial_constant_FLAT: .. rst-class:: classref-enumeration-constant :ref:`WorldBackground` **FLAT** = ``1`` Outside of the defined regions, show a flat terrain. .. _class_Terrain3DMaterial_constant_NOISE: .. rst-class:: classref-enumeration-constant :ref:`WorldBackground` **NOISE** = ``2`` Outside of the defined regions, generate visual-only hills. .. rst-class:: classref-item-separator ---- .. _enum_Terrain3DMaterial_TextureFiltering: .. rst-class:: classref-enumeration enum **TextureFiltering**: :ref:`🔗` .. _class_Terrain3DMaterial_constant_LINEAR_ANISOTROPIC: .. rst-class:: classref-enumeration-constant :ref:`TextureFiltering` **LINEAR_ANISOTROPIC** = ``0`` Textures are filtered using a blend of 4 adjacent pixels, with anisotropic filtering which improves the sharpness of distant terrain off axis from the camera. Use this for most cases for high quality renders. .. _class_Terrain3DMaterial_constant_LINEAR: .. rst-class:: classref-enumeration-constant :ref:`TextureFiltering` **LINEAR** = ``1`` Textures are filtered using a blend of 4 adjacent pixels. Use this for most cases for high quality renders. .. _class_Terrain3DMaterial_constant_NEAREST_ANISOTROPIC: .. rst-class:: classref-enumeration-constant :ref:`TextureFiltering` **NEAREST_ANISOTROPIC** = ``2`` Textures are filtered using the nearest pixel only with anisotropic filtering which improves the sharpness of distant terrain off axis from the camera. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale. .. _class_Terrain3DMaterial_constant_NEAREST: .. rst-class:: classref-enumeration-constant :ref:`TextureFiltering` **NEAREST** = ``3`` Textures are filtered using the nearest pixel only. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale. .. rst-class:: classref-item-separator ---- .. _enum_Terrain3DMaterial_UpdateFlags: .. rst-class:: classref-enumeration enum **UpdateFlags**: :ref:`🔗` .. _class_Terrain3DMaterial_constant_UNIFORMS_ONLY: .. rst-class:: classref-enumeration-constant :ref:`UpdateFlags` **UNIFORMS_ONLY** = ``0`` Non-texture array values are assigned to the shader. This is the default and is always done. .. _class_Terrain3DMaterial_constant_TEXTURE_ARRAYS: .. rst-class:: classref-enumeration-constant :ref:`UpdateFlags` **TEXTURE_ARRAYS** = ``1`` The ground texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`. .. _class_Terrain3DMaterial_constant_REGION_ARRAYS: .. rst-class:: classref-enumeration-constant :ref:`UpdateFlags` **REGION_ARRAYS** = ``2`` The region data texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`. .. _class_Terrain3DMaterial_constant_UPDATE_ARRAYS: .. rst-class:: classref-enumeration-constant :ref:`UpdateFlags` **UPDATE_ARRAYS** = ``3`` Values in `TEXTURE_ARRAYS` and `REGION_ARRAYS` are assigned to the shader. .. _class_Terrain3DMaterial_constant_FULL_REBUILD: .. rst-class:: classref-enumeration-constant :ref:`UpdateFlags` **FULL_REBUILD** = ``7`` The shader is rebuilt, then all values in `UPDATE_ARRAYS` are assigned to the shader. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DMaterial_property__shader_parameters: .. rst-class:: classref-property ``Dictionary`` **_shader_parameters** = ``{}`` :ref:`🔗` This private dictionary stores all of the shader parameters in the resource. It is not a cache. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_auto_shader_enabled: .. rst-class:: classref-property ``bool`` **auto_shader_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_auto_shader_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_auto_shader_enabled**\ (\ ) Enables selecting two texture IDs that will automatically be applied to the terrain based upon slope. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_buffer_shader_override: .. rst-class:: classref-property ``Shader`` **buffer_shader_override** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_buffer_shader_override**\ (\ value\: ``Shader``\ ) - ``Shader`` **get_buffer_shader_override**\ (\ ) If buffer_shader_override_enabled is true and this Shader is valid, the displacement buffer material will use this custom shader code. If this is blank when you enable the override, the system will generate a shader with the current settings. A visual shader will also work here. However we only generate a text based shader so currently a visual shader needs to be constructed with the base code before it can work. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_buffer_shader_override_enabled: .. rst-class:: classref-property ``bool`` **buffer_shader_override_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_buffer_shader_override_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **is_buffer_shader_override_enabled**\ (\ ) Enables use of the :ref:`buffer_shader_override` shader code. Generates default code if shader_override is blank. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_displacement_scale: .. rst-class:: classref-property ``float`` **displacement_scale** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_displacement_scale**\ (\ value\: ``float``\ ) - ``float`` **get_displacement_scale**\ (\ ) A global multiplier for all displaced textures. This is the maximum distance that 2 adjacent verticies can be vertically seperated by. Setting this 1.0 would mean a maximum of + 0.5m, and -0.5m deviation from the collision mesh. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_displacement_sharpness: .. rst-class:: classref-property ``float`` **displacement_sharpness** = ``0.5`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_displacement_sharpness**\ (\ value\: ``float``\ ) - ``float`` **get_displacement_sharpness**\ (\ ) Adjusts the transition between textures. When set at `1.0`, the blending of displacment between textures will match the aldebo/normal blend sharpness exactly. Lower values will have a softer transition, avoiding harsh shapes, without compromising the abldeo and normal blend sharpness. If set at or very near to `0.0`, it is possible that more displaced textures can affect less displaced textures at low blend values even if not visible. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_dual_scaling_enabled: .. rst-class:: classref-property ``bool`` **dual_scaling_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_dual_scaling_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_dual_scaling_enabled**\ (\ ) Enables selecting one texture ID that will have multiple scales applied based upon camera distance. Use it for something like a rock texture so up close it will be nicely detailed, and far away mountains can be covered in the same rock texture without looking tiled. The two blend together at a specified distance. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_macro_variation_enabled: .. rst-class:: classref-property ``bool`` **macro_variation_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_macro_variation_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_macro_variation_enabled**\ (\ ) Allows you to add a couple of noise patterns at different scales and colors to add variation to your terrain to avoid tiled textures. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_output_albedo: .. rst-class:: classref-property ``bool`` **output_albedo** = ``true`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_output_albedo_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_output_albedo_enabled**\ (\ ) Enables the Albedo, aka Base Color or Diffuse, output channel in the shader. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_output_ambient_occlusion: .. rst-class:: classref-property ``bool`` **output_ambient_occlusion** = ``true`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_output_ambient_occlusion_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_output_ambient_occlusion_enabled**\ (\ ) Enables the Ambient Occlusion output channel in the shader. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_output_normal_map: .. rst-class:: classref-property ``bool`` **output_normal_map** = ``true`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_output_normal_map_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_output_normal_map_enabled**\ (\ ) Enables the Normal Map output channel in the shader. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_output_roughness: .. rst-class:: classref-property ``bool`` **output_roughness** = ``true`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_output_roughness_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_output_roughness_enabled**\ (\ ) Enables the Roughness output channel in the shader. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_projection_enabled: .. rst-class:: classref-property ``bool`` **projection_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_projection_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **get_projection_enabled**\ (\ ) Enables textures to be projected vertically when placed on slopes above 45 degrees. This is useful for mapping textures on cliff faces without stretching, even though the polygons are stretched. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_shader_override: .. rst-class:: classref-property ``Shader`` **shader_override** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_shader_override**\ (\ value\: ``Shader``\ ) - ``Shader`` **get_shader_override**\ (\ ) If shader_override_enabled is true and this Shader is valid, the material will use this custom shader code. If this is blank when you enable the override, the system will generate a shader with the current settings. So if you have a debug view enabled, the generated shader will have all of that code. A visual shader will also work here. However we only generate a text based shader so currently a visual shader needs to be constructed with the base code before it can work. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_shader_override_enabled: .. rst-class:: classref-property ``bool`` **shader_override_enabled** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_shader_override_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **is_shader_override_enabled**\ (\ ) Enables using the :ref:`shader_override` shader. An editable shader is generated from the current one if shader_override is blank. The inspector settings above this group determine the code that is used in the current shader. The settings below are uniforms for the current shader. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_autoshader: .. rst-class:: classref-property ``bool`` **show_autoshader** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_autoshader**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_autoshader**\ (\ ) Displays the area designated for use by the autoshader, which shows materials based upon slope. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_checkered: .. rst-class:: classref-property ``bool`` **show_checkered** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_checkered**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_checkered**\ (\ ) Shows a checkerboard display using a shader rendered pattern. This is turned on if the Texture List is empty. Note that when a blank texture slot is created, a 1k checkerboard texture is generated and stored in the texture slot. That takes VRAM. The two patterns have a slightly different scale. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_colormap: .. rst-class:: classref-property ``bool`` **show_colormap** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_colormap**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_colormap**\ (\ ) Shows the color map in the albedo channel. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_contours: .. rst-class:: classref-property ``bool`` **show_contours** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_contours**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_contours**\ (\ ) Overlays contour lines on the terrain. Customize the options in the material when enabled. Press `4` with the mouse in the viewport to toggle. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_control_angle: .. rst-class:: classref-property ``bool`` **show_control_angle** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_angle**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_angle**\ (\ ) Albedo shows the painted angle. Orange means 0°, Yellow 270°, Cyan 180°, Violet 90°. Or warm colors towards -Z, cool colors +Z, greens/yellows +X, reds/blues -X. Draw all angles coming from the center of a circle for a better understanding. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_control_blend: .. rst-class:: classref-property ``bool`` **show_control_blend** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_blend**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_blend**\ (\ ) Displays the values used to blend the textures. Blue shows the autoshader blending, red shows manually painted blending. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_control_scale: .. rst-class:: classref-property ``bool`` **show_control_scale** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_scale**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_scale**\ (\ ) Albedo shows the painted scale. Larger scales are more red, smaller scales are more blue. 0.5 middle grey is the default 100% scale. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_control_texture: .. rst-class:: classref-property ``bool`` **show_control_texture** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_control_texture**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_control_texture**\ (\ ) Albedo shows the base and overlay texture indices defined by the control map. Red pixels indicate the base texture, with brightness showing texture ids 0 to 31. Green pixels indicate the overlay texture. Yellow indicates both. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_displacement_buffer: .. rst-class:: classref-property ``bool`` **show_displacement_buffer** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_displacement_buffer**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_displacement_buffer**\ (\ ) Shows the resulting displacement buffer vertex differential from 0. Black matches collision exactly. Green shows extrusions, Red for depressions into the terrain. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_grey: .. rst-class:: classref-property ``bool`` **show_grey** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_grey**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_grey**\ (\ ) Albedo is set to 0.2 grey. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_heightmap: .. rst-class:: classref-property ``bool`` **show_heightmap** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_heightmap**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_heightmap**\ (\ ) Albedo is a white to black gradient depending on height. The gradient is scaled to a height of 300, so above that or far below 0 will be all white or black. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_instancer_grid: .. rst-class:: classref-property ``bool`` **show_instancer_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_instancer_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_instancer_grid**\ (\ ) Overlays the 32x32m instancer grid on the terrain, which shows how the instancer data is partitioned. Press `2` with the mouse in the viewport to toggle. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_jaggedness: .. rst-class:: classref-property ``bool`` **show_jaggedness** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_jaggedness**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_jaggedness**\ (\ ) Highlights non-smooth areas of the terrain. Jagged peaks, troughs, or edges that are a bit rough with sharp angles between vertices. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_navigation: .. rst-class:: classref-property ``bool`` **show_navigation** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_navigation**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_navigation**\ (\ ) Displays the area designated for generating the navigation mesh. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_region_grid: .. rst-class:: classref-property ``bool`` **show_region_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_region_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_region_grid**\ (\ ) Overlays the region grid on the terrain. This is more accurate than the region grid gizmo for determining where the region border is when editing. Press `1` with the mouse in the viewport to toggle. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_roughmap: .. rst-class:: classref-property ``bool`` **show_roughmap** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_roughmap**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_roughmap**\ (\ ) Albedo is set to the roughness modification map as grey scale. Middle grey, 0.5 means no roughness modification. Black would be high gloss while white is very rough. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_texture_albedo: .. rst-class:: classref-property ``bool`` **show_texture_albedo** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_albedo**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_albedo**\ (\ ) Albedo textures are shown only. Other channels are excluded. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_texture_ao: .. rst-class:: classref-property ``bool`` **show_texture_ao** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_ao**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_ao**\ (\ ) Albedo is set to the painted Ambient Occlusion textures. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_texture_height: .. rst-class:: classref-property ``bool`` **show_texture_height** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_height**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_height**\ (\ ) Albedo is set to the painted Height textures. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_texture_normal: .. rst-class:: classref-property ``bool`` **show_texture_normal** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_normal**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_normal**\ (\ ) Albedo is set to the painted Normal textures. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_texture_rough: .. rst-class:: classref-property ``bool`` **show_texture_rough** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_texture_rough**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_texture_rough**\ (\ ) Albedo is set to the painted Roughness textures. This is different from the roughness modification map above. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_show_vertex_grid: .. rst-class:: classref-property ``bool`` **show_vertex_grid** = ``false`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_show_vertex_grid**\ (\ value\: ``bool``\ ) - ``bool`` **get_show_vertex_grid**\ (\ ) Overlays the vertex grid on the terrain, showing where each vertex is. Press `3` with the mouse in the viewport to toggle. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_texture_filtering: .. rst-class:: classref-property :ref:`TextureFiltering` **texture_filtering** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_texture_filtering**\ (\ value\: :ref:`TextureFiltering`\ ) - :ref:`TextureFiltering` **get_texture_filtering**\ (\ ) Sets how the renderer should filter textures. See :ref:`TextureFiltering` for options. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_property_world_background: .. rst-class:: classref-property :ref:`WorldBackground` **world_background** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_world_background**\ (\ value\: :ref:`WorldBackground`\ ) - :ref:`WorldBackground` **get_world_background**\ (\ ) Sets how the mesh outside of defined regions behave. See :ref:`WorldBackground` for options. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DMaterial_method_get_buffer_material_rid: .. rst-class:: classref-method ``RID`` **get_buffer_material_rid**\ (\ ) |const| :ref:`🔗` Returns the RID of the displacement buffer material used with the Rendering Server. This is set per instance of this class. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_get_buffer_shader_rid: .. rst-class:: classref-method ``RID`` **get_buffer_shader_rid**\ (\ ) |const| :ref:`🔗` Returns the RID of the displacement buffer shader used with the Rendering Server. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_get_material_rid: .. rst-class:: classref-method ``RID`` **get_material_rid**\ (\ ) |const| :ref:`🔗` Returns the RID of the material used with the Rendering Server. This is set per instance of this class. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_get_shader_param: .. rst-class:: classref-method ``Variant`` **get_shader_param**\ (\ name\: ``StringName``\ ) |const| :ref:`🔗` Retrieve a parameter from the active shader (built-in or override shader). .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_get_shader_rid: .. rst-class:: classref-method ``RID`` **get_shader_rid**\ (\ ) |const| :ref:`🔗` Returns the RID of the built in shader used with the Rendering Server. This is different from any shader override which has its own RID. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_save: .. rst-class:: classref-method Error **save**\ (\ path\: ``String`` = ""\ ) :ref:`🔗` Saves this material resource to disk, if saved as an external ``.tres`` or ``.res`` resource file. path - specifies a directory and file name to use from now on. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_set_shader_param: .. rst-class:: classref-method |void| **set_shader_param**\ (\ name\: ``StringName``, value\: ``Variant``\ ) :ref:`🔗` Set a parameter in the active shader (built-in or override shader). .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMaterial_method_update: .. rst-class:: classref-method |void| **update**\ (\ flags\: ``int`` = 0\ ) :ref:`🔗` Sends uniform values to the shader. See :ref:`UpdateFlags` for options. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dmeshasset.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DMeshAsset.xml. .. _class_Terrain3DMeshAsset: Terrain3DMeshAsset ================== **Inherits:** ``Resource`` .. rst-class:: classref-introduction-group Description ----------- This class manages meshes used for instancing. There are two broad types of meshes it can host. 1. Generated Texture Card - this class will generate a QuadMesh. The typical use for this is to create a material in the override material, place a 2D grass texture in the `albedo texture` slot, and enable alpha scissor. This will generate low poly grass. 2. Scene File - you can provide your own mesh in a scene file, which is specifically a PackedScene (.tscn, .scn, .glb, .fbx, etc). You can override the material if desired. MultiMeshes only support one mesh object, so complex objects like tree trunks and leaves, or a door frame and door either need to be combined into one object with multiple materials, or placed by another method. The system will look for MeshInstance3D nodes in the file to use as Levels of Detail (LODs). Ideally they have suffixes like `LOD0`, `LOD1`. We support up to 10 LODs, but recommend no more than 4. \ **Read More:**\ - **Tutorial:** `Foliage Instancing `__\ - **Godot Reference:** `MultiMesh `__, `MultiMeshInstance3D `__ .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | RenderingServer.ShadowCastingSetting | :ref:`cast_shadows` | ``1`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`density` | ``0.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``bool`` | :ref:`enabled` | ``true`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`fade_margin` | ``0.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`generated_faces` | ``2`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``Vector2`` | :ref:`generated_size` | ``Vector2(1, 1)`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | :ref:`GenType` | :ref:`generated_type` | ``0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`height_offset` | ``0.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`id` | ``0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`last_lod` | ``9`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`last_shadow_lod` | ``9`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod0_range` | ``32.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod1_range` | ``64.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod2_range` | ``96.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod3_range` | ``128.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod4_range` | ``160.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod5_range` | ``192.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod6_range` | ``224.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod7_range` | ``256.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod8_range` | ``288.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`lod9_range` | ``320.0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`lod_count` | ``0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``Material`` | :ref:`material_overlay` | | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``Material`` | :ref:`material_override` | | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``String`` | :ref:`name` | ``"New Mesh"`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``PackedScene`` | :ref:`scene_file` | | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`shadow_impostor` | ``0`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`visibility_layers` | ``1`` | +-------------------------------------------------+-------------------------------------------------------------------------------+-------------------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +---------------+----------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear`\ (\ ) | +---------------+----------------------------------------------------------------------------------------------------------------+ | ``Color`` | :ref:`get_highlight_color`\ (\ ) |const| | +---------------+----------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_instance_count`\ (\ ) |const| | +---------------+----------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`get_lod_range`\ (\ lod\: ``int``\ ) |const| | +---------------+----------------------------------------------------------------------------------------------------------------+ | ``Mesh`` | :ref:`get_mesh`\ (\ lod\: ``int`` = 0\ ) |const| | +---------------+----------------------------------------------------------------------------------------------------------------+ | ``Texture2D`` | :ref:`get_thumbnail`\ (\ ) |const| | +---------------+----------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_highlighted`\ (\ ) |const| | +---------------+----------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_highlighted`\ (\ enabled\: ``bool``\ ) | +---------------+----------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_lod_range`\ (\ lod\: ``int``, distance\: ``float``\ ) | +---------------+----------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Signals ------- .. _class_Terrain3DMeshAsset_signal_id_changed: .. rst-class:: classref-signal **id_changed**\ (\ ) :ref:`🔗` Emitted when :ref:`id` is changed. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_signal_instance_count_changed: .. rst-class:: classref-signal **instance_count_changed**\ (\ ) :ref:`🔗` Emitted when instances of this mesh asset have been added or removed. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_signal_instancer_setting_changed: .. rst-class:: classref-signal **instancer_setting_changed**\ (\ ) :ref:`🔗` Emitted when instancer specific settings are changed on this mesh asset, such as :ref:`cast_shadows`, and triggers an instancer rebuild. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_signal_setting_changed: .. rst-class:: classref-signal **setting_changed**\ (\ ) :ref:`🔗` Emitted when settings are changed, other than those tracked by other signals. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DMeshAsset_GenType: .. rst-class:: classref-enumeration enum **GenType**: :ref:`🔗` .. _class_Terrain3DMeshAsset_constant_TYPE_NONE: .. rst-class:: classref-enumeration-constant :ref:`GenType` **TYPE_NONE** = ``0`` No generated mesh is in use, likely because a scene file is used. .. _class_Terrain3DMeshAsset_constant_TYPE_TEXTURE_CARD: .. rst-class:: classref-enumeration-constant :ref:`GenType` **TYPE_TEXTURE_CARD** = ``1`` Generates a QuadMesh to be used as a texture card. .. _class_Terrain3DMeshAsset_constant_TYPE_MAX: .. rst-class:: classref-enumeration-constant :ref:`GenType` **TYPE_MAX** = ``2`` Maximum value for this enum. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DMeshAsset_property_cast_shadows: .. rst-class:: classref-property RenderingServer.ShadowCastingSetting **cast_shadows** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_cast_shadows**\ (\ value\: RenderingServer.ShadowCastingSetting\ ) - RenderingServer.ShadowCastingSetting **get_cast_shadows**\ (\ ) Tells the renderer how to cast shadows from this mesh asset onto the terrain and other objects. This sets ``GeometryInstance3D.cast_shadow`` on all MultiMeshInstances used by this mesh. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_density: .. rst-class:: classref-property ``float`` **density** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_density**\ (\ value\: ``float``\ ) - ``float`` **get_density**\ (\ ) Density is used to set the approximate default spacing between instances based on the size of the mesh. When painting meshes on the terrain, mesh density is multiplied by brush strength. This value is not tied to any real world unit. It is calculated as ``10.f / mesh->get_aabb().get_volume()``, then clamped to a sane range. If the calculated amount is inappropriate, increase or decrease it here. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_enabled: .. rst-class:: classref-property ``bool`` **enabled** = ``true`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_enabled**\ (\ value\: ``bool``\ ) - ``bool`` **is_enabled**\ (\ ) If enabled, MultiMeshInstance3Ds will be generated for this mesh asset. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_fade_margin: .. rst-class:: classref-property ``float`` **fade_margin** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_fade_margin**\ (\ value\: ``float``\ ) - ``float`` **get_fade_margin**\ (\ ) If > 0, sets ``GeometryInstance3D.fade_mode = self``, sets this margin in ``GeometryInstance3D.visibility_range_begin_margin`` and ``GeometryInstance3D.visibility_range_end_margin`` and adjusts the ranges to allow fading between lods. Limited to the smaller of half the distance between LOD0 and LOD1, or 64m. Currently broken and hidden in the inspector until `Godot issue #102799 `__ is fixed. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_generated_faces: .. rst-class:: classref-property ``int`` **generated_faces** = ``2`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_generated_faces**\ (\ value\: ``int``\ ) - ``int`` **get_generated_faces**\ (\ ) Select if you want the generated texture card to have a single QuadMesh, 2 meshes rotated 90° in a cross, or 3 rotated at 60°. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_generated_size: .. rst-class:: classref-property ``Vector2`` **generated_size** = ``Vector2(1, 1)`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_generated_size**\ (\ value\: ``Vector2``\ ) - ``Vector2`` **get_generated_size**\ (\ ) Sets the base size of the QuadMesh texture card. Increasing this size will expand from bottom, not the middle. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_generated_type: .. rst-class:: classref-property :ref:`GenType` **generated_type** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_generated_type**\ (\ value\: :ref:`GenType`\ ) - :ref:`GenType` **get_generated_type**\ (\ ) If enabled, this mesh asset will be set to a generated QuadMesh to be used as a texture card. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_height_offset: .. rst-class:: classref-property ``float`` **height_offset** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_height_offset**\ (\ value\: ``float``\ ) - ``float`` **get_height_offset**\ (\ ) Vertically offsets the origin point of the mesh asset. For example, if you have a 2 meter diameter rock with the mesh origin point in the center, but you want all rocks to be sitting on the ground, you could enter 1 or 0.9 here and it will be placed near its edge. You can also adjust this when painting using the tool settings bar; both options are cummulative. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_id: .. rst-class:: classref-property ``int`` **id** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_id**\ (\ value\: ``int``\ ) - ``int`` **get_id**\ (\ ) The user settable ID of the mesh. You can change this to reorder meshes in the list. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_last_lod: .. rst-class:: classref-property ``int`` **last_lod** = ``9`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_last_lod**\ (\ value\: ``int``\ ) - ``int`` **get_last_lod**\ (\ ) Sets the farthest Level of Detail (LOD) that will be rendered. Farther LODs (greater than this number) will be ignored. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_last_shadow_lod: .. rst-class:: classref-property ``int`` **last_shadow_lod** = ``9`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_last_shadow_lod**\ (\ value\: ``int``\ ) - ``int`` **get_last_shadow_lod**\ (\ ) Sets the farthest Level of Detail (LOD) that will cast shadows. Farther LODs (greater than this number) won't cast shadows. If :ref:`shadow_impostor` is greater than this number, the shadow impostor will still cast shadows. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod0_range: .. rst-class:: classref-property ``float`` **lod0_range** = ``32.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod0_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod0_range**\ (\ ) Sets ``GeometryInstance3D.visibility_range_end`` on all MultiMeshInstances used by this mesh at the closest Level of Detail (LOD), the most detailed. Allows the renderer to cull MMIs beyond this distance. The next LOD level will use this value for its ``GeometryInstance3D.visibility_range_begin``. Set to 0 to disable culling and see this LOD at any distance. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod1_range: .. rst-class:: classref-property ``float`` **lod1_range** = ``64.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod1_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod1_range**\ (\ ) Sets the end visible range for LOD1. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod2_range: .. rst-class:: classref-property ``float`` **lod2_range** = ``96.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod2_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod2_range**\ (\ ) Sets the end visible range for LOD2. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod3_range: .. rst-class:: classref-property ``float`` **lod3_range** = ``128.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod3_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod3_range**\ (\ ) Sets the end visible range for LOD3. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod4_range: .. rst-class:: classref-property ``float`` **lod4_range** = ``160.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod4_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod4_range**\ (\ ) Sets the end visible range for LOD4. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod5_range: .. rst-class:: classref-property ``float`` **lod5_range** = ``192.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod5_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod5_range**\ (\ ) Sets the end visible range for LOD5. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod6_range: .. rst-class:: classref-property ``float`` **lod6_range** = ``224.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod6_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod6_range**\ (\ ) Sets the end visible range for LOD6. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod7_range: .. rst-class:: classref-property ``float`` **lod7_range** = ``256.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod7_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod7_range**\ (\ ) Sets the end visible range for LOD7. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod8_range: .. rst-class:: classref-property ``float`` **lod8_range** = ``288.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod8_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod8_range**\ (\ ) Sets the end visible range for LOD8. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod9_range: .. rst-class:: classref-property ``float`` **lod9_range** = ``320.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_lod9_range**\ (\ value\: ``float``\ ) - ``float`` **get_lod9_range**\ (\ ) Sets the end visible range for LOD9. See :ref:`lod0_range`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_lod_count: .. rst-class:: classref-property ``int`` **lod_count** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - ``int`` **get_lod_count**\ (\ ) Provides the detected number of Levels of Detail (LODs) found in the provided scene file or generated mesh. Read only. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_material_overlay: .. rst-class:: classref-property ``Material`` **material_overlay** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_material_overlay**\ (\ value\: ``Material``\ ) - ``Material`` **get_material_overlay**\ (\ ) This sets ``GeometryInstance3D.material_overlay``, which applies an overriding material using ``next_pass`` that overlays the base material. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_material_override: .. rst-class:: classref-property ``Material`` **material_override** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_material_override**\ (\ value\: ``Material``\ ) - ``Material`` **get_material_override**\ (\ ) This sets ``GeometryInstance3D.material_override``, which replaces the rendered mesh material with this one. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_name: .. rst-class:: classref-property ``String`` **name** = ``"New Mesh"`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_name**\ (\ value\: ``String``\ ) - ``String`` **get_name**\ (\ ) The user specified name for this asset. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_scene_file: .. rst-class:: classref-property ``PackedScene`` **scene_file** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_scene_file**\ (\ value\: ``PackedScene``\ ) - ``PackedScene`` **get_scene_file**\ (\ ) Specifies the PackedScene (.tscn, .scn, .glb, .fbx, etc) to load the mesh from. See the top description. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_shadow_impostor: .. rst-class:: classref-property ``int`` **shadow_impostor** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_shadow_impostor**\ (\ value\: ``int``\ ) - ``int`` **get_shadow_impostor**\ (\ ) Uses this lower quality Level of Detail (LOD) to calculate shadows (as an impostor) instead of the visible mesh. e.g. Normally each LOD casts its own shadows. Given LOD0-3, if ``shadow_impostor = 2`` then when LOD0-1 are visible, they are set to no shadows and LOD2 is set to cast shadows only. When LOD2-3 are visible, each casts their own shadow as normal. Increase to improve performance by lowering shadow quality. Shadow impostors are disabled if this is set to 0 or if :ref:`cast_shadows` is set to shadows only. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_property_visibility_layers: .. rst-class:: classref-property ``int`` **visibility_layers** = ``1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_visibility_layers**\ (\ value\: ``int``\ ) - ``int`` **get_visibility_layers**\ (\ ) Sets :ref:`VisualInstance3D.layers`, which defines the rendering layers the MultiMeshInstance3Ds for this mesh asset are drawn on. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DMeshAsset_method_clear: .. rst-class:: classref-method |void| **clear**\ (\ ) :ref:`🔗` Resets this resource to default settings. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_get_highlight_color: .. rst-class:: classref-method ``Color`` **get_highlight_color**\ (\ ) |const| :ref:`🔗` Returns the color of the current highlight material, if any. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_get_instance_count: .. rst-class:: classref-method ``int`` **get_instance_count**\ (\ ) |const| :ref:`🔗` Returns the number of instances on the ground for this mesh asset. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_get_lod_range: .. rst-class:: classref-method ``float`` **get_lod_range**\ (\ lod\: ``int``\ ) |const| :ref:`🔗` Returns the far visible distance for the specified Level of Detail (LOD). The near visible distance is the LOD range for the next closest LOD. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_get_mesh: .. rst-class:: classref-method ``Mesh`` **get_mesh**\ (\ lod\: ``int`` = 0\ ) |const| :ref:`🔗` Returns the ``Mesh`` resource for the specified Level of Detail (LOD). .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_get_thumbnail: .. rst-class:: classref-method ``Texture2D`` **get_thumbnail**\ (\ ) |const| :ref:`🔗` Returns the thumbnail generated by :ref:`Terrain3DAssets`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_is_highlighted: .. rst-class:: classref-method ``bool`` **is_highlighted**\ (\ ) |const| :ref:`🔗` Returns true if the instances for this mesh asset are currently highlighted on the ground. For editor use. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_set_highlighted: .. rst-class:: classref-method |void| **set_highlighted**\ (\ enabled\: ``bool``\ ) :ref:`🔗` Enables or disables adding a highlight material with a random color to the overlay slot for all instances of this mesh asset. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DMeshAsset_method_set_lod_range: .. rst-class:: classref-method |void| **set_lod_range**\ (\ lod\: ``int``, distance\: ``float``\ ) :ref:`🔗` Sets the far visible distance for the specified Level of Detail (LOD). .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dregion.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DRegion.xml. .. _class_Terrain3DRegion: Terrain3DRegion =============== **Inherits:** ``Resource`` .. rst-class:: classref-introduction-group Description ----------- This resource stores all map data for Terrain3D. See `Controlmap Format `__ and `Data Format Changelog `__. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +----------------+----------------------------------------------------------------------+-------------------+ | ``Image`` | :ref:`color_map` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``Image`` | :ref:`control_map` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``bool`` | :ref:`deleted` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``bool`` | :ref:`edited` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``Image`` | :ref:`height_map` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``Vector2`` | :ref:`height_range` | ``Vector2(0, 0)`` | +----------------+----------------------------------------------------------------------+-------------------+ | ``Dictionary`` | :ref:`instances` | ``{}`` | +----------------+----------------------------------------------------------------------+-------------------+ | ``Vector2i`` | :ref:`location` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``bool`` | :ref:`modified` | | +----------------+----------------------------------------------------------------------+-------------------+ | ``int`` | :ref:`region_size` | ``0`` | +----------------+----------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`version` | ``0.8`` | +----------------+----------------------------------------------------------------------+-------------------+ | ``float`` | :ref:`vertex_spacing` | ``1.0`` | +----------------+----------------------------------------------------------------------+-------------------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`calc_height_range`\ (\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear`\ (\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`dump`\ (\ verbose\: ``bool`` = false\ ) |const| | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Terrain3DRegion` | :ref:`duplicate`\ (\ deep\: ``bool`` = false\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Dictionary`` | :ref:`get_data`\ (\ ) |const| | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`get_map`\ (\ map_type\: :ref:`MapType`\ ) |const| | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Array`\[``Image``\] | :ref:`get_maps`\ (\ ) |const| | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`sanitize_map`\ (\ map_type\: :ref:`MapType`, map\: ``Image``\ ) |const| | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`sanitize_maps`\ (\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | Error | :ref:`save`\ (\ path\: ``String`` = "", save_16_bit\: ``bool`` = false\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_data`\ (\ data\: ``Dictionary``\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_map`\ (\ map_type\: :ref:`MapType`, map\: ``Image``\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_maps`\ (\ maps\: :ref:`Array`\[``Image``\]\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_height`\ (\ height\: ``float``\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | |void| | :ref:`update_heights`\ (\ low_high\: ``Vector2``\ ) | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`validate_map_size`\ (\ map\: ``Image``\ ) |const| | +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Enumerations ------------ .. _enum_Terrain3DRegion_MapType: .. rst-class:: classref-enumeration enum **MapType**: :ref:`🔗` .. _class_Terrain3DRegion_constant_TYPE_HEIGHT: .. rst-class:: classref-enumeration-constant :ref:`MapType` **TYPE_HEIGHT** = ``0`` Height map - real values, eg. 10m, 44.5m. .. _class_Terrain3DRegion_constant_TYPE_CONTROL: .. rst-class:: classref-enumeration-constant :ref:`MapType` **TYPE_CONTROL** = ``1`` Control map - defines where textures and holes are placed. .. _class_Terrain3DRegion_constant_TYPE_COLOR: .. rst-class:: classref-enumeration-constant :ref:`MapType` **TYPE_COLOR** = ``2`` Color map - paints color on the terrain .. _class_Terrain3DRegion_constant_TYPE_MAX: .. rst-class:: classref-enumeration-constant :ref:`MapType` **TYPE_MAX** = ``3`` The number of elements in this enum. Often used to specify all map types for functions that request one. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DRegion_property_color_map: .. rst-class:: classref-property ``Image`` **color_map** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_color_map**\ (\ value\: ``Image``\ ) - ``Image`` **get_color_map**\ (\ ) This map is used to paint color that blends in to the terrain textures. Image format: FORMAT_RGBA8, 32-bits per pixel as four 8-bit components. \ **RGB** is used for color, which is multiplied by albedo in the shader. Multiply is a blend mode that only darkens. \ **A** is used for a roughness modifier. A value of 0.5 means no change to the existing texture roughness. Higher than this value increases roughness, lower decreases it. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_control_map: .. rst-class:: classref-property ``Image`` **control_map** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_control_map**\ (\ value\: ``Image``\ ) - ``Image`` **get_control_map**\ (\ ) This map tells the shader which textures to use where, how to blend, where to place holes, etc. Image format: FORMAT_RF, 32-bit per pixel as full-precision floating-point. However, we interpret these images as format: `RenderingDevice.DATA_FORMAT_R32_UINT `__ aka OpenGL RG32UI 32-bit per pixel as unsigned integer. See `Control map format `__. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_deleted: .. rst-class:: classref-property ``bool`` **deleted** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_deleted**\ (\ value\: ``bool``\ ) - ``bool`` **is_deleted**\ (\ ) This region is marked for deletion. It won't be rendered once :ref:`Terrain3DData.update_maps()` rebuilds the map index. The file will be deleted from disk on :ref:`save()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_edited: .. rst-class:: classref-property ``bool`` **edited** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_edited**\ (\ value\: ``bool``\ ) - ``bool`` **is_edited**\ (\ ) This region is marked for updating by :ref:`Terrain3DData.update_maps()` and for undo/redo tracking when set between :ref:`Terrain3DEditor.start_operation()` and :ref:`Terrain3DEditor.stop_operation()`. The latter method clears the edited flag. This flag serves a different purpose than :ref:`modified`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_height_map: .. rst-class:: classref-property ``Image`` **height_map** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_height_map**\ (\ value\: ``Image``\ ) - ``Image`` **get_height_map**\ (\ ) This map contains the real value heights for the terrain. Image format: FORMAT_RF, 32-bit per pixel as full-precision floating-point. Heights sent to the vertex shader on the GPU which modifies the mesh in real-time. Editing is always done in 32-bit. We do provide an option to save as 16-bit, see :ref:`Terrain3D.save_16_bit`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_height_range: .. rst-class:: classref-property ``Vector2`` **height_range** = ``Vector2(0, 0)`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_height_range**\ (\ value\: ``Vector2``\ ) - ``Vector2`` **get_height_range**\ (\ ) The current minimum and maximum height range for this region, used to calculate the AABB of the terrain. Update it with :ref:`update_height()`, and recalculate it with :ref:`calc_height_range()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_instances: .. rst-class:: classref-property ``Dictionary`` **instances** = ``{}`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_instances**\ (\ value\: ``Dictionary``\ ) - ``Dictionary`` **get_instances**\ (\ ) A Dictionary that stores the instancer transforms for this region. The format is instances{mesh_id:int} -> cells{grid_location:Vector2i} -> ( Array:Transform3D, PackedColorArray, modified:bool ). That is: - A Dictionary keyed by mesh_id that returns: - A Dictionary keyed by the grid location of the 32 x 32m cell that returns: - A 3-item Array that contains: - 0: An Array of Transform3Ds - 1: A PackedColorArray with instance colors, same index as above - 2: A bool that tracks if this cell has been modified After changing this data, call :ref:`Terrain3DInstancer.update_mmis()` to rebuild the MMIs. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_location: .. rst-class:: classref-property ``Vector2i`` **location** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_location**\ (\ value\: ``Vector2i``\ ) - ``Vector2i`` **get_location**\ (\ ) The location in region grid space ``(world space / region_size)`` coordinates. e.g. (-1, 1) equates to (-1024, 1024) in world space given a :ref:`region_size` of 1024. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_modified: .. rst-class:: classref-property ``bool`` **modified** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_modified**\ (\ value\: ``bool``\ ) - ``bool`` **is_modified**\ (\ ) This region has been modified and will be saved to disk upon :ref:`save()`. This serves a different purpose than the temporary :ref:`edited` setting. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_region_size: .. rst-class:: classref-property ``int`` **region_size** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_region_size**\ (\ value\: ``int``\ ) - ``int`` **get_region_size**\ (\ ) The current region size for this region, calculated from the dimensions of the first loaded map. It should match :ref:`Terrain3D.region_size`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_version: .. rst-class:: classref-property ``float`` **version** = ``0.8`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_version**\ (\ value\: ``float``\ ) - ``float`` **get_version**\ (\ ) The data file version. This is independent of the Terrain3D version, though they often align. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_property_vertex_spacing: .. rst-class:: classref-property ``float`` **vertex_spacing** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_vertex_spacing**\ (\ value\: ``float``\ ) - ``float`` **get_vertex_spacing**\ (\ ) Stored instancer transforms are laterally scaled by this value. This value is manage by the instancer on loading or when :ref:`Terrain3D.vertex_spacing` is set, and shouldn't be manually adjusted. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DRegion_method_calc_height_range: .. rst-class:: classref-method |void| **calc_height_range**\ (\ ) :ref:`🔗` Recalculates the height range for this region by looking at every pixel in the heightmap. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_clear: .. rst-class:: classref-method |void| **clear**\ (\ ) :ref:`🔗` Unreferences the maps and resets all of the variables to default values. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_dump: .. rst-class:: classref-method |void| **dump**\ (\ verbose\: ``bool`` = false\ ) |const| :ref:`🔗` Dumps information about the data in the region, including instance IDs, counts, and data pointers. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_duplicate: .. rst-class:: classref-method :ref:`Terrain3DRegion` **duplicate**\ (\ deep\: ``bool`` = false\ ) :ref:`🔗` Returns a duplicate copy of this node, with references to the same image maps and multimeshes. - deep - Also make complete duplicates of the maps and multimeshes. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_get_data: .. rst-class:: classref-method ``Dictionary`` **get_data**\ (\ ) |const| :ref:`🔗` Returns all data in this region in a dictionary. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_get_map: .. rst-class:: classref-method ``Image`` **get_map**\ (\ map_type\: :ref:`MapType`\ ) |const| :ref:`🔗` Returns the specified image map. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_get_maps: .. rst-class:: classref-method :ref:`Array`\[``Image``\] **get_maps**\ (\ ) |const| :ref:`🔗` Returns an Array\ ``Image`` with height, control, and color maps. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_sanitize_map: .. rst-class:: classref-method ``Image`` **sanitize_map**\ (\ map_type\: :ref:`MapType`, map\: ``Image``\ ) |const| :ref:`🔗` Validates and adjusts the map size and format if possible, or creates a usable blank image in the right size and format. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_sanitize_maps: .. rst-class:: classref-method |void| **sanitize_maps**\ (\ ) :ref:`🔗` Sanitizes all map types. See :ref:`sanitize_map()`. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_save: .. rst-class:: classref-method Error **save**\ (\ path\: ``String`` = "", save_16_bit\: ``bool`` = false\ ) :ref:`🔗` Saves this region to the current file name. - path - specifies a directory and file name to use from now on. - 16-bit - save this region with 16-bit height map instead of 32-bit. This process is lossy. Does not change the bit depth in memory. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_set_data: .. rst-class:: classref-method |void| **set_data**\ (\ data\: ``Dictionary``\ ) :ref:`🔗` Overwrites all local variables with values in the dictionary. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_set_map: .. rst-class:: classref-method |void| **set_map**\ (\ map_type\: :ref:`MapType`, map\: ``Image``\ ) :ref:`🔗` Assigns the provided map to the desired map type. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_set_maps: .. rst-class:: classref-method |void| **set_maps**\ (\ maps\: :ref:`Array`\[``Image``\]\ ) :ref:`🔗` Expects an array with three images in it, and assigns them to the height, control, and color maps. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_update_height: .. rst-class:: classref-method |void| **update_height**\ (\ height\: ``float``\ ) :ref:`🔗` When sculpting, this is called to provide the current height. It may expand the vertical bounds, which is used to calculate the terrain AABB. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_update_heights: .. rst-class:: classref-method |void| **update_heights**\ (\ low_high\: ``Vector2``\ ) :ref:`🔗` When sculpting the terrain, this is called to provide both a low and high height. It may expand the vertical bounds, which is used to calculate the terrain AABB. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DRegion_method_validate_map_size: .. rst-class:: classref-method ``bool`` **validate_map_size**\ (\ map\: ``Image``\ ) |const| :ref:`🔗` This validates the map size according to previously loaded maps. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dtextureasset.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DTextureAsset.xml. .. _class_Terrain3DTextureAsset: Terrain3DTextureAsset ===================== **Inherits:** ``Resource`` .. rst-class:: classref-introduction-group Description ----------- A set of texture files and settings that gets added to a :ref:`Terrain3DAssets` resource. Textures must be prepared according to the `documentation `__. .. rst-class:: classref-reftable-group Properties ---------- .. table:: :widths: auto +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``Color`` | :ref:`albedo_color` | ``Color(1, 1, 1, 1)`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``Texture2D`` | :ref:`albedo_texture` | | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`ao_light_affect` | ``0.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`ao_strength` | ``0.5`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`detiling_rotation` | ``0.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`detiling_shift` | ``0.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`displacement_offset` | ``0.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`displacement_scale` | ``0.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``int`` | :ref:`id` | ``0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``String`` | :ref:`name` | ``"New Texture"`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`normal_depth` | ``1.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``Texture2D`` | :ref:`normal_texture` | | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`roughness` | ``0.0`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ | ``float`` | :ref:`uv_scale` | ``0.1`` | +---------------+--------------------------------------------------------------------------------------+-----------------------+ .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +---------------+------------------------------------------------------------------------------------------------------+ | |void| | :ref:`clear`\ (\ ) | +---------------+------------------------------------------------------------------------------------------------------+ | ``Color`` | :ref:`get_highlight_color`\ (\ ) |const| | +---------------+------------------------------------------------------------------------------------------------------+ | ``Texture2D`` | :ref:`get_thumbnail`\ (\ ) |const| | +---------------+------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_highlighted`\ (\ ) |const| | +---------------+------------------------------------------------------------------------------------------------------+ | |void| | :ref:`set_highlighted`\ (\ enabled\: ``bool``\ ) | +---------------+------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Signals ------- .. _class_Terrain3DTextureAsset_signal_file_changed: .. rst-class:: classref-signal **file_changed**\ (\ ) :ref:`🔗` Emitted when :ref:`albedo_texture` or :ref:`normal_texture` are changed. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_signal_id_changed: .. rst-class:: classref-signal **id_changed**\ (\ ) :ref:`🔗` Emitted when :ref:`id` is changed. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_signal_setting_changed: .. rst-class:: classref-signal **setting_changed**\ (\ ) :ref:`🔗` Emitted when any setting is changed, other than id, albedo_texture, or normal_texture. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Property Descriptions --------------------- .. _class_Terrain3DTextureAsset_property_albedo_color: .. rst-class:: classref-property ``Color`` **albedo_color** = ``Color(1, 1, 1, 1)`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_albedo_color**\ (\ value\: ``Color``\ ) - ``Color`` **get_albedo_color**\ (\ ) This color is multiplied by the albedo texture in the shader. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_albedo_texture: .. rst-class:: classref-property ``Texture2D`` **albedo_texture** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_albedo_texture**\ (\ value\: ``Texture2D``\ ) - ``Texture2D`` **get_albedo_texture**\ (\ ) The texture file with albedo on RGB and height on A. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_ao_light_affect: .. rst-class:: classref-property ``float`` **ao_light_affect** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ao_light_affect**\ (\ value\: ``float``\ ) - ``float`` **get_ao_light_affect**\ (\ ) This value is applied directly to the AO_LIGHT_AFFECT in the shader, which dictates how much ambient occlusion affects light from Light3Ds. 0 means AO only affects ambient light. 1 means it also affects lights. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_ao_strength: .. rst-class:: classref-property ``float`` **ao_strength** = ``0.5`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_ao_strength**\ (\ value\: ``float``\ ) - ``float`` **get_ao_strength**\ (\ ) The strength of ambient occlusion, which darkens areas of this texture dictated by the ambient occlusion map. If you have not provided an AO texture, an AO value is approximated by the normal map automatically. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_detiling_rotation: .. rst-class:: classref-property ``float`` **detiling_rotation** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_detiling_rotation**\ (\ value\: ``float``\ ) - ``float`` **get_detiling_rotation**\ (\ ) The shader rotates UV lookups in a detiling pattern based on this value. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_detiling_shift: .. rst-class:: classref-property ``float`` **detiling_shift** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_detiling_shift**\ (\ value\: ``float``\ ) - ``float`` **get_detiling_shift**\ (\ ) The shader laterally shifts UV lookups in a detiling pattern based on this value. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_displacement_offset: .. rst-class:: classref-property ``float`` **displacement_offset** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_displacement_offset**\ (\ value\: ``float``\ ) - ``float`` **get_displacement_offset**\ (\ ) Offset that can be used to raise or lower the displaced surface height for this material. Example: slightly lowering a cobblestone texture so the tops of the cobbles match collision. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_displacement_scale: .. rst-class:: classref-property ``float`` **displacement_scale** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_displacement_scale**\ (\ value\: ``float``\ ) - ``float`` **get_displacement_scale**\ (\ ) The scale of the displaced surface height for this material. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_id: .. rst-class:: classref-property ``int`` **id** = ``0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_id**\ (\ value\: ``int``\ ) - ``int`` **get_id**\ (\ ) The user settable ID of the texture, between 0 and 31. You can change this to reorder textures in the list, however it won't change the ID painted on the terrain. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_name: .. rst-class:: classref-property ``String`` **name** = ``"New Texture"`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_name**\ (\ value\: ``String``\ ) - ``String`` **get_name**\ (\ ) A user specified name for this texture set. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_normal_depth: .. rst-class:: classref-property ``float`` **normal_depth** = ``1.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_normal_depth**\ (\ value\: ``float``\ ) - ``float`` **get_normal_depth**\ (\ ) Increases or decreases the strength of the normal texture. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_normal_texture: .. rst-class:: classref-property ``Texture2D`` **normal_texture** :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_normal_texture**\ (\ value\: ``Texture2D``\ ) - ``Texture2D`` **get_normal_texture**\ (\ ) The texture file with normal on RGB and roughness on A. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_roughness: .. rst-class:: classref-property ``float`` **roughness** = ``0.0`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_roughness**\ (\ value\: ``float``\ ) - ``float`` **get_roughness**\ (\ ) Increases or decreases the roughness texture values. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_property_uv_scale: .. rst-class:: classref-property ``float`` **uv_scale** = ``0.1`` :ref:`🔗` .. rst-class:: classref-property-setget - |void| **set_uv_scale**\ (\ value\: ``float``\ ) - ``float`` **get_uv_scale**\ (\ ) The scale of the textures. .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DTextureAsset_method_clear: .. rst-class:: classref-method |void| **clear**\ (\ ) :ref:`🔗` Clears the texture files and settings. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_method_get_highlight_color: .. rst-class:: classref-method ``Color`` **get_highlight_color**\ (\ ) |const| :ref:`🔗` Returns the color of the current highlight, if any. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_method_get_thumbnail: .. rst-class:: classref-method ``Texture2D`` **get_thumbnail**\ (\ ) |const| :ref:`🔗` Returns the generated thumbnail. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_method_is_highlighted: .. rst-class:: classref-method ``bool`` **is_highlighted**\ (\ ) |const| :ref:`🔗` Returns true if this texture is currently highlighted on the ground. For editor use. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DTextureAsset_method_set_highlighted: .. rst-class:: classref-method |void| **set_highlighted**\ (\ enabled\: ``bool``\ ) :ref:`🔗` Enables or disables adding a random highlight color to this texture wherever placed on the ground. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/class_terrain3dutil.rst ================================================ :github_url: hide .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. XML source: https://github.com/godotengine/godot/tree/master/../_plugins/Terrain3D/doc/doc_classes/Terrain3DUtil.xml. .. _class_Terrain3DUtil: Terrain3DUtil ============= **Inherits:** ``Object`` .. rst-class:: classref-introduction-group Description ----------- This class contains static utility functions. Reference them with the full class name. Eg. ``Terrain3DUtil.as_float()``. Or you can instance the class for a shorter alias: :: var util := Terrain3DUtil.new() var my_float: float = util.as_float(my_int) \ **Note on uints**: Various functions refer to unsigned integers as uint. Though GDScript doesn't support unsigned integers as a type, the C++ that receives and returns these values will interpret them all as unsigned. \ **Note on bitwise-ORing**: To write back to a control map, encode your values and bitwise OR the results, then reinterpret that uint as a float. The shader will interpret the float as uint and extract the bits. :: var bits: int = util.enc_base(base_id) | util.enc_overlay(over_id) | \ util.enc_blend(blend) | util.enc_uv_rotation(uvrotation) | \ util.enc_uv_scale(uvscale) | util.enc_auto(autoshader) | \ util.enc_nav(navigation) | util.enc_hole(hole) var color: Color = Color(util.as_float(bits), 0., 0., 1.) data.set_control(global_pos, color) .. rst-class:: classref-reftable-group Methods ------- .. table:: :widths: auto +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``float`` | :ref:`as_float`\ (\ value\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`as_uint`\ (\ value\: ``float``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`black_to_alpha`\ (\ image\: ``Image``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_auto`\ (\ pixel\: ``bool``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_base`\ (\ base\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_blend`\ (\ blend\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_hole`\ (\ pixel\: ``bool``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_nav`\ (\ pixel\: ``bool``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_overlay`\ (\ overlay\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_uv_rotation`\ (\ rotation\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`enc_uv_scale`\ (\ scale\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector2i`` | :ref:`filename_to_location`\ (\ filename\: ``String``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_base`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_blend`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`get_filled_image`\ (\ size\: ``Vector2i``, color\: ``Color``, create_mipmaps\: ``bool``, format\: Image.Format\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Vector2`` | :ref:`get_min_max`\ (\ image\: ``Image``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_overlay`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`get_thumbnail`\ (\ image\: ``Image``, size\: ``Vector2i`` = Vector2i(256, 256)\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_uv_rotation`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``int`` | :ref:`get_uv_scale`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_auto`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_hole`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``bool`` | :ref:`is_nav`\ (\ pixel\: ``int``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`load_image`\ (\ file_name\: ``String``, cache_mode\: ``int`` = 0, r16_height_range\: ``Vector2`` = Vector2(0, 255), r16_size\: ``Vector2i`` = Vector2i(0, 0)\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``String`` | :ref:`location_to_filename`\ (\ region_location\: ``Vector2i``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`luminance_to_height`\ (\ src_rgb\: ``Image``\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``Image`` | :ref:`pack_image`\ (\ src_rgb\: ``Image``, src_a\: ``Image``, src_ao\: ``Image``, invert_green\: ``bool`` = false, invert_alpha\: ``bool`` = false, normalize_alpha\: ``bool`` = false, alpha_channel\: ``int`` = 0, ao_channel\: ``int`` = 0\ ) |static| | +--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. rst-class:: classref-section-separator ---- .. rst-class:: classref-descriptions-group Method Descriptions ------------------- .. _class_Terrain3DUtil_method_as_float: .. rst-class:: classref-method ``float`` **as_float**\ (\ value\: ``int``\ ) |static| :ref:`🔗` Returns a float typed variable with the contents of the memory stored in value, an integer typed variable. This function does not convert integer values to float values (e.g. 4 -> 4.0). It reinterprets the memory block as if it were a float. If the data in value was a valid integer, it is now an invalid float. \ ``my_float == util.as_float(util.as_uint(my_float))``\ See :ref:`as_uint()` for the opposite. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_as_uint: .. rst-class:: classref-method ``int`` **as_uint**\ (\ value\: ``float``\ ) |static| :ref:`🔗` Returns an integer typed variable with the contents of the memory stored in value, a float typed variable. This function does not convert float values to integer values (e.g. 4.0 -> 4). It reinterprets the memory block as if it were an integer. If the data in value was a valid float, it is now a valid integer, but probably an unexepctedly large value. \ ``my_int == util.as_uint(util.as_float(my_int))``\ See :ref:`as_float()` for the opposite. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_black_to_alpha: .. rst-class:: classref-method ``Image`` **black_to_alpha**\ (\ image\: ``Image``\ ) |static| :ref:`🔗` Receives an image with a black background and returns one with a transparent background, aka an alpha mask. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_auto: .. rst-class:: classref-method ``int`` **enc_auto**\ (\ pixel\: ``bool``\ ) |static| :ref:`🔗` Returns a control map uint with the auto shader bit set. See the top description for usage. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_base: .. rst-class:: classref-method ``int`` **enc_base**\ (\ base\: ``int``\ ) |static| :ref:`🔗` Returns a control map uint with the base texture ID encoded. See the top description for usage. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_blend: .. rst-class:: classref-method ``int`` **enc_blend**\ (\ blend\: ``int``\ ) |static| :ref:`🔗` Returns a control map uint with the blend value encoded. See the top description for usage. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_hole: .. rst-class:: classref-method ``int`` **enc_hole**\ (\ pixel\: ``bool``\ ) |static| :ref:`🔗` Returns a control map uint with the hole bit set. See the top description for usage. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_nav: .. rst-class:: classref-method ``int`` **enc_nav**\ (\ pixel\: ``bool``\ ) |static| :ref:`🔗` Returns a control map uint with the nav bit set. See the top description for usage. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_overlay: .. rst-class:: classref-method ``int`` **enc_overlay**\ (\ overlay\: ``int``\ ) |static| :ref:`🔗` Returns a control map uint with the overlay texture ID encoded. See the top description for usage. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_uv_rotation: .. rst-class:: classref-method ``int`` **enc_uv_rotation**\ (\ rotation\: ``int``\ ) |static| :ref:`🔗` Returns a control map uint with the texture rotation encoded. See the top description for usage. See :ref:`get_uv_rotation()` for values. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_enc_uv_scale: .. rst-class:: classref-method ``int`` **enc_uv_scale**\ (\ scale\: ``int``\ ) |static| :ref:`🔗` Returns a control map uint with the texture scale encoded. See the top description for usage. See :ref:`get_uv_scale()` for values. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_filename_to_location: .. rst-class:: classref-method ``Vector2i`` **filename_to_location**\ (\ filename\: ``String``\ ) |static| :ref:`🔗` Converts a file name string like ``terrain3d-01_02.res`` to a region location like ``(-1, 2)``. - is negative, \_ is positive. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_base: .. rst-class:: classref-method ``int`` **get_base**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns the base texture ID from a control map pixel. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_blend: .. rst-class:: classref-method ``int`` **get_blend**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns the blend value from a control map pixel. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_filled_image: .. rst-class:: classref-method ``Image`` **get_filled_image**\ (\ size\: ``Vector2i``, color\: ``Color``, create_mipmaps\: ``bool``, format\: Image.Format\ ) |static| :ref:`🔗` Returns an Image filled with a specified color and format. If ``color.a < 0``, its filled with a checkered pattern multiplied by ``color.rgb``. The behavior changes if a compressed format is requested: - If the editor is running and the format is DXT1, DXT5, or BPTC_RGBA, it returns a filled image in the requested color and format. - All other compressed formats return a blank image in that format. The reason for this is the Image compression library is available only in the editor. And it is unreliable, offering little control over the output format, choosing automatically and often wrong. We have selected a few compressed formats it gets right. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_min_max: .. rst-class:: classref-method ``Vector2`` **get_min_max**\ (\ image\: ``Image``\ ) |static| :ref:`🔗` Returns the minimum and maximum r channel values of an Image. Used for heightmaps. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_overlay: .. rst-class:: classref-method ``int`` **get_overlay**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns the overlay texture ID from a control map pixel. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_thumbnail: .. rst-class:: classref-method ``Image`` **get_thumbnail**\ (\ image\: ``Image``, size\: ``Vector2i`` = Vector2i(256, 256)\ ) |static| :ref:`🔗` Returns an Image normalized and converted to RGB8. Used for creating a human viewable image of a heightmap, at any size. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_uv_rotation: .. rst-class:: classref-method ``int`` **get_uv_rotation**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns the texture rotation from a control map pixel. Values are 0 - 15, which provides degrees when multiplied by 22.5. (360/16). .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_get_uv_scale: .. rst-class:: classref-method ``int`` **get_uv_scale**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns the texture scale modification from a control map pixel. Values are an index into the array `{ 0, 20, 40, 60, 80, -60, -40, -20 }`. 0 indicates no scale modification. Index 2 indicates a 40% increase in texture scale at that pixel. Index -1 or 7 indicates a -20% texture scale change. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_is_auto: .. rst-class:: classref-method ``bool`` **is_auto**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns true if the control map pixel has the autoshader bit set. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_is_hole: .. rst-class:: classref-method ``bool`` **is_hole**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns true if the control map pixel has the hole bit set. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_is_nav: .. rst-class:: classref-method ``bool`` **is_nav**\ (\ pixel\: ``int``\ ) |static| :ref:`🔗` Returns true if the control map pixel has the nav bit set. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_load_image: .. rst-class:: classref-method ``Image`` **load_image**\ (\ file_name\: ``String``, cache_mode\: ``int`` = 0, r16_height_range\: ``Vector2`` = Vector2(0, 255), r16_size\: ``Vector2i`` = Vector2i(0, 0)\ ) |static| :ref:`🔗` Loads a file from disk and returns an Image. \ ``filename`` - The file name on disk to load. Loads EXR, R16/RAW, PNG, or a ResourceLoader format (jpg, res, tres, etc). \ ``cache_mode`` - Send this flag to the resource loader to force caching or not. \ ``height_range`` - Heights for R16 format. x=Min & y=Max value ranges. Required for R16 import. \ ``size`` - Image dimensions for R16 format. Default (0,0) auto detects size, assuming square images. Required for non-square R16. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_location_to_filename: .. rst-class:: classref-method ``String`` **location_to_filename**\ (\ region_location\: ``Vector2i``\ ) |static| :ref:`🔗` Converts a region location like ``(-1, 2)`` to a file name string like ``terrain3d-01_02.res``. - is negative, \_ is positive. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_luminance_to_height: .. rst-class:: classref-method ``Image`` **luminance_to_height**\ (\ src_rgb\: ``Image``\ ) |static| :ref:`🔗` Generates a greyscale RGB8 height texture from the luminance values of the source image. .. rst-class:: classref-item-separator ---- .. _class_Terrain3DUtil_method_pack_image: .. rst-class:: classref-method ``Image`` **pack_image**\ (\ src_rgb\: ``Image``, src_a\: ``Image``, src_ao\: ``Image``, invert_green\: ``bool`` = false, invert_alpha\: ``bool`` = false, normalize_alpha\: ``bool`` = false, alpha_channel\: ``int`` = 0, ao_channel\: ``int`` = 0\ ) |static| :ref:`🔗` Returns an RGBA Image packed for terrain usage. - ``src_rgb`` - The source Image for the RGB channels. - ``src_a`` - The source image for the A channel. - ``invert_green`` - Inverts the green channel to convert between OpenGL and DirectX normal maps. - ``invert_alpha`` - Inverts the alpha channel to convert between Roughness and Smoothness maps. - ``normalize_alpha`` - Normalizes the alpha channel to use full range for height map. - ``alpha_channel`` - The channel index (0-3: R,G,B,A) to use from src_a for the alpha channel. - ``ao_channel`` - The channel index (0-3: R,G,B,A) to use from src_ao for the ambient occlusion channel. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |required| replace:: :abbr:`required (This method is required to be overridden when extending its base class.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` .. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` .. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` .. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` .. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` .. |void| replace:: :abbr:`void (No return value.)` ================================================ FILE: doc/api/index.rst ================================================ :github_url: hide :allow_comments: False .. DO NOT EDIT THIS FILE!!! .. Generated automatically from Godot engine sources. .. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py. .. _doc_class_reference: All classes =========== Variant types ============= .. toctree:: :maxdepth: 1 :name: toc-class-ref-variants class_variant class_terrain3d class_terrain3dassets class_terrain3dcollision class_terrain3ddata class_terrain3deditor class_terrain3dinstancer class_terrain3dmaterial class_terrain3dmeshasset class_terrain3dregion class_terrain3dtextureasset class_terrain3dutil ================================================ FILE: doc/build_docs.sh ================================================ #!/bin/bash GODOT=/c/gd/bin/Godot_v4.5.1-stable_win64.exe MAKERST=/c/gd/godot/doc/tools/make_rst.py REPO=`git rev-parse --show-toplevel` pushd $REPO echo --- Running Godot to dump XML files cd $REPO/project $GODOT --doctool ../doc --gdextension-docs cd $REPO/doc echo --- Running make_rst.py to produce sphinx output $MAKERST --verbose --filter Terrain3D --output api path doc_classes/ 2>&1 | egrep -v 'Unresolved (type|enum)' echo --- Generating html make clean make html 2>&1 | grep -Pv 'WARNING: undefined label: (?!'\''class_terrain3d)' | egrep -v '(local id not found|copying images|writing output|reading sources|toctree contains reference .+api/class_variant)' start _build/html/index.html popd ================================================ FILE: doc/conf.py ================================================ # Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'Terrain3D' copyright = '2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors' author = 'Cory Petkovsek, Roope Palmroos, and Contributors' release = '1.1.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # TODO sphinx_tabs used in programming_languages.rst requires sphinx < 9. # Migrate to sphinx-design and sphinx 9+, then we can use tabs in markdown extensions = ['myst_parser', 'sphinx_rtd_dark_mode', 'sphinx_tabs.tabs'] myst_heading_anchors = 3 default_dark_mode = False templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] html_css_files = ['theme_overrides.css'] ================================================ FILE: doc/doc_classes/Terrain3D.xml ================================================ Terrain3D is a high performance, editable terrain system for Godot 4. It provides a clipmap based terrain that supports terrains from 64x64m up to 65.5x65.5km with multiple LODs, 32 textures, and editor tools for importing or creating terrains. This class handles mesh generation, and management of the whole system. See [url=https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html]System Architecture[/url] for design details. Generates a static ArrayMesh for the terrain. [code skip-lint]lod[/code] - Determines the granularity of the generated mesh. The range is 0-8. 4 is recommended. [code skip-lint]filter[/code] - Controls how vertex Y coordinates are generated from the height map. See [enum Terrain3DData.HeightFilter]. Generates source geometry faces for input to nav mesh baking. Geometry is only generated where there are no holes and the terrain has been painted as navigable. [code skip-lint]global_aabb[/code] - If non-empty, geometry will be generated only within this AABB. If empty, geometry will be generated for the entire terrain. [code skip-lint]require_nav[/code] - If true, this function will only generate geometry for terrain marked navigable. Otherwise, geometry is generated for the entire terrain within the AABB (which can be useful for dynamic and/or runtime nav mesh baking). Returns the camera the terrain is currently tracking for position, if not overridden by [member clipmap_target]. See [method set_camera]. Returns the position on which the terrain mesh is centered, which may be the camera or a target node. See [member clipmap_target]. Returns the position on which the terrain collision is centered, which may be the camera or a target node. See [member collision_target]. Returns the current Terrain3DEditor instance, if it has been set. Casts a ray from [code skip-lint]src_pos[/code] pointing towards [code skip-lint]direction[/code], attempting to intersect the terrain. This operation is does not use physics and is not a typical raycast, so enabling collision is unnecessary. This function likely won't work if src_pos is below the terrain. This function can operate in one of two modes selected by [code skip-lint]gpu_mode[/code]: - If gpu_mode is disabled (default), it raymarches from src_pos until the terrain is intersected, up to 4000m away. This works with one function call, and can only intersect the terrain where regions exist. It is slower than gpu_mode and gets increasingly slower the farther away the terrain is, though you may not notice. - If gpu_mode is enabled, it uses the GPU to detect the mouse. This works wherever the terrain is visible, even outside of regions, but may need to be called twice. GPU mode places a camera at the specified point and "looks" at the terrain. It uses the depth texture to determine how far away the intersection point is. It requires the use of an editor render layer, (default 32, set with [member mouse_layer]) while using this function. The main caveats of using this mode is that the call to get_intersection() requests a viewport be drawn, but cannot wait for it to finish as there is no "await" in C++ and no force draw function in Godot. So the return value is one frame behind, and invalid on the first call. This also means the function cannot be used more than once per frame. This mode works well when used continuously, once per frame, where one frame of difference won't matter. The editor uses this mode to place the mouse cursor decal. This mode can also be used by your plugins and games, such as a space ship firing lasers at the terrain and causing an explosion at the hit point. However if the calls aren't continuous, eg driven by the mouse, you'll need to call once to capture the viewport image, wait for it to be drawn, then call again to get the result: [codeblock] var target_point = terrain.get_intersection(camera_pos, camera_dir, true) await RenderingServer.frame_post_draw target_point = terrain.get_intersection(camera_pos, camera_dir, true) [/codeblock] Possible return values: - If the terrain is hit, the intersection point is returned. - If there is no intersection, eg. the ray points towards the sky, it returns the maximum double float value [code skip-lint]Vector3(3.402823466e+38F,...)[/code]. You can check this case with this code: [code skip-lint]if point.z > 3.4e38:[/code] - On error, it returns [code skip-lint]Vector3(NAN, NAN, NAN)[/code] and prints a message to the console. Also see [method get_raycast_result] and [method Terrain3DData.get_height] for alternative functions. Returns the EditorPlugin Object connected to Terrain3D. This is a helper function that creates a general physics-based raycast and returns the resulting dictionary; it's not limited to terrain use. Raycasts can only detect collision. It is used by our editor using the `on_collision` option to instance on non-terrain meshes. Direction is added to src_pos and includes magnitude. So to run a raycast from (100, 100, 100) to the ground 100m below, direction would be (0, -110, 0) with margin. Collision_mask has the physics layers the query will detect as a bitmask. By default, all collision layers are detected. See [url=https://docs.godotengine.org/en/stable/classes/class_physicsdirectspacestate3d.html#class-physicsdirectspacestate3d-method-intersect-ray]PhysicsDirectSpaceState3D.intersect_ray[/url] for how to interpret the resulting dictionary. Also see [method get_intersection] and [method Terrain3DData.get_height] for alternative functions. Specifies the camera on which the terrain centers. It attempts to aquire the camera from the active viewport. If the camera is instanced in a sub scene or by code, Terrain3D might not be able to find it, will issue an error, and the terrain will center at (0,0,0) causing LODs to not update until a trackable node is set. Either specify the camera, or specify the clipmap and/or collision targets. It will use the targets first and fall back to the camera if they are null. See [member clipmap_target] and [member collision_target]. Sets the current Terrain3DEditor instance. Sets the EditorPlugin Object connected to Terrain3D. Queues the terrain mesh and collision to snap their positions to the target nodes on the next physics frame. Typically this only happens if the targets have moved sufficiently far. See [member clipmap_target] and [member collision_target]. The list of texture and mesh assets used by Terrain3D. You can optionally save this as an external [code skip-lint].tres[/code] text file if you wish to share it with Terrain3D nodes in other scenes. Tells the renderer how to cast shadows from the terrain onto other objects. This sets [code skip-lint]GeometryInstance3D.ShadowCastingSetting[/code] in the engine. The terrain clipmap mesh and lods will center itself at the position of this node. If null, or if in the editor, it will fall back to the camera position. See [method set_camera]. The active [Terrain3DCollision] object. The physics layers the terrain lives on. Sets [code skip-lint]CollisionObject3D.collision_layer[/code]. Alias for [member Terrain3DCollision.layer]. Also see [member collision_mask]. The physics layers the physics body scans for colliding objects. Sets [code skip-lint]CollisionObject3D.collision_mask[/code]. Alias for [member Terrain3DCollision.mask]. Also see [member collision_layer]. The selected mode determines if collision is generated and how. See [enum Terrain3DCollision.CollisionMode] for details. Alias for [member Terrain3DCollision.mode]. The priority with which the physics server uses to solve collisions. The higher the priority, the lower the penetration of a colliding object. Sets [code skip-lint]CollisionObject3D.collision_priority[/code]. Alias for [member Terrain3DCollision.priority]. If [member collision_mode] is Dynamic, this is the distance range within which collision shapes will be generated. Alias for [member Terrain3DCollision.radius]. If [member collision_mode] is Dynamic, this is the size of each collision shape. Alias for [member Terrain3DCollision.shape_size]. In dynamic mode, the terrain collision will center itself at the position of this node. If null, it will fall back to the [member clipmap_target] position and failing that will use the camera position. The camera is always used in the editor. See [method set_camera]. This margin is added to the vertical component of the terrain mesh bounding boxes (AABB). The terrain already sets its AABB from [method Terrain3DData.get_height_range], which is calculated while sculpting. This setting only needs to be used if the shader has expanded the terrain beyond the AABB and the terrain meshes are being culled at certain viewing angles. This might happen from using [member Terrain3DMaterial.world_background] with NOISE and a height value larger than the terrain heights. This setting is similar to [code skip-lint]GeometryInstance3D.extra_cull_margin[/code], but it only affects the Y axis. This class manages loading, saving, adding, and removing of Terrain3DRegions and access to their content. The directory where terrain data will be saved to and loaded from. The verbosity of debug messages printed to the console. Errors and warnings are always printed. This can also be set via command line using [code skip-lint]--terrain3d-debug=LEVEL[/code] where [code skip-lint]LEVEL[/code] is one of [code skip-lint]ERROR, INFO, DEBUG, EXTREME[/code]. The last includes continuously recurring messages like position updates for the mesh as the camera moves around. A global multiplier for all displaced textures. This is the maximum distance that 2 adjacent verticies can be vertically seperated by. Setting this 1.0 would mean a maximum of + 0.5m, and -0.5m deviation from the collision mesh. Alias for [member Terrain3DMaterial.displacement_scale]. Adjusts the transition between textures. When set at `1.0`, the blending of displacment between textures will match the aldebo/normal blend sharpness exactly. Lower values will have a softer transition, avoiding harsh shapes, without compromising the abldeo and normal blend sharpness. If set at or very near to `0.0`, it is possible that more displaced textures can affect less displaced textures at low blend values even if not visible. Alias for [member Terrain3DMaterial.displacement_sharpness]. Frees ground textures used for editing in _ready(). These textures are used to generate the TextureArrays, so if you don't change any [Terrain3DTextureAsset] settings in game, this can be enabled. Also reloads the texture asset list in _enter_tree() in case you load scenes via code and need the textures again. Calls [method Terrain3DAssets.clear_textures]. Tells the renderer which global illumination mode to use for the terrain mesh. This sets [code skip-lint]GeometryInstance3D.gi_mode[/code] in the engine. The active [Terrain3DInstancer] object. Normal - Generates MultiMeshInstance3Ds and renders all instances as normal. Disabled - prevents the instancer from creating any MultiMeshInstance3Ds. Alias for [member Terrain3DInstancer.mode]. If label_distance is non-zero (try 1024-4096) it will generate and display region coordinates in the viewport so you can identify the exact region files you are editing. This setting is the visible distance of the labels. Sets the font size for region labels. See [member label_distance]. A custom material for Terrain3D. You can optionally save this as an external [code skip-lint].tres[/code] text file if you wish to share it with instances of Terrain3D in other scenes. See [Terrain3DMaterial]. The number of lods generated for the terrain meshes. Enable wireframe mode in the viewport to see them. The correlated size of the terrain meshes. Lod0 has [code skip-lint]4*mesh_size + 2[/code] quads per side. E.g. when mesh_size=8, lod0 has 34 quads to a side, including 2 quads for seams. Godot supports 32 render layers. For most objects, only layers 1-20 are available for selection in the inspector. 21-32 are settable via code, and are considered reserved for editor plugins. This variable sets the editor render layer (21-32) to be used by [code skip-lint]get_intersection[/code], which the mouse cursor uses. You may place other objects on this layer, however [code skip-lint]get_intersection[/code] will report intersections with them. So either dedicate this layer to Terrain3D, or if you must use all 32 layers, dedicate this one during editing or when using [code skip-lint]get_intersection[/code], and then you can use it during game play. See [method get_intersection]. Tells the renderer how to cast shadows from the ocean onto other objects. This sets [code skip-lint]GeometryInstance3D.ShadowCastingSetting[/code] in the engine. This margin is added to the vertical component of the ocean mesh bounding boxes (AABB). When you set the height of your waves in the shader, this margin should be adjusted. If it's too small, the meshes will clip at certain camera angles. If you set it too large, the renderer may have slightly more work to do. This setting is similar to [code skip-lint]GeometryInstance3D.extra_cull_margin[/code], but it only affects the Y axis. Generates another clipmap mesh, which you can apply an ocean shader to and configure independently of the terrain mesh. Tells the renderer which global illumination mode to use for the ocean mesh. This sets [code skip-lint]GeometryInstance3D.gi_mode[/code] in the engine. This sets the _light_direction and _light_color uniforms in the ocean shader, if they are present. You can use this for light scattering, detecting if the light is above the horizon, albedo coloring, etc. You can assign a [code skip-lint]StandardMaterial[/code] here for testing, but you really need a [code skip-lint]ShaderMaterial[/code]. Start with the example in [code skip-lint]addons/terrain_3d/extras/shaders/M_ocean.tres[/code], which you can build on. Or use any of the ocean shaders available around the internet, provided you set `skip_vertex_transform` and copy the geomorphing code from our `vertex()` shader, which will properly handle the LOD transitions on the clipmap. The number of lods generated for the ocean meshes. Enable wireframe mode in the viewport to see them. The correlated size of the ocean meshes. Lod0 has [code skip-lint]4*ocean_mesh_size + 2[/code] quads per side. E.g. when ocean_mesh_size=8, lod0 has 34 quads to a side, including 2 quads for seams. The render layers the ocean is drawn on. This sets [code skip-lint]VisualInstance3D.layers[/code] in the engine. This setting creates up to 6 additional subdivisions of the ocean mesh below LOD0, which provides more vertices for the vertex shader if desired. You can see it in wireframe mode. The distance between vertices, settable up to 100. Godot units are typically considered to be meters. This laterally scales the ocean vertices on X and Z axes, but does not scale wave height. Applies a [code skip-lint]PhysicsMaterial[/code] override to the entire terrain StaticBody. Alias for [member Terrain3DCollision.physics_material] See that entry for details. The number of vertices in each region, and the number of pixels for each map in [Terrain3DRegion]. 1 pixel always corresponds to 1 vertex. [member Terrain3D.vertex_spacing] laterally scales regions, but does not change the number of vertices or pixels in each. There is no undo for this operation. However you can apply it again to reslice, as long as your data doesn't hit the maximum boundaries. The render layers the terrain is drawn on. This sets [code skip-lint]VisualInstance3D.layers[/code] in the engine. The defaults is layer 1 and 32 (for the mouse cursor). When you set this via code, make sure the layer for [member mouse_layer] is included, or set that variable again after this so that the mouse cursor and [method get_intersection] work. If enabled, heightmaps are saved as 16-bit half-precision to reduce file size. Files are always loaded in 32-bit for editing. Upon save, a copy of the heightmap is converted to 16-bit for writing. It does not change what is currently in memory. This process is lossy. 16-bit precision gets increasingly worse with every power of 2. At a height of 256m, the precision interval is .25m. At 512m it is .5m. At 1024m it is 1m. Saving a height of 1024.4m will be rounded down to 1024m. Displays the area designated for use by the autoshader, which shows materials based upon slope. Alias for [member Terrain3DMaterial.show_autoshader]. Shows a checkerboard display using a shader rendered pattern. This is turned on if the Texture List is empty. Note that when a blank texture slot is created, a 1k checkerboard texture is generated and stored in the texture slot. That takes VRAM. The two patterns have a slightly different scale. Alias for [member Terrain3DMaterial.show_checkered]. Shows the color map in the albedo channel. Alias for [member Terrain3DMaterial.show_colormap]. Overlays contour lines on the terrain. Customize the options in the material when enabled. Press `4` with the mouse in the viewport to toggle. Alias for [member Terrain3DMaterial.show_contours]. Albedo shows the painted angle. Orange means 0°, Yellow 270°, Cyan 180°, Violet 90°. Or warm colors towards -Z, cool colors +Z, greens/yellows +X, reds/blues -X. Draw all angles coming from the center of a circle for a better understanding. Alias for [member Terrain3DMaterial.show_control_angle]. Displays the values used to blend the textures. Blue shows the autoshader blending, red shows manually painted blending. Alias for [member Terrain3DMaterial.show_control_blend]. Albedo shows the painted scale. Larger scales are more red, smaller scales are more blue. 0.5 middle grey is the default 100% scale. Alias for [member Terrain3DMaterial.show_control_scale]. Albedo shows the base and overlay texture indices defined by the control map. Red pixels indicate the base texture, with brightness showing texture ids 0 to 31. Green pixels indicate the overlay texture. Yellow indicates both. Alias for [member Terrain3DMaterial.show_control_texture]. Alias for [member Terrain3DMaterial.show_displacement_buffer]. Albedo is set to 0.2 grey. Alias for [member Terrain3DMaterial.show_grey]. Alias for [member Terrain3DMaterial.show_region_grid]. Press `1` with the mouse in the viewport to toggle. Albedo is a white to black gradient depending on height. The gradient is scaled to a height of 300, so above that or far below 0 will be all white or black. Alias for [member Terrain3DMaterial.show_heightmap]. Overlays the 32x32m instancer grid on the terrain, which shows how the instancer data is partitioned. Press `2` with the mouse in the viewport to toggle. Alias for [member Terrain3DMaterial.show_instancer_grid]. Highlights non-smooth areas of the terrain. Jagged peaks, troughs, or edges that are a bit rough with sharp angles between vertices. Alias for [member Terrain3DMaterial.show_jaggedness]. Displays the area designated for generating the navigation mesh. Alias for [member Terrain3DMaterial.show_navigation]. Overlays the region grid on the terrain. This is more accurate than the region grid gizmo for determining where the region border is when editing. Press `1` with the mouse in the viewport to toggle. Alias for [member Terrain3DMaterial.show_region_grid]. Albedo is set to the roughness modification map as grey scale. Middle grey, 0.5 means no roughness modification. Black would be high gloss while white is very rough. Alias for [member Terrain3DMaterial.show_roughmap]. Albedo textures are shown only. Other channels are excluded. Alias for [member Terrain3DMaterial.show_texture_albedo]. Albedo is set to the painted Ambient Occlusion textures. Alias for [member Terrain3DMaterial.show_texture_ao]. Albedo is set to the painted Height textures. Alias for [member Terrain3DMaterial.show_texture_height]. Albedo is set to the painted Normal textures. Alias for [member Terrain3DMaterial.show_texture_normal]. Albedo is set to the painted Roughness textures. This is different from the roughness modification map above. Alias for [member Terrain3DMaterial.show_texture_rough]. Overlays the vertex grid on the terrain, showing where each vertex is. Press `3` with the mouse in the viewport to toggle. Alias for [member Terrain3DMaterial.show_vertex_grid]. Enables displacement using texture heights for additional mesh detail when set above 0. This creates up to 6 additional subdivisions of the terrain mesh below LOD0, and adds a displacement buffer configurable in the material. You can see this in wireframe mode. Set to 0 to disable displacement. See [member Terrain3DMaterial.show_displacement_buffer] and look at the Displacement Buffer debug view. The current version of Terrain3D. The distance between vertices, settable up to 100. Godot units are typically considered to be meters. This laterally scales the terrain on X and Z axes. This variable changes the global position of landscape features. A mountain peak might be at (512, 512), but with a vertex spacing of 2.0 it is now located at (1024, 1024). It retains the same height. All Terrain3D functions with a global_position expect an absolute global value. If you would normally use [method Terrain3DData.import_images] to import an image in the region at (-1024, -1024), with a vertex_spacing of 2, you'll need to import that image at (-2048, -2048) to place it in the same region. To scale heights, export the height map and reimport it with a new height scale. Emitted when [member assets] is changed. Emitted when [member material] is changed. Errors and warnings always print. Typically every function call and other important informational messages. Detailed steps within functions. Messages for continuous operations like snapping and editing. The region size is 64 x 64 meters, vertices, and pixels on Image maps. The region size is 128 x 128 meters, vertices, and pixels on Image maps. The region size is 256 x 256 meters, vertices, and pixels on Image maps. (default) The region size is 512 x 512 meters, vertices, and pixels on Image maps. The region size is 1024 x 1024 meters, vertices, and pixels on Image maps. The region size is 2048 x 2048 meters, vertices, and pixels on Image maps. ================================================ FILE: doc/doc_classes/Terrain3DAssets.xml ================================================ This class contains arrays of [Terrain3DTextureAsset] and [Terrain3DMeshAsset] resources. It is a savable resource, so you can save it to disk and use the same asset list in multiple scenes that use Terrain3D. The amount of data is small, so it can be saved as a git-friendly, text based .tres file or left within the scene file. After textures are loaded, they are combined into a TextureArray. The originals remain in VRAM and are only used if the [Terrain3DTextureAsset] settings are changed and regenerating the TextureArrays are necessary. Use this function to clear the originals if not needed. It removes all textures from the asset list, freeing them if they are not referenced by other objects. Update will regenerate the texture arrays housing the textures drawn on the terrain. This will remove all textures and turn the terrain checkerboard. A similar [code skip-lint]clear_meshes[/code] is less useful so hasn't been included. However you can do the same thing with [code skip-lint]get_mesh_list().clear()[/code]. Generates mesh asset preview thumbnails for the asset dock, stored within each mesh asset. Specify id -1 to generate all. By default, mesh thumbnails are not recreated if they already exist. Specify [code skip-lint]force[/code] to regenerate existing thumbnails. Returns the resource ID of the TextureArray generated from combining all albedo and height textures. Returns the specified Terrain3DMeshAsset resource. Returns the number of mesh assets in the list. Returns the resource ID of the TextureArray generated from combining all normal and roughness textures. Returns the array of ao light affects values each texture asset, indexed by asset id. Returns the array of AO strengths for each texture asset, indexed by asset id. Returns the Terrain3DTextureAsset with the specified ID. Returns the array of albedo tints for each texture asset, indexed by asset id. Returns the number of texture slots used. Returns the array of detiling values for each texture asset, indexed by asset id. Returns the array of displacement offset and scale values for each texture asset, indexed by asset id. Returns the array of normal strengths for each texture asset, indexed by asset id. Returns the array of roughness modification values for each texture asset, indexed by asset id. Returns the array of uv scale values for each texture asset, indexed by asset id. Saves this texture list resource to disk, if saved as an external [code skip-lint].tres[/code] or [code skip-lint].res[/code] resource file. path - specifies a directory and file name to use from now on. Assigns the Terrain3DMeshAsset to the specified ID slot. It can be null to clear the slot. See [method set_texture_asset]. Adds a Terrain3DTextureAsset at the specified ID slot. The texture can be null to clear the slot, or remove it if its the last in the list. If the specified slot is full, it will be swapped with the source texture ID, or will find the next available ID. Updates the internal list of meshes used by the instancer. Regenerates the texture arrays from the list of texture assets, which is sent to the shader. The list of mesh assets. The list of texture assets. Emitted when the mesh list is updated, which happens as a result of a [Terrain3DMeshAsset] changing. Emitted when this list is updated due to changes in the texture slots, or the files or settings of any [Terrain3DTextureAsset]. Asset is type Terrain3DTextureAsset. Asset is type Terrain3DMeshAsset. Hard coded maximum number of textures, with IDs in the range of 0-31. Cannot easily be expanded. Limit of the maximum number of meshes. Arbitrary, easily expanded. ================================================ FILE: doc/doc_classes/Terrain3DCollision.xml ================================================ This class manages collision. Creates collision shapes and calls [method update] to shape them. Calls [method destroy] first, so it is safe to call this to fully rebuild collision any time. Removes all collision shapes and frees any memory used. Returns the RID of the active StaticBody. Returns true if [member mode] is [code skip-lint]Dynamic / Editor[/code] or [code skip-lint]Dynamic / Game[/code]. Returns true if [member mode] is [code skip-lint]Full / Editor[/code] or [code skip-lint]Dynamic / Editor[/code]. Returns true if [member mode] is not [code skip-lint]Disabled[/code]. - If [member mode] is Full, recalculates the specified or all existing collision shapes. Specify a [code skip-lint]region_location[/code] to update only that region, or omit it or use `Vector2i.MAX` to update all regions. If regions have been added or removed, set [code skip-lint]rebuild[/code] to true or call [method build] instead. A full update can be slow. - If [member mode] is Dynamic, repositions collision shapes around the camera and recalculates ones that moved. Set [code skip-lint]rebuild[/code] to true to recalculate all shapes within [member radius]. [code skip-lint]region_location[/code] is ignored. This is very fast, and can be updated at 60fps for little cost. The physics layers the terrain lives on. Sets [code skip-lint]CollisionObject3D.collision_layer[/code]. Also see [member mask]. The physics layers the physics body scans for colliding objects. Sets [code skip-lint]CollisionObject3D.collision_mask[/code]. Also see [member layer]. The selected mode determines if collision is generated and how. See [enum CollisionMode] for details. Applies a [code skip-lint]PhysicsMaterial[/code] override to the entire terrain StaticBody. There's no ability built into Godot to change physics material parameters based on texture or any other factor. However, it might be possible to extend `PhysicsMaterial` in order to inject code into the queries. It would need references to an object position and a terrain, and then it could run [method Terrain3DData.get_texture_id] based on the position and return different physics settings per texture. That would change the settings for the entire terrain for that moment. The priority with which the physics server uses to solve collisions. The higher the priority, the lower the penetration of a colliding object. Sets [code skip-lint]CollisionObject3D.collision_priority[/code]. If [member mode] is Dynamic, this is the distance range within which collision shapes will be generated. If [member mode] is Dynamic, this is the size of each collision shape. No collision shapes will be generated. Collision shapes are generated around the camera as it moves; in game only. Collision shapes are generated around the camera as it moves; in the editor and in game. Enable [code skip-lint]View Gizmos[/code] in the viewport menu to see them. Collision shapes are generated for all regions in game only. One shape per region. Collision shapes are generated for all regions in the editor and in game. One shape per region. This mode is necessary for some 3rd party plugins to detect the terrain using collision. Enable [code skip-lint]View Gizmos[/code] in the viewport menu to see the collision mesh. ================================================ FILE: doc/doc_classes/Terrain3DData.xml ================================================ Terrain3D divides all data into regions which fit on a grid in the world. These coordinates are called region locations. The map data are stored in instances of [Terrain3DRegion], which are saved to individual files. This class manages region loading, unloading, data retreival and manipulation. Adds a region for sculpting and painting. The region should already be configured with the desired location and maps before sending to this function. Upon saving, this region will be written to a data file stored in [member Terrain3D.data_directory]. - update - regenerates the texture arrays if true. Set to false if bulk adding many regions, then true on the last one or use [method update_maps]. Creates and adds a blank region at the specified location. See [method add_region]. Creates and adds a blank region at a region location encompassing the specified global position. See [method add_region]. Recalculates the master height range for the whole terrain by summing the height ranges of all active regions. Recursive mode does the same, but has each region recalculate heights from each heightmap pixel. See [method Terrain3DRegion.calc_height_range]. Reslices terrain data to fit the new region size. This is a destructive process for which there is no undo. However Godot does make an undo entry, which will reslice in reverse. Files on disk are not added or removed until the scene is saved. Calls the callback function for every region within the given area. If using vertex_spacing, area values should be descaled. The callable receives: source Terrain3DRegion, source Rect2i, dest Rect2i, (bindings) You may wish to append .bind() to the callback to pass along variables. For instance internally this function is called when changing region size. We bind the destination Terrain3DRegion, then use do_for_regions to copy segments of source regions to segments of destination regions. See the code for change_region_size() for more. Calls [method Terrain3DRegion.dump] for all regions loaded, active and inactive. Exports the specified map type as one of r16/raw, exr, jpg, png, webp, res, tres. R16 or exr are recommended for roundtrip external editing. R16 can be edited by Krita, however you must know the dimensions and min/max before reimporting. This information is printed to the console. Res/tres stores in Godot's native data format. Returns the associated pixel on the color map at the requested position. Returns [code skip-lint]Color(NAN, NAN, NAN, NAN)[/code] if the position is outside of defined regions. Returns the resource ID of the generated height map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See [url=https://terrain3d.readthedocs.io/en/stable/docs/tips.html#using-the-generated-height-map-in-other-shaders]Tips[/url] for an example. Returns the associated pixel on the control map at the requested position. Returns [code skip-lint]4,294,967,295[/code] aka [code skip-lint]UINT32_MAX[/code] if the position is outside of defined regions. Returns the angle, aka uv rotation, on the control map at the requested position. Values are fixed to 22.5 degree intervals, for a maximum of 16 angles. 360 / 16 = 22.5. Returns [code skip-lint]NAN[/code] if the position is outside of defined regions. Returns whether the autoshader is enabled on the control map at the requested position. Returns [code skip-lint]false[/code] if the position is outside of defined regions. Returns the base texture ID on the control map at the requested position. Values are 0 - 31, which matches the ID of the texture asset in the asset dock. Returns [code skip-lint]4,294,967,295[/code] aka [code skip-lint]UINT32_MAX[/code] if the position is outside of defined regions. Returns the blend value between the base texture ID and the overlay texture ID. The value is clamped between 0.0 - 1.0 where 0.0 shows only the base texture, and 1.0 shows only the overlay texture. Returns [code skip-lint]NAN[/code] if the position is outside of defined regions. Returns whether there is a hole on the control map at the requested position. Returns [code skip-lint]false[/code] if the position is outside of defined regions. Returns the resource ID of the generated control map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See [url=https://terrain3d.readthedocs.io/en/stable/docs/tips.html#using-the-generated-height-map-in-other-shaders]Tips[/url] for an example. Returns whether navigation is enabled on the control map at the requested position. Returns [code skip-lint]false[/code] if the position is outside of defined regions. Returns the overlay texture ID on the control map at the requested position. Values are 0 - 31, which matches the ID of the texture asset in the asset dock. Returns [code skip-lint]4,294,967,295[/code] aka [code skip-lint]UINT32_MAX[/code] if the position is outside of defined regions. Returns the uv scale on the control map at the requested position. The value is rounded to the nearest 20% difference from 100%, ranging between -60% to +80%. Eg. +20% or -40%. Returns [code skip-lint]NAN[/code] if the position is outside of defined regions. Returns the height at the requested position. If the position is close to a vertex, the pixel height on the heightmap is returned. Otherwise the value is interpolated from the 4 vertices surrounding the position. Returns [code skip-lint]NAN[/code] if the requested position is a hole or outside of defined regions. Also see [method Terrain3D.get_raycast_result] and [method Terrain3D.get_intersection] for alternative functions Returns the resource ID of the generated height map texture array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See [url=https://terrain3d.readthedocs.io/en/stable/docs/tips.html#using-the-generated-height-map-in-other-shaders]Tips[/url] for an example. Returns the highest and lowest heights for the sculpted terrain used to set the world AABB. See [method calc_height_range]. Any [member Terrain3DMaterial.world_background] used that extends the mesh outside of this range will not change this variable. You need to set [member Terrain3D.cull_margin] or the renderer will clip meshes. Returns an Array of Images from all regions of the specified map type. Returns the position of a terrain vertex at a certain LOD. If the position is outside of defined regions or there is a hole, it returns [code skip-lint]NAN[/code] in the vector's Y coordinate. [code skip-lint]lod[/code] - Determines how many heights around the given global position will be sampled. Range 0 - 8. [code skip-lint]filter[/code] - Specifies how samples are filtered. See [enum HeightFilter]. [code skip-lint]global_position[/code] - X and Z coordinates of the vertex. Heights will be sampled around these coordinates. Returns the terrain normal at the specified position. This function uses [method get_height]. Returns [code skip-lint]Vector3(NAN, NAN, NAN)[/code] if the requested position is a hole or outside of defined regions. Returns the pixel for the map type associated with the specified position. Returns [code skip-lint]Color(NAN, NAN, NAN, NAN)[/code] if the position is outside of defined regions. Return the [Terrain3DRegion] at the specified location. This will return inactive regions marked for deletion. Check with [member Terrain3DRegion.deleted]. Returns the number of active regions; those not marked for deletion. Returns -1 if no region or out of bounds at the given location, otherwise returns the current region id. The region_id is the index into the TextureArrays sent to the shader, and can change at any time. Gamedevs should generally index regions by location. However, this function is useful to determine if the location is a valid region. Returns the region id at a global position. See [method get_region_id]. Returns the calculated region location for the given global position. This is just a calculation and does no bounds checking or verification that a region exists. See [method get_region_map_index] for bounds checking, or [method has_region] for checking existance. Returns a fully populated 32 x 32 array. The array location contains the region id + 1, or 0, which means no region. See [method get_region_map_index]. Given a region location, returns the index into the region map array. See [method get_region_map]. You can use this function to quickly determine if a location is within the greater world bounds (-16,-16) to (15, 15). It returns -1 if not. Returns the region at the specified global position. This will return inactive regions marked for deletion. Check with [member Terrain3DRegion.deleted]. Returns an array of active regions not marked for deletion. Each region knows its own location. See [member Terrain3DRegion.location]. - copy - returns a shallow copy of the regions; region map references are copied. - deep - returns a deep copy of the regions; region maps are full duplicates. Returns all regions in a dictionary indexed by region location. Some regions may be marked for deletion. Returns the roughness modifier (wetness) on the color map alpha channel associated with the specified position. Returns [code skip-lint]Color(NAN, NAN, NAN, NAN)[/code] if the position is outside of defined regions. Returns [code skip-lint]Vector3(base texture id, overlay id, blend value)[/code]. Returns [code skip-lint]Vector3(NAN, NAN, NAN)[/code] if the position is a hole or outside of defined regions. This is often used for playing sounds on footsteps. It's up to the gamedev to determine which is visually apparent based on shader settings. Due to blending, it won't be pixel perfect. Try having your player controller print this value while walking around to see how the blending values look. Perhaps you'll find that the overlay texture is visible starting at a blend value of .3 to .5, otherwise the base is visible. You can also observe the control blend debug view with [member Terrain3DMaterial.show_control_blend]. Observing how this is done in The Witcher 3, there are only about 6 sounds used (snow, foliage, dirt, gravel, rock, wood), and except for wood, they are not pixel perfect. Wood is easy to do by detecting if the player is walking on wood meshes. The other 5 sounds are played when the player is in an area where the textures are blending. So it might play rock while over a dirt area. This shows pixel perfect accuracy is not important. It will still provide a seamless audio visual experience. Returns true if the specified region location has an active region. Returns true if the specified global position has an active region. Imports an Image set (Height, Control, Color) into this resource. It does NOT normalize values to 0-1. You must do that using get_min_max() and adjusting scale and offset. [code skip-lint]images[/code] - MapType.TYPE_MAX sized array of Images for Height, Control, Color. Images can be blank or null. [code skip-lint]global_position[/code] - X,0,Z position on the region map. Valid range is [member Terrain3D.vertex_spacing] * [member Terrain3D.region_size] * (+/-16, +/-16). [code skip-lint]offset[/code] - Add this factor to all height values, can be negative. [code skip-lint]scale[/code] - Scale all height values by this factor (applied after offset). Returns true if the slope of the terrain at the given position is within the slope range. If normal is provided it will use that instead of querying the terrain. Returns true if the region at the location exists and is marked as deleted. Syntactic sugar for [member Terrain3DRegion.deleted]. Returns true if the region at the location exists and is marked as modified. Syntactic sugar for [member Terrain3DRegion.modified]. Returns an Image of the given map type that contains all regions in one large image. If the world has multiple islands, this function will return an image large enough to encompass all used regions, with black areas in between the islands. Loads all of the Terrain3DRegion files found in the specified directory. Then it rebuilds all map arrays. Loads the specified region location file. - update - rebuild maps if true. Marks the specified region as deleted. This deactivates it so it won't render it on screen once maps are updated, unless marked not deleted. The file will be deleted from disk upon saving. Removes the region at the specified location. See [method remove_region]. Removes the region at the specified global_position. See [method remove_region]. This saves all active regions into the specified directory. Saves the specified active region to the directory. See [method Terrain3DRegion.save]. - region_location - the region to save. - 16_bit - converts the edited 32-bit heightmap to 16-bit. This is a lossy operation. Sets the color on the color map pixel associated with the specified position. See [method set_pixel] for important information. Sets the value on the control map pixel associated with the specified position. See [method set_pixel] for important information. Sets the angle, aka uv rotation, on the control map at the requested position. Values are rounded to the nearest 22.5 degree interval, for a maximum of 16 angles. 360 / 16 = 22.5. See [method set_pixel] for important information. Sets if the material should render the autoshader or manual texturing on the control map at the requested position. See [method set_pixel] for important information. Sets the base texture ID on the control map at the requested position. Values are clamped to 0 - 31, matching the ID of the texture asset in the asset dock. See [method set_pixel] for important information. Sets the blend value between the base texture ID, and the overlay texture ID. The value is clamped between 0.0 - 1.0 where 0.0 shows only the base texture, and 1.0 shows only the overlay texture. See [method set_pixel] for important information. Sets if a hole should be rendered on the control map at the requested position. See [method set_pixel] for important information. Sets if navigation generation is enabled on the control map at the requested position. See [method set_pixel] for important information. Sets the overlay texture ID on the control map at the requested position. Values are clamped to 0 - 31, matching the ID of the texture asset in the asset dock. See [method set_pixel] for important information. Sets the uv scale on the control map at the requested position. The value is rounded to the nearest 20% difference from 100%, ranging between -60% to +80%. See [method set_pixel] for important information. Sets the height value on the heightmap pixel associated with the specified position. See [method set_pixel] for important information. Unlike [method get_height], which interpolates between vertices, this function does not and will set the pixel at floored coordinates. Sets the pixel for the map type associated with the specified position. This method is fine for setting a few pixels, but if you wish to modify thousands of pixels quickly, you should get the region and use [method Terrain3DRegion.get_map], then edit the images directly. After setting pixels you need to call [method update_maps]. You may also need to regenerate collision if you don't have dynamic collision enabled. Marks a region as deleted. It will stop displaying when maps are updated. The file will be removed on save. Sets the region as modified. It will be written to disk when saved. Syntactic sugar for [member Terrain3DRegion.modified]. Sets the roughness modifier (wetness) on the color map alpha channel associated with the specified position. See [method set_pixel] for important information. Regenerates the region map and the TextureArrays that combine the requested map types. This function needs to be called after editing any of the maps. By default, this function rebuilds all maps for all regions. - map_type - Regenerate only maps of this type. - all_regions - Regenerate all regions if true, otherwise only those marked with [member Terrain3DRegion.edited]. - generate_mipmaps - Regenerate mipmaps if map_type is color or all (max), for the regions specified above. This can also be done on individual regions before calling this function with [code skip-lint]region.get_color_map().generate_mipmaps()[/code]. For frequent editing, rather than enabling all_regions, it is more optimal to only update changed regions as follows: [codeblock] terrain.data.set_height(global_position, 10.0) var region:Terrain3DRegion = terrain.data.get_regionp(global_position) region.set_edited(true) terrain.data.update_maps(Terrain3DRegion.TYPE_HEIGHT, false) region.set_edited(false) [/codeblock] An Array[Image] containing references to all of the color maps in all regions. See [member Terrain3DRegion.color_map]. An Array[Image] containing references to all of the control maps in all regions. See [member Terrain3DRegion.control_map]. An Array[Image] containing references to all of the height maps in all regions. See [member Terrain3DRegion.height_map]. The array of all active region locations; those not marked for deletion. Emitted when the color maps array is regenerated. Emitted when the control maps array is regenerated. Emitted when the height maps array is regenerated. Emitted when the region map or any map array has been regenerated. This signal is emitted whenever the editor ([Terrain3DEditor]) is used to: - add or remove a region - alter a region map with a brush tool - undo or redo any of the above operations The parameter contains the axis-aligned bounding box of the area edited. Emitted when the region map is regenerated. Samples the height map at the exact coordinates given. Samples (1 << lod) * 2 heights around the given coordinates and returns the lowest. Hard coded number of regions on a side. The total number of regions is this squared. ================================================ FILE: doc/doc_classes/Terrain3DEditor.xml ================================================ This class handles all of the sculpting and painting operations for Terrain3D. Undo the previous changes, with the provided data. Used by Godot, not gamedevs. Adds a region to the currently pending operation undo snapshot. [method is_operating] must be true. Returns the current selected tool operation (eg. add, subtract). Returns the instance of Terrain3D this class is conneced to. Returns the current tool selected in the editor plugin. Returns true if currently in the middle of a brushing operation. Start brushing. Sets all brush settings used in the editor plugin. Sets the tool operation used in the editor plugin. Sets the instance of Terrain3D this class is connected to. Sets the tool selected in the editor plugin. Begin a sculpting or painting operation. Prepares to create an undo/redo commit. End a sculpting or painting operation. Commits any regions marked with [member Terrain3DRegion.edited] in the undo/redo system and clears that flag. Additive operations. Subtractive operations. Replacing operations. Averaging operations. Gradient operations. The number of elements in this enum. Sculpt heights. Paint textures. Paint on the color map. Paint a roughness modifier, aka wetness. Paint textures rotated by an angle. Paint textures scaled by a value. Paint where the shader automatically textures. Paint where vertices will be invalidated to leave holes. Paint where navigation will be generated. Paint MultiMesh instances on the ground. Add/remove regions. The number of elements in this enum. ================================================ FILE: doc/doc_classes/Terrain3DInstancer.xml ================================================ This class places mesh instances defined in the Terrain3D asset dock into MultiMeshInstance3Ds on the ground. Data is currently stored in [member Terrain3DRegion.instances] and loaded into MultiMeshInstances, which are attached to the scene tree and managed by this class. [b]The methods available for adding instances are:[/b] - [method add_transforms] - Accepts your list of transforms and parses them by region and cell location and stores in our data storage. Recommended for general API instancing. - [method add_multimesh] - Pulls the transforms out of your MultiMesh and calls add_transforms. - [method add_instances] - A feature rich function designed for hand editing via Terrain3DEditor. - Creating your own instance data and inserting it directly into [member Terrain3DRegion.instances]. It's not difficult to do this in GDScript, but a thorough understanding of the C++ code in this class is recommended. [b]The methods available for removing instances are:[/b] - [method remove_instances] - Like add_instances, this is can be used procedurally but is designed for hand editing. - [method clear_by_mesh], [method clear_by_location] - To erase large sections of instances. - Editing [member Terrain3DRegion.instances] directly. After modifying region data, run [method update_mmis] to rebuild the MultiMeshInstance3Ds. [b]Read More:[/b] - [b]Tutorial:[/b] [url=https://terrain3d.readthedocs.io/en/stable/docs/instancer.html]Foliage Instancing[/url] - [b]Godot Reference:[/b] [url=https://docs.godotengine.org/en/stable/classes/class_multimesh.html]MultiMesh[/url] Used by Terrain3DEditor to place instances given many brush parameters. In addition to the brush position, it also uses the following parameters: asset_id:int, size:float, strength:float, fixed_scale:float, random_scale:float, fixed_spin:float, random_spin:float, fixed_tilt:float, random_tilt:float, align_to_normal:bool, height_offset:float, random_height:float, vertex_color:Color, random_hue:float, random_darken:float, slope:Vector2, on_collision:bool, raycast_height:float. All of these settings are set in the editor via tool_settings.gd. Allows procedural placement of meshes, or importing from another MultiMeshInstancer placement tool. The specified mesh_id should already be setup as a [Terrain3DMeshAsset] in the asset dock. This function extracts the instance transforms and colors from a multimesh and passes it to [method add_transforms]. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. Allows procedural placement of meshes. The mesh_id should already be setup as a [Terrain3DMeshAsset] in the asset dock. You provide the array of Transform3Ds and optional Colors, which will be parsed into our data storage. This function adds the [member Terrain3DMeshAsset.height_offset] to the transform along its local Y axis. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. Appends new transforms to the existing data within a region location. The mesh_id should already be setup as a [Terrain3DMeshAsset] in the asset dock. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. Appends new transforms to the existing data within a region location. The mesh_id should already be setup as a [Terrain3DMeshAsset] in the asset dock. Update will regenerate the MultiMeshInstances. Disable for bulk adding, then call at the end. Removes all instancer data and MultiMeshInstance nodes attached to the tree for the specified region location and mesh id. Removes all instancer data and MultiMeshInstance nodes attached to the tree for all regions for the specified mesh id. Removes all instancer data and MultiMeshInstance nodes attached to the tree for the specified region and mesh id. Returns the mesh instance ID closest to the specified global position on the ground. Returns true if [member mode] is not disabled. Uses parameters asset_id:int, size:float, strength:float, slope:Vector2, on_collision:bool, raycast_height:float to randomly remove instances within the indicated brush position and size. Swaps the ID of two meshes without changing the mesh instances on the ground. Queues a request to rebuild the MMIs for the specified IDs and regions on the next RenderingServer.frame_pre_draw signal. This is safe to call multiple times per frame and it will de-duplicate and do the most general requests. So if you first call it for region (0,0), mesh 52, then later in the frame ask for all regions, mesh 52, it will do only the latter. - mesh_id - rebuild MMIs for this mesh id. Use `-1` for all IDs. - region_location - rebuild MMIs for this region. Use `Vector2i.MAX` for all regions. - rebuild_all - destroy all MMIs first before rebuilding. Reviews all existing instance transforms within an AABB and adjusts their heights to match the terrain. Normal - Generates MMIs and renders all instances as normal. Disabled - prevents the instancer from creating any MultiMeshInstance3Ds. Create MultiMeshInstance3Ds and render instances as normal. Disables creation of MultiMeshInstance3Ds and instances. ================================================ FILE: doc/doc_classes/Terrain3DMaterial.xml ================================================ A custom shader material resource for Terrain3D. This class handles options for both the built-in shader and any custom override shader. It collects compiled texture data from the other classes and sends all of it to the shader via the RenderingServer. It is a savable resource, so you can save it to disk and use the same material settings in multiple scenes that use Terrain3D. The amount of data is small, assuming you have saved your shader parameter textures to disk, so it can be saved as a git-friendly, text based .tres file or left within the scene file. While it does mimic some of the functionality of ShaderMaterial, it does not derive from any of the Godot Material classes. It will fail any [code skip-lint]is Material[/code] checks. It is a [code skip-lint]Resource[/code]. Inspector settings above `Custom Shader` and [member shader_override] are used to determine what code is used in the current shader. Inspector settings in `Shader Uniforms` are the public uniforms (not prefaced with `_`) available in the current shader. Returns the RID of the displacement buffer material used with the Rendering Server. This is set per instance of this class. Returns the RID of the displacement buffer shader used with the Rendering Server. Returns the RID of the material used with the Rendering Server. This is set per instance of this class. Retrieve a parameter from the active shader (built-in or override shader). Returns the RID of the built in shader used with the Rendering Server. This is different from any shader override which has its own RID. Saves this material resource to disk, if saved as an external [code skip-lint].tres[/code] or [code skip-lint].res[/code] resource file. path - specifies a directory and file name to use from now on. Set a parameter in the active shader (built-in or override shader). Sends uniform values to the shader. See [enum UpdateFlags] for options. This private dictionary stores all of the shader parameters in the resource. It is not a cache. Enables selecting two texture IDs that will automatically be applied to the terrain based upon slope. If buffer_shader_override_enabled is true and this Shader is valid, the displacement buffer material will use this custom shader code. If this is blank when you enable the override, the system will generate a shader with the current settings. A visual shader will also work here. However we only generate a text based shader so currently a visual shader needs to be constructed with the base code before it can work. Enables use of the [member buffer_shader_override] shader code. Generates default code if shader_override is blank. A global multiplier for all displaced textures. This is the maximum distance that 2 adjacent verticies can be vertically seperated by. Setting this 1.0 would mean a maximum of + 0.5m, and -0.5m deviation from the collision mesh. Adjusts the transition between textures. When set at `1.0`, the blending of displacment between textures will match the aldebo/normal blend sharpness exactly. Lower values will have a softer transition, avoiding harsh shapes, without compromising the abldeo and normal blend sharpness. If set at or very near to `0.0`, it is possible that more displaced textures can affect less displaced textures at low blend values even if not visible. Enables selecting one texture ID that will have multiple scales applied based upon camera distance. Use it for something like a rock texture so up close it will be nicely detailed, and far away mountains can be covered in the same rock texture without looking tiled. The two blend together at a specified distance. Allows you to add a couple of noise patterns at different scales and colors to add variation to your terrain to avoid tiled textures. Enables the Albedo, aka Base Color or Diffuse, output channel in the shader. Enables the Ambient Occlusion output channel in the shader. Enables the Normal Map output channel in the shader. Enables the Roughness output channel in the shader. Enables textures to be projected vertically when placed on slopes above 45 degrees. This is useful for mapping textures on cliff faces without stretching, even though the polygons are stretched. If shader_override_enabled is true and this Shader is valid, the material will use this custom shader code. If this is blank when you enable the override, the system will generate a shader with the current settings. So if you have a debug view enabled, the generated shader will have all of that code. A visual shader will also work here. However we only generate a text based shader so currently a visual shader needs to be constructed with the base code before it can work. Enables using the [member shader_override] shader. An editable shader is generated from the current one if shader_override is blank. The inspector settings above this group determine the code that is used in the current shader. The settings below are uniforms for the current shader. Displays the area designated for use by the autoshader, which shows materials based upon slope. Shows a checkerboard display using a shader rendered pattern. This is turned on if the Texture List is empty. Note that when a blank texture slot is created, a 1k checkerboard texture is generated and stored in the texture slot. That takes VRAM. The two patterns have a slightly different scale. Shows the color map in the albedo channel. Overlays contour lines on the terrain. Customize the options in the material when enabled. Press `4` with the mouse in the viewport to toggle. Albedo shows the painted angle. Orange means 0°, Yellow 270°, Cyan 180°, Violet 90°. Or warm colors towards -Z, cool colors +Z, greens/yellows +X, reds/blues -X. Draw all angles coming from the center of a circle for a better understanding. Displays the values used to blend the textures. Blue shows the autoshader blending, red shows manually painted blending. Albedo shows the painted scale. Larger scales are more red, smaller scales are more blue. 0.5 middle grey is the default 100% scale. Albedo shows the base and overlay texture indices defined by the control map. Red pixels indicate the base texture, with brightness showing texture ids 0 to 31. Green pixels indicate the overlay texture. Yellow indicates both. Shows the resulting displacement buffer vertex differential from 0. Black matches collision exactly. Green shows extrusions, Red for depressions into the terrain. Albedo is set to 0.2 grey. Albedo is a white to black gradient depending on height. The gradient is scaled to a height of 300, so above that or far below 0 will be all white or black. Overlays the 32x32m instancer grid on the terrain, which shows how the instancer data is partitioned. Press `2` with the mouse in the viewport to toggle. Highlights non-smooth areas of the terrain. Jagged peaks, troughs, or edges that are a bit rough with sharp angles between vertices. Displays the area designated for generating the navigation mesh. Overlays the region grid on the terrain. This is more accurate than the region grid gizmo for determining where the region border is when editing. Press `1` with the mouse in the viewport to toggle. Albedo is set to the roughness modification map as grey scale. Middle grey, 0.5 means no roughness modification. Black would be high gloss while white is very rough. Albedo textures are shown only. Other channels are excluded. Albedo is set to the painted Ambient Occlusion textures. Albedo is set to the painted Height textures. Albedo is set to the painted Normal textures. Albedo is set to the painted Roughness textures. This is different from the roughness modification map above. Overlays the vertex grid on the terrain, showing where each vertex is. Press `3` with the mouse in the viewport to toggle. Sets how the renderer should filter textures. See [enum TextureFiltering] for options. Sets how the mesh outside of defined regions behave. See [enum WorldBackground] for options. Outside of the defined regions, hide the mesh. Outside of the defined regions, show a flat terrain. Outside of the defined regions, generate visual-only hills. Textures are filtered using a blend of 4 adjacent pixels, with anisotropic filtering which improves the sharpness of distant terrain off axis from the camera. Use this for most cases for high quality renders. Textures are filtered using a blend of 4 adjacent pixels. Use this for most cases for high quality renders. Textures are filtered using the nearest pixel only with anisotropic filtering which improves the sharpness of distant terrain off axis from the camera. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale. Textures are filtered using the nearest pixel only. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale. Non-texture array values are assigned to the shader. This is the default and is always done. The ground texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`. The region data texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`. Values in `TEXTURE_ARRAYS` and `REGION_ARRAYS` are assigned to the shader. The shader is rebuilt, then all values in `UPDATE_ARRAYS` are assigned to the shader. ================================================ FILE: doc/doc_classes/Terrain3DMeshAsset.xml ================================================ This class manages meshes used for instancing. There are two broad types of meshes it can host. 1. Generated Texture Card - this class will generate a QuadMesh. The typical use for this is to create a material in the override material, place a 2D grass texture in the `albedo texture` slot, and enable alpha scissor. This will generate low poly grass. 2. Scene File - you can provide your own mesh in a scene file, which is specifically a PackedScene (.tscn, .scn, .glb, .fbx, etc). You can override the material if desired. MultiMeshes only support one mesh object, so complex objects like tree trunks and leaves, or a door frame and door either need to be combined into one object with multiple materials, or placed by another method. The system will look for MeshInstance3D nodes in the file to use as Levels of Detail (LODs). Ideally they have suffixes like `LOD0`, `LOD1`. We support up to 10 LODs, but recommend no more than 4. [b]Read More:[/b] - [b]Tutorial:[/b] [url=https://terrain3d.readthedocs.io/en/stable/docs/instancer.html]Foliage Instancing[/url] - [b]Godot Reference:[/b] [url=https://docs.godotengine.org/en/stable/classes/class_multimesh.html]MultiMesh[/url], [url=https://docs.godotengine.org/en/stable/classes/class_meshinstance3d.html#class-meshinstance3d]MultiMeshInstance3D[/url] Resets this resource to default settings. Returns the color of the current highlight material, if any. Returns the number of instances on the ground for this mesh asset. Returns the far visible distance for the specified Level of Detail (LOD). The near visible distance is the LOD range for the next closest LOD. Returns the [code skip-lint]Mesh[/code] resource for the specified Level of Detail (LOD). Returns the thumbnail generated by [Terrain3DAssets]. Returns true if the instances for this mesh asset are currently highlighted on the ground. For editor use. Enables or disables adding a highlight material with a random color to the overlay slot for all instances of this mesh asset. Sets the far visible distance for the specified Level of Detail (LOD). Tells the renderer how to cast shadows from this mesh asset onto the terrain and other objects. This sets [code skip-lint]GeometryInstance3D.cast_shadow[/code] on all MultiMeshInstances used by this mesh. Density is used to set the approximate default spacing between instances based on the size of the mesh. When painting meshes on the terrain, mesh density is multiplied by brush strength. This value is not tied to any real world unit. It is calculated as [code skip-lint]10.f / mesh->get_aabb().get_volume()[/code], then clamped to a sane range. If the calculated amount is inappropriate, increase or decrease it here. If enabled, MultiMeshInstance3Ds will be generated for this mesh asset. If > 0, sets [code skip-lint]GeometryInstance3D.fade_mode = self[/code], sets this margin in [code skip-lint]GeometryInstance3D.visibility_range_begin_margin[/code] and [code skip-lint]GeometryInstance3D.visibility_range_end_margin[/code] and adjusts the ranges to allow fading between lods. Limited to the smaller of half the distance between LOD0 and LOD1, or 64m. Currently broken and hidden in the inspector until [url=https://github.com/godotengine/godot/issues/102799]Godot issue #102799[/url] is fixed. Select if you want the generated texture card to have a single QuadMesh, 2 meshes rotated 90° in a cross, or 3 rotated at 60°. Sets the base size of the QuadMesh texture card. Increasing this size will expand from bottom, not the middle. If enabled, this mesh asset will be set to a generated QuadMesh to be used as a texture card. Vertically offsets the origin point of the mesh asset. For example, if you have a 2 meter diameter rock with the mesh origin point in the center, but you want all rocks to be sitting on the ground, you could enter 1 or 0.9 here and it will be placed near its edge. You can also adjust this when painting using the tool settings bar; both options are cummulative. The user settable ID of the mesh. You can change this to reorder meshes in the list. Sets the farthest Level of Detail (LOD) that will be rendered. Farther LODs (greater than this number) will be ignored. Sets the farthest Level of Detail (LOD) that will cast shadows. Farther LODs (greater than this number) won't cast shadows. If [member shadow_impostor] is greater than this number, the shadow impostor will still cast shadows. Sets [code skip-lint]GeometryInstance3D.visibility_range_end[/code] on all MultiMeshInstances used by this mesh at the closest Level of Detail (LOD), the most detailed. Allows the renderer to cull MMIs beyond this distance. The next LOD level will use this value for its [code skip-lint]GeometryInstance3D.visibility_range_begin[/code]. Set to 0 to disable culling and see this LOD at any distance. Sets the end visible range for LOD1. See [member lod0_range]. Sets the end visible range for LOD2. See [member lod0_range]. Sets the end visible range for LOD3. See [member lod0_range]. Sets the end visible range for LOD4. See [member lod0_range]. Sets the end visible range for LOD5. See [member lod0_range]. Sets the end visible range for LOD6. See [member lod0_range]. Sets the end visible range for LOD7. See [member lod0_range]. Sets the end visible range for LOD8. See [member lod0_range]. Sets the end visible range for LOD9. See [member lod0_range]. Provides the detected number of Levels of Detail (LODs) found in the provided scene file or generated mesh. Read only. This sets [code skip-lint]GeometryInstance3D.material_overlay[/code], which applies an overriding material using [code skip-lint]next_pass[/code] that overlays the base material. This sets [code skip-lint]GeometryInstance3D.material_override[/code], which replaces the rendered mesh material with this one. The user specified name for this asset. Specifies the PackedScene (.tscn, .scn, .glb, .fbx, etc) to load the mesh from. See the top description. Uses this lower quality Level of Detail (LOD) to calculate shadows (as an impostor) instead of the visible mesh. e.g. Normally each LOD casts its own shadows. Given LOD0-3, if [code skip-lint]shadow_impostor = 2[/code] then when LOD0-1 are visible, they are set to no shadows and LOD2 is set to cast shadows only. When LOD2-3 are visible, each casts their own shadow as normal. Increase to improve performance by lowering shadow quality. Shadow impostors are disabled if this is set to 0 or if [member cast_shadows] is set to shadows only. Sets [member VisualInstance3D.layers], which defines the rendering layers the MultiMeshInstance3Ds for this mesh asset are drawn on. Emitted when [member id] is changed. Emitted when instances of this mesh asset have been added or removed. Emitted when instancer specific settings are changed on this mesh asset, such as [member cast_shadows], and triggers an instancer rebuild. Emitted when settings are changed, other than those tracked by other signals. No generated mesh is in use, likely because a scene file is used. Generates a QuadMesh to be used as a texture card. Maximum value for this enum. ================================================ FILE: doc/doc_classes/Terrain3DRegion.xml ================================================ This resource stores all map data for Terrain3D. See [url=https://terrain3d.readthedocs.io/en/stable/docs/controlmap_format.html]Controlmap Format[/url] and [url=https://terrain3d.readthedocs.io/en/stable/docs/controlmap_format.html]Data Format Changelog[/url]. Recalculates the height range for this region by looking at every pixel in the heightmap. Unreferences the maps and resets all of the variables to default values. Dumps information about the data in the region, including instance IDs, counts, and data pointers. Returns a duplicate copy of this node, with references to the same image maps and multimeshes. - deep - Also make complete duplicates of the maps and multimeshes. Returns all data in this region in a dictionary. Returns the specified image map. Returns an Array[Image] with height, control, and color maps. Validates and adjusts the map size and format if possible, or creates a usable blank image in the right size and format. Sanitizes all map types. See [method sanitize_map]. Saves this region to the current file name. - path - specifies a directory and file name to use from now on. - 16-bit - save this region with 16-bit height map instead of 32-bit. This process is lossy. Does not change the bit depth in memory. Overwrites all local variables with values in the dictionary. Assigns the provided map to the desired map type. Expects an array with three images in it, and assigns them to the height, control, and color maps. When sculpting, this is called to provide the current height. It may expand the vertical bounds, which is used to calculate the terrain AABB. When sculpting the terrain, this is called to provide both a low and high height. It may expand the vertical bounds, which is used to calculate the terrain AABB. This validates the map size according to previously loaded maps. This map is used to paint color that blends in to the terrain textures. Image format: FORMAT_RGBA8, 32-bits per pixel as four 8-bit components. [b]RGB[/b] is used for color, which is multiplied by albedo in the shader. Multiply is a blend mode that only darkens. [b]A[/b] is used for a roughness modifier. A value of 0.5 means no change to the existing texture roughness. Higher than this value increases roughness, lower decreases it. This map tells the shader which textures to use where, how to blend, where to place holes, etc. Image format: FORMAT_RF, 32-bit per pixel as full-precision floating-point. However, we interpret these images as format: [url=https://docs.godotengine.org/en/stable/classes/class_renderingdevice.html#class-renderingdevice-constant-data-format-r32-uint]RenderingDevice.DATA_FORMAT_R32_UINT[/url] aka OpenGL RG32UI 32-bit per pixel as unsigned integer. See [url=https://terrain3d.readthedocs.io/en/stable/docs/controlmap_format.html]Control map format[/url]. This region is marked for deletion. It won't be rendered once [method Terrain3DData.update_maps] rebuilds the map index. The file will be deleted from disk on [method save]. This region is marked for updating by [method Terrain3DData.update_maps] and for undo/redo tracking when set between [method Terrain3DEditor.start_operation] and [method Terrain3DEditor.stop_operation]. The latter method clears the edited flag. This flag serves a different purpose than [member modified]. This map contains the real value heights for the terrain. Image format: FORMAT_RF, 32-bit per pixel as full-precision floating-point. Heights sent to the vertex shader on the GPU which modifies the mesh in real-time. Editing is always done in 32-bit. We do provide an option to save as 16-bit, see [member Terrain3D.save_16_bit]. The current minimum and maximum height range for this region, used to calculate the AABB of the terrain. Update it with [method update_height], and recalculate it with [method calc_height_range]. A Dictionary that stores the instancer transforms for this region. The format is instances{mesh_id:int} -> cells{grid_location:Vector2i} -> ( Array:Transform3D, PackedColorArray, modified:bool ). That is: - A Dictionary keyed by mesh_id that returns: - A Dictionary keyed by the grid location of the 32 x 32m cell that returns: - A 3-item Array that contains: - 0: An Array of Transform3Ds - 1: A PackedColorArray with instance colors, same index as above - 2: A bool that tracks if this cell has been modified After changing this data, call [method Terrain3DInstancer.update_mmis] to rebuild the MMIs. The location in region grid space [code skip-lint](world space / region_size)[/code] coordinates. e.g. (-1, 1) equates to (-1024, 1024) in world space given a [member region_size] of 1024. This region has been modified and will be saved to disk upon [method save]. This serves a different purpose than the temporary [member edited] setting. The current region size for this region, calculated from the dimensions of the first loaded map. It should match [member Terrain3D.region_size]. The data file version. This is independent of the Terrain3D version, though they often align. Stored instancer transforms are laterally scaled by this value. This value is manage by the instancer on loading or when [member Terrain3D.vertex_spacing] is set, and shouldn't be manually adjusted. Height map - real values, eg. 10m, 44.5m. Control map - defines where textures and holes are placed. Color map - paints color on the terrain The number of elements in this enum. Often used to specify all map types for functions that request one. ================================================ FILE: doc/doc_classes/Terrain3DTextureAsset.xml ================================================ A set of texture files and settings that gets added to a [Terrain3DAssets] resource. Textures must be prepared according to the [url=https://terrain3d.readthedocs.io/en/stable/docs/texture_prep.html]documentation[/url]. Clears the texture files and settings. Returns the color of the current highlight, if any. Returns the generated thumbnail. Returns true if this texture is currently highlighted on the ground. For editor use. Enables or disables adding a random highlight color to this texture wherever placed on the ground. This color is multiplied by the albedo texture in the shader. The texture file with albedo on RGB and height on A. This value is applied directly to the AO_LIGHT_AFFECT in the shader, which dictates how much ambient occlusion affects light from Light3Ds. 0 means AO only affects ambient light. 1 means it also affects lights. The strength of ambient occlusion, which darkens areas of this texture dictated by the ambient occlusion map. If you have not provided an AO texture, an AO value is approximated by the normal map automatically. The shader rotates UV lookups in a detiling pattern based on this value. The shader laterally shifts UV lookups in a detiling pattern based on this value. Offset that can be used to raise or lower the displaced surface height for this material. Example: slightly lowering a cobblestone texture so the tops of the cobbles match collision. The scale of the displaced surface height for this material. The user settable ID of the texture, between 0 and 31. You can change this to reorder textures in the list, however it won't change the ID painted on the terrain. A user specified name for this texture set. Increases or decreases the strength of the normal texture. The texture file with normal on RGB and roughness on A. Increases or decreases the roughness texture values. The scale of the textures. Emitted when [member albedo_texture] or [member normal_texture] are changed. Emitted when [member id] is changed. Emitted when any setting is changed, other than id, albedo_texture, or normal_texture. ================================================ FILE: doc/doc_classes/Terrain3DUtil.xml ================================================ This class contains static utility functions. Reference them with the full class name. Eg. [code skip-lint]Terrain3DUtil.as_float()[/code]. Or you can instance the class for a shorter alias: [codeblock] var util := Terrain3DUtil.new() var my_float: float = util.as_float(my_int) [/codeblock] [b]Note on uints[/b]: Various functions refer to unsigned integers as uint. Though GDScript doesn't support unsigned integers as a type, the C++ that receives and returns these values will interpret them all as unsigned. [b]Note on bitwise-ORing[/b]: To write back to a control map, encode your values and bitwise OR the results, then reinterpret that uint as a float. The shader will interpret the float as uint and extract the bits. [codeblock] var bits: int = util.enc_base(base_id) | util.enc_overlay(over_id) | \ util.enc_blend(blend) | util.enc_uv_rotation(uvrotation) | \ util.enc_uv_scale(uvscale) | util.enc_auto(autoshader) | \ util.enc_nav(navigation) | util.enc_hole(hole) var color: Color = Color(util.as_float(bits), 0., 0., 1.) data.set_control(global_pos, color) [/codeblock] Returns a float typed variable with the contents of the memory stored in value, an integer typed variable. This function does not convert integer values to float values (e.g. 4 -> 4.0). It reinterprets the memory block as if it were a float. If the data in value was a valid integer, it is now an invalid float. [code skip-lint]my_float == util.as_float(util.as_uint(my_float))[/code] See [method as_uint] for the opposite. Returns an integer typed variable with the contents of the memory stored in value, a float typed variable. This function does not convert float values to integer values (e.g. 4.0 -> 4). It reinterprets the memory block as if it were an integer. If the data in value was a valid float, it is now a valid integer, but probably an unexepctedly large value. [code skip-lint]my_int == util.as_uint(util.as_float(my_int))[/code] See [method as_float] for the opposite. Receives an image with a black background and returns one with a transparent background, aka an alpha mask. Returns a control map uint with the auto shader bit set. See the top description for usage. Returns a control map uint with the base texture ID encoded. See the top description for usage. Returns a control map uint with the blend value encoded. See the top description for usage. Returns a control map uint with the hole bit set. See the top description for usage. Returns a control map uint with the nav bit set. See the top description for usage. Returns a control map uint with the overlay texture ID encoded. See the top description for usage. Returns a control map uint with the texture rotation encoded. See the top description for usage. See [method get_uv_rotation] for values. Returns a control map uint with the texture scale encoded. See the top description for usage. See [method get_uv_scale] for values. Converts a file name string like [code skip-lint]terrain3d-01_02.res[/code] to a region location like [code skip-lint](-1, 2)[/code]. - is negative, _ is positive. Returns the base texture ID from a control map pixel. Returns the blend value from a control map pixel. Returns an Image filled with a specified color and format. If [code skip-lint]color.a < 0[/code], its filled with a checkered pattern multiplied by [code skip-lint]color.rgb[/code]. The behavior changes if a compressed format is requested: - If the editor is running and the format is DXT1, DXT5, or BPTC_RGBA, it returns a filled image in the requested color and format. - All other compressed formats return a blank image in that format. The reason for this is the Image compression library is available only in the editor. And it is unreliable, offering little control over the output format, choosing automatically and often wrong. We have selected a few compressed formats it gets right. Returns the minimum and maximum r channel values of an Image. Used for heightmaps. Returns the overlay texture ID from a control map pixel. Returns an Image normalized and converted to RGB8. Used for creating a human viewable image of a heightmap, at any size. Returns the texture rotation from a control map pixel. Values are 0 - 15, which provides degrees when multiplied by 22.5. (360/16). Returns the texture scale modification from a control map pixel. Values are an index into the array `{ 0, 20, 40, 60, 80, -60, -40, -20 }`. 0 indicates no scale modification. Index 2 indicates a 40% increase in texture scale at that pixel. Index -1 or 7 indicates a -20% texture scale change. Returns true if the control map pixel has the autoshader bit set. Returns true if the control map pixel has the hole bit set. Returns true if the control map pixel has the nav bit set. Loads a file from disk and returns an Image. [code skip-lint]filename[/code] - The file name on disk to load. Loads EXR, R16/RAW, PNG, or a ResourceLoader format (jpg, res, tres, etc). [code skip-lint]cache_mode[/code] - Send this flag to the resource loader to force caching or not. [code skip-lint]height_range[/code] - Heights for R16 format. x=Min & y=Max value ranges. Required for R16 import. [code skip-lint]size[/code] - Image dimensions for R16 format. Default (0,0) auto detects size, assuming square images. Required for non-square R16. Converts a region location like [code skip-lint](-1, 2)[/code] to a file name string like [code skip-lint]terrain3d-01_02.res[/code]. - is negative, _ is positive. Generates a greyscale RGB8 height texture from the luminance values of the source image. Returns an RGBA Image packed for terrain usage. - [code skip-lint]src_rgb[/code] - The source Image for the RGB channels. - [code skip-lint]src_a[/code] - The source image for the A channel. - [code skip-lint]invert_green[/code] - Inverts the green channel to convert between OpenGL and DirectX normal maps. - [code skip-lint]invert_alpha[/code] - Inverts the alpha channel to convert between Roughness and Smoothness maps. - [code skip-lint]normalize_alpha[/code] - Normalizes the alpha channel to use full range for height map. - [code skip-lint]alpha_channel[/code] - The channel index (0-3: R,G,B,A) to use from src_a for the alpha channel. - [code skip-lint]ao_channel[/code] - The channel index (0-3: R,G,B,A) to use from src_ao for the ambient occlusion channel. ================================================ FILE: doc/docs/authors.rst ================================================ Authors ============ .. include:: ../../AUTHORS.md :parser: myst_parser.sphinx_ ================================================ FILE: doc/docs/building_from_source.md ================================================ Building from Source ==================== If you wish to use more recent builds without building from source, see [Nightly Builds](nightly_builds.md). ## 1. Install dependencies Follow Godot's instructions to set up your system to build Godot. You don't need the Godot source code, so stop before then. You only need the build tools, specifically `scons`, `python`, a compiler, and any other tools these pages identify. They provide easy installation instructions. * [Windows](https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html) * [Linux/BSD](https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_linuxbsd.html) * [macOS](https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_macos.html) ## 2. Download this repository You can either grab the zip file, or clone it on the command line. Only type in the commands after the $ prompts. ``` $ git clone git@github.com:TokisanGames/Terrain3D.git Cloning into 'Terrain3D'... Enter passphrase for key: remote: Enumerating objects: 125, done. remote: Counting objects: 100% (125/125), done. remote: Compressing objects: 100% (79/79), done. remote: Total 125 (delta 56), reused 94 (delta 36), pack-reused 0 Receiving objects: 100% (125/125), 42.20 KiB | 194.00 KiB/s, done. Resolving deltas: 100% (56/56), done. ``` ## 3. Initialize the submodule repository ``` $ cd Terrain3D Terrain3D$ git submodule init Submodule 'godot-cpp' (https://github.com/godotengine/godot-cpp.git) registered for path 'godot-cpp' Terrain3D$ git submodule update Cloning into 'C:/GD/Terrain3D/godot-cpp'... Submodule path 'godot-cpp': checked out '9d1c396c54fc3bdfcc7da4f3abcb52b14f6cce8f' ``` Note the version it checked out: **9d1c396**... This hash number is important for the next section. ## 4. Identify the appropriate godot-cpp version The checked out version of the godot-cpp submodule needs to match the version of your Godot engine build. e.g. Godot Engine 4.0.2 official build with godot-cpp checked out to a 4.0.2 branch. The early days of Godot 4.x were very strict and required the exact same major, minor, and patch versions. Since then, the requirements have loosened. For instance we've matched godot-cpp 4.1.3 with Godot engine 4.1.3 through 4.2.1 without issue. In the repository, we leave godot-cpp linked to an older version for broad compatiblity. For your individual needs, you may chose to keep the version we have currently linked, or update it to the version of the engine build you are using. **What is important is that you are aware of which version of godot-cpp you have, which you wish to use, and know how to change it.** You can check the version of your godot-cpp by changing to that directory, typing `git log`, and finding the most recent tag. ``` Terrain3D/godot-cpp$ git log commit 631cd5fe37d4e6df6e5eb66eb4435feca12708cc (HEAD, tag: godot-4.1.3-stable, 4.1) ... ``` Use one of these steps below to find and select the desired tag or commit hash, then move on to step 4. You may need to update your godot-cpp before it can find or checkout the latest tags: ``` Terrain3D/godot-cpp$ git fetch From https://github.com/godotengine/godot-cpp * [new tag] godot-4.0.1-stable -> godot-4.0.1-stable * [new tag] godot-4.0.2-stable -> godot-4.0.2-stable ``` Now we can search for or checkout more recent tags and commits. ### Using tags On the [Godot-cpp repository page](https://github.com/godotengine/godot-cpp), click the branch selector, then `Tags` to identify available tags that match the Godot engine binary you wish to use. If your engine version is in this list, e.g. `godot-4.0.2-stable`, great, move on to step 4. Otherwise explore the commit history on the website or command line as shown below. ```{image} images/build_tags.png :target: ../_images/build_tags.png ``` ### Using the commit history If your engine version doesn't have a tag assigned, because it's a custom build, you can look at the [godot-cpp commit history](https://github.com/godotengine/godot-cpp/commits/master) for a commit that syncs the repository to the upstream engine version. Search for entries named `Sync with upstream commit...`. Eg, from Godot 4.0-stable. ```{image} images/build_commit_history.png :target: ../_images/build_commit_history.png ``` Clicking the `...` in the middle expands the description which shows that this commit syncs godot-cpp with Godot engine `4.0-stable`. To use this commit, copy the commit string on the right in blue. Click the two overlapping squares on the right to copy the commit hash (`9d1c396`). ### Using the command line Alternatively, you can use git to search on the command line. Make sure to fetch to update the submodule (step 3). This will search the server (origin) for all commit messages with the string `stable`, allowing you to find the commits. Make sure to grab the commit hash on top (`9d1c396..`), not the upstream commit shown at the bottom, which is from the Godot repository. ``` Terrain3D/godot-cpp$ git log origin -Gstable commit 9d1c396c54fc3bdfcc7da4f3abcb52b14f6cce8f (HEAD -> master, tag: godot-4.0-stable, origin/master, origin/HEAD, origin/4.0) Author: R mi Verschelde Date: Wed Mar 1 15:32:44 2023 +0100 gdextension: Sync with upstream commit 92bee43adba8d2401ef40e2480e53087bcb1eaf1 (4.0-stable) ``` ## 5. Check out the correct version Once you have identified the proper tag or commit string, and you have updated the godot-cpp submodule (step 3), you just need to check it out. If using a commit string, you may use either the full hash or just the first 6-8 characters, so `9d1c396` would also match 4.0-stable. These examples will change the godot-cpp repository to 4.0-stable and 4.02-stable, respectively: ``` Terrain3D$ cd godot-cpp Terrain3D/godot-cpp$ git checkout 9d1c396c54fc3bdfcc7da4f3abcb52b14f6cce8f HEAD is now at 9d1c396 gdextension: Sync with upstream commit 92bee43adba8d2401ef40e2480e53087bcb1eaf1 (4.0-stable) Terrain3D/godot-cpp$ git checkout godot-4.0.2-stable Previous HEAD position was 9d1c396 gdextension: Sync with upstream commit 92bee43adba8d2401ef40e2480e53087bcb1eaf1 (4.0-stable) HEAD is now at 7fb46e9 gdextension: Sync with upstream commit 7a0977ce2c558fe6219f0a14f8bd4d05aea8f019 (4.0.2-stable) ``` ## 6. Build the extension By default `scons` will build the debug library which works for the editor and debug exports. You can add `target=template_release` to build the release version. ``` Terrain3D/godot-cpp$ cd .. # To build the debug library Terrain3D$ scons # To build both debug and release versions sequentially (bash command line) Terrain3D$ scons && scons target=template_release ``` Upon success you should see something like this at the end: ``` Creating library project\addons\terrain_3d\bin\libterrain.windows.debug.x86_64.lib and object project\addons\terrain_3d\bin\libterrain.windows.debug.x86_64.exp scons: done building targets. ``` ## 7. Set up the extension in Godot 1. Build Terrain3D, then ensure binary libraries exist in `project/addons/terrain_3d/bin`. 2. Run Godot with the console executable so you can see error messages. 3. In the Project Manager, import the project found in `project` and open it. Restart when it prompts. 4. In `Project / Project Settings / Plugins`, ensure that Terrain3D is enabled. 5. Select `Project / Reload Current Project` to restart once more. 6. If the demo scene doesn't open automatically, open `demo/Demo.tscn`. You should see a terrain. Run the scene by pressing `F6`. Close godot before rebuilding again. Continue with the remaining [installation instructions](installation.md#in-your-own-scene). ## Other Build Options The `scons` build system has additional useful options. These come from the GDExtension template we are using, so some options may not be supported or work properly for this particular plugin. e.g. The platform. ### Debug Symbols Build the extension with debug symbols. See [debugging](#debugging-the-source-code) below. ``` scons dev_build=yes ``` ### Clean up build files ``` # Linux, other Unix, Git bash on Windows scons --clean rm project/addons/terrain_3d/bin/* find . -iregex '.+\.\(a\|lib\|o\|obj\|os\)' -delete # Windows scons --clean del /q project\addons\terrain_3d\bin\*.* del /s /q *.a *.lib *.o *.obj *.os ``` ### Manually specify the target platform This plugin supports Windows, Linux and macOS. See [Platform Support](platforms.md) for the status of other platforms. ``` # platform: Target platform (linux|macos|windows|android|ios|javascript) scons platform=linux ``` ### Using C++20 The C++ standard used in Godot and Godot-cpp is C++17. However you may use C++20 for building GDExtensions if desired. Our [SConstruct](https://github.com/TokisanGames/Terrain3D/blob/main/SConstruct#L52-L56) file has some commented code towards the bottom that will replace the standard. ### See all options ``` # Godot custom build options scons --help # Scons application options scons -H ``` ## Troubleshooting ### Debugging the source code In addition to the [debug logs](troubleshooting.md#debug-logs) dumped to the console, it is possible to debug Godot and Terrain3D, stepping through the full source code for both projects, viewing the callstack, and watching variables. We do it in MSVC regularly with these steps. * Build Terrain3D with `scons dev_build=yes`. * Build Godot with `scons debug_symbols=true`. * Start Godot with the debugger from within the Godot source project, rather than the Terrain3D project. * The debugger will attach to the Project Manager. After you load your project in the editor, or your game scene, `Debug/Attach to Process...` to reattach the debugger to the new process. Alternatively, adjust your debug startup command so Godot loads your project in the editor or runs your game scene directly and the debugger will attach to it on startup. * Since you started in the Godot project, upon hitting a breakpoint in Terrain3D, it will ask for the location of the file. Once found, it should have no problem loading the source code. If you have problems, use `Debug/Windows/Modules` to display the dependent libraries and ensure the symbols for Godot and Terrain3D are loaded. You can also debug only Terrain3D, using the official Godot binary. You'll be able to view and step through Terrain3D code. Any calls to Godot will be processed, but you won't be able to step through the code or watch variables without the symbols. ### When running scons, I get these errors: ``` Terrain3D$ scons scons: Reading SConscript files ... scons: warning: Calling missing SConscript without error is deprecated. Transition by adding must_exist=False to SConscript calls. Missing SConscript 'godot-cpp\SConstruct' File "C:\gd\Terrain3D\SConstruct", line 6, in AttributeError: 'NoneType' object has no attribute 'Append': File "C:\gd\Terrain3D\SConstruct", line 9: env.Append(CPPPATH=["src/"]) ``` Your godot-cpp directory is probably empty. Review the instructions above for updating the submodule. ### I can build the plugin, however Godot instantly crashes. Your godot-cpp version probably does not match your engine version. See section 3 above to learn how to identify and change versions. Test the example project in the next question. ### How can I make sure godot-cpp is the right version and working? You'll find a test project in `godot-cpp/test/`. Make sure this test project works with your Godot version first, then come back and try Terrain3D again. * Build the example plugin by typing `scons` while in the `godot-cpp/test/` directory. * Copy `example.gdextension` and `bin` into the root folder of your project. * Run Godot. If it crashes, you're on the wrong version, or Godot-cpp has a problem that the maintainers will need to resolve. * Create a new scene. * Add a new `Example` node. When clicking the node, you should see an `Example` section and `Property From List` and various `Dproperty#` variables in the inspector. ================================================ FILE: doc/docs/collision.md ================================================ Collision ======================= One of the most important things about a terrain is knowing where it is. Using physics based collision is not the only way, nor even the best or fastest way. There are at least 5 ways to detect terrain height: Physics based raycasting on collision, raymarching, the GPU depth texture, get_height(), and reading the heightmap directly. You should use raycasting only when you don't already know the X, Z of the collision point (eg not vertical). ## Physics Based Collision & Raycasting To enable physics based collision, we must generate a StaticBody and CollisionShapes that match the shape of the terrain. This means collision is only generated where you have defined regions. Out in the WorldNoise background, there is no terrain data, so no collision. Collision generation can be slow and consume a lot of memory, so we offer five options: * `Dynamic / Game` is the default, which only generates around the camera while in game. It is node-less and the fastest option. * `Dynamic / Editor` generates around the camera in editor or in game. It attaches nodes to the tree, so is slightly slower, but this allows the shapes to be [visualized](#visualizing-collision). * `Full / Game` generates collision for the entire terrain at game start, using node-less shapes. It consumes a lot of memory on large terrains. * `Full / Editor` does the above with viewable shapes in the editor. * `Disabled` is self explanatory. Some addons or other activities need collision in the editor, which can be enabled with an `Editor` mode above. You can run the game with any option, but the default is recommended for the best performance. See [set_mode](../api/class_terrain3dcollision.rst#class-terrain3dcollision-property-mode) and [CollisionMode](../api/class_terrain3dcollision.rst#enum-terrain3dcollision-collisionmode). Once the desired collision mode is set, to detect ground height with physics, you can use a [ray cast](https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html). The colliding object will either be the [Terrain3D](../api/class_terrain3d.rst) node if in a `Game` mode. Or it will be a StaticBody if using an `Editor` mode. In the latter case, the StaticBody is a child of Terrain3D. Below is an example that will handle either scenario. ```gdscript var space_state: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state var query := PhysicsRayQueryParameters3D.create(position, position + Vector3(0, -500, 0)) query.exclude = [ self ] var result: Dictionary = space_state.intersect_ray(query) if result: var node: Node = result["collider"] # Change node to StaticBody parent in `Editor` collision modes if node is StaticBody3D and node.get_parent() is Terrain3D: node = node.get_parent() # Detect Terrain3D with `is`, since printing the node returns `[Wrapped:0]` if node is Terrain3D: print("Hit: Terrain3D") else: print("Hit: ", node) ``` Alternatively, you can use our helper function: ```gdscript var result: Dictionary = $Terrain3D.get_raycast_result(position, Vector3(0,-500,0))) ``` Godot Physics is far from perfect. If you have issues with raycasts or other physics calculations, try switching to Jolt. If you have trouble with a perfectly vertical raycast, try angling it ever so slightly. Finally, consider using an alternate method below. ## Raycasting Without Physics It is possible to cast a ray from any position and direction to detect the collision point on the terrain without using the physics engine. We have two methods for that: raymarching and "looking" at it with the GPU. Sending the source point and ray direction to [Terrain3D.get_intersection()](../api/class_terrain3d.rst#class-terrain3d-method-get-intersection) will return the intersection point. This function has two modes: In raymarching mode it iterates over get_height() until an intersection is reached. This only works within regions, and is a bit heavy compared to the other mode. In GPU mode, it "looks" at the terrain using a camera and the GPU depth texture. This works outside of regions, even on the WorldNoise. However there are caveats. It returns values for the previous frame, so can only used continuously or used with `await`, and no more than once per frame. Be sure to read the link above to understand all caveats. Review [editor_plugin.gd:_forward_3d_gui_input](https://github.com/TokisanGames/Terrain3D/blob/main/project/addons/terrain_3d/src/editor_plugin.gd) to see an example of using this function to project the mouse position onto the terrain. ## Query Height At Any Position If you already know the X, Z position, use `get_height()`: ```gdscript var height: float = terrain.data.get_height(global_position) ``` `NAN` is returned if the position is a hole, or outside of regions. This is ideal for one lookup. Use the next option for greater efficiency. ### Retreiving The Normal After getting the height, you may also wish to get the normal with `Terrain3DData.get_normal(global_position)`. The normal is a Vector3 that points perpendulcar to the terrain face. ## Query Many Heights If you wish to look up thousands of heights, it will be faster to retrieve the heightmap Image for the region and query the data directly. However, note that `get_height()` above will [interpolate between vertices](https://github.com/TokisanGames/Terrain3D/blob/5bab86ff311159356dd4d837ea2c340f59d139b6/src/terrain_3d_storage.cpp#L493-L502), while this code will not. ```gdscript var region: Terrain3DRegion = terrain.data.get_regionp(global_position) if region and not region.is_deleted(): var img: Image = region.get_height_map() for y in img.get_height(): for x in img.get_width(): var height: float = img.get_pixel(x, y).r ``` ---- ## Additional Tips ### Visualizing Collision To see the collision shape, first set `Terrain3D/Collision/Collision Mode` to `Full / Editor` or `Dynamic / Editor`. To see it in the editor, in the Godot `Perspective` menu, enable `View Gizmos`. Avoid using the Full option on slow systems. To see debug collision in game, in the Godot `Debug` menu, enable `Visible Collision Shapes` and run the scene. ### Enemy Collision The easy approach is to give every enemy a capsule collision shape, and start creating your level. As you fill your level with terrain, rocks, caves, canyons, and dungeons, you'll quickly learn that this approach is terrible for performance. Your system will be brought to its knees when you have 5-10 enemies follow the player into a tight area with many faceted collision shapes. The physics server will need to calculate collisions against hundreds of faces in the area for each of the 10 character bodies. A better alternative is to use physics collision only for the player, and use raycasts for the enemies. This allows you to limit the collision checks to a few per enemy, regardless of how many collision faces there are. However both methods above require collision shapes where the enemy is. What if you want to have enemies stay on the ground when far from the camera, while using a dynamic collision mode? For one, you could disable all enemy processing when away from the camera. Do you really need them moving, playing animations, and applying gravity if they aren't even on screen? If so, you can have each enemy query `Terrain3DData.get_height()`, either in conjunction with checking height with a raycast or physics, or in place of. It could come right after applying gravity so it will snap back to the surface if it dips below. e.g. ``` global_position.y -= gravity * p_delta global_position.y = maxf(global_position.y, terrain.data.get_height(global_position)) ``` ================================================ FILE: doc/docs/contributing.rst ================================================ Contributing ============ .. include:: ../../CONTRIBUTING.md :parser: myst_parser.sphinx_ ================================================ FILE: doc/docs/controlmap_format.md ================================================ Control Map Format ===================== The control map defines how the terrain is textured using unsigned, 32-bit integer data. Godot doesn't fully support integer Image or Texture formats, so we use FORMAT_RF. See further below for details. We process each uint32 pixel as a bit field with the following definition, starting with the left most bits: | Description | Range | # Bits | Bit #s | Encode | Decode |-|-|-|-|-|-| | Base texture id | 0-31 | 5 | 32-28 | `(x & 0x1F) <<27` | `x >>27 & 0x1F` | Overlay texture id | 0-31 | 5 | 27-23 | `(x & 0x1F) <<22` | `x >>22 & 0x1F` | Texture blend | 0-255 | 8 | 22-15 | `(x & 0xFF) <<14` | `x >>14 & 0xFF` | UV angle | 0-15 | 4 | 14-11 | `(x & 0xF) <<10` | `x >>10 & 0xF` | UV scale | 0-7 | 3 | 10-8 | `(x & 0x7) <<7` | `x >>7 & 0x7` | ... reserved ... | | | | Hole: 0 terrain, 1 hole | 0-1 | 1 | 3 | `(x & 0x1) <<2` | `x >>2 & 0x1` | Navigation: 0 no, 1 yes | 0-1 | 1 | 2 | `(x & 0x1) <<1` | `x >>1 & 0x1` | Autoshader: 0 manual, 1 auto | 0-1 | 1 | 1 | `x & 0x1` | `x & 0x1` * The encode/decode formulas work in both C++ or GLSL, though may need a `u` at the end of literals when working with an unsigned integer. e.g. `x >> 14u & 0xFFu`. * We use a FORMAT_RF 32-bit float Image or Texture to allocate the memory. Then in C++, we read or write each uint32 pixel directly into the "float" memory. The values are meaningless when interpreted as floats. We don't convert the integer values to float, so there is no precision loss. Godot shaders support usamplers so we can interpret the memory directly as uint32, without requiring any conversion. * Gamedevs can use the conversion and testing functions found in Terrain3DUtil defined in [C++](https://github.com/TokisanGames/Terrain3D/blob/main/src/terrain_3d_util.h) and [GDScript](../api/class_terrain3dutil.rst). * Possible future plans for reserved bits: * 2 bits - Hole and Navigation might be stored elsewhere, freeing up 2 bits * 5 bits - 32 paintable particles * 3 bits - paintable slope array index+ * 2 bits - 4 layers (including Hole above, eg water, non-destructive, hole, normal mesh) * 1 bit - future use (maybe added to particles) * 2 bits - Texture blend weight array index+ * Array for all + marked 3-bit indices above: `{ 0.0f, .125f, .25f, .334f, .5f, .667f, .8f, 1.0f }`; The 3 bit indices above are used to select a value from an 8-index array of values between 0-1, such as `{ 0.0f, .125f, .25f, .334f, .5f, .667f, .8f, 1.0f };` This allows us to store full range 0-1 values that would normally require 8 bits (256 values) in only 3 bits (8 values), since the fine gradations are not important. This idea came from the Witcher 3 presentation. ================================================ FILE: doc/docs/data_format.md ================================================ Data Format Changelog ========================== The Terrain3DRegion resource files have their own version, independent of the version of Terrain3D, shown at the top of the inspector. Generally this is updated to the latest format upon saving. This information is more relevant for developers, while the [upgrade path](installation.md#upgrade-path) is relevant for all users. The data format version is found as [Terrain3DData.version](../api/class_terrain3ddata.rst#class-terrain3ddata-property-version) and is visible in the inspector under `Version` after double clicking a data file. | Version | Description | |---------|-------------------| | 0.93 | The monolithic storage file was split into one file per region [#374](https://github.com/TokisanGames/Terrain3D/pull/374), [#476](https://github.com/TokisanGames/Terrain3D/pull/476) | 0.92 | Add `Terrain3DInstancer` data [#340](https://github.com/TokisanGames/Terrain3D/pull/340) | 0.842 | Control map changed from FORMAT_RGB to 32-bit packed integer (encoded in FORMAT_RF) [#234](https://github.com/TokisanGames/Terrain3D/pull/234/) | 0.841 | Colormap painted/stored as srgb and converted to linear in the shader (prev painted/stored as linear). [64dc3e4](https://github.com/TokisanGames/Terrain3D/commit/64dc3e4b5e71c11ac3f2cd4fedf9aeb7d235f45c) | 0.84 | Separated material processing from Storage as a `Terrain3DMaterial` resource. [#224](https://github.com/TokisanGames/Terrain3D/pull/224/) | 0.83 | Separated Surfaces (textures) from Storage as a `Terrain3DTextureList` resource. [#188](https://github.com/TokisanGames/Terrain3D/pull/188/) | 0.8 | Initial version ================================================ FILE: doc/docs/displacement.md ================================================ Displacement ==================== For a more detailed terrain mesh, you can enable texture height based displacement. This will subdivide the clipmap mesh, greatly increasing the vertex density around the camera. This feature is compatible with all platforms, including mobile and web, however it does come with a potentially significant performance cost. ```{image} images/displacement_example.jpg :target: ../_images/displacement_example.jpg ``` ```{image} images/displacement_wireframe.jpg :target: ../_images/displacement_wireframe.jpg ``` ## Enabling Displacement You can enable displacement in the `Terrain Mesh` group, by increasing `Tessellation Level` from 0 (disabled, default) up to 6. This will subdivide the terrain around the camera. Generally, it is recommended to keep `Tessellation Level` low, `Mesh Size` as high as performance allows, and `Vertex Spacing` at `1.0` for the best results. All three parameters will have a noticable effect on the subdivision density. When `Tessellation Level` is greater than 0, the `Displacement` subgroup appears with global options. Enable displacement, then setup your textures, then come back to these global options. To setup your texture assets, you need a clear view of the terrain and an understanding of where the collision surface is. Explore any of these options: * Disable instances by setting `Rendering/Instancer Mode` to `Disabled`. * Hide your nodes in the scene that obscure the terrain surface. * Change `Collision/Collision Mode` to editor collision, and enable view gizmos, which will show the collision mesh for direct comparison. * Enable the `Debug Views/Displacement Buffer`. Black areas match collision exactly. Red shows depressions into the terrain, and Green shows extrusions above the terrain. ```{image} images/displacement_buffer.jpg :target: ../_images/displacement_buffer.jpg ``` ## Configuring Textures In order to use displacement, you must have textures packed with height textures as described in [Texture Prep](texture_prep.md). When you configure your `Terrain3DTextureAsset`, you'll find `Displacement Offset` and `Displacement Scale` options. First, adjust `Displacement Scale` until the dimensions of features in the textures are correctly proportioned. Eg. branches are not overly elongated, and pebbles appear round, rather than spiky. Next, adjust `Normal Depth` so that any displaced areas are lit correctly. The normal map normals are used for the higher detail geometry. Finally, adjust `Displacement Offset` if necessary to minimize the amount of colision mismatch. Most of the time, the default `0.0` is adequate. As an example, a cobblestone texture might have the tops of the cobbles protrude above the terrain, but a small negative offset can align the tops of the cobbles with collision. ## Displacement Global Settings Once the texture assets are configured, the global settings can be adjusted in `Terrain Mesh/Displacement`. Use `Displacement Scale` for a global multiplier on all textures. This is the maximum distance that 2 adjacent verticies can be vertically seperated by. Setting this 1.0 would mean a maximum of + 0.5m, and -0.5m deviation from the collision mesh. `Displacement Sharpness` adjusts the transition between textures. When set at `1.0`, the blending of displacment between textures will match the aldebo and normal blend sharpness exactly. Lower values will have a softer transition, avoiding harsh shapes, without compromising the albedo and normal blend sharpness. If set at or very near to `0.0`, it is possible that more displaced textures can affect less displaced textures at low blend values even if not visible. ## Displacement Buffer To calculate displacement, an atlas texture buffer is created via a viewport and canvas_item shader. This buffer updates only when the terrain mesh moves to save on computational cost. You can access the buffer shader by enabling `Terrain Mesh/Displacement/Buffer Shader Override Enabled`, which will generate the default for you. Though primarily for development use, it could be modified to read from a persistent buffer for real-time effects like footsteps in sand or mud, or to change how the blending of height textures is handled. Custom uniforms can be added within the `group_uniforms displacement` block. These will show up in the `Material/Displacement` group. ================================================ FILE: doc/docs/double_precision.md ================================================ Double Precision ================= When the player and camera move 10s to 100s of thousands, or millions of units away from the origin using a single precision float engine, things start to break down. Movements, positions, and rendering starts to become jittery or corrupted as the interval between valid values gets larger and larger. Building Terrain3D with double precision (aka 64-bit) floats allows high precision even at large numbers, but there are some caveats. For a more detailed explanation, see [Large World Coordinates](https://docs.godotengine.org/en/stable/tutorials/physics/large_world_coordinates.html) in the Godot documentation. ## Caveats * This feature is experimental and has had only one user give a positive report so far. * There are many caveats listed in the link above. You should read them all before beginning this process. * You must build Godot and Terrain3D from source. * Terrain3D currently supports a maximum world size of 65.5x65.5km. Although with `vertex_spacing`, you can expand this up to 100x. You can also have Terrain3D regions around the origin, then have your own meshes or a shader generated terrain outside of that maximum world space. * Shaders do not support double precision. Clayjohn wrote an article demonstrating how to [Emulate Double Precision](https://godotengine.org/article/emulating-double-precision-gpu-render-large-worlds/) in shaders. He wrote that the camera and model transform matrices needed to be emulated to support double precision. This is now done automatically in the engine when building it with double precision. There may be other cases where shaders will need this emulation. ## Setup To get Terrain3D and Godot working with double precision floats, you must: 1. [Build Godot from source](https://docs.godotengine.org/en/latest/engine_details/development/compiling/index.html) with `scons precision=double` along with your other parameters like target, platform, etc. 2. Generate custom godot-cpp bindings using this new executable. This will generate a JSON file you will use in the next step. - `godot --dump-extension-api`, which gives you an extension_api.json - `scons custom_api_file=path_to/extension_api.json` 3. [Build Terrain3D from source](building_from_source.md) (which includes godot-cpp) using `scons precision=double custom_api_file=path_to/extension_api.json` After that, you can run the double version of Godot with the double version of Terrain3D. ## Further Reading See [Support Doubles #30](https://github.com/TokisanGames/Terrain3D/issues/30) for our Issue that documents implementing support for doubles in Terrain3D. ================================================ FILE: doc/docs/games.md ================================================ Games Using Terrain3D ======================= Terrain3D is being used in the following games. To add yours, submit it to the #game-dev channel on [our discord server](https://tokisan.com/discord). | Game | Studio | Description | |------|--------|-------------| | [Out of the Ashes](https://store.steampowered.com/app/2296950/Out_of_the_Ashes/) | [Tokisan Games](https://x.com/TokisanGames) | Story driven medieval adventure | [Gunship Origins](https://store.steampowered.com/app/4055780/Gunship_Origins/) | [Jammin Games](https://jammin.games/) | Hellicopter combat | [Black Pellet](https://www.kickstarter.com/projects/raiseledwards/black-pellet) | [BlackPelletGame](https://x.com/BlackPelletGame) | Claymation, Western, TPS, Open world, Action-adventure | [Memora Wanderer](https://store.steampowered.com/app/2937690/Memora_Wanderer/) | [Maytch](https://x.com/Maytch) | Cute nostalgic RPG | [No Gasoline](https://store.steampowered.com/app/2835350/No_Gasoline/) | [Mount Retro](https://x.com/mountretro) | Co-Op/Solo, Adventure-Simulation-Puzzle | [RotorSim](https://store.steampowered.com/app/3376070/RotorSim_Helicopter_Simulator/) | [Immaculate Lift](https://immaculate-lift-studio.github.io/studio-site/) | Retro helicopter simulation | [B&E Ski](https://www.youtube.com/watch?v=pD8Ea3utz9o) | [Penguin Milk](https://bande.ski/) | Skiing game | [Sacred Forest](https://store.steampowered.com/app/2864350/Sacred_Forest/) | [Blekoh](https://www.youtube.com/@sacredforestgame) | Open world 3D pixel art RPG | [Pest Apocalypse](https://store.steampowered.com/app/2506810/Pest_Apocalypse/) | [Kikimora Games](https://x.com/KikimoraGames) | Post-apocalyptic pizza delivery | [Forg](https://store.steampowered.com/app/2807130/Forg/) | [Crow Games](https://www.youtube.com/@crowgamesdev) | FPS tower defense | [open-fpsz](https://gitlab.com/open-fpsz/open-fpsz) | [anyreso](https://mastodon.gamedev.place/@anyreso) | Open-source, Tribes-like FPS multiplayer shooter | [Element](https://devanew.itch.io/element) | [Luke Aaron](https://www.youtube.com/watch?v=b18jDnY1YS4) | Gamejam FPS tactial shooter | [Castaway Cove](https://boolburg.itch.io/castaway-cove) | [Boolburg](https://boolburg.itch.io/) | Tropical island exploration ## Demos | Game | Studio | Description | |------|--------|-------------| | [Island Demo](https://github.com/OverfortGames/LandscapeDemo) | [Overfort Games](https://x.com/OverfortGames) | Island demo w/ source | [Jungle Demo](https://wrobot.itch.io/jungledemo) | [WrobotGames](https://x.com/wrobot123) | Godot rendering demo in a jungle ================================================ FILE: doc/docs/generating_csharp_bindings.md ================================================ Generating C# Bindings ========================== C# Bindings need to be rebuilt after building Terrain3D. At this point in time, C# and GDScript don't share their ClassDB. You can call the functions through reflection, but providing C# Bindings makes coding in C# much easier. As of Terrain3D 1.1, these bindings are generated and included in release builds by a maintainer. How To Generate Bindings ------------------------ 1. Compile Terrain3D as shown in [Building from Source](building_from_source.md). 1. Clone the [CSharp-Wrapper-Generator-for-GDExtension](https://github.com/Delsin-Yu/CSharp-Wrapper-Generator-for-GDExtension) repo. 1. Load the CSharp Wrapper Generator project in the .NET version of Godot. 1. Build the C# project. 1. Ensure the plugin is enabled in project settings and restart. 1. You should see a Wrapper Generator panel in a side panel. Set up the namespace `TokisanGames`, save path `addons/terrain_3d/csharp`, and indentation `tabs`. 1. Add your built `addons/terrain_3d/` folder to this project. I made a link on my filesystem between my Terrain3D repo and this one so I could read from new builds, and generate C# without moving files. 1. Generate, and look for errors or warnings to address. 1. Move the generated C# files to `project/addons/terrain_3d/csharp` in your project. 1. In your project, build the C# project. ================================================ FILE: doc/docs/getting_help.md ================================================ Getting Help =================== **Video Tutorials**: Make sure to watch the [tutorial videos](tutorial_videos.md) which show proper installation, setup, and how to use most of the tools. **Read the docs**: Many questions have already been addressed in this documentation, especially in [Troubleshooting](troubleshooting.md) and [Setting Up Textures](texture_prep.md). **Discord**: For questions or technical issues, join the [Tokisan discord server](https://tokisan.com/discord) and ask in the `#terrain-help` channel. **Issues**: For technical issues or bug reports, search through the [issues](https://github.com/TokisanGames/Terrain3D/issues) to ensure it hasn't already been reported, then create a new one. Use discord for questions. ## Help Us Help You Terrain3D has been stable for thousands of users for more than a year, so we know it works fine when setup properly. If you're having trouble, there's a 90% chance it's not installed or setup properly. We can help you get setup, but only if you provide adequate information. At a minimum we need the following: * A concise description of the problem, what you expect to happen, and what is happening. * The exact version of Godot and of Terrain3D you're using. * Or, click your Terrain3D node and provide a full screenshot, which will include that and other information. * Your Operating system and GPU. * Confirmation of, or a screenshot of your Project Settings / Plugins screen to show that Terrain3D is enabled, and what other plugins you have. * A screenshot or copy of all text in your [console](troubleshooting.md#using-the-console), beginning with the initial Godot Engine startup message. If you have trouble with textures, we also need: * Double click a texture file and show the file format and size as reported by the Godot inspector. Select either the first texture, or a relevant problem texture. * A shot of your import tab with that same texture selected. Finally, be ready to provide [debugging logs](troubleshooting.md#debug-logs), and what steps you tried, and the results of those steps. ================================================ FILE: doc/docs/heightmaps.md ================================================ Heightmaps =========== Terrain3D can be used with pre-made heightmaps. They can be found online, created from heightmap generators, or downloaded from real world data. Once you have your heightmap source, ensure it is 16 or 32-bit, [scaled properly](#scaling), and converted to `exr` or `r16`. Then read [Importing Data](import_export.md) to learn how to import it. What is not covered here is how to use Photoshop, Krita, or Gimp, but you can find tutorials on YouTube. **Table of Contents** * [Pre-made Heightmaps](#pre-made-heightmaps) * [Heightmap Generators](#heightmap-generators) * [Real World Data](#real-world-data) * [Converting Data](#converting-data) * [Scaling](#scaling) ## Pre-made Heightmaps A simple web search for `download terrain heightmaps` will allow you to find heightmaps in a few minutes. Some pages might say `free heightmaps for Unity` or `Unreal Engine`. All of these will work. You need to understand what format they are giving you, ensure it is at least 16 or 32-bit, and [convert](#converting-data) that to `exr` for import. There are also asset packs for game engines like Unreal Engine or Unity that come with premade heightmaps. Both engines provide the ability to export these to a format you can use elsewhere. Be sure to verify the license of the content you're using, but generally anything you've bought can be modified and used elsewhere. ## Heightmap Generators You can use software to generate heightmaps, and in some cases, texture layout maps (aka splat maps, index maps, or our word: control maps). You must look through the documentation of your program to determine what formats and bit depth they export, and convert if necessary. ### Free or Open Source Search github for `heightmap generator` or `noise generator` to see the current projects. Anything that can produce *and* export a 16-bit grayscale image will work. Here are some examples. * [wgen](https://github.com/jice-nospam/wgen) - MIT License. Written in Rust. Exports to 16-bit `png` or `exr`. * [HTerrain](https://github.com/Zylann/godot_heightmap_plugin) - MIT License. A GDScript based terrain system for Godot. It includes a terrain generator that can save a heightmap as a native Godot .res file, or you can export to `exr`. Both will work for Terrain3D. * In the future, Terrain3D will include a generator. Follow [Issue 101](https://github.com/TokisanGames/Terrain3D/issues/101) for progress. ### Commercial Software Some of these tools are free for non-commercial use. Generally they generate both heightmaps and control maps. Currently you can only import the heightmaps into Terrain3D. Importing a control map is possible, but requires you or a contributor to 1) research and document the proprietary control map format available in each tool, 2) create a conversion script for our importer. As programming tasks go, this is pretty easy to do. See [issue 135](https://github.com/TokisanGames/Terrain3D/issues/135) if you'd like to help with one or both. You could export a baked image of the textures, like a satellite image and import that on our color map, and enable `Debug Views / Colormap`. This could aid you in manual texturing, or left for display purposes. * [Gaea](https://quadspinner.com/) * [World Machine](https://www.world-machine.com/) * [World Creator](https://www.world-creator.com/) * [Terragen](https://planetside.co.uk/) * [Houdini](https://www.sidefx.com/products/houdini/) - General procedural modeling software that includes terrain generation. * [Instant Terrain](https://www.wysilab.com/) - Terrain generation plugin for UE or Houdini. ## Real World Data Geographic Information System (GIS) professionals use real world height data from satellite and aircraft surveys. Much of it is available for free from government websites. This data is often referred to as a Digital Elevation Model (DEM), or Surface (DSM) or Terrain (DTM). A DSM might contain buildings, DTM only the ground, while DEM is the umbrella term. This data may come in a variety of formats. There are many, many sources available online. Here are a few examples: * [USGS](https://www.usgs.gov/the-national-map-data-delivery/gis-data-download) provides several tools to download DEMs and Tiffs for the US down to 1m resolution. * [European Space Agency Copernicus program](https://ec.europa.eu/eurostat/web/gisco/geodata/digital-elevation-model/copernicus) provides DEMs for the whole world. * [EU-DEM](https://ec.europa.eu/eurostat/web/gisco/geodata/digital-elevation-model/eu-dem) * [http://www.terrainmap.com/rm39.html](http://www.terrainmap.com/rm39.html) - A list of other websites with GIS data. Some GIS data might include sattelite imagery. You can import that into our color map, and enable `Debug Views / Colormap`. There are some websites that allow you to download a heightmap from real world data, however often they are unusable 8-bit heightmaps. The sites below are some examples that might have higher quality, usable data. But government GIS survey data is probably superior. * [https://manticorp.github.io/unrealheightmap/](https://manticorp.github.io/unrealheightmap/) - exports 16-bit `png` * [https://touchterrain.geol.iastate.edu/](https://touchterrain.geol.iastate.edu/) - exports GeoTiff ## Converting Data Converting data is not trivial. Understanding how your data is stored and how to convert it to another format is an essential skill for you to learn. I promise that any time spent learning this will be useful for your entire life working with computers. Ultimately we want the data stored as an `exr` or `r16` for import into Terrain3D. See [supported formats](import_export.md#supported-formats) for more details. How we get there depends on your source data and tools available. Photoshop, Krita, or Gimp are the most common tools, but you might find other useful tools or even write your own scripts to convert data. How to use these tools is out of scope for this documentation. You might need to work through multiple tools to get the right format and workflow. ### File Format Hopefully your source data is a 16-bit `png` or Tiff/GeoTiff file, which may allow you to convert it directly just by saving the file as an `exr` or `r16`. GeoTiff is a `tiff` file with geospatial metadata embedded. Some Tiff formats are newer and not supported by all image editing apps, and may require experimentation with multiple tools. You might be able to convert it with a python script such as the following, which produces an `r16` file (named raw): ``` from PIL import Image import numpy im = Image.open('image.tif') imarray = numpy.array(im) imarray.astype('int16').tofile("image.raw") ``` If your GIS source data is in a non-image format, you can try converting it with [VTBuilder](http://vterrain.org/). Drag the file into the window, and if it loads, use `Elevation/Export To` and save it as 16-bit `png` or GeoTiff. ### Normalized Data Once the source file is opened in an image editing program, the map might show only an outline of your landscape, with the land in solid white or red, and the ocean in black. You can use the dropper to look at the values, which should show expected values for heights in meters. This is a map with real world values. You can use tone mapping to view the image as a gradient heightmap, however you don't want to save it this way. If the file shows a smooth gradient heightmap, then it's normalized, with all values between 0 and 1. Ensure the image is 16 or 32-bit. 8-bit will give you an ugly terraced terrain and will require a lot of smoothing to be usable. Either normalized values or real values are fine, as long as you understand how your data is formatted and that you must [scale](#scaling) normalized values on import. That means knowing how much to scale by, and that info should have been provided with the data source. ### Conversion Examples * **Ex 1:** I exported a 16-bit `png` from a commercial tool. I opened the file in Photoshop and exported it as `exr`. Or I could have opened it in Krita and exported as `exr` or `r16`. * **Ex 2:** I downloaded a Digital Elevation Model (DEM) from a govt website and received GeoTiff files. * Though I've opened other GeoTiff files in Photoshop before, it couldn't read these. Krita opened them but only displayed black. * I was able open them in Gimp, and reviewing the pixel data showed the ocean at `-inf`, and the land at real world values. I exported as `exr` and imported to Terrain3D, but the land was extremely high. * I was able to open the `exr` in Photoshop and compare with my other known working `exr` files. The landscape shape appeared, but the height values were extremely large. I experimented with various color management and bit depth conversions within Gimp to see if I could get a correct looking `exr` in Photoshop, but couldn't. * In Gimp, I exported as `tiff`, which gave me a file I could open in Photoshop and gave me the heights I was looking for, except the ocean still showed `-inf`. This was a problem as it produced holes in Terrain3D, but not our kind of holes that can be filled in. * In Gimp I found `Colors / RGBClip` in the menu and used it to limit `-inf` to 0, while retaining height values above 1. * Then I exported the Tiff to Photoshop, confirmed the values looked good, exported that to `exr` for import and finally got the desired result. ## Scaling Terrain3D generally expects a ratio of 1px = 1m lateral space with real world heights, and provides options for manipulating these. In order to get expected results on import, there are two important characteristics you need to understand about your data: 1. **Vertical Scale**. If your data is not normalized, your data has real values and vertical scale should remain 1 on import. Most likely 0 is defined as sea level. This is how Terrain3D stores data internally. If your data is normalized with values 0-1, you can multiply by a vertical scale in the import tool to set the peak height according to what your source defines. You can also apply an offset to adjust how your data aligns with 0. 2. **Vertical Aspect Ratio (VAR)**. This is often called `resolution` in GIS and terrain documentation, but there is a crucial distinction from image resolution. Your data has an inherent ratio of lateral space to height, or a 3D aspect ratio of XZ to Y (up). Terrain3D expects 1px = 1m with real world heights, giving a VAR of 1m lateral to 1m vertical. If your data is 1px = 10m with real world heights, the VAR is 10:1. That means your data is 10x wider than it is high and you need to either adjust the data or adjust Terrain3D settings to account for this difference to get an accurate result. This means scaling the heightmap image in Photoshop before import, scaling the heights on import, or adjusting [Mesh / Vertex Spacing](../api/class_terrain3d.rst#class-terrain3d-property-vertex-spacing) on or after import. Which one you choose depends on your needs, as described below. ### Scaling Examples Here are some examples of adjusting settings to account for vertical scale and VAR. These examples will be more meaningful if you've read the [import doc](import_export.md) and have imported at least one heightmap first. * **Ex 1**: I downloaded GIS data that is a 20m resolution DEM and received a GeoTiff. Upon loading it in Photoshop, the dropper tool revealed that sea level is at 0 and other points on land have real world values in meters. This means the vertical scale is built in to the data, and every 1px on the map represents 20m of lateral space, giving us a VAR of 20:1. I want to work with this map in Terrain3D at the default resolution of 1px = 1m. * **Option 1**: In Photoshop I crop the image down to a small 500x500px area that I want to import, then scale the image 20x to 10k x 10k. * Given the large image size, in Terrain3D I increase the region size to 512 (10k / 32 = 312.5 minimum). (Read [why 32](introduction.md#regions)). * Next I import with a scale of 1, offset of 0, and leave vertex_spacing at 1. * I now have an accurate representation of the data within Terrain3D that I can sculpt and refine with the standard vertex spacing. This consumes the VRAM required for a 10k x 10k heightmap. A lot of that is wasted if I do nothing else, since I duplicated the pixels 20x without adding any new data. However this process has given me an accurate base to start sculpting and add my own detail at a reasonable resolution. This is likely my preferred path for real world data. * **Option 2**: I import the original image with scale set to 0.05 (1/20th). Now in Terrain3D I have a visually accurate representation of the data, given that the VAR is correct. However if I were to measure the heights, they would be 1/20th their real world values. 1px = 20m and height = 1/20th. There's no wasted VRAM due to duplication of pixels. This is a good path if I only want to visualize the data. * **Option 3**: I import the image with a scale of 1. In Terrain3D, it will look very spiky. I increase vertex_spacing to 20. Like the option above, there's no wasted VRAM. This gives I accurate heights, but the world might look a bit low poly. * **Ex 2**: I exported a heightmap from a commercial tool and received 4 tiled 1024x1024 heightmaps. Upon opening them in Photoshop, they look like gradient heightmaps and the dropper tool confirms the data is normalized with values between 0-1. I was informed the lowest trough is -500m, highest peak 1500m, sea level is 0, and the resolution is 4px to 1m. * **Option 1**: In Photoshop, I combine the four tiles into one 4k image. * Then I scale the whole image by 4 to 16k. * On import to Terrain3D, I change region size to 512, set offset to -500m, and scale to 2000m (1500m - -500m). I change vertex_spacing to 0.25. * I now have an accurate representation of the data, and am consuming the VRAM for a 16k x 16k map for a high resolution 4k x 4k world. 0.25m is probably overkill and the increased vertex density does have a performance cost. * **Option 2**: After combining and scaling the images into one 16k image, I scale the resolution down to 4k. This erases data between each meter. * On import, I repeat the region size, offset, and scale as above, but leave vertex_spacing at 1. * I still have a 4k x 4k world, with an accurate scale and VAR, but now my terrain resolution is a more optimal 1m. I could also split the difference with a 0.5 or 0.667 vertex_spacing. Read [Importing Data](import_export.md) to learn how to import your `exr`. ================================================ FILE: doc/docs/import_export.md ================================================ Importing & Exporting Data =========================== This page describes how to get data into or out of our tool. You should read [Heightmaps](heightmaps.md) to learn about preparing files from various data sources. Currently importing and exporting is possible via code or our import tool. We will [make a UI](https://github.com/TokisanGames/Terrain3D/issues/81) eventually. In the meantime, we have written a script that uses the Godot Inspector as a makeshift UI. You can use it to make a data file for your other scenes. **Table of Contents** * [Supported Formats](#supported-formats) * [Importing Data](#importing-data) * [Exporting Data](#exporting-data) * [Exporting GLTF](#exporting-gltf) ## Supported Formats Terrain3D has three map types you can import or export: [Height](../api/class_terrain3dregion.rst#class-terrain3dregion-property-height-map), [Control](../api/class_terrain3dregion.rst#class-terrain3dregion-property-control-map), and [Color](../api/class_terrain3dregion.rst#class-terrain3dregion-property-color-map). We can import any supported image format that Godot can read, however we have recomendations below for specific formats to use for the different maps. Godot can read these file formats: * [Godot supported Image file formats](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html#supported-image-formats): `bmp`, `dds`, `exr`, `hdr`, `jpg`, `jpeg`, `png` 8-bit, `tga`, `svg`, `webp` * [Godot resource files](https://docs.godotengine.org/en/stable/classes/class_image.html#enum-image-format): Any data format listed at the link stored as a `tres` or `res`. Godot can write these file formats: * [Godot supported Image save functions](https://docs.godotengine.org/en/stable/classes/class_image.html#class-image-method-save-exr): `exr`, `png`, `jpg`, `webp` * `res`: Godot binary resource file with `ResourceSaver::FLAG_COMPRESS` enabled. The format contained inside is defined in our API linked at the top of this section for each map type. * `tres`: Godot text resource file. Not recommended. ### Height Map Recommendations Use `exr` or `r16/raw` for import / export. * `exr`: Values should be real heights, not normalized (0.0 - 1.0). RGB, not greyscale. 16 or 32-bit float, no transparency. Older versions of Photoshop can use [exr-io](https://www.exr-io.com/). * `r16`: Values are normalized to 0 - 65,535 (maximum 16-bit unsigned int value). Can be read/written by Krita. Min/max heights and image dimensions are not stored in the file, so you must keep track of them elsewhere, such as in the filename. * `raw`: This is not a format specification! It just means the file contains a dump of values. But what format are the values? They could be 8, 16, or 32-bit, signed or unsigned ints or floats, little or big endian byte order (aka Windows or macOS, Intel or Arm/Motorola). In order to read this file you need to know all of these, *and* the dimensions. `Photoshop Raw` only supports 8/16/32-bit float, little or big endian. **Terrain3D interprets a .raw extension as r16**, as does Unity, Unreal Engine, and various commercial software. Other notes: * `png`: Godot only supports 8-bit PNGs. This works fine for the colormap, but not for heightmaps. If you have a 16-bit `png`, convert it to `exr` with an image editor. * Only use 16 or 32-bit height data. If your data is 8-bit, it will look terraced and require a lot of smoothing to be useable. You could convert the image to 16-bit and blur it in Photoshop. * [Zylann's HTerrain](https://github.com/Zylann/godot_heightmap_plugin/) stores height data in a `res` file which we can import directly. No need to export it first, though his tool also exports `exr` and `r16`. ### Control Map Recommendations Our control maps use a [proprietary format](controlmap_format.md). We currently only import our own format. Use `exr` to export and reimport only from this tool. This is only for transferring the data to another Terrain3D data file. ### Color Map Recommendations * Any regular color format is fine. * `png` or `webp` are recommended as they are lossless, unlike `jpg`. * The alpha channel is interpretted as a [roughness modifier](../api/class_terrain3ddata.rst#class-terrain3ddata-property-color-maps) for wetness. So if you wish to edit the color map in an external program, you may need to disable or separate the alpha channel first. ## Importing Data 1. Open `addons/terrain_3d/tools/importer.tscn`. 2. Click Importer in the scene tree. ```{image} images/io_importer.png :target: ../_images/io_importer.png ``` 3. If you're importing a large file, you need to adjust `Terrain3D / Regions / Region Size`. You only get [1024 regions (32x32)](introduction.md#regions) so you need a region size greater than `image width / 32`. E.g., importing 10k x 10k means 10,000/32 = 312.5, so a minimum region size of 512 is required. 4. In the inspector, select a file for height, control, and/or color maps. See [formats](#supported-formats) above. File type is determined by extension. 5. Specify the `import_position` of where in the world you want to import. Values are rounded to the nearest `region_size` (defaults to 256). So a location of (-2000, 1000) will be imported at (-2048, 1024). Notes: * You can import multiple times into the greater world map by specifying different positions. So you could import multiple maps as separate islands or combined regions. * It will slice and pad odd sized images into region sized chunks (default is 256x256). e.g. You could import a 4k x 2k, several 1k x 1ks, and a 5123 x 3769 and position them so they are adjacent. * You can also reimport to the same location to overwrite anything there using individual maps or a complete set of height, control, and/or color. 6. Specify any desired `height_offset` or `import_scale`. The scale gets applied first. (eg. 100, -100 would scale the terrain by 100, then lower the whole terrain by 100). * We store full range values. If you sculpt a hill to a height of 50, that's what goes into the data file. Your heightmap values (esp w/ `exr`) may be normalized to the range of 0-1. If you import and the terrain is still flat, try scaling the height up by 300-500. * See [Scaling](heightmaps.md#scale) for more details on proper scaling and offset. 7. If you have a RAW or R16 file (same thing), it should have an extension of `r16` or `raw`. You can specify the height range and dimensions next. These are not stored in the file so you must know them. I prefer to place them in the filename. 8. Click `Run Import` and wait 10-30 seconds. Look at the console for activity or errors. If the `Terrain3D.debug_level` is set to `debug`, you'll also see progress. 9. When you are happy with the import, scroll down in the inspector until you see `Terrain3D / Data Directory`. Specify an empty directory and save. ```{image} images/io_data_directory.png :target: ../_images/io_data_directory.png ``` You can now load this directory into Terrain3D in any of your scenes. You can also load an existing data directory in the importer, then import more data into it and save it again. ## Exporting Data 1. Open `addons/terrain_3d/tools/importer.tscn`. 2. Click Importer in the scene tree. 3. Scroll the inspector down to `Terrain3D / Data Directory`, and load the directory you wish to export from. ```{image} images/io_data_directory.png :target: ../_images/io_data_directory.png ``` 4. Scroll the inspector to `Export File`. ```{image} images/io_exporter.png :target: ../_images/io_exporter.png ``` 5. Select the type of map you wish to extract: Height (32-bit floats), Color (rgba), Control (proprietary). 6. Specify the full path and file name to save. The file type is determined based upon the extension. You can enter any location on your hard drive, or preface the file name with `res://` to save it in your Godot project folder. See [formats](#supported-formats) for recommendations. 7. Click `Run Export` and wait. 10-30s is normal. Look at your file system or the console for status. Notes: * The exporter takes the smallest rectangle that will fit around all active regions in the world and export that as an image. So, if you have a 1k x 1k island in the NW corner, and a 2k x 3k island in the center, with a 1k strait between them, the resulting export image will be something like 4k x 5k. You'll need to specify the location (rounded to `region_size`) when reimporting to have a perfect round trip. * The exporter tool does not offer region by region export, but there is an API where you can retrieve any given region, then you can use `Image` to save it externally yourself. * Upon export, the console reports the image size and minimum/maximum heights, which is necessary for r16 heightmap exports. ## Exporting GLTF You can export the terrain as a mesh, without texturing. 1. Create a new empty scene and add a Terrain3D node. 2. Load your terrain `Data Directory`. 3. Select the Terrain3D node. 4. At the top of the viewport, select `Terrain3D / Bake ArrayMesh...` and bake at the desired LOD. 5. Delete the Terrain3D and other nodes, leaving only the generated MeshInstance3D node in this scene. 6. In the Godot menu, select `Scene / Export As... / GLTF 2.0 Scene...`. You can then use this mesh in Blender or other tools. It's fine for reference, but isn't an optimal mesh as there is a vertex every square meter. You can decimate or remesh it if you need a more optimal version. ================================================ FILE: doc/docs/installation.md ================================================ Installation & Upgrades ========================== **Table of Contents** * [Requirements](#requirements) * [Installing Terrain3D](#installing-terrain3d) * [Upgrading Terrain3D](#upgrading-terrain3d) ## Requirements * Terrain3D 1.0.1 supports Godot 4.4+. Use 1.0.0 for 4.3. * Supports Windows, Linux, and [macOS (read more)](platforms.md#macos). * Some platforms and renderers are experimental or unsupported. See [Supported Platforms](platforms.md). ## Installing Terrain3D ### From The Asset Library Terrain3D is [listed in the Asset Library](https://godotengine.org/asset-library/asset/3134), so you can download it directly within Godot. 1. Run Godot using the console executable so you can see error messages. 2. Setup a new project within Godot. 3. Click `AssetLib` at the top of the Godot window. 4. Search for `Terrain3D`, and click the entry from `TokisanGames` shown for your Godot version. 5. Click `Download`. 6. Godot will ask you to install files into `addons` and `demo`. Demo is optional, but highly recommended for troubleshooting. Click `Install`. 7. Restart when Godot prompts. 8. In `Project / Project Settings / Plugins`, ensure that Terrain3D is enabled. 9. Select `Project / Reload Current Project` to restart once more. 10. Open `demo/Demo.tscn`. You should see a terrain. Run the scene by pressing `F6`. If the demo isn't working for you, watch the [tutorial videos](tutorial_videos.md) and see [Troubleshooting](troubleshooting.md) and [Getting Help](getting_help.md). Continue below to [In Your Own Scene](#in-your-own-scene). ### From Github 1. Download the [latest binary release](https://github.com/TokisanGames/Terrain3D/releases) and extract the files, or [build the plugin from source](building_from_source.md). 2. Run Godot using the console executable so you can see error messages. 3. In the Project Manager, import the demo project and open it. Restart when it prompts. 4. In `Project / Project Settings / Plugins`, ensure that Terrain3D is enabled. 5. Select `Project / Reload Current Project` to restart once more. 6. If the demo scene doesn't open automatically, open `demo/Demo.tscn`. You should see a terrain. Run the scene by pressing `F6`. If the demo isn't working for you, watch the [tutorial videos](tutorial_videos.md) and see [Troubleshooting](troubleshooting.md) and [Getting Help](getting_help.md). Continue below. ### In Your Own Scene * To use Terrain3D in your own project, copy `addons/terrain_3d` to your project folder as `addons/terrain_3d`. Create the directories if they are missing. * When making a new 3D scene, add a Terrain3D node to your Scene panel. In the Inspector, find `Data Directory` and click the folder icon to specify an empty directory in which to store your data. You can share this directory with other scenes that will load the same terrain map. Different terrain maps need separate directories. * Optionally, click the arrow to the right of `Material` and `Assets` and save these as `.tres` files should you wish to share your material settings and asset dock resources (textures and meshes) with other scenes. This is recommended. Saving these in the data directory is fine. Next, review the [user interface](user_interface.md) or learn how to [prepare your textures](texture_prep.md) if you're ready to start creating. ## Upgrading Terrain3D To update Terrain3D: 1. Close Godot. 2. Remove `addons/terrain_3d` from your project folder. 3. Copy `addons/terrain_3d` from the new release download or build directory into your project addons folder. Don't just copy the new folder over the old, as this won't remove any files that we may have intentionally removed. ### Upgrade Path While later versions of Terrain3D can generally open previous versions, not all data will be transfered unless the supported upgrade path is followed. We occasionally deprecate or rename classes and provide upgrade paths to convert data for a limited time. If upgrading from a very old version, you may need to go through multiple steps to upgrade to the latest version. | Starting Version | Can Upgrade w/ Data Conversion | |------------------|-------------------| | 1.0.1 | 1.1.0 | | 1.0.0 | 1.0.1 - 1.1.0 | | 0.9.3 | 1.0.0 - 1.1.0 | | 0.9.2 | 0.9.3* | | 0.9.1 | 0.9.2 - 0.9.3* | | 0.9.0 | 0.9.2 - 0.9.3* | | 0.8.4 | 0.9.2 - 0.9.3* | | 0.8.3 | 0.8.4 - 0.9.0 | | 0.8.2 | 0.8.4 - 0.9.0 | | 0.8.1 | 0.8.4 - 0.9.0 | | 0.8.0 | 0.8.4 - 0.9.0 | * 0.9.3 - Data storage changed from a single .res file to one file per region saved in a directory. ================================================ FILE: doc/docs/instancer.md ================================================ Foliage Instancing ==================== Terrain3D provides two types of instancing systems that can be used to render not only grass, but also rocks, trees, pinecones, debris, or anything else you want. 1. A Particle Shader allows the GPU to automatically generate meshes around the camera. We have provided an example that you can modify and extend for your own needs in `extras/particle_example`. We refer to these meshes as `particles`. 2. [Terrain3DInstancer](../api/class_terrain3dinstancer.rst) optimally renders hundreds of thousands of meshes that have been intentionally placed either by code or by hand using Godot's [MultiMesh](https://docs.godotengine.org/en/stable/classes/class_multimesh.html) class. See this link for capabilities and engine tutorials. We refer to these meshes as `instances`. See [Procedural Placement](#procedural-placement) for a comparision between these two methods. The rest of this page is dedicated to learning how to use the instancer for manual and code placement. **Table of Contents** * [How To Use The Instancer](#how-to-use-the-instancer) * [Limitations](#limitations) * [LOD Support](#lod-support) * [Wind and Player Interaction](#wind-and-player-interaction) * [Procedural Placement](#procedural-placement) * [Importing From Other Tools](#importing-from-other-tools) ## How To Use The Instancer ### 1. Open the Asset Dock Meshes Click the `Meshes` tab in the Asset Dock. Use the icons to en/disable, edit, or clear an asset, or use these mouse buttons: * LMB - Select. * RMB - Edit. * MMB - Clear the asset and remove all instances. You can only remove entries from the end of the list. You can edit any of them, change the ID to the end, and then remove it. ```{image} images/mesh_asset_dock.png :target: ../_images/mesh_asset_dock.png ``` ### 2. Set Up A Mesh Asset Click the large plus button to add a new mesh asset. This will `Edit` the asset and display its settings in the inspector. ```{image} images/mesh_asset_inspector.png :target: ../_images/mesh_asset_inspector.png ``` Each mesh asset can be a generated texture card or a scene file (.tscn, .scn, .glb, .fbx). Changing the ID will reorder the assets in the list, allowing you to place one at the end for removal. Unlike the texture list, changing IDs won't change what is dispalyed on the ground because it also changes the mesh ID in the data. If your mesh doesn't have a material, add or customize the override material as needed. If you are using a generated texture card, add your texture file to the `albedo_texture` slot in the material and enable transparency. The generated texture card is 1, 2, or 3 QuadMeshes, on which you can apply a 2D texture of a plant as is commonly done in older or low poly games. See the [Terrain3DMeshAsset API](../api/class_terrain3dmeshasset.rst) for a description of the parameters. ### 3. Select The Mesh Asset Click the desired mesh in the Asset Dock. This should also enable the foliage tool in the toolbar, but also select that if not. ### 4. Adjust Placement Options ```{image} images/instancer_options.png :target: ../_images/instancer_options.png ``` The instancer has many options for adjusting the height, scale, rotation, and color shifts while painting. There are options for fixed adjustments and random variances. For instance, using fixed_scale to increase all instances by 200%, and using random_scale to vary each by +/- 20%. The paintable height offset on the tool settings bar is cummulative with the mesh asset height offset in the inspector. This allows you to specify the default on the asset, and override it while painting. Most of the options should be self explanatory. Adjusting the vertex color requires that `vertex_color_use_as_albedo` is enabled in your material. The hue shift applies to the specified vertex color, which should have some saturation to see any effect. e.g. Hue shift on white will not be visible. Hue shift on red will be. ### 4. Paint On The Ground Paint instances on the terrain. You can remove instances of the selected mesh by holding Ctrl while painting. Press Ctrl + Shift + LMB to remove mesh instances of any type. See [User Interface](user_interface.md) for additional keys. ## Limitations There are some caveats and limitations built into the engine that you should be aware of. ### Simple Objects Some 3D assets are complex objects with multiple, separate meshes, such as a tree trunk and leaves, a chest and lid, or a door frame and door. MultiMeshes supports only one mesh. If you give it a complex object with separate trunk and leaves, it won't work as expected. Either combine your complex objects into one (easy to do with the Join operation in Blender, while maintaining separate materials), or use another method of placement, such as AssetPlacer, Scatter, or manual placement. If you use a mesh with multiple materials, make sure they are connected to the Mesh resource on import into Godot, or in the MeshInstance3D override slots in the scene. We currently provide only a single material override. ### No Individual Culling A MultiMesh renders all instances in one draw call and does not cull individual instances via frustum, occlusion, nor distance. We mitigate this by generating multiple MultiMeshes. Each region is divided into 32x32m cells so that these MultiMeshes can be culled by frustum or occlusion. We expose visibility ranges in each mesh asset settings so they can be culled by distance as well. ### No Collision Multimeshes are generated and rendered on the GPU. The physics engine is on the CPU, and doesn't know anything about the placed instances. For now use this only for instances where collision is unnecessary like grass. In the future, instance collision will be generated using the collision shapes stored in your scene file. See [PR 699](https://github.com/TokisanGames/Terrain3D/pull/699). ### No Scene Transforms Currently, the instancer uses the first Mesh resource it finds in the scene file and uses it as is. It ignores all transforms in the file, as they are not stored in the Mesh resource. If you've built and imported your object with a non-zero transform, and have used the position, rotation, or scale in the scene file to fix your placement, then your instanced objects are going to have strange transforms. e.g. Your tree might be laying flat or be extremely large or small. Fix your object in blender by setting the origin point in the center or the bottom of the mesh. Move the mesh origin to (0, 0, 0). Ensure the scale is appropriate to real world units. Apply your transforms, so you have neutral transforms: position (0,0,0), rotation (0,0,0), scale (1,1,1). Be cognizant of your export and import settings. In the past, exporting via Blender FBX and importing into Godot produced a scene file where the mesh was scaled 0.01 and the parent node was scaled to 100 (or the opposite?). We use GLB/GLTF and don't have this issue. Ideally the object in Blender and all nodes in your scene file in Godot have neutral transforms, and your mesh vertex positions are scaled to real world coordinates. ### No VRAM Overrun Protection Godot currently has no protection against filling up your VRAM. You could do so if you have a simple mesh asset, say grass, with hundreds of thousands of instances on the ground, and then replace that mesh in the asset dock with a much larger mesh. That could instantly fill your VRAM. Your console will fill with Vulkan errors complaining about running out of VRAM and you'll have to force Godot to quit and restart. Your system may also crash if you have flakey drivers or hardware. ---------------------------------- ## LOD Support Meshes are often created with multiple Levels of Detail (LODs). These are separate meshes, usually combined in the same file, which are the same mesh at higher and lower vertex counts. They usually use the same material. Often assets have as the last LOD, a single quad mesh with a flat 2D billboard texture with a picture of the mesh. There is a quirk of the LOD numbering convention to keep in mind. LOD0 is the one closest to the camera and the highest detail. LOD4 is farther away, and lower detail. Confusingly, people might refer to LOD0 as higher than LOD4, even though 4 is greater than 0. When refering to a "higher" LOD, does this mean higher detail (LOD0) or higher LOD ID (LOD4)? We prefer using the terms near/far or first/last and avoid the terms higher/lower or greater/lesser. ### Automatic LOD Generation By default, Godot automatically generates LODs on imported meshes. Godot MultiMeshes do work with these auto generated LODs. If that is sufficient for you, ensure [last_lod](../api/class_terrain3dmeshasset.rst#class-terrain3dmeshasset-property-last-lod) and [lod0_range](../api/class_terrain3dmeshasset.rst#class-terrain3dmeshasset-property-lod0-range) are both `0` on the [Terrain3DMeshAsset](../api/class_terrain3dmeshasset.rst) and our LOD system will be disabled for this mesh. You may find that the generated meshes are sub par. If so, the only way to fix it is by disabling the auto LOD generation on the mesh import settings. This can be done with existing mesh instances on the ground. If using assets with artist created lods, you should also disable LOD generation in the import settings. Godot also automatically generates a shadow mesh, which you may desire to disable on import and use an artist created LOD as a [shadow impostor](#shadow-performance) instead. ### Artist Created LODs Your scene file can contain artist created LODs and we will use up to 10. Though we recommend no more than 4, as we will be generating MultiMesh3D nodes for every mesh type, every 32m square that is in use. The recommended tree structure looks like this. Have the MeshInstance3Ds use LOD# suffixes, and be siblings on the same level somewhere below the root node: ```{image} images/mesh_asset_lod_setup.png :target: ../_images/mesh_asset_lod_setup.png ``` Our system scans the provided scene file for _reasonable_ LOD structures. 1. It will first search for meshes that match `*LOD?`. e.g. `MyMeshInstanceLOD0`, `MyMeshInstanceLOD1`. If found, it will sort by the last digit and use the first 10. 2. Otherwise, it will find all meshes and use the first 10 in the order provided. 3. Finally, if the root node is a mesh, it will use that as `LOD0`. After linking your scene file, you can specify the visible distance range for each LOD or use the defaults, spacing every 32m. Instances are drawn using a 32x32m grid of MMIs, so LODs are switched a grid cell at a time, not per instance. If your LODs are fairly similar, pop-in shouldn't be terrible unlike the extreme example shown below. ```{image} images/mesh_asset_lods.jpg :target: ../_images/mesh_asset_lods.jpg ``` ### Shadow Performance Shadow calculation is expensive, especially when rendering a tree with tens of thousands of leaves. In most cases, this detail is far more than you need, especially when rendering on a detailed terrain. We provide two options to improve shadow calculation performance. 1. [last_shadow_lod](../api/class_terrain3dmeshasset.rst#class-terrain3dmeshasset-property-last-shadow-lod) - This setting defines the farthest LOD that will cast shadows. LODs beyond this will render without shadows. DirectionalLight3Ds also provide a shadow distance option, but you may wish to have the DL and tree shadows rendered farther out while grass shadows stop much closer. 2. [shadow_impostor](../api/class_terrain3dmeshasset.rst#class-terrain3dmeshasset-property-shadow-impostor) - We provide the option of using a shadow impostor, which uses a lower resolution mesh to calculate the shadows while rendering a higher resolution mesh. e.g. Setting this to 2 means when an instance is rendered at LOD0, it is rendered without shadows, while its LOD2 is rendered with only shadows. When LOD2 or LOD3 are visible, they use their own shadows. Impostors are only usable if you have more than one LOD mesh. However Godot automatically generates a shadow mesh on import of your meshes. You may wish to use it, or disable it and rely on the artist created LOD. You can see both settings applied in the picture below. Compare with the original. The meshes are 3m above the ground so we can clearly see the shadows. `last_shadow_lod = 1`, disabling shadows for LOD2 (green) and LOD3 (blue). And `shadow_impostor = 1`, making the LOD0 sphere use the LOD1 cube shadow. ```{image} images/mesh_asset_shadow_lods.jpg :target: ../_images/mesh_asset_shadow_lods.jpg ``` Original: ```{image} images/mesh_asset_lods.jpg :target: ../_images/mesh_asset_lods.jpg ``` ---------------------------------- ## Wind And Player Interaction These features can be implemented by having a wind shader or player interaction (grass flattening) shader in a ShaderMaterial attached to your mesh. We don't currently provide these shaders, but you can find both online. We may provide these shaders in the future. The instancer will use whatever material you've attached to the mesh or placed in the override slot, and the MultiMesh will automatically apply it to all instances. ## Procedural Placement Placing instances via code is possible. See the [Terrain3DInstancer API](../api/class_terrain3dinstancer.rst) for available functions. Also read this and the next section. One thing you must consider is if it makes sense to use the MultiMesh based instancer, or if it's more efficient to use a particle shader, such as the example included in our `extras/particle_example` directory. **MultiMesh Pros & Cons:** * Designed for hand painting and manual control * Can combine hand painting and API placement * API placement is possible, but a bit tricky * More control over placement as you can introduce more logic and code that considers many instances * Must place, store, and load all transforms which can be cumbersome for very large or procedural worlds * More optimal for the same number of instances on screen since Particle Shaders use MultiMeshes under the hood **Particle Shader Pros & Cons:** * All automatic placement, no manual control * Less control over placement that can only consider one instance in the logic * No data stored, so the number of instances in memory can be significantly less. For very large or procedural worlds this is much more efficient when loading and running. Perhaps it makes sense to use both, such as particles for grass, and instances for rocks, bushes and trees. You'll need to test and determine which methods will help you achieve your goal best. ## Importing From Other Tools You can find a sample script that will import data from SimpleGrassTextured in `project/addons/terrain_3d/extras/3rd_party/import_sgt.gd`. SGT is another MultiMesh management tool, so the only data that we need from it are the transforms. You could do something similar for other tools. **To use it:** 1. Setup the mesh asset you wish to use in the asset dock. 1. Select your Terrain3D node. 1. In the inspector, click Script (very bottom) and Quick Load `import_sgt.gd`. 1. At the very top, assign your SimpleGrassTextured node. 1. Input the desired mesh asset ID. 1. Click import. The output window and console will report when finished. 1. Clear the script from your Terrain3D node, and save your scene. The instance transforms are now stored in your region files. This script also serves as an example to learn how to use the API for procedural placement. Though this script uses add_multimesh(), you could manually iterate through the SGT multimesh, pull out the transforms, modify them, then send them to the instancer with add_transforms(). ================================================ FILE: doc/docs/introduction.md ================================================ Introduction ===================== Terrain3D is an editable **clipmap terrain** system divided into **regions**. Data can be hand edited or manipulated through the **API** in realtime. This page summarizes the core concepts of the whole system. ## Clipmap Terrain This is a Geomorphing Geometric Clipmap Mesh Terrain, as used in The Witcher 3. The terrain is made of several flat meshes, which have a higher density towards the center, and lower farther away. This provides the infrastructure for automatic Level of Detail (LOD) handling. ```{image} images/mesh_lods_flat.jpg :target: ../_images/mesh_lods_flat.jpg ``` The LODs are then blended together in a circular pattern. ```{image} images/mesh_circular_lods.png :target: ../_images/mesh_circular_lods.png ``` As the camera moves, the terrain meshes are centered on the camera, keeping higher LODs near the camera and lower LODs far away. The meshes and height data are sent to the GPU which updates the vertices of the mesh every frame. Even though the meshes are constantly moving, the terrain appears stable because the height data remains fixed in place. See [System Architecture](system_architecture.md) for more details. ## Regions The terrain is divided into regions, which represent both physical space, and containers for terrain data. By default, regions are 256m x 256m, but can range between 64m and 2048m. The region size defines a grid in the world with borders at -256, 0, 256, 512, 768, 1024, etc. This region size also corresponds to the 256x256 pixel size of the images and textures used to store terrain data. These sizes are independent of your ground texture sizes. You pay in memory and VRAM only for the regions you allocate. Space between the regions can be set to empty, flat, or shader generated noise (See `Terrain3D / Material / WorldBackground`). No collisions are generated outside of regions. ```{image} images/regions_used.jpg :target: ../_images/regions_used.jpg ``` There is currently a limit of 1024 regions or 32 x 32. So the maximum dimensions of the your world are `32 * region_size`, the maximum being 32 * 2048 or 65,536m per side. Region files are stored in the data directory as individual files, with their location coordinates in the filename. e.g. terrain3d_01-02.res, which represents region (+01, -02). The region grid is visible if `View Gizmos` is enabled in the Godot `Perspective` menu. Or if `Terrain3D / Regions / Show Grid` is enabled. ## Region Location Region locations are the grid coordinates that regions fit into. Represented as a Vector2i, eg (-1, 0), this is the primary key used for identifying regions in the API. Converting a global position to a region location is calculated by `floor(global position / region size)`, or by calling the function [Terrain3DData.get_region_location()](../api/class_terrain3ddata.rst#class-terrain3ddata-method-get-region-location). e.g. If region size is 1024, a global position of (2500, 0, 3700) would convert to (2, 3). It gets more complicated as [region_size](../api/class_terrain3d.rst#class-terrain3d-property-region-size) changes. Set `Terrain3D / Regions / Label Distance` to 1024-4096 to see region coordinates in the viewport. ## Vertex Painting This system is a vertex painter, not a pixel painter. In order to make a high performance terrain, we are spreading out say 1024 x 1024 pixels over 1024m x 1024m. Each square meter is influenced by only 4 pixels of data in the corners. We use sophisticated algorithms that allow natural blending of *quality* textures between the vertices. However, it is not magic. Pixel perfect painting is not practical. Texture artists place 4k or 8k textures on a human sized rock to acheive adequate texel density. How much larger than a rock is a 1024m x 1024m terrain, let alone 16km x 16km? Achieving the same texel density on a pixel painted terrain would consume far more VRAM than anyone has. The system we have works well for producing natural environments and is modeled off of the Witcher 3 terrain system. It will most likely work for your game as well. You'll read more about selecting and preparing quality textures in [Texture Prep](texture_prep.md) and [Texture Painting](texture_painting.md). ## API Aplication Programming Interface. This is the [list of variables and functions](../api/index.rst) available to you via code. Like Godot, this documentation is separated in two parts in the sidebar. The first lists various free form tutorial pages describing each aspect of the terrain system, such as what you are reading now. The second section is the API. You can find it at the very bottom left of the document titles. The API is also built into the Godot editor help system, once Terrain3D is installed. Finally, this documentation is versioned. Select the version that matches your version of the plugin in the menu. ================================================ FILE: doc/docs/keyboard_shortcuts.md ================================================ Keyboard Shortcuts ================= The following mouse and keyboard shortcuts or hotkeys are available. **Table of Contents** * [General Keys](#general-keys) * [Overlays](#overlays) * [Tool Selection](#tool-selection) * [Tool Specific Keys](#tool-specific-keys) * [Special Cases](#special-cases) ## General Keys * LMB - **Apply** the current tool to the terrain. * Ctrl + LMB - **Inverse** the current tool. Use Cmd on macOS. * Shift + LMB - **Smooth** height, texture blend, color, wetness. * Alt + LMB - **Alternate mode**, where applicable. * T - **Invert the slope filter** on the tool settings bar. * Ctrl + Z - **Undo**. View the entries in the Godot `History` panel. * Ctrl + Shift + Z - **Redo** * Ctrl + S - **Save** scene & modified terrain data. Saved regions are printed to the [console](troubleshooting.md#using-the-console). ## Overlays These toggle the Overlays found in the inspector. The mouse must be in the 3D Viewport with Terrain3D selected for these to work. * 1 - Toggle **Region Grid**. * 2 - Toggle **Region Label Distance** between 0 and 4096. * 3 - Toggle **Contour Lines**. Customize in the material when enabled. * 4 - Toggle **Instancer Grid**. * 5 - Toggle **Vertex Grid**. ## Tool Selection The mouse must be in the 3D Viewport with Terrain3D selected for these to work. * E - Add / remove **rEgion**. * R - Sculpt **Raise** or lower. * H - Sculpt **Height**. * S - Sculpt **Slope**. * B - Paint **Base** texture. * V - Spray **oVerlay** texture. * A - Paint **Autoshader**. * C - Paint **Color**. * W - Paint **Wetness**. * N - Paint **Navigation**. * X - Paint **Holes**. * I - Add mesh **Instances**. ## Tool Specific Keys ### Region Tool * LMB - **Add** a region. * Ctrl + LMB - **Remove** a region. ### Raise / Lower Tool * LMB - **Raise** the terrain. * Ctrl + LMB - **Lower** the terrain. * Shift + LMB - **Smooth** the terrain height. * Alt + LMB - **Lift floors**: Lifts up lower portions of the terrain without affecting higher terrain. Use it along the bottom of cliff faces. See [videos demonstrating before and after](https://github.com/TokisanGames/Terrain3D/pull/409). * Ctrl + Alt + LMB - **Flatten peaks**: Reduces peaks and ridges without affecting lower terrain around it. ### Height Tool * LMB - **Flatten** the terrain at the height set on the [settings bar](user_interface.md#tool-settings-bar). * Ctrl + LMB - **Pick & Flatten**: Flattens the terrain at the height first clicked. * Shift + LMB - **Smooth** the terrain height. ### Slope Tool *This is not to be confused* with the slope range filter on the [settings bar](user_interface.md#tool-settings-bar). * LMB - Set points to automatically create a slope, or if `Drawable` is checked, manually sculpt between points. * Shift + LMB - **Smooth** the terrain height. ### Paint Tool All operations are performed within the slope range on the [settings bar](user_interface.md#tool-settings-bar). * LMB - **Paint** the base texture. * Shift + LMB - **Smooth** the overlay texture blend value, averaging towards 0.5. * Alt + Shift + LMB - **Smooth** the blend value with a true average. ### Spray Tool All operations are performed within the slope range on the [settings bar](user_interface.md#tool-settings-bar). * LMB - **Spray** the overlay texture, increase the blend value, and once over a certain threshold, set overlay ID. * Alt + LMB - **Spray** the overlay texture, increase the blend value, and set the overlay ID immediately. * Ctrl + LMB - **Remove** the overlay texture. Reduces the blend value, thus showing more of the base. * Shift + LMB - **Smooth** the blend value, averaging towards 0.5. * Alt + Shift + LMB - **Smooth** the blend value with a true average. Note: Yes this is a ridiculous amount of options. But our lead environment artist uses all of them in different ways so we'll keep them for now. In the future we anticipate adding a 3rd texture layer for improved blending, which will likely consolidate all Paint and Spray options into one tool. ### Autoshader Tool * LMB - **Add** areas to the autoshader. * Ctrl + LMB - **Remove** areas from the autoshader. ### Color Tool All operations are performed within the slope range on the [settings bar](user_interface.md#tool-settings-bar). * LMB - **Paint** color on the terrain. * Ctrl + LMB - **Remove** painted color from the terrain. * Shift + LMB - **Smooth** the painted colors. ### Wetness Tool All operations are performed within the slope range on the [settings bar](user_interface.md#tool-settings-bar). * LMB - **Add** wet areas to the terrain. * Ctrl + LMB - **Remove** wet areas from the terrain. * Shift + LMB - **Smooth** the wetness values. ### Navigation Tool * LMB - **Add** navigable areas to the terrain. * Ctrl + LMB - **Remove** navigable areas. ### Holes Tool * LMB - **Add** holes. * Ctrl + LMB - **Remove** holes. * Shift + LMB - **Smooth** the terrain height. ### Instancer Tool All operations *except smoothing* are performed within the slope range on the [settings bar](user_interface.md#tool-settings-bar). * LMB - **Add** the selected mesh instances to the terrain. * Ctrl + LMB - **Remove** instances of the *selected* mesh asset. * Ctrl + Shift + LMB - **Remove** instances of *any* mesh asset. * Shift + LMB - **Smooth** the terrain height. --- ## Special Cases **macOS Users:** Use Cmd instead of Ctrl. **Touchscreen Users:** You'll see an `Invert` checkbox on the settings bar which acts like Ctrl to inverse operations. **Maya Users:** The Alt key can be changed to Space, Meta (Windows key), or Capslock in `Editor Settings / Terrain3D / Config / Alt Key Bind` so it does not conflict with Maya input settings `Editor Settings / 3D / Navigation / Navigation Scheme`. ================================================ FILE: doc/docs/license.rst ================================================ License ========== .. include:: ../../LICENSE.txt ================================================ FILE: doc/docs/navigation.md ================================================ # Navigation Navigation in games is quite a broad and sometimes complex topic. A full description can't be provided here, so it's recommended to familiarize yourself with the basic concepts in the [official Godot documentation](https://docs.godotengine.org/en/stable/tutorials/navigation/navigation_introduction_3d.html) first. This page describes how to bake a nav mesh (navigation mesh) for your terrain. ## Setting Up Navigation Nav meshes take a long time to bake, and in most games, it would be wasteful to generate vast amounts of navigation data for areas that navigation agents aren't going to use. So by default, all terrain is un-navigable. To make parts of it navigable, you must use the Navigable terrain tool. Navigable areas appear in dark magenta when this tool is selected. ```{image} images/nav_painting.png :target: ../_images/nav_painting.png ``` Next, you will need a `NavigationRegion3D` node. If you don't already have one, Terrain3D provides a convenient tool to set one up for you. Select your `Terrain3D` node, then in the `Terrain3D` menu on top, click `Set up Navigation`. ```{image} images/terrain3d_menu.png :target: ../_images/terrain3d_menu.png ``` The same steps can be performed manually if you prefer: 1. Create a `NavigationRegion3D` node. 2. Assign it a blank `NavigationMesh` resource. Review and adjust the settings on it if you need to. 3. If using the default source geometry mode, move the `Terrain3D` node to be a child of the new `NavigationRegion3D` node. Otherwise, if you selected one of the group-based modes, add the Terrain3D node to the group. ## Baking a Nav Mesh Once navigation has been set up, baking and re-baking it is straight-forward: 1. Select the `Terrain3D` node. 2. In Terrain3D menu, click `Bake NavMesh`. This can take a long time to complete. Note that the standard `Bake NavMesh` button that `NavigationRegion3D` provides will not generate a nav mesh for Terrain3D (see [godot-proposals#5138](https://github.com/godotengine/godot-proposals/issues/5138)). Only use the Terrain3D baker, which appears whenever you click the `Terrain3D` node or any `NavigationRegion3D` nodes. ```{image} images/nav_baking.png :target: ../_images/nav_baking.png ``` Note: After loading a scene and clicking a NavigationRegion3D, the menu won't appear until after Terrain3D has been clicked. If this is your first time setting up and baking a nav mesh, the only thing left to do is add your navigation agents. See [Godot's very clear and thorough documentation on navigation agents](https://docs.godotengine.org/en/stable/tutorials/navigation/navigation_using_navigationagents.html), which provides several handy template scripts you can use. You can also play with the `NavigationDemo.tscn` and `CodeGeneratedDemo.tscn` scenes which both demonstrate navigation.
## Tips ### Enable visible navigation for debugging This option enables a blue overlay mesh that displays where the navigation mesh exists. ```{image} images/nav_debugging.png :target: ../_images/nav_debugging.png ``` ### How to remove navigation or hide the purple indication You can remove navigation from the terrain by painting while holding down Ctrl You can hide the purple indication for where navigation is by clicking any tool in the toolbar other than Navigation. ### Save NavigationMesh resources to disk NavigationMesh resources can bloat the size of your scene. It's recommended to save these resources to disk in binary format with the `.res` extension. ### Use multiple nav meshes in large scenes As mentioned, in many games, large areas of terrain are generally unreachable to agents. The 'Navigable Areas' tool is used to reduce the amount of geometry coming from the `Terrain3D` node, however having lots of other meshes in unreachable areas can also lead to long bake times. If you have a very large scene in, for example, an open world RPG, it's better to have multiple small nav meshes that cover only what you need, rather than one giant one covering the entire world. In said example, each RPG town could have its own nav mesh. To do this, you would need to: 1. Create a NavigationRegion3D node for each town, each with their own NavigationMesh resources (i.e. unique, not shared). 2. Define the [`filter_baking_aabb`](https://docs.godotengine.org/en/stable/classes/class_navigationmesh.html#class-navigationmesh-property-filter-baking-aabb) on each nav mesh, so that it only bakes objects within its own area. 3. To use the same Terrain3D node with multiple NavigationRegion3Ds, change the nav meshes to use one of the group modes [`SOURCE_GEOMETRY_GROUPS_*` modes](https://docs.godotengine.org/en/stable/classes/class_navigationmesh.html#class-navigationmesh-property-geometry-source-geometry-mode), add the Terrain3D node to that group and bake. Alternatively, using the default `SOURCE_GEOMETRY_ROOT_NODE_CHILDREN` mode, add Terrain3D as a child of one NavigationRegion3D and bake navigation with the Terrain3D menu. Then move it as a child of the next and bake. ## Common Issues ### Navigation won't generate where foliage instances have been placed. Change [NavigationMesh.parsed_geometry_type](https://docs.godotengine.org/en/stable/classes/class_navigationmesh.html#class-navigationmesh-property-geometry-parsed-geometry-type) from `Mesh Instance` (visual) to `Static Colliders`. ### NavigationMeshSourceGeometryData3D is empty. Parse source geometry first. The engine produces this error if there's nothing for a NavigationRegion3D to generate a nav mesh from. The most likely cause, if you're using Terrain3D, is that you haven't painted any parts of the terrain as navigable. ### Navigation map synchronization error `Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'` There are several possible causes for this. If the `cell_size` of your nav mesh matches the `cell_size` in your project settings, it's currently believed to be caused by [an engine bug](https://github.com/godotengine/godot/issues/85548). This error message shouldn't affect the usability of your nav meshes. ### The Nav mesh is broken over steep slopes The NavigationRegion3D has settings for adjusting the slope you wish to allow in your nav mesh. Adjust those settings, and review the other settings available in Godot's documentation. ### Agents get stuck on collisions, run in circles, go off the nav mesh, or fail to find obvious paths Developing good path-following behaviors is a very complex topic, far beyond the scope of this article. In general, make sure your NavigationMesh settings, NavigationAgent3D settings, and collisions are all consistent with each other. If a NavigationAgent3D is using a NavigationMesh that was baked for smaller agents than itself, for instance, then it's going to get stuck. You can try repainting an area and regenerating the nav mesh. Making reasonable fallback behaviors, when you're able to detect in a script that something has gone awry, can also help. ## Baking a Nav Mesh at Runtime If your project has dynamic, or generated terrain, or if the traversable area of your terrain is so gigantic that it can't be baked in the editor, then you might need to use runtime navmesh baking. Terrain3D contains an example script that shows how to bake terrain nav meshes at runtime, which you can find in the `CodeGeneratedDemo.tscn` scene.
The script periodically re-bakes a nav mesh in the area around the player as it moves through the scene. Adjust `bake_cooldown` if you wish the baker to update more frequently, at the cost of CPU cycles. You can also adjust how frequently the navigation agent updates its path by adjusting `Enemy.gd:RETARGET_COOLDOWN`. If you have a lot of agents, that's going to come with a performance hit. ### Performance Tips Navigation baking is slow. Editor or compile-time baking navigation is usually a better option for games. Runtime baking can still be usable however. There are a few things you can do to speed it up, or work around the slowness: * Create a set of fallback behaviors that get used when proper navigation isn't possible. In this way, you can make the AI degrade without completely failing while waiting for the nav server, or when out of range of the nav mesh. For instance the enemy could simply move straight towards the player if navigation is not ready yet. It could have rudimentary ability to detect cliffs and obstacles with raycasts. * Reduce the speed of the player character. Delays happen frequently in the demo because the player can move across the mesh so rapidly. * Reduce the size of the baked mesh with `mesh_size` to make it cheaper to bake. * Increase the size of the cells (i.e. reduce the resolution of the navmesh) to reduce the amount of work to do. Change `cell_size` in the `template` NavigationMesh. If you increase it too far, obstacles may hide within a cell and break your navigation. You can write fallback behaviors for that as well, or ensure that your obstacles are all larger than the cell size. ================================================ FILE: doc/docs/nightly_builds.md ================================================ Nightly Builds ==================== Traditionally, "nightly builds" are automatically built from the main development tree every night. Our Github repository is configured to build automatically on every commit and PR push. If you want to test more recent versions of Terrain3D than the releases, you can download these "push builds" or "nightly builds". They are exactly the same construction as the releases except for the commit used. However, they are inherently less tested as new commits have more recently been merged in, and may have more bugs. 1. [Click here](https://github.com/TokisanGames/Terrain3D/actions/workflows/build.yml?query=branch%3Amain) for `Github Actions`, `Build All` workflow, `main` branch. 2. Click the most recent successful build: ```{image} images/build_workflow.png :target: ../_images/build_workflow.png ``` 3. Download the artifact, which is just a zip file with a nightly build. You must be logged in to github to download it. ```{image} images/build_artifact.png :target: ../_images/build_artifact.png ``` If have trouble with the unsigned macOS released builds or wish to contribute, learn how to [Build from Source](building_from_source.md) on your own system. ## PR Builds You can also test builds of PRs. Instead of specifying the `main` branch above, select the branch listed at the top of the PR. Or click the `Checks` tab, then `Build All` to see the summary page, which has the artifact. ================================================ FILE: doc/docs/occlusion_culling.md ================================================ Occlusion Culling =================== Occlusion culling allows the renderer to hide objects that are behind the terrain. See the example below. For more information about occlusion culling in Godot, see [the official docs](https://docs.godotengine.org/en/stable/tutorials/3d/occlusion_culling.html).
## Baking Terrain Occlusion First, enable `use_occlusion_culling` in the project settings. Then in the editor: * Select Terrain3D. * Click `Terrain3D`, then `Bake Occluder3D` in the menu above the viewport. * On the popup window accept the default, LOD 4. ```{image} images/terrain3d_menu.png :target: ../_images/terrain3d_menu.png ``` * Select the OccluderInstance3D child node. * In the inspector, click the arrow to the right of the ArrayOccluder resource and choose save. Save the file as a binary `.occ`. ```{image} images/oc_save.png :target: ../_images/oc_save.png ``` ### More Information The LOD value determines the granularity of the occlusion mesh, and therefore the number of vertices used. Baking an occluder at a lower level of detail (higher number) will reduce opportunities for culling, but make occlusion testing quicker. Baking pauses the editor for about 5 seconds per region at LOD4. It has to read every pixel on the height map once to make sure that the generated occluder doesn't extend above or outside the clipmap (at any level of detail). After baking completes, an OccluderInstance3D node is created as a child of the Terrain3D node with an Occluder3D resource in it containing the baked occlusion mesh. The generated Occluder3D resource can be quite large. It's more efficient to store this in binary format than text format, so you should always save this resource to a `.occ` file after it has been baked. The occluder has to be manually baked again each time the terrain is altered. ## Baking Occlusion For An Entire Scene Godot has a built-in tool for baking occlusion for all meshes. It is visible when you add an OccluderInstance3D to the scene tree and select it. ```{image} images/oc_oc_menu.png :target: ../_images/oc_oc_menu.png ``` This tool doesn't know about Terrain3D, so it will bake all MeshInstances and ignore Terrain3D. To get a complete bake, you will need to use our menu to bake the terrain occluder, then add a separate OccluderInstance3D to your scene and bake all of your other meshes. ================================================ FILE: doc/docs/platforms.md ================================================ Supported Platforms ========================= This page documents the status of various platforms and renderers supported by Godot. ## Table of Contents **Operating Systems** * [Windows](#windows) * [Linux](#linux) * [macOS](#macos) * [IOS](#ios) * [Android](#android) * [Steam Deck](#steam-deck) * [HTML / WebGL](#webgl) **Renderers** * [Forward+ / Vulkan](#vulkan) * [Forward+ / Direct3D 12](#d3d12) * [Forward+ / Metal](#metal) * [Mobile / Vulkan](#mobile) * [Compatibility / OpenGLES 3](#compatibility) ## Windows Fully supported. See [renderers](#supported-renderers). ## Linux Fully supported. See [renderers](#supported-renderers). ## macOS Godot and Terrain3D work fine on macOS, however Apple security is overly aggressive when using our release binaries. Users have reported errors like this: `"libterrain.macos.debug" cannot be opened because the developer cannot be verified. macOS cannot verify that this app is free from malware.` Running the following commands within the downloaded and unzipped directory appears to resolve the issue. ``` $ xattr -dr com.apple.quarantine addons/terrain_3d/bin/libterrain.macos.debug.framework/libterrain.macos.debug $ xattr -dr com.apple.quarantine addons/terrain_3d/bin/libterrain.macos.release.framework/libterrain.macos.release ``` You can also [read comments and workarounds](https://github.com/TokisanGames/Terrain3D/issues/227) from other users. If bypassing Apple security is not working, or if approaching a release date, macOS users should [build from source](building_from_source.md) so you can sign the binaries with your own developer account. ## IOS As of Terrain3D 0.9.1 and Godot 4.2, iOS is reported to work with the following setup: * Use textures that Godot imports (converts) such as PNG or TGA, not DDS. * Enable `Project Settings/Rendering/Textures/VRAM Compression/Import ETC2 ASTC`. * Set `Project Settings/Application/Config/Icon` to a valid file (eg `res://icon.png` or svg). * The Terrain3D release includes iOS builds, however they aren't signed and may not work. * If needed, build the iOS library and make sure the binaries are placed where identified in `terrain.gdextension`: ``` scons platform=ios target=template_debug scons platform=ios target=template_release ``` * Select `Project/Export`, Add the iOS export preset and configure with `App Store Team ID` and `Bundle Identifier`, then export. ```{image} images/ios_export.png :target: ../_images/ios_export.png ``` Once it has been exported, you can open it in XCode, run locally, or on your device. Further reading: * [Issue 218](https://github.com/TokisanGames/Terrain3D/issues/218) * [PR 219](https://github.com/TokisanGames/Terrain3D/pull/219) * [PR 295](https://github.com/TokisanGames/Terrain3D/pull/295) ## Android As of Terrain3D 0.9.1 and Godot 4.2, Android is reported to work. It is still a bit experimental. * Use textures that Godot imports (converts) such as PNG or TGA, not DDS. * Enable `Project Settings/Rendering/Textures/VRAM Compression/Import ETC2 ASTC`. The release builds include binaries for arm32 and arm64. Some mobile devices appear to not fully support texture arrays. Or perhaps they need more testing of different texture formats. Further reading: * [Issue 668](https://github.com/TokisanGames/Terrain3D/issues/668) * [Issue 137](https://github.com/TokisanGames/Terrain3D/issues/137) * [Issue 197](https://github.com/TokisanGames/Terrain3D/issues/197) ## Steam Deck As of Terrain3D v0.9.1 and Godot 4.2, the first generation Steam Deck is reported working, running the demo at 200+ fps. The user got it working with the following: * Use SteamOS 3.5.7 * Install `glibc` and `linux-api-headers` in addition to the standard Godot dependencies * [Build from source](building_from_source.md) Further reading: * [Issue 220](https://github.com/TokisanGames/Terrain3D/issues/220#issuecomment-1837552459) ## WebGL The releases and nightly builds include a web build, but web exports are very experimental. We have had success on some platforms. See the progress and setup instructions in [Issue 502](https://github.com/TokisanGames/Terrain3D/issues/502). Supported Renderers ==================== * [Forward+ / Vulkan](#vulkan) * [Forward+ / Direct3D 12](#d3d12) * [Forward+ / Metal](#metal) * [Forwad Mobile](#mobile) * [Compatibility / OpenGLES 3](#compatibility) ## Vulkan The Forward+ Vulkan renderer is fully supported. ## D3D12 The Forward+ Direct3D 12 support should be fully supported as of Godot 4.6. ## Metal Support for Apple's Metal for iOS and macOS was merged into Godot 4.4-dev1. No testing has been done, and Terrain3D support is unknown. ## Mobile The Forward Vulkan Mobile renderer is fully supported. ## Compatibility The OpenGLES 3.0 Compatibility renderer is fully supported since Terrain3D 1.0 and Godot 4.4. A small set of shader pre-processor statements are used to override fma() and dFdxCoarse(). This allows the shader to work with the compatibility renderer without intrusive changes. ================================================ FILE: doc/docs/press.md ================================================ # Press Terrain3D has been featured in the following media: | Organization | Published | Link 1 | Link 2| |---|---|---|---| | 80 Level | January 31, 2025 | [Dynamic Collision Mode](https://80.lv/articles/godot-engine-s-terrain3d-received-dynamic-collision-mode/) | [Tweet](https://x.com/80Level/status/1885274326930194553) | 80 Level | July 19, 2024 | [Paint w/ Texture Rotation & Scale](https://80.lv/articles/you-can-now-paint-texture-rotation-scale-with-godot-s-terrain3d/) | [Tweet](https://x.com/80Level/status/1814253864545042946) | Blips | Dec 26, 2023 | [Terrain System Enters Beta](https://blog.blips.fm/articles/terrain3d-a-terrain-system-for-godot-4-enters-beta-phase) | 80 Level | Dec 19, 2023 | [Terrain System Enters Beta](https://80.lv/articles/this-free-terrain-system-for-godot-engine-enters-beta/) | [Tweet](https://twitter.com/80Level/status/1736937052946543084) | 80 Level | July 27, 2023 | [Free Terrain For Godot](https://80.lv/articles/terrain3d-a-free-terrain-system-for-godot-engine/) | [Tweet](https://twitter.com/80Level/status/1684473704972177409) | Blips | July 24, 2023 | [New Terrain For Godot](https://blog.blips.fm/articles/terrain3d-a-new-terrain-system-for-godot-4) | Godot Engine | July 19, 2023 | [Godot 4.2dev1 & Terrain3D](https://godotengine.org/article/dev-snapshot-godot-4-2-dev-1/) | GameFromScratch | July 18, 2023 | [New Terrain Engine for Godot (video)](https://www.youtube.com/watch?v=NwJEXOglBrQ) | [Article](https://gamefromscratch.com/terrain3d-a-new-terrain-engine-for-godot/) ================================================ FILE: doc/docs/programming_languages.rst ================================================ Programming Languages ===================== Any language Godot supports should be able to work with Terrain3D via the GDExtension interface. This includes `C# `__, and `several others `__. Here are some tips for integrating with Terrain3D. .. image:: images/integrating_gdextension.jpg :target: ../_images/integrating_gdextension.jpg Detecting If Terrain3D Is Installed ----------------------------------- To determine if Terrain3D is installed and active, `ask Godot `__. This works only in the editor for tool scripts and editor plugins. C# might be different depending if you're using the generated bindings :doc:`Generating C# Bindings `. .. tabs:: .. tab:: GDScript .. code:: gdscript print("Terrain3D enabled: ", EditorInterface.is_plugin_enabled("terrain_3d")) .. tab:: C# .. code:: c# GD.Print("Terrain3D enabled: ", EditorInterface.Singleton.IsPluginEnabled("Terrain3D")); .. tab:: C# (Bindings) .. code:: c# using TokisanGames; ... GD.Print("Terrain3D enabled: ", EditorInterface.Singleton.IsPluginEnabled(nameof(Terrain3D))); You can also ask ClassDB if the class exists: .. tabs:: .. tab:: GDScript .. code:: gdscript ClassDB.class_exists("Terrain3D") ClassDB.can_instantiate("Terrain3D") .. tab:: C# .. code:: c# ClassDB.ClassExists("Terrain3D"); ClassDB.CanInstantiate("Terrain3D"); .. tab:: C# (Bindings) .. code:: c# using TokisanGames; ... ClassDB.ClassExists(nameof(Terrain3D)); ClassDB.CanInstantiate(nameof(Terrain3D)); Instantiating & Calling Terrain3D --------------------------------- Terrain3D is instantiated and referenced like any other object. See the ``CodeGeneratedDemo.tscn`` or `CodeGeneratedCSDemo.tscn` demos for examples of initiating Terrain3D from script. .. tabs:: .. tab:: GDScript .. code:: gdscript var terrain: Terrain3D = Terrain3D.new() print(terrain.get_version()) terrain.assets = Terrain3DAssets.new() .. tab:: C# .. code:: c# var terrain = ClassDB.Instantiate("Terrain3D"); GD.Print("Terrain3D version: ", terrain.AsGodotObject().Call("get_version")); terrain.AsGodotObject().Set("assets", ClassDB.Instantiate("Terrain3DAssets")); .. tab:: C# (Bindings) .. code:: c# using TokisanGames; ... Terrain3D terrain = Terrain3D.Instantiate(); GD.Print("Terrain3D version: ", terrain.Version); terrain.Assets = Terrain3DAssets.Instantiate(); You can also check if a node is a Terrain3D object: .. tabs:: .. tab:: GDScript .. code:: gdscript if node is Terrain3D: .. tab:: C# .. code:: c# if (myNode.IsClass("Terrain3D")) { .. tab:: C# (Bindings) .. code:: c# using TokisanGames; ... if (myNode.IsClass(nameof(Terrain3D))) { For more information on C# and other languages, read `Cross-language scripting `__ in the Godot docs. Finding the Terrain3D Instance ------------------------------ These options are for programming scenarios where a user action is intented to provide your code with the Terrain3D instance. - If collision is enabled in game (default) or in the editor (debug only), you can run a raycast and if it hits, it will return a ``Terrain3D`` object. See more in the `raycasting `__ section. - Your script can provide a NodePath and allow the user to select their Terrain3D node. - You can search the current scene tree for `nodes of type `__ “Terrain3D”. .. tabs:: .. tab:: GDScript .. code:: gdscript var terrain: Terrain3D # or Node if you aren't sure if it's installed if Engine.is_editor_hint(): # In editor terrain = get_tree().get_edited_scene_root().find_children("*", "Terrain3D").front() else: # In game terrain = get_tree().get_current_scene().find_children("*", "Terrain3D").front() if terrain: print("Found terrain") .. tab:: C# (Bindings) .. code:: c# using System.Linq; using TokisanGames; ... Terrain3D terrain; Node terrainNode; if (Engine.IsEditorHint()) terrainNode = GetTree().GetEditedSceneRoot().FindChildren("*", nameof(Terrain3D)).FirstOrDefault(); else terrainNode = GetTree().GetCurrentScene().FindChildren("*", nameof(Terrain3D)).FirstOrDefault(); if (terrainNode != null) { terrain = Terrain3D.Bind(terrainNode); GD.Print("Found terrain: ", terrain); } Detecting Terrain Height ------------------------ See `Collision `__ for several methods. Getting Updates on Terrain Changes ---------------------------------- ``Terrain3DData`` has `signals <../api/class_terrain3ddata.rst#signals>`__ that fire when updates occur. You can connect to them to receive updates. ================================================ FILE: doc/docs/shader_design.md ================================================ Shader Design ============== Our shader combines a lot of ideas and code from [cdxntchou's IndexMapTerrain](https://github.com/cdxntchou/IndexMapTerrain) for Unity, [Zylann's HTerrain](https://github.com/Zylann/godot_heightmap_plugin/) for Godot, the Witcher 3 talk linked in the System Design page, and our own thoughts and optimizations. In the material, you can enable `shader_override_enabled` with an empty `shader overide` slot and it will generate the default shader code so you can follow along with this document. You can also find the minimum shader needed to enable the terrain height functionality without texturing in `addons/terrain_3D/extras/shaders/minimum.gdshader`. At its core, the current texture painting and rendering system is a vertex painter, not a pixel painter. We paint codes at each vertex, 1m apart by default, represented as a pixel on the [Control map](controlmap_format.md). The shader uses its many parameters to control how each pixel between the vertices blend together. For an artist, it's not as nice to use as a multi-layer, pixel based painter you might find in Photoshop, but the dynamic nature of the system does afford other benefits. The following describes the various elements of the shader in a linear fashion to help you understand how it works. ## Texture Lookup Methods First some terminology and notes about the various methods used to retreive a texture value. A `pixel` is a colored dot on your screen (aka `picture element`). A `texel` is a colored dot on a texture in memory (aka a `texture pixel`). When a grey value is read from a rock texture, it's a texel. When it is projected on a rock mesh with lighting and rendered on your screen, it's a pixel. The GPU does a lot of work for gamedevs when using the standard lookup function `texture()`, such as calculating which mipmap level to use and automatically interpolating surrounding texels. A lot of this work we don't want done automatically and instead do it ourselves so we can optimize or reuse some of the process. Here's a quick summary of potential operations we might use to retreive a texel: * `texture()` - We provide UVs. The GPU calculates UV derivatives and mipmap LOD, then returns an interpolated value. * `textureGrad()` - We provide UV derivatives. The GPU calculates mipmap LOD and returns an interpolated value. * `textureLod()` - We provide UVs and mipmap LOD. The GPU returns an interpolated value. * `texelFetch()` - We provide UVs and mipmap LOD. The GPU returns the texel. `texture*()` functions interpolate from multiple samples of the texture map if linear filtering is enabled. Using either nearest filtering or `texelFetch()` disables interpolation. ## Uniforms [Terrain3DMaterial](../api/class_terrain3dmaterial.rst) exposes uniforms found in the shader, including any you have added. Uniforms that begin with `_` are considered private and are hidden, but you can still access them via code. See [Tips](tips_technical.md#accessing-private-shader-variables). These notable [Terrain3DData](../api/class_terrain3ddata.rst) arrays are passed in as uniforms. The API has more information on each. * [_region_map](../api/class_terrain3ddata.rst#class-terrain3ddata-method-get-region-map), [_region_locations](../api/class_terrain3ddata.rst#class-terrain3ddata-property-region-locations) store the location and ID of each region * [_height_maps](../api/class_terrain3ddata.rst#class-terrain3ddata-property-height-maps), [_control_maps](../api/class_terrain3ddata.rst#class-terrain3ddata-property-control-maps), and [_color_maps](../api/class_terrain3ddata.rst#class-terrain3ddata-property-color-maps) store the elevation, texture layout, and colors of the terrain, indexed by region ID * [_texture_array_albedo](../api/class_terrain3dassets.rst#class-terrain3dassets-method-get-albedo-array-rid), [_texture_array_normal](../api/class_terrain3dassets.rst#class-terrain3dassets-method-get-normal-array-rid) store the ground textures, indexed by texture ID ## Vertex() & Supporting Functions The CPU has already created flat mesh components that make up the clipmap mesh, and collision shapes with heights. The vertex shader adjusts the mesh to match the collision shape defined by the heightmap. `vertex()` is run for every vertex on these mesh components. Noteworthy supporting functions include `get_index_coord()` and `get_index_uv` which take in world space XZ coordinates and return region coordinates, either real or normalized. They also return the region ID, which indexes into the texture arrays. Within `vertex()`, the controlmap is read to determine if a vertex is a hole, the heightmap is read if valid, and if world noise should be calculated. The values are accumulated to determine the final height, and vertex normal. If the optional world noise is enabled, it generates fractal brownian noise which can be used as background hills outside of your regions. It's a visual only effect, can be costly at high octaves, and does not generate collision. As `render_mode skip_vertex_transform` is used, we apply the necessary matrix transforms to set the final `VERTEX` position, matching the collision mesh. ## Fragment() `fragment()` is run for every screen pixel in which the terrain mesh appears on screen. This is many more times than the number of vertices. ### Grid Offsets, Weights and Derivatives Features like UV rotation, UV scale, detiling, and projection break the continuity of texture UVs. So we must use `textureGrad()` and provide the derivatives for it. We take 1 set of `dfdx(uv)` and `dFdy(uv)` saved in `base_derivatives` and then scale them as needed. The lookup grid and blend weights are initially calculated here, as they are used for both normals, and material lookups. To see the grid, add this at the end of the shader `ALBEDO *= vec3(round(weight), 0.0) + .5;` which shows horizontal stripes in red, and vertical stripes in green. The inverse is stored in `invert`, so where horizontal stripes alternate `red, black, red`, this has `black, red, black`. You can see the vertices if you enable `Debug Views / Vertex Grid`, as shown. ```{image} images/sh_mirror.png :target: ../_images/sh_mirror.png ``` A determination is made with the base derivatives, of whether it is reasonable to skip all additional lookups required to do the bilinear blend. Skipping this can save a significant amount of bandwidth and processing for the GPU depending on how much of the screen is occupied by distant terrain. It's worth noting that as this is calculated from screen space derivatives, it is independent of screen resolution. ### Normal Calculation The next step is calculating the terrain normals. Clipmap terrain vertices are farther apart at lower LODs, causing certain things like normals to look strange when viewed in the distance. Because of this, we calculate normals by taking derivatives from the heightmap in `fragment()`. We use `texelFetch()` to read the height values on each vertex without any automatic interpolation. These values are used to generate a set of normals per-index, and an interpolated value for smooth normals. Using `texture()` here would not only trigger many additional lookups of adjacent vertices for interpolation, but also create artifacts when interpolating across region boundaries. Generating normals in the shader works fine, and modern GPUs can easily handle the load of the additional height lookups and the on-the-fly calculations. Doing this saves 3-4MB VRAM per region (sized at 1024) instead of pre-generating a normal map and passing it to the shader. ### Material Creation The control maps are queried for each of the 4 adjacent grid points (aka vertices and indices) and stored in `control[0]`-`control[3]`. The control map bits, are decoded when needed, as defined in [Controlmap Format](controlmap_format.md). If the Autoshader is enabled, the control_data for each of the 4 grid points is overwritten with the autoshader ids and blend value calculated from the terrain normal and height. We then iterate over the point control_data to calculate the `texture_weight` of each `texture_id`. This takes into account how many points have a given texture, and the bilinear weight. Eg if an ID is present at only 3 of 4 index points, then its weight would be: `weights[0] * blend_weight[0] + weights[2] * blend_weight[2] + weights[3] * blend_weight[3]` This allows smooth interpolation and better blend shaping across the points during the material accumulation. The textures at each vertex are looked up and accumulated, the weight for each lookup is modified by the texture height value, sharpness, and potentially the world normal. The world space normal blend adjustment takes the normal map value of the texture in the base layer and uses it to modify the blend weight of the overlay layer of the same point. Where possible, texture lookups are branched and in some cases only 2 samples are required, bringing VRAM bandwith requirements to a minimum. ### Texture Sampling - Splat Map vs Index Map This analysis compares the *splat map* method used by many other terrain systems with an *index map* method, used by Terrain3D and [cdxntchou's IndexMapTerrain](https://github.com/cdxntchou/IndexMapTerrain). At their core, all height map based terrain tools are just fancy painting applications. For texturing, the "reasonable" approach would be to define a strength value for each texture ID at each pixel and blend them together as occurs when painting in Photoshop. Subtly brushing with red gradually increases the R in the RGB value of the pixel. This is how a splat map works, but instead of painting with just RGBA, it paints with RGBACDEFHIJKLMNO (for 16 textures). The "unreasonable" approach would be to use an entirely different methodology in order to reduce memory or increase speed. The **Splat map approach** specifies an 8-bit strength value for each texture. 16 textures fits into 4 splat maps each made up of 32-bit RGBA values, for a total of 16 bytes. Double for 32 textures. When rendering, all splat maps are sampled, and of the 16-32 values, the 4 strongest are blended together, per terrain pixel. The blending of textures for pixels drawn between vertices is handled by the GPU's linear interpolated texture filter during texture lookups. The **Index map approach** samples a control map at 4 fixed grid points surrounding the current terrain pixel. The 4 surrounding samples have two texture IDs, and a blending value. The texture values range from 0-31, each stored in 5 bits. The blend value is stored in 8-bits. The position of the pixel within its grid square is used to bilinearly interpolate the values of the 4 surrounding samples. We disable the default GPU interpolation on texture lookups and interpolate ourselves here. At distances where the the bilinear blend would occur across only 1 pixel in sceen space, the bilinear interpolation is skipped, requiring only 1/4 of the normal samples. **Comparing the two methods:** * **Texture lookups** - Considering only lookups for which ground texture to use and loading the texture data: * Splat maps use 12-16 lookups per pixel depending on 16 or 32 textures: * 4-8 to get the 4 strongest texture IDs. 4 for 16 textures, 8 for 32. This retreives the texture ID for the closest vertex point. * 8 for the strongest 4 albedo_height textures and the 4 normal_rough textures * Terrain3D uses 5-20 lookups per pixel depending on terrain distance: * 1-4 for the surrounding 4 grid points on the control map. * 4-16 for the 2-4 albedo_height & normal_rough for the base and overlay textures, for each of the 4 grid points. * **VRAM consumed** * Splat maps store 16 texture strength values in 16 bytes per pixel, or 32 in 32 bytes per pixel. On a 4096k terrain with 16M pixels, splat maps consume 256MB for 16 textures, 512MB for 32. * Terrain3D stores 32 texture strengths in 18-bits. 5-bit base ID, 5-bit overlay ID, 8-bit blend value. We can store texture layout for 32 textures on a 4k terrain in only 36MB, for a 93% reduction in VRAM. The calculations above consider only the portion of lookups and VRAM used by the data that defines where textures are place on the terrain. In practical use there are many other features that greatly adjust both. As for usage of the two techniques: * Splat maps - 4 textures can be blended intuitively as one would paint in Photoshop. Some systems might introduce artifacts when 3-4 textures are blended in an area. * Terrain3D - Only 2 textures can stored in a vertex. However pixels are interpolated between the 4 adjacent vertices, so can easily blend between up to 4 textures based on painted blend value, height textures, and material settings. Thus getting a natural looking blend is easily doable if textures are properly setup with heights, using [the right technique](texture_painting.md#manual-painting-technique). ### Calculating Weights & Applying PBR Since each terrain pixel exists within four points on a grid, we can use bilinear interpolation to calculate weights based on how close we are to each grid point. e.g. The current pixel is 75% to the next X and 33% to the next Y, which gives us a weighted strength for the texture values from each adjacent point. We lookup the 4 adjacent textures, take the weighted average, and apply height blending to calculate our final value. The color map and macro variation are multiplied onto the albedo channel. Then all PBR values are sent to the GPU. ================================================ FILE: doc/docs/system_architecture.md ================================================ System Architecture ===================== ## Geometry Clipmap Terrain Some other terrain systems generate a grid of mesh chunks. As the camera moves forward, old meshes far behind are destroyed and new meshes in front are created. Our approach is different. Like The Witcher 3, this system uses a geometry clipmap, where the mesh components are generated once, and at periodic intervals are centered on the camera location. On each update, the vertex heights of the mesh components are adjusted by the GPU vertex shader reading from the terrain heightmap. Levels of Detail (LODs) are built into the mesh generated on startup, so don't require any additional consideration once placed. Lower detail levels are automatically placed far away once all mesh components are recentered on the camera. See Mike Savage's excellent blog below for visual examples and further explanations on the technique. We provide a system where one can allocate regions for sculpting and texturing and only pay the VRAM and storage costs for only the areas used. Think of a world that takes up 16k x 16k, but has multiple small islands. Rather than pay for 16k, our system requires only allocates memory for the regions that contain the islands. The space in between can be flat, hidden, or have collision-less shader generated noise. ### Reference Material * Mike J. Savage: [Geometry clipmaps: simple terrain rendering with level of detail](https://mikejsavage.co.uk/blog/geometry-clipmaps.html). Earlier versions of Terrain3D used Mike's implementation. Blog and repository code released under the MIT license, clarified per email communication with Mike. * NVidia GPU Gems 2: [Terrain Rendering Using GPU-Based Geometry Clipmaps](https://developer.nvidia.com/gpugems/gpugems2/part-i-geometric-complexity/chapter-2-terrain-rendering-using-gpu-based-geometry) * GDC 2014: [The Witcher 3 Clipmap Terrain and Texturing](https://archive.org/details/GDC2014Gollent) - [Slides](https://ubm-twvideo01.s3.amazonaws.com/o1/vault/GDC2014/Presentations/Gollent_Marcin_Landscape_Creation_and.pdf) ## Architecture Here is a diagram showing what the classes do and how they communicate. ```{image} images/sa_uml.png :target: ../_images/sa_uml.png ``` ## Architectural Design Principles ### 1 Pixel == 1 Vertex Currently, we maintain a constant ratio where 1 pixel on height, control, and color maps correlates to 1 world vertex, on LOD0. We provide variable region sizes which limit the number of vertices allocated, and `Terrain3D.vertex_spacing` to allow devs to spread out or condense the spacing between vertices for higher and lower poly worlds. However this 1 pixel == 1 vertex principle is maintained. With a vertex scaling of 2.0, a 1024px^2 map represents a 2048m^2 world, using a 1024 region size. ### Global Positions are Absolute Many functions in the API receive a global position. Before `vertex_spacing`, there was an easy translation to regions, vertex positions, and image coordinates. Now that the user can laterally scale the terrain, landscape features like mountain peaks change their global position, this introduced a design challenge. To make this managable, we've adopted the principle that all functions in the API that take a `global_position` parameter expect an absolute global position from the users perspective. That position is generally descaled for operations internally in local or image coordinates. If a function calls other functions, it will need to send the global position. Care will need to be taken by devs to ensure the descaled and global positions are used at the right time. ================================================ FILE: doc/docs/texture_painting.md ================================================ Texturing the Terrain ========================= ## Texture List Once your texture files have been prepared, this document describes how to use them to texture your terrain. ### Adding a Texture Set 1. Once you've [created your textures](texture_prep.md), place them in your Godot project folder. 2. Set the appropriate Import settings for them as defined in [compression formats](texture_prep.md#compression-format). 3. Make a new texture slot in the `Textures` section of the [Asset Dock](user_interface.md#asset-dock) by clicking `Add New`. 4. Drag your texture file for albedo+height from the `FileSystem` panel into the albedo slot. Drag your normal+roughness texture into the normal slot. 5. In the inspector, name the texture and adjust the other settings as needed. ### Managing the Texture List * Unused texture slots take up memory with the default generated textures. Remove unused slots. * Right-click any texture slot in the Asset Dock to bring it into edit mode. * Middle-click any texture slot to clear or delete it. You can only delete the last texture in the list. * Reorder textures by changing the texture id. This will change the texture rendered in the viewport as it does not change the values painted on the control map. In the future we'll add image processing tools that will allow changing texture ids painted on the terrain. ## Texture Painting Textures are painted on to the terrain in three values: a base texture ID, an overlay texture ID, and a blending value. Our shader then determines the best way considering a variety of factors including the height texture from each set, to determine which texture to show. In areas where autoshading has been enabled, the painted textures are ignored. The general idea for texturing the terrain is to enable the autoshader to automatically texture the terrain in most areas, then manually paint textures only where you need it. There are a handful of tools to familiarize yourself with: * The toolbar has `Paint Texture`, `Spray Texture`, and `Autoshader` tools to paint where the terrain is manually or automatically textured. * The material has an option to enable or disable the autoshader for the whole terrain. * The `Debug Views/Autoshader` displays where the terrain is automatically or manually textured. ### Manual Painting Technique Painting with natural mixing is easy to do as long as your texture sets have height textures, they are quality textures, and you use the correct technique as follows. * Use the `Paint Texture` tool to cover large sections with a single texture. This tool sets the blend value to `0.0` and both base and overlay IDs to the selected texture. * Start Painting your terrain in large sections with this tool. * You can and should Paint similar but different textures in an area for a natural variety. e.g. gravel and dirt; mud, dirt, and rocks. Do what blending you can with Paint first. * Use the `Spray Texture` tool to blend the edges of the Paint work, or lightly in the center of the Painted areas to give it a natural look. This gradually increases the weight of the selected texture. See the [keyboard shortcuts](keyboard_shortcuts.md) for options you can do while Spraying. * Example: Use the Paint tool for both a grass field and a dirt pathway. Then use the Spray tool and repeatedly switch between grass and dirt to blend the edges of the path randomly until it looks realistic. * Use the [control texture](../api/class_terrain3dmaterial.rst#class-terrain3dmaterial-property-show-control-texture) and [control blend](../api/class_terrain3dmaterial.rst#class-terrain3dmaterial-property-show-control-blend) debug views to understand how your textures are painted and blended. ### Autoshading New regions are set to enable the autoshader by default. If you started using Terrain3D before the autoshader was added, all of your regions are set to manual shading. To enable it: * In the material, enable the autoshader. * Specify the base and overlay texture IDs the autoshader should use. * Use the `Autoshader` tool and paint over the areas you want to be autoshaded. Enabling the autoshader will not change your manually painted textures. In autoshaded regions, the shader will ignore your manual painting, but it's still there and will be visible any time you disable the autoshader in the material or by painting. ### Mixing Manual Painting & the Autoshader Since the paint brushes will disable the autoshader, it's easy to make artifacts appear without the right process. This technique will allow you to achieve seamless painting using both the autoshader and manual painting. Let's say we want to paint a pathway through grass, surrounded by autoshaded hills: * Find a flat or evenly sloped area with a uniform texture. Avoid corners where the autoshader transitions between textures. In our example, find a flat grassy area. * Use the `Paint Texture` tool to paint the same grass texture the autoshader is using in the flat area. The changes won't be visible unless you disable the autoshader in the material, or enable the autoshader debug view. But you will be simultanously disabling the painted autoshader, and painting the same texture. * Use the same tool to paint a pathway texture. * Use the `Spray Texture` tool to blend the edges. You can see the manual painting technique is the same as above. The key step here is invisibly carving out a manually painted section by laying down a base texture the same as the autoshader. If you go too far out with the manual painting, you can use the `Autoshader` tool to bring it back in. ## Painting Angle & Scale Both the `Paint Base Texture` and `Spray Overlay Texture` tools have Angle and Scale modifiers, which allow painting textures at different angles and scales. This can be useful for creating paths, water embankments, and generally having textures more closely follow terrain features. These tools have pickers allowing you to select current values from the terrain. Angle also has a `Dynamic` mode which causes the angle to change based upon mouse movement while painting. This ignores any value set by the slider. Paint/Spray brushes have toggleable options for Texture, Angle, and Scale, meaning they can be independently applied. This allows you to disable Texture and Scale in order to repaint an area using only Angle modification. This will change texture rotation without affecting the other parameters. You can paint Angle and Scale on top of the autoshader. ## Color Painting In addition to painting textures, you can also paint colors on the terrain. There are two primary uses for the colormap. ### 1. GIS Applications You can import a full image such as a satellite photo, and enable the color map debug view for GIS visualization. ```{image} images/gis.png :target: ../_images/gis.png ``` ### 2. Color Variation You can use the `Paint Color` tool to paint colors on the terrain. This is useful to add variation. Colors are multiplied on to painted textures, which is a blend mode that only darkens. Try painting your terrain with subtle light grays, greens and browns to add depth, contours, and variation. Subtlety is key. Paint white to reset. You can paint with the colormap on top of the autoshader. You can also use the picker to select a colormap value from the terrain. ## Painting Wetness Use the `Paint Wetness` tool to modify the roughness of the textures. Reduce the roughness percentage to say -30% and wherever you paint the textures will become more glossy. If you wish to turn dirt into mud, try painting a light/medium grey on the colormap to darken it, then paint a -30% on the wetness. Paint 0 to reset. You can paint wetness on top of the autoshader. You can also use the picker to select a wetness value from the terrain. ================================================ FILE: doc/docs/texture_prep.md ================================================ Preparing Textures ========================= Terrain3D supports up to 32 texture sets using albedo, height, normal, and roughness textures, each set are channel packed into 2 files. This page describes everything you need to know to prepare your texture files. Continue on to [Texture Painting](texture_painting.md) to learn how to use them. **Table of Contents** * [Texture Requirements](#texture-requirements) * [Texture Content](#texture-content) * [Channel Pack Textures in Terrain3D](#channel-pack-textures-in-terrain3d) * [Channel Pack Textures with Gimp](#channel-pack-textures-with-gimp) * [Where to Get Textures](#where-to-get-textures) * [Frequently Asked Questions](#faq) ## Texture Requirements ### Texture Files Typically "a texture" say rock comes as a pack of individual texture files: albedo/diffuse/base color, height, normal, smoothness/roughness, ambient occlusion (AO). For maximum efficiency we provide the option of packing these 5 separate files into 2. Terrain3D is designed for texture sets that are channel packed as follows: | Name | Format | | - | - | | albedo_texture | RGB: Albedo, A: Height | normal_texture| RGB: Normal map ([OpenGL](#normal-map-format)), RGB: AO, A: Roughness The terrain can work without the height, normal, ao, or roughness maps. But then you won't have height blending, roughness, or the other features. That may be fine for a low-poly or stylized terrain, but not for a realistic one. Textures can be channel packed using the `Pack Textures...` option in the Terrain3D menu at the top of the viewport (recommended), or in [Gimp](https://www.gimp.org/). Photoshop or [Krita](https://krita.org/) are possible, but working with alpha channels can be a bit challenging. ### Texture Sizes All albedo textures must be the same size, and all normal textures must be the same size. Each type gets combined into separate Texture2DArrays, so their sizes of the two arrays can differ. Double click any texture file and the inspector will show you the size. The demo textures are 1024x1024. For GPU efficiency, it is recommended that all of your textures have dimensions that are a power of 2 (128, 256, 512, 1024, 2048, 4096, 8192), but this isn't required. ### Compression Format All albedo textures must be the same format, and all normal textures must be the same format. Albedo and Normals are combined into separate Texture2DArrays, so the two can have different formats. Double-clicking a texture in the FileSystem panel will display it in the Inspector with the current converted format of the file, size, and mipmaps. Settings may be adjustable on the Godot Import tab. | Type | Supports | Format | | - | - | - | | **PNG** | Desktop, Mobile | RGBA, converts to DXT5 or BPTC (HQ). In Godot you must go to the Import tab and select: `Mode: VRAM Compressed`, `Normal Map: Disabled`, `Mipmaps Generate: On`, optionally check `High Quality`, then reimport each file. | **DDS** | Desktop | BC3 / DXT5, linear (intel plugin), Color + alpha, mipmaps generated. These files are used directly by Godot and are not converted, so there are no import settings.| | **Others** | | Other [Godot supported formats](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html#supported-image-formats) like KTX, TGA, JPG, WEBP should work as long as you match similar settings to PNG. | **EXR** | | While EXRs can work, they store color data as 16/32-bit float, not 8-bit integer. Don't use them for terrain textures unless you know what you're doing. To get the highest quality compression on desktop, use either: * Use PNG with the high quality option in Godot (BC6/BPTC). Pack with our channnel packer and mark HQ. Godot does not currently support importing anything higher than BC3/DXT5 in DDS files. * DDS (BC3/DXT5) made in Gimp are recommended over using the default PNG settings, which produces poor quality BC3/DXT5 files. When creating DDS files in Gimp you have a lot more conversion options, such as different mipmaps filtering algorithms which can be helpful to remove artifacts in reflections (eg try Mitchell). The demo textures are PNG imported as HQ which are converted to BPTC. You can create DDS files by: * Exporting directly from Gimp * Exporting from Photoshop with [Intel's DDS plugin](https://www.intel.com/content/www/us/en/developer/articles/tool/intel-texture-works-plugin.html) * Converting RGBA PNGs using [NVidia's Texture Tools](https://developer.nvidia.com/nvidia-texture-tools-exporter) You can create KTX files with [Khronos' KTX tools](https://github.com/KhronosGroup/KTX-Software/releases). ## Texture Content ### Seamless Textures Make sure you have seamless textures that can be repeated without an obvious seam. ### Height Textures If creating your own height textures, aim for a central point of grey (0.5) with bumps and divots above and below that value. Adjust the contrast so that troughs and peaks reach just about 0 and 1. ### Normal Map Format Normal maps come in two formats: DirectX with -Y, and OpenGL with +Y. Both formats should be normalized. DirectX can be converted to OpenGL and vice versa by inverting the green channel in a photo editing app, or within our texture packing tool. They can often be identified visually by whether bumps appear to stick out (OpenGL) or appear pushed in (DirectX). The sphere and pyramid on the left in the image below are the clearest examples. ```{image} images/tex_normalmap.png :target: ../_images/tex_normalmap.png ``` Natural textures like rock or grass can be very difficult to tell. However if you get assets made for a certain engine like Unreal or Unity, you can generally assume their format and convert as needed. On occasion artists get it wrong though, so if the lighting looks off on your object, try inverting the normal map. | Software | DirectX | OpenGL | |----------|---------|--------| | 3DS Max | ✓ | | | Blender | | ✓ | | Cinema 4D | | ✓ | | CryEngine | ✓ | | | Godot Engine | | ✓ | | Houdini | | ✓ | | Marmoset Toolbag | | ✓ | | Maya | | ✓ | | Substance Painter | ✓ | | | Unity | | ✓ | | Unreal Engine | ✓ | | | Zbrush | | ✓ | ### Roughness vs Smoothness Some "roughness" textures are actually smoothness or gloss textures. You can convert between them by inverting the image in an image editor, or in our texture packing tool. You can tell which is which just by looking at distinctive textures and thinking about the material. If it's glass it should be glossy, so on a roughness texture values will be near 0 and the texture will appear mostly black. If it's dry rock or dirt, it should be mostly white, which is near 1 roughness. A smoothness texture would show the opposite. ### Ambient Occlusion Our built in texture packing tool allows you to easily combine AO texture maps into your normal texture set. This is done by a clever technique of expecting your normal map is normalized, then scaling the vector by the AO value. AO maps are not required for any texture. You can even mix and match on different textures, unlike with sizes or formats. If you haven't included an AO texture, AO will be approximated from the normal map. If you wish to apply AO to your textures manually or in another tool, ensure your normal map is normalized, then pack AO with: `unpacked_normal_vector * (sqrt(ao) * 0.5 + 0.5)`. ## Channel Pack Textures in Terrain3D We recommend you use our built in tool to pack textures. ```{image} images/terrain3d_menu.png :target: ../_images/terrain3d_menu.png ``` 1. At the top of your viewport, click the `Terrain3D` menu, then `Pack Textures`. 2. Select your textures for albedo and height. 3. Optionally, select textures for normal, roughness, and if desired ambient occlusion 4. Optionally, convert a DirectX normal map to OpenGL, or smoothness to roughness map. 5. Optionally, enable Orthogonalise normals if you see a reflective checkerboard pattern appear when using detiling. 6. Click `Pack Textures As...` and save the resulting PNG files to disk. 7. Go to the Import tab and one at a time, select your new PNG files, specify the following settings and click `reimport`. * `Mode: VRAM Compressed` * Optional: `High Quality: On` if you wish BPTC instead of DXT5. * `Normal Map: Disabled` * `Mipmaps Generate: On` Make sure to reimport both files. Double click each file in the filesystem and ensure the inspector reveals the expected format and size. All of the files you put in your texture list must match. ## Channel Pack Textures with Gimp > Note: AO packing normals manually is complex and not reccomended. The formula used is: `unpacked_normal_vector * (sqrt(ao) * 0.5 + 0.5)` 1. Open your RGB Albedo and greyscale Height files (or Normal and Roughness). 2. On the RGB file select `Colors/Components/Decompose`. Select `RGB`. Keep `Decompose to layers` checked. On the resulting image you have three greyscale layers for RGB. 3. Copy the greyscale Height (or Roughness) file and paste it as a new layer into this decomposed file. Name the new layer `alpha`. This would be a good time to invert the green channel if you need to convert a Normalmap from DirectX to OpenGL, or to invert the alpha channel if you need to convert a smoothness texture to a roughness texture. 4. Select `Colors/Components/Compose`. Select `RGBA` and ensure each named layer connects to the correct channel. 5. Now export the file with the following settings. DDS is highly recommended. Also recommended is to export directly into your Godot project folder. Then drag the files from the FileSystem panel into the appropriate texture slots. With this setup, you can make adjustments in Gimp and export again, and Godot will automatically update with any file changes. ### Exporting As DDS * Change `Compression` to `BC3 / DXT5` * `Mipmaps` to `Generate Mipmaps`. * Optionally, change the `Mipmap Options` `Filter`, such as to Mitchell if you get reflection artifacts in your normal+roughness texture. * Insert into Godot and you're done. ```{image} images/io_gimp_dds_export.png :target: ../_images/io_gimp_dds_export.png ``` ### Exporting As PNG * Change `automatic pixel format` to `8bpc RGBA`. * In Godot you must go to the Import tab and select: `Mode: VRAM Compressed`, `Normal Map: Disabled`, `Mipmaps Generate: On`, then click `Reimport`. ```{image} images/io_gimp_png_export.png :target: ../_images/io_gimp_png_export.png ``` ## Where to Get Textures ### Texture Creation Software You can make textures in dedicated texture software, such as those below. There are many other tools and ai texture generators to be found online. You can also paint textures in applications like krita/gimp/photoshop. * [Materialize](http://boundingboxsoftware.com/materialize/) - Great free tool for generating missing maps. e.g. you only have an albedo texture that you love and want to generate a normal and height map * [Material Maker](https://www.materialmaker.org/) - Free, open source material maker made in Godot * [ArmorLab](https://armorpaint.org/) - Free & open source texture creator * Substance Designer - Commercial, "industry standard" texture maker ### Download Textures There are numerous websites where you can download high quality, royalty free textures for free or pay. These textures come as individual maps, with the expectation that you will download only the maps you need and then channel pack them. Here are just a few: * [PolyHaven](https://polyhaven.com/textures) - many free textures (Download PNG, not EXR) * [AmbientCG](https://ambientcg.com/) - many free textures * [Poliigon](https://www.poliigon.com/textures/free) - free and commercial * [GameTextures](https://gametextures.com/) - commercial * [Textures](https://www.textures.com/) - commercial ## FAQ ### Why do we have to channel pack textures? Why is this so difficult? You don't have to. You can use just the albedo map, or also the normal map, without the others. However if you want a realistic terrain with height blending and roughness, you need all of the maps. You could have 5 different texture maps in memory, or pack that down to 2 maps and save precious VRAM. Channel packing is a very common task done by professional game developers. Every pro asset pack you've used has channel packed textures. When you download texture packs from websites, they provide individual textures so you can pack them how you want. They are not intended to be used individually! We offer a built in `Pack Textures` tool, found in the Terrain3D menu at the top of the viewport that facilitates the texture creation process within Godot. Packing can be done in 30 seconds. Finally, we provide easy, 5-step instructions for packing textures with Gimp, which takes less than 2 minutes once you're familiar with the process. If we want high performance games, we need to optimize our games for graphics hardware. A shader can retrieve four channels RGBA from a texture at once. Albedo and normal textures only have RGB. Thus, reading Alpha is free, and a waste if not used. So, we put height / roughness in the Alpha channel. We could have the software let you specify individual maps and we pack textures for you at startup, however that would mean processing up to 160 images every time any scene with Terrain3D loads, both in the editor and running games. Exported games may not even work since Godot's image compression libraries only exist in the editor. The most reasonable path is for gamedevs to learn a simple process that they'll use for their entire career and use it to set up terrain textures one time. ### What about Emissive, Metal, and other texture maps? Most terrain textures like grass, rock, and dirt do not need these. Occasional textures do need additional texture maps. Lava rock might need emissive, or rock with gold veins might need metallic, or some unique texture might need both. These are most likely only 1-2 textures out of the possible 32, so setting up these additional options for all textures is a waste of memory. You can add a [custom shader](tips_technical.md#add-a-custom-texture-map) to add the individual texture map. ### Why not use Standard Godot materials? All materials in Godot are just shaders. The standard shader is both overly complex, and inadequate for our needs. Dirt does not need SSS, refraction, or backlighting for instance. See [a more thorough explanation](https://github.com/TokisanGames/Terrain3D/issues/199). ### What about displacement? Godot doesn't support texture displacement via tessellation or geometry shaders in the renderer. However, we provide the option of subdividing the terrain mesh, to allow textures to displace verteices. For further details see [Displacement](displacement.md). Effects like depth parallax or occlusion mapping etc require many samples in fragment, which can be prohibitivley expensive when applied to already complex terrain shaders. There are [alternatives](https://github.com/TokisanGames/Terrain3D/issues/175) that might prove useful in the future. ### What about... We provide a base texture with the most commonly needed terrain options. Then we provide the option for a custom shader so you can explore `what about` on your own. Any of the options in the Godot StandardMaterial can be converted to a shader, and then you can insert that code into a custom shader. You could experiment with Godot's standard depth parallax technique, or any of the alternatives above. Or anything else you can imagine, like a sinewave that ripples the vertices outward for a VFX ground ripple effect, or ripples on a puddle ground texture. ================================================ FILE: doc/docs/tips_environment.md ================================================ Environment Tips ============================ **Table of Contents** * [Introduction](#introduction) * [Lighting](#lighting) * [Foliage Assets](#foliage-assets) * [Foliage Material](#foliage-material) ## Introduction No matter how good of assets you have, your landscape will not look good without a proper rendering environment. The Godot renderer has greatly improved its lighting over previous generations, but the default material shader is not good at rendering foliage. Terrain3D provides the ground mesh with a sophisticated shader, an instancer, and a particle shader. But sourcing, using, and setting up the textures, foliage assets, materials, lighting, and environment settings are your responsibility. Your choices and artistic technique will dramatically affect what your world looks like. Prepare to spend countless hours tweaking your materials, lighting, and environment settings. To get an attractive environment you need the following, **in order of priority**: 1. White balanced and properly exposed lighting. Use pure white light. Don't use colored light unless you really know what you're doing. 2. Quality [ground textures with heights](texture_prep.md), and [good technique in painting](texture_painting.md#texture-painting). 3. Quality foliage mesh and texture assets. 4. A good foliage shader (below). The default is inadequate. 5. Suitable `WorldEnvironment` settings to balance and polish your look. ## Lighting Lighting is the most important. To be able to adjust your world accurately, you need to ensure the photons hitting your eyes are accurate. If your view of the world is off, your interpretation of it will be off. ### Room Lighting There is a reason every major feature film finishes off their post production with a professional colorist. This person or team has spend tens of thousands of dollars to create a viewing studio with calibrated equipment in order to be able to work in the environment of the ideal movie theater. They can then fix issues **they might not have been able to see** in other environments, such as over or under exposure, inaccurate colors, crushed blacks, blown out highlights, flickering, etc. The goal is not to have your game look good on all monitors. That's an impossible task. What a movie studio aims for is the best possible experience on reference hardware in the ideal environment. They want to meet industry standard specifications defined by THX, Dolby, etc. Then they rely on equipment manufacturers and those building professional and home theaters to also build for those specifications. The closer the end user gets to those standards, the more accurate their experience of the product will be. You don't have to match this level of expense or effort. But you should look at your office and monitors, and fix the worst issues so that you can work in an environment closer to how gamers will consume your product. It's going to be very difficult to produce an attractive game if your screen looks like the left: ```{image} images/monitor_calibration.jpg :target: images/monitor_calibration.jpg ``` **Tips:** * Ensure you don't have light shining on your screen. If you have a glossy screen, make sure there are no reflections on it. * If you have colored lights around your office or computer, turn them off. Turn off keyboard and case LEDs. * Adjust your room lighting for neutrality: * The color of room light should be neutral - close to overcast outdoor lighting. Your monitor is most likely slightly blue like outdoor light, incandescent lights are orange, cool white bulbs are too blue. * The overall brightness of the room should be slightly dim, but not dark. You want your monitor at near full brightness so it can render colors as designed, without competing with other lights or windows. But you don't want the room so dark the bright screen blows out your eyes. * Before buying a screen or laptop: * Make sure it's a matte screen. Never buy glossy screens for graphics work as you'll always be fighting reflections. * Look at it in person. Open a web browser at the store, load up an monitor calibration image, and try to adjust the screen in the store to ensure you can get an accurate result at home. * Look up the monitor on a review website that focuses on screen testing for color and brightness uniformity and rendering. * Lenovo laptop monitors have been very good in recent years (through 2024 at least). * Calibrate your monitor. ### Monitor Calibration The easy way is to buy a device to calibrate your monitor like something from Calibrite or Datacolor. However you can do it manually, and with practice you'll get better at it and train your eye. * Find your screen controls built into your monitor and adjust those first with the tests below. Even laptop screens have brightness controls, which should probably be at or near max. This is the first pass. * Repeat the adjustments with the color calibration software built in to your OS. Windows has `Display Color Calibration` control panel with a wizard. * Work through the pages in [LCD Test](http://www.lagom.nl/lcd-test/) as you adjust the physical, and then later, the software controls. * View a `monitor calibration image` (web search) as you adjust the controls, such as [this one](https://webtransformer.com/calibrate/). * When adjusting, think about what you're looking at on the image; the fruit, skin color, etc. Ask yourself, "Is that what it's supposed to look like?" "Is this what it would look like if it were sitting on my desk in front of me?" Look away, out the window, then look back at it and ask yourself again. Adjust the controls. Make it too saturated. Then make it desaturated. Then find the right spot. Do that with all of the controls. Too bright, too dark, just right. * Color is the hardest. You must find neutral white, which is comprised of equal parts of red, blue, and green as your screen displays them, not what is fed from your video card. Look at white on the reference image or the background of a web browser. Is it too blue? Probably. Most factory shipped monitors and TVs are. Is it too red? Is it too green? Adjust, look away, look, adjust. Make it too red, too blue, too green, just right. * Over many years of color and photography work, you will stop accepting what your brain interprets you're seeing (a white wall) and will perceive the raw input from your eyes (a slightly yellow-grey wall, dark grey in the shadows, a subtle orange tint around the incandescent light, and a soft blue tint from the window). ### Game Environment In your virtual world, the most important aspects are your sky shader, directional light, and camera. The sky shader illuminates the world with reflective light, giving your world a base exposure. The directional light gives your world three-dimensional form through light and shadow. The camera is the eye that perceives the virtual light rays. We won't discuss Global Illumination here. There aren't any good world GI solutions in Godot currently. Even if you do use it, you need to setup your base lighting properly first. Also see [Faking GI](https://docs.godotengine.org/en/stable/tutorials/3d/global_illumination/faking_global_illumination.html). You should familiarize yourself with the Godot docs on [WorldEnvironment](https://docs.godotengine.org/en/stable/classes/class_worldenvironment.html), [Environment](https://docs.godotengine.org/en/stable/classes/class_environment.html), [DirectionalLight3D](https://docs.godotengine.org/en/stable/classes/class_directionallight3d.html), [Camera3D](https://docs.godotengine.org/en/stable/classes/class_camera3d.html), and [CameraAttributes](https://docs.godotengine.org/en/stable/classes/class_cameraattributes.html) and the Practical and Physical child classes. There are many options that need to be tweaked for a good looking setup, which you can learn over time. For now though start with the sky. The built in procedural skies in Godot 4 are sub par. Use either an HDRI, a custom sky shader, or Sky3D. #### HDRIs High Dynamic Range Image. Use 32-bit HDR or EXR images as these have full dynamic range, which will give the highest quality reflective light. A web search will turn up many websites with paid and free options. Here are some sites with free HDRIs: * [AllSkyFree](https://github.com/rpgwhitelock/AllSkyFree_Godot) - 8-bit PNGs, which may be fine for some projects, but they are attractive, game-centric and free; with paid options. * [Polyhaven](https://polyhaven.com/hdris) * [HDRI-Skies](https://hdri-skies.com/free-hdris/) * [HDRMaps](https://hdrmaps.com/freebies/) Once you have the file, resize it according to how much VRAM you want to consume, and how high of quality you want. 2k-4k is most likely all you need. Don't use it at 16k. Place it in `WorldEnvironment/Environment/Sky/PanoramaSkyMaterial/Panorama`. See [PanoramaSkyMaterial](https://docs.godotengine.org/en/stable/classes/class_panoramaskymaterial.html). #### Sky Shader You can write your own sky material or look online for already written sky shaders. Place your `ShaderMaterial` in `WorldEnvironment/Environment/Sky`. For a shader developer there's no limit to what you can do. You could have clouds, stars, multiple suns, multiple moons. All of these features can provide reflective light that you'll see in reflections and psuedo bounce lighting. #### Sky3D We use use, maintain, and recommend [Sky3D](https://github.com/TokisanGames/Sky3D), which is a feature rich sky shader. It provides: * Consolidated lighting & environmental controls * A day/night cycle * A game time manager Sky3D renders a sun, moon, clouds, and fog. It's compatible with Godot's basic and volumetric fogs, plus has its own screen space fog. We've consolidated all of Godot's various lighting and environmental controls into one panel, and provide a great number of customizable options. **Tips:** * The Sky3D fog uses a `render_priority` of 100. This is relevant here because you want your foliage to render lower than this. However if you have text on screen with a Label3D, you want it to have a higher priority so it renders on top. ------------ ## Foliage Assets There are lots of ways to get foliage assets. You need to look around, acquire, and import into Godot, which is all out of scope for this document. **Options:** * Download countless available assets from other engine Asset stores. Almost all assets you have a license for can be legally exported for use and distribution in your Godot project. Check the license for your asset and considering how you want to use it. * FAB / Unreal Engine * Unity Engine * Trees can be made with [Treeit](https://www.evolved-software.com/treeit/treeit). * Make assets yourself in Blender. ------------ ## Foliage Material The default material in Godot is sub par for foliage. Here are some tips to improve rendering. However **[setup your lighting first!](#lighting)** You cannot properly adjust your materials if you aren't seeing objective "reality". Many of the options below can only be done in a custom shader. But you can setup a StandardMaterial, place it in a material slot, then right-click and choose `Convert To ShaderMaterial`. This will turn the existing configuration into a shader you can edit. The shader snippets below are incomplete. Where you see `void fragment() {` or `vertex()` it is assumed you will add the subsequent lines into that function; typically at the end. Study some shader tutorials to learn what you're doing if it's not working right. ### Texture Cards Foliage is made up of texture cards: 2D textures UV mapped onto a flat polygon. This is also how hair works. Many games place a grass texture on a QuadMesh or PlaneMesh, often using 2 or 3 cards in a cross or asterisk shape, like the default Terrain3DMeshAsset. With 3D foliage, the leaves are 2D textures UV mapped onto flat polygon cards distributed around 3D polygon branches. For all of these cards, only one side of each polygon is the front face, the other is the back. By default, the back face is culled. * If you want to see the back face, change `cull_mode` to disabled. In the shader, change the render mode at the top from `cull_back` to: ```glsl render_mode cull_disabled; ``` * The above will make the back face dark in certain lighting conditions. Enable `Back Lighting` to allow light from the front side to shine through. In the shader: ```glsl uniform vec3 backlight : source_color = vec3(.5, .5, .5); void fragment() { BACKLIGHT = backlight.rgb; } ``` ### Normals A normal is the perpendular vector of any given point of an object. This helps the renderer figure out the appropriate direction to bounce a light ray. Each face has a normal. Each vertex has a normal. Each pixel of the normal map texture has a normal. #### Face Normals You've likely read about "recalculating your normals in Blender". That typically means recalculating the vector perpendicular to each **front face**. That needs to be done for proper rendering of a mesh or light reflects off of it will look weird. Typically once calculated in Blender, this doesn't need to be accessed in the shader. However, you can reverse the normals of back faces so that all faces will render like they are front faces. This is good for your foliage leaves made of cards. We want to see above and below leaves without the lighting reversed because of the face normal. Note NORMAL, not NORMAL_MAP. ```glsl void fragment() { NORMAL = !FRONT_FACING ? -NORMAL : NORMAL; } ``` #### Vertex Normals Like faces, each vertex also has a normal. If they are messed up, you might see odd lighting in the corners of faces instead of on the whole mesh. These can also be reset in Blender. Vertex normals have a unique caveat when rendering foliage like thick tree canopies and bushes. First, let's look at real life. Notice how the canopies are made up of many individual elements, yet the overall lighting is very consistent with light on top and darkness on bottom. ```{image} images/vertex_normals_photo.jpg :target: ../_images/vertex_normals_photo.jpg ``` Unfortunately, if you render foliage in the same way as other 3D objects, some meshes will be lit inconsistently. Observe this Poplar tree below with the default lighting on the left compared to the above real trees and a more desirable rendering. ```{image} images/vertex_normals_3d.jpg :target: ../_images/vertex_normals_3d.jpg ``` To achieve the effect on the right, edit your mesh in Blender or another tool and reorient the vertex normals of the canopy as if they are extending outward from the origin of the mesh like an expanding sphere or dome. You will have to learn how your DCC can transfer vertex normals from one object to another, as a Blender tutorial is out of scope for this documentation. But you can learn more about the concept and applying it in DCCs here: * [https://ericchadwick.com/img/tree_shading_examples.html](https://ericchadwick.com/img/tree_shading_examples.html) * [http://wiki.polycount.com/wiki/VertexNormal#Transfer_Attributes](http://wiki.polycount.com/wiki/VertexNormal#Transfer_Attributes) You can achieve a similar affect in the shader by calculating vertex normals as they extend outward from the origin of the mesh. You can apply this to the shader on your leaves. This is how the above image on the right was created. ```glsl void vertex() { NORMAL = normalize(VERTEX); // Recalculate vertex normals from model origin TANGENT = normalize(cross(NORMAL, vec3(0., 0., 1.))); BINORMAL = normalize(cross(NORMAL, TANGENT)); } ``` #### Texture Normals As the renderer displays each pixel of a mesh on screen, it uses the texture normal map to determine how light will bounce off of the material. Normal maps are either in a format for DirectX systems, others are in OpenGL format. You need to be able to convert between them. Sometimes the map you got is mislabeled, and needs to be corrected. You can convert between OpenGL and DirectX by inverting the green channel either in Photoshop, in the Godot import settings, or with the shader code below. The latter is useful to be able to quickly compare how the light reflects off of the leaves. Unlike with rocks, it's often much harder to visually tell them apart on foliage. Make sure you're looking at the front face of the texture card (see above). Add the line referencing `NORMAL_MAP.g` after your NORMAL_MAP texture lookup, then you can toggle the uniform to switch. ```glsl uniform bool directx_normals = false; void fragment() { NORMAL_MAP = texture(texture_normal, base_uv).rgb; NORMAL_MAP.g += (1.0 - 2.0 * NORMAL_MAP.g) * float(directx_normals); // Invert Green if DirectX } ``` ### Thin Trees The Godot renderer will quickly discard pixels of thin foliage as the camera moves away. To combat this, prefer broad leaf foliage over thin leaves like pine needles. Of course this isn't ideal for all environments. These shader snippets will also help. For a deeper explanation, this is partially caused by mipmap averaged alpha values. As the mip level increases, the alpha can decrease due to more and more 0 alpha pixels being included. Say 51% of a leaf texture has alpha 0, with a clip threshold of 0.5. Mip0 will look as desired, but as the mip level increases the edges get blended under the clip threshold. This makes the foliage textures shrink aggressively. Disabling mipmaps for alpha clip textures will stop this happening, but results garbage noisy pixels. A lot of games use a MAX comparison when generating alpha clip texture mipmaps to help with this, though Godot doesn't currently have this option. We compensate for this in the snippets below. * This code makes trees maintain volume at a distance. You can change `log` to `log2` or even remove it for greater thickness. This boosts the alpha value closer to 1.0 as the distance increases, which counteracts the issue with mipmap averaging against 0 alpha. ```glsl uniform float alpha_scissor_threshold : hint_range(0,1) = 0.5; uniform float alpha_antialiasing_edge : hint_range(0,1) = 0.3; void fragment() { ALPHA = clamp(albedo_tex.a * max(log(-VERTEX.z), 1.0), 0.0, 1.0); ALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold; ALPHA_ANTIALIASING_EDGE = alpha_antialiasing_edge; } ``` * Alpha-to-coverage adjusts anti aliasing, which further helps foliage maintain density at a distance. Set the value to the size of your albedo texture, though there doesn't seem to be an issue if hard coded larger. e.g. 16384. ```glsl render_mode alpha_to_coverage; void fragment() { ALPHA_TEXTURE_COORDINATE = UV * vec2(albedo_texture_size); } ``` ### Artifacts * If your mesh has weird colors or darkness that shouldn't be there. Disable `Vertex Color / Use as albedo`. In the shader, look for and remove `COLOR`, e.g. `ALBEDO = albedo_tex.rgb * COLOR.rgb`. If that wasn't the cause, the darkness might be due to normals. Read [Normals](#normals) above. * If your leaves and branches are in reverse order, don't use `Transparency/Alpha`. Use only `Depth Pre-Pass`, or `Alpha Scissor`. The former initially appears to look better, but it puts the material in the slower transparency pipeline, and still has rendering and Z-order issues. We recommend using only `Alpha Scissors`. Also enable `Alpha Antialiasing Mode`. Then adjust `alpha_scissor_threshold` and `alpha_antialiasing_edge` to suit. Read [Thin Trees](#thin-trees) above. * Specular and Roughness for reflective light are appropriate up close, but they can produce ugly artifacts when far away. If one or more of your plants are too shiny at a distance, fade out specular. ```glsl uniform float specular_distance_fade : hint_range(0,1000,.1) = 15.; void fragment() { vec3 camera_pos = INV_VIEW_MATRIX[3].xyz; vec3 pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz; SPECULAR = mix(specular, 0., clamp(length(pixel_pos - camera_pos) / specular_distance_fade, 0., 1.)); } ``` * Some foliage renders with a white edge even though it's not visible on the texture. * First, ensure all of your texture samplers have the repeat_disable flag: ```glsl uniform sampler2D texture_albedo : source_color, repeat_disable; ``` * Next, confirm if the issue is caused by the roughness or specular channels, and adjust as above. * Finally, if you're sure the issue is in ALBEDO, place this line *after* assigning the texture to ALPHA, and it will allow you to clip off white pixels. ```glsl uniform float alpha_white_filter : hint_range(0,1) = 0.0; void fragment() { ALPHA *= step(alpha_white_filter, 1.0 - max(max(ALBEDO.r, ALBEDO.g), ALBEDO.b)); } ``` * Usually you want to keep your values appropriately clamped or normalized or you will get rendering artifacts. E.g. `ROUGHNESS = clamp(roughness, 0., 1.); NORMAL_MAP = normalize(normal);`. Other times, it's useful to use values higher than the typical range. E.g. `ALBEDO` and `BACKLIGHT`. Don't be afraid to experiment to find appropriate value ranges. But test extreme values on multiple cards and drivers before shipping your game or they might give you more artifacts. ### Conserve VRAM Save as much VRAM as you can. The less data transfer across the bus, the faster your game will be. The smaller your texture files, the less data transfer. The more efficient your shader and fewer texture lookups, the less traffic on the bus. * A lot of foliage leaves don't need metal/roughness/AO maps. If you get an asset with these maps, ensure they make a difference before using them. You can adjust roughness and specular without a map. Using only albedo and normal maps are often adequate for foliage. * Material textures don't have to be the same resolution. You could have an 8k albedo texture that covers a lot of plants, and find that a 2k or 4k normal map is sufficient. Zoom your camera as close to the plant leaves as your player will be. If you can't tell a difference between a 2k, 4k, or 8k normal map or roughness texture, then use the smallest that makes a difference. ================================================ FILE: doc/docs/tips_technical.md ================================================ Technical Tips ==================== ## Are Certain Features Supported? This list are for items that don't already have dedicated pages in the documentation. | Feature | Status | | ------------- | ------------- | | Destructibility | Real-time modification is possible by changing the data and updating the maps and collision. You can sculpt heights, change textures, or make holes. If you want tunnels or caves though you need to add your own meshes or use [Zylann's Voxel Terrain](https://github.com/Zylann/godot_voxel). | GPU Sculpting| [Pending](https://github.com/TokisanGames/Terrain3D/issues/174). Currently painting occurs on the CPU in C++. It's reasonably fast, but we have a soft limit of 200 on the brush size, as larger sizes lag. | Holes | Holes work for both visual and collision. | Jolt | [Godot-Jolt](https://github.com/godot-jolt/godot-jolt) was merged into Godot. Terrain3D works with both Godot and Jolt physics. Collision is generated where regions are defined. | Non-destructive layers | Used for things like river beds, roads or paths that follow a curve and tweak the terrain. It's [possible](https://github.com/TokisanGames/Terrain3D/issues/129) in the future. | Object placement | The [instancer](instancer.md) supports placing foliage. Placing objects that shouldn't be in a MultiMeshInstance node is [out of scope](https://github.com/TokisanGames/Terrain3D/issues/47). See 3rd party tools below. | Streaming | There is no streaming built in to Godot. Region Streaming is [in progress](https://github.com/TokisanGames/Terrain3D/pull/675). | Roads | Look at [Godot Road Generator](https://github.com/TheDuckCow/godot-road-generator/). | Water | Use [WaterWays](https://github.com/Arnklit/Waterways) for rivers, or [Realistic Water Shader](https://github.com/godot-extended-libraries/godot-realistic-water/) or a variety of other water shaders available online for lakes or oceans. |**Rendering**| | Frustum Culling | The terrain is made up of several meshes, so half can be culled if the camera is near the ground. | SDFGI | Works fine. | VoxelGI | Works fine. | Lightmaps | Not possible. There is no static mesh, nor UV2 channel to bake lightmaps on to. | **3rd Party Tools** | | [Scatter](https://github.com/HungryProton/scatter) | For placing MeshInstance3D objects algorithmically, with or without collision. We provide [a script](https://github.com/TokisanGames/Terrain3D/blob/main/project/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd) that allows Scatter to detect our terrain. Or you can change collision mode to `Full / Editor` and use the default `Project on Colliders`. Don't use it for MultiMeshInstances, use our built-in instancer. | [AssetPlacer](https://cookiebadger.itch.io/assetplacer) | A level design tool for placing MeshInstance3D assets manually. Works on Terrain3D with placement mode set to Terrain3D or using the default mode and collision mode set to `Full / Editor`. ## Regions Outside of regions, there is no collision. Raycasts won't hit anything. Querying terrain heights or other data will result in NANs or INF. Look through the API for specific return values. See [Collision] for more. You can determine if a given location is within a region by using `Terrain3DData.has_regionp(global_position)`. It will return -1 if the XZ location is not within a region. Y is ignored. ## Terrain3DObjects Just as the instancer keeps foliage stuck to the ground when sculpting, we provide a special node that does the same for regular MeshInstance3D objects. Objects that are children of this node will maintain the same vertical offset relative to the terrain as they are moved laterally or as the terrain is sculpted. For example you can place a sphere on the ground, move it laterally where a hill exists, and it will snap up to the new ground height. Or you can lower the ground and the sphere will drop with the changes. You can then adjust the vertical position of the sphere so it is half embedded in the ground, then repeat either of the above and the sphere will snap with the same vertical offset, half embedded in the ground. To use it: * Create a new node of type Terrain3DObjects * Add your MeshInstance3D objects as children of this node ## Performance * The Terrain3DMaterial shader has some advanced features that look nice but consume some performance. You can get better performance by disabling them: * Set `WorldBackground` to `Flat` or `None` * Disable `Auto Shader` * Disable `Dual Scaling` * `WorldBackground` as `Noise` exposes additional shader settings, such as octaves and LOD. You can adjust these settings for performance. However this world generating noise is expensive. Consider not using it at all in a commercial game, and instead obscure your background with meshes, or use an HDR skybox with mountains built in. * Reduce the size of the mesh and levels of detail by reducing `Terrain Mesh/Size` or `Terrain Mesh/Lods` in the `Terrain3D` node. * Reduce `Terrain Mesh/Tessellation Level` or set to 0 (default) to completely disable texture displacement. * Increase `Terrain Mesh/Vertex Spacing`, which increases the lateral scale and gives you a more low-poly terrain. Preferrably do this before you sculpt, but if done after, you can export the heightmap and manipulate it in Photoshop to rescale it. * Don't use `Renderer/Cull Margin`. It should only be needed if using the noise background. Otherwise the AABB should be correctly calculated via editing, so there is no need to expand the cull margin. Keeping it enabled can cost more processing time. * Experiment with `Renderer/free_editor_textures`, which is enabled by default. It saves VRAM by removing the initial textures used to generate the texture arrays. * For cases where performance is paramount, an example `lightweight` shader is provided in `extras/shaders`. This shader is designed to do the minimum possible amount of texture lookups, while still providing basic texturing, including height blending. Normals are also fully calculated in `vertex()`. This shader removes advanced features like projection, detiling, and paintable rotation and scale for significant performance gains on low-end hardware, mobile, and VR applications. Or you can use the `minimum` shader and craft your own texturing and coloring without any extra features. ## Shaders ### Minimal Shaders This terrain is driven by the GPU, and controlled by our shader. We provide a minimal shader that has only the code needed to shape the terrain mesh without any texturing that you can use as a base to build your own. There's also versions that use the color map, and have a low-poly look with flat normals. Find them all in `extras/shaders/minimum.gdshader`. Load this shader into the override shader slot and enable it. It includes no texturing so you can create your own. ### Low-poly & PS1 Styles Older style asthetics has a few different looks: **PS1 style** often has blocky textures, which can be achieved by: * Use low res textures * In the Terrain3DTextureAsset, decreasing UV Scale * In the material, change Texture Filtering to Nearest. If you make your own shader, make sure to change your samplers to use Nearest filtering instead of Linear. **Low-poly Style** often has large, flat shaded polygons. To get the best results: * Increase `vertex_spacing` to a large value like 10 * Enable `Flat Terrain Normals` in the material settings. ### Day/Night cycles & light under the terrain If you have a day/night cycle and the sun sets below the horizon, it might shine through the terrain. There are a few options you can try: * Check `Material / Shader Override Enabled` and change the second line of the generated shader to `cull_disabled`. The terrain shader is set to `cull_back` by default, meaning back faces are neither rendered, nor do they block light. This will change that, though it does cost some performance. * You can also try changing `Rendering / cast_shadows` to double sided. * Turn off your light when it sinks below the horizon. Changing the number of visible DirectionalLight3Ds will likely cause a shader recompile in the engine as it's a different lighting configuration. Instead you can tween light energy to 0, or you could turn off the sun light and turn on a moon light as long as it is done simultaneously in the same frame. ### Accessing private shader variables Variables in the shader prefaced with `_` are considered private and not saved to disk. They can be accessed externally via the Rendering Server: ```gdscript RenderingServer.material_get_param(terrain.get_material().get_material_rid(), "_background_mode") ``` ### Using the generated height map in other shaders Here we get the resource ID of a material on a mesh. We assign the RID of the generated heightmap to texture slot in that material. ```gdscript var mat: RID = $MeshInstance3D.mesh.surface_get_material(0).get_rid() RenderingServer.material_set_param(mat, "texture_albedo", get_data().get_height_maps_rid()) ``` This is a quick demonstration that shows results. However the generated texture arrays should be accessed with sampler2DArray in a shader, not the regular sampler which is what will will happen here. This also works with the control and color maps. ### Add a custom texture map Here's an example of using a custom texture map for one texture, such as adding an emissive texture for lava. Add in this code and add an emissive texture, then adjust the emissive ID to match the lava texture, and adjust the strength. Add these uniforms at the top of the file with the other uniforms: ```glsl uniform int emissive_id : hint_range(0, 31) = 0; uniform float emissive_strength = 1.0; uniform sampler2D emissive_tex : source_color, filter_linear_mipmap_anisotropic, repeat_enable; ``` Add a variable to store emissive value in the Material struct. ```glsl struct material { ... vec3 emissive; }; ``` Modify `accumulate_material()` to read the emissive texture with the next several options. Add the initial value for emissive by adding a vec3 at the end: ```glsl // Struct to accumulate all texture data. material mat = material(vec4(0.0), vec4(0.0), 0., 0., 0., vec3(0.)); ``` Near the bottom of `accumulate_material()`: ```glsl mat.normal_rough += nrm * id_weight; mat.normal_map_depth += _texture_normal_depth_array[id] * id_weight; mat.ao_strength += _texture_ao_strength_array[id] * id_weight; mat.total_weight += id_weight; ``` on the next line add: ```glsl if(id == emissive_id) { mat.emissive += textureGrad(emissive_tex, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw).rgb *= id_weight; } ``` Find this in `fragment()`, before the final `}`, apply the weighting and send it to the GPU. ```glsl // normalize accumulated values back to 0.0 - 1.0 range. float weight_inv = 1.0 / total_weight; mat.albedo_height *= weight_inv; ... mat.emissive *= weight_inv; EMISSION = mat.emissive * emissive_strength; ``` Next, add your emissive texture to the texture sampler and adjust the values on the newly exposed uniforms. ### Avoid sub branches Avoid placing an if statement within an if statement. Enable your FPS counter so you can test as you build your code. Some branch configurations may be free, some may be very expensive, or even more performant than you expect. Always test. Sometimes it's faster to always calculate than it is to branch. Sometimes you can do tricks like this to avoid sub branching: ```glsl uniform bool auto_shader; if (height > 256) { if (auto_shader) { albedo = snow_color; } } ``` ```glsl uniform bool auto_shader; if (height > 256) { albedo = float(!auto_shader)*albedo + float(auto_shader)*snow_color; } ``` These two are equivalent, and avoids the sub branch by always calculating. If auto_shader is true, the line is `albedo = 0.*albedo + 1.*snow_color`. ================================================ FILE: doc/docs/troubleshooting.md ================================================ Troubleshooting ================= Terrain3D is working for thousands of users. If you're having trouble you've likely missed a step or an important piece of documentation. Ensure you are using the [console version of Godot](#using-the-console), and have reviewed the [installation instructions](installation.md) before continuing. You can also watch the [tutorial videos](tutorial_videos.md) which show proper installation and setup, and read [Technical Tips](tips_technical.md) which may have other helpful information. **Table of Contents** * [Installation](#installation) * [Textures](#textures) * [Usage](#usage) * [Crashing](#crashing) * [Using the Console](#using-the-console) * [Debug Logs](#debug-logs) ## Installation ### Unable to load addon script from path `Unable to load addon script from path: xxxxxxxxxxx. This might be due to a code error in that script. Disabling the addon at 'res://addons/terrain_3d/plugin.cfg' to prevent further errors."` Most certainly you've installed the plugin improperly. These are the common causes: 1) You downloaded the repository code, not a [binary release](https://github.com/TokisanGames/Terrain3D/releases). 2) You moved the files into the wrong directory. The Terrain3D files should be in `project/addons/terrain_3d`. `Editor.gd` should be found at `res://addons/terrain_3d/editor/editor.gd`. [See an example issue here](https://github.com/TokisanGames/Terrain3D/issues/200). Basically, the binary library file isn't where it's supposed to be. The error messages in your [console](#using-the-console) (not the output panel) will tell you exactly the file name and path it's looking for. View that location on your hard drive. On windows you might be looking for `libterrain.windows.debug.x86_64.dll`. Does that file exist where it's looking in the logs? Probably not. Download the correct package, and review the instructions to install the files in the right location. ### There are no tools. The texture list is blank Ensure the plugin is [installed properly](installation.md) and enabled in the project settings. Then, click the Terrain3D node in the Scene panel to activate the tools and asset dock. ### The editor tools panel is there, but the buttons are blank or missing Restart Godot twice before using it. Review the [installation instructions](installation.md). ### Start up is very slow You probably have a large terrain and are generating collision for all of it. Set your Collision Mode to `Disabled` to test. You can dynamically create only a small collision area around the camera by setting your Collision Mode to `Dynamic / Game` (default) or `Dynamic / Editor`. Read more about [collision modes here](collision.md#physics-based-collision-raycasting). Or you've added a bunch of the generated textures, which are binary data saved as text in the scene. Or you've disconnected your textures from the files on disk, which has done the same thing. If your .tscn scene file (or .tres asset list if you've saved it off) is very large, and has lots of binary data as text, this is the case. Review your textures and ensure each is linked to a file saved on disk. Terrain3D should also report problem textures to your [terminal](#using-the-console). --- ## Textures ### Added a texture, now the terrain is white Your [console](#using-the-console) also reports something like: `Texture 1 albedo / normal, size / format... doesn't match size of first texture... They must be identical.` The new texture doesn't match the format or size of the existing ones. [Texture Preparation](texture_prep.md) descibes the requirements, which includes the same format and size for each. Double click a texture in the filesystem and Godot will tell you what it is. You can also click the texture in the inspector when editing an entry in the asset dock to see the same thing. If adding textures to the demo, the format is PNG marked HQ so it converts to BPTC, which you can read about on the link above. ### The terrain is all black Check with the default shader by disabling any override shader you have created. Otherwise, this is usually incompatibilities with textures or texture arrays. We have seen this when using the Compatibility Renderer in older versions where texture import settings need to be set to VRAM Uncompressed. In 4.4, the Compatibility Renderer hasn't had this problem. We've seen it running on mobile and switching to another phone works. This might be caused by poor support in the phone driver or Godot for textures in the format you're using, texture arrays in general, or texture arrays of the size we are using (1024). We've seen all three. It will take experimentation on your end, or updated drivers from the manufacturer or better support from Godot to fix it. ### The terrain is checkered If starting up for the first time, this is normal. Add a texture to the asset dock and paint with it. See [Texture Preparation](texture_prep.md). If loading scenes via code, this is because `free_editor_textures` is enabled by default and is conserving memory and VRAM. When running in game, Terrain3D combines the textures into texture arrays for the shader. It then clears the texture list so Godot can unload the original textures if they aren't used by other systems. To load them in all scenes, you can either: * Disable `free_editor_textures` * Make your asset list unique to each scene. (Think about this one. A shared texture list is useful.) * Reload the textures when loading a scene with Terrain3D: ``` terrain.assets = ResourceLoader.load("res://scenes/terrains/asset_list.tres", "", ResourceLoader.CACHE_MODE_IGNORE) ``` Terrain3D will continue to clear the textures on `_ready()` after compiling the arrays. You can clear textures at other times with `terrain.assets.clear_textures()`. --- ## Usage ### Collision is offset from the mesh and it's showing lower LODs near the camera If you're using multiple cameras, or viewports, you need to tell Terrain3D which camera you want to use with `Terrain3D.set_camera()`. You can update it every frame if you like. ### Textures flicker This is caused by temporal effects: FSR, TAA, and motion blur shaders. The Terrain3D mesh is constantly moving, which causes Godot to unnecessarily generate motion vectors for the terrain. To fix the flickering, disable the effects above, or use Godot 4.5+ and Terrain3D v1.0.2+, v1.1.0+, or source built against the godot-cpp 4.5 branch or higher. ### Intersecting meshes flicker When another mesh intersects with Terrain3D far away from the camera, such as in the case of water on a beach, the two meshes can flicker as the renderer can't decide which mesh should be displayed in front. This is called Z-fighting. You can greatly reduce it by increasing `Camera3D.near` to 0.25. You can also set it for the editor camera in the main viewport by adjusting `View/Settings/View Z-Near`. ### Segments of the terrain disappear on camera movement You can increase `Renderer/Cull Margin`. For sculpted regions this should not be necessary as sculpting sets the AABB for the meshes already. However for shader based backgrounds like WorldBackground, or Flat with ground level, there is no AABB detection. You need to specify an increased cull margin if the mesh goes higher or lower than the sculpted areas. This consumes some performance so it is ideal if not used. --- ## Crashing ### Godot crashes on load If this is the first startup after installing the plugin, this is normal due to a bug in the engine currently. Restart Godot. If it still crashes, try the demo scene. If that doesn't work, most likely the Terrain3D library version does not match the engine version. If you downloaded a release binary, download the exactly matching engine version. If you built from source review the [instructions](building_from_source.md) to make sure your `godot-cpp` directory exactly matches the engine version you want to use. If the demo scene does work, you have an issue in your project. It could be a setting or file given to Terrain3D, or it could be anywhere else in your project. Divide and conquer. Copy your project and start ripping things out until you find the cause. ### Exported game crashes on startup First make sure your game works running in the editor. Then ensure it works as a debug export with the console open. If there are challenges, you can enable [Terrain3D debugging](#debug-logs) before exporting with debug so you can see activity. Only then, test in release mode. Make sure you have both the debug and release binaries on your system, or have built Terrain3D in [both debug and release mode](building_from_source.md#5-build-the-extension), and that upon export both libraries are in the export directory (eg. `libterrain.windows.debug.x86_64.dll` and `libterrain.windows.release.x86_64.dll`). These libraries must be in the same directory as the executable, or somewhere in the library search path. Every OS has this option. If Godot can't open the libraries you linked to, your game will close instantly upon startup. --- ## Using the Console As a gamedev, you should always be running with the console or terminal window open. This means you ran `Godot_v4.*_console.exe` or ran Godot in a terminal window. ```{image} images/console_exec.png :target: ../_images/console_exec.png ``` This is what it looks like this when you have the console open. ```{image} images/console_window.png :target: ../_images/console_window.png ``` Godot, Terrain3D, and every other addon gives you additional information here, and when things don't work properly, these messages often tell you exactly why. Godot also has an `Output` panel at the bottom of the screen, but it is slow, will skip messages if it's busy, and not all messages appear there. ## Debug Logs Terrain3D has debug logs for everything, which it can dump to the [console](#using-the-console). These logs *may* also be saved to Godot's log files on disk. Set `Terrain3D.debug_level` to `Info` or `Debug` and you'll get copious activity logs that will help troubleshoot problems. You can also enable debugging from the command line by running Godot with `--terrain3d-debug=` where `` is one of `ERROR`, `INFO`, `DEBUG`, `EXTREME`. Extreme dumps everything including repetitive messages such as those that appear on camera movement. To run the demo from the command line with debugging, open a terminal, and change to the project folder (where `project.godot` is): Adjust the file paths to your system. The console executable is not needed since you're already running these commands in a terminal window. ``` # To run the demo with debug messages cd /c/gd/Terrain3D/project /c/gd/bin/Godot_v4.1.3-stable_win64.exe --terrain3d-debug=DEBUG # To run the editor with debug messages /c/gd/bin/Godot_v4.1.3-stable_win64.exe -e --terrain3d-debug=DEBUG ``` When asking for help on anything you can't solve yourself, you'll need to provide a full log from your console or file system. As long as Godot doesn't crash, it saves the log files on your drive. In Godot select, `Editor, Open Editor Data/Settings Menu`. On windows this opens `%appdata%\Godot` (e.g. `C:\Users\%username%\AppData\Roaming\Godot`). Look under `app_userdata\\logs`. Otherwise, you can copy and paste messages from the console window above. ================================================ FILE: doc/docs/tutorial_videos.md ================================================ Tutorial Videos ================== *Note: Some instructions in the videos may be out of date. Be sure to review the documentation for the latest info. Be aware you can select the appropriate documentation version in the menu.* ## Using Terrain3D Part 1 Covers: * Installation from Github * Setup (storage is out of date, specify a directory now) * Basic Usage * Preparing textures * Importing/Exporting data --- ## Using Terrain3D Part 2 Covers: * Texture Painting * Autoshader * Material Settings * Occlusion Culling * Holes * Navigation * Mesh intersection flickering (e.g. for ocean planes) * Advanced Usage --- ## Terrain3D v0.9.3a New Features - Tutorial 3 Covers: * Asset library installation * Platform updates * Region features like splitting and grid * Asset Dock * Detiling * Instancer * Tool bar changes and tool settings * Alpha stamping ================================================ FILE: doc/docs/user_interface.md ================================================ User Interface ================= This document describes the various UI components of Terrain3D. **Table of Contents** * [Main Toolbar](#main-toolbar) * [Terrain3D Menu](#terrain3d-menu) * [Tool Settings Bar](#tool-settings-bar) * [Asset Dock](#asset-dock) ## Main Toolbar After properly installing and enabling the plugin, add a Terrain3D node to your Scene and select it. This will enable the toolbar. ```{image} images/ui_tools.png :target: ../_images/ui_tools.png ``` The tools provide options for sculpting, texturing, and other features. Each button has a tooltip that describes what it does. First, select the Region Tool (first one: square with a cross), and click the ground. This allocates space for you to sculpt and paint. ## Terrain3D Menu After selecting the Terrain3D node, the Terrain3D menu appears at the top of the viewport. This provides a variety of utilities such as our channel packer, mesh baker, occlusion baker, etc. These are documented elsewhere. ```{image} images/terrain3d_menu.png :target: ../_images/terrain3d_menu.png ``` --- ## Tool Settings Bar Depending on which tool is selected on the toolbar, various tool settings will appear on the bottom bar. ```{image} images/ui_tool_settings.jpg :target: ../_images/ui_tool_settings.jpg ``` Many tool settings offer a slider with a fixed range, and an input box where you can manually enter a much larger setting. Click the label of any setting to reset the value to its default, or checkboxes to toggle. The settings are saved across sessions in `Editor Settings / Terrain3D / Tool Settings`. Some tools like `Paint`, `Spray`, and `Color` have options to disable some features. e.g. Disabling `Texture` on `Paint` means it will only apply scale or angle. Enabling `Texture` on `Color` will filter color painting to the selected texture. The three dots button on the right is the advanced options menu. One noteworthy setting is `Brush Spin Speed`, which is what causes the brush to spin while painting. Reduce it to zero if you don't want this. Brushes can be edited in the `addons/terrain_3d/brushes` directory, using your OS folder explorer. The folder is hidden to Godot. The files are 100x100 alpha masks saved as EXR. Larger sizes should work fine, but will be slow if too big. We scale all brushes up to a minimum of 1024px on selection. Most other options are self explanatory. See [Foliage Instancing](instancer.md) for specific details on its settings. ### Slope Range Filter The slope filter on the settings bar allows you to paint by slope. If the slope filter is 0-45 degrees, then it will paint if the slope of the ground is 45 degrees or less. Don't confuse this with the slope sculpting tool on the left toolbar. These tools work with the slope filter: **Paint**, **Spray**, **Color**, **Wetness**, **Instancer**. See the [keyboard shortcuts](keyboard_shortcuts.md) for usage, including the option to inverse the slope. --- ## Asset Dock The asset dock houses the list of textures and meshes available for use in Terrain3D. The contents are stored in the [Terrain3DAssets](../api/class_terrain3dassets.rst) resource shown in the inspector when the Terrain3D node is selected. Click `Textures` or `Meshes` to switch between the asset types. Mesh thumbnail generation is currently unreliable. Hover over a mesh or click `Meshes` to regenerate them. ```{image} images/ui_asset_dock_bottom.png :target: ../_images/ui_asset_dock_bottom.png ``` ### Dock Position The dropdown option allows you to select the dock position. Shown above, it is docked to the bottom panel. Below, it is docked to the right sidebar, in the [bottom right position](https://docs.godotengine.org/en/stable/classes/class_editorplugin.html#class-editorplugin-constant-dock-slot-left-ul). ```{image} images/ui_asset_dock_sidebar.png :target: ../_images/ui_asset_dock_sidebar.png ``` The icon with white and grey boxes in the top right can be used to pop out the dock into its own window. While in this state, there is a pin button that allows you to enable or disable always-on-top. Next the slider will resize the thumbnails. Finally, when the dock is in the sidebar, there are three vertical, grey dots, shown in the image above, in the top right. This also allows you to change the sidebar position, however setting it here won't save. Ignore this and use our dropdown instead. ### Adding Assets You can add resources by dragging a texture onto the `Add Texture` icon, a mesh (a packed scene: tscn, scn, fbx, glb) onto the `Add Mesh` icon, or by clicking either `Add` button and setting them up manually. If you add a new texture and the terrain turns white, see [Troubleshooting](troubleshooting.md#added-a-texture-now-the-terrain-is-white). Each asset resource type has their own settings described in the API docs for [Terrain3DTextureAsset](../api/class_terrain3dtextureasset.rst) and [Terrain3DMeshAsset](../api/class_terrain3dmeshasset.rst). You can read more about mesh setup on the [Foliage Instancer page](instancer.md#how-to-use-the-instancer). ### Dock Operations * LMB - Select the asset to paint with. * RMB - Edit the asset in the inspector. You can also click the pencil on the thumbnail. * MMB - Clear the asset. You can also click the X on the thumbnail. If this asset is at the end of the list, this will also remove it. You can clear and reuse this asset, or change its ID to move it to the end for removal. When using the instancer, this will remove all instances painted on the ground. It will ask for confirmation first. ================================================ FILE: doc/dump_contributors.py ================================================ # This scripts dumps all contributors names, handles, and urls for a repository. # To use modify the settings below. pat_file = "/c/Users/Cory/github_terrain3d_pat.txt" # Github Personal Access Token to expand rate limiting repo_url = "https://api.github.com/repos/TokisanGames/Terrain3D/contributors" import requests import sys import unicodedata import time import pathlib # Set UTF-8 encoding for stdout on all platforms sys.stdout.reconfigure(encoding='utf-8') # Function to normalize Unicode characters to ASCII def normalize_name(name): if not name: return "" try: # Normalize Unicode to ASCII, removing diacritics normalized = unicodedata.normalize('NFKD', name).encode('ascii', 'ignore').decode('ascii') return normalized.strip() except Exception: return "" # Function to normalize Git Bash /c/ paths to Windows C:\ paths def normalize_gitbash_path(path): if sys.platform == "win32" and path.startswith("/c/"): return "C:\\" + path[3:].replace("/", "\\") return path # Read GitHub PAT from file pat_file = normalize_gitbash_path(pat_file) pat_path = pathlib.Path(pat_file) print(f"Reading PAT file from: {pat_path}") try: with pat_path.open("r", encoding="utf-8") as f: github_pat = f.read().strip() except FileNotFoundError: print(f"Error: Could not find PAT file at {pat_path}") sys.exit(1) except Exception as e: print(f"Error reading PAT file at {pat_path}: {e}") sys.exit(1) # GitHub API endpoint for contributors headers = { "Accept": "application/vnd.github.v3+json", "User-Agent": "Python script", # Avoid rate-limiting "Authorization": f"token {github_pat}" # Use PAT from file } params = {"per_page": 100} # Max per page # Fetch all contributors with pagination contributors = [] page = 1 while True: response = requests.get(repo_url, headers=headers, params={**params, "page": page}) if response.status_code != 200: print(f"API error fetching contributors: {response.status_code} - {response.text}") break try: data = response.json() except ValueError: print(f"Failed to parse JSON response for contributors page {page}") break # Handle API errors if not isinstance(data, list): print(f"API error: {data.get('message', 'Unknown error')}") break if not data: break contributors.extend(data) page += 1 # Process each contributor for contributor in contributors: username = contributor.get("login", "Unknown") if username == "Unknown": print(f"Skipping contributor with missing login") continue # Fetch user profile profile_url = f"https://api.github.com/users/{username}" try: profile_response = requests.get(profile_url, headers=headers) time.sleep(0.5) # Delay to avoid rate-limiting if profile_response.status_code != 200: print(f"Failed to fetch profile for {username}: {profile_response.status_code}") name = "" else: try: profile = profile_response.json() # Ensure profile is valid and has data if not isinstance(profile, dict): print(f"Invalid profile data for {username}") name = "" else: # Get published name (leave blank if None or empty) name = profile.get("name", "").strip() if profile.get("name") else "" if name.lower() == "none" or not name: name = "" else: name = normalize_name(name) + " " except ValueError: print(f"Failed to parse JSON profile for {username}") name = "" except requests.RequestException as e: print(f"Error fetching profile for {username}: {e}") name = "" # Print formatted output try: print(f"* {name}[@{username}](https://github.com/{username})") except UnicodeEncodeError: # Fallback: print without name if encoding fails print(f"* [@{username}](https://github.com/{username})") ================================================ FILE: doc/index.rst ================================================ .. Terrain3D documentation master file, created by sphinx-quickstart on Wed Nov 29 15:57:36 2023. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. |logo| image:: docs/images/terrain3d.jpg :target: _images/terrain3d.jpg |logo| Terrain3D Documentation =============================== `Terrain3D `_ is a high performance, editable terrain system for Godot 4. Start with :doc:`Introduction ` and the other pages in the Getting Started section in the left sidebar to learn how to use this system. Credit ------------- Developed for the Godot community by: +------------------------------------+-------------------------------------+ | **Cory Petkovsek, Tokisan Games** | |t-x| |t-gh| |t-web| |t-ds| |t-yt| | +------------------------------------+-------------------------------------+ | **Roope Palmroos, Outobugi Games** | |o-x| |o-gh| |o-web| |o-yt| | +------------------------------------+-------------------------------------+ .. |t-x| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/twitter.png?raw=true :target: https://twitter.com/TokisanGames :width: 24 .. |t-gh| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/github.png?raw=true :target: https://github.com/TokisanGames :width: 24 .. |t-web| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/www.png?raw=true :target: https://tokisan.com/ :width: 24 .. |t-ds| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/discord.png?raw=true :target: https://tokisan.com/discord :width: 24 .. |t-yt| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/youtube.png?raw=true :target: https://www.youtube.com/@TokisanGames :width: 24 .. |o-x| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/twitter.png?raw=true :target: https://twitter.com/outobugi :width: 24 .. |o-gh| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/github.png?raw=true :target: https://github.com/outobugi :width: 24 .. |o-web| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/www.png?raw=true :target: https://outobugi.com/ :width: 24 .. |o-yt| image:: https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/youtube.png?raw=true :target: https://www.youtube.com/@outobugi :width: 24 And the contribution team in :doc:`docs/authors` and displayed on `github contributors `_. .. toctree:: :maxdepth: 1 :caption: About docs/games docs/press docs/authors docs/license .. toctree:: :maxdepth: 1 :caption: Getting Started docs/introduction docs/tutorial_videos docs/installation docs/platforms docs/user_interface docs/keyboard_shortcuts docs/troubleshooting docs/getting_help .. toctree:: :maxdepth: 1 :caption: Basic Usage docs/texture_prep docs/texture_painting docs/heightmaps docs/import_export docs/instancer docs/tips_technical docs/tips_environment .. toctree:: :maxdepth: 1 :caption: Advanced Usage docs/collision docs/displacement docs/double_precision docs/navigation docs/occlusion_culling docs/programming_languages .. toctree:: :maxdepth: 1 :caption: Development docs/nightly_builds docs/building_from_source docs/system_architecture docs/shader_design docs/controlmap_format docs/data_format docs/generating_csharp_bindings docs/contributing .. toctree:: :maxdepth: 2 :caption: API api/index ================================================ FILE: doc/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) if "%1" == "" goto help %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd ================================================ FILE: doc/requirements.txt ================================================ docutils>=0.17 myst-parser>=3.0.0 sphinx_rtd_theme>=1.1.1 sphinx-rtd-dark-mode sphinx-tabs>=3.4.7 sphinx<9.0.0 ================================================ FILE: project/Terrain3D.csproj ================================================ net8.0 true ================================================ FILE: project/Terrain3D.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terrain3D", "Terrain3D.csproj", "{E3C3EDF1-6587-443E-B932-EBA7704DB53E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU ExportDebug|Any CPU = ExportDebug|Any CPU ExportRelease|Any CPU = ExportRelease|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E3C3EDF1-6587-443E-B932-EBA7704DB53E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E3C3EDF1-6587-443E-B932-EBA7704DB53E}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3C3EDF1-6587-443E-B932-EBA7704DB53E}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU {E3C3EDF1-6587-443E-B932-EBA7704DB53E}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU {E3C3EDF1-6587-443E-B932-EBA7704DB53E}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU {E3C3EDF1-6587-443E-B932-EBA7704DB53E}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU EndGlobalSection EndGlobal ================================================ FILE: project/addons/terrain_3d/brushes/.gdignore ================================================ ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3D.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3D : Node3D { private new static readonly StringName NativeName = new StringName("Terrain3D"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3D object), please use the Instantiate() method instead.")] protected Terrain3D() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3D Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3D wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3D); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3D).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3D)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3D" type. public new static Terrain3D Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum DebugLevelEnum { Error = 0, Info = 1, Debug = 2, Extreme = 3, } public enum RegionSizeEnum { Size64 = 64, Size128 = 128, Size256 = 256, Size512 = 512, Size1024 = 1024, Size2048 = 2048, } public new static class GDExtensionSignalName { public new static readonly StringName MaterialChanged = "material_changed"; public new static readonly StringName AssetsChanged = "assets_changed"; } public new delegate void MaterialChangedSignalHandler(); private MaterialChangedSignalHandler _materialChangedSignal; private Callable _materialChangedSignalCallable; public event MaterialChangedSignalHandler MaterialChangedSignal { add { if (_materialChangedSignal is null) { _materialChangedSignalCallable = Callable.From(() => _materialChangedSignal?.Invoke()); Connect(GDExtensionSignalName.MaterialChanged, _materialChangedSignalCallable); } _materialChangedSignal += value; } remove { _materialChangedSignal -= value; if (_materialChangedSignal is not null) return; Disconnect(GDExtensionSignalName.MaterialChanged, _materialChangedSignalCallable); _materialChangedSignalCallable = default; } } public new delegate void AssetsChangedSignalHandler(); private AssetsChangedSignalHandler _assetsChangedSignal; private Callable _assetsChangedSignalCallable; public event AssetsChangedSignalHandler AssetsChangedSignal { add { if (_assetsChangedSignal is null) { _assetsChangedSignalCallable = Callable.From(() => _assetsChangedSignal?.Invoke()); Connect(GDExtensionSignalName.AssetsChanged, _assetsChangedSignalCallable); } _assetsChangedSignal += value; } remove { _assetsChangedSignal -= value; if (_assetsChangedSignal is not null) return; Disconnect(GDExtensionSignalName.AssetsChanged, _assetsChangedSignalCallable); _assetsChangedSignalCallable = default; } } public new static class GDExtensionPropertyName { public new static readonly StringName Version = "version"; public new static readonly StringName DebugLevel = "debug_level"; public new static readonly StringName DataDirectory = "data_directory"; public new static readonly StringName Material = "material"; public new static readonly StringName Assets = "assets"; public new static readonly StringName Data = "data"; public new static readonly StringName Collision = "collision"; public new static readonly StringName Instancer = "instancer"; public new static readonly StringName RegionSize = "region_size"; public new static readonly StringName Save16Bit = "save_16_bit"; public new static readonly StringName LabelDistance = "label_distance"; public new static readonly StringName LabelSize = "label_size"; public new static readonly StringName ShowGrid = "show_grid"; public new static readonly StringName CollisionMode = "collision_mode"; public new static readonly StringName CollisionShapeSize = "collision_shape_size"; public new static readonly StringName CollisionRadius = "collision_radius"; public new static readonly StringName CollisionTarget = "collision_target"; public new static readonly StringName CollisionLayer = "collision_layer"; public new static readonly StringName CollisionMask = "collision_mask"; public new static readonly StringName CollisionPriority = "collision_priority"; public new static readonly StringName PhysicsMaterial = "physics_material"; public new static readonly StringName ClipmapTarget = "clipmap_target"; public new static readonly StringName MeshLods = "mesh_lods"; public new static readonly StringName TessellationLevel = "tessellation_level"; public new static readonly StringName MeshSize = "mesh_size"; public new static readonly StringName VertexSpacing = "vertex_spacing"; public new static readonly StringName CullMargin = "cull_margin"; public new static readonly StringName CastShadows = "cast_shadows"; public new static readonly StringName GiMode = "gi_mode"; public new static readonly StringName RenderLayers = "render_layers"; public new static readonly StringName DisplacementScale = "displacement_scale"; public new static readonly StringName DisplacementSharpness = "displacement_sharpness"; public new static readonly StringName BufferShaderOverrideEnabled = "buffer_shader_override_enabled"; public new static readonly StringName BufferShaderOverride = "buffer_shader_override"; public new static readonly StringName OceanEnabled = "ocean_enabled"; public new static readonly StringName OceanMeshLods = "ocean_mesh_lods"; public new static readonly StringName OceanTessellationLevel = "ocean_tessellation_level"; public new static readonly StringName OceanMeshSize = "ocean_mesh_size"; public new static readonly StringName OceanVertexSpacing = "ocean_vertex_spacing"; public new static readonly StringName OceanCullMargin = "ocean_cull_margin"; public new static readonly StringName OceanCastShadows = "ocean_cast_shadows"; public new static readonly StringName OceanGiMode = "ocean_gi_mode"; public new static readonly StringName OceanRenderLayers = "ocean_render_layers"; public new static readonly StringName OceanMaterial = "ocean_material"; public new static readonly StringName OceanLightTarget = "ocean_light_target"; public new static readonly StringName MouseLayer = "mouse_layer"; public new static readonly StringName FreeEditorTextures = "free_editor_textures"; public new static readonly StringName InstancerMode = "instancer_mode"; public new static readonly StringName ShowRegionGrid = "show_region_grid"; public new static readonly StringName ShowInstancerGrid = "show_instancer_grid"; public new static readonly StringName ShowVertexGrid = "show_vertex_grid"; public new static readonly StringName ShowContours = "show_contours"; public new static readonly StringName ShowNavigation = "show_navigation"; public new static readonly StringName ShowCheckered = "show_checkered"; public new static readonly StringName ShowGrey = "show_grey"; public new static readonly StringName ShowHeightmap = "show_heightmap"; public new static readonly StringName ShowJaggedness = "show_jaggedness"; public new static readonly StringName ShowAutoshader = "show_autoshader"; public new static readonly StringName ShowControlTexture = "show_control_texture"; public new static readonly StringName ShowControlBlend = "show_control_blend"; public new static readonly StringName ShowControlAngle = "show_control_angle"; public new static readonly StringName ShowControlScale = "show_control_scale"; public new static readonly StringName ShowColormap = "show_colormap"; public new static readonly StringName ShowRoughmap = "show_roughmap"; public new static readonly StringName ShowDisplacementBuffer = "show_displacement_buffer"; public new static readonly StringName ShowTextureAlbedo = "show_texture_albedo"; public new static readonly StringName ShowTextureHeight = "show_texture_height"; public new static readonly StringName ShowTextureNormal = "show_texture_normal"; public new static readonly StringName ShowTextureRough = "show_texture_rough"; public new static readonly StringName ShowTextureAo = "show_texture_ao"; } public new string Version { get => Get(GDExtensionPropertyName.Version).As(); } public new Variant DebugLevel { get => Get(GDExtensionPropertyName.DebugLevel).As(); set => Set(GDExtensionPropertyName.DebugLevel, value); } public new string DataDirectory { get => Get(GDExtensionPropertyName.DataDirectory).As(); set => Set(GDExtensionPropertyName.DataDirectory, value); } public new Terrain3DMaterial Material { get => Terrain3DMaterial.Bind(Get(GDExtensionPropertyName.Material).As()); set => Set(GDExtensionPropertyName.Material, value); } public new Terrain3DAssets Assets { get => Terrain3DAssets.Bind(Get(GDExtensionPropertyName.Assets).As()); set => Set(GDExtensionPropertyName.Assets, value); } public new Terrain3DData Data { get => Terrain3DData.Bind(Get(GDExtensionPropertyName.Data).As()); } public new Terrain3DCollision Collision { get => Terrain3DCollision.Bind(Get(GDExtensionPropertyName.Collision).As()); } public new Terrain3DInstancer Instancer { get => Terrain3DInstancer.Bind(Get(GDExtensionPropertyName.Instancer).As()); } public new Variant RegionSize { get => Get(GDExtensionPropertyName.RegionSize).As(); set => Set(GDExtensionPropertyName.RegionSize, value); } public new bool Save16Bit { get => Get(GDExtensionPropertyName.Save16Bit).As(); set => Set(GDExtensionPropertyName.Save16Bit, value); } public new double LabelDistance { get => Get(GDExtensionPropertyName.LabelDistance).As(); set => Set(GDExtensionPropertyName.LabelDistance, value); } public new long LabelSize { get => Get(GDExtensionPropertyName.LabelSize).As(); set => Set(GDExtensionPropertyName.LabelSize, value); } public new bool ShowGrid { get => Get(GDExtensionPropertyName.ShowGrid).As(); set => Set(GDExtensionPropertyName.ShowGrid, value); } public new Variant CollisionMode { get => Get(GDExtensionPropertyName.CollisionMode).As(); set => Set(GDExtensionPropertyName.CollisionMode, value); } public new long CollisionShapeSize { get => Get(GDExtensionPropertyName.CollisionShapeSize).As(); set => Set(GDExtensionPropertyName.CollisionShapeSize, value); } public new long CollisionRadius { get => Get(GDExtensionPropertyName.CollisionRadius).As(); set => Set(GDExtensionPropertyName.CollisionRadius, value); } public new Node3D CollisionTarget { get => Get(GDExtensionPropertyName.CollisionTarget).As(); set => Set(GDExtensionPropertyName.CollisionTarget, value); } public new long CollisionLayer { get => Get(GDExtensionPropertyName.CollisionLayer).As(); set => Set(GDExtensionPropertyName.CollisionLayer, value); } public new long CollisionMask { get => Get(GDExtensionPropertyName.CollisionMask).As(); set => Set(GDExtensionPropertyName.CollisionMask, value); } public new double CollisionPriority { get => Get(GDExtensionPropertyName.CollisionPriority).As(); set => Set(GDExtensionPropertyName.CollisionPriority, value); } public new PhysicsMaterial PhysicsMaterial { get => Get(GDExtensionPropertyName.PhysicsMaterial).As(); set => Set(GDExtensionPropertyName.PhysicsMaterial, value); } public new Node3D ClipmapTarget { get => Get(GDExtensionPropertyName.ClipmapTarget).As(); set => Set(GDExtensionPropertyName.ClipmapTarget, value); } public new long MeshLods { get => Get(GDExtensionPropertyName.MeshLods).As(); set => Set(GDExtensionPropertyName.MeshLods, value); } public new long TessellationLevel { get => Get(GDExtensionPropertyName.TessellationLevel).As(); set => Set(GDExtensionPropertyName.TessellationLevel, value); } public new long MeshSize { get => Get(GDExtensionPropertyName.MeshSize).As(); set => Set(GDExtensionPropertyName.MeshSize, value); } public new double VertexSpacing { get => Get(GDExtensionPropertyName.VertexSpacing).As(); set => Set(GDExtensionPropertyName.VertexSpacing, value); } public new double CullMargin { get => Get(GDExtensionPropertyName.CullMargin).As(); set => Set(GDExtensionPropertyName.CullMargin, value); } public new long CastShadows { get => Get(GDExtensionPropertyName.CastShadows).As(); set => Set(GDExtensionPropertyName.CastShadows, value); } public new long GiMode { get => Get(GDExtensionPropertyName.GiMode).As(); set => Set(GDExtensionPropertyName.GiMode, value); } public new long RenderLayers { get => Get(GDExtensionPropertyName.RenderLayers).As(); set => Set(GDExtensionPropertyName.RenderLayers, value); } public new double DisplacementScale { get => Get(GDExtensionPropertyName.DisplacementScale).As(); set => Set(GDExtensionPropertyName.DisplacementScale, value); } public new double DisplacementSharpness { get => Get(GDExtensionPropertyName.DisplacementSharpness).As(); set => Set(GDExtensionPropertyName.DisplacementSharpness, value); } public new bool BufferShaderOverrideEnabled { get => Get(GDExtensionPropertyName.BufferShaderOverrideEnabled).As(); set => Set(GDExtensionPropertyName.BufferShaderOverrideEnabled, value); } public new Shader BufferShaderOverride { get => Get(GDExtensionPropertyName.BufferShaderOverride).As(); set => Set(GDExtensionPropertyName.BufferShaderOverride, value); } public new bool OceanEnabled { get => Get(GDExtensionPropertyName.OceanEnabled).As(); set => Set(GDExtensionPropertyName.OceanEnabled, value); } public new long OceanMeshLods { get => Get(GDExtensionPropertyName.OceanMeshLods).As(); set => Set(GDExtensionPropertyName.OceanMeshLods, value); } public new long OceanTessellationLevel { get => Get(GDExtensionPropertyName.OceanTessellationLevel).As(); set => Set(GDExtensionPropertyName.OceanTessellationLevel, value); } public new long OceanMeshSize { get => Get(GDExtensionPropertyName.OceanMeshSize).As(); set => Set(GDExtensionPropertyName.OceanMeshSize, value); } public new double OceanVertexSpacing { get => Get(GDExtensionPropertyName.OceanVertexSpacing).As(); set => Set(GDExtensionPropertyName.OceanVertexSpacing, value); } public new double OceanCullMargin { get => Get(GDExtensionPropertyName.OceanCullMargin).As(); set => Set(GDExtensionPropertyName.OceanCullMargin, value); } public new long OceanCastShadows { get => Get(GDExtensionPropertyName.OceanCastShadows).As(); set => Set(GDExtensionPropertyName.OceanCastShadows, value); } public new long OceanGiMode { get => Get(GDExtensionPropertyName.OceanGiMode).As(); set => Set(GDExtensionPropertyName.OceanGiMode, value); } public new long OceanRenderLayers { get => Get(GDExtensionPropertyName.OceanRenderLayers).As(); set => Set(GDExtensionPropertyName.OceanRenderLayers, value); } public new Material OceanMaterial { get => Get(GDExtensionPropertyName.OceanMaterial).As(); set => Set(GDExtensionPropertyName.OceanMaterial, value); } public new Node3D OceanLightTarget { get => Get(GDExtensionPropertyName.OceanLightTarget).As(); set => Set(GDExtensionPropertyName.OceanLightTarget, value); } public new long MouseLayer { get => Get(GDExtensionPropertyName.MouseLayer).As(); set => Set(GDExtensionPropertyName.MouseLayer, value); } public new bool FreeEditorTextures { get => Get(GDExtensionPropertyName.FreeEditorTextures).As(); set => Set(GDExtensionPropertyName.FreeEditorTextures, value); } public new Terrain3DInstancer.InstancerMode InstancerMode { get => Get(GDExtensionPropertyName.InstancerMode).As(); set => Set(GDExtensionPropertyName.InstancerMode, Variant.From(value)); } public new bool ShowRegionGrid { get => Get(GDExtensionPropertyName.ShowRegionGrid).As(); set => Set(GDExtensionPropertyName.ShowRegionGrid, value); } public new bool ShowInstancerGrid { get => Get(GDExtensionPropertyName.ShowInstancerGrid).As(); set => Set(GDExtensionPropertyName.ShowInstancerGrid, value); } public new bool ShowVertexGrid { get => Get(GDExtensionPropertyName.ShowVertexGrid).As(); set => Set(GDExtensionPropertyName.ShowVertexGrid, value); } public new bool ShowContours { get => Get(GDExtensionPropertyName.ShowContours).As(); set => Set(GDExtensionPropertyName.ShowContours, value); } public new bool ShowNavigation { get => Get(GDExtensionPropertyName.ShowNavigation).As(); set => Set(GDExtensionPropertyName.ShowNavigation, value); } public new bool ShowCheckered { get => Get(GDExtensionPropertyName.ShowCheckered).As(); set => Set(GDExtensionPropertyName.ShowCheckered, value); } public new bool ShowGrey { get => Get(GDExtensionPropertyName.ShowGrey).As(); set => Set(GDExtensionPropertyName.ShowGrey, value); } public new bool ShowHeightmap { get => Get(GDExtensionPropertyName.ShowHeightmap).As(); set => Set(GDExtensionPropertyName.ShowHeightmap, value); } public new bool ShowJaggedness { get => Get(GDExtensionPropertyName.ShowJaggedness).As(); set => Set(GDExtensionPropertyName.ShowJaggedness, value); } public new bool ShowAutoshader { get => Get(GDExtensionPropertyName.ShowAutoshader).As(); set => Set(GDExtensionPropertyName.ShowAutoshader, value); } public new bool ShowControlTexture { get => Get(GDExtensionPropertyName.ShowControlTexture).As(); set => Set(GDExtensionPropertyName.ShowControlTexture, value); } public new bool ShowControlBlend { get => Get(GDExtensionPropertyName.ShowControlBlend).As(); set => Set(GDExtensionPropertyName.ShowControlBlend, value); } public new bool ShowControlAngle { get => Get(GDExtensionPropertyName.ShowControlAngle).As(); set => Set(GDExtensionPropertyName.ShowControlAngle, value); } public new bool ShowControlScale { get => Get(GDExtensionPropertyName.ShowControlScale).As(); set => Set(GDExtensionPropertyName.ShowControlScale, value); } public new bool ShowColormap { get => Get(GDExtensionPropertyName.ShowColormap).As(); set => Set(GDExtensionPropertyName.ShowColormap, value); } public new bool ShowRoughmap { get => Get(GDExtensionPropertyName.ShowRoughmap).As(); set => Set(GDExtensionPropertyName.ShowRoughmap, value); } public new bool ShowDisplacementBuffer { get => Get(GDExtensionPropertyName.ShowDisplacementBuffer).As(); set => Set(GDExtensionPropertyName.ShowDisplacementBuffer, value); } public new bool ShowTextureAlbedo { get => Get(GDExtensionPropertyName.ShowTextureAlbedo).As(); set => Set(GDExtensionPropertyName.ShowTextureAlbedo, value); } public new bool ShowTextureHeight { get => Get(GDExtensionPropertyName.ShowTextureHeight).As(); set => Set(GDExtensionPropertyName.ShowTextureHeight, value); } public new bool ShowTextureNormal { get => Get(GDExtensionPropertyName.ShowTextureNormal).As(); set => Set(GDExtensionPropertyName.ShowTextureNormal, value); } public new bool ShowTextureRough { get => Get(GDExtensionPropertyName.ShowTextureRough).As(); set => Set(GDExtensionPropertyName.ShowTextureRough, value); } public new bool ShowTextureAo { get => Get(GDExtensionPropertyName.ShowTextureAo).As(); set => Set(GDExtensionPropertyName.ShowTextureAo, value); } public new static class GDExtensionMethodName { public new static readonly StringName SetEditor = "set_editor"; public new static readonly StringName GetEditor = "get_editor"; public new static readonly StringName SetPlugin = "set_plugin"; public new static readonly StringName GetPlugin = "get_plugin"; public new static readonly StringName SetCamera = "set_camera"; public new static readonly StringName GetCamera = "get_camera"; public new static readonly StringName GetClipmapTargetPosition = "get_clipmap_target_position"; public new static readonly StringName GetCollisionTargetPosition = "get_collision_target_position"; public new static readonly StringName Snap = "snap"; public new static readonly StringName GetIntersection = "get_intersection"; public new static readonly StringName GetRaycastResult = "get_raycast_result"; public new static readonly StringName BakeMesh = "bake_mesh"; public new static readonly StringName GenerateNavMeshSourceGeometry = "generate_nav_mesh_source_geometry"; } public new void SetEditor(Terrain3DEditor editor) => Call(GDExtensionMethodName.SetEditor, [editor]); public new Terrain3DEditor GetEditor() => Terrain3DEditor.Bind(Call(GDExtensionMethodName.GetEditor, []).As()); public new void SetPlugin(GodotObject plugin) => Call(GDExtensionMethodName.SetPlugin, [plugin]); public new GodotObject GetPlugin() => Call(GDExtensionMethodName.GetPlugin, []).As(); public new void SetCamera(Camera3D camera) => Call(GDExtensionMethodName.SetCamera, [camera]); public new Camera3D GetCamera() => Call(GDExtensionMethodName.GetCamera, []).As(); public new Vector3 GetClipmapTargetPosition() => Call(GDExtensionMethodName.GetClipmapTargetPosition, []).As(); public new Vector3 GetCollisionTargetPosition() => Call(GDExtensionMethodName.GetCollisionTargetPosition, []).As(); public new void Snap() => Call(GDExtensionMethodName.Snap, []); public new Vector3 GetIntersection(Vector3 srcPos, Vector3 direction, bool gpuMode = false) => Call(GDExtensionMethodName.GetIntersection, [srcPos, direction, gpuMode]).As(); public new Godot.Collections.Dictionary GetRaycastResult(Vector3 srcPos, Vector3 direction, long collisionMask = 4294967295, bool excludeTerrain = false) => Call(GDExtensionMethodName.GetRaycastResult, [srcPos, direction, collisionMask, excludeTerrain]).As(); public new Mesh BakeMesh(long lod, Terrain3DData.HeightFilter filter = Terrain3DData.HeightFilter.Nearest) => Call(GDExtensionMethodName.BakeMesh, [lod, Variant.From(filter)]).As(); public new Vector3[] GenerateNavMeshSourceGeometry(Aabb globalAabb, bool requireNav = true) => Call(GDExtensionMethodName.GenerateNavMeshSourceGeometry, [globalAabb, requireNav]).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3D.cs.uid ================================================ uid://des50dgjni8ga ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DAssets.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DAssets : Resource { private new static readonly StringName NativeName = new StringName("Terrain3DAssets"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DAssets object), please use the Instantiate() method instead.")] protected Terrain3DAssets() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DAssets Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DAssets wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DAssets); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DAssets).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DAssets)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DAssets" type. public new static Terrain3DAssets Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum AssetType { Texture = 0, Mesh = 1, } public new static class GDExtensionSignalName { public new static readonly StringName MeshesChanged = "meshes_changed"; public new static readonly StringName TexturesChanged = "textures_changed"; } public new delegate void MeshesChangedSignalHandler(); private MeshesChangedSignalHandler _meshesChangedSignal; private Callable _meshesChangedSignalCallable; public event MeshesChangedSignalHandler MeshesChangedSignal { add { if (_meshesChangedSignal is null) { _meshesChangedSignalCallable = Callable.From(() => _meshesChangedSignal?.Invoke()); Connect(GDExtensionSignalName.MeshesChanged, _meshesChangedSignalCallable); } _meshesChangedSignal += value; } remove { _meshesChangedSignal -= value; if (_meshesChangedSignal is not null) return; Disconnect(GDExtensionSignalName.MeshesChanged, _meshesChangedSignalCallable); _meshesChangedSignalCallable = default; } } public new delegate void TexturesChangedSignalHandler(); private TexturesChangedSignalHandler _texturesChangedSignal; private Callable _texturesChangedSignalCallable; public event TexturesChangedSignalHandler TexturesChangedSignal { add { if (_texturesChangedSignal is null) { _texturesChangedSignalCallable = Callable.From(() => _texturesChangedSignal?.Invoke()); Connect(GDExtensionSignalName.TexturesChanged, _texturesChangedSignalCallable); } _texturesChangedSignal += value; } remove { _texturesChangedSignal -= value; if (_texturesChangedSignal is not null) return; Disconnect(GDExtensionSignalName.TexturesChanged, _texturesChangedSignalCallable); _texturesChangedSignalCallable = default; } } public new static class GDExtensionPropertyName { public new static readonly StringName MeshList = "mesh_list"; public new static readonly StringName TextureList = "texture_list"; } public new Godot.Collections.Array MeshList { get => Get(GDExtensionPropertyName.MeshList).As(); set => Set(GDExtensionPropertyName.MeshList, value); } public new Godot.Collections.Array TextureList { get => Get(GDExtensionPropertyName.TextureList).As(); set => Set(GDExtensionPropertyName.TextureList, value); } public new static class GDExtensionMethodName { public new static readonly StringName SetTextureAsset = "set_texture_asset"; public new static readonly StringName GetTextureAsset = "get_texture_asset"; public new static readonly StringName GetTextureCount = "get_texture_count"; public new static readonly StringName GetAlbedoArrayRid = "get_albedo_array_rid"; public new static readonly StringName GetNormalArrayRid = "get_normal_array_rid"; public new static readonly StringName GetTextureColors = "get_texture_colors"; public new static readonly StringName GetTextureNormalDepths = "get_texture_normal_depths"; public new static readonly StringName GetTextureAoStrengths = "get_texture_ao_strengths"; public new static readonly StringName GetTextureAoLightAffects = "get_texture_ao_light_affects"; public new static readonly StringName GetTextureRoughnessMods = "get_texture_roughness_mods"; public new static readonly StringName GetTextureUvScales = "get_texture_uv_scales"; public new static readonly StringName GetTextureDetiles = "get_texture_detiles"; public new static readonly StringName GetTextureDisplacements = "get_texture_displacements"; public new static readonly StringName ClearTextures = "clear_textures"; public new static readonly StringName UpdateTextureList = "update_texture_list"; public new static readonly StringName SetMeshAsset = "set_mesh_asset"; public new static readonly StringName GetMeshAsset = "get_mesh_asset"; public new static readonly StringName GetMeshCount = "get_mesh_count"; public new static readonly StringName CreateMeshThumbnails = "create_mesh_thumbnails"; public new static readonly StringName UpdateMeshList = "update_mesh_list"; public new static readonly StringName Save = "save"; } public new void SetTextureAsset(long id, Terrain3DTextureAsset texture) => Call(GDExtensionMethodName.SetTextureAsset, [id, texture]); public new Terrain3DTextureAsset GetTextureAsset(long id) => Terrain3DTextureAsset.Bind(Call(GDExtensionMethodName.GetTextureAsset, [id]).As()); public new long GetTextureCount() => Call(GDExtensionMethodName.GetTextureCount, []).As(); public new Rid GetAlbedoArrayRid() => Call(GDExtensionMethodName.GetAlbedoArrayRid, []).As(); public new Rid GetNormalArrayRid() => Call(GDExtensionMethodName.GetNormalArrayRid, []).As(); public new Color[] GetTextureColors() => Call(GDExtensionMethodName.GetTextureColors, []).As(); public new float[] GetTextureNormalDepths() => Call(GDExtensionMethodName.GetTextureNormalDepths, []).As(); public new float[] GetTextureAoStrengths() => Call(GDExtensionMethodName.GetTextureAoStrengths, []).As(); public new float[] GetTextureAoLightAffects() => Call(GDExtensionMethodName.GetTextureAoLightAffects, []).As(); public new float[] GetTextureRoughnessMods() => Call(GDExtensionMethodName.GetTextureRoughnessMods, []).As(); public new float[] GetTextureUvScales() => Call(GDExtensionMethodName.GetTextureUvScales, []).As(); public new Vector2[] GetTextureDetiles() => Call(GDExtensionMethodName.GetTextureDetiles, []).As(); public new Vector2[] GetTextureDisplacements() => Call(GDExtensionMethodName.GetTextureDisplacements, []).As(); public new void ClearTextures(bool update = false) => Call(GDExtensionMethodName.ClearTextures, [update]); public new void UpdateTextureList() => Call(GDExtensionMethodName.UpdateTextureList, []); public new void SetMeshAsset(long id, Terrain3DMeshAsset mesh) => Call(GDExtensionMethodName.SetMeshAsset, [id, mesh]); public new Terrain3DMeshAsset GetMeshAsset(long id) => Terrain3DMeshAsset.Bind(Call(GDExtensionMethodName.GetMeshAsset, [id]).As()); public new long GetMeshCount() => Call(GDExtensionMethodName.GetMeshCount, []).As(); public new void CreateMeshThumbnails(long id = -1, Vector2I size = default, bool force = false) => Call(GDExtensionMethodName.CreateMeshThumbnails, [id, size, force]); public new void UpdateMeshList() => Call(GDExtensionMethodName.UpdateMeshList, []); public new Error Save(string path = "") => Call(GDExtensionMethodName.Save, [path]).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DAssets.cs.uid ================================================ uid://myg4xtgeyn1n ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DCollision.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DCollision : GodotObject { private new static readonly StringName NativeName = new StringName("Terrain3DCollision"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DCollision object), please use the Instantiate() method instead.")] protected Terrain3DCollision() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DCollision Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DCollision wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DCollision); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DCollision).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DCollision)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DCollision" type. public new static Terrain3DCollision Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum CollisionMode { Disabled = 0, DynamicGame = 1, DynamicEditor = 2, FullGame = 3, FullEditor = 4, } public new static class GDExtensionPropertyName { public new static readonly StringName Mode = "mode"; public new static readonly StringName ShapeSize = "shape_size"; public new static readonly StringName Radius = "radius"; public new static readonly StringName Layer = "layer"; public new static readonly StringName Mask = "mask"; public new static readonly StringName Priority = "priority"; public new static readonly StringName PhysicsMaterial = "physics_material"; } public new Variant Mode { get => Get(GDExtensionPropertyName.Mode).As(); set => Set(GDExtensionPropertyName.Mode, value); } public new long ShapeSize { get => Get(GDExtensionPropertyName.ShapeSize).As(); set => Set(GDExtensionPropertyName.ShapeSize, value); } public new long Radius { get => Get(GDExtensionPropertyName.Radius).As(); set => Set(GDExtensionPropertyName.Radius, value); } public new long Layer { get => Get(GDExtensionPropertyName.Layer).As(); set => Set(GDExtensionPropertyName.Layer, value); } public new long Mask { get => Get(GDExtensionPropertyName.Mask).As(); set => Set(GDExtensionPropertyName.Mask, value); } public new double Priority { get => Get(GDExtensionPropertyName.Priority).As(); set => Set(GDExtensionPropertyName.Priority, value); } public new PhysicsMaterial PhysicsMaterial { get => Get(GDExtensionPropertyName.PhysicsMaterial).As(); set => Set(GDExtensionPropertyName.PhysicsMaterial, value); } public new static class GDExtensionMethodName { public new static readonly StringName Build = "build"; public new static readonly StringName Update = "update"; public new static readonly StringName Destroy = "destroy"; public new static readonly StringName IsEnabled = "is_enabled"; public new static readonly StringName IsEditorMode = "is_editor_mode"; public new static readonly StringName IsDynamicMode = "is_dynamic_mode"; public new static readonly StringName GetRid = "get_rid"; } public new void Build() => Call(GDExtensionMethodName.Build, []); public new void Update(bool rebuild = false) => Call(GDExtensionMethodName.Update, [rebuild]); public new void Destroy() => Call(GDExtensionMethodName.Destroy, []); public new bool IsEnabled() => Call(GDExtensionMethodName.IsEnabled, []).As(); public new bool IsEditorMode() => Call(GDExtensionMethodName.IsEditorMode, []).As(); public new bool IsDynamicMode() => Call(GDExtensionMethodName.IsDynamicMode, []).As(); public new Rid GetRid() => Call(GDExtensionMethodName.GetRid, []).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DCollision.cs.uid ================================================ uid://yab31a0ysqa3 ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DData.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DData : GodotObject { private new static readonly StringName NativeName = new StringName("Terrain3DData"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DData object), please use the Instantiate() method instead.")] protected Terrain3DData() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DData Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DData wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DData); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DData).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DData)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DData" type. public new static Terrain3DData Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum HeightFilter { Nearest = 0, Minimum = 1, } public new static class GDExtensionSignalName { public new static readonly StringName MapsChanged = "maps_changed"; public new static readonly StringName RegionMapChanged = "region_map_changed"; public new static readonly StringName HeightMapsChanged = "height_maps_changed"; public new static readonly StringName ControlMapsChanged = "control_maps_changed"; public new static readonly StringName ColorMapsChanged = "color_maps_changed"; public new static readonly StringName MapsEdited = "maps_edited"; } public new delegate void MapsChangedSignalHandler(); private MapsChangedSignalHandler _mapsChangedSignal; private Callable _mapsChangedSignalCallable; public event MapsChangedSignalHandler MapsChangedSignal { add { if (_mapsChangedSignal is null) { _mapsChangedSignalCallable = Callable.From(() => _mapsChangedSignal?.Invoke()); Connect(GDExtensionSignalName.MapsChanged, _mapsChangedSignalCallable); } _mapsChangedSignal += value; } remove { _mapsChangedSignal -= value; if (_mapsChangedSignal is not null) return; Disconnect(GDExtensionSignalName.MapsChanged, _mapsChangedSignalCallable); _mapsChangedSignalCallable = default; } } public new delegate void RegionMapChangedSignalHandler(); private RegionMapChangedSignalHandler _regionMapChangedSignal; private Callable _regionMapChangedSignalCallable; public event RegionMapChangedSignalHandler RegionMapChangedSignal { add { if (_regionMapChangedSignal is null) { _regionMapChangedSignalCallable = Callable.From(() => _regionMapChangedSignal?.Invoke()); Connect(GDExtensionSignalName.RegionMapChanged, _regionMapChangedSignalCallable); } _regionMapChangedSignal += value; } remove { _regionMapChangedSignal -= value; if (_regionMapChangedSignal is not null) return; Disconnect(GDExtensionSignalName.RegionMapChanged, _regionMapChangedSignalCallable); _regionMapChangedSignalCallable = default; } } public new delegate void HeightMapsChangedSignalHandler(); private HeightMapsChangedSignalHandler _heightMapsChangedSignal; private Callable _heightMapsChangedSignalCallable; public event HeightMapsChangedSignalHandler HeightMapsChangedSignal { add { if (_heightMapsChangedSignal is null) { _heightMapsChangedSignalCallable = Callable.From(() => _heightMapsChangedSignal?.Invoke()); Connect(GDExtensionSignalName.HeightMapsChanged, _heightMapsChangedSignalCallable); } _heightMapsChangedSignal += value; } remove { _heightMapsChangedSignal -= value; if (_heightMapsChangedSignal is not null) return; Disconnect(GDExtensionSignalName.HeightMapsChanged, _heightMapsChangedSignalCallable); _heightMapsChangedSignalCallable = default; } } public new delegate void ControlMapsChangedSignalHandler(); private ControlMapsChangedSignalHandler _controlMapsChangedSignal; private Callable _controlMapsChangedSignalCallable; public event ControlMapsChangedSignalHandler ControlMapsChangedSignal { add { if (_controlMapsChangedSignal is null) { _controlMapsChangedSignalCallable = Callable.From(() => _controlMapsChangedSignal?.Invoke()); Connect(GDExtensionSignalName.ControlMapsChanged, _controlMapsChangedSignalCallable); } _controlMapsChangedSignal += value; } remove { _controlMapsChangedSignal -= value; if (_controlMapsChangedSignal is not null) return; Disconnect(GDExtensionSignalName.ControlMapsChanged, _controlMapsChangedSignalCallable); _controlMapsChangedSignalCallable = default; } } public new delegate void ColorMapsChangedSignalHandler(); private ColorMapsChangedSignalHandler _colorMapsChangedSignal; private Callable _colorMapsChangedSignalCallable; public event ColorMapsChangedSignalHandler ColorMapsChangedSignal { add { if (_colorMapsChangedSignal is null) { _colorMapsChangedSignalCallable = Callable.From(() => _colorMapsChangedSignal?.Invoke()); Connect(GDExtensionSignalName.ColorMapsChanged, _colorMapsChangedSignalCallable); } _colorMapsChangedSignal += value; } remove { _colorMapsChangedSignal -= value; if (_colorMapsChangedSignal is not null) return; Disconnect(GDExtensionSignalName.ColorMapsChanged, _colorMapsChangedSignalCallable); _colorMapsChangedSignalCallable = default; } } public new delegate void MapsEditedSignalHandler(Aabb editedArea); private MapsEditedSignalHandler _mapsEditedSignal; private Callable _mapsEditedSignalCallable; public event MapsEditedSignalHandler MapsEditedSignal { add { if (_mapsEditedSignal is null) { _mapsEditedSignalCallable = Callable.From((Variant editedArea) => _mapsEditedSignal?.Invoke(editedArea.As())); Connect(GDExtensionSignalName.MapsEdited, _mapsEditedSignalCallable); } _mapsEditedSignal += value; } remove { _mapsEditedSignal -= value; if (_mapsEditedSignal is not null) return; Disconnect(GDExtensionSignalName.MapsEdited, _mapsEditedSignalCallable); _mapsEditedSignalCallable = default; } } public new static class GDExtensionPropertyName { public new static readonly StringName RegionLocations = "region_locations"; public new static readonly StringName HeightMaps = "height_maps"; public new static readonly StringName ControlMaps = "control_maps"; public new static readonly StringName ColorMaps = "color_maps"; } public new Godot.Collections.Array RegionLocations { get => Get(GDExtensionPropertyName.RegionLocations).As(); set => Set(GDExtensionPropertyName.RegionLocations, value); } public new Godot.Collections.Array HeightMaps { get => Get(GDExtensionPropertyName.HeightMaps).As(); } public new Godot.Collections.Array ControlMaps { get => Get(GDExtensionPropertyName.ControlMaps).As(); } public new Godot.Collections.Array ColorMaps { get => Get(GDExtensionPropertyName.ColorMaps).As(); } public new static class GDExtensionMethodName { public new static readonly StringName GetRegionCount = "get_region_count"; public new static readonly StringName GetRegionsActive = "get_regions_active"; public new static readonly StringName GetRegionsAll = "get_regions_all"; public new static readonly StringName GetRegionMap = "get_region_map"; public new static readonly StringName GetRegionMapIndex = "get_region_map_index"; public new static readonly StringName DoForRegions = "do_for_regions"; public new static readonly StringName ChangeRegionSize = "change_region_size"; public new static readonly StringName GetRegionLocation = "get_region_location"; public new static readonly StringName GetRegionId = "get_region_id"; public new static readonly StringName GetRegionIdp = "get_region_idp"; public new static readonly StringName HasRegion = "has_region"; public new static readonly StringName HasRegionp = "has_regionp"; public new static readonly StringName GetRegion = "get_region"; public new static readonly StringName GetRegionp = "get_regionp"; public new static readonly StringName SetRegionModified = "set_region_modified"; public new static readonly StringName IsRegionModified = "is_region_modified"; public new static readonly StringName SetRegionDeleted = "set_region_deleted"; public new static readonly StringName IsRegionDeleted = "is_region_deleted"; public new static readonly StringName AddRegionBlankp = "add_region_blankp"; public new static readonly StringName AddRegionBlank = "add_region_blank"; public new static readonly StringName AddRegion = "add_region"; public new static readonly StringName RemoveRegionp = "remove_regionp"; public new static readonly StringName RemoveRegionl = "remove_regionl"; public new static readonly StringName RemoveRegion = "remove_region"; public new static readonly StringName SaveDirectory = "save_directory"; public new static readonly StringName SaveRegion = "save_region"; public new static readonly StringName LoadDirectory = "load_directory"; public new static readonly StringName LoadRegion = "load_region"; public new static readonly StringName GetMaps = "get_maps"; public new static readonly StringName UpdateMaps = "update_maps"; public new static readonly StringName GetHeightMapsRid = "get_height_maps_rid"; public new static readonly StringName GetControlMapsRid = "get_control_maps_rid"; public new static readonly StringName GetColorMapsRid = "get_color_maps_rid"; public new static readonly StringName SetPixel = "set_pixel"; public new static readonly StringName GetPixel = "get_pixel"; public new static readonly StringName SetHeight = "set_height"; public new static readonly StringName GetHeight = "get_height"; public new static readonly StringName SetColor = "set_color"; public new static readonly StringName GetColor = "get_color"; public new static readonly StringName SetControl = "set_control"; public new static readonly StringName GetControl = "get_control"; public new static readonly StringName SetRoughness = "set_roughness"; public new static readonly StringName GetRoughness = "get_roughness"; public new static readonly StringName SetControlBaseId = "set_control_base_id"; public new static readonly StringName GetControlBaseId = "get_control_base_id"; public new static readonly StringName SetControlOverlayId = "set_control_overlay_id"; public new static readonly StringName GetControlOverlayId = "get_control_overlay_id"; public new static readonly StringName SetControlBlend = "set_control_blend"; public new static readonly StringName GetControlBlend = "get_control_blend"; public new static readonly StringName SetControlAngle = "set_control_angle"; public new static readonly StringName GetControlAngle = "get_control_angle"; public new static readonly StringName SetControlScale = "set_control_scale"; public new static readonly StringName GetControlScale = "get_control_scale"; public new static readonly StringName SetControlHole = "set_control_hole"; public new static readonly StringName GetControlHole = "get_control_hole"; public new static readonly StringName SetControlNavigation = "set_control_navigation"; public new static readonly StringName GetControlNavigation = "get_control_navigation"; public new static readonly StringName SetControlAuto = "set_control_auto"; public new static readonly StringName GetControlAuto = "get_control_auto"; public new static readonly StringName GetNormal = "get_normal"; public new static readonly StringName IsInSlope = "is_in_slope"; public new static readonly StringName GetTextureId = "get_texture_id"; public new static readonly StringName GetMeshVertex = "get_mesh_vertex"; public new static readonly StringName GetHeightRange = "get_height_range"; public new static readonly StringName CalcHeightRange = "calc_height_range"; public new static readonly StringName ImportImages = "import_images"; public new static readonly StringName ExportImage = "export_image"; public new static readonly StringName LayeredToImage = "layered_to_image"; public new static readonly StringName Dump = "dump"; } public new long GetRegionCount() => Call(GDExtensionMethodName.GetRegionCount, []).As(); public new Godot.Collections.Array GetRegionsActive(bool copy = false, bool deep = false) => Call(GDExtensionMethodName.GetRegionsActive, [copy, deep]).As(); public new Godot.Collections.Dictionary GetRegionsAll() => Call(GDExtensionMethodName.GetRegionsAll, []).As(); public new int[] GetRegionMap() => Call(GDExtensionMethodName.GetRegionMap, []).As(); public new static long GetRegionMapIndex(Vector2I regionLocation) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetRegionMapIndex, [regionLocation]).As(); public new void DoForRegions(Rect2I area, Callable callback) => Call(GDExtensionMethodName.DoForRegions, [area, callback]); public new void ChangeRegionSize(long regionSize) => Call(GDExtensionMethodName.ChangeRegionSize, [regionSize]); public new Vector2I GetRegionLocation(Vector3 globalPosition) => Call(GDExtensionMethodName.GetRegionLocation, [globalPosition]).As(); public new long GetRegionId(Vector2I regionLocation) => Call(GDExtensionMethodName.GetRegionId, [regionLocation]).As(); public new long GetRegionIdp(Vector3 globalPosition) => Call(GDExtensionMethodName.GetRegionIdp, [globalPosition]).As(); public new bool HasRegion(Vector2I regionLocation) => Call(GDExtensionMethodName.HasRegion, [regionLocation]).As(); public new bool HasRegionp(Vector3 globalPosition) => Call(GDExtensionMethodName.HasRegionp, [globalPosition]).As(); public new Terrain3DRegion GetRegion(Vector2I regionLocation) => Terrain3DRegion.Bind(Call(GDExtensionMethodName.GetRegion, [regionLocation]).As()); public new Terrain3DRegion GetRegionp(Vector3 globalPosition) => Terrain3DRegion.Bind(Call(GDExtensionMethodName.GetRegionp, [globalPosition]).As()); public new void SetRegionModified(Vector2I regionLocation, bool modified) => Call(GDExtensionMethodName.SetRegionModified, [regionLocation, modified]); public new bool IsRegionModified(Vector2I regionLocation) => Call(GDExtensionMethodName.IsRegionModified, [regionLocation]).As(); public new void SetRegionDeleted(Vector2I regionLocation, bool deleted) => Call(GDExtensionMethodName.SetRegionDeleted, [regionLocation, deleted]); public new bool IsRegionDeleted(Vector2I regionLocation) => Call(GDExtensionMethodName.IsRegionDeleted, [regionLocation]).As(); public new Terrain3DRegion AddRegionBlankp(Vector3 globalPosition, bool update = true) => Terrain3DRegion.Bind(Call(GDExtensionMethodName.AddRegionBlankp, [globalPosition, update]).As()); public new Terrain3DRegion AddRegionBlank(Vector2I regionLocation, bool update = true) => Terrain3DRegion.Bind(Call(GDExtensionMethodName.AddRegionBlank, [regionLocation, update]).As()); public new Error AddRegion(Terrain3DRegion region, bool update = true) => Call(GDExtensionMethodName.AddRegion, [region, update]).As(); public new void RemoveRegionp(Vector3 globalPosition, bool update = true) => Call(GDExtensionMethodName.RemoveRegionp, [globalPosition, update]); public new void RemoveRegionl(Vector2I regionLocation, bool update = true) => Call(GDExtensionMethodName.RemoveRegionl, [regionLocation, update]); public new void RemoveRegion(Terrain3DRegion region, bool update = true) => Call(GDExtensionMethodName.RemoveRegion, [region, update]); public new void SaveDirectory(string directory) => Call(GDExtensionMethodName.SaveDirectory, [directory]); public new void SaveRegion(Vector2I regionLocation, string directory, bool save16Bit = false) => Call(GDExtensionMethodName.SaveRegion, [regionLocation, directory, save16Bit]); public new void LoadDirectory(string directory) => Call(GDExtensionMethodName.LoadDirectory, [directory]); public new void LoadRegion(Vector2I regionLocation, string directory, bool update = true) => Call(GDExtensionMethodName.LoadRegion, [regionLocation, directory, update]); public new Godot.Collections.Array GetMaps(Terrain3DRegion.MapType mapType) => Call(GDExtensionMethodName.GetMaps, [Variant.From(mapType)]).As(); public new void UpdateMaps(Terrain3DRegion.MapType mapType = Terrain3DRegion.MapType.Max, bool allRegions = true, bool generateMipmaps = false) => Call(GDExtensionMethodName.UpdateMaps, [Variant.From(mapType), allRegions, generateMipmaps]); public new Rid GetHeightMapsRid() => Call(GDExtensionMethodName.GetHeightMapsRid, []).As(); public new Rid GetControlMapsRid() => Call(GDExtensionMethodName.GetControlMapsRid, []).As(); public new Rid GetColorMapsRid() => Call(GDExtensionMethodName.GetColorMapsRid, []).As(); public new void SetPixel(Terrain3DRegion.MapType mapType, Vector3 globalPosition, Color pixel) => Call(GDExtensionMethodName.SetPixel, [Variant.From(mapType), globalPosition, pixel]); public new Color GetPixel(Terrain3DRegion.MapType mapType, Vector3 globalPosition) => Call(GDExtensionMethodName.GetPixel, [Variant.From(mapType), globalPosition]).As(); public new void SetHeight(Vector3 globalPosition, double height) => Call(GDExtensionMethodName.SetHeight, [globalPosition, height]); public new double GetHeight(Vector3 globalPosition) => Call(GDExtensionMethodName.GetHeight, [globalPosition]).As(); public new void SetColor(Vector3 globalPosition, Color color) => Call(GDExtensionMethodName.SetColor, [globalPosition, color]); public new Color GetColor(Vector3 globalPosition) => Call(GDExtensionMethodName.GetColor, [globalPosition]).As(); public new void SetControl(Vector3 globalPosition, long control) => Call(GDExtensionMethodName.SetControl, [globalPosition, control]); public new long GetControl(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControl, [globalPosition]).As(); public new void SetRoughness(Vector3 globalPosition, double roughness) => Call(GDExtensionMethodName.SetRoughness, [globalPosition, roughness]); public new double GetRoughness(Vector3 globalPosition) => Call(GDExtensionMethodName.GetRoughness, [globalPosition]).As(); public new void SetControlBaseId(Vector3 globalPosition, long textureId) => Call(GDExtensionMethodName.SetControlBaseId, [globalPosition, textureId]); public new long GetControlBaseId(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlBaseId, [globalPosition]).As(); public new void SetControlOverlayId(Vector3 globalPosition, long textureId) => Call(GDExtensionMethodName.SetControlOverlayId, [globalPosition, textureId]); public new long GetControlOverlayId(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlOverlayId, [globalPosition]).As(); public new void SetControlBlend(Vector3 globalPosition, double blendValue) => Call(GDExtensionMethodName.SetControlBlend, [globalPosition, blendValue]); public new double GetControlBlend(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlBlend, [globalPosition]).As(); public new void SetControlAngle(Vector3 globalPosition, double degrees) => Call(GDExtensionMethodName.SetControlAngle, [globalPosition, degrees]); public new double GetControlAngle(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlAngle, [globalPosition]).As(); public new void SetControlScale(Vector3 globalPosition, double percentageModifier) => Call(GDExtensionMethodName.SetControlScale, [globalPosition, percentageModifier]); public new double GetControlScale(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlScale, [globalPosition]).As(); public new void SetControlHole(Vector3 globalPosition, bool enable) => Call(GDExtensionMethodName.SetControlHole, [globalPosition, enable]); public new bool GetControlHole(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlHole, [globalPosition]).As(); public new void SetControlNavigation(Vector3 globalPosition, bool enable) => Call(GDExtensionMethodName.SetControlNavigation, [globalPosition, enable]); public new bool GetControlNavigation(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlNavigation, [globalPosition]).As(); public new void SetControlAuto(Vector3 globalPosition, bool enable) => Call(GDExtensionMethodName.SetControlAuto, [globalPosition, enable]); public new bool GetControlAuto(Vector3 globalPosition) => Call(GDExtensionMethodName.GetControlAuto, [globalPosition]).As(); public new Vector3 GetNormal(Vector3 globalPosition) => Call(GDExtensionMethodName.GetNormal, [globalPosition]).As(); public new bool IsInSlope(Vector3 globalPosition, Vector2 slopeRange, Vector3 normal = default) => Call(GDExtensionMethodName.IsInSlope, [globalPosition, slopeRange, normal]).As(); public new Vector3 GetTextureId(Vector3 globalPosition) => Call(GDExtensionMethodName.GetTextureId, [globalPosition]).As(); public new Vector3 GetMeshVertex(long lod, Terrain3DData.HeightFilter filter, Vector3 globalPosition) => Call(GDExtensionMethodName.GetMeshVertex, [lod, Variant.From(filter), globalPosition]).As(); public new Vector2 GetHeightRange() => Call(GDExtensionMethodName.GetHeightRange, []).As(); public new void CalcHeightRange(bool recursive = false) => Call(GDExtensionMethodName.CalcHeightRange, [recursive]); public new void ImportImages(Godot.Collections.Array images, Vector3 globalPosition = default, double offset = 0, double scale = 1) => Call(GDExtensionMethodName.ImportImages, [images, globalPosition, offset, scale]); public new Error ExportImage(string fileName, Terrain3DRegion.MapType mapType) => Call(GDExtensionMethodName.ExportImage, [fileName, Variant.From(mapType)]).As(); public new Image LayeredToImage(Terrain3DRegion.MapType mapType) => Call(GDExtensionMethodName.LayeredToImage, [Variant.From(mapType)]).As(); public new void Dump(bool verbose = false) => Call(GDExtensionMethodName.Dump, [verbose]); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DData.cs.uid ================================================ uid://ct5j1v1bbb4x3 ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DEditor.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DEditor : GodotObject { private new static readonly StringName NativeName = new StringName("Terrain3DEditor"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DEditor object), please use the Instantiate() method instead.")] protected Terrain3DEditor() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DEditor Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DEditor wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DEditor); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DEditor).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DEditor)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DEditor" type. public new static Terrain3DEditor Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum Operation { Add = 0, Subtract = 1, Replace = 2, Average = 3, Gradient = 4, OpMax = 5, } public enum Tool { Sculpt = 1, Height = 2, Texture = 3, Color = 4, Roughness = 5, Angle = 10, Scale = 11, Autoshader = 6, Holes = 7, Navigation = 8, Instancer = 9, Region = 0, Max = 12, } public new static class GDExtensionMethodName { public new static readonly StringName SetTerrain = "set_terrain"; public new static readonly StringName GetTerrain = "get_terrain"; public new static readonly StringName SetBrushData = "set_brush_data"; public new static readonly StringName SetTool = "set_tool"; public new static readonly StringName GetTool = "get_tool"; public new static readonly StringName SetOperation = "set_operation"; public new static readonly StringName GetOperation = "get_operation"; public new static readonly StringName StartOperation = "start_operation"; public new static readonly StringName IsOperating = "is_operating"; public new static readonly StringName Operate = "operate"; public new static readonly StringName BackupRegion = "backup_region"; public new static readonly StringName StopOperation = "stop_operation"; public new static readonly StringName ApplyUndo = "apply_undo"; } public new void SetTerrain(Terrain3D terrain) => Call(GDExtensionMethodName.SetTerrain, [terrain]); public new Terrain3D GetTerrain() => Terrain3D.Bind(Call(GDExtensionMethodName.GetTerrain, []).As()); public new void SetBrushData(Godot.Collections.Dictionary data) => Call(GDExtensionMethodName.SetBrushData, [data]); public new void SetTool(Terrain3DEditor.Tool tool) => Call(GDExtensionMethodName.SetTool, [Variant.From(tool)]); public new Terrain3DEditor.Tool GetTool() => Call(GDExtensionMethodName.GetTool, []).As(); public new void SetOperation(Terrain3DEditor.Operation operation) => Call(GDExtensionMethodName.SetOperation, [Variant.From(operation)]); public new Terrain3DEditor.Operation GetOperation() => Call(GDExtensionMethodName.GetOperation, []).As(); public new void StartOperation(Vector3 position) => Call(GDExtensionMethodName.StartOperation, [position]); public new bool IsOperating() => Call(GDExtensionMethodName.IsOperating, []).As(); public new void Operate(Vector3 position, double cameraDirection) => Call(GDExtensionMethodName.Operate, [position, cameraDirection]); public new void BackupRegion(Terrain3DRegion region) => Call(GDExtensionMethodName.BackupRegion, [region]); public new void StopOperation() => Call(GDExtensionMethodName.StopOperation, []); public new void ApplyUndo(Godot.Collections.Dictionary data) => Call(GDExtensionMethodName.ApplyUndo, [data]); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DEditor.cs.uid ================================================ uid://bhdx3ry8edaq5 ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DInstancer.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DInstancer : GodotObject { private new static readonly StringName NativeName = new StringName("Terrain3DInstancer"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DInstancer object), please use the Instantiate() method instead.")] protected Terrain3DInstancer() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DInstancer Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DInstancer wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DInstancer); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DInstancer).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DInstancer)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DInstancer" type. public new static Terrain3DInstancer Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum InstancerMode { Normal = 1, Disabled = 0, } public new static class GDExtensionPropertyName { public new static readonly StringName Mode = "mode"; } public new Terrain3DInstancer.InstancerMode Mode { get => Get(GDExtensionPropertyName.Mode).As(); set => Set(GDExtensionPropertyName.Mode, Variant.From(value)); } public new static class GDExtensionMethodName { public new static readonly StringName ClearByMesh = "clear_by_mesh"; public new static readonly StringName ClearByLocation = "clear_by_location"; public new static readonly StringName ClearByRegion = "clear_by_region"; public new static readonly StringName IsEnabled = "is_enabled"; public new static readonly StringName AddInstances = "add_instances"; public new static readonly StringName RemoveInstances = "remove_instances"; public new static readonly StringName AddMultimesh = "add_multimesh"; public new static readonly StringName AddTransforms = "add_transforms"; public new static readonly StringName AppendLocation = "append_location"; public new static readonly StringName AppendRegion = "append_region"; public new static readonly StringName UpdateTransforms = "update_transforms"; public new static readonly StringName GetClosestMeshId = "get_closest_mesh_id"; public new static readonly StringName UpdateMmis = "update_mmis"; public new static readonly StringName SwapIds = "swap_ids"; } public new void ClearByMesh(long meshId) => Call(GDExtensionMethodName.ClearByMesh, [meshId]); public new void ClearByLocation(Vector2I regionLocation, long meshId) => Call(GDExtensionMethodName.ClearByLocation, [regionLocation, meshId]); public new void ClearByRegion(Terrain3DRegion region, long meshId) => Call(GDExtensionMethodName.ClearByRegion, [region, meshId]); public new bool IsEnabled() => Call(GDExtensionMethodName.IsEnabled, []).As(); public new void AddInstances(Vector3 globalPosition, Godot.Collections.Dictionary @params) => Call(GDExtensionMethodName.AddInstances, [globalPosition, @params]); public new void RemoveInstances(Vector3 globalPosition, Godot.Collections.Dictionary @params) => Call(GDExtensionMethodName.RemoveInstances, [globalPosition, @params]); public new void AddMultimesh(long meshId, MultiMesh multimesh, Transform3D transform = default, bool update = true) => Call(GDExtensionMethodName.AddMultimesh, [meshId, multimesh, transform, update]); public new void AddTransforms(long meshId, Godot.Collections.Array transforms, Color[] colors = default, bool update = true) => Call(GDExtensionMethodName.AddTransforms, [meshId, transforms, colors, update]); public new void AppendLocation(Vector2I regionLocation, long meshId, Godot.Collections.Array transforms, Color[] colors, bool update = true) => Call(GDExtensionMethodName.AppendLocation, [regionLocation, meshId, transforms, colors, update]); public new void AppendRegion(Terrain3DRegion region, long meshId, Godot.Collections.Array transforms, Color[] colors, bool update = true) => Call(GDExtensionMethodName.AppendRegion, [region, meshId, transforms, colors, update]); public new void UpdateTransforms(Aabb aabb) => Call(GDExtensionMethodName.UpdateTransforms, [aabb]); public new long GetClosestMeshId(Vector3 globalPosition) => Call(GDExtensionMethodName.GetClosestMeshId, [globalPosition]).As(); public new void UpdateMmis(long meshId = -1, Vector2I regionLocation = default, bool rebuildAll = false) => Call(GDExtensionMethodName.UpdateMmis, [meshId, regionLocation, rebuildAll]); public new void SwapIds(long srcId, long destId) => Call(GDExtensionMethodName.SwapIds, [srcId, destId]); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DInstancer.cs.uid ================================================ uid://v155pladupha ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DMaterial.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DMaterial : Resource { private new static readonly StringName NativeName = new StringName("Terrain3DMaterial"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DMaterial object), please use the Instantiate() method instead.")] protected Terrain3DMaterial() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DMaterial Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DMaterial wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DMaterial); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DMaterial).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DMaterial)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DMaterial" type. public new static Terrain3DMaterial Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum WorldBackgroundEnum { None = 0, Flat = 1, Noise = 2, } public enum TextureFilteringEnum { Linear = 0, Nearest = 1, } public enum UpdateFlags { UniformsOnly = 0, TextureArrays = 1, RegionArrays = 2, Arrays = 3, FullRebuild = 7, } public new static class GDExtensionPropertyName { public new static readonly StringName WorldBackground = "world_background"; public new static readonly StringName TextureFiltering = "texture_filtering"; public new static readonly StringName AutoShaderEnabled = "auto_shader_enabled"; public new static readonly StringName DualScalingEnabled = "dual_scaling_enabled"; public new static readonly StringName MacroVariationEnabled = "macro_variation_enabled"; public new static readonly StringName ProjectionEnabled = "projection_enabled"; public new static readonly StringName OutputAlbedo = "output_albedo"; public new static readonly StringName OutputRoughness = "output_roughness"; public new static readonly StringName OutputNormalMap = "output_normal_map"; public new static readonly StringName OutputAmbientOcclusion = "output_ambient_occlusion"; public new static readonly StringName ShaderOverrideEnabled = "shader_override_enabled"; public new static readonly StringName ShaderOverride = "shader_override"; public new static readonly StringName ShowRegionGrid = "show_region_grid"; public new static readonly StringName ShowInstancerGrid = "show_instancer_grid"; public new static readonly StringName ShowVertexGrid = "show_vertex_grid"; public new static readonly StringName ShowContours = "show_contours"; public new static readonly StringName ShowNavigation = "show_navigation"; public new static readonly StringName DisplacementScale = "displacement_scale"; public new static readonly StringName DisplacementSharpness = "displacement_sharpness"; public new static readonly StringName BufferShaderOverrideEnabled = "buffer_shader_override_enabled"; public new static readonly StringName BufferShaderOverride = "buffer_shader_override"; public new static readonly StringName ShowCheckered = "show_checkered"; public new static readonly StringName ShowGrey = "show_grey"; public new static readonly StringName ShowHeightmap = "show_heightmap"; public new static readonly StringName ShowJaggedness = "show_jaggedness"; public new static readonly StringName ShowAutoshader = "show_autoshader"; public new static readonly StringName ShowControlTexture = "show_control_texture"; public new static readonly StringName ShowControlBlend = "show_control_blend"; public new static readonly StringName ShowControlAngle = "show_control_angle"; public new static readonly StringName ShowControlScale = "show_control_scale"; public new static readonly StringName ShowColormap = "show_colormap"; public new static readonly StringName ShowRoughmap = "show_roughmap"; public new static readonly StringName ShowTextureAlbedo = "show_texture_albedo"; public new static readonly StringName ShowTextureHeight = "show_texture_height"; public new static readonly StringName ShowTextureNormal = "show_texture_normal"; public new static readonly StringName ShowTextureAo = "show_texture_ao"; public new static readonly StringName ShowTextureRough = "show_texture_rough"; public new static readonly StringName ShowDisplacementBuffer = "show_displacement_buffer"; } public new Terrain3DMaterial.WorldBackgroundEnum WorldBackground { get => Get(GDExtensionPropertyName.WorldBackground).As(); set => Set(GDExtensionPropertyName.WorldBackground, Variant.From(value)); } public new long TextureFiltering { get => Get(GDExtensionPropertyName.TextureFiltering).As(); set => Set(GDExtensionPropertyName.TextureFiltering, value); } public new bool AutoShaderEnabled { get => Get(GDExtensionPropertyName.AutoShaderEnabled).As(); set => Set(GDExtensionPropertyName.AutoShaderEnabled, value); } public new bool DualScalingEnabled { get => Get(GDExtensionPropertyName.DualScalingEnabled).As(); set => Set(GDExtensionPropertyName.DualScalingEnabled, value); } public new bool MacroVariationEnabled { get => Get(GDExtensionPropertyName.MacroVariationEnabled).As(); set => Set(GDExtensionPropertyName.MacroVariationEnabled, value); } public new bool ProjectionEnabled { get => Get(GDExtensionPropertyName.ProjectionEnabled).As(); set => Set(GDExtensionPropertyName.ProjectionEnabled, value); } public new bool OutputAlbedo { get => Get(GDExtensionPropertyName.OutputAlbedo).As(); set => Set(GDExtensionPropertyName.OutputAlbedo, value); } public new bool OutputRoughness { get => Get(GDExtensionPropertyName.OutputRoughness).As(); set => Set(GDExtensionPropertyName.OutputRoughness, value); } public new bool OutputNormalMap { get => Get(GDExtensionPropertyName.OutputNormalMap).As(); set => Set(GDExtensionPropertyName.OutputNormalMap, value); } public new bool OutputAmbientOcclusion { get => Get(GDExtensionPropertyName.OutputAmbientOcclusion).As(); set => Set(GDExtensionPropertyName.OutputAmbientOcclusion, value); } public new bool ShaderOverrideEnabled { get => Get(GDExtensionPropertyName.ShaderOverrideEnabled).As(); set => Set(GDExtensionPropertyName.ShaderOverrideEnabled, value); } public new Shader ShaderOverride { get => Get(GDExtensionPropertyName.ShaderOverride).As(); set => Set(GDExtensionPropertyName.ShaderOverride, value); } public new bool ShowRegionGrid { get => Get(GDExtensionPropertyName.ShowRegionGrid).As(); set => Set(GDExtensionPropertyName.ShowRegionGrid, value); } public new bool ShowInstancerGrid { get => Get(GDExtensionPropertyName.ShowInstancerGrid).As(); set => Set(GDExtensionPropertyName.ShowInstancerGrid, value); } public new bool ShowVertexGrid { get => Get(GDExtensionPropertyName.ShowVertexGrid).As(); set => Set(GDExtensionPropertyName.ShowVertexGrid, value); } public new bool ShowContours { get => Get(GDExtensionPropertyName.ShowContours).As(); set => Set(GDExtensionPropertyName.ShowContours, value); } public new bool ShowNavigation { get => Get(GDExtensionPropertyName.ShowNavigation).As(); set => Set(GDExtensionPropertyName.ShowNavigation, value); } public new double DisplacementScale { get => Get(GDExtensionPropertyName.DisplacementScale).As(); set => Set(GDExtensionPropertyName.DisplacementScale, value); } public new double DisplacementSharpness { get => Get(GDExtensionPropertyName.DisplacementSharpness).As(); set => Set(GDExtensionPropertyName.DisplacementSharpness, value); } public new bool BufferShaderOverrideEnabled { get => Get(GDExtensionPropertyName.BufferShaderOverrideEnabled).As(); set => Set(GDExtensionPropertyName.BufferShaderOverrideEnabled, value); } public new Variant BufferShaderOverride { get => Get(GDExtensionPropertyName.BufferShaderOverride).As(); set => Set(GDExtensionPropertyName.BufferShaderOverride, value); } public new bool ShowCheckered { get => Get(GDExtensionPropertyName.ShowCheckered).As(); set => Set(GDExtensionPropertyName.ShowCheckered, value); } public new bool ShowGrey { get => Get(GDExtensionPropertyName.ShowGrey).As(); set => Set(GDExtensionPropertyName.ShowGrey, value); } public new bool ShowHeightmap { get => Get(GDExtensionPropertyName.ShowHeightmap).As(); set => Set(GDExtensionPropertyName.ShowHeightmap, value); } public new bool ShowJaggedness { get => Get(GDExtensionPropertyName.ShowJaggedness).As(); set => Set(GDExtensionPropertyName.ShowJaggedness, value); } public new bool ShowAutoshader { get => Get(GDExtensionPropertyName.ShowAutoshader).As(); set => Set(GDExtensionPropertyName.ShowAutoshader, value); } public new bool ShowControlTexture { get => Get(GDExtensionPropertyName.ShowControlTexture).As(); set => Set(GDExtensionPropertyName.ShowControlTexture, value); } public new bool ShowControlBlend { get => Get(GDExtensionPropertyName.ShowControlBlend).As(); set => Set(GDExtensionPropertyName.ShowControlBlend, value); } public new bool ShowControlAngle { get => Get(GDExtensionPropertyName.ShowControlAngle).As(); set => Set(GDExtensionPropertyName.ShowControlAngle, value); } public new bool ShowControlScale { get => Get(GDExtensionPropertyName.ShowControlScale).As(); set => Set(GDExtensionPropertyName.ShowControlScale, value); } public new bool ShowColormap { get => Get(GDExtensionPropertyName.ShowColormap).As(); set => Set(GDExtensionPropertyName.ShowColormap, value); } public new bool ShowRoughmap { get => Get(GDExtensionPropertyName.ShowRoughmap).As(); set => Set(GDExtensionPropertyName.ShowRoughmap, value); } public new bool ShowTextureAlbedo { get => Get(GDExtensionPropertyName.ShowTextureAlbedo).As(); set => Set(GDExtensionPropertyName.ShowTextureAlbedo, value); } public new bool ShowTextureHeight { get => Get(GDExtensionPropertyName.ShowTextureHeight).As(); set => Set(GDExtensionPropertyName.ShowTextureHeight, value); } public new bool ShowTextureNormal { get => Get(GDExtensionPropertyName.ShowTextureNormal).As(); set => Set(GDExtensionPropertyName.ShowTextureNormal, value); } public new bool ShowTextureAo { get => Get(GDExtensionPropertyName.ShowTextureAo).As(); set => Set(GDExtensionPropertyName.ShowTextureAo, value); } public new bool ShowTextureRough { get => Get(GDExtensionPropertyName.ShowTextureRough).As(); set => Set(GDExtensionPropertyName.ShowTextureRough, value); } public new bool ShowDisplacementBuffer { get => Get(GDExtensionPropertyName.ShowDisplacementBuffer).As(); set => Set(GDExtensionPropertyName.ShowDisplacementBuffer, value); } public new static class GDExtensionMethodName { public new static readonly StringName Update = "update"; public new static readonly StringName GetMaterialRid = "get_material_rid"; public new static readonly StringName GetShaderRid = "get_shader_rid"; public new static readonly StringName GetBufferMaterialRid = "get_buffer_material_rid"; public new static readonly StringName GetBufferShaderRid = "get_buffer_shader_rid"; public new static readonly StringName SetShaderParam = "set_shader_param"; public new static readonly StringName GetShaderParam = "get_shader_param"; public new static readonly StringName Save = "save"; } public new void Update(long flags = 0) => Call(GDExtensionMethodName.Update, [flags]); public new Rid GetMaterialRid() => Call(GDExtensionMethodName.GetMaterialRid, []).As(); public new Rid GetShaderRid() => Call(GDExtensionMethodName.GetShaderRid, []).As(); public new Rid GetBufferMaterialRid() => Call(GDExtensionMethodName.GetBufferMaterialRid, []).As(); public new Rid GetBufferShaderRid() => Call(GDExtensionMethodName.GetBufferShaderRid, []).As(); public new void SetShaderParam(StringName name, Variant value) => Call(GDExtensionMethodName.SetShaderParam, [name, value]); public new void GetShaderParam(StringName name) => Call(GDExtensionMethodName.GetShaderParam, [name]); public new Error Save(string path = "") => Call(GDExtensionMethodName.Save, [path]).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DMaterial.cs.uid ================================================ uid://cq4wvyd0gl2do ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DMeshAsset.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DMeshAsset : Resource { private new static readonly StringName NativeName = new StringName("Terrain3DMeshAsset"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DMeshAsset object), please use the Instantiate() method instead.")] protected Terrain3DMeshAsset() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DMeshAsset Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DMeshAsset wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DMeshAsset); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DMeshAsset).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DMeshAsset)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DMeshAsset" type. public new static Terrain3DMeshAsset Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum GenType { None = 0, TextureCard = 1, Max = 2, } public new static class GDExtensionSignalName { public new static readonly StringName IdChanged = "id_changed"; public new static readonly StringName SettingChanged = "setting_changed"; public new static readonly StringName InstancerSettingChanged = "instancer_setting_changed"; public new static readonly StringName InstanceCountChanged = "instance_count_changed"; } public new delegate void IdChangedSignalHandler(); private IdChangedSignalHandler _idChangedSignal; private Callable _idChangedSignalCallable; public event IdChangedSignalHandler IdChangedSignal { add { if (_idChangedSignal is null) { _idChangedSignalCallable = Callable.From(() => _idChangedSignal?.Invoke()); Connect(GDExtensionSignalName.IdChanged, _idChangedSignalCallable); } _idChangedSignal += value; } remove { _idChangedSignal -= value; if (_idChangedSignal is not null) return; Disconnect(GDExtensionSignalName.IdChanged, _idChangedSignalCallable); _idChangedSignalCallable = default; } } public new delegate void SettingChangedSignalHandler(); private SettingChangedSignalHandler _settingChangedSignal; private Callable _settingChangedSignalCallable; public event SettingChangedSignalHandler SettingChangedSignal { add { if (_settingChangedSignal is null) { _settingChangedSignalCallable = Callable.From(() => _settingChangedSignal?.Invoke()); Connect(GDExtensionSignalName.SettingChanged, _settingChangedSignalCallable); } _settingChangedSignal += value; } remove { _settingChangedSignal -= value; if (_settingChangedSignal is not null) return; Disconnect(GDExtensionSignalName.SettingChanged, _settingChangedSignalCallable); _settingChangedSignalCallable = default; } } public new delegate void InstancerSettingChangedSignalHandler(); private InstancerSettingChangedSignalHandler _instancerSettingChangedSignal; private Callable _instancerSettingChangedSignalCallable; public event InstancerSettingChangedSignalHandler InstancerSettingChangedSignal { add { if (_instancerSettingChangedSignal is null) { _instancerSettingChangedSignalCallable = Callable.From(() => _instancerSettingChangedSignal?.Invoke()); Connect(GDExtensionSignalName.InstancerSettingChanged, _instancerSettingChangedSignalCallable); } _instancerSettingChangedSignal += value; } remove { _instancerSettingChangedSignal -= value; if (_instancerSettingChangedSignal is not null) return; Disconnect(GDExtensionSignalName.InstancerSettingChanged, _instancerSettingChangedSignalCallable); _instancerSettingChangedSignalCallable = default; } } public new delegate void InstanceCountChangedSignalHandler(); private InstanceCountChangedSignalHandler _instanceCountChangedSignal; private Callable _instanceCountChangedSignalCallable; public event InstanceCountChangedSignalHandler InstanceCountChangedSignal { add { if (_instanceCountChangedSignal is null) { _instanceCountChangedSignalCallable = Callable.From(() => _instanceCountChangedSignal?.Invoke()); Connect(GDExtensionSignalName.InstanceCountChanged, _instanceCountChangedSignalCallable); } _instanceCountChangedSignal += value; } remove { _instanceCountChangedSignal -= value; if (_instanceCountChangedSignal is not null) return; Disconnect(GDExtensionSignalName.InstanceCountChanged, _instanceCountChangedSignalCallable); _instanceCountChangedSignalCallable = default; } } public new static class GDExtensionPropertyName { public new static readonly StringName Name = "name"; public new static readonly StringName Id = "id"; public new static readonly StringName Enabled = "enabled"; public new static readonly StringName SceneFile = "scene_file"; public new static readonly StringName GeneratedType = "generated_type"; public new static readonly StringName HeightOffset = "height_offset"; public new static readonly StringName Density = "density"; public new static readonly StringName CastShadows = "cast_shadows"; public new static readonly StringName VisibilityLayers = "visibility_layers"; public new static readonly StringName MaterialOverride = "material_override"; public new static readonly StringName MaterialOverlay = "material_overlay"; public new static readonly StringName GeneratedFaces = "generated_faces"; public new static readonly StringName GeneratedSize = "generated_size"; public new static readonly StringName LodCount = "lod_count"; public new static readonly StringName LastLod = "last_lod"; public new static readonly StringName LastShadowLod = "last_shadow_lod"; public new static readonly StringName ShadowImpostor = "shadow_impostor"; public new static readonly StringName Lod0Range = "lod0_range"; public new static readonly StringName Lod1Range = "lod1_range"; public new static readonly StringName Lod2Range = "lod2_range"; public new static readonly StringName Lod3Range = "lod3_range"; public new static readonly StringName Lod4Range = "lod4_range"; public new static readonly StringName Lod5Range = "lod5_range"; public new static readonly StringName Lod6Range = "lod6_range"; public new static readonly StringName Lod7Range = "lod7_range"; public new static readonly StringName Lod8Range = "lod8_range"; public new static readonly StringName Lod9Range = "lod9_range"; public new static readonly StringName FadeMargin = "fade_margin"; } public new string Name { get => Get(GDExtensionPropertyName.Name).As(); set => Set(GDExtensionPropertyName.Name, value); } public new long Id { get => Get(GDExtensionPropertyName.Id).As(); set => Set(GDExtensionPropertyName.Id, value); } public new bool Enabled { get => Get(GDExtensionPropertyName.Enabled).As(); set => Set(GDExtensionPropertyName.Enabled, value); } public new PackedScene SceneFile { get => Get(GDExtensionPropertyName.SceneFile).As(); set => Set(GDExtensionPropertyName.SceneFile, value); } public new Variant GeneratedType { get => Get(GDExtensionPropertyName.GeneratedType).As(); set => Set(GDExtensionPropertyName.GeneratedType, value); } public new double HeightOffset { get => Get(GDExtensionPropertyName.HeightOffset).As(); set => Set(GDExtensionPropertyName.HeightOffset, value); } public new double Density { get => Get(GDExtensionPropertyName.Density).As(); set => Set(GDExtensionPropertyName.Density, value); } public new long CastShadows { get => Get(GDExtensionPropertyName.CastShadows).As(); set => Set(GDExtensionPropertyName.CastShadows, value); } public new long VisibilityLayers { get => Get(GDExtensionPropertyName.VisibilityLayers).As(); set => Set(GDExtensionPropertyName.VisibilityLayers, value); } public new Material MaterialOverride { get => Get(GDExtensionPropertyName.MaterialOverride).As(); set => Set(GDExtensionPropertyName.MaterialOverride, value); } public new Material MaterialOverlay { get => Get(GDExtensionPropertyName.MaterialOverlay).As(); set => Set(GDExtensionPropertyName.MaterialOverlay, value); } public new long GeneratedFaces { get => Get(GDExtensionPropertyName.GeneratedFaces).As(); set => Set(GDExtensionPropertyName.GeneratedFaces, value); } public new Vector2 GeneratedSize { get => Get(GDExtensionPropertyName.GeneratedSize).As(); set => Set(GDExtensionPropertyName.GeneratedSize, value); } public new long LodCount { get => Get(GDExtensionPropertyName.LodCount).As(); } public new long LastLod { get => Get(GDExtensionPropertyName.LastLod).As(); set => Set(GDExtensionPropertyName.LastLod, value); } public new long LastShadowLod { get => Get(GDExtensionPropertyName.LastShadowLod).As(); set => Set(GDExtensionPropertyName.LastShadowLod, value); } public new long ShadowImpostor { get => Get(GDExtensionPropertyName.ShadowImpostor).As(); set => Set(GDExtensionPropertyName.ShadowImpostor, value); } public new double Lod0Range { get => Get(GDExtensionPropertyName.Lod0Range).As(); set => Set(GDExtensionPropertyName.Lod0Range, value); } public new double Lod1Range { get => Get(GDExtensionPropertyName.Lod1Range).As(); set => Set(GDExtensionPropertyName.Lod1Range, value); } public new double Lod2Range { get => Get(GDExtensionPropertyName.Lod2Range).As(); set => Set(GDExtensionPropertyName.Lod2Range, value); } public new double Lod3Range { get => Get(GDExtensionPropertyName.Lod3Range).As(); set => Set(GDExtensionPropertyName.Lod3Range, value); } public new double Lod4Range { get => Get(GDExtensionPropertyName.Lod4Range).As(); set => Set(GDExtensionPropertyName.Lod4Range, value); } public new double Lod5Range { get => Get(GDExtensionPropertyName.Lod5Range).As(); set => Set(GDExtensionPropertyName.Lod5Range, value); } public new double Lod6Range { get => Get(GDExtensionPropertyName.Lod6Range).As(); set => Set(GDExtensionPropertyName.Lod6Range, value); } public new double Lod7Range { get => Get(GDExtensionPropertyName.Lod7Range).As(); set => Set(GDExtensionPropertyName.Lod7Range, value); } public new double Lod8Range { get => Get(GDExtensionPropertyName.Lod8Range).As(); set => Set(GDExtensionPropertyName.Lod8Range, value); } public new double Lod9Range { get => Get(GDExtensionPropertyName.Lod9Range).As(); set => Set(GDExtensionPropertyName.Lod9Range, value); } public new double FadeMargin { get => Get(GDExtensionPropertyName.FadeMargin).As(); set => Set(GDExtensionPropertyName.FadeMargin, value); } public new static class GDExtensionMethodName { public new static readonly StringName Clear = "clear"; public new static readonly StringName SetHighlighted = "set_highlighted"; public new static readonly StringName IsHighlighted = "is_highlighted"; public new static readonly StringName GetHighlightColor = "get_highlight_color"; public new static readonly StringName GetThumbnail = "get_thumbnail"; public new static readonly StringName GetMesh = "get_mesh"; public new static readonly StringName SetLodRange = "set_lod_range"; public new static readonly StringName GetLodRange = "get_lod_range"; public new static readonly StringName GetInstanceCount = "get_instance_count"; } public new void Clear() => Call(GDExtensionMethodName.Clear, []); public new void SetHighlighted(bool enabled) => Call(GDExtensionMethodName.SetHighlighted, [enabled]); public new bool IsHighlighted() => Call(GDExtensionMethodName.IsHighlighted, []).As(); public new Color GetHighlightColor() => Call(GDExtensionMethodName.GetHighlightColor, []).As(); public new Texture2D GetThumbnail() => Call(GDExtensionMethodName.GetThumbnail, []).As(); public new Mesh GetMesh(long lod = 0) => Call(GDExtensionMethodName.GetMesh, [lod]).As(); public new void SetLodRange(long lod, double distance) => Call(GDExtensionMethodName.SetLodRange, [lod, distance]); public new double GetLodRange(long lod) => Call(GDExtensionMethodName.GetLodRange, [lod]).As(); public new long GetInstanceCount() => Call(GDExtensionMethodName.GetInstanceCount, []).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DMeshAsset.cs.uid ================================================ uid://c1mg74hkhl5m2 ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DRegion.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DRegion : Resource { private new static readonly StringName NativeName = new StringName("Terrain3DRegion"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DRegion object), please use the Instantiate() method instead.")] protected Terrain3DRegion() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DRegion Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DRegion wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DRegion); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DRegion).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DRegion)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DRegion" type. public new static Terrain3DRegion Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public enum MapType { Height = 0, Control = 1, Color = 2, Max = 3, } public new static class GDExtensionPropertyName { public new static readonly StringName Version = "version"; public new static readonly StringName RegionSize = "region_size"; public new static readonly StringName VertexSpacing = "vertex_spacing"; public new static readonly StringName HeightRange = "height_range"; public new static readonly StringName HeightMap = "height_map"; public new static readonly StringName ControlMap = "control_map"; public new static readonly StringName ColorMap = "color_map"; public new static readonly StringName Instances = "instances"; public new static readonly StringName Edited = "edited"; public new static readonly StringName Deleted = "deleted"; public new static readonly StringName Modified = "modified"; public new static readonly StringName Location = "location"; } public new double Version { get => Get(GDExtensionPropertyName.Version).As(); set => Set(GDExtensionPropertyName.Version, value); } public new long RegionSize { get => Get(GDExtensionPropertyName.RegionSize).As(); set => Set(GDExtensionPropertyName.RegionSize, value); } public new double VertexSpacing { get => Get(GDExtensionPropertyName.VertexSpacing).As(); set => Set(GDExtensionPropertyName.VertexSpacing, value); } public new Vector2 HeightRange { get => Get(GDExtensionPropertyName.HeightRange).As(); set => Set(GDExtensionPropertyName.HeightRange, value); } public new Image HeightMap { get => Get(GDExtensionPropertyName.HeightMap).As(); set => Set(GDExtensionPropertyName.HeightMap, value); } public new Image ControlMap { get => Get(GDExtensionPropertyName.ControlMap).As(); set => Set(GDExtensionPropertyName.ControlMap, value); } public new Image ColorMap { get => Get(GDExtensionPropertyName.ColorMap).As(); set => Set(GDExtensionPropertyName.ColorMap, value); } public new Godot.Collections.Dictionary Instances { get => Get(GDExtensionPropertyName.Instances).As(); set => Set(GDExtensionPropertyName.Instances, value); } public new bool Edited { get => Get(GDExtensionPropertyName.Edited).As(); set => Set(GDExtensionPropertyName.Edited, value); } public new bool Deleted { get => Get(GDExtensionPropertyName.Deleted).As(); set => Set(GDExtensionPropertyName.Deleted, value); } public new bool Modified { get => Get(GDExtensionPropertyName.Modified).As(); set => Set(GDExtensionPropertyName.Modified, value); } public new Vector2I Location { get => Get(GDExtensionPropertyName.Location).As(); set => Set(GDExtensionPropertyName.Location, value); } public new static class GDExtensionMethodName { public new static readonly StringName Clear = "clear"; public new static readonly StringName SetMap = "set_map"; public new static readonly StringName GetMap = "get_map"; public new static readonly StringName SetMaps = "set_maps"; public new static readonly StringName GetMaps = "get_maps"; public new static readonly StringName SanitizeMaps = "sanitize_maps"; public new static readonly StringName SanitizeMap = "sanitize_map"; public new static readonly StringName ValidateMapSize = "validate_map_size"; public new static readonly StringName UpdateHeight = "update_height"; public new static readonly StringName UpdateHeights = "update_heights"; public new static readonly StringName CalcHeightRange = "calc_height_range"; public new static readonly StringName Save = "save"; public new static readonly StringName SetData = "set_data"; public new static readonly StringName GetData = "get_data"; public new static readonly StringName Duplicate = "duplicate"; public new static readonly StringName Dump = "dump"; } public new void Clear() => Call(GDExtensionMethodName.Clear, []); public new void SetMap(Terrain3DRegion.MapType mapType, Image map) => Call(GDExtensionMethodName.SetMap, [Variant.From(mapType), map]); public new Image GetMap(Terrain3DRegion.MapType mapType) => Call(GDExtensionMethodName.GetMap, [Variant.From(mapType)]).As(); public new void SetMaps(Godot.Collections.Array maps) => Call(GDExtensionMethodName.SetMaps, [maps]); public new Godot.Collections.Array GetMaps() => Call(GDExtensionMethodName.GetMaps, []).As(); public new void SanitizeMaps() => Call(GDExtensionMethodName.SanitizeMaps, []); public new Image SanitizeMap(Terrain3DRegion.MapType mapType, Image map) => Call(GDExtensionMethodName.SanitizeMap, [Variant.From(mapType), map]).As(); public new bool ValidateMapSize(Image map) => Call(GDExtensionMethodName.ValidateMapSize, [map]).As(); public new void UpdateHeight(double height) => Call(GDExtensionMethodName.UpdateHeight, [height]); public new void UpdateHeights(Vector2 lowHigh) => Call(GDExtensionMethodName.UpdateHeights, [lowHigh]); public new void CalcHeightRange() => Call(GDExtensionMethodName.CalcHeightRange, []); public new Error Save(string path = "", bool save16Bit = false) => Call(GDExtensionMethodName.Save, [path, save16Bit]).As(); public new void SetData(Godot.Collections.Dictionary data) => Call(GDExtensionMethodName.SetData, [data]); public new Godot.Collections.Dictionary GetData() => Call(GDExtensionMethodName.GetData, []).As(); public new Terrain3DRegion Duplicate(bool deep = false) => Terrain3DRegion.Bind(Call(GDExtensionMethodName.Duplicate, [deep]).As()); public new void Dump(bool verbose = false) => Call(GDExtensionMethodName.Dump, [verbose]); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DRegion.cs.uid ================================================ uid://bcby10sji7q8i ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DTextureAsset.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DTextureAsset : Resource { private new static readonly StringName NativeName = new StringName("Terrain3DTextureAsset"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DTextureAsset object), please use the Instantiate() method instead.")] protected Terrain3DTextureAsset() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DTextureAsset Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DTextureAsset wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DTextureAsset); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DTextureAsset).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DTextureAsset)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DTextureAsset" type. public new static Terrain3DTextureAsset Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public new static class GDExtensionSignalName { public new static readonly StringName IdChanged = "id_changed"; public new static readonly StringName FileChanged = "file_changed"; public new static readonly StringName SettingChanged = "setting_changed"; } public new delegate void IdChangedSignalHandler(); private IdChangedSignalHandler _idChangedSignal; private Callable _idChangedSignalCallable; public event IdChangedSignalHandler IdChangedSignal { add { if (_idChangedSignal is null) { _idChangedSignalCallable = Callable.From(() => _idChangedSignal?.Invoke()); Connect(GDExtensionSignalName.IdChanged, _idChangedSignalCallable); } _idChangedSignal += value; } remove { _idChangedSignal -= value; if (_idChangedSignal is not null) return; Disconnect(GDExtensionSignalName.IdChanged, _idChangedSignalCallable); _idChangedSignalCallable = default; } } public new delegate void FileChangedSignalHandler(); private FileChangedSignalHandler _fileChangedSignal; private Callable _fileChangedSignalCallable; public event FileChangedSignalHandler FileChangedSignal { add { if (_fileChangedSignal is null) { _fileChangedSignalCallable = Callable.From(() => _fileChangedSignal?.Invoke()); Connect(GDExtensionSignalName.FileChanged, _fileChangedSignalCallable); } _fileChangedSignal += value; } remove { _fileChangedSignal -= value; if (_fileChangedSignal is not null) return; Disconnect(GDExtensionSignalName.FileChanged, _fileChangedSignalCallable); _fileChangedSignalCallable = default; } } public new delegate void SettingChangedSignalHandler(); private SettingChangedSignalHandler _settingChangedSignal; private Callable _settingChangedSignalCallable; public event SettingChangedSignalHandler SettingChangedSignal { add { if (_settingChangedSignal is null) { _settingChangedSignalCallable = Callable.From(() => _settingChangedSignal?.Invoke()); Connect(GDExtensionSignalName.SettingChanged, _settingChangedSignalCallable); } _settingChangedSignal += value; } remove { _settingChangedSignal -= value; if (_settingChangedSignal is not null) return; Disconnect(GDExtensionSignalName.SettingChanged, _settingChangedSignalCallable); _settingChangedSignalCallable = default; } } public new static class GDExtensionPropertyName { public new static readonly StringName Name = "name"; public new static readonly StringName Id = "id"; public new static readonly StringName AlbedoColor = "albedo_color"; public new static readonly StringName AlbedoTexture = "albedo_texture"; public new static readonly StringName NormalTexture = "normal_texture"; public new static readonly StringName NormalDepth = "normal_depth"; public new static readonly StringName AoStrength = "ao_strength"; public new static readonly StringName AoLightAffect = "ao_light_affect"; public new static readonly StringName Roughness = "roughness"; public new static readonly StringName DisplacementScale = "displacement_scale"; public new static readonly StringName DisplacementOffset = "displacement_offset"; public new static readonly StringName UvScale = "uv_scale"; public new static readonly StringName DetilingRotation = "detiling_rotation"; public new static readonly StringName DetilingShift = "detiling_shift"; } public new string Name { get => Get(GDExtensionPropertyName.Name).As(); set => Set(GDExtensionPropertyName.Name, value); } public new long Id { get => Get(GDExtensionPropertyName.Id).As(); set => Set(GDExtensionPropertyName.Id, value); } public new Color AlbedoColor { get => Get(GDExtensionPropertyName.AlbedoColor).As(); set => Set(GDExtensionPropertyName.AlbedoColor, value); } public new Texture2D AlbedoTexture { get => Get(GDExtensionPropertyName.AlbedoTexture).As(); set => Set(GDExtensionPropertyName.AlbedoTexture, value); } public new Texture2D NormalTexture { get => Get(GDExtensionPropertyName.NormalTexture).As(); set => Set(GDExtensionPropertyName.NormalTexture, value); } public new double NormalDepth { get => Get(GDExtensionPropertyName.NormalDepth).As(); set => Set(GDExtensionPropertyName.NormalDepth, value); } public new double AoStrength { get => Get(GDExtensionPropertyName.AoStrength).As(); set => Set(GDExtensionPropertyName.AoStrength, value); } public new double AoLightAffect { get => Get(GDExtensionPropertyName.AoLightAffect).As(); set => Set(GDExtensionPropertyName.AoLightAffect, value); } public new double Roughness { get => Get(GDExtensionPropertyName.Roughness).As(); set => Set(GDExtensionPropertyName.Roughness, value); } public new double DisplacementScale { get => Get(GDExtensionPropertyName.DisplacementScale).As(); set => Set(GDExtensionPropertyName.DisplacementScale, value); } public new double DisplacementOffset { get => Get(GDExtensionPropertyName.DisplacementOffset).As(); set => Set(GDExtensionPropertyName.DisplacementOffset, value); } public new double UvScale { get => Get(GDExtensionPropertyName.UvScale).As(); set => Set(GDExtensionPropertyName.UvScale, value); } public new double DetilingRotation { get => Get(GDExtensionPropertyName.DetilingRotation).As(); set => Set(GDExtensionPropertyName.DetilingRotation, value); } public new double DetilingShift { get => Get(GDExtensionPropertyName.DetilingShift).As(); set => Set(GDExtensionPropertyName.DetilingShift, value); } public new static class GDExtensionMethodName { public new static readonly StringName Clear = "clear"; public new static readonly StringName SetHighlighted = "set_highlighted"; public new static readonly StringName IsHighlighted = "is_highlighted"; public new static readonly StringName GetHighlightColor = "get_highlight_color"; public new static readonly StringName GetThumbnail = "get_thumbnail"; } public new void Clear() => Call(GDExtensionMethodName.Clear, []); public new void SetHighlighted(bool enabled) => Call(GDExtensionMethodName.SetHighlighted, [enabled]); public new bool IsHighlighted() => Call(GDExtensionMethodName.IsHighlighted, []).As(); public new Color GetHighlightColor() => Call(GDExtensionMethodName.GetHighlightColor, []).As(); public new Texture2D GetThumbnail() => Call(GDExtensionMethodName.GetThumbnail, []).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DTextureAsset.cs.uid ================================================ uid://bbee5m52swmhh ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DUtil.cs ================================================ #pragma warning disable CS0109 using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Godot; using Godot.Collections; namespace TokisanGames; [Tool] public partial class Terrain3DUtil : GodotObject { private new static readonly StringName NativeName = new StringName("Terrain3DUtil"); [Obsolete("Wrapper types cannot be constructed with constructors (it only instantiate the underlying Terrain3DUtil object), please use the Instantiate() method instead.")] protected Terrain3DUtil() { } private static CSharpScript _wrapperScriptAsset; /// /// Try to cast the script on the supplied to the wrapper type, /// if no script has attached to the type, or the script attached to the type does not inherit the wrapper type, /// a new instance of the wrapper script will get attaches to the . /// /// The developer should only supply the that represents the correct underlying GDExtension type. /// The that represents the correct underlying GDExtension type. /// The existing or a new instance of the wrapper script attached to the supplied . public new static Terrain3DUtil Bind(GodotObject godotObject) { if (!IsInstanceValid(godotObject)) return null; if (godotObject is Terrain3DUtil wrapperScriptInstance) return wrapperScriptInstance; #if DEBUG var expectedType = typeof(Terrain3DUtil); var currentObjectClassName = godotObject.GetClass(); if (!ClassDB.IsParentClass(expectedType.Name, currentObjectClassName)) throw new InvalidOperationException($"The supplied GodotObject ({currentObjectClassName}) is not the {expectedType.Name} type."); #endif if (_wrapperScriptAsset is null) { var scriptPathAttribute = typeof(Terrain3DUtil).GetCustomAttributes().FirstOrDefault(); if (scriptPathAttribute is null) throw new UnreachableException(); _wrapperScriptAsset = ResourceLoader.Load(scriptPathAttribute.Path); } var instanceId = godotObject.GetInstanceId(); godotObject.SetScript(_wrapperScriptAsset); return (Terrain3DUtil)InstanceFromId(instanceId); } /// /// Creates an instance of the GDExtension type, and attaches a wrapper script instance to it. /// /// The wrapper instance linked to the underlying GDExtension "Terrain3DUtil" type. public new static Terrain3DUtil Instantiate() => Bind(ClassDB.Instantiate(NativeName).As()); public new static class GDExtensionMethodName { public new static readonly StringName AsFloat = "as_float"; public new static readonly StringName AsUint = "as_uint"; public new static readonly StringName GetBase = "get_base"; public new static readonly StringName EncBase = "enc_base"; public new static readonly StringName GetOverlay = "get_overlay"; public new static readonly StringName EncOverlay = "enc_overlay"; public new static readonly StringName GetBlend = "get_blend"; public new static readonly StringName EncBlend = "enc_blend"; public new static readonly StringName GetUvRotation = "get_uv_rotation"; public new static readonly StringName EncUvRotation = "enc_uv_rotation"; public new static readonly StringName GetUvScale = "get_uv_scale"; public new static readonly StringName EncUvScale = "enc_uv_scale"; public new static readonly StringName IsHole = "is_hole"; public new static readonly StringName EncHole = "enc_hole"; public new static readonly StringName IsNav = "is_nav"; public new static readonly StringName EncNav = "enc_nav"; public new static readonly StringName IsAuto = "is_auto"; public new static readonly StringName EncAuto = "enc_auto"; public new static readonly StringName FilenameToLocation = "filename_to_location"; public new static readonly StringName LocationToFilename = "location_to_filename"; public new static readonly StringName BlackToAlpha = "black_to_alpha"; public new static readonly StringName GetMinMax = "get_min_max"; public new static readonly StringName GetThumbnail = "get_thumbnail"; public new static readonly StringName GetFilledImage = "get_filled_image"; public new static readonly StringName LoadImage = "load_image"; public new static readonly StringName PackImage = "pack_image"; public new static readonly StringName LuminanceToHeight = "luminance_to_height"; } public new static double AsFloat(long value) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.AsFloat, [value]).As(); public new static long AsUint(double value) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.AsUint, [value]).As(); public new static long GetBase(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetBase, [pixel]).As(); public new static long EncBase(long @base) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncBase, [@base]).As(); public new static long GetOverlay(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetOverlay, [pixel]).As(); public new static long EncOverlay(long overlay) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncOverlay, [overlay]).As(); public new static long GetBlend(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetBlend, [pixel]).As(); public new static long EncBlend(long blend) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncBlend, [blend]).As(); public new static long GetUvRotation(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetUvRotation, [pixel]).As(); public new static long EncUvRotation(long rotation) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncUvRotation, [rotation]).As(); public new static long GetUvScale(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetUvScale, [pixel]).As(); public new static long EncUvScale(long scale) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncUvScale, [scale]).As(); public new static bool IsHole(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.IsHole, [pixel]).As(); public new static long EncHole(bool pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncHole, [pixel]).As(); public new static bool IsNav(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.IsNav, [pixel]).As(); public new static long EncNav(bool pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncNav, [pixel]).As(); public new static bool IsAuto(long pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.IsAuto, [pixel]).As(); public new static long EncAuto(bool pixel) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.EncAuto, [pixel]).As(); public new static Vector2I FilenameToLocation(string filename) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.FilenameToLocation, [filename]).As(); public new static string LocationToFilename(Vector2I regionLocation) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.LocationToFilename, [regionLocation]).As(); public new static Image BlackToAlpha(Image image) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.BlackToAlpha, [image]).As(); public new static Vector2 GetMinMax(Image image) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetMinMax, [image]).As(); public new static Image GetThumbnail(Image image, Vector2I size = default) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetThumbnail, [image, size]).As(); public new static Image GetFilledImage(Vector2I size, Color color, bool createMipmaps, Image.Format format) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.GetFilledImage, [size, color, createMipmaps, Variant.From(format)]).As(); public new static Image LoadImage(string fileName, long cacheMode = 0, Vector2 r16HeightRange = default, Vector2I r16Size = default) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.LoadImage, [fileName, cacheMode, r16HeightRange, r16Size]).As(); public new static Image PackImage(Image srcRgb, Image srcA, Image srcAo, bool invertGreen = false, bool invertAlpha = false, bool normalizeAlpha = false, long alphaChannel = 0, long aoChannel = 0) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.PackImage, [srcRgb, srcA, srcAo, invertGreen, invertAlpha, normalizeAlpha, alphaChannel, aoChannel]).As(); public new static Image LuminanceToHeight(Image srcRgb) => ClassDB.ClassCallStatic(NativeName, GDExtensionMethodName.LuminanceToHeight, [srcRgb]).As(); } ================================================ FILE: project/addons/terrain_3d/csharp/Terrain3DUtil.cs.uid ================================================ uid://b5712pl5vpmet ================================================ FILE: project/addons/terrain_3d/extras/3rd_party/import_sgt.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Import From SimpleGrassTextured # # This script demonstrates how to import transforms from SimpleGrassTextured. To use it: # # 1. Setup the mesh asset you wish to use in the asset dock. # 1. Select your Terrain3D node. # 1. In the inspector, click Script (very bottom) and Quick Load import_sgt.gd. # 1. At the very top, assign your SimpleGrassTextured node. # 1. Input the desired mesh asset ID. # 1. Click import. The output window and console will report when finished. # 1. Clear the script from your Terrain3D node, and save your scene. # # The instance transforms are now stored in your region files. # # Use clear_instances to erase all instances that match the assign_mesh_id. # # The add_transforms function (called by add_multimesh) applies the height_offset specified in the # Terrain3DMeshAsset. # Once the transforms are imported, you can reassign any mesh you like into this mesh slot. @tool extends Terrain3D @export var simple_grass_textured: MultiMeshInstance3D @export var assign_mesh_id: int @export_tool_button("Import") var import = import_sgt @export_tool_button("Clear Instances") var clear_instances = clear_multimeshes func clear_multimeshes() -> void: get_instancer().clear_by_mesh(assign_mesh_id) func import_sgt() -> void: var sgt_mm: MultiMesh = simple_grass_textured.multimesh var global_xform: Transform3D = simple_grass_textured.global_transform print("Starting to import %d instances from SimpleGrassTextured using mesh id %d" % [ sgt_mm.instance_count, assign_mesh_id]) var time: int = Time.get_ticks_msec() get_instancer().add_multimesh(assign_mesh_id, sgt_mm, simple_grass_textured.global_transform) print("Import complete in %.2f seconds" % [ float(Time.get_ticks_msec() - time)/1000. ]) ================================================ FILE: project/addons/terrain_3d/extras/3rd_party/import_sgt.gd.uid ================================================ uid://bllcuwetve45k ================================================ FILE: project/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter # It provides a `Project on Terrain3D` modifier, which allows Scatter # to detect the terrain height from Terrain3D without using collision. # # Copy this file into /addons/proton_scatter/src/modifiers # Then uncomment everything below (select, press CTRL+K) # In the editor, add this modifier to Scatter, then set your Terrain3D node #@tool #extends "base_modifier.gd" # # #signal projection_completed # # #@export var terrain_node : NodePath #@export var align_with_collision_normal : bool = false #@export_range(0.0, 90.0, 0.1) var max_slope : float = 90.0 #@export var enable_texture_filtering : bool = false #@export_range(0, 31) var target_texture_id : int = 0 #@export var not_target_texture : bool = false #@export_range(0.0, 1.0, 0.01) var texture_threshold : float = 0.8 # #var _terrain: Terrain3D # # #func _init() -> void: #display_name = "Project On Terrain3D" #category = "Edit" #can_restrict_height = false #global_reference_frame_available = true #local_reference_frame_available = true #individual_instances_reference_frame_available = true #use_global_space_by_default() # #documentation.add_paragraph( #"This is a modified version of `Project on Colliders` that queries Terrain3D #for heights without using collision. It constrains placement by slope or texture. # #This modifier must have terrain_node set to a Terrain3D node.") # #var p := documentation.add_parameter("Terrain Node") #p.set_type("NodePath") #p.set_description("Set your Terrain3D node.") # #p = documentation.add_parameter("Align with collision normal") #p.set_type("bool") #p.set_description( #"Rotate the transform to align it with the collision normal in case #the ray cast hit a collider.") # #p = documentation.add_parameter("Enable Texture Filtering") #p.set_type("bool") #p.set_description( #"If enabled, objects will only be placed based on the ground texture specified.") # #p = documentation.add_parameter("Target Texture ID") #p.set_type("int") #p.set_description( #"The ID of the texture to place objects on (0-31). Objects will only be placed on this texture.") # #p = documentation.add_parameter("Not Target Texture") #p.set_type("bool") #p.set_description( #"If true, objects will be placed on all textures EXCEPT the target texture.") # #p = documentation.add_parameter("Texture Threshold") #p.set_type("float") #p.set_description("The blend value required for placement on the texture.") # # #func _process_transforms(transforms, domain, _seed) -> void: #if transforms.is_empty(): #return # #if terrain_node: #_terrain = domain.get_root().get_node_or_null(terrain_node) # #if not _terrain: #warning += """No Terrain3D node found""" #return # #if not _terrain.data: #warning += """Terrain3DData is not initialized""" #return # ## Review transforms #var gt: Transform3D = domain.get_global_transform() #var gt_inverse := gt.affine_inverse() #var new_transforms_array: Array[Transform3D] = [] #var remapped_max_slope: float = remap(max_slope, 0.0, 90.0, 0.0, 1.0) #for i in transforms.list.size(): #var t: Transform3D = transforms.list[i] # #var location: Vector3 = (gt * t).origin #var height: float = _terrain.data.get_height(location) #if is_nan(height): #continue # #var normal: Vector3 = _terrain.data.get_normal(location) #if not abs(Vector3.UP.dot(normal)) >= (1.0 - remapped_max_slope): #continue # #if enable_texture_filtering: #var texture_info: Vector3 = _terrain.data.get_texture_id(location) #var base_id: int = int(texture_info.x) #var overlay_id: int = int(texture_info.y) #var blend_value: float = texture_info.z ## Skip if overlay or blend != target texture, unless inverted #if ((overlay_id != target_texture_id or blend_value < texture_threshold) and \ #(base_id != target_texture_id or blend_value >= texture_threshold)) != not_target_texture: #continue # #if align_with_collision_normal and not is_nan(normal.x): #t.basis.y = normal #t.basis.x = -t.basis.z.cross(normal) #t.basis = t.basis.orthonormalized() # #t.origin.y = height - gt.origin.y #new_transforms_array.push_back(t) # #transforms.list.clear() #transforms.list.append_array(new_transforms_array) # #if transforms.is_empty(): #warning += """All transforms have been removed. Possible reasons include: \n""" #if enable_texture_filtering: #warning += """+ No matching texture found at any position. #+ Texture threshold may be too high. #""" #warning += """+ No collider is close enough to the shapes. #+ Ray length is too short. #+ Ray direction is incorrect. #+ Collision mask is not set properly. #+ Max slope is too low. #""" ================================================ FILE: project/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd.uid ================================================ uid://g3opjh3m3iww ================================================ FILE: project/addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn ================================================ [gd_scene load_steps=5 format=3 uid="uid://d3sr0a7dxfkr8"] [ext_resource type="Script" uid="uid://bp7r4ppgq1m0g" path="res://addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd" id="1_gl3qg"] [ext_resource type="Material" uid="uid://el5y10hnh13g" path="res://addons/terrain_3d/extras/particle_example/process_material.tres" id="2_2gon1"] [ext_resource type="Material" uid="uid://ljo1wt61kbkq" path="res://addons/terrain_3d/extras/particle_example/grass_material.tres" id="3_qyjnw"] [sub_resource type="RibbonTrailMesh" id="RibbonTrailMesh_fwrtk"] shape = 0 section_length = 0.18 section_segments = 1 [node name="Terrain3DParticles" type="Node3D"] script = ExtResource("1_gl3qg") instance_spacing = 0.25 cell_width = 24.0 grid_width = 5 rows = 96 amount = 9216 process_material = ExtResource("2_2gon1") mesh = SubResource("RibbonTrailMesh_fwrtk") shadow_mode = 0 mesh_material_override = ExtResource("3_qyjnw") min_draw_distance = 60.0 particle_count = 230400 metadata/_edit_lock_ = true ================================================ FILE: project/addons/terrain_3d/extras/particle_example/grass.gdshader ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. shader_type spatial; render_mode skip_vertex_transform, cull_disabled, blend_mix; uniform vec2 wind_direction = vec2(1.0, 1.0); varying vec4 data; // A lot of hard coded things here atm. void vertex() { // Wind effect from model data, in this case no vertex colors, // so just use vertex Y component, including mesh offset data[2] = (VERTEX.y + 0.55); data[2] *= data[2]; // make non-linear // Ribbon used as a grass mesh.. so pinch the top. VERTEX.xz *= (1.0 - data[2]); // Brighten tips COLOR = mix(COLOR, vec4(1.0), smoothstep(0.9, 1.0, data[2])); // Darken base, skip is scale is less than threshold, as this means "grow in" is occuring. COLOR *= INSTANCE_CUSTOM[3] < 0.35 ? 1. : mix(1.0, 0.75, smoothstep(0.35, 0.0, data[2])); // Save red/green shift for fragment data.rg = INSTANCE_CUSTOM.rg; // World space vertex vec3 w_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // Get wind force and scale from process() float scale = pow(INSTANCE_CUSTOM[3] * INSTANCE_CUSTOM[3], 0.707); float force = INSTANCE_CUSTOM[2] * data[2] * scale; // Add some cheap jitter at high wind values force -= fract(force * 256.0) * force * 0.05; // Curve the result force = pow(force, 0.707); // These 2 combined result in a decent bend without resorting to matrices or pivot data. // Lateral move and wobble float lateral_wobble = sin(TIME * 2.0 * (1.0 + data.r + data.g)) * 0.25 * (1.0 - INSTANCE_CUSTOM[2]); vec2 direction = normalize(wind_direction); w_vertex.xz -= (vec2(-direction.y, direction.x) * lateral_wobble + direction) * force; // Flatten w_vertex.y -= INSTANCE_CUSTOM[2] * force * data[2]; // Save final wind force value for fragment. data[3] = force; VERTEX = (VIEW_MATRIX * vec4(w_vertex, 1.0)).xyz; NORMAL = MODELVIEW_NORMAL_MATRIX * NORMAL; BINORMAL = MODELVIEW_NORMAL_MATRIX * BINORMAL; TANGENT = MODELVIEW_NORMAL_MATRIX * TANGENT; } void fragment() { // Hard coded color. ALBEDO = vec3(0.20, 0.22, 0.05) * (data[2] * 0.5 + 0.5); ALBEDO.rg *= (data.rg * 0.3 + 0.9); ALBEDO *= pow(COLOR.rgb, vec3(2.2)); // Modify roughness / specular based on wind force for added detail float spec_rough = clamp(max(data[2], data[3]), 0., 1.); ROUGHNESS = 1. - spec_rough; SPECULAR = clamp(spec_rough * 0.25, 0., .15); BACKLIGHT = vec3(0.33); #if CURRENT_RENDERER == RENDERER_COMPATIBILITY ALBEDO = pow(ALBEDO, vec3(0.4)); #endif } ================================================ FILE: project/addons/terrain_3d/extras/particle_example/grass.gdshader.uid ================================================ uid://dq3lfyp3u5oxt ================================================ FILE: project/addons/terrain_3d/extras/particle_example/grass_material.tres ================================================ [gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://ljo1wt61kbkq"] [ext_resource type="Shader" uid="uid://dq3lfyp3u5oxt" path="res://addons/terrain_3d/extras/particle_example/grass.gdshader" id="1_nkru0"] [resource] render_priority = 0 shader = ExtResource("1_nkru0") shader_parameter/wind_direction = Vector2(1, 1) ================================================ FILE: project/addons/terrain_3d/extras/particle_example/particles.gdshader ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // This is an example particle shader designed to procedurally place // particles like grass, small rocks, and other ground effects, on the terrain // surface by reading the Terrain3D data maps. It works in tandem with the // provided GDScript. shader_type particles; render_mode disable_velocity, disable_force; group_uniforms options; uniform sampler2D main_noise; uniform float main_noise_scale = 0.01; uniform vec3 position_offset = vec3(0.); uniform bool align_to_normal = true; uniform float normal_strength : hint_range(0.01, 1.0, 0.01) = 0.3; uniform bool random_rotation = true; uniform float random_spacing : hint_range(0.0, 1.0, 0.01) = 0.5; uniform vec3 min_scale = vec3(1.0); uniform vec3 max_scale = vec3(1.0); group_uniforms wind; uniform float noise_scale = 0.0041; uniform float wind_speed = 0.025; uniform float wind_strength : hint_range(0.0, 1.0, 0.01) = 1.0; uniform float wind_dithering = 4.0; uniform vec2 wind_direction = vec2(1.0,1.0); group_uniforms shaping; uniform float clod_scale_boost = 3.0; uniform float clod_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.2; uniform float clod_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.5; uniform float patch_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.025; uniform float patch_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.2; group_uniforms filtering; uniform float condition_dither_range : hint_range(0.0, 1.0, 0.01) = 0.15; uniform float surface_slope_min : hint_range(0.0, 1.0, 0.01) = 0.87; uniform float distance_fade_ammount : hint_range(0.0, 1.0, 0.01) = 0.5; group_uniforms private; uniform float max_dist = 1.; uniform vec3 camera_position = vec3(0.); uniform uint instance_rows = 1; uniform float instance_spacing = 0.5; uniform uint _background_mode = 0u; uniform float _vertex_spacing = 1.0; uniform float _vertex_density = 1.0; // = 1/_vertex_spacing uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1/REGION_SIZE uniform int _region_map_size = 32; uniform int _region_map[1024]; uniform vec2 _region_locations[1024]; uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; uniform highp sampler2DArray _color_maps : repeat_disable; // Defined Constants #define SKIP_PASS 0 #define VERTEX_PASS 1 #define FRAGMENT_PASS 2 // Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none) // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region ivec3 get_index_coord(const vec2 uv, const int search) { vec2 r_uv = round(uv); vec2 o_uv = mod(r_uv,_region_size); ivec2 pos; int bounds, layer_index = -1; for (int i = -1; i < 0; i++) { if ((layer_index == -1 && _background_mode == 0u) || i < 0) { r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x)); pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2); bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1); } } return ivec3(ivec2(mod(r_uv,_region_size)), layer_index); } #if CURRENT_RENDERER == RENDERER_COMPATIBILITY #define fma(a, b, c) ((a) * (b) + (c)) #endif float random(vec2 v) { return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x))))); } mat3 rotation_matrix(vec3 axis, float angle) { float c = cos(angle); float s = sin(angle); float t = 1.0 - c; vec3 n = normalize(axis); float x = n.x; float y = n.y; float z = n.z; return mat3( vec3(t * x * x + c, t * x * y - z * s, t * x * z + y * s), vec3(t * x * y + z * s, t * y * y + c, t * y * z - x * s), vec3(t * x * z - y * s, t * y * z + x * s, t * z * z + c)); } mat3 align_to_vector(vec3 normal) { vec3 up = vec3(0.0, 1.0, 0.0); if (abs(dot(normal, up)) > 0.9999) { // Avoid singularity up = vec3(1.0, 0.0, 0.0); } vec3 tangent = normalize(cross(up, normal)); vec3 bitangent = normalize(cross(tangent, normal)); return mat3(tangent, normal, bitangent); } void start() { // Create centered a grid vec3 pos = vec3(float(INDEX % instance_rows), 0.0, float(INDEX / instance_rows)) - float(instance_rows >>1u); // Apply spcaing pos *= instance_spacing; // Move the grid to the emitter, snapping is handled CPU side pos.xz += EMISSION_TRANSFORM[3].xz; // Create random values per-instance, incorporating the seed, mask bits to avoid NAN/INF float seed = fract(uintBitsToFloat(RANDOM_SEED & 0x7EFFFFFFu)); vec3 r = fract(vec3(random(pos.xz), random(pos.xz + vec2(0.5)), random(pos.xz - vec2(0.5))) + seed); // Randomize instance spacing pos.x += ((r.x * 2.0) - 1.0) * random_spacing * instance_spacing; pos.z += ((r.z * 2.0) - 1.0) * random_spacing * instance_spacing; // Lookup offsets, ID and blend weight const vec3 offsets = vec3(0, 1, 2); vec2 index_id = floor(pos.xz * _vertex_density); vec2 weight = fract(pos.xz * _vertex_density); vec2 invert = 1.0 - weight; vec4 weights = vec4( invert.x * weight.y, // 0 weight.x * weight.y, // 1 weight.x * invert.y, // 2 invert.x * invert.y // 3 ); ivec3 index[4]; // Map lookups index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS); index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS); index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS); index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS); highp float h[8]; h[0] = texelFetch(_height_maps, index[0], 0).r; // 0 (0,1) h[1] = texelFetch(_height_maps, index[1], 0).r; // 1 (1,1) h[2] = texelFetch(_height_maps, index[2], 0).r; // 2 (1,0) h[3] = texelFetch(_height_maps, index[3], 0).r; // 3 (0,0) h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, VERTEX_PASS), 0).r; // 4 (1,2) h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, VERTEX_PASS), 0).r; // 5 (2,1) h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, VERTEX_PASS), 0).r; // 6 (2,0) h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, VERTEX_PASS), 0).r; // 7 (0,2) vec3 index_normal[4]; index_normal[0] = vec3(h[0] - h[1], _vertex_spacing, h[0] - h[7]); index_normal[1] = vec3(h[1] - h[5], _vertex_spacing, h[1] - h[4]); index_normal[2] = vec3(h[2] - h[6], _vertex_spacing, h[2] - h[1]); index_normal[3] = vec3(h[3] - h[2], _vertex_spacing, h[3] - h[0]); vec3 w_normal = normalize( index_normal[0] * weights[0] + index_normal[1] * weights[1] + index_normal[2] * weights[2] + index_normal[3] * weights[3]); // Set the height according to the heightmap data pos.y = h[0] * weights[0] + h[1] * weights[1] + h[2] * weights[2] + h[3] * weights[3] ; // Offset, Rotation, Alignment. TRANSFORM = mat4(1.0); vec3 orientation = vec3(0., 1., 0.); vec2 uv = (pos.xz) * main_noise_scale; float noise = textureLod(main_noise, uv, 0.0).r; float clods = smoothstep(clod_min_threshold, clod_max_threshold, noise) * clod_scale_boost; float patch = smoothstep(patch_min_threshold, patch_max_threshold, noise); float width_modifier = 1.0 + 3.0 * smoothstep(0., max_dist, length(camera_position - pos)); // Calculate scale vec3 scale = vec3( mix(min_scale.x, max_scale.x, r.x) * width_modifier, mix(min_scale.y, max_scale.y, r.y) + clods, mix(min_scale.z, max_scale.z, r.z) * width_modifier) * patch; // Apply scale to offset vec3 offset = position_offset * scale; // Apply normal orientation if (align_to_normal) { orientation = mix(orientation, w_normal, normal_strength); mat3 alignment = align_to_vector(orientation); offset = alignment * offset; TRANSFORM = mat4(alignment); } // Apply rotation around orientation if (random_rotation) { mat3 rotation = rotation_matrix(orientation, r.x * TAU); TRANSFORM = mat4(rotation) * TRANSFORM; } // Filtering - Causes some particles to be rendered as degenerate triangles // via 0./0. - Particles filtered this way are still processed by the GPU. // For compatibility it seems we must shift as well. // Surface slope filtering if (surface_slope_min > w_normal.y + (r.y - 0.5) * condition_dither_range) { pos.y = 0. / 0.; pos.xz = vec2(100000.0); } // Read color map highp vec4 c[4]; #define COLOR_MAP vec4(1., 1., 1., 0.5) c[0] = index[0].z >= 0 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP; // 0 (0,1) c[1] = index[1].z >= 0 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP; // 1 (1,1) c[2] = index[2].z >= 0 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP; // 2 (1,0) c[3] = index[3].z >= 0 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP; // 3 (0,0) vec4 color_map = c[0] * weights[0] + c[1] * weights[1] + c[2] * weights[2] + c[3] * weights[3] ; COLOR = color_map; // Read control maps uvec4 control = uvec4( floatBitsToUint(texelFetch(_control_maps, index[0], 0).r), floatBitsToUint(texelFetch(_control_maps, index[1], 0).r), floatBitsToUint(texelFetch(_control_maps, index[2], 0).r), floatBitsToUint(texelFetch(_control_maps, index[3], 0).r)); bool hole = any(bvec4(control >> uvec4(2u) & uvec4(0x1u))); bool auto = any(bvec4(control & uvec4(0x1u))); int base = int(control[3] >> 27u & 0x1Fu); int over = int(control[3] >> 22u & 0x1Fu); float blend = float(control[3] >> 14u & 0xFFu) * 0.003921568627450; // 1. / 255. // Filter out holes if (hole) { pos.y = 0. / 0.; pos.xz = vec2(100000.0); } // Hardcoded example, hand painted texture id 0 is filtered out. if (!auto && ((base == 0 && blend < 0.7) || (over == 0 && blend >= 0.3))) { pos.y = 0. / 0.; pos.xz = vec2(100000.0); } if (length(camera_position - pos) > max_dist) { pos.y = 0. / 0.; pos.xz = vec2(100000.0); } else { float fade_factor = 1.0 - smoothstep(max_dist * distance_fade_ammount, max_dist + 0.0001, length(camera_position - pos)) + 0.001; scale.y *= fade_factor; offset *= fade_factor; } // Apply scale TRANSFORM[0] *= scale.x; TRANSFORM[1] *= scale.y; TRANSFORM[2] *= scale.z; // Apply the position TRANSFORM[3].xyz = pos.xyz + offset; // Save Fixed 2 Random values for Reg/Green color randomness CUSTOM.rg = r.rg; // Save Y component scale pre-rotation CUSTOM[3] = scale.y; } void process() { // Extract world space UV from Transform Matrix vec2 uv = (TRANSFORM[3].xz + CUSTOM.rg * wind_dithering) * noise_scale; // Scaled wind noise, updated per instance, at process FPS. Passed to Vertex() CUSTOM[2] = textureLod(main_noise, uv + TIME * wind_speed * normalize(wind_direction), 0.0).r * wind_strength; } ================================================ FILE: project/addons/terrain_3d/extras/particle_example/particles.gdshader.uid ================================================ uid://dce675i014xcn ================================================ FILE: project/addons/terrain_3d/extras/particle_example/process_material.tres ================================================ [gd_resource type="ShaderMaterial" load_steps=4 format=3 uid="uid://el5y10hnh13g"] [ext_resource type="Shader" uid="uid://dce675i014xcn" path="res://addons/terrain_3d/extras/particle_example/particles.gdshader" id="1_t55me"] [sub_resource type="FastNoiseLite" id="FastNoiseLite_544iv"] noise_type = 2 frequency = 0.0145 cellular_return_type = 4 [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_544iv"] noise = SubResource("FastNoiseLite_544iv") seamless = true [resource] shader = ExtResource("1_t55me") shader_parameter/main_noise = SubResource("NoiseTexture2D_544iv") shader_parameter/main_noise_scale = 0.01 shader_parameter/position_offset = Vector3(0, 0.45, 0) shader_parameter/align_to_normal = true shader_parameter/normal_strength = 0.3 shader_parameter/random_rotation = true shader_parameter/random_spacing = 0.5 shader_parameter/min_scale = Vector3(0.125, 0.5, 0.125) shader_parameter/max_scale = Vector3(0.125, 1, 0.125) shader_parameter/noise_scale = 0.0041 shader_parameter/wind_speed = 0.025 shader_parameter/wind_strength = 1.0 shader_parameter/wind_dithering = 4.0 shader_parameter/wind_direction = Vector2(1, 1) shader_parameter/clod_scale_boost = 2.0 shader_parameter/clod_min_threshold = 0.2 shader_parameter/clod_max_threshold = 0.8 shader_parameter/patch_min_threshold = 0.025 shader_parameter/patch_max_threshold = 0.2 shader_parameter/condition_dither_range = 0.15 shader_parameter/surface_slope_min = 0.87 shader_parameter/distance_fade_ammount = 0.66 shader_parameter/max_dist = 1.0 shader_parameter/camera_position = Vector3(0, 0, 0) shader_parameter/instance_rows = 1 shader_parameter/instance_spacing = 0.5 shader_parameter/_background_mode = 0 shader_parameter/_vertex_spacing = 1.0 shader_parameter/_vertex_density = 1.0 shader_parameter/_region_size = 1024.0 shader_parameter/_region_texel_size = 0.000976563 shader_parameter/_region_map_size = 32 shader_parameter/_region_map = PackedInt32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) shader_parameter/_region_locations = PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) ================================================ FILE: project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # # This is an example of using a particle shader with Terrain3D. # To use it, add `Terrain3DParticles.tscn` to your scene and assign the terrain. # Then customize the settings, materials and shader to extend it and make it your own. @tool extends Node3D #region settings ## Auto set if attached as a child of a Terrain3D node @export var terrain: Terrain3D: set(value): terrain = value _create_grid() ## Distance between instances @export_range(0.125, 2.0, 0.015625) var instance_spacing: float = 0.5: set(value): instance_spacing = clamp(round(value * 64.0) * 0.015625, 0.125, 2.0) rows = maxi(int(cell_width / instance_spacing), 1) amount = rows * rows _set_offsets() ## Width of an individual cell of the grid @export_range(8.0, 256.0, 1.0) var cell_width: float = 32.0: set(value): cell_width = clamp(value, 8.0, 256.0) rows = maxi(int(cell_width / instance_spacing), 1) amount = rows * rows min_draw_distance = 1.0 # Have to update aabb if terrain and terrain.data: var height_range: Vector2 = terrain.data.get_height_range() var height: float = height_range[0] - height_range[1] var aabb: AABB = AABB() aabb.size = Vector3(cell_width, height, cell_width) aabb.position = aabb.size * -0.5 aabb.position.y = height_range[1] for p in particle_nodes: p.custom_aabb = aabb _set_offsets() ## Grid width. Must be odd. ## Higher values cull slightly better, draw further out. @export_range(1, 15, 2) var grid_width: int = 9: set(value): grid_width = value particle_count = 1 min_draw_distance = 1.0 _create_grid() @export_storage var rows: int = 1 @export_storage var amount: int = 1: set(value): amount = value particle_count = value last_pos = Vector3.ZERO for p in particle_nodes: p.amount = amount @export_range(1, 256, 1) var process_fixed_fps: int = 30: set(value): process_fixed_fps = maxi(value, 1) for p in particle_nodes: p.fixed_fps = process_fixed_fps p.preprocess = 1.0 / float(process_fixed_fps) ## Access to process material parameters @export var process_material: ShaderMaterial ## The mesh that each particle will render @export var mesh: Mesh @export var shadow_mode: GeometryInstance3D.ShadowCastingSetting = ( GeometryInstance3D.ShadowCastingSetting.SHADOW_CASTING_SETTING_ON): set(value): shadow_mode = value for p in particle_nodes: p.cast_shadow = value ## Override material for the particle mesh @export_custom( PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial") var mesh_material_override: Material: set(value): mesh_material_override = value for p in particle_nodes: p.material_override = mesh_material_override @export_group("Info") ## The minimum distance that particles will be drawn upto ## If using fade out effects like pixel alpha this is the limit to use. @export var min_draw_distance: float = 1.0: set(value): min_draw_distance = float(cell_width * grid_width) * 0.5 ## Displays current total particle count based on Cell Width and Instance Spacing @export var particle_count: int = 1: set(value): particle_count = amount * grid_width * grid_width #endregion var offsets: Array[Vector3] var last_pos: Vector3 = Vector3.ZERO var particle_nodes: Array[GPUParticles3D] func _ready() -> void: if not terrain: var parent: Node = get_parent() if parent is Terrain3D: terrain = parent _create_grid() func _notification(what: int) -> void: if what == NOTIFICATION_PREDELETE: _destroy_grid() func _physics_process(delta: float) -> void: if terrain: var camera: Camera3D = terrain.get_camera() if camera: if last_pos.distance_squared_to(camera.global_position) > 1.0: var pos: Vector3 = camera.global_position.snapped(Vector3.ONE) _position_grid(pos) RenderingServer.material_set_param(process_material.get_rid(), "camera_position", pos ) last_pos = camera.global_position _update_process_parameters() else: set_physics_process(false) func _create_grid() -> void: _destroy_grid() if not terrain: return set_physics_process(true) _set_offsets() var hr: Vector2 = terrain.data.get_height_range() var height: float = hr.x - hr.y var aabb: AABB = AABB() aabb.size = Vector3(cell_width, height, cell_width) aabb.position = aabb.size * -0.5 aabb.position.y = hr.y var half_grid: int = grid_width / 2 # Iterating the array like this allows identifying grid position, in case setting # different mesh or materials is desired for LODs etc. for x in range(-half_grid, half_grid + 1): for z in range(-half_grid, half_grid + 1): #var ring: int = maxi(maxi(absi(x), absi(z)), 0) var particle_node = GPUParticles3D.new() particle_node.lifetime = 600.0 particle_node.amount = amount particle_node.explosiveness = 1.0 particle_node.amount_ratio = 1.0 particle_node.process_material = process_material particle_node.draw_pass_1 = mesh particle_node.speed_scale = 1.0 particle_node.custom_aabb = aabb particle_node.cast_shadow = shadow_mode particle_node.fixed_fps = process_fixed_fps # This prevent minor grid alignment errors when the camera is moving very fast particle_node.preprocess = 1.0 / float(process_fixed_fps) if mesh_material_override: particle_node.material_override = mesh_material_override particle_node.use_fixed_seed = true if (x > -half_grid and z > -half_grid): # Use the same seed across all nodes particle_node.seed = particle_nodes[0].seed self.add_child(particle_node) particle_node.emitting = true particle_nodes.push_back(particle_node) last_pos = Vector3.ZERO func _set_offsets() -> void: var half_grid: int = grid_width / 2 offsets.clear() for x in range(-half_grid, half_grid + 1): for z in range(-half_grid, half_grid + 1): var offset := Vector3( float(x * rows) * instance_spacing, 0.0, float(z * rows) * instance_spacing ) offsets.append(offset) func _destroy_grid() -> void: for node: GPUParticles3D in particle_nodes: if is_instance_valid(node): node.queue_free() particle_nodes.clear() func _position_grid(pos: Vector3) -> void: for i in particle_nodes.size(): var node: GPUParticles3D = particle_nodes[i] var snap = Vector3(pos.x, 0, pos.z).snapped(Vector3.ONE) + offsets[i] node.global_position = (snap / instance_spacing).round() * instance_spacing node.reset_physics_interpolation() node.restart(true) # keep the same seed. func _update_process_parameters() -> void: if process_material: var process_rid: RID = process_material.get_rid() if terrain and process_rid.is_valid(): RenderingServer.material_set_param(process_rid, "_background_mode", terrain.material.world_background) RenderingServer.material_set_param(process_rid, "_vertex_spacing", terrain.vertex_spacing) RenderingServer.material_set_param(process_rid, "_vertex_density", 1.0 / terrain.vertex_spacing) RenderingServer.material_set_param(process_rid, "_region_size", terrain.region_size) RenderingServer.material_set_param(process_rid, "_region_texel_size", 1.0 / terrain.region_size) RenderingServer.material_set_param(process_rid, "_region_map_size", 32) RenderingServer.material_set_param(process_rid, "_region_map", terrain.data.get_region_map()) RenderingServer.material_set_param(process_rid, "_region_locations", terrain.data.get_region_locations()) RenderingServer.material_set_param(process_rid, "_height_maps", terrain.data.get_height_maps_rid()) RenderingServer.material_set_param(process_rid, "_control_maps", terrain.data.get_control_maps_rid()) RenderingServer.material_set_param(process_rid, "_color_maps", terrain.data.get_color_maps_rid()) RenderingServer.material_set_param(process_rid, "instance_spacing", instance_spacing) RenderingServer.material_set_param(process_rid, "instance_rows", rows) RenderingServer.material_set_param(process_rid, "max_dist", min_draw_distance) ================================================ FILE: project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd.uid ================================================ uid://bp7r4ppgq1m0g ================================================ FILE: project/addons/terrain_3d/extras/shaders/M_ocean.tres ================================================ [gd_resource type="ShaderMaterial" load_steps=4 format=3 uid="uid://cgk4glwmc4vk2"] [ext_resource type="Shader" uid="uid://biawd3fhk5weg" path="res://addons/terrain_3d/extras/shaders/ocean_shader.gdshader" id="1_t7ju5"] [sub_resource type="FastNoiseLite" id="FastNoiseLite_m6kh4"] noise_type = 2 frequency = 0.029 fractal_type = 2 fractal_octaves = 3 fractal_gain = 0.195 cellular_return_type = 3 [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_i2r35"] generate_mipmaps = false noise = SubResource("FastNoiseLite_m6kh4") seamless = true [resource] render_priority = 0 shader = ExtResource("1_t7ju5") shader_parameter/sea_level = 5.000000819565997 shader_parameter/water_color = Color(0.1058, 0.1568, 0.2117, 1) shader_parameter/visible_depth = 256.0 shader_parameter/depth_curve = 0.1 shader_parameter/shore_blend = 0.999999977648 shader_parameter/refraction_strength = 0.02 shader_parameter/time_scale = 8.0 shader_parameter/height_scale = 3.0 shader_parameter/ocean_scale = 2.0 shader_parameter/fresnel_scale = 0.5 shader_parameter/fresnel_power = 1.0 shader_parameter/specular = 1.0 shader_parameter/foam_enabled = true shader_parameter/foam_height = 1.350000064125 shader_parameter/foam_top_intensity = 2.0 shader_parameter/foam_distance = 3072.00014591674 shader_parameter/foam_shore_enabled = true shader_parameter/foam_shore_intensity = 1.3000000584901201 shader_parameter/foam_noise_texture = SubResource("NoiseTexture2D_i2r35") shader_parameter/foam_noise_scale = 1.5 shader_parameter/light_scattering_enabled = true shader_parameter/height_scattering = 6.000000285 shader_parameter/height_threshold = 0.1 shader_parameter/height_angle_threshold = 0.21 shader_parameter/fersnel_scattering = 20.0 shader_parameter/direct_scattering = 10.0 shader_parameter/_mesh_size = 32.0 shader_parameter/_subdiv = 1.0 shader_parameter/_vertex_spacing = 4.0 shader_parameter/_vertex_density = 0.25 shader_parameter/_target_pos = Vector3(0, 0, 0) shader_parameter/_light_direction = Vector3(0, 0.7671652, 0.64144963) shader_parameter/_light_color = Color(1, 1, 1, 1) ================================================ FILE: project/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // This shader snippet draws a hex grid // To use it, add this line to the top of your shader: // #include "res://addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc" // And this line at the bottom of your shader: // draw_hex_grid(uv2, _region_texel_size, w_normal, ALBEDO); mat2 rotate2d(float _angle) { return mat2(vec2(cos(_angle),-sin(_angle)), vec2(sin(_angle), cos(_angle))); } void draw_hex_grid(vec2 uv, float texel_size, vec3 normal, inout vec3 albedo) { float hex_size = 0.02; float line_thickness = 0.04; vec2 guv = (uv - vec2(0.5 * texel_size)) / hex_size; // Convert UV to axial hex coordinates float q = (sqrt(3.0) / 3.0 * guv.x - 1.0 / 3.0 * guv.y); float r = (2.0 / 3.0 * guv.y); // Cube coordinates for the hex (q, r, -q-r) float x = q; float z = r; float y = -x - z; // Round to the nearest hex center vec3 rounded = round(vec3(x, y, z)); vec3 diff = abs(vec3(x, y, z) - rounded); // Fix rounding errors if (diff.x > diff.y && diff.x > diff.z) { rounded.x = -rounded.y - rounded.z; } else if (diff.y > diff.z) { rounded.y = -rounded.x - rounded.z; } else { rounded.z = -rounded.x - rounded.y; } // Find the hex center in UV space vec2 hex_center = vec2( sqrt(3.0) * rounded.x + sqrt(3.0) / 2.0 * rounded.z, 3.0 / 2.0 * rounded.z ); // Relative position within the hex vec2 local_pos = guv - hex_center; vec2 lines_uv = local_pos; float line = 1.0; for (int i = 0; i < 6; i++) { vec2 luv = lines_uv * rotate2d(radians(60.0 * float(i) + 30.0)); float dist = abs(dot(luv + vec2(0.90), vec2(0.0, 1.0))); line = min(line, dist); } // Filter lines by slope float slope = 4.; // Can also assign to (auto_slope * 4.) to match grass placement float slope_factor = clamp(dot(vec3(0., 1., 0.), slope * (normal - 1.) + 1.), 0., 1.); // Draw hex grid albedo = mix(albedo, vec3(1.0), smoothstep(line_thickness + 0.02, line_thickness, line) * slope_factor); // Draw Hex center dot albedo = mix(albedo, vec3(0.0, 0.5, 0.5), smoothstep(0.11, 0.10, length(local_pos)) * slope_factor); } ================================================ FILE: project/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc.uid ================================================ uid://mri8pfoj2mfk ================================================ FILE: project/addons/terrain_3d/extras/shaders/lightweight.gdshader ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // This shader is the minimum needed to allow the terrain to function, without any texturing. shader_type spatial; render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform; /* This is an example stripped down shader with maximum performance in mind. * Only Autoshader/Base/Over/Blend/Holes/Colormap are supported. * Displacement is not enabled. Mesh Tesselation level must be set to 0. * All terrain normal calculations take place in vertex(). * * Control map indices are processed such that each ID only requires reading ONCE. * The following features: projection, detiling, and paintable rotation / scale * cannot work with this method, without the additional samples required for blending * between same ID textures with different values across indices. */ // Defined Constants #define COLOR_MAP vec4(1.0, 1.0, 1.0, 0.5) #define DIV_255 0.003921568627450 // 1. / 255. // Inline Functions #define DECODE_BLEND(control) float(control >>14u & 0xFFu) * DIV_255 #define DECODE_AUTO(control) bool(control & 0x1u) #define DECODE_BASE(control) int(control >>27u & 0x1Fu) #define DECODE_OVER(control) int(control >>22u & 0x1Fu) #define DECODE_HOLE(control) bool(control >>2u & 0x1u) #if CURRENT_RENDERER == RENDERER_COMPATIBILITY #define fma(a, b, c) ((a) * (b) + (c)) #define dFdxCoarse(a) dFdx(a) #define dFdyCoarse(a) dFdy(a) #endif // Private uniforms group_uniforms shader_uniforms; uniform vec3 _target_pos = vec3(0.f); uniform float _mesh_size = 48.f; uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2 uniform uint _mouse_layer = 0x80000000u; // Layer 32 uniform float _vertex_spacing = 1.0; uniform float _vertex_density = 1.0; // = 1./_vertex_spacing uniform float _subdiv = 1.0; uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1./region_size uniform int _region_map_size = 32; uniform int _region_map[1024]; uniform vec2 _region_locations[1024]; uniform float _texture_normal_depth_array[32]; uniform float _texture_ao_strength_array[32]; uniform float _texture_ao_affect_array[32]; uniform float _texture_roughness_mod_array[32]; uniform float _texture_uv_scale_array[32]; uniform vec4 _texture_color_array[32]; uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable; uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable; uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable; group_uniforms; // Public uniforms group_uniforms shader_uniforms.general; uniform float ground_level : hint_range(-1000., 1000.) = 0.0; uniform float region_blend : hint_range(.001, 1., 0.001) = 0.25; uniform bool flat_terrain_normals = false; uniform bool textures_enabled = true; uniform float blend_sharpness : hint_range(0, 1) = 0.5; group_uniforms; group_uniforms shader_uniforms.auto_shader; uniform float auto_slope : hint_range(0, 10) = 1.0; uniform float auto_height_reduction : hint_range(0, 1) = 0.1; uniform int auto_base_texture : hint_range(0, 31) = 0; uniform int auto_overlay_texture : hint_range(0, 31) = 1; group_uniforms; group_uniforms shader_uniforms.macro_variation; uniform bool macro_variation_enabled = true; uniform vec3 macro_variation1 : source_color = vec3(1.); uniform vec3 macro_variation2 : source_color = vec3(1.); uniform float macro_variation_slope : hint_range(0., 1.) = 0.333; uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable; uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x uniform float noise1_angle : hint_range(0, 6.283) = 0.; uniform vec2 noise1_offset = vec2(0.5); uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x group_uniforms; // Varyings & Types varying vec3 v_vertex; varying float v_vertex_xz_dist; varying vec3 v_normal; varying vec3 v_camera_pos; varying mat3 TBN; //////////////////////// // Vertex //////////////////////// // Takes in world space XZ (UV) coordinates // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region ivec3 get_index_coord(const vec2 uv) { vec2 r_uv = round(uv); ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1; return ivec3(ivec2(mod(r_uv, _region_size)), layer_index); } // Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with: // XY: (0. to 1.) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region vec3 get_index_uv(const vec2 uv2) { ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1; return vec3(uv2 - _region_locations[layer_index], float(layer_index)); } // Takes in world space XZ (UV) and returns if the coordinate should be part of the NONE background. bool is_none_bg(const vec2 uv) { ivec4 regions = ivec4( get_index_coord(uv - vec2(0.5, 0.0)).z, get_index_coord(uv - vec2(0.0, 0.5)).z, get_index_coord(uv + vec2(1.0, 0.0)).z, get_index_coord(uv + vec2(0.0, 1.0)).z); return any(equal(regions, ivec4(-1))); } // Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not. float check_region(const vec2 uv2) { ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); int layer_index = 0; if (uint(pos.x | pos.y) < uint(_region_map_size)) { layer_index = clamp(_region_map[ pos.y * _region_map_size + pos.x ] - 1, -1, 0) + 1; } return float(layer_index); } // Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions float get_region_blend(vec2 uv2) { uv2 -= 0.5; const vec2 offset = vec2(0.0, 1.0); float a = check_region(uv2 + offset.xy); float b = check_region(uv2 + offset.yy); float c = check_region(uv2 + offset.yx); float d = check_region(uv2 + offset.xx); vec2 w = smoothstep(vec2(0.0), vec2(1.0), fract(uv2)); float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y); return 1.0 - blend; } float interpolated_height(vec2 pos) { const vec2 offsets = vec2(0, 1); vec2 index_id = floor(pos); ivec3 index[4]; index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); float h0 = texelFetch(_height_maps, index[0], 0).r; float h1 = texelFetch(_height_maps, index[1], 0).r; float h2 = texelFetch(_height_maps, index[2], 0).r; float h3 = texelFetch(_height_maps, index[3], 0).r; vec2 f = fract(pos); vec2 i = 1.0 - f; vec4 w = vec4(i.x * f.y, f.x * f.y, f.x * i.y, i.x * i.y); float h = h0 * w[0] + h1 * w[1] + h2 * w[2] + h3 * w[3]; return h; } void vertex() { // Save Camera Position to varying for access in later functions v_camera_pos = MAIN_CAM_INV_VIEW_MATRIX[3].xyz; // Get vertex of flat plane in world coordinates and set world UV v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // Distance from target node to vertex on a flat plane v_vertex_xz_dist = length(v_vertex.xz - _target_pos.xz); // Geomorph vertex across clipmap LODs, set end and start for linear height interpolate float scale = MODEL_MATRIX[0][0]; float inv_scale = 1.0 / scale; float max_xz = max(abs(v_vertex.x - _target_pos.x), abs(v_vertex.z - _target_pos.z)); float vertex_lerp = smoothstep(0.0, 1.0, (max_xz * inv_scale - _mesh_size - 4.0) / (_mesh_size - 4.0)); vec2 vertex_fract = fract(VERTEX.xz * 0.5) * 2.0; // For LOD0 morph from a regular grid to an alternating grid to align with LOD1+ vec2 shift = (scale < _vertex_spacing / _subdiv + 1e-6) ? // LOD0 or not // Shift from regular to symmetric mix(vertex_fract, vec2(vertex_fract.x, -vertex_fract.y), round(fract(round(mod(v_vertex.z * inv_scale, 4.0)) * round(mod(v_vertex.x * inv_scale, 4.0)) * 0.25))) : // Symmetric shift vertex_fract * round((fract(v_vertex.xz * 0.25 * inv_scale) - 0.5) * 4.0); vec2 start_pos = v_vertex.xz * _vertex_density; vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density; v_vertex.xz -= shift * scale * vertex_lerp; // UV coordinates in region space. 0-1 covers 1 region, 1-2 is the next region, etc. UV = v_vertex.xz * _vertex_density; // UV coordinates in region space + texel offset. Values are 0 to 1 within regions UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size)); // Discard vertices for Holes. 1 lookup ivec3 v_region = get_index_coord(start_pos); uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r; bool hole = DECODE_HOLE(control); // Show holes to all cameras except mouse camera (on exactly 1 layer) if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) && (hole || (_background_mode == 0u && is_none_bg(UV)))) { v_vertex.x = 0. / 0.; } else { // Set final vertex height, and vertex normal. float h, u, v; // This branch is static for each of the clipmap segments // Interpolated reads only occur where sub-texel values are required. if (scale < _vertex_spacing) { h = interpolated_height(UV); u = interpolated_height(UV + vec2(1, 0)); v = interpolated_height(UV + vec2(0, 1)); } else { ivec3 coord_a = get_index_coord(start_pos); ivec3 coord_b = get_index_coord(end_pos); ivec3 coord_ua = get_index_coord(start_pos + vec2(1, 0)); ivec3 coord_ub = get_index_coord(end_pos + vec2(1, 0)); ivec3 coord_va = get_index_coord(start_pos + vec2(0, 1)); ivec3 coord_vb = get_index_coord(end_pos + vec2(0, 1)); h = mix(texelFetch(_height_maps, coord_a, 0).r, texelFetch(_height_maps, coord_b, 0).r, vertex_lerp); u = mix(texelFetch(_height_maps, coord_ua, 0).r, texelFetch(_height_maps, coord_ub, 0).r, vertex_lerp); v = mix(texelFetch(_height_maps, coord_va, 0).r, texelFetch(_height_maps, coord_vb, 0).r, vertex_lerp); } // Apply background ground level and region blend vec2 ruv = UV * _region_texel_size; h += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(ruv)); u += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(ruv + vec2(_region_texel_size, 0.))); v += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(ruv + vec2(0., _region_texel_size))); v_vertex.y = h; v_normal = normalize(vec3(h - u, _vertex_spacing, h - v)); } // Convert model space to view space w/ skip_vertex_transform render mode VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz; // Apply terrain normals vec3 w_tangent = normalize(cross(v_normal, vec3(0.0, 0.0, 1.0))); vec3 w_binormal = normalize(cross(v_normal, w_tangent)); TBN = mat3(w_tangent, w_binormal, v_normal); NORMAL = normalize((VIEW_MATRIX * vec4(v_normal, 0.0)).xyz); BINORMAL = normalize((VIEW_MATRIX * vec4(w_binormal, 0.0)).xyz); TANGENT = normalize((VIEW_MATRIX * vec4(w_tangent, 0.0)).xyz); } //////////////////////// // Fragment //////////////////////// mat2 rotate_plane(float angle) { float c = cos(angle), s = sin(angle); return mat2(vec2(c, s), vec2(-s, c)); } void fragment() { // Recover UVs vec2 uv = UV; vec2 uv2 = UV2; // Lookup offsets, ID and blend weight vec3 region_uv = get_index_uv(uv2); const vec3 offsets = vec3(0, 1, 2); vec2 index_id = floor(uv); vec2 weight = fract(uv); vec2 invert = 1.0 - weight; vec4 weights = vec4( invert.x * weight.y, // 0 weight.x * weight.y, // 1 weight.x * invert.y, // 2 invert.x * invert.y // 3 ); ivec3 index[4]; // control map lookups, used for some normal lookups as well index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); vec3 base_ddx = dFdxCoarse(v_vertex); vec3 base_ddy = dFdyCoarse(v_vertex); vec4 base_dd = vec4(base_ddx.xz, base_ddy.xz); // Calculate the effective mipmap for regionspace float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density); // Color map vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP; if (flat_terrain_normals) { NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX))); TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz)); BINORMAL = normalize(cross(NORMAL, TANGENT)); } // defaults vec4 normal_rough = vec4(0., 1., 0., 0.7); vec4 albedo_height = vec4(1.); float normal_map_depth = 1.; float ao = 1.0; float ao_affect = 0.; if (textures_enabled) { // set to zero before accumulation albedo_height = vec4(0.); normal_rough = vec4(0.); normal_map_depth = 0.; ao = 0.; float total_weight = 0.; float sharpness = fma(56., blend_sharpness, 8.); // Get index control data // 1 - 4 lookups uvec4 control = floatBitsToUint(vec4( texelFetch(_control_maps, index[0], 0).r, texelFetch(_control_maps, index[1], 0).r, texelFetch(_control_maps, index[2], 0).r, texelFetch(_control_maps, index[3], 0).r)); { // Auto blend calculation float auto_blend = clamp(fma(auto_slope * 2.0, (v_normal.y - 1.0), 1.0) - auto_height_reduction * 0.01 * v_vertex.y, 0.0, 1.0); // Enable Autoshader if outside regions or painted in regions, otherwise manual painted uvec4 is_auto = (control & uvec4(0x1u)) | uvec4(lessThan(ivec4(index[0].z, index[1].z, index[2].z, index[3].z), ivec4(0))); uint u_auto = ((uint(auto_base_texture) & 0x1Fu) << 27u) | ((uint(auto_overlay_texture) & 0x1Fu) << 22u) | ((uint(fma(auto_blend, 255.0 , 0.5)) & 0xFFu) << 14u); control = control * (1u - is_auto) + u_auto * is_auto; } // Texture weights // Vectorised Deocode of all texture IDs, then swizzle to per index mapping. ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)), ivec4(control >> uvec4(22u) & uvec4(0x1Fu))}; ivec2 texture_ids[4] = ivec2[4]( ivec2(t_id[0].x, t_id[1].x), ivec2(t_id[0].y, t_id[1].y), ivec2(t_id[0].z, t_id[1].z), ivec2(t_id[0].w, t_id[1].w)); // interpolated weights. vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255 * weights; vec4 weights_id_0 = weights - weights_id_1; vec2 t_weights[4] = {vec2(0), vec2(0), vec2(0), vec2(0)}; for (int i = 0; i < 4; i++) { vec2 w_0 = vec2(weights_id_0[i]); vec2 w_1 = vec2(weights_id_1[i]); ivec2 id_0 = texture_ids[i].xx; ivec2 id_1 = texture_ids[i].yy; t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1))); t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1))); t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1))); t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1))); } // Process control data to determine each texture ID present, so that only // a single sample will be needed later, as all id are contiguous when features // like detiling, scale, rotation, and projection are not present. // 2 to 16 lookups uint id_read = 0u; // 1 bit per possible ID // world normal adjustment requires acess to previous id during next iteration vec4 nrm = vec4(0.0, 1.0, 0.0, 1.0); vec2 world_uv = v_vertex.xz; for (int i = 0; i < 4; i++) { for (int t = 0; t < 2; t++) { int id = texture_ids[i][t]; uint mask = 1u << uint(id); if ((id_read & mask) == 0u) { // Set this id bit id_read |= mask; float id_w = t_weights[i][t]; float id_scale = _texture_uv_scale_array[id] * 0.5; vec2 id_uv = fma(world_uv, vec2(id_scale), vec2(0.5)); vec4 i_dd = base_dd * id_scale; vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw); float world_normal = clamp(fma(TBN[0], vec3(nrm.x), fma(TBN[1], vec3(nrm.z), v_normal * vec3(nrm.y))).y, 0., 1.); nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw); alb.rgb *= _texture_color_array[id].rgb; nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.); // Unpack normal map for blending. nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0)); float id_ao = length(nrm.xyz) * 2.0 - 1.0; id_ao = mix(id_ao * id_ao * _texture_ao_strength_array[id] + 1.0 - _texture_ao_strength_array[id], 1.0, alb.a * alb.a); // height weight modifier. float id_weight = exp2(sharpness * log2(id_w + alb.a * world_normal)); albedo_height += alb * id_weight; normal_rough += nrm * id_weight; normal_map_depth += _texture_normal_depth_array[id] * id_weight; ao_affect += _texture_ao_affect_array[id] * id_weight; ao += id_ao * id_weight; total_weight += id_weight; } } } // normalize accumulated values back to 0.0 - 1.0 range. float weight_inv = 1.0 / max(total_weight, 1e-8); albedo_height *= weight_inv; normal_rough *= weight_inv; normal_map_depth *= weight_inv; ao_affect *= weight_inv; ao *= weight_inv; } // Macro variation. 2 lookups vec3 macrov = vec3(1.); if (macro_variation_enabled) { float noise1 = texture(noise_texture, (uv * noise1_scale * .1 + noise1_offset) * rotate_plane(noise1_angle)).r; float noise2 = texture(noise_texture, uv * noise2_scale * .1).r; macrov = mix(macro_variation1, vec3(1.), noise1); macrov *= mix(macro_variation2, vec3(1.), noise2); macrov = mix(vec3(1.0), macrov, clamp(v_normal.y + macro_variation_slope, 0., 1.)); } // Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range, clamped to Godot roughness values float roughness = clamp(fma(color_map.a - 0.5, 2.0, normal_rough.a), 0., 1.); // Apply PBR ALBEDO = albedo_height.rgb * color_map.rgb * macrov; ROUGHNESS = roughness; SPECULAR = 1. - normal_rough.a; // Repack final normal map value. NORMAL_MAP = fma(normalize(normal_rough.xzy), vec3(0.5), vec3(0.5)); NORMAL_MAP_DEPTH = normal_map_depth; AO = clamp(ao, 0., 1.); AO_LIGHT_AFFECT = ao_affect; } ================================================ FILE: project/addons/terrain_3d/extras/shaders/lightweight.gdshader.uid ================================================ uid://bbx2xhanpq5l3 ================================================ FILE: project/addons/terrain_3d/extras/shaders/minimum.gdshader ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // This shader is the minimum needed to allow the terrain to function, without any texturing. shader_type spatial; render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform; #if CURRENT_RENDERER == RENDERER_COMPATIBILITY #define fma(a, b, c) ((a) * (b) + (c)) #define dFdxCoarse(a) dFdx(a) #define dFdyCoarse(a) dFdy(a) #endif // Private uniforms // Commented uniforms aren't needed for this shader, but are available for your own needs. group_uniforms shader_uniforms; uniform vec3 _target_pos = vec3(0.f); uniform float _mesh_size = 48.f; uniform float _subdiv = 1.f; uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2 uniform uint _mouse_layer = 0x80000000u; // Layer 32 uniform float _vertex_spacing = 1.0; uniform float _vertex_density = 1.0; // = 1./_vertex_spacing uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1./region_size uniform int _region_map_size = 32; uniform int _region_map[1024]; //uniform vec2 _region_locations[1024]; //uniform float _texture_normal_depth_array[32]; //uniform float _texture_ao_strength_array[32]; //uniform float _texture_ao_affect_array[32]; //uniform float _texture_roughness_mod_array[32]; //uniform float _texture_uv_scale_array[32]; //uniform vec2 _texture_detile_array[32]; //uniform vec4 _texture_color_array[32]; uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; //uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable; //uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable; //uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable; group_uniforms; // Public uniforms group_uniforms shader_uniforms.general; uniform bool flat_terrain_normals = false; group_uniforms; // Varyings & Types // Some are required for editor functions varying vec3 v_vertex; varying float v_vertex_xz_dist; varying vec3 v_camera_pos; //////////////////////// // Vertex //////////////////////// // Takes in world space XZ (UV) coordinates // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region ivec3 get_index_coord(const vec2 uv) { vec2 r_uv = round(uv); ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1; return ivec3(ivec2(mod(r_uv, _region_size)), layer_index); } // Takes in world space XZ (UV) and returns if the coordinate should be part of the NONE background. bool is_none_bg(const vec2 uv) { ivec4 regions = ivec4( get_index_coord(uv - vec2(0.5, 0.0)).z, get_index_coord(uv - vec2(0.0, 0.5)).z, get_index_coord(uv + vec2(1.0, 0.0)).z, get_index_coord(uv + vec2(0.0, 1.0)).z); return any(equal(regions, ivec4(-1))); } float interpolated_height(vec2 pos) { const vec2 offsets = vec2(0, 1); vec2 index_id = floor(pos); ivec3 index[4]; index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); float h0 = texelFetch(_height_maps, index[0], 0).r; float h1 = texelFetch(_height_maps, index[1], 0).r; float h2 = texelFetch(_height_maps, index[2], 0).r; float h3 = texelFetch(_height_maps, index[3], 0).r; vec2 f = fract(pos); vec2 i = 1.0 - f; vec4 w = vec4(i.x * f.y, f.x * f.y, f.x * i.y, i.x * i.y); float h = h0 * w[0] + h1 * w[1] + h2 * w[2] + h3 * w[3]; return h; } void vertex() { // Save Camera Position to varying for access in later functions v_camera_pos = MAIN_CAM_INV_VIEW_MATRIX[3].xyz; // Get vertex of flat plane in world coordinates and set world UV v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // Distance from target node to vertex on a flat plane v_vertex_xz_dist = length(v_vertex.xz - _target_pos.xz); // Geomorph vertex across clipmap LODs, set end and start for linear height interpolate float scale = MODEL_MATRIX[0][0]; float inv_scale = 1.0 / scale; float max_xz = max(abs(v_vertex.x - _target_pos.x), abs(v_vertex.z - _target_pos.z)); float vertex_lerp = smoothstep(0.0, 1.0, (max_xz * inv_scale - _mesh_size - 4.0) / (_mesh_size - 4.0)); vec2 vertex_fract = fract(VERTEX.xz * 0.5) * 2.0; // For LOD0 morph from a regular grid to an alternating grid to align with LOD1+ vec2 shift = (scale < _vertex_spacing / _subdiv + 1e-6) ? // LOD0 or not // Shift from regular to symmetric mix(vertex_fract, vec2(vertex_fract.x, -vertex_fract.y), round(fract(round(mod(v_vertex.z * inv_scale, 4.0)) * round(mod(v_vertex.x * inv_scale, 4.0)) * 0.25))) : // Symmetric shift vertex_fract * round((fract(v_vertex.xz * 0.25 * inv_scale) - 0.5) * 4.0); vec2 start_pos = v_vertex.xz * _vertex_density; vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density; v_vertex.xz -= shift * scale * vertex_lerp; // UV coordinates in region space. 0-1 covers 1 region, 1-2 is the next region, etc. UV = v_vertex.xz * _vertex_density; // UV coordinates in region space + texel offset. Values are 0 to 1 within regions UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size)); // Discard vertices for Holes. 1 lookup ivec3 v_region = get_index_coord(start_pos); uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r; bool hole = bool(control >>2u & 0x1u); // Show holes to all cameras except mouse camera (on exactly 1 layer) if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) && (hole || (_background_mode == 0u && is_none_bg(UV)))) { v_vertex.x = 0. / 0.; } else { // Set final vertex height. float h; // This branch is static for each of the clipmap segments // Interpolated reads only occur where sub-texel values are required. if (scale < _vertex_spacing) { h = interpolated_height(UV); } else { ivec3 coord_a = get_index_coord(start_pos); ivec3 coord_b = get_index_coord(end_pos); h = mix(texelFetch(_height_maps, coord_a, 0).r,texelFetch(_height_maps, coord_b, 0).r,vertex_lerp); } v_vertex.y = h; } // Convert model space to view space w/ skip_vertex_transform render mode VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz; NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz); TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz); } //////////////////////// // Fragment //////////////////////// void fragment() { // Recover UVs vec2 uv = UV; //vec2 uv2 = UV2; // Lookup offsets, ID and blend weight const vec3 offsets = vec3(0, 1, 2); vec2 index_id = floor(uv); vec2 weight = fract(uv); vec2 invert = 1.0 - weight; vec4 weights = vec4( invert.x * weight.y, // 0 weight.x * weight.y, // 1 weight.x * invert.y, // 2 invert.x * invert.y // 3 ); vec3 base_ddx = dFdxCoarse(v_vertex); vec3 base_ddy = dFdyCoarse(v_vertex); //vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz); // Calculate the effective mipmap for regionspace, and if less than 0, // skip all extra lookups required for bilinear blend. float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density); bool bilerp = region_mip < 4.0; ivec3 index[4]; // control map lookups, used for some normal lookups as well index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); // Terrain normals vec3 index_normal[4]; float h[4]; // allows additional derivatives, eg world noise, brush previews etc float u = 0.0; float v = 0.0; // Re-use index[] for the first lookups, skipping some math. 3 lookups h[3] = texelFetch(_height_maps, index[3], 0).r; // 0 (0,0) h[2] = texelFetch(_height_maps, index[2], 0).r; // 1 (1,0) h[0] = texelFetch(_height_maps, index[0], 0).r; // 2 (0,1) index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v)); // Set flat world normal - overriden if bilerp is true vec3 w_normal = index_normal[3]; // Branching smooth normals must be done seperatley for correct normals at all 4 index ids if (bilerp) { // 5 lookups // Fetch the additional required height values for smooth normals h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1,1) float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz), 0).r; // 4 (1,2) float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy), 0).r; // 5 (2,1) float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx), 0).r; // 6 (2,0) float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz), 0).r; // 7 (0,2) // Calculate the normal for the remaining index ids. index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v)); index_normal[1] = normalize(vec3(h[1] - h_5 + u, _vertex_spacing, h[1] - h_4 + v)); index_normal[2] = normalize(vec3(h[2] - h_6 + u, _vertex_spacing, h[2] - h[1] + v)); // Set interpolated world normal w_normal = index_normal[0] * weights[0] + index_normal[1] * weights[1] + index_normal[2] * weights[2] + index_normal[3] * weights[3] ; } // Apply terrain normals vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0))); vec3 w_binormal = normalize(cross(w_normal, w_tangent)); NORMAL = mat3(VIEW_MATRIX) * w_normal; TANGENT = mat3(VIEW_MATRIX) * w_tangent; BINORMAL = mat3(VIEW_MATRIX) * w_binormal; if (flat_terrain_normals) { NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX))); TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz)); BINORMAL = normalize(cross(NORMAL, TANGENT)); } // Apply PBR ALBEDO = vec3(.2); ROUGHNESS = .7; } ================================================ FILE: project/addons/terrain_3d/extras/shaders/minimum.gdshader.uid ================================================ uid://01qauauvd8aa ================================================ FILE: project/addons/terrain_3d/extras/shaders/ocean_shader.gdshader ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // This file is an example ocean shader. You should be able to use any ocean shader with slight modification // for the clipmap. See the note above vertex(). shader_type spatial; render_mode cull_back, depth_draw_always, diffuse_burley, specular_schlick_ggx, shadows_disabled, skip_vertex_transform; ////////////////////////////// // Constants ////////////////////////////// const int ITERATIONS_VERTEX = 1; const int ITERATIONS_FRAGMENT = 3; ////////////////////////////// // Uniforms ////////////////////////////// group_uniforms water; uniform float sea_level : hint_range(-50.0, 50.0, 0.001) = 1.0; uniform vec3 water_color : source_color = vec3(.1058, .1568, .2117); // This is a common deep blue //uniform vec3 water_color : source_color = vec3(0.102, 0.212, 0.235); // This is a more green ocean uniform float visible_depth : hint_range(0.001, 10000., .1) = 256.; uniform float depth_curve : hint_range(0.001, 5.0) = .1; uniform float shore_blend : hint_range(0.0, 10.0, 0.001) = 1.; uniform float refraction_strength : hint_range(0.0, .2, 0.001) = 0.02; group_uniforms; group_uniforms waves; uniform float time_scale : hint_range(0.0, 64.0) = 8.0; uniform float height_scale : hint_range(0.0, 16.0) = 3.0; uniform float ocean_scale : hint_range(0.1, 16.0) = 2.0; group_uniforms; group_uniforms reflections; uniform float fresnel_scale : hint_range(0., 1.) = .5; uniform float fresnel_power : hint_range(0., 10.) = 1.; uniform float specular : hint_range(0., 10.) = 1.; group_uniforms; group_uniforms foam; uniform bool foam_enabled = true; uniform float foam_height : hint_range(0., 5.0) = 2.5; uniform float foam_top_intensity : hint_range(.1, 100.0) = 2.0; uniform float foam_distance : hint_range(0.1, 16384.0) = 2048.; uniform bool foam_shore_enabled = true; uniform float foam_shore_intensity : hint_range(0.1, 100.0) = 2.; uniform sampler2D foam_noise_texture; uniform float foam_noise_scale : hint_range(0.1, 5.0) = 1.5; group_uniforms; group_uniforms light_scattering; uniform bool light_scattering_enabled = true; uniform float height_scattering : hint_range(0.0, 15.0) = 3.0; uniform float height_threshold : hint_range(0.0, 1.0) = 0.1; uniform float height_angle_threshold : hint_range(0.0, 1.0) = 0.21; uniform float fersnel_scattering : hint_range(0.0, 70.0) = 20.0; uniform float direct_scattering : hint_range(0.0, 70.0) = 10.0; group_uniforms; // Uniforms set by C++ group_uniforms private_set_by_terrain; uniform float _mesh_size = 32.; uniform float _subdiv = 1.; uniform float _vertex_spacing = 1.; uniform float _vertex_density = 1.; uniform vec3 _target_pos = vec3(0.,0.,0.); uniform vec3 _light_direction = vec3(-0.196, 0.980, 0); uniform vec3 _light_color : source_color = vec3(1.0, 1.0, .735); group_uniforms; uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; varying vec3 v_vertex; varying float v_vertex_distance; ////////////////////////////// // Ocean Noise ////////////////////////////// float hash(vec2 p) { uvec2 q = uvec2(ivec2(p)) * uvec2(1597334673u, 3812015801u); uint n = (q.x ^ q.y) * 1597334673u; return float(n) * (1.0 / float(0xffffffffu)); } float noise(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); vec2 u = smoothstep(0., 1., f); vec2 i1 = i + 1.0; float a = hash(i); float b = hash(vec2(i1.x, i.y)); float c = hash(vec2(i.x, i1.y)); float d = hash(i1); return 2.0 * mix(mix(a, b, u.x), mix(c, d, u.x), u.y) - 1.0; } float octave(vec2 uv) { uv += noise((uv) * 0.5) * 2.; vec2 wave = 1.0 - abs(sin(uv)); // Shaping float v = wave.x * wave.y + 1e-6; float s = v * inversesqrt(v); return 1.0 - 2.0 * s + v; } float ocean_height(vec2 pos, int iterations) { float freq = 0.03 * ocean_scale; float amp = 4.0; vec2 uv = pos; float h = 0.0; float t = TIME * time_scale; for (int i = 0; i < iterations; i++) { h += (octave((uv + t) * freq) + octave((uv - t) * freq)) * amp; uv *= mat2(vec2(1.8, 0.8), vec2(-0.8, 1.8)); freq *= 1.9; amp *= 0.2; } return h * 0.1 * height_scale; } ////////////////////////////// // Vertex // // vertex() positions the vertices of the clipmap mesh across the geomorphed lods. You'll need the // first two blocks for any other water shader, from the start to setting v_vertex.xz. After that // your own shader can set the UV and v_vertex.Y however you like. ////////////////////////////// void vertex() { // Get vertex of flat plane in world coordinates and set world UV v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // Geomorph vertex across clipmap LODs float scale = MODEL_MATRIX[0][0]; float inv_scale = 1.0 / scale; float max_xz = max(abs(v_vertex.x - _target_pos.x), abs(v_vertex.z - _target_pos.z)); float vertex_lerp = smoothstep(0.0, 1.0, (max_xz * inv_scale - _mesh_size - 4.0) / (_mesh_size - 4.0)); vec2 vertex_fract = fract(VERTEX.xz * 0.5) * 2.0; // For LOD0 morph from a regular grid to an alternating grid to align with LOD1+ vec2 shift = (scale < _vertex_spacing / _subdiv + 1e-6) ? // LOD0 or not // Shift from regular to symmetric mix(vertex_fract, vec2(vertex_fract.x, -vertex_fract.y), round(fract(round(mod(v_vertex.z * inv_scale, 4.0)) * round(mod(v_vertex.x * inv_scale, 4.0)) * 0.25))) : // Symmetric shift vertex_fract * round((fract(v_vertex.xz * 0.25 * inv_scale) - 0.5) * 4.0); v_vertex.xz -= shift * scale * vertex_lerp; // UV coordinates in region space. 0-1 covers 1 region, 1-2 is the next region, etc. UV = v_vertex.xz; // Set height according to waves v_vertex.y = sea_level + ocean_height(UV, ITERATIONS_VERTEX); VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz; v_vertex_distance = length(v_vertex - _target_pos); } ////////////////////////////// // Fragment ////////////////////////////// void fragment() { // Normals float wave_height, u, v; // Adjust normal domain as screen space distance increases to reduce high frequency values across pixels float dv = max(0.25, length(fwidth(VERTEX))); vec2 n_uv = UV + dv * -0.66; wave_height = ocean_height(n_uv, ITERATIONS_FRAGMENT); u = ocean_height(n_uv + vec2(dv, 0.0), ITERATIONS_FRAGMENT); v = ocean_height(n_uv + vec2(0.0, dv), ITERATIONS_FRAGMENT); const float wave_distance = 512.; vec3 world_normal = normalize(vec3(wave_height - u, max(.1, v_vertex_distance/wave_distance), wave_height - v)); NORMAL = mat3(VIEW_MATRIX) * world_normal; TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz)); BINORMAL = normalize(cross(NORMAL, TANGENT)); // Horizon mask: 1.0 when light is above horizon w/ fade at edge float daytime_mask = smoothstep(0., .1, _light_direction.y); // Reflections float cos_theta = max(0.0, dot(NORMAL, VIEW)); // 1.0 when perpendicular (looking straight down), ~0.0 grazing float fresnel_base = pow(1.0 - cos_theta, fresnel_power); // high at grazing, low straight down float fresnel = clamp(fresnel_scale * fresnel_base, 0.0, 1.0); ROUGHNESS = mix(0.02, 0.8, fresnel) * daytime_mask; SPECULAR = specular * fresnel; SPECULAR = clamp (SPECULAR - .5 * (1. - daytime_mask), 0.15, 1.); // Mask far distance when sun is below horizon // Terrain pixel position float depth_raw = textureLod(depth_texture, SCREEN_UV, 0.0).r; vec4 clip_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_raw, 1.0); vec3 view_pos = clip_pos.xyz / clip_pos.w; vec4 world_pos_hom = INV_VIEW_MATRIX * vec4(view_pos, 1.0); vec3 terrain_position = world_pos_hom.xyz / world_pos_hom.w; // Shore mask: 1.0 near the shore line float shore_mask = 1.0 - clamp(1.0 - smoothstep(view_pos.z + 2. * shore_blend, view_pos.z, VERTEX.z), 0.0, 1.0); ALPHA = 1.0 - shore_mask; // Albedo accumulation: starting with water and sunlight colors vec3 light_mult = light_scattering_enabled ? _light_color : vec3(1.0); ALBEDO = water_color * light_mult; // Refraction offset vec2 refract_offset = NORMAL.xz * refraction_strength; float vp_y_mask = 1.0 - smoothstep(0.0, length(refract_offset) * 2.0, 1.0 - SCREEN_UV.y); refract_offset = mix(refract_offset, vec2(0.0), max(shore_mask, vp_y_mask)); vec2 refract_uv = SCREEN_UV + refract_offset; refract_uv = clamp(refract_uv, vec2(0.001, 0.001), vec2(0.999, 0.999)); // Sample refracted terrain depth & pixel position float refract_depth_raw = textureLod(depth_texture, refract_uv, 0.0).r; vec4 refract_clip_pos = INV_PROJECTION_MATRIX * vec4(refract_uv * 2.0 - 1.0, refract_depth_raw, 1.0); vec3 refract_view_pos = refract_clip_pos.xyz / refract_clip_pos.w; vec4 refract_world_hom = INV_VIEW_MATRIX * vec4(refract_view_pos, 1.0); vec3 refract_world_pos = refract_world_hom.xyz / refract_world_hom.w; // Filter out pixels above the water surface (ie player's head), or beyond the visible depth bool pixel_above_water = refract_world_pos.y >= v_vertex.y; bool in_refraction_depth = VERTEX.z < refract_view_pos.z + 2. * visible_depth; bool use_refracted = !pixel_above_water && in_refraction_depth; vec2 final_refract_uv = use_refracted ? refract_uv : SCREEN_UV; // Sample undersea meshes vec3 background_color = texture(screen_texture, final_refract_uv).rgb; // Depth fade based on vertical depth, with curve // The minimum amount helps to avoid ghosting around the player under the water float water_depth = max(20., sea_level + v_vertex.y - terrain_position.y); float normalized_depth = water_depth / visible_depth; float depth_fade = pow(clamp(normalized_depth, 0.0, 1.0), depth_curve); ALBEDO = mix(background_color, ALBEDO, depth_fade); // Foam float foam_mask = 0.0; if (foam_enabled) { float detail_wave = wave_height * 0.005 ; float foam_noise = texture(foam_noise_texture, UV * .1 * foam_noise_scale).r; foam_mask = clamp(wave_height * 0.5 + (detail_wave * foam_top_intensity + float(foam_shore_enabled) * shore_mask * foam_shore_intensity) * foam_noise * 1.5 - foam_height, 0.0, 1.0); float dist_fade = 1. - smoothstep(0., 1., v_vertex_distance / foam_distance); foam_mask *= dist_fade; ALBEDO = mix(ALBEDO, vec3(0.8), foam_mask); } // Light scattering if (light_scattering_enabled) { vec3 ray_dir = normalize(mat3(INV_VIEW_MATRIX) * VIEW); // Light scattering based on height and sun angle vec3 light_factor = height_scattering * max(wave_height, height_threshold) * max(pow(dot(_light_direction, -ray_dir), 4.0), height_angle_threshold) * _light_color * pow(0.5 - 0.5 * dot(_light_direction, world_normal), 3.0); // Light scattering based on view angle light_factor += fersnel_scattering * pow(dot(ray_dir, world_normal), 2.0) * _light_color * water_color; // Direct light scattering light_factor += direct_scattering * dot(_light_direction, world_normal) * water_color * _light_color; // Apply horizon fade to the whole scattering BACKLIGHT = light_factor * 4.0 * clamp(depth_fade, 0.4, 1.0) * max(1.0 - foam_mask, 0.5) * daytime_mask; } } ================================================ FILE: project/addons/terrain_3d/extras/shaders/ocean_shader.gdshader.uid ================================================ uid://biawd3fhk5weg ================================================ FILE: project/addons/terrain_3d/icons/autoshader.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bdwolwswwy8wr" path="res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/autoshader.svg" dest_files=["res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/color_paint.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://krrmpalen8xu" path="res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/color_paint.svg" dest_files=["res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_add.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bcmbqryggekg1" path="res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_add.svg" dest_files=["res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_div.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://danh7tb2v6rx7" path="res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_div.svg" dest_files=["res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_flat.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://crj0xfyiyr45u" path="res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_flat.svg" dest_files=["res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_mul.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bu3q0645kb3el" path="res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_mul.svg" dest_files=["res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_slope.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://0cd7so4kw7da" path="res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_slope.svg" dest_files=["res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_smooth.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://chrbx4xnxyiel" path="res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_smooth.svg" dest_files=["res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/height_sub.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://mo3hnbk3ffjs" path="res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/height_sub.svg" dest_files=["res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/holes.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bsmaxekrmnuy2" path="res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/holes.svg" dest_files=["res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/layers.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://cs1la1mashf2e" path="res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/layers.svg" dest_files=["res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/multimesh.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://cjlcl5lf20ve0" path="res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/multimesh.svg" dest_files=["res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/navigation.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://f3po5pogkv2b" path="res://.godot/imported/navigation.svg-1e4cf210c589be8d2911c522d4a17d78.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/navigation.svg" dest_files=["res://.godot/imported/navigation.svg-1e4cf210c589be8d2911c522d4a17d78.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/picker_checked.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bg8x6o32ggt88" path="res://.godot/imported/picker_checked.svg-81f35b6ae38bccc8aa9e7ae22b530168.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/picker_checked.svg" dest_files=["res://.godot/imported/picker_checked.svg-81f35b6ae38bccc8aa9e7ae22b530168.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/region_add.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://c0tn453fsckv5" path="res://.godot/imported/region_add.svg-a05dc161a452dd3e024f9835a737d9f0.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/region_add.svg" dest_files=["res://.godot/imported/region_add.svg-a05dc161a452dd3e024f9835a737d9f0.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/region_remove.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://cbpo5eamf3bx2" path="res://.godot/imported/region_remove.svg-5710e8aeb34f1eaa06e637634f4a7d16.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/region_remove.svg" dest_files=["res://.godot/imported/region_remove.svg-5710e8aeb34f1eaa06e637634f4a7d16.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/terrain3d.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bnsydn4jkyeyn" path="res://.godot/imported/terrain3d.svg-eb45756f1a003759fda81eaa1db10769.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/terrain3d.svg" dest_files=["res://.godot/imported/terrain3d.svg-eb45756f1a003759fda81eaa1db10769.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/texture_paint.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://duo8valena3a2" path="res://.godot/imported/texture_paint.svg-72da4fd2096377e625a8fe09cdacb0e4.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/texture_paint.svg" dest_files=["res://.godot/imported/texture_paint.svg-72da4fd2096377e625a8fe09cdacb0e4.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/texture_spray.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://16yfxe7xe703" path="res://.godot/imported/texture_spray.svg-326fee11cf418653e621bc222a470861.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/texture_spray.svg" dest_files=["res://.godot/imported/texture_spray.svg-326fee11cf418653e621bc222a470861.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/icons/wetness.svg.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://bcgg0srmqsh3n" path="res://.godot/imported/wetness.svg-9b2ddec096ab7734492b77b20c75c82b.ctex" metadata={ "has_editor_variant": true, "vram_texture": false } [deps] source_file="res://addons/terrain_3d/icons/wetness.svg" dest_files=["res://.godot/imported/wetness.svg-9b2ddec096ab7734492b77b20c75c82b.ctex"] [params] compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 svg/scale=1.0 editor/scale_with_editor_scale=true editor/convert_colors_with_editor_theme=false ================================================ FILE: project/addons/terrain_3d/menu/bake_lod_dialog.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Bake LOD Dialog for Terrain3D @tool extends ConfirmationDialog var lod: int = 0 var description: String = "" func _ready() -> void: set_unparent_when_invisible(true) about_to_popup.connect(_on_about_to_popup) visibility_changed.connect(_on_visibility_changed) %LodBox.value_changed.connect(_on_lod_box_value_changed) func _on_about_to_popup() -> void: lod = %LodBox.value func _on_visibility_changed() -> void: # Change text on the autowrap label only when the popup is visible. # Works around Godot issue #47005: # https://github.com/godotengine/godot/issues/47005 if visible: %DescriptionLabel.text = description func _on_lod_box_value_changed(p_value: float) -> void: lod = %LodBox.value ================================================ FILE: project/addons/terrain_3d/menu/bake_lod_dialog.gd.uid ================================================ uid://cqmt8f5x5c2ad ================================================ FILE: project/addons/terrain_3d/menu/bake_lod_dialog.tscn ================================================ [gd_scene load_steps=2 format=3 uid="uid://bhvrrmb8bk1bt"] [ext_resource type="Script" uid="uid://cqmt8f5x5c2ad" path="res://addons/terrain_3d/menu/bake_lod_dialog.gd" id="1_sf76d"] [node name="bake_lod_dialog" type="ConfirmationDialog"] title = "Bake Terrain3D Mesh" position = Vector2i(0, 36) size = Vector2i(400, 155) visible = true script = ExtResource("1_sf76d") [node name="MarginContainer" type="MarginContainer" parent="."] offset_left = 8.0 offset_top = 8.0 offset_right = 392.0 offset_bottom = 106.0 theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 [node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] layout_mode = 2 [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] layout_mode = 2 theme_override_constants/separation = 20 [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 text = "LOD:" [node name="LodBox" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 max_value = 8.0 value = 4.0 [node name="DescriptionLabel" type="Label" parent="MarginContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 autowrap_mode = 2 ================================================ FILE: project/addons/terrain_3d/menu/baker.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Baker for Terrain3D extends Node const BakeLodDialog: PackedScene = preload("res://addons/terrain_3d/menu/bake_lod_dialog.tscn") const BAKE_MESH_DESCRIPTION: String = "This will create a child MeshInstance3D. LOD4+ is recommended. LOD0 is slow and dense with vertices every 1 unit. It is not an optimal mesh." const BAKE_OCCLUDER_DESCRIPTION: String = "This will create a child OccluderInstance3D. LOD4+ is recommended and will take 5+ seconds per region to generate. LOD0 is unnecessarily dense and slow." const SET_UP_NAVIGATION_DESCRIPTION: String = "This operation will: - Create a NavigationRegion3D node, - Assign it a blank NavigationMesh resource, - Move the Terrain3D node to be a child of the new node, - And bake the nav mesh. Once setup is complete, you can modify the settings on your nav mesh, and rebake without having to run through the setup again. If preferred, this setup can be canceled and the steps performed manually. For the best results, adjust the settings on the NavigationMesh resource to match the settings of your navigation agents and collisions." var plugin: EditorPlugin var bake_method: Callable var bake_lod_dialog: ConfirmationDialog var confirm_dialog: ConfirmationDialog func _enter_tree() -> void: bake_lod_dialog = BakeLodDialog.instantiate() bake_lod_dialog.hide() bake_lod_dialog.confirmed.connect(func(): bake_method.call()) bake_lod_dialog.set_unparent_when_invisible(true) confirm_dialog = ConfirmationDialog.new() confirm_dialog.hide() confirm_dialog.confirmed.connect(func(): bake_method.call()) confirm_dialog.set_unparent_when_invisible(true) func _exit_tree() -> void: bake_lod_dialog.queue_free() confirm_dialog.queue_free() func bake_mesh_popup() -> void: if plugin.terrain: bake_method = _bake_mesh bake_lod_dialog.description = BAKE_MESH_DESCRIPTION EditorInterface.popup_dialog_centered(bake_lod_dialog) func _bake_mesh() -> void: if plugin.terrain.data.get_region_count() == 0: push_error("Terrain3D has no active regions to bake") return var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_NEAREST) if !mesh: push_error("Failed to bake mesh from Terrain3D") return var undo: EditorUndoRedoManager = plugin.get_undo_redo() undo.create_action("Terrain3D Bake ArrayMesh") var mesh_instance := plugin.terrain.get_node_or_null(^"MeshInstance3D") as MeshInstance3D if !mesh_instance: mesh_instance = MeshInstance3D.new() mesh_instance.name = &"MeshInstance3D" mesh_instance.set_skeleton_path(NodePath()) mesh_instance.mesh = mesh undo.add_do_method(plugin.terrain, &"add_child", mesh_instance, true) undo.add_undo_method(plugin.terrain, &"remove_child", mesh_instance) undo.add_do_property(mesh_instance, &"owner", EditorInterface.get_edited_scene_root()) undo.add_do_reference(mesh_instance) else: undo.add_do_property(mesh_instance, &"mesh", mesh) undo.add_undo_property(mesh_instance, &"mesh", mesh_instance.mesh) if mesh_instance.mesh.resource_path: var path := mesh_instance.mesh.resource_path undo.add_do_method(mesh, &"take_over_path", path) undo.add_undo_method(mesh_instance.mesh, &"take_over_path", path) undo.add_do_method(ResourceSaver, &"save", mesh) undo.add_undo_method(ResourceSaver, &"save", mesh_instance.mesh) undo.commit_action() func bake_occluder_popup() -> void: if plugin.terrain: bake_method = _bake_occluder bake_lod_dialog.description = BAKE_OCCLUDER_DESCRIPTION EditorInterface.popup_dialog_centered(bake_lod_dialog) func _bake_occluder() -> void: if plugin.terrain.data.get_region_count() == 0: push_error("Terrain3D has no active regions to bake") return var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_MINIMUM) if !mesh: push_error("Failed to bake mesh from Terrain3D") return assert(mesh.get_surface_count() == 1) var undo: EditorUndoRedoManager = plugin.get_undo_redo() undo.create_action("Terrain3D Bake Occluder3D") var occluder := ArrayOccluder3D.new() var arrays: Array = mesh.surface_get_arrays(0) assert(arrays.size() > Mesh.ARRAY_INDEX) assert(arrays[Mesh.ARRAY_INDEX] != null) occluder.set_arrays(arrays[Mesh.ARRAY_VERTEX], arrays[Mesh.ARRAY_INDEX]) var occluder_instance := plugin.terrain.get_node_or_null(^"OccluderInstance3D") as OccluderInstance3D if !occluder_instance: occluder_instance = OccluderInstance3D.new() occluder_instance.name = &"OccluderInstance3D" occluder_instance.occluder = occluder undo.add_do_method(plugin.terrain, &"add_child", occluder_instance, true) undo.add_undo_method(plugin.terrain, &"remove_child", occluder_instance) undo.add_do_property(occluder_instance, &"owner", EditorInterface.get_edited_scene_root()) undo.add_do_reference(occluder_instance) else: undo.add_do_property(occluder_instance, &"occluder", occluder) undo.add_undo_property(occluder_instance, &"occluder", occluder_instance.occluder) if occluder_instance.occluder.resource_path: var path := occluder_instance.occluder.resource_path undo.add_do_method(occluder, &"take_over_path", path) undo.add_undo_method(occluder_instance.occluder, &"take_over_path", path) undo.add_do_method(ResourceSaver, &"save", occluder) undo.add_undo_method(ResourceSaver, &"save", occluder_instance.occluder) undo.commit_action() func find_nav_region_terrains(p_nav_region: NavigationRegion3D) -> Array[Terrain3D]: var result: Array[Terrain3D] = [] if not p_nav_region.navigation_mesh: return result var source_mode: NavigationMesh.SourceGeometryMode source_mode = p_nav_region.navigation_mesh.geometry_source_geometry_mode if source_mode == NavigationMesh.SOURCE_GEOMETRY_ROOT_NODE_CHILDREN: result.append_array(p_nav_region.find_children("", "Terrain3D", true, true)) return result var group_nodes: Array = p_nav_region.get_tree().get_nodes_in_group(p_nav_region.navigation_mesh.geometry_source_group_name) for node in group_nodes: if node is Terrain3D: result.push_back(node) if source_mode == NavigationMesh.SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN: result.append_array(node.find_children("", "Terrain3D", true, true)) return result func find_terrain_nav_regions(p_terrain: Terrain3D) -> Array[NavigationRegion3D]: var result: Array[NavigationRegion3D] = [] var root: Node = EditorInterface.get_edited_scene_root() if not root: return result for nav_region in root.find_children("", "NavigationRegion3D", true, true): if find_nav_region_terrains(nav_region).has(p_terrain): result.push_back(nav_region) return result func bake_nav_mesh() -> void: if plugin.nav_region: # A NavigationRegion3D is selected. We only need to bake that one navmesh. _bake_nav_region_nav_mesh(plugin.nav_region) print("Terrain3DNavigation: Finished baking 1 NavigationMesh.") elif plugin.terrain: if plugin.terrain.data.get_region_count() == 0: push_error("Terrain3D has no active regions to bake") return # A Terrain3D is selected. There are potentially multiple navmeshes to bake and we need to # find them all. (The multiple navmesh use-case is likely on very large scenes with lots of # geometry. Each navmesh in this case would define its own, non-overlapping, baking AABB, to # cut down on the amount of geometry to bake. In a large open-world RPG, for instance, there # could be a navmesh for each town.) var nav_regions: Array[NavigationRegion3D] = find_terrain_nav_regions(plugin.terrain) for nav_region in nav_regions: _bake_nav_region_nav_mesh(nav_region) print("Terrain3DNavigation: Finished baking %d NavigationMesh(es)." % nav_regions.size()) func _bake_nav_region_nav_mesh(p_nav_region: NavigationRegion3D) -> void: var nav_mesh: NavigationMesh = p_nav_region.navigation_mesh assert(nav_mesh != null) var source_geometry_data := NavigationMeshSourceGeometryData3D.new() NavigationServer3D.parse_source_geometry_data(nav_mesh, source_geometry_data, p_nav_region) for terrain in find_nav_region_terrains(p_nav_region): var aabb: AABB = nav_mesh.filter_baking_aabb aabb.position += nav_mesh.filter_baking_aabb_offset aabb = p_nav_region.global_transform * aabb var faces: PackedVector3Array = terrain.generate_nav_mesh_source_geometry(aabb) if not faces.is_empty(): source_geometry_data.add_faces(faces, Transform3D.IDENTITY) NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry_data) _postprocess_nav_mesh(nav_mesh) # Assign null first to force the debug display to actually update: p_nav_region.set_navigation_mesh(null) p_nav_region.set_navigation_mesh(nav_mesh) # Trigger save to disk if it is saved as an external file if not nav_mesh.get_path().is_empty(): ResourceSaver.save(nav_mesh, nav_mesh.get_path(), ResourceSaver.FLAG_COMPRESS) # Let other editor plugins and tool scripts know the nav mesh was just baked: p_nav_region.bake_finished.emit() func _postprocess_nav_mesh(p_nav_mesh: NavigationMesh) -> void: # Post-process the nav mesh to work around Godot issue #85548 # Round all the vertices in the nav_mesh to the nearest cell_size/cell_height so that it doesn't # contain any edges shorter than cell_size/cell_height (one cause of #85548). var vertices: PackedVector3Array = _postprocess_nav_mesh_round_vertices(p_nav_mesh) # Rounding vertices can collapse some edges to 0 length. We remove these edges, and any polygons # that have been reduced to 0 area. var polygons: Array[PackedInt32Array] = _postprocess_nav_mesh_remove_empty_polygons(p_nav_mesh, vertices) # Another cause of #85548 is baking producing overlapping polygons. We remove these. _postprocess_nav_mesh_remove_overlapping_polygons(p_nav_mesh, vertices, polygons) p_nav_mesh.clear_polygons() p_nav_mesh.set_vertices(vertices) for polygon in polygons: p_nav_mesh.add_polygon(polygon) func _postprocess_nav_mesh_round_vertices(p_nav_mesh: NavigationMesh) -> PackedVector3Array: assert(p_nav_mesh != null) assert(p_nav_mesh.cell_size > 0.0) assert(p_nav_mesh.cell_height > 0.0) var cell_size: Vector3 = Vector3(p_nav_mesh.cell_size, p_nav_mesh.cell_height, p_nav_mesh.cell_size) # Round a little harder to avoid rounding errors with non-power-of-two cell_size/cell_height # causing the navigation map to put two non-matching edges in the same cell: var round_factor := cell_size * 1.001 var vertices: PackedVector3Array = p_nav_mesh.get_vertices() for i in range(vertices.size()): vertices[i] = (vertices[i] / round_factor).floor() * round_factor return vertices func _postprocess_nav_mesh_remove_empty_polygons(p_nav_mesh: NavigationMesh, p_vertices: PackedVector3Array) -> Array[PackedInt32Array]: var polygons: Array[PackedInt32Array] = [] for i in range(p_nav_mesh.get_polygon_count()): var old_polygon: PackedInt32Array = p_nav_mesh.get_polygon(i) var new_polygon: PackedInt32Array = [] # Remove duplicate vertices (introduced by rounding) from the polygon: var polygon_vertices: PackedVector3Array = [] for index in old_polygon: var vertex: Vector3 = p_vertices[index] if polygon_vertices.has(vertex): continue polygon_vertices.push_back(vertex) new_polygon.push_back(index) # If we removed some vertices, we might be able to remove the polygon too: if new_polygon.size() <= 2: continue polygons.push_back(new_polygon) return polygons func _postprocess_nav_mesh_remove_overlapping_polygons(p_nav_mesh: NavigationMesh, p_vertices: PackedVector3Array, p_polygons: Array[PackedInt32Array]) -> void: # Occasionally, a baked nav mesh comes out with overlapping polygons: # https://github.com/godotengine/godot/issues/85548#issuecomment-1839341071 # Until the bug is fixed in the engine, this function attempts to detect and remove overlapping # polygons. # This function has to make a choice of which polygon to remove when an overlap is detected, # because in this case the nav mesh is ambiguous. To do this it uses a heuristic: # (1) an 'overlap' is defined as an edge that is shared by 3 or more polygons. # (2) a 'bad polygon' is defined as a polygon that contains 2 or more 'overlaps'. # The function removes the 'bad polygons', which in practice seems to be enough to remove all # overlaps without creating holes in the nav mesh. var cell_size: Vector3 = Vector3(p_nav_mesh.cell_size, p_nav_mesh.cell_height, p_nav_mesh.cell_size) # `edges` is going to map edges (vertex pairs) to arrays of polygons that contain that edge. var edges: Dictionary = {} for polygon_index in range(p_polygons.size()): var polygon: PackedInt32Array = p_polygons[polygon_index] for j in range(polygon.size()): var vertex: Vector3 = p_vertices[polygon[j]] var next_vertex: Vector3 = p_vertices[polygon[(j + 1) % polygon.size()]] # edge_key is a key we can use in the edges dictionary that uniquely identifies the # edge. We use cell coordinates here (Vector3i) because with a non-power-of-two # cell_size, rounding errors can cause Vector3 vertices to not be equal. # Array.sort IS defined for vector types - see the Godot docs. It's necessary here # because polygons that share an edge can have their vertices in a different order. var edge_key: Array = [Vector3i(vertex / cell_size), Vector3i(next_vertex / cell_size)] edge_key.sort() if !edges.has(edge_key): edges[edge_key] = [] edges[edge_key].push_back(polygon_index) var overlap_count: Dictionary = {} for connections in edges.values(): if connections.size() <= 2: continue for polygon_index in connections: overlap_count[polygon_index] = overlap_count.get(polygon_index, 0) + 1 var bad_polygons: Array = [] for polygon_index in overlap_count.keys(): if overlap_count[polygon_index] >= 2: bad_polygons.push_back(polygon_index) bad_polygons.sort() for i in range(bad_polygons.size() - 1, -1, -1): p_polygons.remove_at(bad_polygons[i]) func set_up_navigation_popup() -> void: if plugin.terrain: bake_method = _set_up_navigation confirm_dialog.dialog_text = SET_UP_NAVIGATION_DESCRIPTION EditorInterface.popup_dialog_centered(confirm_dialog) func _set_up_navigation() -> void: assert(plugin.terrain) if plugin.terrain == EditorInterface.get_edited_scene_root(): push_error("Terrain3D Navigation setup not possible if Terrain3D node is scene root") return if plugin.terrain.data.get_region_count() == 0: push_error("Terrain3D has no active regions") return var terrain: Terrain3D = plugin.terrain var nav_region := NavigationRegion3D.new() nav_region.name = &"NavigationRegion3D" nav_region.navigation_mesh = NavigationMesh.new() var undo_redo: EditorUndoRedoManager = plugin.get_undo_redo() undo_redo.create_action("Terrain3D Set up Navigation") undo_redo.add_do_method(self, &"_do_set_up_navigation", nav_region, terrain) undo_redo.add_undo_method(self, &"_undo_set_up_navigation", nav_region, terrain) undo_redo.add_do_reference(nav_region) undo_redo.commit_action() EditorInterface.inspect_object(nav_region) assert(plugin.nav_region == nav_region) bake_nav_mesh() func _do_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrain3D) -> void: var parent: Node = p_terrain.get_parent() var index: int = p_terrain.get_index() var t_owner: Node = p_terrain.owner parent.add_child(p_nav_region, true) p_terrain.reparent(p_nav_region) parent.move_child(p_nav_region, index) p_nav_region.owner = t_owner p_terrain.owner = t_owner func _undo_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrain3D) -> void: assert(p_terrain.get_parent() == p_nav_region) var parent: Node = p_nav_region.get_parent() var index: int = p_nav_region.get_index() var t_owner: Node = p_nav_region.get_owner() p_terrain.reparent(parent) parent.remove_child(p_nav_region) parent.move_child(p_terrain, index) p_terrain.owner = t_owner ================================================ FILE: project/addons/terrain_3d/menu/baker.gd.uid ================================================ uid://4ulaeevj5jvi ================================================ FILE: project/addons/terrain_3d/menu/channel_packer.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Channel Packer for Terrain3D extends RefCounted const WINDOW_SCENE: String = "res://addons/terrain_3d/menu/channel_packer.tscn" const TEMPLATE_PATH: String = "res://addons/terrain_3d/menu/channel_packer_import_template.txt" const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/menu/channel_packer_dragdrop.gd" enum { INFO, WARN, ERROR, } enum { IMAGE_ALBEDO, IMAGE_HEIGHT, IMAGE_NORMAL, IMAGE_ROUGHNESS, IMAGE_AO } var plugin: EditorPlugin var window: Window var save_file_dialog: EditorFileDialog var open_file_dialog: EditorFileDialog var invert_green_checkbox: CheckBox var invert_smooth_checkbox: CheckBox var invert_height_checkbox: CheckBox var normalize_height_checkbox: CheckBox var lumin_height_button: Button var generate_mipmaps_checkbox: CheckBox var high_quality_checkbox: CheckBox var align_normals_checkbox: CheckBox var resize_toggle_checkbox: CheckBox var resize_option_box: SpinBox var height_channel: Array[Button] var height_channel_selected: int = 0 var roughness_channel: Array[Button] var roughness_channel_selected: int = 0 var occlusion_channel: Array[Button] var occlusion_channel_selected: int = 0 var last_opened_directory: String var last_saved_directory: String var packing_albedo: bool = false var queue_pack_normal_roughness: bool = false var images: Array[Image] = [null, null, null, null, null] var status_label: Label var no_op: Callable = func(): pass var last_file_selected_fn: Callable = no_op var normal_vector: Vector3 func pack_textures_popup() -> void: if window != null: window.show() window.grab_focus() window.move_to_center() return # Set background color to match theme window = (load(WINDOW_SCENE) as PackedScene).instantiate() var base_color: Color = plugin.editor_settings.get_setting("interface/theme/base_color") var panel_style := StyleBoxFlat.new() panel_style.bg_color = base_color window.get_node("PanelContainer").set("theme_override_styles/panel", panel_style) # Connect button signals lumin_height_button = window.get_node("%LuminanceAsHeightButton") as Button invert_height_checkbox = window.get_node("%ConvertDepthToHeight") as CheckBox normalize_height_checkbox = window.get_node("%NormalizeHeight") as CheckBox invert_green_checkbox = window.get_node("%InvertGreenChannelCheckBox") as CheckBox align_normals_checkbox = window.get_node("%AlignNormalsCheckBox") as CheckBox invert_smooth_checkbox = window.get_node("%InvertSmoothCheckBox") as CheckBox resize_toggle_checkbox = window.get_node("%ResizeToggle") as CheckBox resize_option_box = window.get_node("%ResizeOptionBox") as SpinBox resize_toggle_checkbox.toggled.connect(func(val:bool) -> void: resize_option_box.set_visible(val)) generate_mipmaps_checkbox = window.get_node("%GenerateMipmapsCheckBox") as CheckBox high_quality_checkbox = window.get_node("%HighQualityCheckBox") as CheckBox window.get_node("%PackButton").pressed.connect(_on_pack_button_pressed) status_label = window.get_node("%StatusLabel") as Label window.get_node("%CloseButton").pressed.connect(_on_close_requested) window.close_requested.connect(_on_close_requested) window.window_input.connect(func(event:InputEvent): if event is InputEventKey: if event.pressed and event.keycode == KEY_ESCAPE: _on_close_requested() ) height_channel = [ window.get_node("%HeightChannelR") as Button, window.get_node("%HeightChannelG") as Button, window.get_node("%HeightChannelB") as Button, window.get_node("%HeightChannelA") as Button ] roughness_channel = [ window.get_node("%RoughnessChannelR") as Button, window.get_node("%RoughnessChannelG") as Button, window.get_node("%RoughnessChannelB") as Button, window.get_node("%RoughnessChannelA") as Button ] occlusion_channel = [ window.get_node("%AOChannelR") as Button, window.get_node("%AOChannelG") as Button, window.get_node("%AOChannelB") as Button, window.get_node("%AOChannelA") as Button ] height_channel[0].pressed.connect(func() -> void: height_channel_selected = 0) height_channel[1].pressed.connect(func() -> void: height_channel_selected = 1) height_channel[2].pressed.connect(func() -> void: height_channel_selected = 2) height_channel[3].pressed.connect(func() -> void: height_channel_selected = 3) roughness_channel[0].pressed.connect(func() -> void: roughness_channel_selected = 0) roughness_channel[1].pressed.connect(func() -> void: roughness_channel_selected = 1) roughness_channel[2].pressed.connect(func() -> void: roughness_channel_selected = 2) roughness_channel[3].pressed.connect(func() -> void: roughness_channel_selected = 3) occlusion_channel[0].pressed.connect(func() -> void: occlusion_channel_selected = 0) occlusion_channel[1].pressed.connect(func() -> void: occlusion_channel_selected = 1) occlusion_channel[2].pressed.connect(func() -> void: occlusion_channel_selected = 2) occlusion_channel[3].pressed.connect(func() -> void: occlusion_channel_selected = 3) plugin.add_child(window) _init_file_dialogs() # the dialog disables the parent window "on top" so, restore it after 1 frame to alow the dialog to clear. var set_on_top_fn: Callable = func(_file: String = "") -> void: await RenderingServer.frame_post_draw window.always_on_top = true save_file_dialog.file_selected.connect(set_on_top_fn) save_file_dialog.canceled.connect(set_on_top_fn) open_file_dialog.file_selected.connect(set_on_top_fn) open_file_dialog.canceled.connect(set_on_top_fn) _init_texture_picker(window.get_node("%AlbedoVBox"), IMAGE_ALBEDO) _init_texture_picker(window.get_node("%HeightVBox"), IMAGE_HEIGHT) _init_texture_picker(window.get_node("%NormalVBox"), IMAGE_NORMAL) _init_texture_picker(window.get_node("%RoughnessVBox"), IMAGE_ROUGHNESS) _init_texture_picker(window.get_node("%AOVBox"), IMAGE_AO) func _on_close_requested() -> void: last_file_selected_fn = no_op images = [null, null, null, null, null] window.queue_free() window = null func _init_file_dialogs() -> void: save_file_dialog = EditorFileDialog.new() save_file_dialog.set_filters(PackedStringArray(["*.png"])) save_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE) save_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM save_file_dialog.file_selected.connect(_on_save_file_selected) save_file_dialog.ok_button_text = "Save" save_file_dialog.size = Vector2i(550, 550) #save_file_dialog.transient = false #save_file_dialog.exclusive = false #save_file_dialog.popup_window = true open_file_dialog = EditorFileDialog.new() open_file_dialog.set_filters(PackedStringArray( ["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", "*.ktx", "*.dds"])) open_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_OPEN_FILE) open_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM open_file_dialog.ok_button_text = "Open" open_file_dialog.size = Vector2i(550, 550) #open_file_dialog.transient = false #open_file_dialog.exclusive = false #open_file_dialog.popup_window = true window.add_child(save_file_dialog) window.add_child(open_file_dialog) func _init_texture_picker(p_parent: Node, p_image_index: int) -> void: var line_edit: LineEdit = p_parent.find_child("LineEdit") as LineEdit var file_pick_button: Button = p_parent.find_child("PickButton") as Button var clear_button: Button = p_parent.find_child("ClearButton") as Button var texture_rect: TextureRect = p_parent.find_child("TextureRect") as TextureRect var texture_button: Button = p_parent.find_child("TextureButton") as Button texture_button.set_script(load(DRAG_DROP_SCRIPT) as GDScript) var set_channel_fn: Callable = func(used_channels: int) -> void: var channel_count: int = 4 # enum Image.UsedChannels match used_channels: Image.USED_CHANNELS_L, Image.USED_CHANNELS_R: channel_count = 1 Image.USED_CHANNELS_LA, Image.USED_CHANNELS_RG: channel_count = 2 Image.USED_CHANNELS_RGB: channel_count = 3 Image.USED_CHANNELS_RGBA: channel_count = 4 if p_image_index == IMAGE_HEIGHT: for i in 4: height_channel[i].visible = i < channel_count height_channel[0].button_pressed = true height_channel[0].pressed.emit() elif p_image_index == IMAGE_ROUGHNESS: for i in 4: roughness_channel[i].visible = i < channel_count roughness_channel[0].button_pressed = true roughness_channel[0].pressed.emit() elif p_image_index == IMAGE_AO: for i in 4: occlusion_channel[i].visible = i < channel_count occlusion_channel[0].button_pressed = true occlusion_channel[0].pressed.emit() var load_image_fn: Callable = func(path: String): var image: Image = Image.new() var error: int = OK # Special case for dds files if path.get_extension() == "dds": image = ResourceLoader.load(path).get_image() if not image.is_empty(): # if the dds file is loaded, we must clear any mipmaps and # decompress if needed in order to do per pixel operations. image.clear_mipmaps() image.decompress() else: error = FAILED else: error = image.load(path) if error != OK: _show_message(ERROR, "Failed to load texture '" + path + "'") texture_rect.texture = null images[p_image_index] = null else: _show_message(INFO, "Loaded texture '" + path + "'") texture_rect.texture = ImageTexture.create_from_image(image) images[p_image_index] = image _set_wh_labels(p_image_index, image.get_width(), image.get_height()) if p_image_index == IMAGE_NORMAL: _set_normal_vector(image) if p_image_index in [ IMAGE_HEIGHT, IMAGE_ROUGHNESS, IMAGE_AO ]: set_channel_fn.call(image.detect_used_channels()) var os_drop_fn: Callable = func(files: PackedStringArray) -> void: # OS drag drop holds mouse focus until released, # Get mouse pos and check directly if inside texture_rect var rect = texture_button.get_global_rect() var mouse_position = texture_button.get_global_mouse_position() if rect.has_point(mouse_position): if files.size() != 1: _show_message(ERROR, "Cannot load multiple files") else: line_edit.text = files[0] load_image_fn.call(files[0]) var godot_drop_fn: Callable = func(path: String) -> void: path = ProjectSettings.globalize_path(path) line_edit.text = path load_image_fn.call(path) var open_fn: Callable = func() -> void: open_file_dialog.current_path = last_opened_directory if last_file_selected_fn != no_op: open_file_dialog.file_selected.disconnect(last_file_selected_fn) last_file_selected_fn = func(path: String) -> void: line_edit.text = path load_image_fn.call(path) open_file_dialog.file_selected.connect(last_file_selected_fn) open_file_dialog.popup_centered_ratio() var line_edit_submit_fn: Callable = func(path: String) -> void: line_edit.text = path load_image_fn.call(path) var clear_fn: Callable = func() -> void: line_edit.text = "" texture_rect.texture = null images[p_image_index] = null _set_wh_labels(p_image_index, -1, -1) line_edit.text_submitted.connect(line_edit_submit_fn) file_pick_button.pressed.connect(open_fn) texture_button.pressed.connect(open_fn) clear_button.pressed.connect(clear_fn) texture_button.dropped.connect(godot_drop_fn) window.files_dropped.connect(os_drop_fn) if p_image_index == IMAGE_HEIGHT: var lumin_fn: Callable = func() -> void: if !images[IMAGE_ALBEDO]: _show_message(ERROR, "Albedo Image Required for Operation") else: line_edit.text = "Generated Height" var height_texture: Image = Terrain3DUtil.luminance_to_height(images[IMAGE_ALBEDO]) if height_texture.is_empty(): _show_message(ERROR, "Height Texture Generation error") # blur the image by resizing down and back.. var w: int = height_texture.get_width() var h: int = height_texture.get_height() height_texture.resize(w / 4, h / 4) height_texture.resize(w, h, Image.INTERPOLATE_CUBIC) # "Load" the height texture images[IMAGE_HEIGHT] = height_texture texture_rect.texture = ImageTexture.create_from_image(images[IMAGE_HEIGHT]) _set_wh_labels(IMAGE_HEIGHT, height_texture.get_width(), height_texture.get_height()) set_channel_fn.call(Image.USED_CHANNELS_R) _show_message(INFO, "Height Texture generated sucsessfully") lumin_height_button.pressed.connect(lumin_fn) plugin.ui.set_button_editor_icon(file_pick_button, "Folder") plugin.ui.set_button_editor_icon(clear_button, "Remove") func _set_wh_labels(p_image_index: int, width: int, height: int) -> void: match p_image_index: 0: window.get_node("%AlbedoSize").text = "(%d, %d)" % [ width, height ] 1: window.get_node("%HeightSize").text = "(%d, %d)" % [ width, height ] 2: window.get_node("%NormalSize").text = "(%d, %d)" % [ width, height ] 3: window.get_node("%RoughnessSize").text = "(%d, %d)" % [ width, height ] 4: window.get_node("%AOSize").text = "(%d, %d)" % [ width, height ] func _show_message(p_level: int, p_text: String) -> void: status_label.text = p_text match p_level: INFO: print("Terrain3DChannelPacker: " + p_text) status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14)) WARN: push_warning("Terrain3DChannelPacker: " + p_text) status_label.add_theme_color_override("font_color", Color(0.9, 0.9, 0)) ERROR,_: push_error("Terrain3DChannelPacker: " + p_text) status_label.add_theme_color_override("font_color", Color(0.9, 0, 0)) func _create_import_file(png_path: String) -> void: var dst_import_path: String = png_path + ".import" var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ) var template_content: String = file.get_as_text() file.close() template_content = template_content.replace( "$SOURCE_FILE", png_path).replace( "$HIGH_QUALITY", str(high_quality_checkbox.button_pressed)).replace( "$GENERATE_MIPMAPS", str(generate_mipmaps_checkbox.button_pressed) ) var import_content: String = template_content file = FileAccess.open(dst_import_path, FileAccess.WRITE) file.store_string(import_content) file.close() func _on_pack_button_pressed() -> void: packing_albedo = images[IMAGE_ALBEDO] != null and images[IMAGE_HEIGHT] != null var packing_normal_roughness: bool = images[IMAGE_NORMAL] != null and images[IMAGE_ROUGHNESS] != null if not packing_albedo and not packing_normal_roughness: _show_message(WARN, "Please select an albedo and height texture or a normal and roughness texture") return if packing_albedo: save_file_dialog.current_path = last_saved_directory + "packed_albedo_height" save_file_dialog.title = "Save Packed Albedo/Height Texture" save_file_dialog.popup_centered_ratio() if packing_normal_roughness: queue_pack_normal_roughness = true return if packing_normal_roughness: save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness" save_file_dialog.title = "Save Packed Normal/Roughness Texture" save_file_dialog.popup_centered_ratio() func _on_save_file_selected(p_dst_path) -> void: last_saved_directory = p_dst_path.get_base_dir() + "/" var error: int if packing_albedo: error = _pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], null, p_dst_path, false, invert_height_checkbox.button_pressed, false, normalize_height_checkbox.button_pressed, height_channel_selected) else: error = _pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], images[IMAGE_AO], p_dst_path, invert_green_checkbox.button_pressed, invert_smooth_checkbox.button_pressed, align_normals_checkbox.button_pressed, false, roughness_channel_selected, occlusion_channel_selected) if error == OK: EditorInterface.get_resource_filesystem().scan() if window.visible: window.hide() await EditorInterface.get_resource_filesystem().resources_reimported # wait 1 extra frame, to ensure the UI is responsive. await RenderingServer.frame_post_draw window.show() if queue_pack_normal_roughness: queue_pack_normal_roughness = false packing_albedo = false save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness" save_file_dialog.title = "Save Packed Normal/Roughness Texture" save_file_dialog.call_deferred("popup_centered_ratio") save_file_dialog.call_deferred("grab_focus") func _alignment_basis(normal: Vector3) -> Basis: var up: Vector3 = Vector3(0, 0, 1) var v: Vector3 = normal.cross(up) var c: float = normal.dot(up) var k: float = 1.0 / (1.0 + c) var vxy: float = v.x * v.y * k var vxz: float = v.x * v.z * k var vyz: float = v.y * v.z * k return Basis(Vector3(v.x * v.x * k + c, vxy - v.z, vxz + v.y), Vector3(vxy + v.z, v.y * v.y * k + c, vyz - v.x), Vector3(vxz - v.y, vyz + v.x, v.z * v.z * k + c) ) func _set_normal_vector(source: Image, quiet: bool = false) -> void: # Calculate texture normal sum direction var normal: Image = source var sum: Color = Color(0.0, 0.0, 0.0, 0.0) for x in normal.get_width(): for y in normal.get_height(): sum += normal.get_pixel(x, y) var div: float = normal.get_height() * normal.get_width() sum /= Color(div, div, div) sum *= 2.0 sum -= Color(1.0, 1.0, 1.0) normal_vector = Vector3(sum.r, sum.g, sum.b).normalized() if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && !quiet: _show_message(WARN, "Normal Texture Not Orthoganol to UV plane.\nFor Compatability with Detiling and Rotation, Select Orthoganolize Normals") func _align_normals(source: Image, iteration: int = 0) -> void: # generate matrix to re-align the normalmap var mat3: Basis = _alignment_basis(normal_vector) # re-align the normal map pixels for x in source.get_width(): for y in source.get_height(): var old_pixel: Color = source.get_pixel(x, y) var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b) vector_pixel *= 2.0 vector_pixel -= Vector3.ONE vector_pixel = vector_pixel.normalized() vector_pixel = vector_pixel * mat3 vector_pixel += Vector3.ONE vector_pixel *= 0.5 var new_pixel: Color = Color(vector_pixel.x, vector_pixel.y, vector_pixel.z, old_pixel.a) source.set_pixel(x, y, new_pixel) _set_normal_vector(source, true) if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && iteration < 3: ++iteration _align_normals(source, iteration) func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_ao_image: Image, p_dst_path: String, p_invert_green: bool, p_invert_smooth: bool, p_align_normals: bool, p_normalize_height: bool, p_alpha_channel: int, p_occlusion_channel: int = 0) -> Error: if p_rgb_image and p_a_image: if p_rgb_image.get_size() != p_a_image.get_size() and !resize_toggle_checkbox.button_pressed: _show_message(ERROR, "Textures must be the same size.\nEnable resize to override image dimensions") return FAILED if p_ao_image: if p_rgb_image.get_size() != p_ao_image.get_size() and !resize_toggle_checkbox.button_pressed: _show_message(ERROR, "Textures must be the same size.\nEnable resize to override image dimensions") return FAILED if resize_toggle_checkbox.button_pressed: var size: int = max(128, resize_option_box.value) p_rgb_image.resize(size, size, Image.INTERPOLATE_CUBIC) p_a_image.resize(size, size, Image.INTERPOLATE_CUBIC) if p_ao_image: p_ao_image.resize(size, size, Image.INTERPOLATE_CUBIC) if p_align_normals and normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999: _align_normals(p_rgb_image) elif p_align_normals: _show_message(INFO, "Alignment OK, skipping Normal Orthogonalization") var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image, p_ao_image, p_invert_green, p_invert_smooth, p_normalize_height, p_alpha_channel, p_occlusion_channel) if not output_image: _show_message(ERROR, "Failed to pack textures") return FAILED if output_image.detect_alpha() != Image.ALPHA_BLEND: _show_message(WARN, "Warning, Alpha channel empty") output_image.save_png(p_dst_path) _create_import_file(p_dst_path) _show_message(INFO, "Packed to " + p_dst_path + ".") return OK else: _show_message(ERROR, "Failed to load one or more textures") return FAILED ================================================ FILE: project/addons/terrain_3d/menu/channel_packer.gd.uid ================================================ uid://bwldx4itd58o7 ================================================ FILE: project/addons/terrain_3d/menu/channel_packer.tscn ================================================ [gd_scene load_steps=12 format=3 uid="uid://nud6dwjcnj5v"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mbo40"] content_margin_left = 10.0 content_margin_top = 10.0 content_margin_right = 10.0 content_margin_bottom = 10.0 draw_center = false border_width_left = 3 border_width_top = 3 border_width_right = 3 border_width_bottom = 3 border_color = Color(0.28, 0.28, 0.28, 1) border_blend = true corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 [sub_resource type="LabelSettings" id="LabelSettings_pxdc5"] font_size = 24 [sub_resource type="LabelSettings" id="LabelSettings_mbo40"] font_color = Color(1, 1, 1, 0.705882) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cb0xf"] bg_color = Color(0.147, 0.168, 0.203, 1) draw_center = false border_width_left = 3 border_width_top = 3 border_width_right = 3 border_width_bottom = 3 border_color = Color(0.5655, 0.582, 0.6095, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7qdas"] [sub_resource type="LabelSettings" id="LabelSettings_2eo1k"] shadow_color = Color(0, 0, 0, 1) [sub_resource type="ButtonGroup" id="ButtonGroup_wnxik"] [sub_resource type="LabelSettings" id="LabelSettings_nt754"] font_size = 24 [sub_resource type="ButtonGroup" id="ButtonGroup_47nos"] [sub_resource type="ButtonGroup" id="ButtonGroup_mbo40"] [sub_resource type="LabelSettings" id="LabelSettings_3c3a7"] font_size = 24 [node name="Window" type="Window"] title = "Terrain3D Channel Packer" initial_position = 1 size = Vector2i(1446, 730) wrap_controls = true always_on_top = true [node name="PanelContainer" type="PanelContainer" parent="."] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 4 size_flags_vertical = 0 [node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer"] layout_mode = 2 [node name="MarginContainer" type="MarginContainer" parent="PanelContainer/ScrollContainer"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_constants/margin_left = 5 theme_override_constants/margin_top = 5 theme_override_constants/margin_right = 5 theme_override_constants/margin_bottom = 0 [node name="FlowContainer" type="HFlowContainer" parent="PanelContainer/ScrollContainer/MarginContainer"] layout_mode = 2 [node name="Albedo_Height" type="PanelContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer"] layout_mode = 2 size_flags_horizontal = 3 theme_override_styles/panel = SubResource("StyleBoxFlat_mbo40") [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height"] layout_mode = 2 theme_override_constants/separation = 10 [node name="AlbHtLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox"] layout_mode = 2 text = "1. Albedo + Height Texture" label_settings = SubResource("LabelSettings_pxdc5") [node name="HSeparator" type="HSeparator" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox"] layout_mode = 2 [node name="AlbedoVBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 10 [node name="AlbedoLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox"] layout_mode = 2 text = "Albedo Texture" label_settings = SubResource("LabelSettings_mbo40") [node name="AlbedoFileHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox"] layout_mode = 2 [node name="LineEdit" type="LineEdit" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoFileHBox"] layout_mode = 2 size_flags_horizontal = 3 [node name="PickButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoFileHBox"] layout_mode = 2 [node name="ClearButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoFileHBox"] layout_mode = 2 [node name="AlbedoMapHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_constants/separation = 30 [node name="Panel" type="Panel" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoMapHBox"] custom_minimum_size = Vector2(110, 110) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf") [node name="TextureRect" type="TextureRect" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoMapHBox/Panel"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -50.0 offset_top = -50.0 offset_right = 50.0 offset_bottom = 50.0 grow_horizontal = 2 grow_vertical = 2 expand_mode = 1 [node name="TextureButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoMapHBox/Panel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas") [node name="AlbedoSize" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoMapHBox/Panel"] unique_name_in_owner = true layout_mode = 1 anchors_preset = 7 anchor_left = 0.5 anchor_top = 1.0 anchor_right = 0.5 anchor_bottom = 1.0 offset_left = -18.5 offset_top = -23.0 offset_right = 18.5 grow_horizontal = 2 grow_vertical = 0 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) text = "(0, 0)" label_settings = SubResource("LabelSettings_2eo1k") horizontal_alignment = 1 [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoMapHBox"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_constants/separation = 5 [node name="LuminanceAsHeightButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/AlbedoVBox/AlbedoMapHBox/VBox"] unique_name_in_owner = true layout_mode = 2 text = " Generate Height from Luminance" icon_alignment = 2 [node name="HSeparator2" type="HSeparator" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox"] layout_mode = 2 theme_override_constants/separation = 20 [node name="HeightVBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 10 [node name="HeightLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox"] layout_mode = 2 text = "Height Texture" label_settings = SubResource("LabelSettings_mbo40") [node name="HeightFileHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox"] layout_mode = 2 [node name="LineEdit" type="LineEdit" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightFileHBox"] layout_mode = 2 size_flags_horizontal = 3 [node name="PickButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightFileHBox"] layout_mode = 2 [node name="ClearButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightFileHBox"] layout_mode = 2 [node name="HeightMapHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_constants/separation = 30 [node name="Panel" type="Panel" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox"] custom_minimum_size = Vector2(110, 110) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf") [node name="TextureRect" type="TextureRect" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/Panel"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -50.0 offset_top = -50.0 offset_right = 50.0 offset_bottom = 50.0 grow_horizontal = 2 grow_vertical = 2 expand_mode = 1 [node name="TextureButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/Panel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas") [node name="HeightSize" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/Panel"] unique_name_in_owner = true layout_mode = 1 anchors_preset = 7 anchor_left = 0.5 anchor_top = 1.0 anchor_right = 0.5 anchor_bottom = 1.0 offset_left = -40.0 offset_top = -23.0 offset_right = 40.0 grow_horizontal = 2 grow_vertical = 0 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) text = "(0, 0)" label_settings = SubResource("LabelSettings_2eo1k") horizontal_alignment = 1 [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_constants/separation = 5 [node name="ChannelHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox"] layout_mode = 2 size_flags_horizontal = 0 alignment = 1 [node name="HeightChannelLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox/ChannelHBox"] layout_mode = 2 text = " Source Channel: " horizontal_alignment = 2 [node name="HeightChannelR" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_pressed = true button_group = SubResource("ButtonGroup_wnxik") text = "R" [node name="HeightChannelB" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_wnxik") text = "G" [node name="HeightChannelG" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_wnxik") text = "B" [node name="HeightChannelA" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_wnxik") text = "A" [node name="ConvertDepthToHeight" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox"] unique_name_in_owner = true layout_mode = 2 text = " Convert Depth to Height" icon_alignment = 2 [node name="NormalizeHeight" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox/HeightMapHBox/VBox"] unique_name_in_owner = true layout_mode = 2 text = "Normalize Height" icon_alignment = 2 [node name="Spacer" type="Control" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Albedo_Height/VBox/HeightVBox"] layout_mode = 2 [node name="Normal_Roughness" type="PanelContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer"] layout_mode = 2 size_flags_horizontal = 3 theme_override_styles/panel = SubResource("StyleBoxFlat_mbo40") [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness"] layout_mode = 2 theme_override_constants/separation = 10 [node name="NrmRghLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] layout_mode = 2 text = "2. Normal + Roughness Texture" label_settings = SubResource("LabelSettings_nt754") [node name="HSeparator" type="HSeparator" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] layout_mode = 2 [node name="NormalVBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 10 [node name="NormalLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox"] layout_mode = 2 text = "Normal Texture" label_settings = SubResource("LabelSettings_mbo40") [node name="NormalFileHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox"] layout_mode = 2 [node name="LineEdit" type="LineEdit" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalFileHBox"] layout_mode = 2 size_flags_horizontal = 3 [node name="PickButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalFileHBox"] layout_mode = 2 [node name="ClearButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalFileHBox"] layout_mode = 2 [node name="NormalMapHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_constants/separation = 30 [node name="Panel" type="Panel" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox"] custom_minimum_size = Vector2(110, 110) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf") [node name="TextureRect" type="TextureRect" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox/Panel"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -50.0 offset_top = -50.0 offset_right = 50.0 offset_bottom = 50.0 grow_horizontal = 2 grow_vertical = 2 expand_mode = 1 [node name="TextureButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox/Panel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas") [node name="NormalSize" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox/Panel"] unique_name_in_owner = true layout_mode = 1 anchors_preset = 7 anchor_left = 0.5 anchor_top = 1.0 anchor_right = 0.5 anchor_bottom = 1.0 offset_left = -18.5 offset_top = -23.0 offset_right = 18.5 grow_horizontal = 2 grow_vertical = 0 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) text = "(0, 0)" label_settings = SubResource("LabelSettings_2eo1k") horizontal_alignment = 1 [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_constants/separation = 5 [node name="InvertGreenChannelCheckBox" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox/VBox"] unique_name_in_owner = true layout_mode = 2 tooltip_text = "This setting determines if the normal map is +Y up (OpenGL) or down (DirectX) on the green channel. Some engines like Unreal Engine use DirectX normal maps. Godot uses OpenGL. This option inverts the green channel." text = " Convert DirectX to OpenGL" [node name="AlignNormalsCheckBox" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/NormalVBox/NormalMapHBox/VBox"] unique_name_in_owner = true layout_mode = 2 text = " Orthoganolise Normals" [node name="HSeparator2" type="HSeparator" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] layout_mode = 2 theme_override_constants/separation = 20 [node name="RoughnessVBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 10 [node name="RoughnessLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox"] layout_mode = 2 text = "Roughness Texture" label_settings = SubResource("LabelSettings_mbo40") [node name="RoughnessFileHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox"] layout_mode = 2 [node name="LineEdit" type="LineEdit" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessFileHBox"] layout_mode = 2 size_flags_horizontal = 3 [node name="PickButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessFileHBox"] layout_mode = 2 [node name="ClearButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessFileHBox"] layout_mode = 2 [node name="RoughnessMapHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_constants/separation = 30 [node name="Panel" type="Panel" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox"] custom_minimum_size = Vector2(110, 110) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf") [node name="TextureRect" type="TextureRect" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/Panel"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -50.0 offset_top = -50.0 offset_right = 50.0 offset_bottom = 50.0 grow_horizontal = 2 grow_vertical = 2 expand_mode = 1 [node name="TextureButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/Panel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas") [node name="RoughnessSize" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/Panel"] unique_name_in_owner = true layout_mode = 1 anchors_preset = 7 anchor_left = 0.5 anchor_top = 1.0 anchor_right = 0.5 anchor_bottom = 1.0 offset_left = -40.0 offset_top = -23.0 offset_right = 40.0 grow_horizontal = 2 grow_vertical = 0 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) text = "(0, 0)" label_settings = SubResource("LabelSettings_2eo1k") horizontal_alignment = 1 [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_constants/separation = 5 [node name="ChannelHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox"] layout_mode = 2 size_flags_horizontal = 0 alignment = 1 [node name="RoughnessChannelLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox/ChannelHBox"] layout_mode = 2 text = " Source Channel: " [node name="RoughnessChannelR" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_pressed = true button_group = SubResource("ButtonGroup_47nos") text = "R" [node name="RoughnessChannelG" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_47nos") text = "G" [node name="RoughnessChannelB" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_47nos") text = "B" [node name="RoughnessChannelA" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_47nos") text = "A" [node name="InvertSmoothCheckBox" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/RoughnessVBox/RoughnessMapHBox/VBox"] unique_name_in_owner = true layout_mode = 2 tooltip_text = "Some engines like Unity use Smoothness maps instead of Roughness maps like Godot Engine. This option inverts the map." text = " Convert Smooth to Rough" [node name="HSeparator3" type="HSeparator" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] layout_mode = 2 theme_override_constants/separation = 20 [node name="AOVBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 10 [node name="AOLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox"] layout_mode = 2 text = "AO Texture (Opt)" label_settings = SubResource("LabelSettings_mbo40") [node name="AOFileHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox"] layout_mode = 2 [node name="LineEdit" type="LineEdit" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOFileHBox"] layout_mode = 2 size_flags_horizontal = 3 [node name="PickButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOFileHBox"] layout_mode = 2 [node name="ClearButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOFileHBox"] layout_mode = 2 [node name="AOMapHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_constants/separation = 30 [node name="Panel" type="Panel" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox"] custom_minimum_size = Vector2(110, 110) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 4 theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf") [node name="TextureRect" type="TextureRect" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/Panel"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -50.0 offset_top = -50.0 offset_right = 50.0 offset_bottom = 50.0 grow_horizontal = 2 grow_vertical = 2 expand_mode = 1 [node name="TextureButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/Panel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas") [node name="AOSize" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/Panel"] unique_name_in_owner = true layout_mode = 1 anchors_preset = 7 anchor_left = 0.5 anchor_top = 1.0 anchor_right = 0.5 anchor_bottom = 1.0 offset_left = -18.5 offset_top = -23.0 offset_right = 18.5 grow_horizontal = 2 grow_vertical = 0 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) text = "(0, 0)" label_settings = SubResource("LabelSettings_2eo1k") horizontal_alignment = 1 [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_constants/separation = 5 [node name="ChannelHBox" type="HBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/VBox"] layout_mode = 2 size_flags_horizontal = 0 alignment = 1 [node name="AOChannelLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/VBox/ChannelHBox"] layout_mode = 2 text = " Source Channel: " horizontal_alignment = 2 [node name="AOChannelR" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_pressed = true button_group = SubResource("ButtonGroup_mbo40") text = "R" [node name="AOChannelG" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_mbo40") text = "G" [node name="AOChannelB" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_mbo40") text = "B" [node name="AOChannelA" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox/AOMapHBox/VBox/ChannelHBox"] unique_name_in_owner = true layout_mode = 2 toggle_mode = true button_group = SubResource("ButtonGroup_mbo40") text = "A" [node name="Spacer" type="Control" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Normal_Roughness/VBox/AOVBox"] layout_mode = 2 [node name="Packing" type="PanelContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer"] layout_mode = 2 size_flags_horizontal = 3 theme_override_styles/panel = SubResource("StyleBoxFlat_mbo40") [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing"] custom_minimum_size = Vector2(300, 0) layout_mode = 2 theme_override_constants/separation = 10 [node name="PackingLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] layout_mode = 2 text = "3. Pack Textures" label_settings = SubResource("LabelSettings_3c3a7") [node name="HSeparator" type="HSeparator" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] layout_mode = 2 [node name="VBox" type="VBoxContainer" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] layout_mode = 2 size_flags_horizontal = 4 [node name="ResizeToggle" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox/VBox"] unique_name_in_owner = true layout_mode = 2 text = " Resize Packed Image" [node name="ResizeOptionBox" type="SpinBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox/VBox"] unique_name_in_owner = true visible = false layout_mode = 2 tooltip_text = "A value of 0 disables resizing." min_value = 128.0 max_value = 4096.0 step = 128.0 value = 1024.0 [node name="GenerateMipmapsCheckBox" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox/VBox"] unique_name_in_owner = true layout_mode = 2 button_pressed = true text = "Generate Mipmaps" [node name="HighQualityCheckBox" type="CheckBox" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox/VBox"] unique_name_in_owner = true layout_mode = 2 text = "Import High Quality" [node name="Instructions" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] custom_minimum_size = Vector2(150, 0) layout_mode = 2 text = "You can pack the albedo, the normal, or both." horizontal_alignment = 1 autowrap_mode = 3 [node name="PackButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] unique_name_in_owner = true layout_mode = 2 text = "Pack textures as..." [node name="StatusLabel" type="Label" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] unique_name_in_owner = true custom_minimum_size = Vector2(0, 60) layout_mode = 2 horizontal_alignment = 1 autowrap_mode = 3 [node name="CloseButton" type="Button" parent="PanelContainer/ScrollContainer/MarginContainer/FlowContainer/Packing/VBox"] unique_name_in_owner = true visible = false layout_mode = 2 text = "Close" ================================================ FILE: project/addons/terrain_3d/menu/channel_packer_dragdrop.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Channel Packer Dragdropper for Terrain3D @tool extends Button signal dropped func _can_drop_data(p_position, p_data) -> bool: if typeof(p_data) == TYPE_DICTIONARY: if p_data.files.size() == 1: match p_data.files[0].get_extension(): "png", "bmp", "exr", "hdr", "jpg", "jpeg", "tga", "svg", "webp", "ktx", "dds": return true return false func _drop_data(p_position, p_data) -> void: dropped.emit(p_data.files[0]) ================================================ FILE: project/addons/terrain_3d/menu/channel_packer_dragdrop.gd.uid ================================================ uid://br45krrqbw8bg ================================================ FILE: project/addons/terrain_3d/menu/channel_packer_import_template.txt ================================================ [remap] importer="texture" type="CompressedTexture2D" metadata={ "imported_formats": ["s3tc_bptc"], "vram_texture": true } [deps] source_file="$SOURCE_FILE" [params] compress/mode=2 compress/high_quality=$HIGH_QUALITY compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=2 compress/channel_pack=0 mipmaps/generate=$GENERATE_MIPMAPS mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 ================================================ FILE: project/addons/terrain_3d/menu/directory_setup.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Directory Setup for Terrain3D extends Node const DIRECTORY_SETUP: String = "res://addons/terrain_3d/menu/directory_setup.tscn" var plugin: EditorPlugin var dialog: ConfirmationDialog var select_dir_btn: Button var selected_dir_le: LineEdit var editor_file_dialog: EditorFileDialog func _init() -> void: editor_file_dialog = EditorFileDialog.new() editor_file_dialog.set_filters(PackedStringArray(["*.res"])) editor_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE) editor_file_dialog.access = EditorFileDialog.ACCESS_RESOURCES editor_file_dialog.ok_button_text = "Open" editor_file_dialog.title = "Open a folder or file" editor_file_dialog.dir_selected.connect(_on_dir_selected) editor_file_dialog.size = Vector2i(850, 550) editor_file_dialog.transient = false editor_file_dialog.exclusive = false editor_file_dialog.popup_window = true add_child(editor_file_dialog) func directory_setup_popup() -> void: dialog = load(DIRECTORY_SETUP).instantiate() dialog.hide() # Nodes select_dir_btn = dialog.get_node("Margin/VBox/DirHBox/SelectDir") selected_dir_le = dialog.get_node("Margin/VBox/DirHBox/LineEdit") if plugin.terrain.data_directory: selected_dir_le.text = plugin.terrain.data_directory # Icons plugin.ui.set_button_editor_icon(select_dir_btn, "Folder") #Signals select_dir_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_DIR)) dialog.confirmed.connect(_on_close_requested) dialog.canceled.connect(_on_close_requested) dialog.get_ok_button().pressed.connect(_on_ok_pressed) # Popup EditorInterface.popup_dialog_centered(dialog) func _on_close_requested() -> void: dialog.queue_free() dialog = null func _on_select_file_pressed(file_mode: EditorFileDialog.FileMode) -> void: editor_file_dialog.file_mode = file_mode editor_file_dialog.popup_centered() func _on_dir_selected(path: String) -> void: selected_dir_le.text = path func _on_ok_pressed() -> void: if not plugin.terrain: push_error("Not connected terrain. Click the Terrain3D node first") return if selected_dir_le.text.is_empty(): push_error("No data directory specified") return if not DirAccess.dir_exists_absolute(selected_dir_le.text): push_error("Directory doesn't exist: ", selected_dir_le.text) return # Check if directory empty of terrain files var data_found: bool = false var files: Array = DirAccess.get_files_at(selected_dir_le.text) for file in files: if file.begins_with("terrain3d") || file.ends_with(".res"): data_found = true break print("Setting terrain directory: ", selected_dir_le.text) plugin.terrain.data_directory = selected_dir_le.text ================================================ FILE: project/addons/terrain_3d/menu/directory_setup.gd.uid ================================================ uid://0034ukv2mngn ================================================ FILE: project/addons/terrain_3d/menu/directory_setup.tscn ================================================ [gd_scene format=3 uid="uid://by3kr2nqbqr67"] [node name="DirectorySetup" type="ConfirmationDialog"] title = "Terrain3D Data Directory Setup" position = Vector2i(0, 36) size = Vector2i(750, 330) visible = true [node name="Margin" type="MarginContainer" parent="."] offset_left = 8.0 offset_top = 8.0 offset_right = 742.0 offset_bottom = 281.0 theme_override_constants/margin_left = 20 theme_override_constants/margin_top = 20 theme_override_constants/margin_right = 20 theme_override_constants/margin_bottom = 20 [node name="VBox" type="VBoxContainer" parent="Margin"] layout_mode = 2 [node name="Instructions" type="Label" parent="Margin/VBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 text = "Terrain3D now stores data in a directory instead of a single file. Each region is stored in a separate file named `terrain3d[-_]##[-_]##.res`. For instance, the region at location (-1, 1) would be named `terrain3d-01_01.res`. Enable Terrain3D / Debug / Show Region Labels for a visual display." autowrap_mode = 3 [node name="DirectoryLabel" type="Label" parent="Margin/VBox"] custom_minimum_size = Vector2(400, 0) layout_mode = 2 text = " Specify the directory to store your data. Any existing region files will be loaded." autowrap_mode = 3 [node name="DirHBox" type="HBoxContainer" parent="Margin/VBox"] layout_mode = 2 [node name="LineEdit" type="LineEdit" parent="Margin/VBox/DirHBox"] layout_mode = 2 size_flags_horizontal = 3 placeholder_text = "Data directory" [node name="SelectDir" type="Button" parent="Margin/VBox/DirHBox"] layout_mode = 2 [node name="Spacer" type="Control" parent="Margin/VBox"] custom_minimum_size = Vector2(0, 40) layout_mode = 2 ================================================ FILE: project/addons/terrain_3d/menu/terrain_menu.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Menu for Terrain3D extends HBoxContainer const DirectoryWizard: Script = preload("res://addons/terrain_3d/menu/directory_setup.gd") const ChannelPacker: Script = preload("res://addons/terrain_3d/menu/channel_packer.gd") const LodBaker: Script = preload("res://addons/terrain_3d/menu/baker.gd") var plugin: EditorPlugin var menu_button: MenuButton = MenuButton.new() var directory_setup: DirectoryWizard = DirectoryWizard.new() var packer: ChannelPacker = ChannelPacker.new() var baker: LodBaker = LodBaker.new() # These are IDs and order must be consistent with add_item and set_disabled IDs enum { MENU_DIRECTORY_SETUP, MENU_PACK_TEXTURES, MENU_SEPARATOR, MENU_BAKE_ARRAY_MESH, MENU_BAKE_OCCLUDER, MENU_SEPARATOR2, MENU_SET_UP_NAVIGATION, MENU_BAKE_NAV_MESH, } func _enter_tree() -> void: directory_setup.plugin = plugin packer.plugin = plugin baker.plugin = plugin add_child(directory_setup) add_child(baker) menu_button.text = "Terrain3D" menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP) menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES) menu_button.get_popup().add_separator("", MENU_SEPARATOR) menu_button.get_popup().add_item("Bake ArrayMesh...", MENU_BAKE_ARRAY_MESH) menu_button.get_popup().add_item("Bake Occluder3D...", MENU_BAKE_OCCLUDER) menu_button.get_popup().add_separator("", MENU_SEPARATOR2) menu_button.get_popup().add_item("Set up Navigation...", MENU_SET_UP_NAVIGATION) menu_button.get_popup().add_item("Bake NavMesh...", MENU_BAKE_NAV_MESH) menu_button.get_popup().id_pressed.connect(_on_menu_pressed) menu_button.about_to_popup.connect(_on_menu_about_to_popup) add_child(menu_button) func _on_menu_pressed(p_id: int) -> void: match p_id: MENU_DIRECTORY_SETUP: directory_setup.directory_setup_popup() MENU_PACK_TEXTURES: packer.pack_textures_popup() MENU_BAKE_ARRAY_MESH: baker.bake_mesh_popup() MENU_BAKE_OCCLUDER: baker.bake_occluder_popup() MENU_SET_UP_NAVIGATION: baker.set_up_navigation_popup() MENU_BAKE_NAV_MESH: baker.bake_nav_mesh() func _on_menu_about_to_popup() -> void: menu_button.get_popup().set_item_disabled(MENU_DIRECTORY_SETUP, not plugin.terrain) menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain) menu_button.get_popup().set_item_disabled(MENU_BAKE_ARRAY_MESH, not plugin.terrain) menu_button.get_popup().set_item_disabled(MENU_BAKE_OCCLUDER, not plugin.terrain) if plugin.terrain: var nav_regions: Array[NavigationRegion3D] = baker.find_terrain_nav_regions(plugin.terrain) menu_button.get_popup().set_item_disabled(MENU_BAKE_NAV_MESH, nav_regions.size() == 0) menu_button.get_popup().set_item_disabled(MENU_SET_UP_NAVIGATION, nav_regions.size() != 0) elif plugin.nav_region: var terrains: Array[Terrain3D] = baker.find_nav_region_terrains(plugin.nav_region) menu_button.get_popup().set_item_disabled(MENU_BAKE_NAV_MESH, terrains.size() == 0) menu_button.get_popup().set_item_disabled(MENU_SET_UP_NAVIGATION, true) else: menu_button.get_popup().set_item_disabled(MENU_BAKE_NAV_MESH, true) menu_button.get_popup().set_item_disabled(MENU_SET_UP_NAVIGATION, true) ================================================ FILE: project/addons/terrain_3d/menu/terrain_menu.gd.uid ================================================ uid://3gxvahogxa10 ================================================ FILE: project/addons/terrain_3d/plugin.cfg ================================================ [plugin] name="Terrain3D" description="A high performance, editable terrain system for Godot 4." author="Cory Petkovsek & Roope Palmroos" version="1.1.0-dev" script="src/editor_plugin.gd" ================================================ FILE: project/addons/terrain_3d/src/asset_dock.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Asset Dock for Terrain3D @tool extends PanelContainer signal confirmation_closed signal confirmation_confirmed signal confirmation_canceled const ES_DOCK_SLOT: String = "terrain3d/dock/slot" const ES_DOCK_TILE_SIZE: String = "terrain3d/dock/tile_size" const ES_DOCK_FLOATING: String = "terrain3d/dock/floating" const ES_DOCK_PINNED: String = "terrain3d/dock/always_on_top" const ES_DOCK_WINDOW_POSITION: String = "terrain3d/dock/window_position" const ES_DOCK_WINDOW_SIZE: String = "terrain3d/dock/window_size" const ES_DOCK_TAB: String = "terrain3d/dock/tab" var texture_list: ListContainer var mesh_list: ListContainer var current_list: ListContainer var _updating_list: bool var placement_opt: OptionButton var floating_btn: Button var pinned_btn: Button var size_slider: HSlider var box: BoxContainer var buttons: BoxContainer var textures_btn: Button var meshes_btn: Button var asset_container: ScrollContainer var confirm_dialog: ConfirmationDialog var _confirmed: bool = false var search_box: TextEdit var search_button: Button # Used only for editor, so change to single visible/hiddden enum { HIDDEN = -1, SIDEBAR = 0, BOTTOM = 1, WINDOWED = 2, } var state: int = HIDDEN enum { POS_LEFT_UL = 0, POS_LEFT_BL = 1, POS_LEFT_UR = 2, POS_LEFT_BR = 3, POS_RIGHT_UL = 4, POS_RIGHT_BL = 5, POS_RIGHT_UR = 6, POS_RIGHT_BR = 7, POS_BOTTOM = 8, POS_MAX = 9, } var slot: int = POS_RIGHT_BR var _initialized: bool = false var plugin: EditorPlugin var window: Window var _godot_last_state: Window.Mode = Window.MODE_FULLSCREEN func initialize(p_plugin: EditorPlugin) -> void: if p_plugin: plugin = p_plugin _godot_last_state = plugin.godot_editor_window.mode placement_opt = $Box/Buttons/PlacementOpt pinned_btn = $Box/Buttons/Pinned floating_btn = $Box/Buttons/Floating floating_btn.owner = null # Godot complains about buttons that are reparented size_slider = $Box/Buttons/SizeSlider size_slider.owner = null box = $Box buttons = $Box/Buttons textures_btn = $Box/Buttons/TexturesBtn meshes_btn = $Box/Buttons/MeshesBtn asset_container = $Box/ScrollContainer search_box = $Box/Buttons/SearchBox search_box.owner = null search_button = $Box/Buttons/SearchBox/SearchButton texture_list = ListContainer.new() texture_list.name = "TextureList" texture_list.plugin = plugin texture_list.type = Terrain3DAssets.TYPE_TEXTURE asset_container.add_child(texture_list, true) mesh_list = ListContainer.new() mesh_list.name = "MeshList" mesh_list.plugin = plugin mesh_list.type = Terrain3DAssets.TYPE_MESH mesh_list.visible = false asset_container.add_child(mesh_list, true) current_list = texture_list load_editor_settings() # Connect signals resized.connect(update_layout) textures_btn.pressed.connect(_on_textures_pressed) meshes_btn.pressed.connect(_on_meshes_pressed) placement_opt.item_selected.connect(set_slot) floating_btn.pressed.connect(make_dock_float) pinned_btn.toggled.connect(_on_pin_changed) pinned_btn.visible = ( window != null ) pinned_btn.owner = null size_slider.value_changed.connect(_on_slider_changed) plugin.ui.toolbar.tool_changed.connect(_on_tool_changed) meshes_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale()) textures_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale()) _initialized = true update_dock() update_layout() func _ready() -> void: if not _initialized: return # Setup styles set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel")) # Avoid saving icon resources in tscn when editing w/ a tool script if EditorInterface.get_edited_scene_root() != self: pinned_btn.icon = get_theme_icon("Pin", "EditorIcons") pinned_btn.text = "" floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons") floating_btn.text = "" search_button.icon = get_theme_icon("Search", "EditorIcons") search_box.text_changed.connect(_on_search_text_changed) search_button.pressed.connect(_on_search_button_pressed) confirm_dialog = ConfirmationDialog.new() add_child(confirm_dialog, true) confirm_dialog.hide() confirm_dialog.confirmed.connect(func(): _confirmed = true; \ emit_signal("confirmation_closed"); \ emit_signal("confirmation_confirmed") ) confirm_dialog.canceled.connect(func(): _confirmed = false; \ emit_signal("confirmation_closed"); \ emit_signal("confirmation_canceled") ) func _gui_input(p_event: InputEvent) -> void: if p_event is InputEventMouseButton: if search_box.has_focus(): if plugin.debug: print("Terrain3DAssetDock: _on_box_gui_input: search_box releasing focus") search_box.release_focus() ## Dock placement func set_slot(p_slot: int) -> void: if plugin.debug: print("Terrain3DAssetDock: set_slot: ", p_slot) p_slot = clamp(p_slot, 0, POS_MAX-1) if slot != p_slot: slot = p_slot placement_opt.selected = slot save_editor_settings() plugin.select_terrain() update_dock() func remove_dock(p_force: bool = false) -> void: if state == SIDEBAR: plugin.remove_control_from_docks(self) state = HIDDEN elif state == BOTTOM: plugin.remove_control_from_bottom_panel(self) state = HIDDEN # If windowed and destination is not window or final exit, otherwise leave elif state == WINDOWED and p_force and window: var parent: Node = get_parent() if parent: parent.remove_child(self) plugin.godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered) plugin.godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered) plugin.godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited) window.hide() window.queue_free() window = null floating_btn.button_pressed = false floating_btn.visible = true pinned_btn.visible = false placement_opt.visible = true state = HIDDEN update_dock() # return window to side/bottom func update_dock() -> void: if not _initialized or window: return update_assets() # Move dock to new destination remove_dock() # Sidebar if slot < POS_BOTTOM: state = SIDEBAR plugin.add_control_to_dock(slot, self) # Bottom elif slot == POS_BOTTOM: state = BOTTOM plugin.add_control_to_bottom_panel(self, "Terrain3D") plugin.make_bottom_panel_item_visible(self) func update_layout() -> void: if plugin.debug > 1: print("Terrain3DAssetDock: update_layout") if not _initialized: return # Detect if we have a new window from Make floating, grab it so we can free it properly if not window and get_parent() and get_parent().get_parent() is Window: window = get_parent().get_parent() make_dock_float() return # Will call this function again upon display # Vertical layout: buttons on top if size.x < 500 or ( not window and slot < POS_BOTTOM ): box.vertical = true buttons.vertical = false search_box.reparent(box) box.move_child(search_box, 1) size_slider.reparent(box) box.move_child(size_slider, 2) floating_btn.reparent(buttons) pinned_btn.reparent(buttons) else: # Wide layout: buttons on left box.vertical = false buttons.vertical = true search_box.reparent(buttons) buttons.move_child(search_box, 0) size_slider.reparent(buttons) buttons.move_child(size_slider, 4) floating_btn.reparent(box) pinned_btn.reparent(box) save_editor_settings() func set_selected_by_asset_id(p_id: int) -> void: search_box.text = "" _on_search_text_changed() current_list.set_selected_id(p_id) func _on_search_text_changed() -> void: if plugin.debug: print("Terrain3DAssetDock: _on_search_text_changed: ", search_box.text) search_box.text = search_box.text.strip_escapes() var len: int = search_box.text.length() if len > 0: search_box.set_caret_column(len) search_button.icon = get_theme_icon("Close", "EditorIcons") else: search_button.icon = get_theme_icon("Search", "EditorIcons") mesh_list.search_text = search_box.text texture_list.search_text = search_box.text current_list.update_asset_list() current_list.set_selected_id(0) func _on_search_button_pressed() -> void: if plugin.debug: print("Terrain3DAssetDock: _on_search_button_pressed") if search_box.text.length() > 0: search_box.text = "" _on_search_text_changed() else: if plugin.debug: print("Terrain3DAssetDock: _on_search_button_pressed: Search box grabbing focus") search_box.grab_focus() ## Dock Button handlers func _on_pin_changed(toggled: bool) -> void: if window: window.always_on_top = pinned_btn.button_pressed save_editor_settings() func _on_slider_changed(value: float) -> void: # Set both lists so they match if texture_list: texture_list.set_entry_width(value) if mesh_list: mesh_list.set_entry_width(value) save_editor_settings() # Hack to trigger ScrollContainer::_reposition_children() to update size of scroll bar handle asset_container.layout_direction = Control.LAYOUT_DIRECTION_LTR asset_container.layout_direction = Control.LAYOUT_DIRECTION_INHERITED func _on_textures_pressed() -> void: if plugin.debug: print("Terrain3DAssetDock: _on_textures_pressed") if _updating_list or current_list == texture_list: return _updating_list = true current_list = texture_list texture_list.visible = true mesh_list.visible = false textures_btn.set_pressed_no_signal(true) meshes_btn.set_pressed_no_signal(false) texture_list.update_asset_list() if plugin.is_terrain_valid(): EditorInterface.edit_node(plugin.terrain) save_editor_settings() _updating_list = false func _on_meshes_pressed() -> void: if plugin.debug: print("Terrain3DAssetDock: _on_meshes_pressed") if _updating_list or current_list == mesh_list: return _updating_list = true current_list = mesh_list mesh_list.visible = true texture_list.visible = false meshes_btn.set_pressed_no_signal(true) textures_btn.set_pressed_no_signal(false) mesh_list.update_asset_list() if plugin.is_terrain_valid(): EditorInterface.edit_node(plugin.terrain) save_editor_settings() _updating_list = false func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void: if plugin.debug: print("Terrain3DAssetDock: _on_tool_changed: ", p_tool, ", ", p_operation) remove_all_highlights() if p_tool == Terrain3DEditor.INSTANCER: _on_meshes_pressed() elif p_tool in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]: _on_textures_pressed() ## Update Dock Contents func update_assets() -> void: if plugin.debug: print("Terrain3DAssetDock: update_assets: ", plugin.terrain.assets if plugin.terrain else "") if not _initialized: return # Verify signals to individual lists if plugin.is_terrain_valid() and plugin.terrain.assets: if not plugin.terrain.assets.textures_changed.is_connected(texture_list.update_asset_list): plugin.terrain.assets.textures_changed.connect(texture_list.update_asset_list) if not plugin.terrain.assets.meshes_changed.is_connected(mesh_list.update_asset_list): plugin.terrain.assets.meshes_changed.connect(mesh_list.update_asset_list) current_list.update_asset_list() func remove_all_highlights(): if not plugin.terrain: return for i: int in texture_list.entries.size(): var resource: Terrain3DTextureAsset = texture_list.entries[i].resource if resource and resource.is_highlighted(): resource.set_highlighted(false) for i: int in mesh_list.entries.size(): var resource: Terrain3DMeshAsset = mesh_list.entries[i].resource if resource and resource.is_highlighted(): resource.set_highlighted(false) ## Window Management func make_dock_float() -> void: # If not already created (eg from editor panel 'Make Floating' button) if not window: remove_dock() create_window() state = WINDOWED visible = true # Asset dock contents are hidden when popping out of the bottom! pinned_btn.visible = true floating_btn.visible = false placement_opt.visible = false window.title = "Terrain3D Asset Dock" window.always_on_top = pinned_btn.button_pressed window.close_requested.connect(remove_dock.bind(true)) window.window_input.connect(_on_window_input) window.focus_exited.connect(save_editor_settings) window.mouse_exited.connect(save_editor_settings) window.size_changed.connect(save_editor_settings) plugin.godot_editor_window.mouse_entered.connect(_on_godot_window_entered) plugin.godot_editor_window.focus_entered.connect(_on_godot_focus_entered) plugin.godot_editor_window.focus_exited.connect(_on_godot_focus_exited) plugin.godot_editor_window.grab_focus() update_assets() save_editor_settings() func create_window() -> void: window = Window.new() window.wrap_controls = true var mc := MarginContainer.new() mc.set_anchors_preset(PRESET_FULL_RECT, false) mc.add_child(self, true) window.add_child(mc, true) window.set_transient(false) window.set_size(plugin.get_setting(ES_DOCK_WINDOW_SIZE, Vector2i(512, 512))) window.set_position(plugin.get_setting(ES_DOCK_WINDOW_POSITION, Vector2i(704, 284))) plugin.add_child(window, true) window.show() func clamp_window_position() -> void: if window and window.visible: var bounds: Vector2i if EditorInterface.get_editor_settings().get_setting("interface/editor/single_window_mode"): bounds = EditorInterface.get_base_control().size else: bounds = DisplayServer.screen_get_position(window.current_screen) bounds += DisplayServer.screen_get_size(window.current_screen) var margin: int = 40 window.position.x = clamp(window.position.x, -window.size.x + 2*margin, bounds.x - margin) window.position.y = clamp(window.position.y, 25, bounds.y - margin) func _on_window_input(event: InputEvent) -> void: # Capture CTRL+S when doc focused to save scene if event is InputEventKey and event.keycode == KEY_S and event.pressed and event.is_command_or_control_pressed(): save_editor_settings() EditorInterface.save_scene() func _on_godot_window_entered() -> void: if plugin.debug > 1: print("Terrain3DAssetDock: _on_godot_window_entered") if is_instance_valid(window) and window.has_focus(): plugin.godot_editor_window.grab_focus() func _on_godot_focus_entered() -> void: if plugin.debug > 1: print("Terrain3DAssetDock: _on_godot_focus_entered") # If asset dock is windowed, and Godot was minimized, and now is not, restore asset dock window if is_instance_valid(window): if _godot_last_state == Window.MODE_MINIMIZED and plugin.godot_editor_window.mode != Window.MODE_MINIMIZED: window.show() _godot_last_state = plugin.godot_editor_window.mode plugin.godot_editor_window.grab_focus() func _on_godot_focus_exited() -> void: if plugin.debug > 1: print("Terrain3DAssetDock: _on_godot_focus_exited") if is_instance_valid(window) and plugin.godot_editor_window.mode == Window.MODE_MINIMIZED: window.hide() _godot_last_state = plugin.godot_editor_window.mode ## Manage Editor Settings func load_editor_settings() -> void: floating_btn.button_pressed = plugin.get_setting(ES_DOCK_FLOATING, false) pinned_btn.button_pressed = plugin.get_setting(ES_DOCK_PINNED, true) size_slider.value = plugin.get_setting(ES_DOCK_TILE_SIZE, 90) _on_slider_changed(size_slider.value) set_slot(plugin.get_setting(ES_DOCK_SLOT, POS_BOTTOM)) if floating_btn.button_pressed: make_dock_float() # TODO Don't save tab until thumbnail generation more reliable #if plugin.get_setting(ES_DOCK_TAB, 0) == 1: # _on_meshes_pressed() func save_editor_settings() -> void: if not _initialized: return clamp_window_position() plugin.set_setting(ES_DOCK_SLOT, slot) plugin.set_setting(ES_DOCK_TILE_SIZE, size_slider.value) plugin.set_setting(ES_DOCK_FLOATING, floating_btn.button_pressed) plugin.set_setting(ES_DOCK_PINNED, pinned_btn.button_pressed) # TODO Don't save tab until thumbnail generation more reliable # plugin.set_setting(ES_DOCK_TAB, 0 if current_list == texture_list else 1) if window: plugin.set_setting(ES_DOCK_WINDOW_SIZE, window.size) plugin.set_setting(ES_DOCK_WINDOW_POSITION, window.position) ############################################################## ## class ListContainer ############################################################## class ListContainer extends Container: var plugin: EditorPlugin var type := Terrain3DAssets.TYPE_TEXTURE var entries: Array[ListEntry] var selected_id: int = 0 var height: float = 0. var width: float = 90. var focus_style: StyleBox var _clearing_resource: bool = false var search_text: String = "" func _ready() -> void: set_v_size_flags(SIZE_EXPAND_FILL) set_h_size_flags(SIZE_EXPAND_FILL) add_theme_color_override("font_color", Color.WHITE) add_theme_color_override("font_shadow_color", Color.BLACK) add_theme_constant_override("shadow_offset_x", 1) add_theme_constant_override("shadow_offset_y", 1) func clear() -> void: for e in entries: e.get_parent().remove_child(e) e.queue_free() entries.clear() func update_asset_list() -> void: if plugin.debug: print("Terrain3DListContainer ", name, ": update_asset_list") clear() # Grab terrain var t: Terrain3D = plugin.get_terrain() if not (t and t.assets): return if type == Terrain3DAssets.TYPE_TEXTURE: var texture_count: int = t.assets.get_texture_count() for i in texture_count: var texture: Terrain3DTextureAsset = t.assets.get_texture_asset(i) add_item(texture) if texture_count < Terrain3DAssets.MAX_TEXTURES: add_item() else: if plugin.terrain: plugin.terrain.assets.create_mesh_thumbnails() var mesh_count: int = t.assets.get_mesh_count() for i in mesh_count: var mesh: Terrain3DMeshAsset = t.assets.get_mesh_asset(i) add_item(mesh) if mesh_count < Terrain3DAssets.MAX_MESHES: add_item() set_selected_id(selected_id) func add_item(p_resource: Resource = null) -> void: var entry: ListEntry = ListEntry.new() entry.focus_style = focus_style entry.set_edited_resource(p_resource) if not entry.get_resource_name().containsn(search_text) and not search_text == "": return var res_id: int = p_resource.id if p_resource else entries.size() entry.hovered.connect(_on_resource_hovered.bind(res_id)) entry.clicked.connect(clicked_id.bind(entries.size())) entry.inspected.connect(_on_resource_inspected) entry.changed.connect(_on_resource_changed.bind(res_id)) entry.type = type add_child(entry, true) entries.push_back(entry) if p_resource: if not p_resource.id_changed.is_connected(set_selected_after_swap): p_resource.id_changed.connect(set_selected_after_swap) func _on_resource_hovered(p_id: int): if type == Terrain3DAssets.TYPE_MESH: if plugin.terrain: plugin.terrain.assets.create_mesh_thumbnails(p_id, Vector2i(512, 512), true) func set_selected_after_swap(p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int) -> void: EditorInterface.mark_scene_as_unsaved() set_selected_id(clamp(p_new_id, 0, entries.size() - 2)) func clicked_id(p_id: int) -> void: # Select Tool if clicking an asset plugin.select_terrain() if type == Terrain3DAssets.TYPE_TEXTURE and \ not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]: plugin.ui.toolbar.change_tool("PaintTexture") elif type == Terrain3DAssets.TYPE_MESH and plugin.editor.get_tool() != Terrain3DEditor.INSTANCER: plugin.ui.toolbar.change_tool("InstanceMeshes") set_selected_id(p_id) func set_selected_id(p_id: int) -> void: # "Add new" is the final entry only when search box is blank var max_id: int = max(0, entries.size() - (1 if search_text else 2)) if plugin.debug: print("Terrain3DListContainer ", name, ": set_selected_id: ", selected_id, " to ", clamp(p_id, 0, max_id)) selected_id = clamp(p_id, 0, max_id) for i in entries.size(): var entry: ListEntry = entries[i] entry.set_selected(i == selected_id) plugin.ui._on_setting_changed() func get_selected_asset_id() -> int: # "Add new" is the final entry only when search box is blank var max_id: int = max(0, entries.size() - (1 if search_text else 2)) var id: int = clamp(selected_id, 0, max_id) if plugin.debug: print("Terrain3DListContainer ", name, ": get_selected_asset_id: selected_id: ", selected_id, ", clamped: ", id, ", entries: ", entries.size()) if id >= entries.size(): return 0 var res: Resource = entries[id].resource if not res: return 0 if type == Terrain3DAssets.AssetType.TYPE_MESH: return (res as Terrain3DMeshAsset).id else: return (res as Terrain3DTextureAsset).id func _on_resource_inspected(p_resource: Resource) -> void: await get_tree().process_frame EditorInterface.edit_resource(p_resource) func _on_resource_changed(p_resource: Resource, p_id: int) -> void: if not p_resource and _clearing_resource: return if not p_resource: if plugin.debug: print("Terrain3DListContainer ", name, ": _on_resource_changed: removing asset ID: ", p_id) _clearing_resource = true var asset_dock: Control = get_parent().get_parent().get_parent() if type == Terrain3DAssets.TYPE_TEXTURE: asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this texture?" else: asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this mesh and delete all instances?" asset_dock.confirm_dialog.popup_centered() await asset_dock.confirmation_closed if not asset_dock._confirmed: update_asset_list() _clearing_resource = false return if not plugin.is_terrain_valid(): plugin.select_terrain() await get_tree().process_frame if plugin.is_terrain_valid(): if type == Terrain3DAssets.TYPE_TEXTURE: plugin.terrain.assets.set_texture_asset(p_id, p_resource) else: plugin.terrain.assets.set_mesh_asset(p_id, p_resource) # If removing an entry, clear inspector if not p_resource: EditorInterface.inspect_object(null) _clearing_resource = false func set_entry_width(value: float) -> void: width = clamp(value, 90., 512.) redraw() func get_entry_width() -> float: return width func redraw() -> void: height = 0 var id: int = 0 var separation: float = 2. var columns: int = 3 columns = clamp(size.x / width, 1, 100) var tile_size: Vector2 = Vector2(width, width) - Vector2(separation, separation) var count_font_size = clamp(tile_size.x/11, 11, 16) var name_font_size = clamp(tile_size.x/12, 12, 18) for c in get_children(): if is_instance_valid(c): c.size = tile_size c.position = Vector2(id % columns, id / columns) * width + \ Vector2(separation / columns, separation / columns) height = max(height, c.position.y + width) id += 1 if type == Terrain3DAssets.TYPE_MESH: c.count_label.add_theme_font_size_override("font_size", count_font_size) c.name_label.add_theme_font_size_override("font_size", name_font_size) # Needed to enable ScrollContainer scroll bar func _get_minimum_size() -> Vector2: return Vector2(0, height) func _notification(p_what) -> void: if p_what == NOTIFICATION_SORT_CHILDREN: redraw() ############################################################## ## class ListEntry ############################################################## class ListEntry extends MarginContainer: signal hovered() signal clicked() signal changed(resource: Resource) signal inspected(resource: Resource) var resource: Resource var type := Terrain3DAssets.TYPE_TEXTURE var _thumbnail: Texture2D var drop_data: bool = false var is_hovered: bool = false var is_selected: bool = false var is_highlighted: bool = false var name_label: Label var count_label: Label var button_row: FlowContainer var button_enabled: TextureButton var button_highlight: TextureButton var button_edit: TextureButton var spacer: Control var button_clear: TextureButton @onready var focus_style: StyleBox = get_theme_stylebox("focus", "Button").duplicate() @onready var background: StyleBox = get_theme_stylebox("pressed", "Button") @onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons") @onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons") @onready var enabled_icon: Texture2D = get_theme_icon("GuiVisibilityVisible", "EditorIcons") @onready var disabled_icon: Texture2D = get_theme_icon("GuiVisibilityHidden", "EditorIcons") @onready var highlight_icon: Texture2D = get_theme_icon("PreviewSun", "EditorIcons") @onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons") func _ready() -> void: name = "ListEntry" custom_minimum_size = Vector2i(86., 86.) mouse_filter = Control.MOUSE_FILTER_PASS add_theme_constant_override("margin_top", 5) add_theme_constant_override("margin_left", 5) add_theme_constant_override("margin_right", 5) if resource: is_highlighted = resource.is_highlighted() setup_buttons() setup_label() setup_count_label() focus_style.set_border_width_all(2) focus_style.set_border_color(Color(1, 1, 1, .67)) func setup_buttons() -> void: destroy_buttons() button_row = FlowContainer.new() button_enabled = TextureButton.new() button_highlight = TextureButton.new() button_edit = TextureButton.new() spacer = Control.new() button_clear = TextureButton.new() var icon_size: Vector2 = Vector2(12, 12) button_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL button_row.alignment = FlowContainer.ALIGNMENT_CENTER button_row.mouse_filter = Control.MOUSE_FILTER_PASS add_child(button_row, true) if type == Terrain3DAssets.TYPE_MESH: button_enabled.set_texture_normal(enabled_icon) button_enabled.set_texture_pressed(disabled_icon) button_enabled.set_custom_minimum_size(icon_size) button_enabled.set_h_size_flags(Control.SIZE_SHRINK_END) button_enabled.set_visible(resource != null) button_enabled.tooltip_text = "Enable Instances" button_enabled.toggle_mode = true button_enabled.mouse_filter = Control.MOUSE_FILTER_PASS button_enabled.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button_enabled.pressed.connect(_on_enable) button_row.add_child(button_enabled, true) button_highlight.set_texture_normal(highlight_icon) button_highlight.set_custom_minimum_size(icon_size) button_highlight.set_h_size_flags(Control.SIZE_SHRINK_END) button_highlight.set_visible(resource != null) button_highlight.tooltip_text = "Highlight " + ( "Instances" if type == Terrain3DAssets.TYPE_MESH else "Texture" ) button_highlight.toggle_mode = true button_highlight.mouse_filter = Control.MOUSE_FILTER_PASS button_highlight.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button_highlight.set_pressed_no_signal(is_highlighted) button_highlight.pressed.connect(_on_highlight) button_row.add_child(button_highlight, true) button_edit.set_texture_normal(edit_icon) button_edit.set_custom_minimum_size(icon_size) button_edit.set_h_size_flags(Control.SIZE_SHRINK_END) button_edit.set_visible(resource != null) button_edit.tooltip_text = "Edit Asset" button_edit.mouse_filter = Control.MOUSE_FILTER_PASS button_edit.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button_edit.pressed.connect(_on_edit) button_row.add_child(button_edit, true) spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL spacer.mouse_filter = Control.MOUSE_FILTER_PASS button_row.add_child(spacer, true) button_clear.set_texture_normal(clear_icon) button_clear.set_custom_minimum_size(icon_size) button_clear.set_h_size_flags(Control.SIZE_SHRINK_END) button_clear.set_visible(resource != null) button_clear.tooltip_text = "Clear Asset" button_clear.mouse_filter = Control.MOUSE_FILTER_PASS button_clear.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button_clear.pressed.connect(_on_clear) button_row.add_child(button_clear, true) func destroy_buttons() -> void: if button_row: button_row.free() button_row = null if button_enabled: button_enabled.free() button_enabled = null if button_highlight: button_highlight.free() button_highlight = null if button_edit: button_edit.free() button_edit = null if spacer: spacer.free() spacer = null if button_clear: button_clear.free() button_clear = null func get_resource_name() -> StringName: if resource: if resource is Terrain3DMeshAsset: return (resource as Terrain3DMeshAsset).get_name() elif resource is Terrain3DTextureAsset: return (resource as Terrain3DTextureAsset).get_name() return "" func setup_label() -> void: name_label = Label.new() name_label.name = "MeshLabel" name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL name_label.add_theme_font_size_override("font_size", 14) name_label.add_theme_color_override("font_color", Color.WHITE) name_label.add_theme_color_override("font_shadow_color", Color.BLACK) name_label.add_theme_constant_override("shadow_offset_x", 1) name_label.add_theme_constant_override("shadow_offset_y", 1) name_label.visible = false name_label.autowrap_mode = TextServer.AUTOWRAP_OFF name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS add_child(name_label, true) func setup_count_label() -> void: count_label = Label.new() count_label.name = "CountLabel" count_label.text = "" count_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT count_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM count_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL count_label.size_flags_vertical = Control.SIZE_EXPAND_FILL count_label.add_theme_font_size_override("font_size", 14) count_label.add_theme_color_override("font_color", Color.WHITE) count_label.add_theme_color_override("font_shadow_color", Color.BLACK) count_label.add_theme_constant_override("shadow_offset_x", 1) count_label.add_theme_constant_override("shadow_offset_y", 1) add_child(count_label, true) var mesh_resource: Terrain3DMeshAsset = resource as Terrain3DMeshAsset if not mesh_resource: return mesh_resource.instance_count_changed.connect(update_count_label) update_count_label() func update_count_label() -> void: if not type == Terrain3DAssets.AssetType.TYPE_MESH or \ ( resource and not resource.is_enabled() ): count_label.text = "" return var mesh_resource: Terrain3DMeshAsset = resource as Terrain3DMeshAsset if not mesh_resource: count_label.text = str(0) else: count_label.text = _format_number(mesh_resource.get_instance_count()) func _notification(p_what) -> void: match p_what: NOTIFICATION_PREDELETE: destroy_buttons() NOTIFICATION_DRAW: # Hide spacer if icons are crowding small textures spacer.visible = size.x > 94. or type == Terrain3DAssets.TYPE_TEXTURE var rect: Rect2 = Rect2(Vector2.ZERO, get_size()) if !resource: draw_style_box(background, rect) draw_texture(add_icon, (get_size() / 2) - (add_icon.get_size() / 2)) else: _thumbnail = resource.get_thumbnail() if _thumbnail: draw_texture_rect(_thumbnail, rect, false) texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS else: draw_rect(rect, Color(.15, .15, .15, 1.)) if type == Terrain3DAssets.TYPE_TEXTURE: self_modulate = resource.get_highlight_color() if is_highlighted else resource.get_albedo_color() else: button_enabled.set_pressed_no_signal(!resource.is_enabled()) self_modulate = resource.get_highlight_color() button_highlight.self_modulate = Color("FC7F7F") if is_highlighted else Color.WHITE if drop_data: draw_style_box(focus_style, rect) if is_hovered: draw_rect(rect, Color(1, 1, 1, 0.2)) if is_selected: draw_style_box(focus_style, rect) NOTIFICATION_MOUSE_ENTER: if not resource: name_label.visible = false else: name_label.visible = true is_hovered = true name_label.text = get_resource_name() tooltip_text = get_resource_name() emit_signal("hovered") queue_redraw() NOTIFICATION_MOUSE_EXIT: name_label.visible = false is_hovered = false drop_data = false queue_redraw() func _gui_input(p_event: InputEvent) -> void: if p_event is InputEventMouseButton: if p_event.is_pressed(): match p_event.get_button_index(): MOUSE_BUTTON_LEFT: # If `Add new` is clicked if !resource: if type == Terrain3DAssets.TYPE_TEXTURE: set_edited_resource(Terrain3DTextureAsset.new(), false) else: set_edited_resource(Terrain3DMeshAsset.new(), false) _on_edit() else: emit_signal("clicked") MOUSE_BUTTON_RIGHT: if resource: _on_edit() MOUSE_BUTTON_MIDDLE: if resource: _on_clear() func _can_drop_data(p_at_position: Vector2, p_data: Variant) -> bool: drop_data = false if typeof(p_data) == TYPE_DICTIONARY: if p_data.files.size() == 1: queue_redraw() drop_data = true return drop_data func _drop_data(p_at_position: Vector2, p_data: Variant) -> void: if typeof(p_data) == TYPE_DICTIONARY: var res: Resource = load(p_data.files[0]) if res is Texture2D and type == Terrain3DAssets.TYPE_TEXTURE: var ta := Terrain3DTextureAsset.new() if resource is Terrain3DTextureAsset: ta.id = resource.id ta.set_albedo_texture(res) set_edited_resource(ta, false) resource = ta elif res is Terrain3DTextureAsset and type == Terrain3DAssets.TYPE_TEXTURE: if resource is Terrain3DTextureAsset: res.id = resource.id set_edited_resource(res, false) elif res is PackedScene and type == Terrain3DAssets.TYPE_MESH: if not resource: resource = Terrain3DMeshAsset.new() set_edited_resource(resource, false) resource.set_scene_file(res) elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH: if resource is Terrain3DMeshAsset: res.id = resource.id set_edited_resource(res, false) emit_signal("clicked") emit_signal("inspected", resource) func set_edited_resource(p_res: Resource, p_no_signal: bool = true) -> void: resource = p_res if resource: if not resource.setting_changed.is_connected(_on_resource_changed): resource.setting_changed.connect(_on_resource_changed) if resource is Terrain3DTextureAsset: if not resource.file_changed.is_connected(_on_resource_changed): resource.file_changed.connect(_on_resource_changed) elif resource is Terrain3DMeshAsset: if not resource.instancer_setting_changed.is_connected(_on_resource_changed): resource.instancer_setting_changed.connect(_on_resource_changed) if button_clear: button_clear.set_visible(resource != null) queue_redraw() if not p_no_signal: emit_signal("changed", resource) func _on_resource_changed(_value: int = 0) -> void: queue_redraw() emit_signal("changed", resource) func set_selected(value: bool) -> void: is_selected = value if is_selected: # Handle scrolling to show the selected item await get_tree().process_frame if is_inside_tree(): get_parent().get_parent().get_v_scroll_bar().ratio = position.y / get_parent().size.y queue_redraw() func _on_clear() -> void: if resource: name_label.hide() set_edited_resource(null, false) update_count_label() func _on_edit() -> void: emit_signal("clicked") emit_signal("inspected", resource) func _on_enable() -> void: if resource is Terrain3DMeshAsset: resource.set_enabled(!resource.is_enabled()) func _on_highlight() -> void: is_highlighted = !is_highlighted resource.set_highlighted(is_highlighted) func _format_number(num: int) -> String: var is_negative: bool = num < 0 var str_num: String = str(abs(num)) var result: String = "" var length: int = str_num.length() for i in length: result = str_num[length - 1 - i] + result if i < length - 1 and (i + 1) % 3 == 0: result = "," + result return "-" + result if is_negative else result ================================================ FILE: project/addons/terrain_3d/src/asset_dock.gd.uid ================================================ uid://bgoifepft1hjw ================================================ FILE: project/addons/terrain_3d/src/asset_dock.tscn ================================================ [gd_scene load_steps=2 format=3 uid="uid://dkb6hii5e48m2"] [ext_resource type="Script" uid="uid://bgoifepft1hjw" path="res://addons/terrain_3d/src/asset_dock.gd" id="1_e23pg"] [node name="Terrain3D" type="PanelContainer"] custom_minimum_size = Vector2(256, 95) offset_right = 766.0 offset_bottom = 100.0 script = ExtResource("1_e23pg") [node name="Box" type="BoxContainer" parent="."] layout_mode = 2 size_flags_vertical = 3 vertical = true [node name="Buttons" type="BoxContainer" parent="Box"] layout_mode = 2 [node name="SearchBox" type="TextEdit" parent="Box/Buttons"] custom_minimum_size = Vector2(100, 30) layout_mode = 2 placeholder_text = "Search" emoji_menu_enabled = false scroll_fit_content_height = true caret_blink = true caret_multiple = false [node name="SearchButton" type="Button" parent="Box/Buttons/SearchBox"] layout_mode = 1 anchors_preset = 6 anchor_left = 1.0 anchor_top = 0.5 anchor_right = 1.0 anchor_bottom = 0.5 offset_left = -13.0 offset_top = -4.0 offset_right = -5.0 offset_bottom = 4.0 grow_horizontal = 0 grow_vertical = 2 action_mode = 0 [node name="TexturesBtn" type="Button" parent="Box/Buttons"] custom_minimum_size = Vector2(80, 30) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_font_sizes/font_size = 16 toggle_mode = true button_pressed = true text = "Textures" [node name="MeshesBtn" type="Button" parent="Box/Buttons"] custom_minimum_size = Vector2(80, 30) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 theme_override_font_sizes/font_size = 16 toggle_mode = true text = "Meshes" [node name="PlacementOpt" type="OptionButton" parent="Box/Buttons"] custom_minimum_size = Vector2(80, 30) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 0 selected = 7 item_count = 9 popup/item_0/text = "Left_UL" popup/item_0/id = 0 popup/item_1/text = "Left_BL" popup/item_1/id = 1 popup/item_2/text = "Left_UR" popup/item_2/id = 2 popup/item_3/text = "Left_BR" popup/item_3/id = 3 popup/item_4/text = "Right_UL" popup/item_4/id = 4 popup/item_5/text = "Right_BL " popup/item_5/id = 5 popup/item_6/text = "Right_UR" popup/item_6/id = 6 popup/item_7/text = "Right_BR" popup/item_7/id = 7 popup/item_8/text = "Bottom" popup/item_8/id = 8 [node name="SizeSlider" type="HSlider" parent="Box/Buttons"] custom_minimum_size = Vector2(80, 10) layout_mode = 2 size_flags_horizontal = 3 min_value = 90.0 max_value = 512.0 value = 90.0 [node name="Floating" type="Button" parent="Box/Buttons"] layout_mode = 2 size_flags_horizontal = 0 size_flags_vertical = 0 tooltip_text = "Pop this dock out to a floating window." toggle_mode = true text = "F" flat = true [node name="Pinned" type="Button" parent="Box/Buttons"] layout_mode = 2 size_flags_horizontal = 0 size_flags_vertical = 0 tooltip_text = "Make this window \"Always on top\"." toggle_mode = true text = "P" flat = true [node name="ScrollContainer" type="ScrollContainer" parent="Box"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 ================================================ FILE: project/addons/terrain_3d/src/double_slider.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # DoubleSlider for Terrain3D # Should work for other UIs @tool class_name DoubleSlider extends Control signal value_changed(Vector2) var label: Label var suffix: String var grabbed_handle: int = 0 # -1 left, 0 none, 1 right var min_value: float = 0.0 var max_value: float = 100.0 var step: float = 1.0 var range := Vector2(0, 100) var display_scale: float = 1. var position_x: float = 0. var minimum_x: float = 60. func _ready() -> void: # Setup Display Scale # 0 auto, 1 75%, 2 100%, 3 125%, 4 150%, 5 175%, 6 200%, 7 custom var es: EditorSettings = EditorInterface.get_editor_settings() var ds: int = es.get_setting("interface/editor/display_scale") if ds == 0: ds = 2 elif ds == 7: display_scale = es.get_setting("interface/editor/custom_display_scale") else: display_scale = float(ds + 2) * .25 update_label() func set_min(p_value: float) -> void: min_value = p_value if range.x <= min_value: range.x = min_value set_value(range) update_label() func get_min() -> float: return min_value func set_max(p_value: float) -> void: max_value = p_value if range.y == 0 or range.y >= max_value: range.y = max_value set_value(range) update_label() func get_max() -> float: return max_value func set_step(p_step: float) -> void: step = p_step func get_step() -> float: return step func set_value(p_range: Vector2) -> void: range.x = clamp(p_range.x, min_value, max_value) range.y = clamp(p_range.y, min_value, max_value) if range.y < range.x: var tmp: float = range.x range.x = range.y range.y = tmp update_label() emit_signal("value_changed", Vector2(range.x, range.y)) queue_redraw() func get_value() -> Vector2: return range func update_label() -> void: if label: label.set_text(str(range.x) + suffix + "/" + str(range.y) + suffix) if position_x == 0: position_x = label.position.x else: label.position.x = position_x + 5 * display_scale label.custom_minimum_size.x = minimum_x + 5 * display_scale func _get_handle() -> int: return 1 func _gui_input(p_event: InputEvent) -> void: if p_event is InputEventMouseButton: var button: int = p_event.get_button_index() if button in [ MOUSE_BUTTON_LEFT, MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN ]: if p_event.is_pressed(): var mid_point = (range.x + range.y) / 2.0 var xpos: float = p_event.get_position().x * 2.0 if xpos >= mid_point: grabbed_handle = 1 else: grabbed_handle = -1 match button: MOUSE_BUTTON_LEFT: set_slider(p_event.get_position().x) MOUSE_BUTTON_WHEEL_DOWN: set_slider(-1., true) MOUSE_BUTTON_WHEEL_UP: set_slider(1., true) else: grabbed_handle = 0 if p_event is InputEventMouseMotion: if grabbed_handle != 0: set_slider(p_event.get_position().x) func set_slider(p_xpos: float, p_relative: bool = false) -> void: if grabbed_handle == 0: return var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value) if(grabbed_handle < 0): if p_relative: range.x += p_xpos else: range.x = xpos_step else: if p_relative: range.y += p_xpos else: range.y = xpos_step set_value(range) func _notification(p_what: int) -> void: if p_what == NOTIFICATION_DRAW: # Draw background bar var bg: StyleBox = get_theme_stylebox("slider", "HSlider") var bg_height: float = bg.get_minimum_size().y var mid_y: float = (size.y - bg_height) / 2.0 draw_style_box(bg, Rect2(Vector2(0, mid_y), Vector2(size.x, bg_height))) # Draw foreground bar var handle: Texture2D = get_theme_icon("grabber", "HSlider") var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider") var startx: float = (range.x / max_value) * size.x var endx: float = (range.y / max_value) * size.x draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height))) # Draw handles, slightly in so they don't get on the outside edges var handle_pos: Vector2 handle_pos.x = clamp(startx - handle.get_size().x/2, -10, size.x) handle_pos.y = clamp(endx - handle.get_size().x/2, 0, size.x - 10) draw_texture(handle, Vector2(handle_pos.x, -mid_y - 10 * (display_scale - 1.))) draw_texture(handle, Vector2(handle_pos.y, -mid_y - 10 * (display_scale - 1.))) update_label() ================================================ FILE: project/addons/terrain_3d/src/double_slider.gd.uid ================================================ uid://stro0p1oawfb ================================================ FILE: project/addons/terrain_3d/src/editor_plugin.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Editor Plugin for Terrain3D @tool extends EditorPlugin # Includes const Terrain3DUI: Script = preload("res://addons/terrain_3d/src/ui.gd") const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn" # Editor Plugin var debug: int = 0 # Set in _edit() var editor: Terrain3DEditor var editor_settings: EditorSettings var ui: Node # Terrain3DUI see Godot #75388 var asset_dock: PanelContainer var current_region_position: Vector2 var mouse_global_position: Vector3 = Vector3.ZERO var godot_editor_window: Window # The Godot Editor window var viewport: SubViewport # Viewport the mouse was last in var mouse_in_main: bool = false # Helper to track when mouse is in the editor vp # Terrain var terrain: Terrain3D var _last_terrain: Terrain3D var nav_region: NavigationRegion3D # Input var modifier_ctrl: bool var modifier_alt: bool var modifier_shift: bool var _last_modifiers: int = 0 var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating var rmb_release_time: int = 0 var _use_meta: bool = false func _init() -> void: if debug: print("Terrain3DEditorPlugin: _init") if OS.get_name() == "macOS": _use_meta = true # Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent() godot_editor_window.focus_entered.connect(_on_godot_focus_entered) EditorInterface.get_inspector().mouse_entered.connect(func(): mouse_in_main = false) func _enter_tree() -> void: if debug: print("Terrain3DEditorPlugin: _enter_tree") editor = Terrain3DEditor.new() setup_editor_settings() ui = Terrain3DUI.new() ui.plugin = self add_child(ui) scene_changed.connect(_on_scene_changed) asset_dock = load(ASSET_DOCK).instantiate() asset_dock.initialize(self) func _exit_tree() -> void: if debug: print("Terrain3DEditorPlugin: _exit_tree") asset_dock.remove_dock(true) asset_dock.queue_free() ui.queue_free() editor.free() scene_changed.disconnect(_on_scene_changed) godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered) func _on_godot_focus_entered() -> void: if debug > 1: print("Terrain3DEditorPlugin: _on_godot_focus_entered") _read_input() ## EditorPlugin selection function call chain isn't consistent. Here's the map of calls: ## Assume we handle Terrain3D and NavigationRegion3D # Click Terrain3D: _handles(Terrain3D), _edit(Terrain3D), _make_visible(true) # Deselect: _edit(null), _make_visible(false) # Click other node: _handles(OtherNode) # Click NavRegion3D: _handles(NavReg3D), _edit(NavReg3D), _make_visible(true) # Click NavRegion3D, Terrain3D: _handles(Terrain3D), _make_visible(true), _edit(Terrain3D) # Click Terrain3D, NavRegion3D: _handles(NavReg3D), _make_visible(true), _edit(NavReg3D) func _handles(p_object: Object) -> bool: if p_object is Terrain3D: return true elif p_object is NavigationRegion3D and is_instance_valid(_last_terrain): return true # Terrain3DObjects requires access to EditorUndoRedoManager. The only way to make sure it # always has it, is to pass it in here. _edit is NOT called if the node is cut and pasted. elif p_object is Terrain3DObjects: p_object.editor_setup(self) elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects: p_object.get_parent().editor_setup(self) return false func _edit(p_object: Object) -> void: if !p_object: _clear() if p_object is Terrain3D: if p_object == terrain: return terrain = p_object _last_terrain = terrain terrain.set_plugin(self) terrain.set_editor(editor) debug = terrain.debug_level editor.set_terrain(terrain) terrain.set_meta("_edit_lock_", true) ui.set_visible(true) # Get alerted when a new asset list is loaded if not terrain.assets_changed.is_connected(asset_dock.update_assets): terrain.assets_changed.connect(asset_dock.update_assets) asset_dock.update_assets() else: _clear() if is_terrain_valid(_last_terrain): if p_object is NavigationRegion3D: ui.set_visible(true, true) nav_region = p_object else: nav_region = null func _make_visible(p_visible: bool, p_redraw: bool = false) -> void: if debug: print("Terrain3DEditorPlugin: _make_visible(%s, %s)" % [ p_visible, p_redraw ]) if p_visible and is_selected(): ui.set_visible(true) asset_dock.update_dock() else: ui.set_visible(false) func _clear() -> void: if is_terrain_valid(): editor.set_tool(Terrain3DEditor.TOOL_MAX) editor.set_operation(Terrain3DEditor.OP_MAX) terrain = null editor.set_terrain(null) ui.clear_picking() func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> AfterGUIInput: mouse_in_main = true if not is_terrain_valid(): return AFTER_GUI_INPUT_PASS var continue_input: AfterGUIInput = _read_input(p_event) if continue_input != AFTER_GUI_INPUT_CUSTOM: return continue_input ## Setup active camera & viewport # Always update this for all inputs, as the mouse position can move without # necessarily being a InputEventMouseMotion object. get_intersection() also # returns the last frame position, and should be updated more frequently. # Snap terrain to current camera terrain.set_camera(p_viewport_camera) # Detect if viewport is set to half_resolution # Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D viewport = p_viewport_camera.get_parent() var full_resolution: bool = false if viewport.get_parent().stretch_shrink == 2 else true ## Get mouse location on terrain # Project 2D mouse position to 3D position and direction var vp_mouse_pos: Vector2 = viewport.get_mouse_position() var mouse_pos: Vector2 = vp_mouse_pos if full_resolution else vp_mouse_pos / 2 var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos) var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos) ui.update_decal() # If region tool, grab mouse position without considering height if editor.get_tool() == Terrain3DEditor.REGION: var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir) mouse_global_position = (camera_pos + t * camera_dir) else: #Else look for intersection with terrain var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir, true) if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan return AFTER_GUI_INPUT_PASS mouse_global_position = intersection_point ## Handle mouse movement if p_event is InputEventMouseMotion: if _input_mode != -1: # Not cam rotation ## Update region highlight var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \ / (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor() if _input_mode > 0 and editor.is_operating(): # Inject pressure - Relies on C++ set_brush_data() using same dictionary instance ui.brush_data["mouse_pressure"] = p_event.pressure editor.operate(mouse_global_position, p_viewport_camera.rotation.y) return AFTER_GUI_INPUT_STOP return AFTER_GUI_INPUT_PASS if p_event is InputEventMouseButton and _input_mode > 0: if p_event.is_pressed(): # If picking if ui.is_picking(): ui.pick(mouse_global_position) if not ui.operation_builder or not ui.operation_builder.is_ready(): return AFTER_GUI_INPUT_STOP if modifier_ctrl and editor.get_tool() == Terrain3DEditor.HEIGHT: var height: float = terrain.data.get_height(mouse_global_position) ui.brush_data["height"] = height ui.tool_settings.set_setting("height", height) # If adjusting regions if editor.get_tool() == Terrain3DEditor.REGION: # Skip regions that already exist or don't var has_region: bool = terrain.data.has_regionp(mouse_global_position) var op: int = editor.get_operation() if ( has_region and op == Terrain3DEditor.ADD) or \ ( not has_region and op == Terrain3DEditor.SUBTRACT ): return AFTER_GUI_INPUT_STOP # If an automatic operation is ready to go (e.g. gradient) if ui.operation_builder and ui.operation_builder.is_ready(): ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y) return AFTER_GUI_INPUT_STOP # Mouse clicked, start editing editor.start_operation(mouse_global_position) editor.operate(mouse_global_position, p_viewport_camera.rotation.y) return AFTER_GUI_INPUT_STOP # _input_apply released, save undo data elif editor.is_operating(): editor.stop_operation() return AFTER_GUI_INPUT_STOP return AFTER_GUI_INPUT_PASS func _read_input(p_event: InputEvent = null) -> AfterGUIInput: ## Determine if user is moving camera or applying if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) or \ p_event is InputEventMouseButton and p_event.is_released() and \ p_event.get_button_index() == MOUSE_BUTTON_LEFT: _input_mode = 1 else: _input_mode = 0 match get_setting("editors/3d/navigation/navigation_scheme", 0): 2, 1: # Modo, Maya if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \ ( Input.is_key_pressed(KEY_ALT) and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) ): _input_mode = -1 if p_event is InputEventMouseButton and p_event.is_released() and \ ( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \ ( Input.is_key_pressed(KEY_ALT) and p_event.get_button_index() == MOUSE_BUTTON_LEFT )): rmb_release_time = Time.get_ticks_msec() 0, _: # Godot if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \ Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE): _input_mode = -1 if p_event is InputEventMouseButton and p_event.is_released() and \ ( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \ p_event.get_button_index() == MOUSE_BUTTON_MIDDLE ): rmb_release_time = Time.get_ticks_msec() if _input_mode < 0: # Camera is moving, skip input return AFTER_GUI_INPUT_PASS ## Determine modifiers pressed modifier_shift = Input.is_key_pressed(KEY_SHIFT) # Editor responds to modifier_ctrl so we must register touchscreen Invert if _use_meta: modifier_ctrl = Input.is_key_pressed(KEY_META) || ui.inverted_input else: modifier_ctrl = Input.is_key_pressed(KEY_CTRL) || ui.inverted_input # Keybind enum: Alt,Space,Meta,Capslock var alt_key: int match get_setting("terrain3d/config/alt_key_bind", 0): 3: alt_key = KEY_CAPSLOCK 2: alt_key = KEY_META 1: alt_key = KEY_SPACE 0, _: alt_key = KEY_ALT modifier_alt = Input.is_key_pressed(alt_key) var current_mods: int = int(modifier_shift) | int(modifier_ctrl) << 1 | int(modifier_alt) << 2 ## Process Hotkeys if p_event is InputEventKey and \ current_mods == 0 and \ p_event.is_pressed() and \ not p_event.is_echo() and \ consume_hotkey(p_event.keycode): # Hotkey found, consume event, and stop input processing EditorInterface.get_editor_viewport_3d().set_input_as_handled() return AFTER_GUI_INPUT_STOP # Brush data is cleared on set_tool, or clicking textures in the asset dock # Update modifiers if changed or missing if _last_modifiers != current_mods or not ui.brush_data.has("modifier_shift"): _last_modifiers = current_mods ui.brush_data["modifier_shift"] = modifier_shift ui.brush_data["modifier_ctrl"] = modifier_ctrl ui.brush_data["modifier_alt"] = modifier_alt ui.set_active_operation() ## Continue processing input return AFTER_GUI_INPUT_CUSTOM # Returns true if hotkey matches and operation triggered func consume_hotkey(keycode: int) -> bool: match keycode: KEY_1, KEY_KP_1: terrain.material.set_show_region_grid(!terrain.material.get_show_region_grid()) KEY_2, KEY_KP_2: terrain.label_distance = 4096.0 if is_zero_approx(terrain.label_distance) else 0.0 KEY_3, KEY_KP_3: terrain.material.set_show_contours(!terrain.material.get_show_contours()) KEY_4, KEY_KP_4: terrain.material.set_show_instancer_grid(!terrain.material.get_show_instancer_grid()) KEY_5, KEY_KP_5: terrain.material.set_show_vertex_grid(!terrain.material.get_show_vertex_grid()) KEY_E: ui.toolbar.get_button("AddRegion").set_pressed(true) KEY_R: ui.toolbar.get_button("Raise").set_pressed(true) KEY_H: ui.toolbar.get_button("Height").set_pressed(true) KEY_S: ui.toolbar.get_button("Slope").set_pressed(true) KEY_C: ui.toolbar.get_button("PaintColor").set_pressed(true) KEY_N: ui.toolbar.get_button("PaintNavigableArea").set_pressed(true) KEY_I: ui.toolbar.get_button("InstanceMeshes").set_pressed(true) KEY_X: ui.toolbar.get_button("AddHoles").set_pressed(true) KEY_W: ui.toolbar.get_button("PaintWetness").set_pressed(true) KEY_B: ui.toolbar.get_button("PaintTexture").set_pressed(true) KEY_V: ui.toolbar.get_button("SprayTexture").set_pressed(true) KEY_A: ui.toolbar.get_button("PaintAutoshader").set_pressed(true) KEY_T: ui.tool_settings.inverse_slope_range() _: return false return true func _on_scene_changed(scene_root: Node) -> void: if debug: print("Terrain3DEditorPlugin: _on_scene_changed: ", scene_root) if not scene_root: return for node in scene_root.find_children("", "Terrain3DObjects"): node.editor_setup(self) asset_dock.update_assets() func get_terrain() -> Terrain3D: if is_terrain_valid(): return terrain elif is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain): return _last_terrain else: return null func is_terrain_valid(p_terrain: Terrain3D = null) -> bool: var t: Terrain3D if p_terrain: t = p_terrain else: t = terrain if is_instance_valid(t) and t.is_inside_tree() and t.data: return true return false func is_selected() -> bool: var selected: Array[Node] = EditorInterface.get_selection().get_selected_nodes() for node in selected: if ( is_instance_valid(_last_terrain) and node.get_instance_id() == _last_terrain.get_instance_id() ) or \ node is Terrain3D: return true return false func select_terrain() -> void: if debug and is_selected(): print("Terrain3DEditorPlugin: Terrain is selected, skipping") if is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain) and not is_selected(): var es: EditorSelection = EditorInterface.get_selection() if debug: print("Terrain3DEditorPlugin: Clearing and reselecting terrain") es.clear() es.add_node(_last_terrain) ## Editor Settings func setup_editor_settings() -> void: editor_settings = EditorInterface.get_editor_settings() if not editor_settings.has_setting("terrain3d/config/alt_key_bind"): editor_settings.set("terrain3d/config/alt_key_bind", 0) var property_info = { "name": "terrain3d/config/alt_key_bind", "type": TYPE_INT, "hint": PROPERTY_HINT_ENUM, "hint_string": "Alt,Space,Meta,Capslock" } editor_settings.add_property_info(property_info) func set_setting(p_str: String, p_value: Variant) -> void: editor_settings.set_setting(p_str, p_value) func get_setting(p_str: String, p_default: Variant) -> Variant: if editor_settings.has_setting(p_str): return editor_settings.get_setting(p_str) else: return p_default func has_setting(p_str: String) -> bool: return editor_settings.has_setting(p_str) func erase_setting(p_str: String) -> void: editor_settings.erase(p_str) ## Undo / Redo Functions func create_undo_action(p_action_name: String) -> void: get_undo_redo().create_action(p_action_name, UndoRedo.MERGE_DISABLE, terrain) func add_undo_method(p_method: Callable) -> void: var args := [ p_method.get_object(), p_method.get_method() ] args.append_array(p_method.get_bound_arguments()) get_undo_redo().add_undo_method.callv(args) func add_do_method(p_method: Callable) -> void: var args := [ p_method.get_object(), p_method.get_method() ] args.append_array(p_method.get_bound_arguments()) get_undo_redo().add_do_method.callv(args) func commit_action(p_execute: bool) -> void: get_undo_redo().commit_action(p_execute) ================================================ FILE: project/addons/terrain_3d/src/editor_plugin.gd.uid ================================================ uid://bsgxo1qywjdf3 ================================================ FILE: project/addons/terrain_3d/src/gradient_operation_builder.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Gradient Operation Builder for Terrain3D extends "res://addons/terrain_3d/src/operation_builder.gd" const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd") func _get_point_picker() -> MultiPicker: return tool_settings.settings["gradient_points"] func _get_brush_size() -> float: return tool_settings.get_setting("size") func _is_drawable() -> bool: return tool_settings.get_setting("drawable") func is_picking() -> bool: return not _get_point_picker().all_points_selected() func pick(p_global_position: Vector3, p_terrain: Terrain3D) -> void: if not _get_point_picker().all_points_selected(): _get_point_picker().add_point(p_global_position) func is_ready() -> bool: return _get_point_picker().all_points_selected() and not _is_drawable() # This function runs a "brush" operation from point1 to point2, when drawable is not checked func apply_operation(p_editor: Terrain3DEditor, p_global_position: Vector3, p_camera_direction: float) -> void: var points: PackedVector3Array = _get_point_picker().get_points() assert(points.size() == 2) assert(not _is_drawable()) var brush_size: float = _get_brush_size() assert(brush_size > 0.0) var start: Vector3 = points[0] var end: Vector3 = points[1] p_editor.start_operation(start) var dir: Vector3 = (end - start).normalized() var pos: Vector3 = start while dir.dot(end - pos) > 0.0: p_editor.operate(pos, p_camera_direction) pos += dir * brush_size * 0.2 p_editor.stop_operation() _get_point_picker().clear() ================================================ FILE: project/addons/terrain_3d/src/gradient_operation_builder.gd.uid ================================================ uid://def7sych6dp8b ================================================ FILE: project/addons/terrain_3d/src/multi_picker.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Multipicker for Terrain3D extends HBoxContainer signal pressed signal value_changed const ICON_PICKER_CHECKED: String = "res://addons/terrain_3d/icons/picker_checked.svg" const MAX_POINTS: int = 2 var icon_picker: Texture2D var icon_picker_checked: Texture2D var points: PackedVector3Array var picking_index: int = -1 func _enter_tree() -> void: icon_picker = get_theme_icon("ColorPick", "EditorIcons") icon_picker_checked = load(ICON_PICKER_CHECKED) points.resize(MAX_POINTS) for i in range(MAX_POINTS): var button := Button.new() button.icon = icon_picker button.tooltip_text = "Pick point on the Terrain" button.set_meta(&"point_index", i) button.pressed.connect(_on_button_pressed.bind(i)) add_child(button) _update_buttons() func _on_button_pressed(button_index: int) -> void: points[button_index] = Vector3.ZERO picking_index = button_index _update_buttons() pressed.emit() func _update_buttons() -> void: for child in get_children(): if child is Button: _update_button(child) func _update_button(button: Button) -> void: var index: int = button.get_meta(&"point_index") if points[index] != Vector3.ZERO: button.icon = icon_picker_checked else: button.icon = icon_picker func clear() -> void: points.fill(Vector3.ZERO) _update_buttons() value_changed.emit() func all_points_selected() -> bool: return points.count(Vector3.ZERO) == 0 func add_point(p_value: Vector3) -> void: if points.has(p_value): return # If manually selecting a point individually if picking_index != -1: points[picking_index] = p_value picking_index = -1 else: # Else picking a sequence of points (non-drawable) for i in range(MAX_POINTS): if points[i] == Vector3.ZERO: points[i] = p_value break _update_buttons() value_changed.emit() func get_points() -> PackedVector3Array: return points ================================================ FILE: project/addons/terrain_3d/src/multi_picker.gd.uid ================================================ uid://dvdtoa32h6xdn ================================================ FILE: project/addons/terrain_3d/src/operation_builder.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Operation Builder for Terrain3D extends RefCounted const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd") var tool_settings: ToolSettings func is_picking() -> bool: return false func pick(p_global_position: Vector3, p_terrain: Terrain3D) -> void: pass func is_ready() -> bool: return false func apply_operation(editor: Terrain3DEditor, p_global_position: Vector3, p_camera_direction: float) -> void: pass ================================================ FILE: project/addons/terrain_3d/src/operation_builder.gd.uid ================================================ uid://bu5cm0eh052rm ================================================ FILE: project/addons/terrain_3d/src/tool_settings.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Tool settings bar for Terrain3D extends PanelContainer signal picking(type: Terrain3DEditor.Tool, callback: Callable) signal setting_changed(setting: Variant) enum Layout { HORIZONTAL, VERTICAL, GRID, } enum SettingType { CHECKBOX, COLOR_SELECT, DOUBLE_SLIDER, OPTION, PICKER, MULTI_PICKER, SLIDER, LABEL, TYPE_MAX, } const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd") const DEFAULT_BRUSH: String = "circle0.exr" const BRUSH_PATH: String = "res://addons/terrain_3d/brushes" const ES_TOOL_SETTINGS: String = "terrain3d/tool_settings/" # Add settings flags const NONE: int = 0x0 const ALLOW_LARGER: int = 0x1 const ALLOW_SMALLER: int = 0x2 const ALLOW_OUT_OF_BOUNDS: int = 0x3 # LARGER|SMALLER const NO_LABEL: int = 0x4 const ADD_SEPARATOR: int = 0x8 # Add a vertical line before this entry const ADD_SPACER: int = 0x10 # Add a space before this entry const NO_SAVE: int = 0x20 # Don't save this in EditorSettings var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors var brush_preview_material: ShaderMaterial var select_brush_button: Button var selected_brush_imgs: Array var main_list: HFlowContainer var advanced_list: VBoxContainer var height_list: VBoxContainer var scale_list: VBoxContainer var rotation_list: VBoxContainer var color_list: VBoxContainer var collision_list: VBoxContainer var settings: Dictionary = {} func _ready() -> void: # Remove old editor settings, newer first so oldest can be removed for setting in ["jitter", "lift_floor", "flatten_peaks", "lift_flatten", "automatic_regions", "show_cursor_while_painting", "crosshair_threshold"]: plugin.erase_setting(ES_TOOL_SETTINGS + setting) # Setup buttons main_list = HFlowContainer.new() add_child(main_list, true) add_brushes(main_list) add_setting({ "name":"instructions", "label":"Click the terrain to add a region. CTRL+Click to remove. Or select another tool on the left.", "type":SettingType.LABEL, "list":main_list, "flags":NO_LABEL|NO_SAVE }) add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":20, "unit":"m", "range":Vector3(0.1, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER }) add_setting({ "name":"strength", "type":SettingType.SLIDER, "list":main_list, "default":33, "unit":"%", "range":Vector3(1, 100, 1), "flags":ALLOW_LARGER }) add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":20, "unit":"m", "range":Vector3(-500, 500, 0.1), "flags":ALLOW_OUT_OF_BOUNDS }) add_setting({ "name":"height_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.HEIGHT, "flags":NO_LABEL, "tooltip":"Pick Height from the terrain." }) add_setting({ "name":"color", "type":SettingType.COLOR_SELECT, "list":main_list, "default":Color.WHITE, "flags":ADD_SEPARATOR }) add_setting({ "name":"color_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.COLOR, "flags":NO_LABEL, "tooltip":"Pick Color from the terrain." }) add_setting({ "name":"roughness", "type":SettingType.SLIDER, "list":main_list, "default":-65, "unit":"%", "range":Vector3(-100, 100, 1), "flags":ADD_SEPARATOR }) add_setting({ "name":"roughness_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.ROUGHNESS, "flags":NO_LABEL, "tooltip":"Pick Wetness from the terrain." }) add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX, "list":main_list, "default":true, "flags":ADD_SEPARATOR }) add_setting({ "name":"texture_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.TEXTURE, "flags":NO_LABEL, "tooltip":"Pick Texture from the terrain." }) add_setting({ "name":"texture_filter", "label":"Texture Filter", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SEPARATOR }) add_setting({ "name":"margin", "type":SettingType.SLIDER, "list":main_list, "default":0, "unit":"", "range":Vector3(-50, 50, 1), "flags":ALLOW_OUT_OF_BOUNDS }) # Slope painting filter add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list, "default":Vector2(0, 90), "unit":"°", "range":Vector3(0, 90, 1), "flags":ADD_SEPARATOR }) add_setting({ "name":"enable_angle", "label":"Angle", "type":SettingType.CHECKBOX, "list":main_list, "default":true, "flags":ADD_SEPARATOR }) add_setting({ "name":"angle", "type":SettingType.SLIDER, "list":main_list, "default":0, "unit":"%", "range":Vector3(0, 337.5, 22.5), "flags":NO_LABEL }) add_setting({ "name":"angle_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.ANGLE, "flags":NO_LABEL, "tooltip":"Pick Angle from the terrain." }) add_setting({ "name":"dynamic_angle", "label":"Dynamic", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SPACER }) add_setting({ "name":"enable_scale", "label":"Scale", "type":SettingType.CHECKBOX, "list":main_list, "default":true, "flags":ADD_SEPARATOR }) add_setting({ "name":"scale", "label":"±", "type":SettingType.SLIDER, "list":main_list, "default":0, "unit":"%", "range":Vector3(-60, 80, 20), "flags":NO_LABEL }) add_setting({ "name":"scale_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.SCALE, "flags":NO_LABEL, "tooltip":"Pick Scale from the terrain." }) ## Slope sculpting brush add_setting({ "name":"gradient_points", "type":SettingType.MULTI_PICKER, "label":"Points", "list":main_list, "default":Terrain3DEditor.SCULPT, "flags":ADD_SEPARATOR }) add_setting({ "name":"drawable", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SEPARATOR }) settings["drawable"].toggled.connect(_on_drawable_toggled) ## Instancer add_setting({ "name":"mesh_picker", "type":SettingType.PICKER, "list":main_list, "default":Terrain3DEditor.INSTANCER, "flags":NO_LABEL|ADD_SEPARATOR, "tooltip":"Pick a Mesh asset from the terrain, within an instancer cell. (See overlays.)" }) height_list = create_submenu(main_list, "Height", Layout.VERTICAL) add_setting({ "name":"height_offset", "type":SettingType.SLIDER, "list":height_list, "default":0, "unit":"m", "range":Vector3(-10, 10, 0.05), "flags":ALLOW_OUT_OF_BOUNDS }) add_setting({ "name":"random_height", "label":"Random Height ±", "type":SettingType.SLIDER, "list":height_list, "default":0, "unit":"m", "range":Vector3(0, 10, 0.05), "flags":ALLOW_OUT_OF_BOUNDS }) scale_list = create_submenu(main_list, "Scale", Layout.VERTICAL) add_setting({ "name":"fixed_scale", "type":SettingType.SLIDER, "list":scale_list, "default":100, "unit":"%", "range":Vector3(1, 1000, 1), "flags":ALLOW_OUT_OF_BOUNDS }) add_setting({ "name":"random_scale", "label":"Random Scale ±", "type":SettingType.SLIDER, "list":scale_list, "default":20, "unit":"%", "range":Vector3(0, 99, 1), "flags":ALLOW_OUT_OF_BOUNDS }) rotation_list = create_submenu(main_list, "Rotation", Layout.VERTICAL) add_setting({ "name":"fixed_spin", "label":"Fixed Spin (Around Y)", "type":SettingType.SLIDER, "list":rotation_list, "default":0, "unit":"°", "range":Vector3(0, 360, 1) }) add_setting({ "name":"random_spin", "type":SettingType.SLIDER, "list":rotation_list, "default":360, "unit":"°", "range":Vector3(0, 360, 1) }) add_setting({ "name":"fixed_tilt", "label":"Fixed Tilt", "type":SettingType.SLIDER, "list":rotation_list, "default":0, "unit":"°", "range":Vector3(-85, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS }) add_setting({ "name":"random_tilt", "label":"Random Tilt ±", "type":SettingType.SLIDER, "list":rotation_list, "default":10, "unit":"°", "range":Vector3(0, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS }) add_setting({ "name":"align_to_normal", "type":SettingType.CHECKBOX, "list":rotation_list, "default":false }) color_list = create_submenu(main_list, "Color", Layout.VERTICAL) add_setting({ "name":"vertex_color", "type":SettingType.COLOR_SELECT, "list":color_list, "default":Color.WHITE }) add_setting({ "name":"random_hue", "label":"Random Hue Shift ±", "type":SettingType.SLIDER, "list":color_list, "default":0, "unit":"°", "range":Vector3(0, 360, 1) }) add_setting({ "name":"random_darken", "type":SettingType.SLIDER, "list":color_list, "default":50, "unit":"%", "range":Vector3(0, 100, 1) }) collision_list = create_submenu(main_list, "Collision", Layout.VERTICAL) add_setting({ "name":"on_collision", "label":"On Collision", "type":SettingType.CHECKBOX, "list":collision_list, "default":true }) add_setting({ "name":"raycast_height", "label":"Raycast Height", "type":SettingType.SLIDER, "list":collision_list, "default":10, "unit":"m", "range":Vector3(0, 200, .25) }) if DisplayServer.is_touchscreen_available(): add_setting({ "name":"invert", "label":"Invert", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SEPARATOR }) var spacer: Control = Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL main_list.add_child(spacer, true) ## Advanced Settings Menu advanced_list = create_submenu(main_list, "", Layout.VERTICAL, false) add_setting({ "name":"auto_regions", "label":"Add regions while sculpting", "type":SettingType.CHECKBOX, "list":advanced_list, "default":true }) advanced_list.add_child(HSeparator.new(), true) add_setting({ "name":"show_brush_texture", "type":SettingType.CHECKBOX, "list":advanced_list, "default":true }) add_setting({ "name":"align_to_view", "type":SettingType.CHECKBOX, "list":advanced_list, "default":true }) add_setting({ "name":"brush_spin_speed", "type":SettingType.SLIDER, "list":advanced_list, "default":50, "unit":"%", "range":Vector3(0, 100, 1) }) add_setting({ "name":"gamma", "type":SettingType.SLIDER, "list":advanced_list, "default":1.0, "unit":"γ", "range":Vector3(0.1, 2.0, 0.01) }) func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout, p_hover_pop: bool = true) -> Container: var menu_button: Button = Button.new() if p_button_name.is_empty(): menu_button.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons") else: menu_button.set_text(p_button_name) menu_button.set_toggle_mode(true) menu_button.set_v_size_flags(SIZE_SHRINK_CENTER) menu_button.toggled.connect(_on_show_submenu.bind(menu_button)) var submenu: PopupPanel = PopupPanel.new() submenu.popup_hide.connect(menu_button.set_pressed.bind(false)) var panel_style: StyleBox = get_theme_stylebox("panel", "PopupMenu").duplicate() panel_style.set_content_margin_all(10) submenu.set("theme_override_styles/panel", panel_style) submenu.add_to_group("terrain3d_submenus") # Pop up menu on hover, hide on exit if p_hover_pop: menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button)) submenu.mouse_entered.connect(func(): submenu.set_meta("mouse_entered", true)) submenu.mouse_exited.connect(func(): # On mouse_exit, hide popup unless LineEdit focused var focused_element: Control = submenu.gui_get_focus_owner() if not focused_element is LineEdit: _on_show_submenu(false, menu_button) submenu.set_meta("mouse_entered", false) return focused_element.focus_exited.connect(func(): # Close submenu once lineedit loses focus if not submenu.get_meta("mouse_entered"): _on_show_submenu(false, menu_button) submenu.set_meta("mouse_entered", false) ) ) var sublist: Container match(p_layout): Layout.GRID: sublist = GridContainer.new() Layout.VERTICAL: sublist = VBoxContainer.new() Layout.HORIZONTAL, _: sublist = HBoxContainer.new() p_parent.add_child(menu_button, true) menu_button.add_child(submenu, true) submenu.add_child(sublist, true) return sublist func _on_show_submenu(p_toggled: bool, p_button: Button) -> void: # Don't show if mouse already down (from painting) if p_toggled and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): return # Hide menu if mouse is not in button or panel var button_rect: Rect2 = Rect2(p_button.get_screen_transform().origin, p_button.get_global_rect().size) var in_button: bool = button_rect.has_point(DisplayServer.mouse_get_position()) var popup: PopupPanel = p_button.get_child(0) var popup_rect: Rect2 = Rect2(popup.position, popup.size) var in_popup: bool = popup_rect.has_point(DisplayServer.mouse_get_position()) if not p_toggled and ( in_button or in_popup ): return # Hide all submenus before possibly enabling the current one get_tree().call_group("terrain3d_submenus", "set_visible", false) popup.set_visible(p_toggled) var popup_pos: Vector2 = p_button.get_screen_transform().origin popup_pos.y -= popup.size.y if popup.get_child_count()>0 and popup.get_child(0) == advanced_list: popup_pos.x -= popup.size.x - p_button.size.x popup.set_position(popup_pos) func add_brushes(p_parent: Control) -> void: var brush_list: GridContainer = create_submenu(p_parent, "Brush", Layout.GRID) brush_list.name = "BrushList" var brush_button_group: ButtonGroup = ButtonGroup.new() brush_button_group.pressed.connect(_on_setting_changed) var default_brush_btn: Button var dir: DirAccess = DirAccess.open(BRUSH_PATH) if dir: dir.list_dir_begin() var file_name = dir.get_next() while file_name != "": if !dir.current_is_dir() and file_name.ends_with(".exr"): var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name) if img: var value_range: Vector2 = Terrain3DUtil.get_min_max(img) if value_range.y - value_range.x < 0.333: push_warning("'%s' has a low value range and may not be visible in the brush gallery or cursor" % file_name) var thumbimg: Image = img.duplicate() img.convert(Image.FORMAT_RF) if thumbimg.get_width() != 100 and thumbimg.get_height() != 100: thumbimg.resize(100, 100, Image.INTERPOLATE_CUBIC) thumbimg = Terrain3DUtil.black_to_alpha(thumbimg) thumbimg.convert(Image.FORMAT_LA8) var thumbtex: ImageTexture = ImageTexture.create_from_image(thumbimg) var brush_btn: Button = Button.new() brush_btn.set_custom_minimum_size(Vector2.ONE * 100) brush_btn.set_button_icon(thumbtex) brush_btn.set_meta("image", img) brush_btn.set_expand_icon(true) brush_btn.set_material(_get_brush_preview_material()) brush_btn.set_toggle_mode(true) brush_btn.set_button_group(brush_button_group) brush_btn.mouse_entered.connect(_on_brush_hover.bind(true, brush_btn)) brush_btn.mouse_exited.connect(_on_brush_hover.bind(false, brush_btn)) brush_list.add_child(brush_btn, true) if file_name == DEFAULT_BRUSH: default_brush_btn = brush_btn var lbl: Label = Label.new() brush_btn.name = file_name.get_basename().to_pascal_case() brush_btn.add_child(lbl, true) lbl.text = brush_btn.name lbl.visible = false lbl.position.y = 70 lbl.add_theme_color_override("font_shadow_color", Color.BLACK) lbl.add_theme_constant_override("shadow_offset_x", 1) lbl.add_theme_constant_override("shadow_offset_y", 1) lbl.add_theme_font_size_override("font_size", 16) file_name = dir.get_next() brush_list.columns = sqrt(brush_list.get_child_count()) + 2 if not default_brush_btn: default_brush_btn = brush_button_group.get_buttons()[0] default_brush_btn.set_pressed(true) _generate_brush_texture(default_brush_btn) settings["brush"] = brush_button_group select_brush_button = brush_list.get_parent().get_parent() # Optionally erase the main brush button text and replace it with the texture select_brush_button.set_text("") select_brush_button.set_button_icon(default_brush_btn.get_button_icon()) select_brush_button.set_custom_minimum_size(Vector2.ONE * 36) select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER) select_brush_button.set_expand_icon(true) func _on_brush_hover(p_hovering: bool, p_button: Button) -> void: if p_button.get_child_count() > 0: var child = p_button.get_child(0) if child is Label: if p_hovering: child.visible = true else: child.visible = false func _on_pick(p_type: Terrain3DEditor.Tool) -> void: if plugin.debug: print("Terrain3DToolSettings: _on_pick: emitting picking: ", p_type, ", ", _on_picked) emit_signal("picking", p_type, _on_picked) func _on_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3) -> void: match p_type: Terrain3DEditor.HEIGHT: settings["height"].value = p_color.r if not is_nan(p_color.r) else 0. Terrain3DEditor.COLOR: settings["color"].color = p_color if not is_nan(p_color.r) else Color.WHITE Terrain3DEditor.ROUGHNESS: # This converts 0,1 to -100,100 # It also quantizes explicitly so picked values matches painted values settings["roughness"].value = round(200. * float(int(p_color.a * 255.) / 255. - .5)) if not is_nan(p_color.r) else 0. Terrain3DEditor.ANGLE: settings["angle"].value = p_color.r Terrain3DEditor.SCALE: settings["scale"].value = p_color.r Terrain3DEditor.INSTANCER: if p_color.r < 0: return plugin.asset_dock.set_selected_by_asset_id(p_color.r) Terrain3DEditor.TEXTURE: if p_color.r < 0: return plugin.asset_dock.set_selected_by_asset_id(p_color.r) _on_setting_changed() func _on_point_pick(p_type: Terrain3DEditor.Tool, p_name: String) -> void: assert(p_type == Terrain3DEditor.SCULPT) if plugin.debug: print("Terrain3DToolSettings: _on_pick: emitting picking: ", p_type, ", ", _on_point_picked) emit_signal("picking", p_type, _on_point_picked.bind(p_name)) func _on_point_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3, p_name: String) -> void: assert(p_type == Terrain3DEditor.SCULPT) var point: Vector3 = p_global_position point.y = p_color.r settings[p_name].add_point(point) _on_setting_changed() func add_setting(p_args: Dictionary) -> void: var p_name: StringName = p_args.get("name", "") var p_label: String = p_args.get("label", "") # Optional replacement for name var p_type: SettingType = p_args.get("type", SettingType.TYPE_MAX) var p_list: Control = p_args.get("list") var p_default: Variant = p_args.get("default") var p_suffix: String = p_args.get("unit", "") var p_range: Vector3 = p_args.get("range", Vector3(0, 0, 1)) var p_minimum: float = p_range.x var p_maximum: float = p_range.y var p_step: float = p_range.z var p_flags: int = p_args.get("flags", NONE) var p_tooltip: String = p_args.get("tooltip", "") if p_name.is_empty() or p_type == SettingType.TYPE_MAX: return var container: HBoxContainer = HBoxContainer.new() container.custom_minimum_size.y = 36 container.set_v_size_flags(SIZE_EXPAND_FILL) var control: Control # Houses the setting to be saved var pending_children: Array[Control] match p_type: SettingType.LABEL: var label := Label.new() label.set_text(p_label) pending_children.push_back(label) control = label SettingType.CHECKBOX: var checkbox := CheckBox.new() if !(p_flags & NO_SAVE): checkbox.set_pressed_no_signal(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default)) checkbox.toggled.connect( ( func(value, path): plugin.set_setting(path, value) ).bind(ES_TOOL_SETTINGS + p_name) ) else: checkbox.set_pressed_no_signal(p_default) checkbox.pressed.connect(_on_setting_changed) pending_children.push_back(checkbox) control = checkbox SettingType.COLOR_SELECT: var picker := ColorPickerButton.new() picker.set_custom_minimum_size(Vector2(100, 25)) picker.edit_alpha = false picker.get_picker().set_color_mode(ColorPicker.MODE_HSV) if !(p_flags & NO_SAVE): picker.set_pick_color(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default)) picker.color_changed.connect( ( func(value, path): plugin.set_setting(path, value) ).bind(ES_TOOL_SETTINGS + p_name) ) else: picker.set_pick_color(p_default) picker.color_changed.connect(_on_setting_changed) pending_children.push_back(picker) control = picker SettingType.PICKER: var button := Button.new() button.set_v_size_flags(SIZE_SHRINK_CENTER) button.icon = get_theme_icon("ColorPick", "EditorIcons") button.pressed.connect(_on_pick.bind(p_default)) pending_children.push_back(button) control = button SettingType.MULTI_PICKER: var multi_picker: HBoxContainer = MultiPicker.new() multi_picker.pressed.connect(_on_point_pick.bind(p_default, p_name)) multi_picker.value_changed.connect(_on_setting_changed) pending_children.push_back(multi_picker) control = multi_picker SettingType.OPTION: var option := OptionButton.new() for i in int(p_maximum): option.add_item("a", i) option.selected = p_minimum option.item_selected.connect(_on_setting_changed) pending_children.push_back(option) control = option SettingType.SLIDER, SettingType.DOUBLE_SLIDER: var slider: Control if p_type == SettingType.SLIDER: # Create an editable value box var spin_slider := EditorSpinSlider.new() spin_slider.set_flat(false) spin_slider.set_hide_slider(true) spin_slider.value_changed.connect(_on_setting_changed) spin_slider.set_max(p_maximum) spin_slider.set_min(p_minimum) spin_slider.set_step(p_step) spin_slider.set_suffix(p_suffix) spin_slider.set_v_size_flags(SIZE_SHRINK_CENTER) spin_slider.set_custom_minimum_size(Vector2(65, 0)) # Create horizontal slider linked to the above box slider = HSlider.new() slider.share(spin_slider) if p_flags & ALLOW_LARGER: slider.set_allow_greater(true) if p_flags & ALLOW_SMALLER: slider.set_allow_lesser(true) pending_children.push_back(slider) pending_children.push_back(spin_slider) control = spin_slider else: # DOUBLE_SLIDER var label := Label.new() label.set_custom_minimum_size(Vector2(60, 0)) label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER) slider = DoubleSlider.new() slider.label = label slider.suffix = p_suffix slider.value_changed.connect(_on_setting_changed) pending_children.push_back(slider) pending_children.push_back(label) control = slider slider.set_min(p_minimum) slider.set_max(p_maximum) slider.set_step(p_step) slider.set_value(p_default) slider.set_v_size_flags(SIZE_SHRINK_CENTER) slider.set_custom_minimum_size(Vector2(50, 10)) if !(p_flags & NO_SAVE): slider.set_value(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default)) slider.value_changed.connect( ( func(value, path): plugin.set_setting(path, value) ).bind(ES_TOOL_SETTINGS + p_name) ) else: slider.set_value(p_default) control.name = p_name.to_pascal_case() if not p_tooltip.is_empty(): control.tooltip_text = p_tooltip settings[p_name] = control # Setup button labels if not (p_flags & NO_LABEL): # Labels are actually buttons styled to look like labels var label := Button.new() label.set("theme_override_styles/normal", get_theme_stylebox("normal", "Label")) label.set("theme_override_styles/hover", get_theme_stylebox("normal", "Label")) label.set("theme_override_styles/pressed", get_theme_stylebox("normal", "Label")) label.set("theme_override_styles/focus", get_theme_stylebox("normal", "Label")) label.pressed.connect(_on_label_pressed.bind(p_name, p_default)) if p_label.is_empty(): label.set_text(p_name.capitalize() + ": ") else: label.set_text(p_label.capitalize() + ": ") pending_children.push_front(label) # Add separators to front if p_flags & ADD_SEPARATOR: pending_children.push_front(VSeparator.new()) if p_flags & ADD_SPACER: var spacer := Control.new() spacer.set_custom_minimum_size(Vector2(5, 0)) pending_children.push_front(spacer) # Add all children to container and list for child in pending_children: container.add_child(child, true) p_list.add_child(container, true) # If label button is pressed, reset value to default or toggle checkbox func _on_label_pressed(p_name: String, p_default: Variant) -> void: var control: Control = settings.get(p_name) if not control: return if control is CheckBox: set_setting(p_name, !control.button_pressed) elif p_default != null: set_setting(p_name, p_default) func get_settings() -> Dictionary: var dict: Dictionary for key in settings.keys(): dict[key] = get_setting(key) return dict func get_setting(p_setting: String) -> Variant: var object: Object = settings.get(p_setting) var value: Variant if object is Range: value = object.get_value() # Adjust widths of all sliders on update of values var width: float = clamp( (1 + _count_digits(value)) * 19., 50, 80) * clamp(EditorInterface.get_editor_scale(), .9, 2) object.set_custom_minimum_size(Vector2(width, 0)) elif object is DoubleSlider: value = object.get_value() elif object is ButtonGroup: # "brush" value = selected_brush_imgs elif object is CheckBox: value = object.is_pressed() elif object is ColorPickerButton: value = object.color elif object is MultiPicker: value = object.get_points() if value == null: value = 0 return value func set_setting(p_setting: String, p_value: Variant) -> void: var object: Object = settings.get(p_setting) if object is DoubleSlider: # Expects p_value is Vector2 object.set_value(p_value) elif object is Range: object.set_value(p_value) elif object is ButtonGroup: # Expects p_value is Array [ "button name", boolean ] if p_value is Array and p_value.size() == 2: for button in object.get_buttons(): if button.name == p_value[0]: button.button_pressed = p_value[1] elif object is CheckBox: object.button_pressed = p_value elif object is ColorPickerButton: object.color = p_value plugin.set_setting(ES_TOOL_SETTINGS + p_setting, p_value) # Signal doesn't fire on CPB elif object is MultiPicker: # Expects p_value is PackedVector3Array object.points = p_value _on_setting_changed(object) func show_settings(p_settings: PackedStringArray) -> void: for setting in settings.keys(): var object: Object = settings[setting] if object is Control: if setting in p_settings: object.get_parent().show() else: object.get_parent().hide() if select_brush_button: if not "brush" in p_settings: select_brush_button.hide() else: select_brush_button.show() func _on_setting_changed(p_setting: Variant = null) -> void: # If a brush was selected if p_setting is Button and p_setting.get_parent().name == "BrushList": _generate_brush_texture(p_setting) # Optionally Set selected brush texture in main brush button if select_brush_button: select_brush_button.set_button_icon(p_setting.get_button_icon()) # Hide popup p_setting.get_parent().get_parent().set_visible(false) # Hide label if p_setting.get_child_count() > 0: p_setting.get_child(0).visible = false if plugin.debug: print("Terrain3DToolSettings: _on_setting_changed: emitting setting_changed: ", p_setting) emit_signal("setting_changed", p_setting) func _generate_brush_texture(p_btn: Button) -> void: if p_btn is Button: var img: Image = p_btn.get_meta("image") if img.get_width() < 1024 and img.get_height() < 1024: img = img.duplicate() img.resize(1024, 1024, Image.INTERPOLATE_CUBIC) var tex: ImageTexture = ImageTexture.create_from_image(img) selected_brush_imgs = [ img, tex ] func _on_drawable_toggled(p_button_pressed: bool) -> void: if not p_button_pressed: settings["gradient_points"].clear() func _get_brush_preview_material() -> ShaderMaterial: if !brush_preview_material: brush_preview_material = ShaderMaterial.new() var shader: Shader = Shader.new() var code: String = "shader_type canvas_item;\n" code += "varying vec4 v_vertex_color;\n" code += "void vertex() {\n" code += " v_vertex_color = COLOR;\n" code += "}\n" code += "void fragment(){\n" code += " vec4 tex = texture(TEXTURE, UV);\n" code += " COLOR.a *= pow(tex.r, 0.666);\n" code += " COLOR.rgb = v_vertex_color.rgb;\n" code += "}\n" shader.set_code(code) brush_preview_material.set_shader(shader) return brush_preview_material # Counts digits of a number including negative sign, decimal points, and up to 3 decimals func _count_digits(p_value: float) -> int: var count: int = 1 for i in range(5, 0, -1): if abs(p_value) >= pow(10, i): count = i+1 break if p_value - floor(p_value) >= .1: count += 1 # For the decimal if p_value*10 - floor(p_value*10.) >= .1: count += 1 if p_value*100 - floor(p_value*100.) >= .1: count += 1 if p_value*1000 - floor(p_value*1000.) >= .1: count += 1 # Negative sign if p_value < 0: count += 1 return count func inverse_slope_range() -> void: var slope_range: Vector2 = get_setting("slope") if slope_range.y - slope_range.x > 89.99: return if slope_range.x == 0.0: slope_range = Vector2(slope_range.y, 90.0) elif slope_range.y == 90.0: slope_range = Vector2(0.0, slope_range.x) else: # If midpoint <= 45, inverse to 90, else to 0 var midpoint: float = 0.5 * (slope_range.x + slope_range.y) if midpoint <= 45.0: slope_range = Vector2(slope_range.y, 90.0) else: slope_range = Vector2(0.0, slope_range.x) set_setting("slope", slope_range) ================================================ FILE: project/addons/terrain_3d/src/tool_settings.gd.uid ================================================ uid://ciskaaennrffu ================================================ FILE: project/addons/terrain_3d/src/toolbar.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Toolbar for Terrain3D extends VFlowContainer signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) const ICON_REGION_ADD: String = "res://addons/terrain_3d/icons/region_add.svg" const ICON_REGION_REMOVE: String = "res://addons/terrain_3d/icons/region_remove.svg" const ICON_HEIGHT_ADD: String = "res://addons/terrain_3d/icons/height_add.svg" const ICON_HEIGHT_SUB: String = "res://addons/terrain_3d/icons/height_sub.svg" const ICON_HEIGHT_FLAT: String = "res://addons/terrain_3d/icons/height_flat.svg" const ICON_HEIGHT_SLOPE: String = "res://addons/terrain_3d/icons/height_slope.svg" const ICON_HEIGHT_SMOOTH: String = "res://addons/terrain_3d/icons/height_smooth.svg" const ICON_PAINT_TEXTURE: String = "res://addons/terrain_3d/icons/texture_paint.svg" const ICON_SPRAY_TEXTURE: String = "res://addons/terrain_3d/icons/texture_spray.svg" const ICON_COLOR: String = "res://addons/terrain_3d/icons/color_paint.svg" const ICON_WETNESS: String = "res://addons/terrain_3d/icons/wetness.svg" const ICON_AUTOSHADER: String = "res://addons/terrain_3d/icons/autoshader.svg" const ICON_HOLES: String = "res://addons/terrain_3d/icons/holes.svg" const ICON_NAVIGATION: String = "res://addons/terrain_3d/icons/navigation.svg" const ICON_INSTANCER: String = "res://addons/terrain_3d/icons/multimesh.svg" var add_tool_group: ButtonGroup = ButtonGroup.new() var sub_tool_group: ButtonGroup = ButtonGroup.new() var buttons: Dictionary var plugin: EditorPlugin func _init() -> void: set_custom_minimum_size(Vector2(20, 0)) func _ready() -> void: add_tool_group.pressed.connect(_on_tool_selected) sub_tool_group.pressed.connect(_on_tool_selected) add_tool_button({ "tool":Terrain3DEditor.REGION, "add_text":"Add Region (E)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD, "sub_text":"Remove Region", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_REGION_REMOVE }) add_child(HSeparator.new()) add_tool_button({ "tool":Terrain3DEditor.SCULPT, "add_text":"Raise (R)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_ADD, "sub_text":"Lower (R)", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_SUB }) add_tool_button({ "tool":Terrain3DEditor.SCULPT, "add_text":"Smooth (Shift)", "add_op":Terrain3DEditor.AVERAGE, "add_icon":ICON_HEIGHT_SMOOTH }) add_tool_button({ "tool":Terrain3DEditor.HEIGHT, "add_text":"Height (H)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_FLAT, "sub_text":"Height (H)", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_FLAT }) add_tool_button({ "tool":Terrain3DEditor.SCULPT, "add_text":"Slope (S)", "add_op":Terrain3DEditor.GRADIENT, "add_icon":ICON_HEIGHT_SLOPE }) add_child(HSeparator.new()) add_tool_button({ "tool":Terrain3DEditor.TEXTURE, "add_text":"Paint Texture (B)", "add_op":Terrain3DEditor.REPLACE, "add_icon":ICON_PAINT_TEXTURE }) add_tool_button({ "tool":Terrain3DEditor.TEXTURE, "add_text":"Spray Texture (V)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_SPRAY_TEXTURE }) add_tool_button({ "tool":Terrain3DEditor.AUTOSHADER, "add_text":"Paint Autoshader (A)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_AUTOSHADER, "sub_text":"Disable Autoshader (A)", "sub_op":Terrain3DEditor.SUBTRACT }) add_child(HSeparator.new()) add_tool_button({ "tool":Terrain3DEditor.COLOR, "add_text":"Paint Color (C)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_COLOR, "sub_text":"Remove Color (C)", "sub_op":Terrain3DEditor.SUBTRACT }) add_tool_button({ "tool":Terrain3DEditor.ROUGHNESS, "add_text":"Paint Wetness (W)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_WETNESS, "sub_text":"Remove Wetness (W)", "sub_op":Terrain3DEditor.SUBTRACT }) add_child(HSeparator.new()) add_tool_button({ "tool":Terrain3DEditor.HOLES, "add_text":"Add Holes (X)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HOLES, "sub_text":"Remove Holes (X)", "sub_op":Terrain3DEditor.SUBTRACT }) add_tool_button({ "tool":Terrain3DEditor.NAVIGATION, "add_text":"Paint Navigable Area (N)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_NAVIGATION, "sub_text":"Remove Navigable Area (N)", "sub_op":Terrain3DEditor.SUBTRACT }) add_tool_button({ "tool":Terrain3DEditor.INSTANCER, "add_text":"Instance Meshes (I)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_INSTANCER, "sub_text":"Remove Meshes (I)", "sub_op":Terrain3DEditor.SUBTRACT }) # Select first button var buttons: Array[BaseButton] = add_tool_group.get_buttons() buttons[0].set_pressed(true) show_add_buttons(true) func add_tool_button(p_params: Dictionary) -> void: # Additive button var button := Button.new() var name_str: String = p_params.get("add_text", "blank").get_slice('(', 0).to_pascal_case() button.set_name(name_str) button.set_meta("Tool", p_params.get("tool", 0)) button.set_meta("Operation", p_params.get("add_op", 0)) button.set_meta("ID", add_tool_group.get_buttons().size() + 1) button.set_tooltip_text(p_params.get("add_text", "blank")) button.set_button_icon(load(p_params.get("add_icon"))) button.set_flat(true) button.set_toggle_mode(true) button.set_h_size_flags(SIZE_SHRINK_END) button.set_button_group(p_params.get("group", add_tool_group)) add_child(button, true) buttons[button.get_name()] = button # Subtractive button var button2: Button if p_params.has("sub_text"): button2 = Button.new() name_str = p_params.get("sub_text", "blank").get_slice('(', 0).to_pascal_case() button2.set_name(name_str) button2.set_meta("Tool", p_params.get("tool", 0)) button2.set_meta("Operation", p_params.get("sub_op", 0)) button2.set_meta("ID", button.get_meta("ID")) button2.set_tooltip_text(p_params.get("sub_text", "blank")) button2.set_button_icon(load(p_params.get("sub_icon", p_params.get("add_icon")))) button2.set_flat(true) button2.set_toggle_mode(true) button2.set_h_size_flags(SIZE_SHRINK_END) else: button2 = button.duplicate() button2.set_button_group(p_params.get("group", sub_tool_group)) add_child(button2, true) buttons[button2.get_name()] = button func get_button(p_name: String) -> Button: return buttons.get(p_name, null) func show_add_buttons(p_enable: bool) -> void: for button in add_tool_group.get_buttons(): button.visible = p_enable for button in sub_tool_group.get_buttons(): button.visible = !p_enable func _on_tool_selected(p_button: BaseButton) -> void: if plugin.debug: print("Terrain3DToolbar: _on_tool_selected: ", p_button) # Select same tool on negative bar var group: ButtonGroup = p_button.get_button_group() var change_group: ButtonGroup = add_tool_group if group == sub_tool_group else sub_tool_group var id: int = p_button.get_meta("ID", -2) for button in change_group.get_buttons(): button.set_pressed_no_signal(button.get_meta("ID", -1) == id) if plugin.debug: print("Terrain3DToolbar: _on_tool_selected: emitting tool_changed, ", p_button.get_meta("Tool", Terrain3DEditor.TOOL_MAX), ", ", p_button.get_meta("Operation", Terrain3DEditor.OP_MAX)) emit_signal("tool_changed", p_button.get_meta("Tool", Terrain3DEditor.TOOL_MAX), p_button.get_meta("Operation", Terrain3DEditor.OP_MAX)) func change_tool(p_name: String) -> void: var btn: Button = get_node_or_null(p_name) if plugin.debug: print("Terrain3DToolbar: change_tool: ", p_name, ", pressed: ", btn and btn.button_pressed) if btn and not btn.button_pressed: await get_tree().process_frame btn.set_pressed(true) ================================================ FILE: project/addons/terrain_3d/src/toolbar.gd.uid ================================================ uid://b1j37u6utjbom ================================================ FILE: project/addons/terrain_3d/src/ui.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # UI for Terrain3D extends Node # Includes const TerrainMenu: Script = preload("res://addons/terrain_3d/menu/terrain_menu.gd") const TerrainToolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd") const TerrainToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd") const OperationBuilder: Script = preload("res://addons/terrain_3d/src/operation_builder.gd") const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd") # Decal colors const COLOR_RAISE := Color(1., 1., 1.) # White const COLOR_LOWER := Color(0.2, 0.2, 0.2) # Dark gray const COLOR_SMOOTH := Color(0.5, 0.0, 0.2) # Dark Red const COLOR_AVERAGE := Color(0.6, 0.1, 0.3) # Neutral purple const COLOR_LIFT := Color(1.0, 0.6, 0.0) # Bright orange const COLOR_FLATTEN := Color(0.0, 0.6, 1.0) # Cyan const COLOR_HEIGHT := Color(0.0, 0.8, 0.8) # Brighter cyan const COLOR_SLOPE := Color(1.0, 1.0, 0.0) # Bright yellow const COLOR_PAINT := Color(0.0, 0.5, 0.0) # Dark green const COLOR_SPRAY := Color(0.4, 0.8, 0.4) # Lighter green const COLOR_UNSPRAY := Color(0.5, 0.2, 0.5) # Neutral purple const COLOR_WET := Color(0.4, 0.6, 1.0) # Light blue const COLOR_DRY := Color(0.6, 0.4, 0.0) # Warm brown const COLOR_AUTOSHADER := Color(0.36, 0.2, 0.09) # Chocolate const COLOR_HOLES := Color(0.1, 0.1, 0.1) # Near-black const COLOR_NAVIGATION := Color(0.5, 0.2, 0.5) # Purple const COLOR_INSTANCE := Color(0.863, 0.08, 0.235) # Crimson const COLOR_UNINSTANCE := Color(0.2, 0.9, 0.6) # Cyan-green const COLOR_PICK := Color.WHITE const OP_NONE: int = 0x0 const OP_POSITIVE_ONLY: int = 0x01 const OP_NEGATIVE_ONLY: int = 0x02 @onready var region_texture := ImageTexture.new() : set(value): var image: Image = Image.create_empty(1, 1, false, Image.FORMAT_R8) image.fill(Color.WHITE) value.create_from_image(image) region_texture = value var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors var toolbar: TerrainToolbar var tool_settings: TerrainToolSettings var terrain_menu: TerrainMenu var setting_has_changed: bool = false var visible: bool = false var picking: int = Terrain3DEditor.TOOL_MAX var picking_callback: Callable var brush_data: Dictionary var operation_builder: OperationBuilder var active_tool: Terrain3DEditor.Tool = Terrain3DEditor.TOOL_MAX var _selected_tool: Terrain3DEditor.Tool = Terrain3DEditor.TOOL_MAX var active_operation: Terrain3DEditor.Operation = Terrain3DEditor.OP_MAX var _selected_operation: Terrain3DEditor.Operation = Terrain3DEditor.OP_MAX var inverted_input: bool = false # 3 Editor decals: 0 = cursor, 1 = slope point1, 2 = slope point2 var mat_rid: RID var editor_brush_texture_rid: RID = RID() var editor_decal_position: Array[Vector2] = [Vector2(), Vector2(), Vector2()] var editor_decal_rotation: Array[float] = [0., 0., 0.] var editor_decal_size: Array[float] = [0., 0., 0.] var editor_decal_color: Array[Color] = [Color(), Color(), Color()] var editor_decal_visible: Array[bool] = [false, false, false] var editor_decal_part: Array[bool] = [true, true] # Decal[0] cursor components: brush, reticle var editor_decal_timer: Timer var editor_decal_fade: float : set(value): editor_decal_fade = value if editor_decal_color.size() > 0: editor_decal_color[0].a = value if is_shader_valid(): RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color) if value < 0.001: var r_map: PackedInt32Array = plugin.terrain.data.get_region_map() RenderingServer.material_set_param(mat_rid, "_region_map", r_map) func _enter_tree() -> void: if plugin.debug: print("Terrain3DUI: _enter_tree()") toolbar = TerrainToolbar.new() toolbar.plugin = plugin toolbar.hide() toolbar.tool_changed.connect(_on_tool_changed) tool_settings = TerrainToolSettings.new() tool_settings.setting_changed.connect(_on_setting_changed) tool_settings.picking.connect(_on_picking) tool_settings.plugin = plugin tool_settings.hide() terrain_menu = TerrainMenu.new() terrain_menu.plugin = plugin terrain_menu.hide() plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar) plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings) plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_menu) _on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD) editor_decal_timer = Timer.new() editor_decal_timer.wait_time = .5 editor_decal_timer.one_shot = true editor_decal_timer.timeout.connect(func(): get_tree().create_tween().tween_property(self, "editor_decal_fade", 0.0, 0.15)) add_child(editor_decal_timer) func _exit_tree() -> void: if plugin.debug: print("Terrain3DUI: _exit_tree()") plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar) plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings) toolbar.queue_free() tool_settings.queue_free() terrain_menu.queue_free() editor_decal_timer.queue_free() func set_visible(p_visible: bool, p_menu_only: bool = false) -> void: if plugin.debug: print("Terrain3DUI: set_visible(%s, %s)" % [ p_visible, p_menu_only ]) terrain_menu.set_visible(p_visible) if p_menu_only: toolbar.set_visible(false) tool_settings.set_visible(false) else: visible = p_visible toolbar.set_visible(p_visible) tool_settings.set_visible(p_visible) if plugin.editor and plugin.terrain and p_visible: await get_tree().process_frame # Won't work, otherwise if plugin.debug: print("Terrain3DUI: set_visible: calling _on_tool_changed()") _on_tool_changed(_selected_tool, _selected_operation) if _selected_tool in [ Terrain3DEditor.REGION, Terrain3DEditor.NAVIGATION ]: plugin.terrain.material.update(Terrain3DMaterial.FULL_REBUILD) func set_menu_visibility(p_list: Control, p_visible: bool) -> void: if p_list: p_list.get_parent().get_parent().visible = p_visible func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void: if plugin.debug: print("Terrain3DUI: _on_tool_changed: ", p_tool, ", ", p_operation) if active_tool == p_tool and active_operation == p_operation: return _selected_tool = p_tool _selected_operation = p_operation clear_picking() set_menu_visibility(tool_settings.advanced_list, true) set_menu_visibility(tool_settings.scale_list, false) set_menu_visibility(tool_settings.rotation_list, false) set_menu_visibility(tool_settings.height_list, false) set_menu_visibility(tool_settings.color_list, false) set_menu_visibility(tool_settings.collision_list, false) # Select which settings to show. Options in tool_settings.gd:_ready var to_show: PackedStringArray = [] match _selected_tool: Terrain3DEditor.REGION: to_show.push_back("instructions") to_show.push_back("invert") set_menu_visibility(tool_settings.advanced_list, false) Terrain3DEditor.SCULPT: to_show.push_back("brush") to_show.push_back("size") to_show.push_back("strength") if _selected_operation in [Terrain3DEditor.ADD, Terrain3DEditor.SUBTRACT]: to_show.push_back("invert") elif _selected_operation == Terrain3DEditor.GRADIENT: to_show.push_back("gradient_points") to_show.push_back("drawable") Terrain3DEditor.HEIGHT: to_show.push_back("brush") to_show.push_back("size") to_show.push_back("strength") to_show.push_back("height") to_show.push_back("height_picker") to_show.push_back("invert") Terrain3DEditor.TEXTURE: to_show.push_back("brush") to_show.push_back("size") to_show.push_back("enable_texture") to_show.push_back("texture_picker") if _selected_operation == Terrain3DEditor.ADD: to_show.push_back("strength") to_show.push_back("invert") to_show.push_back("slope") to_show.push_back("enable_angle") to_show.push_back("angle") to_show.push_back("angle_picker") to_show.push_back("dynamic_angle") to_show.push_back("enable_scale") to_show.push_back("scale") to_show.push_back("scale_picker") Terrain3DEditor.COLOR: to_show.push_back("brush") to_show.push_back("size") to_show.push_back("strength") to_show.push_back("color") to_show.push_back("color_picker") to_show.push_back("slope") to_show.push_back("texture_filter") to_show.push_back("margin") to_show.push_back("invert") Terrain3DEditor.ROUGHNESS: to_show.push_back("brush") to_show.push_back("size") to_show.push_back("strength") to_show.push_back("roughness") to_show.push_back("roughness_picker") to_show.push_back("slope") to_show.push_back("texture_filter") to_show.push_back("margin") to_show.push_back("invert") Terrain3DEditor.AUTOSHADER, Terrain3DEditor.HOLES, Terrain3DEditor.NAVIGATION: to_show.push_back("brush") to_show.push_back("size") to_show.push_back("invert") Terrain3DEditor.INSTANCER: to_show.push_back("size") to_show.push_back("strength") to_show.push_back("slope") to_show.push_back("mesh_picker") set_menu_visibility(tool_settings.height_list, true) to_show.push_back("height_offset") to_show.push_back("random_height") set_menu_visibility(tool_settings.scale_list, true) to_show.push_back("fixed_scale") to_show.push_back("random_scale") set_menu_visibility(tool_settings.rotation_list, true) to_show.push_back("fixed_spin") to_show.push_back("random_spin") to_show.push_back("fixed_tilt") to_show.push_back("random_tilt") to_show.push_back("align_to_normal") set_menu_visibility(tool_settings.color_list, true) to_show.push_back("vertex_color") to_show.push_back("random_darken") to_show.push_back("random_hue") set_menu_visibility(tool_settings.collision_list, true) to_show.push_back("on_collision") to_show.push_back("raycast_height") to_show.push_back("invert") _: pass # Advanced menu settings to_show.push_back("auto_regions") to_show.push_back("align_to_view") to_show.push_back("show_brush_texture") to_show.push_back("gamma") to_show.push_back("brush_spin_speed") tool_settings.show_settings(to_show) if plugin.debug: print("Terrain3DUI: _on_tool_changed: calling _on_setting_changed()") _on_setting_changed() func _on_setting_changed(p_setting: Variant = null) -> void: if plugin.debug: print("Terrain3DUI: _on_setting_changed: ", p_setting if p_setting else "update all") if not plugin.asset_dock: # Skip function if not _ready() return brush_data = tool_settings.get_settings() brush_data["asset_id"] = plugin.asset_dock.current_list.get_selected_asset_id() if plugin.debug: print("Terrain3DUI: _on_setting_changed: selected resource ID: ", brush_data["asset_id"]) if plugin.editor: plugin.editor.set_brush_data(brush_data) inverted_input = brush_data.get("invert", false) if p_setting is CheckBox and p_setting.name == &"Invert": plugin._read_input() # Revalidate keyboard input for modifier_ctrl set_active_operation() update_decal() # Change tool/operation based on modifiers. Called from: # * editor_plugin.gd:_read_input() - when a modifier key is pressed # * _on_tool_changed() via: # * _on_setting_changed() eg. Touchscreen Invert func set_active_operation() -> void: var inverted: bool = plugin.modifier_ctrl || inverted_input # Toggle toolbar buttons toolbar.show_add_buttons(not inverted) # If Shift, Smoothness if plugin.modifier_shift and not inverted: match _selected_tool: Terrain3DEditor.SCULPT, Terrain3DEditor.HEIGHT, Terrain3DEditor.HOLES, \ Terrain3DEditor.INSTANCER: active_tool = Terrain3DEditor.SCULPT active_operation = Terrain3DEditor.AVERAGE Terrain3DEditor.TEXTURE: active_tool = Terrain3DEditor.TEXTURE active_operation = Terrain3DEditor.AVERAGE Terrain3DEditor.COLOR: active_tool = Terrain3DEditor.COLOR active_operation = Terrain3DEditor.AVERAGE Terrain3DEditor.ROUGHNESS: active_tool = Terrain3DEditor.ROUGHNESS active_operation = Terrain3DEditor.AVERAGE # Else if Ctrl/Invert checked, opposite elif _selected_operation == Terrain3DEditor.ADD and inverted: active_tool = _selected_tool active_operation = Terrain3DEditor.SUBTRACT elif _selected_operation == Terrain3DEditor.SUBTRACT and not inverted: active_tool = _selected_tool active_operation = Terrain3DEditor.ADD # Else use default and set else: active_tool = _selected_tool active_operation = _selected_operation # Initiate Multipoint operation operation_builder = null if active_operation == Terrain3DEditor.GRADIENT: operation_builder = GradientOperationBuilder.new() operation_builder.tool_settings = tool_settings if plugin.editor: plugin.editor.set_tool(active_tool) plugin.editor.set_operation(active_operation) func update_decal() -> void: if not plugin.terrain or not plugin.viewport or brush_data.size() <= 3: return # If not a state that should show the decal, hide everything and return mat_rid = plugin.terrain.material.get_material_rid() # Used in hide_decal() and below if not visible or \ plugin._input_mode == -1 or \ # After moving camera, wait for mouse cursor to update before revealing # See https://github.com/godotengine/godot/issues/70098 Time.get_ticks_msec() - plugin.rmb_release_time <= 100: hide_decal() return # Only show decal if in viewport or toolbars var main: Control = EditorInterface.get_editor_main_screen() var main_rect := Rect2(main.position, main.size) main_rect.size.y += tool_settings.size.y if not ( main_rect.has_point(plugin.viewport.get_mouse_position()) && plugin.mouse_in_main ): return reset_decal_arrays() editor_decal_position[0] = Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z) editor_decal_visible = [true, false, false] # Show cursor by default editor_decal_part = [true, true] # Show brush and reticle by default editor_decal_timer.start() ## Region Operations var r_map: PackedInt32Array = plugin.terrain.data.get_region_map() if plugin.editor.get_tool() == Terrain3DEditor.REGION: var r_size: float = float(plugin.terrain.get_region_size()) * plugin.terrain.get_vertex_spacing() var map_size: int = plugin.terrain.data.REGION_MAP_SIZE var half_r_size: float = r_size * 0.5 var pos: Vector2 = (Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z) + Vector2(half_r_size, half_r_size)).snappedf(r_size) - Vector2(half_r_size, half_r_size) editor_brush_texture_rid = region_texture.get_rid() editor_decal_position[0] = pos editor_decal_size[0] = r_size editor_decal_rotation[0] = 0.0 editor_decal_part[1] = false # Disable reticle var loc: Vector2i = plugin.terrain.data.get_region_location(plugin.mouse_global_position) loc += Vector2i(map_size / 2, map_size / 2) if !(loc.x < 0 or loc.x > map_size - 1 or loc.y < 0 or loc.y > map_size - 1): var index: int = clampi(loc.y * map_size + loc.x, 0, map_size * map_size - 1) if plugin.terrain.material.get_world_background() == Terrain3DMaterial.WorldBackground.NONE: if r_map[index] == 0 and active_operation == Terrain3DEditor.ADD: r_map[index] = -index - 1 else: r_map[index] = r_map[index] match active_operation: Terrain3DEditor.ADD: if r_map[index] <= 0: editor_decal_color[0] = Color.WHITE editor_decal_color[0].a = 0.25 else: hide_decal() Terrain3DEditor.SUBTRACT: if r_map[index] > 0: editor_decal_color[0] = Color.WHITE * .15 editor_decal_color[0].a = 0.75 else: hide_decal() else: hide_decal() ## Picking elif picking != Terrain3DEditor.TOOL_MAX: editor_decal_part[0] = false # Hide brush editor_decal_size[0] = plugin.terrain.get_vertex_spacing() editor_decal_color[0] = COLOR_PICK editor_decal_color[0].a = 1.0 ## Brushing Operations else: editor_brush_texture_rid = brush_data["brush"][1].get_rid() editor_decal_size[0] = maxf(brush_data["size"], .5) if brush_data["align_to_view"]: var cam: Camera3D = plugin.terrain.get_camera(); if (cam): editor_decal_rotation[0] = cam.rotation.y else: editor_decal_rotation[0] = 0. match plugin.editor.get_tool(): Terrain3DEditor.SCULPT: match active_operation: Terrain3DEditor.ADD: if plugin.modifier_alt: editor_decal_color[0] = COLOR_LIFT editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) else: editor_decal_color[0] = COLOR_RAISE editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5) Terrain3DEditor.SUBTRACT: if plugin.modifier_alt: editor_decal_color[0] = COLOR_FLATTEN editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5) + .1 else: editor_decal_color[0] = COLOR_LOWER editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25 Terrain3DEditor.AVERAGE: editor_decal_color[0] = COLOR_SMOOTH editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25 Terrain3DEditor.GRADIENT: editor_decal_color[0] = COLOR_SLOPE editor_decal_color[0].a = clamp(brush_data["strength"], .2, .4) Terrain3DEditor.HEIGHT: editor_decal_color[0] = COLOR_HEIGHT editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25 Terrain3DEditor.TEXTURE: if plugin._input_mode == 1: editor_decal_part[0] = false # Hide brush if plugin.modifier_shift: editor_decal_color[0] = COLOR_AVERAGE editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25 else: match active_operation: Terrain3DEditor.REPLACE: editor_decal_color[0] = COLOR_PAINT editor_decal_color[0].a = .6 Terrain3DEditor.SUBTRACT: editor_decal_color[0] = COLOR_UNSPRAY editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1 Terrain3DEditor.ADD: editor_decal_color[0] = COLOR_SPRAY editor_decal_color[0].a = clamp(brush_data["strength"], .15, .4) Terrain3DEditor.COLOR: if plugin.modifier_shift: editor_decal_color[0] = COLOR_AVERAGE editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25 elif plugin.modifier_ctrl: editor_decal_color[0] = Color.WHITE editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) else: editor_decal_color[0] = brush_data["color"].srgb_to_linear() editor_decal_color[0].a *= clamp(brush_data["strength"], .3, .5) Terrain3DEditor.ROUGHNESS: if plugin._input_mode == 1: editor_decal_part[0] = false # Hide brush if plugin.modifier_shift: editor_decal_color[0] = COLOR_AVERAGE editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25 elif plugin.modifier_ctrl: editor_decal_color[0] = COLOR_DRY editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1 else: editor_decal_color[0] = COLOR_WET editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1 Terrain3DEditor.AUTOSHADER: editor_decal_color[0] = COLOR_AUTOSHADER editor_decal_color[0].a = .6 Terrain3DEditor.HOLES: editor_decal_color[0] = COLOR_HOLES editor_decal_color[0].a = .75 Terrain3DEditor.NAVIGATION: editor_decal_color[0] = COLOR_NAVIGATION editor_decal_color[0].a = .80 Terrain3DEditor.INSTANCER: editor_decal_part[0] = false # Hide brush if plugin.modifier_ctrl: editor_decal_color[0] = COLOR_UNINSTANCE editor_decal_color[0].a = .75 else: editor_decal_color[0] = COLOR_INSTANCE editor_decal_color[0].a = .75 if plugin.editor.get_tool() != Terrain3DEditor.REGION and not brush_data["show_brush_texture"]: editor_decal_part[0] = false # Hide brush if active_operation == Terrain3DEditor.GRADIENT: var point1: Vector3 = brush_data["gradient_points"][0] if point1 != Vector3.ZERO: editor_decal_color[1] = COLOR_SLOPE editor_decal_size[1] = 0.25 editor_decal_visible[1] = true editor_decal_position[1] = Vector2(point1.x, point1.z) var point2: Vector3 = brush_data["gradient_points"][1] if point2 != Vector3.ZERO: editor_decal_color[2] = COLOR_SLOPE editor_decal_size[2] = 0.25 editor_decal_visible[2] = true editor_decal_position[2] = Vector2(point2.x, point2.z) if RenderingServer.get_current_rendering_method().contains("gl_compatibility"): for i in editor_decal_color.size(): editor_decal_color[i].a = maxf(0.1, editor_decal_color[i].a - .25) editor_decal_fade = editor_decal_color[0].a # Update Shader params if is_shader_valid(): RenderingServer.material_set_param(mat_rid, "_editor_brush_texture", editor_brush_texture_rid) RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position) RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation) RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size) RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color) RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible) RenderingServer.material_set_param(mat_rid, "_editor_decal_part", editor_decal_part) RenderingServer.material_set_param(mat_rid, "_region_map", r_map) func is_shader_valid() -> bool: # As long as the compiled shader contains at least 1 uniform, we can use it to check # if the shader compilation has failed, as this will then return an empty dictionary. if not plugin.terrain: return false var params = RenderingServer.get_shader_parameter_list(plugin.terrain.material.get_shader_rid()) if params.is_empty(): return false else: return true func hide_decal() -> void: editor_decal_visible = [false, false, false] if is_shader_valid(): var r_map: PackedInt32Array = plugin.terrain.data.get_region_map() RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible) RenderingServer.material_set_param(mat_rid, "_region_map", r_map) # These array sizes are reset to 0 when closing scenes for some unknown reason, so check and reset func reset_decal_arrays() -> void: if editor_decal_color.size() < 3: editor_brush_texture_rid = RID() editor_decal_position = [Vector2(), Vector2(), Vector2()] editor_decal_rotation = [0., 0., 0.] editor_decal_size = [0., 0., 0.] editor_decal_color = [Color(), Color(), Color()] editor_decal_visible = [false, false, false] editor_decal_part = [true, true] func set_decal_rotation(p_rot: float) -> void: editor_decal_rotation[0] = p_rot if is_shader_valid(): RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation) func _on_picking(p_type: Terrain3DEditor.Tool, p_callback: Callable) -> void: picking = p_type picking_callback = p_callback func clear_picking() -> void: picking = Terrain3DEditor.TOOL_MAX func is_picking() -> bool: if picking != Terrain3DEditor.TOOL_MAX: return true if operation_builder and operation_builder.is_picking(): return true return false func pick(p_global_position: Vector3) -> void: if picking != Terrain3DEditor.TOOL_MAX: var color: Color match picking: Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT: color = Color(plugin.terrain.data.get_height(p_global_position), 0., 0., 1.) Terrain3DEditor.ROUGHNESS: color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position) Terrain3DEditor.COLOR: color = plugin.terrain.data.get_color(p_global_position) Terrain3DEditor.ANGLE: color = Color(plugin.terrain.data.get_control_angle(p_global_position), 0., 0., 1.) Terrain3DEditor.SCALE: color = Color(plugin.terrain.data.get_control_scale(p_global_position), 0., 0., 1.) Terrain3DEditor.INSTANCER: var mesh_asset_id: int = plugin.terrain.instancer.get_closest_mesh_id(p_global_position) color = Color(mesh_asset_id, 0., 0., 1.) Terrain3DEditor.TEXTURE: var texture_blend_data: Vector3 = plugin.terrain.data.get_texture_id(p_global_position) if not texture_blend_data.is_finite(): return if texture_blend_data.z < 0.65: color = Color(texture_blend_data.x, 0., 0., 1.) else: color = Color(texture_blend_data.y, 0., 0., 1.) _: push_error("Unsupported picking type: ", picking) return if picking_callback.is_valid(): picking_callback.call(picking, color, p_global_position) picking_callback = Callable() picking = Terrain3DEditor.TOOL_MAX elif operation_builder and operation_builder.is_picking(): operation_builder.pick(p_global_position, plugin.terrain) func set_button_editor_icon(p_button: Button, p_icon_name: String) -> void: p_button.icon = EditorInterface.get_base_control().get_theme_icon(p_icon_name, "EditorIcons") ================================================ FILE: project/addons/terrain_3d/src/ui.gd.uid ================================================ uid://bpad72s36mwkx ================================================ FILE: project/addons/terrain_3d/terrain.gdextension ================================================ [configuration] entry_symbol = "terrain_3d_init" compatibility_minimum = 4.4 [icons] Terrain3D = "res://addons/terrain_3d/icons/terrain3d.svg" [libraries] windows.debug.x86_64 = "res://addons/terrain_3d/bin/libterrain.windows.debug.x86_64.dll" windows.release.x86_64 = "res://addons/terrain_3d/bin/libterrain.windows.release.x86_64.dll" linux.debug.x86_64 = "res://addons/terrain_3d/bin/libterrain.linux.debug.x86_64.so" linux.release.x86_64 = "res://addons/terrain_3d/bin/libterrain.linux.release.x86_64.so" linux.debug.arm64 = "res://addons/terrain_3d/bin/libterrain.linux.debug.arm64.so" linux.release.arm64 = "res://addons/terrain_3d/bin/libterrain.linux.release.arm64.so" linux.debug.rv64 = "res://addons/terrain_3d/bin/libterrain.linux.debug.rv64.so" linux.release.rv64 = "res://addons/terrain_3d/bin/libterrain.linux.release.rv64.so" macos.debug = "res://addons/terrain_3d/bin/libterrain.macos.debug.framework" macos.release = "res://addons/terrain_3d/bin/libterrain.macos.release.framework" android.debug.arm64 = "res://addons/terrain_3d/bin/libterrain.android.debug.arm64.so" android.release.arm64 = "res://addons/terrain_3d/bin/libterrain.android.release.arm64.so" ios.debug = "res://addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib" ios.release = "res://addons/terrain_3d/bin/libterrain.ios.release.universal.dylib" web.debug = "res://addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm" web.release = "res://addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm" ================================================ FILE: project/addons/terrain_3d/terrain.gdextension.uid ================================================ uid://bdn2ygldx56a8 ================================================ FILE: project/addons/terrain_3d/tools/importer.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Importer for Terrain3D @tool extends Terrain3D @export_tool_button("Clear All") var clear_all = reset_settings @export_tool_button("Clear Terrain") var clear_terrain = reset_terrain @export_tool_button("Update Height Range") var update_height_range = update_heights func reset_settings() -> void: height_file_name = "" control_file_name = "" color_file_name = "" destination_directory = "" import_position = Vector2i.ZERO height_offset = 0.0 import_scale = 1.0 r16_range = Vector2(0, 1) r16_size = Vector2i(1024, 1024) material = null assets = null reset_terrain() func reset_terrain() -> void: data_directory = "" for region:Terrain3DRegion in data.get_regions_active(): data.remove_region(region, false) data.update_maps(Terrain3DRegion.TYPE_MAX, true, false) ## Recalculates min and max heights for all regions. func update_heights() -> void: if data: data.calc_height_range(true) @export_group("Import File") ## EXR or R16 are recommended for heightmaps. 16-bit PNGs are down sampled to 8-bit and not recommended. @export_global_file var height_file_name: String = "" ## Only use EXR files in our proprietary format. @export_global_file var control_file_name: String = "" ## Any RGB or RGBA format is fine; PNG or Webp are recommended. @export_global_file var color_file_name: String = "" ## The top left (-X, -Y) corner position of where to place the imported data. Positions are descaled and ignore the vertex_spacing setting. @export var import_position: Vector2i = Vector2i(0, 0) : set = set_import_position ## This scales the height of imported values. @export var import_scale: float = 1.0 ## This vertically offsets the height of imported values. @export var height_offset: float = 0.0 ## The lowest and highest height values of the imported image. Only use for r16 files. @export var r16_range: Vector2 = Vector2(0, 1) ## The dimensions of the imported image. Only use for r16 files. @export var r16_size: Vector2i = Vector2i(1024, 1024) : set = set_r16_size @export_tool_button("Run Import") var run_import = start_import @export_dir var destination_directory: String = "" @export_tool_button("Save to Disk") var save_to_disk = save_data func set_import_position(p_value: Vector2i) -> void: import_position.x = clamp(p_value.x, -8192, 8192) import_position.y = clamp(p_value.y, -8192, 8192) func set_r16_size(p_value: Vector2i) -> void: r16_size.x = clamp(p_value.x, 0, 16384) r16_size.y = clamp(p_value.y, 0, 16384) func start_import() -> void: print("Terrain3DImporter: Importing files:\n\t%s\n\t%s\n\t%s" % [ height_file_name, control_file_name, color_file_name]) var imported_images: Array[Image] imported_images.resize(Terrain3DRegion.TYPE_MAX) var min_max := Vector2(0, 1) var img: Image if height_file_name: img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size) min_max = Terrain3DUtil.get_min_max(img) imported_images[Terrain3DRegion.TYPE_HEIGHT] = img if control_file_name: img = Terrain3DUtil.load_image(control_file_name, ResourceLoader.CACHE_MODE_IGNORE) imported_images[Terrain3DRegion.TYPE_CONTROL] = img if color_file_name: img = Terrain3DUtil.load_image(color_file_name, ResourceLoader.CACHE_MODE_IGNORE) imported_images[Terrain3DRegion.TYPE_COLOR] = img if assets.get_texture_count() == 0: material.show_checkered = false material.show_colormap = true var pos := Vector3(import_position.x * vertex_spacing, 0, import_position.y * vertex_spacing) data.import_images(imported_images, pos, height_offset, import_scale) print("Terrain3DImporter: Import finished") func save_data() -> void: if destination_directory.is_empty(): push_error("Set destination directory first") return data.save_directory(destination_directory) @export_group("Export File") enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR } @export_enum("Height:0", "Control:1", "Color:2") var map_type: int = TYPE_HEIGHT @export var file_name_out: String = "" @export_tool_button("Run Export") var run_export = start_export func start_export() -> void: var err: int = data.export_image(file_name_out, map_type) print("Terrain3DImporter: Export error status: ", err, " ", error_string(err)) ================================================ FILE: project/addons/terrain_3d/tools/importer.gd.uid ================================================ uid://cib2rig7vup10 ================================================ FILE: project/addons/terrain_3d/tools/importer.tscn ================================================ [gd_scene load_steps=9 format=3 uid="uid://blaieaqp413k7"] [ext_resource type="Script" path="res://addons/terrain_3d/tools/importer.gd" id="1_60b8f"] [sub_resource type="Gradient" id="Gradient_88f3t"] offsets = PackedFloat32Array(0.2, 1) colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1) [sub_resource type="FastNoiseLite" id="FastNoiseLite_muvel"] noise_type = 2 frequency = 0.03 cellular_jitter = 3.0 cellular_return_type = 0 domain_warp_enabled = true domain_warp_type = 1 domain_warp_amplitude = 50.0 domain_warp_fractal_type = 2 domain_warp_fractal_lacunarity = 1.5 domain_warp_fractal_gain = 1.0 [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ve0yk"] seamless = true color_ramp = SubResource("Gradient_88f3t") noise = SubResource("FastNoiseLite_muvel") [sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_p55u0"] _shader_parameters = { "blend_sharpness": 0.87, "height_blending": true, "macro_variation1": Color(1, 1, 1, 1), "macro_variation2": Color(1, 1, 1, 1), "noise1_angle": 0.0, "noise1_offset": Vector2(0.5, 0.5), "noise1_scale": 0.04, "noise2_scale": 0.076, "noise3_scale": 0.225, "noise_texture": SubResource("NoiseTexture2D_ve0yk"), "vertex_normals_distance": 128.0 } show_checkered = true [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8rvqy"] cull_mode = 2 vertex_color_use_as_albedo = true backlight_enabled = true backlight = Color(0.5, 0.5, 0.5, 1) [sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_7je72"] height_offset = 0.5 density = 10.0 material_override = SubResource("StandardMaterial3D_8rvqy") generated_type = 1 [sub_resource type="Terrain3DAssets" id="Terrain3DAssets_op32e"] mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_7je72")]) [node name="Importer" type="Terrain3D"] material = SubResource("Terrain3DMaterial_p55u0") assets = SubResource("Terrain3DAssets_op32e") mesh_lods = 8 top_level = true script = ExtResource("1_60b8f") metadata/_edit_lock_ = true ================================================ FILE: project/addons/terrain_3d/tools/region_mover.gd ================================================ # This script can be used to move your regions by an offset. # Eventually this tool will find its way into a built in UI # # Attach it to your Terrain3D node # Save and reload your scene # Select your Terrain3D node # Enter a valid `offset` where all regions will be within -16, +15 # Run it # It should unload the regions, rename files, and reload them # Clear the script and resave your scene @tool extends Terrain3D @export var offset: Vector2i @export_tool_button("Run") var run = start_rename func start_rename() -> void: if offset == Vector2i.ZERO: return var dir_name: String = data_directory data_directory = "" var dir := DirAccess.open(dir_name) if not dir: print("An error occurred when trying to access the path: ", data_directory) return var affected_files: PackedStringArray var files: PackedStringArray = dir.get_files() for file_name in files: if file_name.match("terrain3d*.res") and not dir.current_is_dir(): var region_loc: Vector2i = Terrain3DUtil.filename_to_location(file_name) var new_loc: Vector2i = region_loc + offset if new_loc.x < -16 or new_loc.x > 15 or new_loc.y < -16 or new_loc.y > 15: push_error("New location %.0v out of bounds for region %.0v. Aborting" % [ new_loc, region_loc ]) return var new_name: String = "tmp_" + Terrain3DUtil.location_to_filename(new_loc) dir.rename(file_name, new_name) affected_files.push_back(new_name) print("File: %s renamed to: %s" % [ file_name, new_name ]) for file_name in affected_files: var new_name: String = file_name.trim_prefix("tmp_") dir.rename(file_name, new_name) print("File: %s renamed to: %s" % [ file_name, new_name ]) data_directory = dir_name EditorInterface.get_resource_filesystem().scan() ================================================ FILE: project/addons/terrain_3d/tools/region_mover.gd.uid ================================================ uid://bngnvtbm6ifkk ================================================ FILE: project/addons/terrain_3d/utils/terrain_3d_objects.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Objects parent for Terrain3D # Children nodes get transform updates on sculpting @tool extends Node3D class_name Terrain3DObjects const TransformChangedNotifier: Script = preload("res://addons/terrain_3d/utils/transform_changed_notifier.gd") const CHILD_HELPER_NAME: StringName = &"TransformChangedSignaller" const CHILD_HELPER_PATH: NodePath = ^"TransformChangedSignaller" var _undo_redo = null var _terrain_id: int var _offsets: Dictionary # Object ID -> Vector3(X, Y offset relative to terrain height, Z) var _ignore_transform_change: bool = false func _enter_tree() -> void: if not Engine.is_editor_hint(): return for child in get_children(): _on_child_entered_tree(child) child_entered_tree.connect(_on_child_entered_tree) child_exiting_tree.connect(_on_child_exiting_tree) func _exit_tree() -> void: if not Engine.is_editor_hint(): return child_entered_tree.disconnect(_on_child_entered_tree) child_exiting_tree.disconnect(_on_child_exiting_tree) for child in get_children(): _on_child_exiting_tree(child) func editor_setup(p_plugin) -> void: _undo_redo = p_plugin.get_undo_redo() func get_terrain() -> Terrain3D: var terrain := instance_from_id(_terrain_id) as Terrain3D if not terrain or terrain.is_queued_for_deletion() or not terrain.is_inside_tree(): var terrains: Array[Node] = Engine.get_singleton(&"EditorInterface").get_edited_scene_root().find_children("", "Terrain3D") if terrains.size() > 0: terrain = terrains[0] _terrain_id = terrain.get_instance_id() if terrain else 0 if terrain and terrain.data and not terrain.data.maps_edited.is_connected(_on_maps_edited): terrain.data.maps_edited.connect(_on_maps_edited) return terrain func _get_terrain_height(p_global_position: Vector3) -> float: var terrain: Terrain3D = get_terrain() if not terrain or not terrain.data: return 0.0 var height: float = terrain.data.get_height(p_global_position) if is_nan(height): return 0.0 return height func _on_child_entered_tree(p_node: Node) -> void: if not (p_node is Node3D): return assert(p_node.get_parent() == self) var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH) if not helper: helper = TransformChangedNotifier.new() helper.name = CHILD_HELPER_NAME p_node.add_child(helper, true, INTERNAL_MODE_BACK) assert(p_node.has_node(CHILD_HELPER_PATH)) # When reparenting a Node3D, Godot changes its transform _after_ reparenting it. So here, # we must use call_deferred, to avoid receiving transform_changed as a result of reparenting. _setup_child_signal.call_deferred(p_node, helper) func _setup_child_signal(p_node: Node, helper: TransformChangedNotifier) -> void: if not p_node.is_inside_tree(): return if helper.transform_changed.is_connected(_on_child_transform_changed): return helper.transform_changed.connect(_on_child_transform_changed.bind(p_node)) _update_child_offset(p_node) func _on_child_exiting_tree(p_node: Node) -> void: if not (p_node is Node3D) or not p_node.has_node(CHILD_HELPER_PATH): return var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH) if helper: if helper.transform_changed.is_connected(_on_child_transform_changed): helper.transform_changed.disconnect(_on_child_transform_changed) p_node.remove_child(helper) helper.queue_free() _offsets.erase(p_node.get_instance_id()) func _is_node_selected(p_node: Node) -> bool: var editor_sel = Engine.get_singleton(&"EditorInterface").get_selection() return editor_sel.get_transformable_selected_nodes().has(p_node) func _on_child_transform_changed(p_node: Node3D) -> void: if _ignore_transform_change: return var lmb_down := Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) if lmb_down and (_is_node_selected(p_node) or _is_node_selected(self)): # The user may be moving the node using gizmos. # We should wait until they're done before updating otherwise gizmos + this node conflict. return if not _offsets.has(p_node.get_instance_id()): return var old_offset: Vector3 = _offsets[p_node.get_instance_id()] var old_h: float = _get_terrain_height(old_offset) var old_position: Vector3 = old_offset + Vector3(0, old_h, 0) var new_position: Vector3 = p_node.global_position if old_position.is_equal_approx(new_position): return var new_h: float = _get_terrain_height(new_position) var new_offset: Vector3 = new_position - Vector3(0, new_h, 0) var translate_without_reposition: bool = Input.is_key_pressed(KEY_SHIFT) var y_changed: bool = not is_equal_approx(old_position.y, p_node.global_position.y) if not y_changed and not translate_without_reposition: new_offset.y = old_offset.y new_position = new_offset + Vector3(0, new_h, 0) # Make sure that when the user undo's the translation, the offset change gets undone too! _undo_redo.create_action("Translate", UndoRedo.MERGE_ALL) _undo_redo.add_do_method(self, &"_set_offset_and_position", p_node.get_instance_id(), new_offset, new_position) _undo_redo.add_undo_method(self, &"_set_offset_and_position", p_node.get_instance_id(), old_offset, old_position) _undo_redo.commit_action() func _set_offset_and_position(p_id: int, p_offset: Vector3, p_position: Vector3) -> void: var node := instance_from_id(p_id) as Node if not is_instance_valid(node): return _ignore_transform_change = true node.global_position = p_position _offsets[p_id] = p_offset _ignore_transform_change = false # Overwrite current offset stored for node with its current Y position relative to the terrain func _update_child_offset(p_node: Node3D) -> void: var position: Vector3 = global_transform * p_node.position var h: float = _get_terrain_height(position) var offset: Vector3 = position - Vector3(0, h, 0) _offsets[p_node.get_instance_id()] = offset # Overwrite node's current position with terrain height + stored offset for this node func _update_child_position(p_node: Node3D) -> void: if not _offsets.has(p_node.get_instance_id()): return var position: Vector3 = global_transform * p_node.position var h: float = _get_terrain_height(position) var offset: Vector3 = _offsets[p_node.get_instance_id()] var new_position: Vector3 = global_transform.inverse() * (offset + Vector3(0, h, 0)) if not p_node.position.is_equal_approx(new_position): p_node.position = new_position func _on_maps_edited(p_edited_aabb: AABB) -> void: var edited_area: AABB = p_edited_aabb.grow(1) edited_area.position.y = -INF edited_area.end.y = INF for child in get_children(): var node := child as Node3D if node && edited_area.has_point(node.global_position): _update_child_position(node) ================================================ FILE: project/addons/terrain_3d/utils/terrain_3d_objects.gd.uid ================================================ uid://dbndw8p05yam7 ================================================ FILE: project/addons/terrain_3d/utils/transform_changed_notifier.gd ================================================ # Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. # Transform Changed Notifier for Terrain3D @tool extends Node3D signal transform_changed func _ready() -> void: assert(Engine.is_editor_hint()) set_notify_transform(true) func _notification(what: int) -> void: if what == NOTIFICATION_TRANSFORM_CHANGED: transform_changed.emit() ================================================ FILE: project/addons/terrain_3d/utils/transform_changed_notifier.gd.uid ================================================ uid://claxtgppe8keq ================================================ FILE: project/demo/CodeGeneratedDemo.tscn ================================================ [gd_scene load_steps=8 format=3 uid="uid://cofnhdcclon1w"] [ext_resource type="Script" uid="uid://dakis6gu8b7nm" path="res://demo/src/CodeGenerated.gd" id="1_h7vyv"] [ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="2_3v2uf"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_71ikj"] [ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="4_p8qry"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="4_x5ge4"] [ext_resource type="Script" uid="uid://brh8x1wnycrl5" path="res://demo/src/RuntimeNavigationBaker.gd" id="5_445ur"] [sub_resource type="NavigationMesh" id="NavigationMesh_vs6am"] geometry_parsed_geometry_type = 1 agent_height = 2.0 agent_max_slope = 30.0 [node name="CodeGenerated" type="Node"] script = ExtResource("1_h7vyv") [node name="Environment" parent="." instance=ExtResource("3_71ikj")] [node name="Player" parent="." instance=ExtResource("2_3v2uf")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 0) [node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("4_p8qry")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 50, -10) target = NodePath("../Player") [node name="UI" parent="." instance=ExtResource("4_x5ge4")] [node name="RuntimeNavigationBaker" type="Node" parent="." node_paths=PackedStringArray("player")] script = ExtResource("5_445ur") enabled = false template = SubResource("NavigationMesh_vs6am") player = NodePath("../Player") [node name="RunThisSceneLabel3D" type="Label3D" parent="."] pixel_size = 0.001 billboard = 1 no_depth_test = true fixed_size = true text = "Run This Scene" font_size = 64 outline_size = 10 ================================================ FILE: project/demo/Demo.tscn ================================================ [gd_scene load_steps=10 format=3 uid="uid://chol2xlfbq7cu"] [ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_k7qca"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_nqak5"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_egbwy"] [ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="3_ht63y"] [ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="3_kdh0b"] [ext_resource type="Terrain3DMaterial" uid="uid://ccvn15t8km0ft" path="res://demo/data/M_terrain.tres" id="5_fwrtk"] [ext_resource type="Material" uid="uid://cgk4glwmc4vk2" path="res://addons/terrain_3d/extras/shaders/M_ocean.tres" id="7_fwrtk"] [ext_resource type="PackedScene" uid="uid://d3sr0a7dxfkr8" path="res://addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn" id="8_fwrtk"] [ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="8_g2of2"] [node name="Demo" type="Node"] script = ExtResource("1_k7qca") [node name="UI" parent="." instance=ExtResource("2_nqak5")] [node name="Environment" parent="." instance=ExtResource("3_egbwy")] [node name="Tunnel" parent="." instance=ExtResource("3_kdh0b")] [node name="Player" parent="." instance=ExtResource("3_ht63y")] transform = Transform3D(0.176947, 0, -0.98422, 0, 1, 0, 0.98422, 0, 0.176947, 223.143, 105.348, -1833.08) [node name="Terrain3D" type="Terrain3D" parent="." node_paths=PackedStringArray("collision_target", "clipmap_target", "ocean_light_target")] data_directory = "res://demo/data" material = ExtResource("5_fwrtk") assets = ExtResource("8_g2of2") collision_target = NodePath("../Player") collision_mask = 3 clipmap_target = NodePath("../Player") tessellation_level = 3 mesh_size = 96 cull_margin = 1662.5 ocean_enabled = true ocean_material = ExtResource("7_fwrtk") ocean_light_target = NodePath("../Environment/DirectionalLight3D") top_level = true metadata/_edit_lock_ = true [node name="Terrain3DParticles" parent="Terrain3D" node_paths=PackedStringArray("terrain") instance=ExtResource("8_fwrtk")] terrain = NodePath("..") [editable path="Environment"] ================================================ FILE: project/demo/NavigationDemo.tscn ================================================ [gd_scene load_steps=10 format=3 uid="uid://x34e4v60vdmy"] [ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_po4vw"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="2_i74wg"] [ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="4_fk3ul"] [ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="5_2lvlr"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="5_rc7e2"] [ext_resource type="NavigationMesh" uid="uid://yf3fu23666k8" path="res://demo/data/nav_mesh.res" id="8_6jfdr"] [ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="10_qmhh8"] [ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="12_b2xl8"] [sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_s5mq5"] _shader_parameters = { "_mouse_layer": 2147483648, "auto_base_texture": 0, "auto_height_reduction": 0.1, "auto_overlay_texture": 1, "auto_slope": 0.5, "bias_distance": 512.0, "blend_sharpness": 0.87, "depth_blur": 0.0, "dual_scale_far": 170.0, "dual_scale_near": 100.0, "dual_scale_reduction": 0.3, "dual_scale_texture": 0, &"flat_terrain_normals": false, "mipmap_bias": 1.0, &"shader_uniforms": null, &"shader_uniforms::auto_shader": null, &"shader_uniforms::dual_scaling": null, &"shader_uniforms::general": null, &"shader_uniforms::mipmaps": null, "tri_scale_reduction": 0.3 } world_background = 0 auto_shader_enabled = true dual_scaling_enabled = true [node name="Demo" type="Node"] script = ExtResource("1_po4vw") [node name="UI" parent="." instance=ExtResource("5_rc7e2")] [node name="Environment" parent="." instance=ExtResource("2_i74wg")] [node name="Tunnel" parent="." instance=ExtResource("5_2lvlr")] [node name="Player" parent="." instance=ExtResource("4_fk3ul")] transform = Transform3D(0.0774673, 0, -0.996995, 0, 1, 0, 0.996995, 0, 0.0774673, 229.418, 104.956, -1838.16) [node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("12_b2xl8")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 192.727, 105.171, -1787.81) target = NodePath("../Player") [node name="NavigationRegion3D" type="NavigationRegion3D" parent="."] navigation_mesh = ExtResource("8_6jfdr") [node name="Terrain3D" type="Terrain3D" parent="NavigationRegion3D"] data_directory = "res://demo/data" material = SubResource("Terrain3DMaterial_s5mq5") assets = ExtResource("10_qmhh8") collision_mask = 3 mesh_size = 64 displacement_sharpness = 0.5 top_level = true metadata/_edit_lock_ = true ================================================ FILE: project/demo/assets/materials/M_crystal_blue.tres ================================================ [gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://fkhkir6t1hml"] [ext_resource type="Texture2D" uid="uid://dabyathlpy04p" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_3is54"] [resource] transparency = 1 vertex_color_use_as_albedo = true albedo_color = Color(0.4, 1, 1, 0.647059) metallic_specular = 1.0 roughness = 0.2 roughness_texture = ExtResource("2_3is54") roughness_texture_channel = 3 emission_enabled = true emission = Color(0, 0.145098, 0.776471, 1) emission_energy_multiplier = 1.4 normal_enabled = true normal_scale = 0.4 normal_texture = ExtResource("2_3is54") rim_enabled = true rim = 0.25 clearcoat_roughness = 0.0 ao_light_affect = 1.0 ao_texture_channel = 3 refraction_enabled = true refraction_scale = -0.1 ================================================ FILE: project/demo/assets/materials/M_crystal_purple.tres ================================================ [gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://cso4f2iyuxpmc"] [ext_resource type="Texture2D" uid="uid://dabyathlpy04p" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_1t610"] [resource] transparency = 1 vertex_color_use_as_albedo = true albedo_color = Color(0.4, 0.4, 1, 0.556863) metallic_specular = 1.0 roughness = 0.2 roughness_texture = ExtResource("2_1t610") roughness_texture_channel = 3 emission_enabled = true emission = Color(0.235294, 0.145098, 0.776471, 1) normal_enabled = true normal_scale = 0.4 normal_texture = ExtResource("2_1t610") rim_enabled = true rim = 0.25 clearcoat_roughness = 0.0 ao_light_affect = 1.0 ao_texture_channel = 3 refraction_enabled = true refraction_scale = -0.1 ================================================ FILE: project/demo/assets/materials/M_crystal_red.tres ================================================ [gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://ickkffutwcvo"] [ext_resource type="Texture2D" uid="uid://dabyathlpy04p" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_k2yhv"] [resource] transparency = 1 albedo_color = Color(0.670588, 0.0588235, 0.384314, 0.509804) metallic_specular = 1.0 roughness = 0.2 roughness_texture = ExtResource("2_k2yhv") roughness_texture_channel = 3 emission_enabled = true emission = Color(0.258824, 0.0823529, 0.25098, 1) normal_enabled = true normal_scale = 0.4 normal_texture = ExtResource("2_k2yhv") rim_enabled = true rim = 0.25 clearcoat_roughness = 0.0 ao_enabled = true ao_light_affect = 1.0 ao_texture_channel = 3 refraction_enabled = true refraction_scale = -0.1 ================================================ FILE: project/demo/assets/materials/M_rock23_black_tp.tres ================================================ [gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://d0hyi5n6ng25w"] [ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="1_1js8i"] [ext_resource type="Texture2D" uid="uid://dabyathlpy04p" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_snl3x"] [resource] albedo_color = Color(0.501406, 0.501407, 0.501406, 1) albedo_texture = ExtResource("1_1js8i") roughness = 0.95 roughness_texture = ExtResource("2_snl3x") roughness_texture_channel = 3 normal_enabled = true normal_texture = ExtResource("2_snl3x") uv1_scale = Vector3(0.4, 0.4, 0.4) uv1_triplanar = true uv1_world_triplanar = true texture_filter = 5 ================================================ FILE: project/demo/assets/materials/M_rock23_tp.tres ================================================ [gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://nbbdrx8vma80"] [ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="1_mek0c"] [ext_resource type="Texture2D" uid="uid://dabyathlpy04p" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_pp13c"] [resource] vertex_color_use_as_albedo = true albedo_color = Color(0.83, 0.83, 0.83, 1) albedo_texture = ExtResource("1_mek0c") normal_enabled = true normal_texture = ExtResource("2_pp13c") uv1_scale = Vector3(0.02, 0.02, 0.02) uv1_triplanar = true uv1_triplanar_sharpness = 6.06286 uv1_world_triplanar = true texture_filter = 5 ================================================ FILE: project/demo/assets/models/CrystalC.tscn ================================================ [gd_scene load_steps=4 format=3 uid="uid://cribhhvg03u8g"] [ext_resource type="PackedScene" uid="uid://c8cx4xjwluvxw" path="res://demo/assets/models/RockC.glb" id="1_1vww1"] [ext_resource type="Material" uid="uid://fkhkir6t1hml" path="res://demo/assets/materials/M_crystal_blue.tres" id="2_fclne"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_ycsjC"] data = PackedVector3Array(1.3907, -4.3898, 0.264, 0.3707, -6.5026, 0.2358, 0.9197, -5.2139, 0.5803, 1.3907, -4.3898, 0.264, 1.5973, -4.4989, -0.523, 0.3707, -6.5026, 0.2358, 2.7182, -1.9153, 1.6333, 2.6862, -0.2733, 1.344, 2.791, -2.0828, 0.6971, -0.6892, -7.3327, -0.0456, -1.2211, -6.6432, -1.3733, -1.8677, -6.7222, 0.0053, 0.1584, -6.6788, -0.712, 0.8528, -5.1659, -1.2387, -0.129, -6.0155, -1.6627, 0.3707, -6.5026, 0.2358, 0.8249, -4.8431, 1.2373, 0.9197, -5.2139, 0.5803, 0.3707, -6.5026, 0.2358, -0.1504, -6.0468, 1.6347, 0.8249, -4.8431, 1.2373, 2.7182, -1.9153, 1.6333, 2.5729, -0.0463, 2.323, 2.6862, -0.2733, 1.344, -1.5434, -2.0626, 2.9798, -0.0319, -0.907, 3.3884, -0.6296, -2.1168, 3.0274, -1.5434, -2.0626, 2.9798, -2.2391, -1.0013, 3.2378, -0.0319, -0.907, 3.3884, -3.2503, -1.9438, -0.0444, -3.1045, -0.0443, -0.8667, -2.9895, -0.0523, 0.8233, -0.5191, -2.1018, -2.9939, -2.0489, -0.9812, -3.014, -1.4897, -2.0239, -2.8933, -0.5191, -2.1018, -2.9939, 0.0444, -0.8451, -3.3989, -2.0489, -0.9812, -3.014, 3.4189, -1.5098, -2.1519, 3.3947, 0.8197, -1.419, 2.9911, 0.177, -2.5055, -0.9484, -5.3598, 2.2535, -0.8975, -3.7031, 2.6018, -0.1302, -3.9225, 2.4564, -2.8602, -5.2902, -0.0034, -3.2063, -3.9919, -0.0118, -3.0246, -4.4027, 0.8468, -0.9423, -5.2303, -2.2174, -1.6189, -3.1121, -2.5666, -2.3373, -3.6333, -2.2624, -0.9423, -5.2303, -2.2174, -0.9004, -3.6647, -2.5803, -1.6189, -3.1121, -2.5666, 2.4159, -3.3908, 0.0683, 2.6374, -3.7219, -1.0723, 1.5973, -4.4989, -0.523, 0.0817, 0.7564, 3.232, 1.3747, 1.7762, 2.618, 1.9677, 0.877, 2.8315, 0.0817, 0.7564, 3.232, 0.5064, 1.8556, 2.7003, 1.3747, 1.7762, 2.618, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 0.6919, 3.2703, 2.2188, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -2.0691, 2.5964, 1.5185, -1.2285, 2.4662, -2.4325, -1.544, 3.8438, -1.471, -2.3777, 2.1645, -2.0413, 2.3543, 3.9299, -1.7328, 1.4379, 5.2295, -1.8152, 1.1494, 2.3687, -2.734, 1.4341, 6.0081, 1.2447, 0.9325, 6.924, 0.4365, 2.3528, 6.0999, 0.4838, -1.8677, -6.7222, 0.0053, -2.3434, -5.4566, 1.3758, -1.2352, -6.7568, 1.4079, 3.0694, 0.3768, 0.2917, 3.6836, 2.3906, 0.1427, 3.39, -0.1345, -0.5433, 1.5686, 6.857, -0.4389, 0.7824, 7.1818, -0.4789, 0.9515, 6.8097, -1.0683, 2.4165, 5.7335, -0.6508, 1.4379, 5.2295, -1.8152, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 1.1494, 2.3687, -2.734, 0.0399, 0.8971, -3.3453, -0.1803, 5.4522, -1.6664, -0.2609, 6.3493, -0.8464, -1.544, 3.8438, -1.471, -3.1045, -0.0443, -0.8667, -2.9635, 2.0631, -0.0469, -2.9895, -0.0523, 0.8233, -2.8218, -0.0908, -2.3316, -2.8949, 1.7465, -1.4628, -3.1045, -0.0443, -0.8667, -1.137, 5.0901, -0.2048, -0.2548, 6.3653, 0.3115, -0.7059, 4.7856, 0.8682, -1.8973, 4.0438, -0.0231, -1.6818, 3.3981, 1.0198, -2.3926, 3.3478, 0.3255, -2.9895, -0.0523, 0.8233, -2.7222, 1.5834, 1.3251, -2.9289, -0.1232, 2.5316, -0.7059, 4.7856, 0.8682, -0.2548, 6.3653, 0.3115, 0.4487, 4.8708, 1.6514, -0.7059, 4.7856, 0.8682, 0.4487, 4.8708, 1.6514, -0.2637, 3.5029, 1.8153, 3.3947, 0.8197, -1.419, 3.6143, 2.6541, -0.7787, 3.0814, 2.2747, -1.5878, 3.3017, 4.3689, -0.5397, 2.4165, 5.7335, -0.6508, 2.3543, 3.9299, -1.7328, -2.1314, 0.7603, 3.0046, -1.0522, 1.986, 2.2721, 0.0817, 0.7564, 3.232, 0.0399, 0.8971, -3.3453, 1.1494, 2.3687, -2.734, -1.2285, 2.4662, -2.4325, 0.549, 3.3793, -2.5003, 0.4374, 4.5707, -2.224, -0.1459, 3.4745, -2.3857, 1.4341, 6.0081, 1.2447, 2.3528, 6.0999, 0.4838, 2.8818, 4.7444, 1.3483, -3.1045, -0.0443, -0.8667, -2.8949, 1.7465, -1.4628, -2.9635, 2.0631, -0.0469, -2.7186, 2.9035, -1.3821, -2.3162, 3.6228, -0.9721, -2.7435, 3.293, -0.5137, -0.1803, 5.4522, -1.6664, -1.544, 3.8438, -1.471, -1.2285, 2.4662, -2.4325, -2.1314, 0.7603, 3.0046, -2.0691, 2.5964, 1.5185, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -1.6818, 3.3981, 1.0198, -1.0522, 1.986, 2.2721, 0.0399, 0.8971, -3.3453, -1.2285, 2.4662, -2.4325, -2.0395, 0.8474, -2.9039, 1.9677, 0.877, 2.8315, 1.5121, 2.8416, 2.3106, 2.2766, 3.3307, 2.1367, 1.9677, 0.877, 2.8315, 1.3747, 1.7762, 2.618, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 2.2766, 3.3307, 2.1367, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 0.4487, 4.8708, 1.6514, 2.2766, 3.3307, 2.1367, 3.3947, 0.8197, -1.419, 3.0814, 2.2747, -1.5878, 2.9911, 0.177, -2.5055, 2.8818, 4.7444, 1.3483, 2.3528, 6.0999, 0.4838, 3.481, 4.5391, 0.4718, 2.0681, 1.0413, -2.9849, 2.3543, 3.9299, -1.7328, 1.1494, 2.3687, -2.734, 2.5729, -0.0463, 2.323, 2.8449, 1.8063, 1.8788, 2.6862, -0.2733, 1.344, -1.4897, -2.0239, -2.8933, -2.3373, -3.6333, -2.2624, -1.6189, -3.1121, -2.5666, -1.4897, -2.0239, -2.8933, -2.0489, -0.9812, -3.014, -2.3373, -3.6333, -2.2624, -2.0395, 0.8474, -2.9039, -1.2285, 2.4662, -2.4325, -2.3777, 2.1645, -2.0413, 2.1308, -0.8122, -3.0291, 0.0444, -0.8451, -3.3989, 1.2123, -2.2074, -2.6786, -3.2503, -1.9438, -0.0444, -2.9895, -0.0523, 0.8233, -3.048, -2.4163, 1.5053, -2.9895, -0.0523, 0.8233, -2.9635, 2.0631, -0.0469, -2.7222, 1.5834, 1.3251, -2.8218, -0.0908, -2.3316, -3.1045, -0.0443, -0.8667, -3.1353, -2.4738, -1.533, -0.6296, -2.1168, 3.0274, -0.0319, -0.907, 3.3884, 0.6607, -2.1929, 2.7904, 0.0817, 0.7564, 3.232, -1.0522, 1.986, 2.2721, 0.5064, 1.8556, 2.7003, 3.0694, 0.3768, 0.2917, 3.39, -0.1345, -0.5433, 3.0799, -1.877, -0.2485, 2.332, -3.2513, 1.4491, 1.8096, -3.8731, 0.517, 1.7532, -3.8773, 1.1862, 2.332, -3.2513, 1.4491, 2.4159, -3.3908, 0.0683, 1.8096, -3.8731, 0.517, 3.0694, 0.3768, 0.2917, 3.2927, 2.696, 1.0915, 3.6836, 2.3906, 0.1427, 0.8528, -5.1659, -1.2387, 0.5989, -4.5033, -1.836, -0.129, -6.0155, -1.6627, 1.6723, -4.1113, -1.5062, 2.2303, -3.7001, -1.8804, 1.5835, -3.611, -1.9401, 2.6485, -2.4319, -2.56, 2.1308, -0.8122, -3.0291, 1.2123, -2.2074, -2.6786, -1.2211, -6.6432, -1.3733, -2.3738, -5.4707, -1.3892, -1.8677, -6.7222, 0.0053, 1.2123, -2.2074, -2.6786, 0.0444, -0.8451, -3.3989, -0.5191, -2.1018, -2.9939, -0.9423, -5.2303, -2.2174, -0.0313, -3.8492, -2.3572, -0.9004, -3.6647, -2.5803, -2.9895, -0.0523, 0.8233, -2.9289, -0.1232, 2.5316, -3.048, -2.4163, 1.5053, -3.1353, -2.4738, -1.533, -3.1045, -0.0443, -0.8667, -3.2503, -1.9438, -0.0444, -2.8602, -5.2902, -0.0034, -3.0733, -4.4498, -0.8737, -3.2063, -3.9919, -0.0118, -0.1504, -6.0468, 1.6347, 0.6052, -4.4095, 1.8566, 0.8249, -4.8431, 1.2373, -2.3213, -3.5933, 2.2613, -1.5434, -2.0626, 2.9798, -1.6095, -3.1051, 2.5592, -2.3213, -3.5933, 2.2613, -2.2391, -1.0013, 3.2378, -1.5434, -2.0626, 2.9798, 3.0799, -1.877, -0.2485, 3.39, -0.1345, -0.5433, 3.4106, -2.2767, -1.2285, -0.0319, -0.907, 3.3884, 1.8364, -0.871, 2.91, 0.6607, -2.1929, 2.7904, 1.7532, -3.8773, 1.1862, 1.5121, -3.4321, 1.801, 2.332, -3.2513, 1.4491, -0.9484, -5.3598, 2.2535, -1.6095, -3.1051, 2.5592, -0.8975, -3.7031, 2.6018, -0.9484, -5.3598, 2.2535, -2.3213, -3.5933, 2.2613, -1.6095, -3.1051, 2.5592, 2.4159, -3.3908, 0.0683, 1.3907, -4.3898, 0.264, 1.8096, -3.8731, 0.517, 2.4159, -3.3908, 0.0683, 1.5973, -4.4989, -0.523, 1.3907, -4.3898, 0.264, 0.6607, -2.1929, 2.7904, 1.8364, -0.871, 2.91, 2.1556, -2.2057, 2.3132, -0.6892, -7.3327, -0.0456, 0.3707, -6.5026, 0.2358, 0.1584, -6.6788, -0.712, 2.7182, -1.9153, 1.6333, 2.332, -3.2513, 1.4491, 2.1556, -2.2057, 2.3132, -0.8975, -3.7031, 2.6018, -1.5434, -2.0626, 2.9798, -0.6296, -2.1168, 3.0274, -0.8975, -3.7031, 2.6018, -1.6095, -3.1051, 2.5592, -1.5434, -2.0626, 2.9798, -0.5191, -2.1018, -2.9939, -1.6189, -3.1121, -2.5666, -0.9004, -3.6647, -2.5803, -0.5191, -2.1018, -2.9939, -1.4897, -2.0239, -2.8933, -1.6189, -3.1121, -2.5666, 3.4106, -2.2767, -1.2285, 2.2303, -3.7001, -1.8804, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 3.4189, -1.5098, -2.1519, 2.6485, -2.4319, -2.56, 2.2303, -3.7001, -1.8804, 3.4106, -2.2767, -1.2285, 3.4189, -1.5098, -2.1519, 0.6919, 3.2703, 2.2188, 1.3747, 1.7762, 2.618, 0.5064, 1.8556, 2.7003, 0.6919, 3.2703, 2.2188, 1.5121, 2.8416, 2.3106, 1.3747, 1.7762, 2.618, -2.7186, 2.9035, -1.3821, -2.8949, 1.7465, -1.4628, -2.3777, 2.1645, -2.0413, 3.6143, 2.6541, -0.7787, 3.481, 4.5391, 0.4718, 3.3017, 4.3689, -0.5397, 3.6143, 2.6541, -0.7787, 3.6836, 2.3906, 0.1427, 3.481, 4.5391, 0.4718, 0.7824, 7.1818, -0.4789, -0.2548, 6.3653, 0.3115, -0.2609, 6.3493, -0.8464, 0.7824, 7.1818, -0.4789, 0.9325, 6.924, 0.4365, -0.2548, 6.3653, 0.3115, -0.1504, -6.0468, 1.6347, -1.2352, -6.7568, 1.4079, -0.9484, -5.3598, 2.2535, 1.8096, -3.8731, 0.517, 0.8249, -4.8431, 1.2373, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 1.3907, -4.3898, 0.264, 0.9197, -5.2139, 0.5803, 0.8249, -4.8431, 1.2373, 1.8096, -3.8731, 0.517, 1.3907, -4.3898, 0.264, -0.1302, -3.9225, 2.4564, 1.5121, -3.4321, 1.801, 0.6052, -4.4095, 1.8566, -0.1302, -3.9225, 2.4564, 0.6607, -2.1929, 2.7904, 1.5121, -3.4321, 1.801, 2.4159, -3.3908, 0.0683, 2.791, -2.0828, 0.6971, 3.0799, -1.877, -0.2485, 1.5973, -4.4989, -0.523, 1.6723, -4.1113, -1.5062, 0.8528, -5.1659, -1.2387, -3.0246, -4.4027, 0.8468, -2.3213, -3.5933, 2.2613, -2.3434, -5.4566, 1.3758, -3.0246, -4.4027, 0.8468, -3.048, -2.4163, 1.5053, -2.3213, -3.5933, 2.2613, -1.2211, -6.6432, -1.3733, -0.129, -6.0155, -1.6627, -0.9423, -5.2303, -2.2174, -3.0733, -4.4498, -0.8737, -2.3373, -3.6333, -2.2624, -3.1353, -2.4738, -1.533, -3.0733, -4.4498, -0.8737, -2.3738, -5.4707, -1.3892, -2.3373, -3.6333, -2.2624, 1.5835, -3.611, -1.9401, -0.0313, -3.8492, -2.3572, 0.5989, -4.5033, -1.836, 1.5835, -3.611, -1.9401, 1.2123, -2.2074, -2.6786, -0.0313, -3.8492, -2.3572, 2.5729, -0.0463, 2.323, 1.8364, -0.871, 2.91, 1.9677, 0.877, 2.8315, -2.9289, -0.1232, 2.5316, -2.1314, 0.7603, 3.0046, -2.2391, -1.0013, 3.2378, -2.0395, 0.8474, -2.9039, -2.8218, -0.0908, -2.3316, -2.0489, -0.9812, -3.014, 2.1308, -0.8122, -3.0291, 2.9911, 0.177, -2.5055, 2.0681, 1.0413, -2.9849, 3.2927, 2.696, 1.0915, 2.2766, 3.3307, 2.1367, 2.8818, 4.7444, 1.3483, 3.2927, 2.696, 1.0915, 2.8449, 1.8063, 1.8788, 2.2766, 3.3307, 2.1367, -2.9635, 2.0631, -0.0469, -2.7435, 3.293, -0.5137, -2.3926, 3.3478, 0.3255, 2.3528, 6.0999, 0.4838, 1.5686, 6.857, -0.4389, 2.4165, 5.7335, -0.6508, -2.3162, 3.6228, -0.9721, -1.137, 5.0901, -0.2048, -1.8973, 4.0438, -0.0231, -2.3162, 3.6228, -0.9721, -1.544, 3.8438, -1.471, -1.137, 5.0901, -0.2048, 0.4374, 4.5707, -2.224, 0.9515, 6.8097, -1.0683, -0.1803, 5.4522, -1.6664, 0.4374, 4.5707, -2.224, 1.4379, 5.2295, -1.8152, 0.9515, 6.8097, -1.0683, -1.2352, -6.7568, 1.4079, 0.3707, -6.5026, 0.2358, -0.6892, -7.3327, -0.0456, -1.2352, -6.7568, 1.4079, -0.1504, -6.0468, 1.6347, 0.3707, -6.5026, 0.2358, 0.6607, -2.1929, 2.7904, -0.8975, -3.7031, 2.6018, -0.6296, -2.1168, 3.0274, 0.6607, -2.1929, 2.7904, -0.1302, -3.9225, 2.4564, -0.8975, -3.7031, 2.6018, 2.791, -2.0828, 0.6971, 2.332, -3.2513, 1.4491, 2.7182, -1.9153, 1.6333, 2.791, -2.0828, 0.6971, 2.4159, -3.3908, 0.0683, 2.332, -3.2513, 1.4491, 1.6723, -4.1113, -1.5062, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 1.6723, -4.1113, -1.5062, 1.5973, -4.4989, -0.523, 2.6374, -3.7219, -1.0723, -3.048, -2.4163, 1.5053, -3.2063, -3.9919, -0.0118, -3.2503, -1.9438, -0.0444, -3.048, -2.4163, 1.5053, -3.0246, -4.4027, 0.8468, -3.2063, -3.9919, -0.0118, -0.129, -6.0155, -1.6627, -0.6892, -7.3327, -0.0456, 0.1584, -6.6788, -0.712, -0.129, -6.0155, -1.6627, -1.2211, -6.6432, -1.3733, -0.6892, -7.3327, -0.0456, 1.2123, -2.2074, -2.6786, 2.2303, -3.7001, -1.8804, 2.6485, -2.4319, -2.56, 1.2123, -2.2074, -2.6786, 1.5835, -3.611, -1.9401, 2.2303, -3.7001, -1.8804, 3.3947, 0.8197, -1.419, 3.6836, 2.3906, 0.1427, 3.6143, 2.6541, -0.7787, 3.3947, 0.8197, -1.419, 3.39, -0.1345, -0.5433, 3.6836, 2.3906, 0.1427, -2.1314, 0.7603, 3.0046, -2.7222, 1.5834, 1.3251, -2.0691, 2.5964, 1.5185, -2.1314, 0.7603, 3.0046, -2.9289, -0.1232, 2.5316, -2.7222, 1.5834, 1.3251, 2.9911, 0.177, -2.5055, 2.6485, -2.4319, -2.56, 3.4189, -1.5098, -2.1519, 2.9911, 0.177, -2.5055, 2.1308, -0.8122, -3.0291, 2.6485, -2.4319, -2.56, -2.7435, 3.293, -0.5137, -2.8949, 1.7465, -1.4628, -2.7186, 2.9035, -1.3821, -2.7435, 3.293, -0.5137, -2.9635, 2.0631, -0.0469, -2.8949, 1.7465, -1.4628, -0.1459, 3.4745, -2.3857, 1.1494, 2.3687, -2.734, 0.549, 3.3793, -2.5003, -0.1459, 3.4745, -2.3857, -1.2285, 2.4662, -2.4325, 1.1494, 2.3687, -2.734, 2.3543, 3.9299, -1.7328, 3.6143, 2.6541, -0.7787, 3.3017, 4.3689, -0.5397, 2.3543, 3.9299, -1.7328, 3.0814, 2.2747, -1.5878, 3.6143, 2.6541, -0.7787, 1.5686, 6.857, -0.4389, 0.9325, 6.924, 0.4365, 0.7824, 7.1818, -0.4789, 1.5686, 6.857, -0.4389, 2.3528, 6.0999, 0.4838, 0.9325, 6.924, 0.4365, -1.544, 3.8438, -1.471, -2.7186, 2.9035, -1.3821, -2.3777, 2.1645, -2.0413, -1.544, 3.8438, -1.471, -2.3162, 3.6228, -0.9721, -2.7186, 2.9035, -1.3821, 1.4379, 5.2295, -1.8152, 0.549, 3.3793, -2.5003, 1.1494, 2.3687, -2.734, 1.4379, 5.2295, -1.8152, 0.4374, 4.5707, -2.224, 0.549, 3.3793, -2.5003, 2.1556, -2.2057, 2.3132, 1.5121, -3.4321, 1.801, 0.6607, -2.1929, 2.7904, 2.1556, -2.2057, 2.3132, 2.332, -3.2513, 1.4491, 1.5121, -3.4321, 1.801, 3.4106, -2.2767, -1.2285, 2.4159, -3.3908, 0.0683, 3.0799, -1.877, -0.2485, 3.4106, -2.2767, -1.2285, 2.6374, -3.7219, -1.0723, 2.4159, -3.3908, 0.0683, 0.1584, -6.6788, -0.712, 1.5973, -4.4989, -0.523, 0.8528, -5.1659, -1.2387, 0.1584, -6.6788, -0.712, 0.3707, -6.5026, 0.2358, 1.5973, -4.4989, -0.523, -3.2503, -1.9438, -0.0444, -3.0733, -4.4498, -0.8737, -3.1353, -2.4738, -1.533, -3.2503, -1.9438, -0.0444, -3.2063, -3.9919, -0.0118, -3.0733, -4.4498, -0.8737, -0.5191, -2.1018, -2.9939, -0.0313, -3.8492, -2.3572, 1.2123, -2.2074, -2.6786, -0.5191, -2.1018, -2.9939, -0.9004, -3.6647, -2.5803, -0.0313, -3.8492, -2.3572, 3.4189, -1.5098, -2.1519, 3.39, -0.1345, -0.5433, 3.3947, 0.8197, -1.419, 3.4189, -1.5098, -2.1519, 3.4106, -2.2767, -1.2285, 3.39, -0.1345, -0.5433, 2.7182, -1.9153, 1.6333, 1.8364, -0.871, 2.91, 2.5729, -0.0463, 2.323, 2.7182, -1.9153, 1.6333, 2.1556, -2.2057, 2.3132, 1.8364, -0.871, 2.91, -2.3777, 2.1645, -2.0413, -2.8218, -0.0908, -2.3316, -2.0395, 0.8474, -2.9039, -2.3777, 2.1645, -2.0413, -2.8949, 1.7465, -1.4628, -2.8218, -0.0908, -2.3316, 3.481, 4.5391, 0.4718, 3.2927, 2.696, 1.0915, 2.8818, 4.7444, 1.3483, 3.481, 4.5391, 0.4718, 3.6836, 2.3906, 0.1427, 3.2927, 2.696, 1.0915, 0.6919, 3.2703, 2.2188, -1.0522, 1.986, 2.2721, -0.2637, 3.5029, 1.8153, 0.6919, 3.2703, 2.2188, 0.5064, 1.8556, 2.7003, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -2.9635, 2.0631, -0.0469, -2.3926, 3.3478, 0.3255, -2.0691, 2.5964, 1.5185, -2.7222, 1.5834, 1.3251, -2.9635, 2.0631, -0.0469, -0.2548, 6.3653, 0.3115, 1.4341, 6.0081, 1.2447, 0.4487, 4.8708, 1.6514, -0.2548, 6.3653, 0.3115, 0.9325, 6.924, 0.4365, 1.4341, 6.0081, 1.2447, 3.3017, 4.3689, -0.5397, 2.3528, 6.0999, 0.4838, 2.4165, 5.7335, -0.6508, 3.3017, 4.3689, -0.5397, 3.481, 4.5391, 0.4718, 2.3528, 6.0999, 0.4838, -0.2609, 6.3493, -0.8464, -1.137, 5.0901, -0.2048, -1.544, 3.8438, -1.471, -0.2609, 6.3493, -0.8464, -0.2548, 6.3653, 0.3115, -1.137, 5.0901, -0.2048, 0.7824, 7.1818, -0.4789, -0.1803, 5.4522, -1.6664, 0.9515, 6.8097, -1.0683, 0.7824, 7.1818, -0.4789, -0.2609, 6.3493, -0.8464, -0.1803, 5.4522, -1.6664, 2.4165, 5.7335, -0.6508, 0.9515, 6.8097, -1.0683, 1.4379, 5.2295, -1.8152, 2.4165, 5.7335, -0.6508, 1.5686, 6.857, -0.4389, 0.9515, 6.8097, -1.0683, 0.4374, 4.5707, -2.224, -1.2285, 2.4662, -2.4325, -0.1459, 3.4745, -2.3857, 0.4374, 4.5707, -2.224, -0.1803, 5.4522, -1.6664, -1.2285, 2.4662, -2.4325, -1.8973, 4.0438, -0.0231, -0.7059, 4.7856, 0.8682, -1.6818, 3.3981, 1.0198, -1.8973, 4.0438, -0.0231, -1.137, 5.0901, -0.2048, -0.7059, 4.7856, 0.8682, -2.3162, 3.6228, -0.9721, -2.3926, 3.3478, 0.3255, -2.7435, 3.293, -0.5137, -2.3162, 3.6228, -0.9721, -1.8973, 4.0438, -0.0231, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -0.2637, 3.5029, 1.8153, -1.0522, 1.986, 2.2721, -1.6818, 3.3981, 1.0198, -0.7059, 4.7856, 0.8682, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 2.8818, 4.7444, 1.3483, 2.2766, 3.3307, 2.1367, 0.4487, 4.8708, 1.6514, 1.4341, 6.0081, 1.2447, 2.8818, 4.7444, 1.3483, 3.0814, 2.2747, -1.5878, 2.0681, 1.0413, -2.9849, 2.9911, 0.177, -2.5055, 3.0814, 2.2747, -1.5878, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 0.0444, -0.8451, -3.3989, -2.0395, 0.8474, -2.9039, -2.0489, -0.9812, -3.014, 0.0444, -0.8451, -3.3989, 0.0399, 0.8971, -3.3453, -2.0395, 0.8474, -2.9039, -2.2391, -1.0013, 3.2378, 0.0817, 0.7564, 3.232, -0.0319, -0.907, 3.3884, -2.2391, -1.0013, 3.2378, -2.1314, 0.7603, 3.0046, 0.0817, 0.7564, 3.232, 2.5729, -0.0463, 2.323, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 2.5729, -0.0463, 2.323, 1.9677, 0.877, 2.8315, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 3.0694, 0.3768, 0.2917, 2.6862, -0.2733, 1.344, 2.8449, 1.8063, 1.8788, 3.2927, 2.696, 1.0915, 3.0694, 0.3768, 0.2917, 2.1308, -0.8122, -3.0291, 0.0399, 0.8971, -3.3453, 0.0444, -0.8451, -3.3989, 2.1308, -0.8122, -3.0291, 2.0681, 1.0413, -2.9849, 0.0399, 0.8971, -3.3453, -2.0489, -0.9812, -3.014, -3.1353, -2.4738, -1.533, -2.3373, -3.6333, -2.2624, -2.0489, -0.9812, -3.014, -2.8218, -0.0908, -2.3316, -3.1353, -2.4738, -1.533, -2.9289, -0.1232, 2.5316, -2.3213, -3.5933, 2.2613, -3.048, -2.4163, 1.5053, -2.9289, -0.1232, 2.5316, -2.2391, -1.0013, 3.2378, -2.3213, -3.5933, 2.2613, -0.0319, -0.907, 3.3884, 1.9677, 0.877, 2.8315, 1.8364, -0.871, 2.91, -0.0319, -0.907, 3.3884, 0.0817, 0.7564, 3.232, 1.9677, 0.877, 2.8315, 2.6862, -0.2733, 1.344, 3.0799, -1.877, -0.2485, 2.791, -2.0828, 0.6971, 2.6862, -0.2733, 1.344, 3.0694, 0.3768, 0.2917, 3.0799, -1.877, -0.2485, 0.8528, -5.1659, -1.2387, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, 0.8528, -5.1659, -1.2387, 1.6723, -4.1113, -1.5062, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, -0.9423, -5.2303, -2.2174, -0.129, -6.0155, -1.6627, 0.5989, -4.5033, -1.836, -0.0313, -3.8492, -2.3572, -0.9423, -5.2303, -2.2174, -1.2211, -6.6432, -1.3733, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -1.2211, -6.6432, -1.3733, -0.9423, -5.2303, -2.2174, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -2.3738, -5.4707, -1.3892, -3.0733, -4.4498, -0.8737, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -1.8677, -6.7222, 0.0053, -2.8602, -5.2902, -0.0034, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -0.9484, -5.3598, 2.2535, -1.2352, -6.7568, 1.4079, -2.3434, -5.4566, 1.3758, -2.3213, -3.5933, 2.2613, -0.9484, -5.3598, 2.2535, -0.1504, -6.0468, 1.6347, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, -0.1504, -6.0468, 1.6347, -0.9484, -5.3598, 2.2535, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 0.6052, -4.4095, 1.8566, 1.5121, -3.4321, 1.801, 1.7532, -3.8773, 1.1862, -0.6892, -7.3327, -0.0456, -1.8677, -6.7222, 0.0053, -1.2352, -6.7568, 1.4079) [node name="CrystalC" instance=ExtResource("1_1vww1")] collision_mask = 3 [node name="Rock3" parent="." index="0"] surface_material_override/0 = ExtResource("2_fclne") [node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"] shape = SubResource("ConcavePolygonShape3D_ycsjC") ================================================ FILE: project/demo/assets/models/LOD10Example.tscn ================================================ [gd_scene load_steps=21 format=3 uid="uid://dqwgv1ahsvqio"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"] albedo_color = Color(1, 0, 0, 1) [sub_resource type="SphereMesh" id="SphereMesh_u4ac2"] material = SubResource("StandardMaterial3D_788j8") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0qa1y"] albedo_color = Color(1, 0.907, 0.38, 1) [sub_resource type="TorusMesh" id="TorusMesh_tjrnq"] material = SubResource("StandardMaterial3D_0qa1y") inner_radius = 0.141 outer_radius = 0.601 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3i52q"] albedo_color = Color(1, 0.540167, 0.11, 1) [sub_resource type="BoxMesh" id="BoxMesh_xyuxq"] material = SubResource("StandardMaterial3D_3i52q") size = Vector3(0.85, 0.85, 0.85) [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p0ha4"] albedo_color = Color(0.48, 1, 0.497333, 1) [sub_resource type="PrismMesh" id="PrismMesh_xnm4h"] material = SubResource("StandardMaterial3D_p0ha4") size = Vector3(0.89, 0.82, 0.835) [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jv6fb"] albedo_color = Color(0.3384, 0.538933, 0.94, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_i1yhp"] material = SubResource("StandardMaterial3D_jv6fb") top_radius = 0.0 bottom_radius = 0.59 height = 1.015 radial_segments = 4 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0qk4s"] albedo_color = Color(0.62325, 0.495, 0.9, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_bvmel"] material = SubResource("StandardMaterial3D_0qk4s") top_radius = 0.58 bottom_radius = 0.25 height = 0.895 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_438mn"] albedo_color = Color(0.933333, 0.2, 1, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_x8oip"] material = SubResource("StandardMaterial3D_438mn") top_radius = 0.4 bottom_radius = 0.4 height = 0.96 radial_segments = 7 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hqr78"] albedo_color = Color(0.51, 0.456705, 0.3417, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_dp7xj"] material = SubResource("StandardMaterial3D_hqr78") top_radius = 0.29 bottom_radius = 0.59 height = 0.65 radial_segments = 4 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_aba40"] albedo_color = Color(0.1512, 0.36, 0.17208, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_8ctts"] material = SubResource("StandardMaterial3D_aba40") top_radius = 0.45 bottom_radius = 0.495 height = 0.89 radial_segments = 4 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tl8dq"] albedo_color = Color(0.2279, 0.36888, 0.53, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_w2aj8"] material = SubResource("StandardMaterial3D_tl8dq") top_radius = 0.0 bottom_radius = 0.575 height = 1.185 radial_segments = 4 [node name="Lod10Example" type="Node3D"] [node name="Node3D" type="Node3D" parent="."] [node name="MeshInstance3D_LOD0" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("SphereMesh_u4ac2") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD1" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("TorusMesh_tjrnq") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD2" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("BoxMesh_xyuxq") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD3" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.118133, 0) mesh = SubResource("PrismMesh_xnm4h") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD4" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0) mesh = SubResource("CylinderMesh_i1yhp") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD5" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("CylinderMesh_bvmel") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD6" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("CylinderMesh_x8oip") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD7" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0) mesh = SubResource("CylinderMesh_dp7xj") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD8" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("CylinderMesh_8ctts") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD9" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.126, 0) mesh = SubResource("CylinderMesh_w2aj8") skeleton = NodePath("../..") ================================================ FILE: project/demo/assets/models/LOD5Example.tscn ================================================ [gd_scene load_steps=11 format=3 uid="uid://bn5nf4esciwex"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"] albedo_color = Color(1, 0, 0, 1) [sub_resource type="SphereMesh" id="SphereMesh_u4ac2"] material = SubResource("StandardMaterial3D_788j8") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3i52q"] albedo_color = Color(1, 0.540167, 0.11, 1) [sub_resource type="BoxMesh" id="BoxMesh_xyuxq"] material = SubResource("StandardMaterial3D_3i52q") size = Vector3(0.85, 0.85, 0.85) [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p0ha4"] albedo_color = Color(0.48, 1, 0.497333, 1) [sub_resource type="PrismMesh" id="PrismMesh_xnm4h"] material = SubResource("StandardMaterial3D_p0ha4") size = Vector3(1, 1, 0.855) [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4koei"] albedo_color = Color(0.45, 0.596667, 1, 1) [sub_resource type="CylinderMesh" id="CylinderMesh_dp7xj"] material = SubResource("StandardMaterial3D_4koei") top_radius = 0.0 bottom_radius = 0.34 height = 1.54 radial_segments = 4 [sub_resource type="QuadMesh" id="QuadMesh_264m5"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xl2nj"] cull_mode = 2 albedo_color = Color(0.6205, 0.31, 1, 1) [node name="Lod5Example" type="Node3D"] [node name="Node3D" type="Node3D" parent="."] [node name="MeshInstance3D_LOD0" type="MeshInstance3D" parent="Node3D"] mesh = SubResource("SphereMesh_u4ac2") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD1" type="MeshInstance3D" parent="Node3D"] visible = false mesh = SubResource("BoxMesh_xyuxq") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD2" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.118133, 0) visible = false mesh = SubResource("PrismMesh_xnm4h") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD3" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0) visible = false mesh = SubResource("CylinderMesh_dp7xj") skeleton = NodePath("../..") [node name="MeshInstance3D_LOD4" type="MeshInstance3D" parent="Node3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0) visible = false mesh = SubResource("QuadMesh_264m5") skeleton = NodePath("../..") surface_material_override/0 = SubResource("StandardMaterial3D_xl2nj") ================================================ FILE: project/demo/assets/models/RockA.glb.import ================================================ [remap] importer="scene" importer_version=1 type="PackedScene" uid="uid://bxwiqxgwoh630" path="res://.godot/imported/RockA.glb-2e416adf16cd89c5c5afc8e204027147.scn" [deps] source_file="res://demo/assets/models/RockA.glb" dest_files=["res://.godot/imported/RockA.glb-2e416adf16cd89c5c5afc8e204027147.scn"] [params] nodes/root_type="StaticBody3D" nodes/root_name="Scene Root" nodes/apply_root_scale=true nodes/root_scale=1.0 nodes/import_as_skeleton_bones=false nodes/use_node_type_suffixes=true meshes/ensure_tangents=true meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 animation/trimming=false animation/remove_immutable_tracks=true animation/import_rest_as_RESET=false import_script/path="" _subresources={ "materials": { "@MATERIAL:0": { "use_external/enabled": true, "use_external/path": "res://demo/assets/materials/M_rock30.tres" } } } gltf/naming_version=0 gltf/embedded_image_handling=1 ================================================ FILE: project/demo/assets/models/RockA.tscn ================================================ [gd_scene load_steps=4 format=3 uid="uid://be6nrf0b8j4l0"] [ext_resource type="PackedScene" uid="uid://bxwiqxgwoh630" path="res://demo/assets/models/RockA.glb" id="1_nu743"] [ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_pmd5x"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_2s34t"] data = PackedVector3Array(0.8507, -2.8222, -2.0706, 0.5747, -2.1329, -3.01, 0.0002, -3.2065, -2.7359, 2.2873, -1.7649, -0.5811, 2.4378, -0.9615, -0.323, 2.038, -0.9707, -1.2311, -0.6382, -2.68, -2.9376, -1.1067, -1.2731, -2.7048, -1.5545, -2.3633, -2.567, -0.6382, -2.68, -2.9376, -0.2162, -1.825, -3.2439, -1.1067, -1.2731, -2.7048, 0.0002, -3.2065, -2.7359, 0.0411, -3.76, -1.9518, 0.8507, -2.8222, -2.0706, 2.425, -2.0284, 0.49, 2.4024, -1.5988, 1.5279, 2.5851, -1.2056, 0.7305, 0.0831, -2.9699, 1.939, -0.0859, -2.2445, 2.6136, 0.748, -2.001, 2.5127, -2.448, -1.0454, -1.0528, -2.5272, 0.3979, -0.8764, -2.399, -0.7585, 0.2313, -1.5104, 0.6775, -2.512, -1.3227, 1.7291, -2.1514, -2.0387, 1.5996, -2.2267, 0.1427, 2.3057, -2.4478, -0.4979, 2.1368, -2.1727, -0.4143, 1.2902, -3.2951, 0.1351, -3.9012, -0.5924, 0.2299, -3.4688, 0.9248, 0.5827, -3.159, 0.0985, -1.6255, -3.1162, -1.4188, -2.0226, -2.1644, -0.379, -1.2838, -3.0035, 0.2928, -1.1067, -1.2731, -2.7048, -1.5104, 0.6775, -2.512, -1.9542, -0.4605, -2.5215, 0.748, -2.001, 2.5127, 1.4775, -1.3103, 2.8259, 1.9116, -1.7142, 2.341, 1.8789, 1.1592, -0.8303, 1.3001, 2.6786, -0.7585, 0.8831, 2.5385, -1.8483, 0.5951, -0.2972, 3.54, 1.1272, 0.2617, 2.9365, 1.3352, -0.2905, 3.0366, 0.5951, -0.2972, 3.54, 0.7324, 0.4498, 3.1493, 1.1272, 0.2617, 2.9365, -1.3976, 0.6213, 1.8313, -0.8149, 0.6856, 2.232, -1.0617, -0.1771, 2.0331, -0.7449, 2.9351, -0.0395, -0.5981, 2.6867, 0.9116, -1.33, 2.7367, 0.359, 0.9156, 3.0784, 0.1405, 0.1518, 3.0337, 0.6723, 0.0726, 3.4231, -0.5764, 0.7064, 1.456, 2.7883, 1.3708, 1.9744, 2.321, 1.1174, 1.3846, 2.616, 0.7064, 1.456, 2.7883, 0.5198, 2.1281, 2.596, 1.3708, 1.9744, 2.321, -1.6255, -3.1162, -1.4188, -1.2838, -3.0035, 0.2928, -0.6766, -3.9798, -0.6577, 2.4717, 0.9465, 0.6202, 2.4515, -0.0586, -0.1403, 2.6117, 0.0377, 0.454, 2.4515, -0.0586, -0.1403, 2.1036, 1.6103, 0.0759, 1.8789, 1.1592, -0.8303, 2.4515, -0.0586, -0.1403, 2.4717, 0.9465, 0.6202, 2.1036, 1.6103, 0.0759, 1.0451, 2.7721, 1.514, 0.2361, 2.7435, 1.8554, 0.1518, 3.0337, 0.6723, 1.0451, 2.7721, 1.514, 0.1518, 3.0337, 0.6723, 0.9156, 3.0784, 0.1405, 0.1427, 2.3057, -2.4478, -0.1394, 3.1268, -1.5759, -0.4979, 2.1368, -2.1727, 0.1518, 3.0337, 0.6723, 0.2361, 2.7435, 1.8554, -0.5981, 2.6867, 0.9116, 0.1518, 3.0337, 0.6723, -0.5981, 2.6867, 0.9116, -0.7449, 2.9351, -0.0395, -2.4827, 1.6372, -1.6179, -2.4412, 1.9847, -0.6202, -2.5272, 0.3979, -0.8764, -0.7996, 2.0136, 1.7125, -0.1737, 2.1372, 2.2437, -0.5194, 1.6162, 2.2076, -0.7996, 2.0136, 1.7125, -0.5194, 1.6162, 2.2076, -1.313, 1.5855, 1.6731, -1.6866, -0.7727, 1.7598, -1.0617, -0.1771, 2.0331, -1.0177, -1.42, 2.2188, 0.2988, 1.5533, 2.7449, 0.5198, 2.1281, 2.596, 0.7064, 1.456, 2.7883, 0.3266, 0.5073, 3.1476, 0.7324, 0.4498, 3.1493, 0.5951, -0.2972, 3.54, 1.8789, 1.1592, -0.8303, 2.1036, 1.6103, 0.0759, 1.3001, 2.6786, -0.7585, 2.1338, 1.9216, 1.0603, 1.8537, 2.1593, 1.6508, 1.6716, 2.5645, 0.6123, -0.0859, -2.2445, 2.6136, 0.5411, -1.3085, 3.348, 0.748, -2.001, 2.5127, -0.4979, 2.1368, -2.1727, -0.1394, 3.1268, -1.5759, -0.6639, 2.8841, -1.1739, 0.0726, 3.4231, -0.5764, 0.1518, 3.0337, 0.6723, -0.7449, 2.9351, -0.0395, 1.1174, 1.3846, 2.616, 1.9058, 0.4561, 2.6704, 1.3631, 0.7103, 2.7557, 1.1174, 1.3846, 2.616, 1.3708, 1.9744, 2.321, 1.9058, 0.4561, 2.6704, -2.2917, 0.7985, 0.4167, -2.1878, 1.682, 0.5464, -1.9453, 1.0221, 1.1785, -1.533, 2.1485, 1.1174, -0.7996, 2.0136, 1.7125, -1.313, 1.5855, 1.6731, -1.8821, -1.8796, 1.075, -0.8935, -2.6081, 1.7932, -1.2838, -3.0035, 0.2928, -0.3641, -1.322, 2.8122, -0.3719, -0.4418, 2.6845, 0.0592, -0.6578, 3.3401, -0.3719, -0.4418, 2.6845, -0.2183, 0.1183, 2.8781, 0.0592, -0.6578, 3.3401, -2.2917, 0.7985, 0.4167, -1.9453, 1.0221, 1.1785, -2.1903, -0.0178, 1.0505, 1.9116, -1.7142, 2.341, 1.4775, -1.3103, 2.8259, 2.1165, -0.7174, 2.6008, 1.3352, -0.2905, 3.0366, 1.3631, 0.7103, 2.7557, 1.9058, 0.4561, 2.6704, 1.3352, -0.2905, 3.0366, 1.1272, 0.2617, 2.9365, 1.3631, 0.7103, 2.7557, 0.8304, 0.2995, -2.6326, 1.3709, 0.7135, -1.8767, 0.6753, 1.5794, -2.5902, 2.4418, 0.2925, 1.9718, 2.1338, 1.9216, 1.0603, 2.4414, 1.1989, 1.2948, 2.4418, 0.2925, 1.9718, 1.8537, 2.1593, 1.6508, 2.1338, 1.9216, 1.0603, 0.8831, 2.5385, -1.8483, 1.3001, 2.6786, -0.7585, 0.5065, 3.2659, -1.3942, 2.4024, -1.5988, 1.5279, 2.6677, -0.3337, 1.0018, 2.5851, -1.2056, 0.7305, 2.4024, -1.5988, 1.5279, 2.4418, 0.2925, 1.9718, 2.6677, -0.3337, 1.0018, -1.5104, 0.6775, -2.512, -2.0387, 1.5996, -2.2267, -1.9542, -0.4605, -2.5215, -2.0037, 2.5218, -1.5743, -1.3876, 2.8143, -0.9703, -1.9473, 2.7594, -0.6554, 0.8304, 0.2995, -2.6326, 0.6753, 1.5794, -2.5902, 0.0953, 0.6217, -3.4674, -2.0226, -2.1644, -0.379, -1.8821, -1.8796, 1.075, -1.2838, -3.0035, 0.2928, -1.6866, -0.7727, 1.7598, -1.3976, 0.6213, 1.8313, -1.0617, -0.1771, 2.0331, -1.2743, 2.358, -1.6626, -1.3876, 2.8143, -0.9703, -2.0037, 2.5218, -1.5743, 0.2299, -3.4688, 0.9248, 0.6312, -2.7374, 1.3685, 0.5827, -3.159, 0.0985, 0.748, -2.001, 2.5127, 0.5411, -1.3085, 3.348, 1.4775, -1.3103, 2.8259, 1.3964, -0.6655, -2.1748, 0.8304, 0.2995, -2.6326, 0.5493, -0.7511, -3.2244, 1.7659, -2.4495, -1.2002, 1.4806, -1.8538, -1.9812, 0.8507, -2.8222, -2.0706, 2.4418, 0.2925, 1.9718, 2.6117, 0.0377, 0.454, 2.6677, -0.3337, 1.0018, 2.6117, 0.0377, 0.454, 2.4414, 1.1989, 1.2948, 2.4717, 0.9465, 0.6202, 2.6117, 0.0377, 0.454, 2.4418, 0.2925, 1.9718, 2.4414, 1.1989, 1.2948, 0.5493, -0.7511, -3.2244, 0.8304, 0.2995, -2.6326, 0.0953, 0.6217, -3.4674, 2.4515, -0.0586, -0.1403, 2.038, -0.9707, -1.2311, 2.4378, -0.9615, -0.323, 2.4515, -0.0586, -0.1403, 1.8789, 1.1592, -0.8303, 2.038, -0.9707, -1.2311, -0.1975, -0.7247, -3.4884, -0.5122, 0.3139, -3.5925, -0.7053, -0.3406, -3.1144, -1.1067, -1.2731, -2.7048, -1.9542, -0.4605, -2.5215, -1.5545, -2.3633, -2.567, -0.4143, 1.2902, -3.2951, -0.4979, 2.1368, -2.1727, -0.8466, 1.1377, -2.7759, -0.7053, -0.3406, -3.1144, -0.5122, 0.3139, -3.5925, -0.9168, 0.4069, -2.985, -2.4827, 1.6372, -1.6179, -2.5272, 0.3979, -0.8764, -2.4716, -0.4239, -1.8891, -2.4716, -0.4239, -1.8891, -2.5272, 0.3979, -0.8764, -2.448, -1.0454, -1.0528, -2.2072, -1.9264, -1.9655, -2.4716, -0.4239, -1.8891, -2.448, -1.0454, -1.0528, 0.1351, -3.9012, -0.5924, 0.5827, -3.159, 0.0985, 0.6881, -3.2916, -0.9673, -1.2838, -3.0035, 0.2928, -0.8935, -2.6081, 1.7932, -0.3984, -3.542, 1.0253, 1.3964, -0.6655, -2.1748, 1.3709, 0.7135, -1.8767, 0.8304, 0.2995, -2.6326, 1.1446, -2.3077, 1.6179, 1.8479, -2.2708, 1.4206, 1.3113, -2.5503, 0.6426, 1.2555, -2.8919, -0.6692, 1.3113, -2.5503, 0.6426, 1.8655, -2.6668, 0.0778, -0.6766, -3.9798, -0.6577, -1.2838, -3.0035, 0.2928, -0.3984, -3.542, 1.0253, 1.4806, -1.8538, -1.9812, 0.5747, -2.1329, -3.01, 0.8507, -2.8222, -2.0706, 1.3113, -2.5503, 0.6426, 1.8479, -2.2708, 1.4206, 1.8655, -2.6668, 0.0778, -0.6836, -3.6027, -2.5845, 0.0002, -3.2065, -2.7359, -0.6382, -2.68, -2.9376, 1.7659, -2.4495, -1.2002, 2.425, -2.0284, 0.49, 2.2873, -1.7649, -0.5811, 1.7659, -2.4495, -1.2002, 1.8655, -2.6668, 0.0778, 2.425, -2.0284, 0.49, 0.0831, -2.9699, 1.939, 0.2299, -3.4688, 0.9248, -0.3984, -3.542, 1.0253, -0.9168, 0.4069, -2.985, -0.8466, 1.1377, -2.7759, -1.5104, 0.6775, -2.512, -1.9473, 2.7594, -0.6554, -2.1878, 1.682, 0.5464, -2.4412, 1.9847, -0.6202, -2.1878, 1.682, 0.5464, -1.33, 2.7367, 0.359, -1.533, 2.1485, 1.1174, -2.1878, 1.682, 0.5464, -1.9473, 2.7594, -0.6554, -1.33, 2.7367, 0.359, 0.0726, 3.4231, -0.5764, -0.1394, 3.1268, -1.5759, 0.5065, 3.2659, -1.3942, 2.1036, 1.6103, 0.0759, 2.4414, 1.1989, 1.2948, 2.1338, 1.9216, 1.0603, 2.1036, 1.6103, 0.0759, 2.4717, 0.9465, 0.6202, 2.4414, 1.1989, 1.2948, 0.2361, 2.7435, 1.8554, 0.5198, 2.1281, 2.596, -0.1737, 2.1372, 2.2437, 0.0411, -3.76, -1.9518, -0.6766, -3.9798, -0.6577, 0.1351, -3.9012, -0.5924, 0.0411, -3.76, -1.9518, -0.6585, -3.9829, -1.9964, -0.6766, -3.9798, -0.6577, 1.2555, -2.8919, -0.6692, 0.8507, -2.8222, -2.0706, 0.6881, -3.2916, -0.9673, 1.4806, -1.8538, -1.9812, 2.038, -0.9707, -1.2311, 1.3964, -0.6655, -2.1748, -0.2162, -1.825, -3.2439, 0.5493, -0.7511, -3.2244, -0.1975, -0.7247, -3.4884, -0.2162, -1.825, -3.2439, 0.5747, -2.1329, -3.01, 0.5493, -0.7511, -3.2244, -1.1679, -3.5644, -2.3336, -2.2072, -1.9264, -1.9655, -1.6255, -3.1162, -1.4188, -1.1679, -3.5644, -2.3336, -1.5545, -2.3633, -2.567, -2.2072, -1.9264, -1.9655, -0.4143, 1.2902, -3.2951, -0.5122, 0.3139, -3.5925, 0.0953, 0.6217, -3.4674, 2.6677, -0.3337, 1.0018, 2.4378, -0.9615, -0.323, 2.5851, -1.2056, 0.7305, 2.4378, -0.9615, -0.323, 2.6117, 0.0377, 0.454, 2.4515, -0.0586, -0.1403, 2.4378, -0.9615, -0.323, 2.6677, -0.3337, 1.0018, 2.6117, 0.0377, 0.454, 0.6312, -2.7374, 1.3685, 0.748, -2.001, 2.5127, 1.1446, -2.3077, 1.6179, 2.4024, -1.5988, 1.5279, 1.8479, -2.2708, 1.4206, 1.9116, -1.7142, 2.341, -1.8821, -1.8796, 1.075, -2.1903, -0.0178, 1.0505, -1.6866, -0.7727, 1.7598, -1.8821, -1.8796, 1.075, -2.399, -0.7585, 0.2313, -2.1903, -0.0178, 1.0505, -1.0177, -1.42, 2.2188, -0.0859, -2.2445, 2.6136, -0.8935, -2.6081, 1.7932, -1.0177, -1.42, 2.2188, -0.3641, -1.322, 2.8122, -0.0859, -2.2445, 2.6136, -2.0037, 2.5218, -1.5743, -2.4827, 1.6372, -1.6179, -2.0387, 1.5996, -2.2267, 0.8831, 2.5385, -1.8483, 0.1427, 2.3057, -2.4478, 0.6753, 1.5794, -2.5902, -1.3227, 1.7291, -2.1514, -0.4979, 2.1368, -2.1727, -1.2743, 2.358, -1.6626, 2.1165, -0.7174, 2.6008, 1.9058, 0.4561, 2.6704, 2.4418, 0.2925, 1.9718, 0.5951, -0.2972, 3.54, 0.5411, -1.3085, 3.348, 0.0592, -0.6578, 3.3401, -1.9453, 1.0221, 1.1785, -1.313, 1.5855, 1.6731, -1.3976, 0.6213, 1.8313, -0.6639, 2.8841, -1.1739, -0.7449, 2.9351, -0.0395, -1.3876, 2.8143, -0.9703, 1.3001, 2.6786, -0.7585, 1.6716, 2.5645, 0.6123, 0.9156, 3.0784, 0.1405, 1.1174, 1.3846, 2.616, 0.7324, 0.4498, 3.1493, 0.7064, 1.456, 2.7883, 0.7324, 0.4498, 3.1493, 1.3631, 0.7103, 2.7557, 1.1272, 0.2617, 2.9365, 0.7324, 0.4498, 3.1493, 1.1174, 1.3846, 2.616, 1.3631, 0.7103, 2.7557, 1.3708, 1.9744, 2.321, 1.0451, 2.7721, 1.514, 1.8537, 2.1593, 1.6508, -0.8149, 0.6856, 2.232, 0.3266, 0.5073, 3.1476, -0.2183, 0.1183, 2.8781, 0.3266, 0.5073, 3.1476, -0.5194, 1.6162, 2.2076, 0.2988, 1.5533, 2.7449, 0.3266, 0.5073, 3.1476, -0.8149, 0.6856, 2.232, -0.5194, 1.6162, 2.2076, -0.6585, -3.9829, -1.9964, 0.0002, -3.2065, -2.7359, -0.6836, -3.6027, -2.5845, -0.6585, -3.9829, -1.9964, 0.0411, -3.76, -1.9518, 0.0002, -3.2065, -2.7359, 0.8507, -2.8222, -2.0706, 1.8655, -2.6668, 0.0778, 1.7659, -2.4495, -1.2002, 0.8507, -2.8222, -2.0706, 1.2555, -2.8919, -0.6692, 1.8655, -2.6668, 0.0778, 2.038, -0.9707, -1.2311, 1.7659, -2.4495, -1.2002, 2.2873, -1.7649, -0.5811, 2.038, -0.9707, -1.2311, 1.4806, -1.8538, -1.9812, 1.7659, -2.4495, -1.2002, -1.5545, -2.3633, -2.567, -0.6836, -3.6027, -2.5845, -0.6382, -2.68, -2.9376, -1.5545, -2.3633, -2.567, -1.1679, -3.5644, -2.3336, -0.6836, -3.6027, -2.5845, 2.5851, -1.2056, 0.7305, 2.2873, -1.7649, -0.5811, 2.425, -2.0284, 0.49, 2.5851, -1.2056, 0.7305, 2.4378, -0.9615, -0.323, 2.2873, -1.7649, -0.5811, 0.748, -2.001, 2.5127, 0.2299, -3.4688, 0.9248, 0.0831, -2.9699, 1.939, 0.748, -2.001, 2.5127, 0.6312, -2.7374, 1.3685, 0.2299, -3.4688, 0.9248, -2.399, -0.7585, 0.2313, -2.0226, -2.1644, -0.379, -2.448, -1.0454, -1.0528, -2.399, -0.7585, 0.2313, -1.8821, -1.8796, 1.075, -2.0226, -2.1644, -0.379, -0.3641, -1.322, 2.8122, -1.0617, -0.1771, 2.0331, -0.3719, -0.4418, 2.6845, -0.3641, -1.322, 2.8122, -1.0177, -1.42, 2.2188, -1.0617, -0.1771, 2.0331, -2.2917, 0.7985, 0.4167, -2.4412, 1.9847, -0.6202, -2.1878, 1.682, 0.5464, -2.2917, 0.7985, 0.4167, -2.5272, 0.3979, -0.8764, -2.4412, 1.9847, -0.6202, 1.9058, 0.4561, 2.6704, 1.4775, -1.3103, 2.8259, 1.3352, -0.2905, 3.0366, 1.9058, 0.4561, 2.6704, 2.1165, -0.7174, 2.6008, 1.4775, -1.3103, 2.8259, -1.313, 1.5855, 1.6731, -2.1878, 1.682, 0.5464, -1.533, 2.1485, 1.1174, -1.313, 1.5855, 1.6731, -1.9453, 1.0221, 1.1785, -2.1878, 1.682, 0.5464, -0.7449, 2.9351, -0.0395, -0.1394, 3.1268, -1.5759, 0.0726, 3.4231, -0.5764, -0.7449, 2.9351, -0.0395, -0.6639, 2.8841, -1.1739, -0.1394, 3.1268, -1.5759, 1.6716, 2.5645, 0.6123, 2.1036, 1.6103, 0.0759, 2.1338, 1.9216, 1.0603, 1.6716, 2.5645, 0.6123, 1.3001, 2.6786, -0.7585, 2.1036, 1.6103, 0.0759, 1.0451, 2.7721, 1.514, 0.5198, 2.1281, 2.596, 0.2361, 2.7435, 1.8554, 1.0451, 2.7721, 1.514, 1.3708, 1.9744, 2.321, 0.5198, 2.1281, 2.596, -0.8149, 0.6856, 2.232, -0.3719, -0.4418, 2.6845, -1.0617, -0.1771, 2.0331, -0.8149, 0.6856, 2.232, -0.2183, 0.1183, 2.8781, -0.3719, -0.4418, 2.6845, -0.5981, 2.6867, 0.9116, -1.533, 2.1485, 1.1174, -1.33, 2.7367, 0.359, -0.5981, 2.6867, 0.9116, -0.7996, 2.0136, 1.7125, -1.533, 2.1485, 1.1174, -0.3984, -3.542, 1.0253, 0.1351, -3.9012, -0.5924, -0.6766, -3.9798, -0.6577, -0.3984, -3.542, 1.0253, 0.2299, -3.4688, 0.9248, 0.1351, -3.9012, -0.5924, -0.6382, -2.68, -2.9376, 0.5747, -2.1329, -3.01, -0.2162, -1.825, -3.2439, -0.6382, -2.68, -2.9376, 0.0002, -3.2065, -2.7359, 0.5747, -2.1329, -3.01, -2.448, -1.0454, -1.0528, -1.6255, -3.1162, -1.4188, -2.2072, -1.9264, -1.9655, -2.448, -1.0454, -1.0528, -2.0226, -2.1644, -0.379, -1.6255, -3.1162, -1.4188, -0.9168, 0.4069, -2.985, -1.1067, -1.2731, -2.7048, -0.7053, -0.3406, -3.1144, -0.9168, 0.4069, -2.985, -1.5104, 0.6775, -2.512, -1.1067, -1.2731, -2.7048, -0.8466, 1.1377, -2.7759, -0.5122, 0.3139, -3.5925, -0.4143, 1.2902, -3.2951, -0.8466, 1.1377, -2.7759, -0.9168, 0.4069, -2.985, -0.5122, 0.3139, -3.5925, 2.425, -2.0284, 0.49, 1.8479, -2.2708, 1.4206, 2.4024, -1.5988, 1.5279, 2.425, -2.0284, 0.49, 1.8655, -2.6668, 0.0778, 1.8479, -2.2708, 1.4206, 0.0831, -2.9699, 1.939, -0.8935, -2.6081, 1.7932, -0.0859, -2.2445, 2.6136, 0.0831, -2.9699, 1.939, -0.3984, -3.542, 1.0253, -0.8935, -2.6081, 1.7932, -1.9473, 2.7594, -0.6554, -2.4827, 1.6372, -1.6179, -2.0037, 2.5218, -1.5743, -1.9473, 2.7594, -0.6554, -2.4412, 1.9847, -0.6202, -2.4827, 1.6372, -1.6179, 0.5065, 3.2659, -1.3942, 0.1427, 2.3057, -2.4478, 0.8831, 2.5385, -1.8483, 0.5065, 3.2659, -1.3942, -0.1394, 3.1268, -1.5759, 0.1427, 2.3057, -2.4478, -1.5104, 0.6775, -2.512, -0.4979, 2.1368, -2.1727, -1.3227, 1.7291, -2.1514, -1.5104, 0.6775, -2.512, -0.8466, 1.1377, -2.7759, -0.4979, 2.1368, -2.1727, 1.3352, -0.2905, 3.0366, 0.5411, -1.3085, 3.348, 0.5951, -0.2972, 3.54, 1.3352, -0.2905, 3.0366, 1.4775, -1.3103, 2.8259, 0.5411, -1.3085, 3.348, -1.33, 2.7367, 0.359, -1.3876, 2.8143, -0.9703, -0.7449, 2.9351, -0.0395, -1.33, 2.7367, 0.359, -1.9473, 2.7594, -0.6554, -1.3876, 2.8143, -0.9703, 0.0726, 3.4231, -0.5764, 1.3001, 2.6786, -0.7585, 0.9156, 3.0784, 0.1405, 0.0726, 3.4231, -0.5764, 0.5065, 3.2659, -1.3942, 1.3001, 2.6786, -0.7585, -0.1737, 2.1372, 2.2437, 0.2988, 1.5533, 2.7449, -0.5194, 1.6162, 2.2076, -0.1737, 2.1372, 2.2437, 0.5198, 2.1281, 2.596, 0.2988, 1.5533, 2.7449, 0.2361, 2.7435, 1.8554, -0.7996, 2.0136, 1.7125, -0.5981, 2.6867, 0.9116, 0.2361, 2.7435, 1.8554, -0.1737, 2.1372, 2.2437, -0.7996, 2.0136, 1.7125, 1.8537, 2.1593, 1.6508, 0.9156, 3.0784, 0.1405, 1.6716, 2.5645, 0.6123, 1.8537, 2.1593, 1.6508, 1.0451, 2.7721, 1.514, 0.9156, 3.0784, 0.1405, -0.5194, 1.6162, 2.2076, -1.3976, 0.6213, 1.8313, -1.313, 1.5855, 1.6731, -0.5194, 1.6162, 2.2076, -0.8149, 0.6856, 2.232, -1.3976, 0.6213, 1.8313, 0.3266, 0.5073, 3.1476, 0.7064, 1.456, 2.7883, 0.7324, 0.4498, 3.1493, 0.3266, 0.5073, 3.1476, 0.2988, 1.5533, 2.7449, 0.7064, 1.456, 2.7883, -0.2183, 0.1183, 2.8781, 0.5951, -0.2972, 3.54, 0.0592, -0.6578, 3.3401, -0.2183, 0.1183, 2.8781, 0.3266, 0.5073, 3.1476, 0.5951, -0.2972, 3.54, 1.3708, 1.9744, 2.321, 2.4418, 0.2925, 1.9718, 1.9058, 0.4561, 2.6704, 1.3708, 1.9744, 2.321, 1.8537, 2.1593, 1.6508, 2.4418, 0.2925, 1.9718, 1.3709, 0.7135, -1.8767, 0.8831, 2.5385, -1.8483, 0.6753, 1.5794, -2.5902, 1.3709, 0.7135, -1.8767, 1.8789, 1.1592, -0.8303, 0.8831, 2.5385, -1.8483, -1.2743, 2.358, -1.6626, -0.6639, 2.8841, -1.1739, -1.3876, 2.8143, -0.9703, -1.2743, 2.358, -1.6626, -0.4979, 2.1368, -2.1727, -0.6639, 2.8841, -1.1739, -1.3227, 1.7291, -2.1514, -2.0037, 2.5218, -1.5743, -2.0387, 1.5996, -2.2267, -1.3227, 1.7291, -2.1514, -1.2743, 2.358, -1.6626, -2.0037, 2.5218, -1.5743, -2.5272, 0.3979, -0.8764, -2.1903, -0.0178, 1.0505, -2.399, -0.7585, 0.2313, -2.5272, 0.3979, -0.8764, -2.2917, 0.7985, 0.4167, -2.1903, -0.0178, 1.0505, -1.9453, 1.0221, 1.1785, -1.6866, -0.7727, 1.7598, -2.1903, -0.0178, 1.0505, -1.9453, 1.0221, 1.1785, -1.3976, 0.6213, 1.8313, -1.6866, -0.7727, 1.7598, -0.0859, -2.2445, 2.6136, 0.0592, -0.6578, 3.3401, 0.5411, -1.3085, 3.348, -0.0859, -2.2445, 2.6136, -0.3641, -1.322, 2.8122, 0.0592, -0.6578, 3.3401, 2.4024, -1.5988, 1.5279, 2.1165, -0.7174, 2.6008, 2.4418, 0.2925, 1.9718, 2.4024, -1.5988, 1.5279, 1.9116, -1.7142, 2.341, 2.1165, -0.7174, 2.6008, 0.6753, 1.5794, -2.5902, -0.4143, 1.2902, -3.2951, 0.0953, 0.6217, -3.4674, 0.6753, 1.5794, -2.5902, 0.1427, 2.3057, -2.4478, -0.4143, 1.2902, -3.2951, -2.0387, 1.5996, -2.2267, -2.4716, -0.4239, -1.8891, -1.9542, -0.4605, -2.5215, -2.0387, 1.5996, -2.2267, -2.4827, 1.6372, -1.6179, -2.4716, -0.4239, -1.8891, -1.8821, -1.8796, 1.075, -1.0177, -1.42, 2.2188, -0.8935, -2.6081, 1.7932, -1.8821, -1.8796, 1.075, -1.6866, -0.7727, 1.7598, -1.0177, -1.42, 2.2188, 1.1446, -2.3077, 1.6179, 1.9116, -1.7142, 2.341, 1.8479, -2.2708, 1.4206, 1.1446, -2.3077, 1.6179, 0.748, -2.001, 2.5127, 1.9116, -1.7142, 2.341, 0.6312, -2.7374, 1.3685, 1.3113, -2.5503, 0.6426, 0.5827, -3.159, 0.0985, 0.6312, -2.7374, 1.3685, 1.1446, -2.3077, 1.6179, 1.3113, -2.5503, 0.6426, 1.8789, 1.1592, -0.8303, 1.3964, -0.6655, -2.1748, 2.038, -0.9707, -1.2311, 1.8789, 1.1592, -0.8303, 1.3709, 0.7135, -1.8767, 1.3964, -0.6655, -2.1748, -0.1975, -0.7247, -3.4884, 0.0953, 0.6217, -3.4674, -0.5122, 0.3139, -3.5925, -0.1975, -0.7247, -3.4884, 0.5493, -0.7511, -3.2244, 0.0953, 0.6217, -3.4674, -0.2162, -1.825, -3.2439, -0.7053, -0.3406, -3.1144, -1.1067, -1.2731, -2.7048, -0.2162, -1.825, -3.2439, -0.1975, -0.7247, -3.4884, -0.7053, -0.3406, -3.1144, -1.9542, -0.4605, -2.5215, -2.2072, -1.9264, -1.9655, -1.5545, -2.3633, -2.567, -1.9542, -0.4605, -2.5215, -2.4716, -0.4239, -1.8891, -2.2072, -1.9264, -1.9655, -1.1679, -3.5644, -2.3336, -0.6766, -3.9798, -0.6577, -0.6585, -3.9829, -1.9964, -1.1679, -3.5644, -2.3336, -1.6255, -3.1162, -1.4188, -0.6766, -3.9798, -0.6577, 1.4806, -1.8538, -1.9812, 0.5493, -0.7511, -3.2244, 0.5747, -2.1329, -3.01, 1.4806, -1.8538, -1.9812, 1.3964, -0.6655, -2.1748, 0.5493, -0.7511, -3.2244, 0.0411, -3.76, -1.9518, 0.6881, -3.2916, -0.9673, 0.8507, -2.8222, -2.0706, 0.0411, -3.76, -1.9518, 0.1351, -3.9012, -0.5924, 0.6881, -3.2916, -0.9673, 0.5827, -3.159, 0.0985, 1.2555, -2.8919, -0.6692, 0.6881, -3.2916, -0.9673, 0.5827, -3.159, 0.0985, 1.3113, -2.5503, 0.6426, 1.2555, -2.8919, -0.6692, -0.6836, -3.6027, -2.5845, -1.1679, -3.5644, -2.3336, -0.6585, -3.9829, -1.9964) [node name="RockA" instance=ExtResource("1_nu743")] collision_mask = 3 [node name="Rock1" parent="." index="0"] surface_material_override/0 = ExtResource("2_pmd5x") [node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"] shape = SubResource("ConcavePolygonShape3D_2s34t") ================================================ FILE: project/demo/assets/models/RockB.glb.import ================================================ [remap] importer="scene" importer_version=1 type="PackedScene" uid="uid://nta3sef6c2el" path="res://.godot/imported/RockB.glb-d0df90244ab14da61106a961f4faa07f.scn" [deps] source_file="res://demo/assets/models/RockB.glb" dest_files=["res://.godot/imported/RockB.glb-d0df90244ab14da61106a961f4faa07f.scn"] [params] nodes/root_type="StaticBody3D" nodes/root_name="Scene Root" nodes/apply_root_scale=true nodes/root_scale=1.0 nodes/import_as_skeleton_bones=false nodes/use_node_type_suffixes=true meshes/ensure_tangents=true meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 animation/trimming=false animation/remove_immutable_tracks=true animation/import_rest_as_RESET=false import_script/path="" _subresources={ "materials": { "@MATERIAL:0": { "use_external/enabled": true, "use_external/path": "res://demo/assets/materials/M_rock30.tres" } } } gltf/naming_version=0 gltf/embedded_image_handling=1 ================================================ FILE: project/demo/assets/models/RockB.tscn ================================================ [gd_scene load_steps=4 format=3 uid="uid://bwvtgwartxt0g"] [ext_resource type="PackedScene" uid="uid://nta3sef6c2el" path="res://demo/assets/models/RockB.glb" id="1_2nhli"] [ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_dbm2k"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_wfrmp"] data = PackedVector3Array(0.4584, -3.889, 0.8266, 0.6021, -4.1961, -0.0392, 0.169, -4.9369, 0.2441, 2.1498, -1.9824, 1.4097, 2.1074, -0.9551, 0.9434, 2.3387, -2.1373, 0.6494, -0.4293, -5.0774, 0.0003, -0.5108, -3.8408, -1.1476, -1.1208, -4.5065, 0.1953, 0.0671, -5.0216, -0.4515, 0.4756, -4.5162, -0.6549, 0.1398, -4.5397, -0.9338, 0.169, -4.9369, 0.2441, -0.0922, -4.5871, 1.3087, 0.4584, -3.889, 0.8266, 2.1498, -1.9824, 1.4097, 1.9393, -0.8033, 1.6608, 2.1074, -0.9551, 0.9434, -1.2288, -2.368, 2.4204, -1.4963, -0.9921, 2.5267, -0.7814, -0.8418, 2.7775, -2.6648, -1.8501, 0.307, -2.907, 0.041, -0.3088, -2.5202, -0.0318, 0.8043, -1.0077, -1.3716, -2.1657, -0.7692, -0.6389, -2.6654, -1.6842, -0.4729, -2.3712, 2.1195, -1.771, -1.5705, 2.5987, -0.8743, -1.1872, 1.9584, -0.8816, -2.0923, -0.2829, -3.5923, 2.1108, -0.2939, -2.4569, 2.4482, 0.5442, -2.0915, 2.2664, -1.1208, -4.5065, 0.1953, -2.179, -3.4152, 0.4259, -1.8849, -3.8097, 1.1593, -0.5108, -3.8408, -1.1476, -0.2695, -2.508, -2.0432, -1.1054, -2.8992, -1.0824, 2.1726, -2.8941, -0.5813, 1.87, -2.8735, -1.1419, 1.5863, -3.2514, -0.7541, 0.7315, 0.8274, 2.7196, 1.1136, 1.849, 2.4998, 1.409, 0.9631, 2.386, -0.4271, 2.5556, 2.4897, 0.5261, 4.2194, 1.9538, 0.5038, 3.0339, 2.4087, -1.9764, 3.1849, 0.2685, -1.0116, 3.5059, 0.8103, -1.5979, 2.7577, 1.2075, -0.9393, 2.872, -1.8539, -1.2485, 3.6723, -1.0264, -1.7216, 2.2099, -1.5001, 1.1827, 2.8613, -1.4341, 0.9077, 3.2903, -1.4616, 0.9177, 2.8679, -1.5387, 0.8594, 5.2909, 1.0912, 0.6378, 5.6752, 0.4017, 1.3249, 5.3073, 0.5102, -1.1208, -4.5065, 0.1953, -1.8849, -3.8097, 1.1593, -0.8729, -4.7982, 1.121, 2.4617, 0.7259, 0.0288, 2.5473, 1.387, -0.0726, 2.6196, 0.7185, -0.2944, 0.9445, 5.5277, -0.3666, 0.5746, 5.6649, -0.3989, 0.6884, 5.4332, -0.7011, 1.7744, 4.3299, -0.4393, 1.3226, 4.3217, -0.9655, 2.0312, 2.7158, -1.0135, 0.9877, 0.4313, -2.4389, 0.7444, 0.8429, -2.2856, 0.635, 0.4247, -2.5749, -0.1392, 4.5027, -1.2571, -0.2125, 5.045, -0.6732, -1.2485, 3.6723, -1.0264, -0.1392, 4.5027, -1.2571, -1.2485, 3.6723, -1.0264, -0.9393, 2.872, -1.8539, -1.7965, 0.899, -2.2295, -1.7216, 2.2099, -1.5001, -2.3834, 0.7782, -1.5631, -0.791, 4.3118, -0.0929, -0.2232, 5.1794, 0.1938, -0.5404, 4.4098, 0.4328, -1.3695, 3.5538, -0.0189, -1.0116, 3.5059, 0.8103, -1.9764, 3.1849, 0.2685, -2.5202, -0.0318, 0.8043, -2.1461, 1.5782, 1.2673, -2.0758, -0.0137, 1.8949, -0.5404, 4.4098, 0.4328, -0.2232, 5.1794, 0.1938, -0.0735, 4.938, 0.8308, -1.0116, 3.5059, 0.8103, -0.2982, 4.0929, 1.4648, -0.878, 3.135, 1.7811, 2.6003, 1.0473, -1.0133, 2.4031, 2.6779, -0.4122, 2.0312, 2.7158, -1.0135, 2.4031, 2.6779, -0.4122, 1.7744, 4.3299, -0.4393, 2.0312, 2.7158, -1.0135, -1.0159, 0.9756, 2.6776, -0.4271, 2.5556, 2.4897, -0.2195, 1.1266, 2.8293, -0.0823, 1.3076, -2.4751, 0.0456, 1.662, -2.2329, -0.2095, 1.8203, -2.3278, 0.1709, 2.9305, -1.8154, 0.1823, 3.4347, -1.7089, -0.0988, 3.0024, -1.9036, -2.907, 0.041, -0.3088, -2.575, 1.9843, 0.2578, -2.5202, -0.0318, 0.8043, -2.5502, 1.5321, -0.8246, -1.9974, 2.3952, -0.8364, -2.286, 2.7057, -0.3882, -1.9974, 2.3952, -0.8364, -1.7496, 3.0465, -0.6307, -2.286, 2.7057, -0.3882, 0.8594, 5.2909, 1.0912, 1.3249, 5.3073, 0.5102, 1.6148, 4.6612, 1.1746, -1.6176, 1.4128, 2.2491, -1.7058, 2.0492, 1.8707, -1.3001, 2.2705, 2.1959, -1.5979, 2.7577, 1.2075, -1.0116, 3.5059, 0.8103, -0.878, 3.135, 1.7811, -0.7261, 0.8081, -2.8158, -0.8202, 1.5078, -2.6285, -1.246, 0.719, -2.7492, 1.409, 0.9631, 2.386, 1.1136, 1.849, 2.4998, 1.6219, 2.267, 2.1861, 0.5038, 3.0339, 2.4087, 0.5261, 4.2194, 1.9538, 1.2634, 3.7734, 1.9919, 2.6003, 1.0473, -1.0133, 2.0312, 2.7158, -1.0135, 2.1515, 0.9506, -1.6595, 2.2544, 3.8319, 0.7934, 2.1174, 4.2399, 0.396, 2.3685, 3.5019, 0.414, 1.4453, 1.3219, -1.8647, 1.4442, 1.9, -1.6223, 1.2471, 1.631, -1.7857, 1.9293, 0.908, 1.6849, 2.1901, 2.3652, 1.3543, 2.1038, 1.0245, 0.9114, -1.0077, -1.3716, -2.1657, -1.6842, -0.4729, -2.3712, -1.6361, -1.5711, -1.5378, -1.7965, 0.899, -2.2295, -0.9393, 2.872, -1.8539, -1.7216, 2.2099, -1.5001, 1.1377, -0.8428, -2.7439, 0.7304, -0.9533, -2.8604, 0.9457, -1.6804, -2.7624, -2.6648, -1.8501, 0.307, -2.5202, -0.0318, 0.8043, -2.3406, -2.1709, 1.3672, -2.5202, -0.0318, 0.8043, -2.575, 1.9843, 0.2578, -2.1461, 1.5782, 1.2673, -1.6842, -0.4729, -2.3712, -2.3999, -0.6295, -1.5513, -1.6361, -1.5711, -1.5378, -0.2939, -2.4569, 2.4482, 0.256, -0.9397, 2.7009, 0.5442, -2.0915, 2.2664, -0.2195, 1.1266, 2.8293, -0.4271, 2.5556, 2.4897, 0.5038, 3.0339, 2.4087, 2.4385, -0.7713, 0.0524, 2.634, -0.7691, -0.3318, 2.5652, -1.538, -0.1083, 1.8065, -2.8661, 1.2715, 1.9869, -3.1191, 0.2426, 1.2817, -3.3103, 0.5621, 2.1038, 1.0245, 0.9114, 2.1901, 2.3652, 1.3543, 2.3678, 2.1513, 0.6399, 0.9342, -3.6502, -1.2505, 0.9178, -3.3811, -1.9588, 0.3423, -3.927, -1.4233, 0.9342, -3.6502, -1.2505, 1.4243, -3.0391, -1.5379, 0.9178, -3.3811, -1.9588, 2.1195, -1.771, -1.5705, 1.9584, -0.8816, -2.0923, 1.5426, -2.0265, -2.2438, -0.5108, -3.8408, -1.1476, -1.1054, -2.8992, -1.0824, -1.1208, -4.5065, 0.1953, 0.3194, -2.8417, -2.3407, 0.0974, -1.7758, -2.6186, -0.2695, -2.508, -2.0432, -0.5108, -3.8408, -1.1476, 0.3194, -2.8417, -2.3407, -0.2695, -2.508, -2.0432, -2.5202, -0.0318, 0.8043, -2.0758, -0.0137, 1.8949, -2.3406, -2.1709, 1.3672, -1.9059, -2.3487, -0.7483, -2.5883, -1.4508, -0.7517, -2.3688, -2.5627, -0.2622, -1.809, -3.263, -0.2744, -1.9059, -2.3487, -0.7483, -2.3688, -2.5627, -0.2622, -0.0922, -4.5871, 1.3087, 0.4197, -3.4404, 1.5487, 0.4584, -3.889, 0.8266, -1.7887, -3.0231, 1.9828, -1.4963, -0.9921, 2.5267, -1.2288, -2.368, 2.4204, 2.5358, -1.9021, -0.8254, 2.5987, -0.8743, -1.1872, 2.1195, -1.771, -1.5705, 0.256, -0.9397, 2.7009, 1.2883, -0.8696, 2.2885, 0.5442, -2.0915, 2.2664, 1.2817, -3.3103, 0.5621, 0.9704, -3.0065, 1.4497, 1.8065, -2.8661, 1.2715, -1.1122, -3.8993, 1.9946, -1.7887, -3.0231, 1.9828, -1.2288, -2.368, 2.4204, 1.9869, -3.1191, 0.2426, 1.267, -3.4909, -0.189, 1.2817, -3.3103, 0.5621, 0.5442, -2.0915, 2.2664, 1.2883, -0.8696, 2.2885, 1.6056, -1.9424, 1.9813, -0.4293, -5.0774, 0.0003, 0.169, -4.9369, 0.2441, 0.0671, -5.0216, -0.4515, 2.1498, -1.9824, 1.4097, 1.8065, -2.8661, 1.2715, 1.6056, -1.9424, 1.9813, -2.179, -3.4152, 0.4259, -2.3688, -2.5627, -0.2622, -2.6648, -1.8501, 0.307, 1.87, -2.8735, -1.1419, 2.1195, -1.771, -1.5705, 1.4243, -3.0391, -1.5379, -1.7058, 2.0492, 1.8707, -2.1461, 1.5782, 1.2673, -1.5979, 2.7577, 1.2075, 1.2471, 1.631, -1.7857, 0.0456, 1.662, -2.2329, 0.7444, 0.8429, -2.2856, 0.0456, 1.662, -2.2329, 0.9177, 2.8679, -1.5387, 0.1709, 2.9305, -1.8154, 0.0456, 1.662, -2.2329, 1.2471, 1.631, -1.7857, 0.9177, 2.8679, -1.5387, 2.5473, 1.387, -0.0726, 2.3685, 3.5019, 0.414, 2.4031, 2.6779, -0.4122, 2.5473, 1.387, -0.0726, 2.3678, 2.1513, 0.6399, 2.3685, 3.5019, 0.414, 0.5746, 5.6649, -0.3989, -0.2232, 5.1794, 0.1938, -0.2125, 5.045, -0.6732, 0.5746, 5.6649, -0.3989, 0.6378, 5.6752, 0.4017, -0.2232, 5.1794, 0.1938, -0.0922, -4.5871, 1.3087, -1.1122, -3.8993, 1.9946, -0.2829, -3.5923, 2.1108, -0.0922, -4.5871, 1.3087, -0.8729, -4.7982, 1.121, -1.1122, -3.8993, 1.9946, 0.9704, -3.0065, 1.4497, 0.4197, -3.4404, 1.5487, 0.5442, -2.0915, 2.2664, 2.3387, -2.1373, 0.6494, 2.1726, -2.8941, -0.5813, 1.9869, -3.1191, 0.2426, 2.1726, -2.8941, -0.5813, 2.5652, -1.538, -0.1083, 2.5358, -1.9021, -0.8254, 2.1726, -2.8941, -0.5813, 2.3387, -2.1373, 0.6494, 2.5652, -1.538, -0.1083, 0.9342, -3.6502, -1.2505, 1.267, -3.4909, -0.189, 1.5863, -3.2514, -0.7541, 1.267, -3.4909, -0.189, 0.4756, -4.5162, -0.6549, 0.6021, -4.1961, -0.0392, 1.267, -3.4909, -0.189, 0.9342, -3.6502, -1.2505, 0.4756, -4.5162, -0.6549, -1.8849, -3.8097, 1.1593, -2.3406, -2.1709, 1.3672, -1.7887, -3.0231, 1.9828, -0.5108, -3.8408, -1.1476, 0.1398, -4.5397, -0.9338, 0.3423, -3.927, -1.4233, -1.1054, -2.8992, -1.0824, -1.6361, -1.5711, -1.5378, -1.9059, -2.3487, -0.7483, 0.9178, -3.3811, -1.9588, 0.9457, -1.6804, -2.7624, 0.3194, -2.8417, -2.3407, 0.9178, -3.3811, -1.9588, 1.5426, -2.0265, -2.2438, 0.9457, -1.6804, -2.7624, 2.4617, 0.7259, 0.0288, 2.1074, -0.9551, 0.9434, 2.1038, 1.0245, 0.9114, 2.4617, 0.7259, 0.0288, 2.4385, -0.7713, 0.0524, 2.1074, -0.9551, 0.9434, 2.6196, 0.7185, -0.2944, 2.5987, -0.8743, -1.1872, 2.634, -0.7691, -0.3318, 2.6196, 0.7185, -0.2944, 2.6003, 1.0473, -1.0133, 2.5987, -0.8743, -1.1872, 0.256, -0.9397, 2.7009, -0.2195, 1.1266, 2.8293, 0.7315, 0.8274, 2.7196, 0.256, -0.9397, 2.7009, -0.7814, -0.8418, 2.7775, -0.2195, 1.1266, 2.8293, 1.9393, -0.8033, 1.6608, 1.409, 0.9631, 2.386, 1.9293, 0.908, 1.6849, 1.9393, -0.8033, 1.6608, 1.2883, -0.8696, 2.2885, 1.409, 0.9631, 2.386, -2.0758, -0.0137, 1.8949, -1.0159, 0.9756, 2.6776, -1.4963, -0.9921, 2.5267, -2.0758, -0.0137, 1.8949, -1.6176, 1.4128, 2.2491, -1.0159, 0.9756, 2.6776, -1.246, 0.719, -2.7492, -1.7965, 0.899, -2.2295, -1.6842, -0.4729, -2.3712, -2.5502, 1.5321, -0.8246, -2.3999, -0.6295, -1.5513, -2.3834, 0.7782, -1.5631, -2.3999, -0.6295, -1.5513, -2.907, 0.041, -0.3088, -2.5883, -1.4508, -0.7517, -2.3999, -0.6295, -1.5513, -2.5502, 1.5321, -0.8246, -2.907, 0.041, -0.3088, 1.1377, -0.8428, -2.7439, 1.4453, 1.3219, -1.8647, 0.9877, 0.4313, -2.4389, 1.4453, 1.3219, -1.8647, 1.9584, -0.8816, -2.0923, 2.1515, 0.9506, -1.6595, 1.4453, 1.3219, -1.8647, 1.1377, -0.8428, -2.7439, 1.9584, -0.8816, -2.0923, -0.0823, 1.3076, -2.4751, 0.7304, -0.9533, -2.8604, 0.635, 0.4247, -2.5749, 0.7304, -0.9533, -2.8604, -0.7692, -0.6389, -2.6654, 0.0974, -1.7758, -2.6186, -0.7692, -0.6389, -2.6654, -0.0823, 1.3076, -2.4751, -0.7261, 0.8081, -2.8158, 0.7304, -0.9533, -2.8604, -0.0823, 1.3076, -2.4751, -0.7692, -0.6389, -2.6654, 1.2634, 3.7734, 1.9919, 2.1901, 2.3652, 1.3543, 1.6219, 2.267, 2.1861, 2.1901, 2.3652, 1.3543, 1.6148, 4.6612, 1.1746, 2.2544, 3.8319, 0.7934, 2.1901, 2.3652, 1.3543, 1.2634, 3.7734, 1.9919, 1.6148, 4.6612, 1.1746, -1.3001, 2.2705, 2.1959, -0.878, 3.135, 1.7811, -0.4271, 2.5556, 2.4897, -1.9764, 3.1849, 0.2685, -2.575, 1.9843, 0.2578, -2.286, 2.7057, -0.3882, -0.2095, 1.8203, -2.3278, -0.9393, 2.872, -1.8539, -0.8202, 1.5078, -2.6285, -0.2095, 1.8203, -2.3278, -0.0988, 3.0024, -1.9036, -0.9393, 2.872, -1.8539, 1.4442, 1.9, -1.6223, 2.0312, 2.7158, -1.0135, 1.1827, 2.8613, -1.4341, -0.0735, 4.938, 0.8308, 0.5261, 4.2194, 1.9538, -0.2982, 4.0929, 1.4648, -0.0735, 4.938, 0.8308, 0.8594, 5.2909, 1.0912, 0.5261, 4.2194, 1.9538, 1.3249, 5.3073, 0.5102, 1.7744, 4.3299, -0.4393, 2.1174, 4.2399, 0.396, 1.3249, 5.3073, 0.5102, 0.9445, 5.5277, -0.3666, 1.7744, 4.3299, -0.4393, -1.7496, 3.0465, -0.6307, -0.791, 4.3118, -0.0929, -1.3695, 3.5538, -0.0189, -1.7496, 3.0465, -0.6307, -1.2485, 3.6723, -1.0264, -0.791, 4.3118, -0.0929, 0.9077, 3.2903, -1.4616, -0.1392, 4.5027, -1.2571, 0.1823, 3.4347, -1.7089, -0.1392, 4.5027, -1.2571, 1.3226, 4.3217, -0.9655, 0.6884, 5.4332, -0.7011, -0.1392, 4.5027, -1.2571, 0.9077, 3.2903, -1.4616, 1.3226, 4.3217, -0.9655, -0.8729, -4.7982, 1.121, 0.169, -4.9369, 0.2441, -0.4293, -5.0774, 0.0003, -0.8729, -4.7982, 1.121, -0.0922, -4.5871, 1.3087, 0.169, -4.9369, 0.2441, 2.3387, -2.1373, 0.6494, 1.8065, -2.8661, 1.2715, 2.1498, -1.9824, 1.4097, 2.3387, -2.1373, 0.6494, 1.9869, -3.1191, 0.2426, 1.8065, -2.8661, 1.2715, 0.9342, -3.6502, -1.2505, 1.87, -2.8735, -1.1419, 1.4243, -3.0391, -1.5379, 0.9342, -3.6502, -1.2505, 1.5863, -3.2514, -0.7541, 1.87, -2.8735, -1.1419, -2.3406, -2.1709, 1.3672, -2.179, -3.4152, 0.4259, -2.6648, -1.8501, 0.307, -2.3406, -2.1709, 1.3672, -1.8849, -3.8097, 1.1593, -2.179, -3.4152, 0.4259, 0.1398, -4.5397, -0.9338, -0.4293, -5.0774, 0.0003, 0.0671, -5.0216, -0.4515, 0.1398, -4.5397, -0.9338, -0.5108, -3.8408, -1.1476, -0.4293, -5.0774, 0.0003, -1.6361, -1.5711, -1.5378, -0.2695, -2.508, -2.0432, -1.0077, -1.3716, -2.1657, -1.6361, -1.5711, -1.5378, -1.1054, -2.8992, -1.0824, -0.2695, -2.508, -2.0432, 1.5426, -2.0265, -2.2438, 1.4243, -3.0391, -1.5379, 2.1195, -1.771, -1.5705, 1.5426, -2.0265, -2.2438, 0.9178, -3.3811, -1.9588, 1.4243, -3.0391, -1.5379, 2.6003, 1.0473, -1.0133, 2.5473, 1.387, -0.0726, 2.4031, 2.6779, -0.4122, 2.6003, 1.0473, -1.0133, 2.6196, 0.7185, -0.2944, 2.5473, 1.387, -0.0726, -0.7814, -0.8418, 2.7775, -0.2939, -2.4569, 2.4482, -1.2288, -2.368, 2.4204, -0.7814, -0.8418, 2.7775, 0.256, -0.9397, 2.7009, -0.2939, -2.4569, 2.4482, -1.6176, 1.4128, 2.2491, -2.1461, 1.5782, 1.2673, -1.7058, 2.0492, 1.8707, -1.6176, 1.4128, 2.2491, -2.0758, -0.0137, 1.8949, -2.1461, 1.5782, 1.2673, -2.5502, 1.5321, -0.8246, -1.7216, 2.2099, -1.5001, -1.9974, 2.3952, -0.8364, -2.5502, 1.5321, -0.8246, -2.3834, 0.7782, -1.5631, -1.7216, 2.2099, -1.5001, -0.0823, 1.3076, -2.4751, 0.7444, 0.8429, -2.2856, 0.0456, 1.662, -2.2329, -0.0823, 1.3076, -2.4751, 0.635, 0.4247, -2.5749, 0.7444, 0.8429, -2.2856, 1.2634, 3.7734, 1.9919, 1.1136, 1.849, 2.4998, 0.5038, 3.0339, 2.4087, 1.2634, 3.7734, 1.9919, 1.6219, 2.267, 2.1861, 1.1136, 1.849, 2.4998, -0.878, 3.135, 1.7811, -1.7058, 2.0492, 1.8707, -1.5979, 2.7577, 1.2075, -0.878, 3.135, 1.7811, -1.3001, 2.2705, 2.1959, -1.7058, 2.0492, 1.8707, -0.0988, 3.0024, -1.9036, 0.0456, 1.662, -2.2329, 0.1709, 2.9305, -1.8154, -0.0988, 3.0024, -1.9036, -0.2095, 1.8203, -2.3278, 0.0456, 1.662, -2.2329, 0.9445, 5.5277, -0.3666, 0.6378, 5.6752, 0.4017, 0.5746, 5.6649, -0.3989, 0.9445, 5.5277, -0.3666, 1.3249, 5.3073, 0.5102, 0.6378, 5.6752, 0.4017, -1.2485, 3.6723, -1.0264, -1.9974, 2.3952, -0.8364, -1.7216, 2.2099, -1.5001, -1.2485, 3.6723, -1.0264, -1.7496, 3.0465, -0.6307, -1.9974, 2.3952, -0.8364, 0.9077, 3.2903, -1.4616, 0.1709, 2.9305, -1.8154, 0.9177, 2.8679, -1.5387, 0.9077, 3.2903, -1.4616, 0.1823, 3.4347, -1.7089, 0.1709, 2.9305, -1.8154, -1.2288, -2.368, 2.4204, -0.2829, -3.5923, 2.1108, -1.1122, -3.8993, 1.9946, -1.2288, -2.368, 2.4204, -0.2939, -2.4569, 2.4482, -0.2829, -3.5923, 2.1108, 1.6056, -1.9424, 1.9813, 0.9704, -3.0065, 1.4497, 0.5442, -2.0915, 2.2664, 1.6056, -1.9424, 1.9813, 1.8065, -2.8661, 1.2715, 0.9704, -3.0065, 1.4497, 2.1195, -1.771, -1.5705, 2.1726, -2.8941, -0.5813, 2.5358, -1.9021, -0.8254, 2.1195, -1.771, -1.5705, 1.87, -2.8735, -1.1419, 2.1726, -2.8941, -0.5813, 0.0671, -5.0216, -0.4515, 0.6021, -4.1961, -0.0392, 0.4756, -4.5162, -0.6549, 0.0671, -5.0216, -0.4515, 0.169, -4.9369, 0.2441, 0.6021, -4.1961, -0.0392, -2.3688, -2.5627, -0.2622, -1.1208, -4.5065, 0.1953, -1.809, -3.263, -0.2744, -2.3688, -2.5627, -0.2622, -2.179, -3.4152, 0.4259, -1.1208, -4.5065, 0.1953, 2.3678, 2.1513, 0.6399, 2.4617, 0.7259, 0.0288, 2.1038, 1.0245, 0.9114, 2.3678, 2.1513, 0.6399, 2.5473, 1.387, -0.0726, 2.4617, 0.7259, 0.0288, 0.5038, 3.0339, 2.4087, 0.7315, 0.8274, 2.7196, -0.2195, 1.1266, 2.8293, 0.5038, 3.0339, 2.4087, 1.1136, 1.849, 2.4998, 0.7315, 0.8274, 2.7196, 2.1498, -1.9824, 1.4097, 1.2883, -0.8696, 2.2885, 1.9393, -0.8033, 1.6608, 2.1498, -1.9824, 1.4097, 1.6056, -1.9424, 1.9813, 1.2883, -0.8696, 2.2885, -2.6648, -1.8501, 0.307, -2.5883, -1.4508, -0.7517, -2.907, 0.041, -0.3088, -2.6648, -1.8501, 0.307, -2.3688, -2.5627, -0.2622, -2.5883, -1.4508, -0.7517, 1.2471, 1.631, -1.7857, 0.9877, 0.4313, -2.4389, 1.4453, 1.3219, -1.8647, 1.2471, 1.631, -1.7857, 0.7444, 0.8429, -2.2856, 0.9877, 0.4313, -2.4389, -1.0077, -1.3716, -2.1657, 0.0974, -1.7758, -2.6186, -0.7692, -0.6389, -2.6654, -1.0077, -1.3716, -2.1657, -0.2695, -2.508, -2.0432, 0.0974, -1.7758, -2.6186, 2.3685, 3.5019, 0.414, 2.1901, 2.3652, 1.3543, 2.2544, 3.8319, 0.7934, 2.3685, 3.5019, 0.414, 2.3678, 2.1513, 0.6399, 2.1901, 2.3652, 1.3543, -1.5979, 2.7577, 1.2075, -2.575, 1.9843, 0.2578, -1.9764, 3.1849, 0.2685, -1.5979, 2.7577, 1.2075, -2.1461, 1.5782, 1.2673, -2.575, 1.9843, 0.2578, 0.9177, 2.8679, -1.5387, 1.4442, 1.9, -1.6223, 1.1827, 2.8613, -1.4341, 0.9177, 2.8679, -1.5387, 1.2471, 1.631, -1.7857, 1.4442, 1.9, -1.6223, -0.2232, 5.1794, 0.1938, 0.8594, 5.2909, 1.0912, -0.0735, 4.938, 0.8308, -0.2232, 5.1794, 0.1938, 0.6378, 5.6752, 0.4017, 0.8594, 5.2909, 1.0912, 2.4031, 2.6779, -0.4122, 2.1174, 4.2399, 0.396, 1.7744, 4.3299, -0.4393, 2.4031, 2.6779, -0.4122, 2.3685, 3.5019, 0.414, 2.1174, 4.2399, 0.396, -0.2125, 5.045, -0.6732, -0.791, 4.3118, -0.0929, -1.2485, 3.6723, -1.0264, -0.2125, 5.045, -0.6732, -0.2232, 5.1794, 0.1938, -0.791, 4.3118, -0.0929, 0.5746, 5.6649, -0.3989, -0.1392, 4.5027, -1.2571, 0.6884, 5.4332, -0.7011, 0.5746, 5.6649, -0.3989, -0.2125, 5.045, -0.6732, -0.1392, 4.5027, -1.2571, 1.7744, 4.3299, -0.4393, 0.6884, 5.4332, -0.7011, 1.3226, 4.3217, -0.9655, 1.7744, 4.3299, -0.4393, 0.9445, 5.5277, -0.3666, 0.6884, 5.4332, -0.7011, 1.3226, 4.3217, -0.9655, 1.1827, 2.8613, -1.4341, 2.0312, 2.7158, -1.0135, 1.3226, 4.3217, -0.9655, 0.9077, 3.2903, -1.4616, 1.1827, 2.8613, -1.4341, 0.1823, 3.4347, -1.7089, -0.9393, 2.872, -1.8539, -0.0988, 3.0024, -1.9036, 0.1823, 3.4347, -1.7089, -0.1392, 4.5027, -1.2571, -0.9393, 2.872, -1.8539, -1.3695, 3.5538, -0.0189, -0.5404, 4.4098, 0.4328, -1.0116, 3.5059, 0.8103, -1.3695, 3.5538, -0.0189, -0.791, 4.3118, -0.0929, -0.5404, 4.4098, 0.4328, -1.7496, 3.0465, -0.6307, -1.9764, 3.1849, 0.2685, -2.286, 2.7057, -0.3882, -1.7496, 3.0465, -0.6307, -1.3695, 3.5538, -0.0189, -1.9764, 3.1849, 0.2685, -1.0116, 3.5059, 0.8103, -0.0735, 4.938, 0.8308, -0.2982, 4.0929, 1.4648, -1.0116, 3.5059, 0.8103, -0.5404, 4.4098, 0.4328, -0.0735, 4.938, 0.8308, -0.2982, 4.0929, 1.4648, -0.4271, 2.5556, 2.4897, -0.878, 3.135, 1.7811, -0.2982, 4.0929, 1.4648, 0.5261, 4.2194, 1.9538, -0.4271, 2.5556, 2.4897, 0.5261, 4.2194, 1.9538, 1.6148, 4.6612, 1.1746, 1.2634, 3.7734, 1.9919, 0.5261, 4.2194, 1.9538, 0.8594, 5.2909, 1.0912, 1.6148, 4.6612, 1.1746, 1.3249, 5.3073, 0.5102, 2.2544, 3.8319, 0.7934, 1.6148, 4.6612, 1.1746, 1.3249, 5.3073, 0.5102, 2.1174, 4.2399, 0.396, 2.2544, 3.8319, 0.7934, 2.5987, -0.8743, -1.1872, 2.1515, 0.9506, -1.6595, 1.9584, -0.8816, -2.0923, 2.5987, -0.8743, -1.1872, 2.6003, 1.0473, -1.0133, 2.1515, 0.9506, -1.6595, 2.0312, 2.7158, -1.0135, 1.4453, 1.3219, -1.8647, 2.1515, 0.9506, -1.6595, 2.0312, 2.7158, -1.0135, 1.4442, 1.9, -1.6223, 1.4453, 1.3219, -1.8647, -0.7261, 0.8081, -2.8158, -0.2095, 1.8203, -2.3278, -0.8202, 1.5078, -2.6285, -0.7261, 0.8081, -2.8158, -0.0823, 1.3076, -2.4751, -0.2095, 1.8203, -2.3278, -0.7692, -0.6389, -2.6654, -1.246, 0.719, -2.7492, -1.6842, -0.4729, -2.3712, -0.7692, -0.6389, -2.6654, -0.7261, 0.8081, -2.8158, -1.246, 0.719, -2.7492, -0.8202, 1.5078, -2.6285, -1.7965, 0.899, -2.2295, -1.246, 0.719, -2.7492, -0.8202, 1.5078, -2.6285, -0.9393, 2.872, -1.8539, -1.7965, 0.899, -2.2295, -2.907, 0.041, -0.3088, -2.286, 2.7057, -0.3882, -2.575, 1.9843, 0.2578, -2.907, 0.041, -0.3088, -2.5502, 1.5321, -0.8246, -2.286, 2.7057, -0.3882, -1.0159, 0.9756, 2.6776, -1.3001, 2.2705, 2.1959, -0.4271, 2.5556, 2.4897, -1.0159, 0.9756, 2.6776, -1.6176, 1.4128, 2.2491, -1.3001, 2.2705, 2.1959, -1.4963, -0.9921, 2.5267, -0.2195, 1.1266, 2.8293, -0.7814, -0.8418, 2.7775, -1.4963, -0.9921, 2.5267, -1.0159, 0.9756, 2.6776, -0.2195, 1.1266, 2.8293, 1.9293, 0.908, 1.6849, 1.6219, 2.267, 2.1861, 2.1901, 2.3652, 1.3543, 1.9293, 0.908, 1.6849, 1.409, 0.9631, 2.386, 1.6219, 2.267, 2.1861, 1.9393, -0.8033, 1.6608, 2.1038, 1.0245, 0.9114, 2.1074, -0.9551, 0.9434, 1.9393, -0.8033, 1.6608, 1.9293, 0.908, 1.6849, 2.1038, 1.0245, 0.9114, 1.1377, -0.8428, -2.7439, 0.635, 0.4247, -2.5749, 0.7304, -0.9533, -2.8604, 1.1377, -0.8428, -2.7439, 0.9877, 0.4313, -2.4389, 0.635, 0.4247, -2.5749, 1.9584, -0.8816, -2.0923, 0.9457, -1.6804, -2.7624, 1.5426, -2.0265, -2.2438, 1.9584, -0.8816, -2.0923, 1.1377, -0.8428, -2.7439, 0.9457, -1.6804, -2.7624, 0.7304, -0.9533, -2.8604, 0.3194, -2.8417, -2.3407, 0.9457, -1.6804, -2.7624, 0.7304, -0.9533, -2.8604, 0.0974, -1.7758, -2.6186, 0.3194, -2.8417, -2.3407, -1.6842, -0.4729, -2.3712, -2.3834, 0.7782, -1.5631, -2.3999, -0.6295, -1.5513, -1.6842, -0.4729, -2.3712, -1.7965, 0.899, -2.2295, -2.3834, 0.7782, -1.5631, -2.3999, -0.6295, -1.5513, -1.9059, -2.3487, -0.7483, -1.6361, -1.5711, -1.5378, -2.3999, -0.6295, -1.5513, -2.5883, -1.4508, -0.7517, -1.9059, -2.3487, -0.7483, -2.0758, -0.0137, 1.8949, -1.7887, -3.0231, 1.9828, -2.3406, -2.1709, 1.3672, -2.0758, -0.0137, 1.8949, -1.4963, -0.9921, 2.5267, -1.7887, -3.0231, 1.9828, 0.256, -0.9397, 2.7009, 1.409, 0.9631, 2.386, 1.2883, -0.8696, 2.2885, 0.256, -0.9397, 2.7009, 0.7315, 0.8274, 2.7196, 1.409, 0.9631, 2.386, 2.4385, -0.7713, 0.0524, 2.6196, 0.7185, -0.2944, 2.634, -0.7691, -0.3318, 2.4385, -0.7713, 0.0524, 2.4617, 0.7259, 0.0288, 2.6196, 0.7185, -0.2944, 2.1074, -0.9551, 0.9434, 2.5652, -1.538, -0.1083, 2.3387, -2.1373, 0.6494, 2.1074, -0.9551, 0.9434, 2.4385, -0.7713, 0.0524, 2.5652, -1.538, -0.1083, 2.634, -0.7691, -0.3318, 2.5358, -1.9021, -0.8254, 2.5652, -1.538, -0.1083, 2.634, -0.7691, -0.3318, 2.5987, -0.8743, -1.1872, 2.5358, -1.9021, -0.8254, 0.4756, -4.5162, -0.6549, 0.3423, -3.927, -1.4233, 0.1398, -4.5397, -0.9338, 0.4756, -4.5162, -0.6549, 0.9342, -3.6502, -1.2505, 0.3423, -3.927, -1.4233, 0.9178, -3.3811, -1.9588, -0.5108, -3.8408, -1.1476, 0.3423, -3.927, -1.4233, 0.9178, -3.3811, -1.9588, 0.3194, -2.8417, -2.3407, -0.5108, -3.8408, -1.1476, -1.1054, -2.8992, -1.0824, -1.809, -3.263, -0.2744, -1.1208, -4.5065, 0.1953, -1.1054, -2.8992, -1.0824, -1.9059, -2.3487, -0.7483, -1.809, -3.263, -0.2744, -1.8849, -3.8097, 1.1593, -1.1122, -3.8993, 1.9946, -0.8729, -4.7982, 1.121, -1.8849, -3.8097, 1.1593, -1.7887, -3.0231, 1.9828, -1.1122, -3.8993, 1.9946, 1.9869, -3.1191, 0.2426, 1.5863, -3.2514, -0.7541, 1.267, -3.4909, -0.189, 1.9869, -3.1191, 0.2426, 2.1726, -2.8941, -0.5813, 1.5863, -3.2514, -0.7541, 1.267, -3.4909, -0.189, 0.4584, -3.889, 0.8266, 1.2817, -3.3103, 0.5621, 1.267, -3.4909, -0.189, 0.6021, -4.1961, -0.0392, 0.4584, -3.889, 0.8266, -0.0922, -4.5871, 1.3087, 0.5442, -2.0915, 2.2664, 0.4197, -3.4404, 1.5487, -0.0922, -4.5871, 1.3087, -0.2829, -3.5923, 2.1108, 0.5442, -2.0915, 2.2664, 0.4197, -3.4404, 1.5487, 1.2817, -3.3103, 0.5621, 0.4584, -3.889, 0.8266, 0.4197, -3.4404, 1.5487, 0.9704, -3.0065, 1.4497, 1.2817, -3.3103, 0.5621, -0.4293, -5.0774, 0.0003, -1.1208, -4.5065, 0.1953, -0.8729, -4.7982, 1.121) [node name="RockB" instance=ExtResource("1_2nhli")] collision_mask = 3 [node name="Rock2" parent="." index="0"] surface_material_override/0 = ExtResource("2_dbm2k") [node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"] shape = SubResource("ConcavePolygonShape3D_wfrmp") ================================================ FILE: project/demo/assets/models/RockC.glb.import ================================================ [remap] importer="scene" importer_version=1 type="PackedScene" uid="uid://c8cx4xjwluvxw" path="res://.godot/imported/RockC.glb-5881ee1ff2072066a3de122ce4239bee.scn" [deps] source_file="res://demo/assets/models/RockC.glb" dest_files=["res://.godot/imported/RockC.glb-5881ee1ff2072066a3de122ce4239bee.scn"] [params] nodes/root_type="StaticBody3D" nodes/root_name="Scene Root" nodes/apply_root_scale=true nodes/root_scale=1.0 nodes/import_as_skeleton_bones=false nodes/use_node_type_suffixes=true meshes/ensure_tangents=true meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 animation/trimming=false animation/remove_immutable_tracks=true animation/import_rest_as_RESET=false import_script/path="" _subresources={ "materials": { "@MATERIAL:0": { "use_external/enabled": true, "use_external/path": "res://demo/assets/materials/M_rock30.tres" } } } gltf/naming_version=0 gltf/embedded_image_handling=1 ================================================ FILE: project/demo/assets/models/RockC.tscn ================================================ [gd_scene load_steps=4 format=3 uid="uid://lsvs8a7urkca"] [ext_resource type="PackedScene" uid="uid://c8cx4xjwluvxw" path="res://demo/assets/models/RockC.glb" id="1_pb27A"] [ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_pcex8"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_ycsjC"] data = PackedVector3Array(1.3907, -4.3898, 0.264, 0.3707, -6.5026, 0.2358, 0.9197, -5.2139, 0.5803, 1.3907, -4.3898, 0.264, 1.5973, -4.4989, -0.523, 0.3707, -6.5026, 0.2358, 2.7182, -1.9153, 1.6333, 2.6862, -0.2733, 1.344, 2.791, -2.0828, 0.6971, -0.6892, -7.3327, -0.0456, -1.2211, -6.6432, -1.3733, -1.8677, -6.7222, 0.0053, 0.1584, -6.6788, -0.712, 0.8528, -5.1659, -1.2387, -0.129, -6.0155, -1.6627, 0.3707, -6.5026, 0.2358, 0.8249, -4.8431, 1.2373, 0.9197, -5.2139, 0.5803, 0.3707, -6.5026, 0.2358, -0.1504, -6.0468, 1.6347, 0.8249, -4.8431, 1.2373, 2.7182, -1.9153, 1.6333, 2.5729, -0.0463, 2.323, 2.6862, -0.2733, 1.344, -1.5434, -2.0626, 2.9798, -0.0319, -0.907, 3.3884, -0.6296, -2.1168, 3.0274, -1.5434, -2.0626, 2.9798, -2.2391, -1.0013, 3.2378, -0.0319, -0.907, 3.3884, -3.2503, -1.9438, -0.0444, -3.1045, -0.0443, -0.8667, -2.9895, -0.0523, 0.8233, -0.5191, -2.1018, -2.9939, -2.0489, -0.9812, -3.014, -1.4897, -2.0239, -2.8933, -0.5191, -2.1018, -2.9939, 0.0444, -0.8451, -3.3989, -2.0489, -0.9812, -3.014, 3.4189, -1.5098, -2.1519, 3.3947, 0.8197, -1.419, 2.9911, 0.177, -2.5055, -0.9484, -5.3598, 2.2535, -0.8975, -3.7031, 2.6018, -0.1302, -3.9225, 2.4564, -2.8602, -5.2902, -0.0034, -3.2063, -3.9919, -0.0118, -3.0246, -4.4027, 0.8468, -0.9423, -5.2303, -2.2174, -1.6189, -3.1121, -2.5666, -2.3373, -3.6333, -2.2624, -0.9423, -5.2303, -2.2174, -0.9004, -3.6647, -2.5803, -1.6189, -3.1121, -2.5666, 2.4159, -3.3908, 0.0683, 2.6374, -3.7219, -1.0723, 1.5973, -4.4989, -0.523, 0.0817, 0.7564, 3.232, 1.3747, 1.7762, 2.618, 1.9677, 0.877, 2.8315, 0.0817, 0.7564, 3.232, 0.5064, 1.8556, 2.7003, 1.3747, 1.7762, 2.618, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 0.6919, 3.2703, 2.2188, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -2.0691, 2.5964, 1.5185, -1.2285, 2.4662, -2.4325, -1.544, 3.8438, -1.471, -2.3777, 2.1645, -2.0413, 2.3543, 3.9299, -1.7328, 1.4379, 5.2295, -1.8152, 1.1494, 2.3687, -2.734, 1.4341, 6.0081, 1.2447, 0.9325, 6.924, 0.4365, 2.3528, 6.0999, 0.4838, -1.8677, -6.7222, 0.0053, -2.3434, -5.4566, 1.3758, -1.2352, -6.7568, 1.4079, 3.0694, 0.3768, 0.2917, 3.6836, 2.3906, 0.1427, 3.39, -0.1345, -0.5433, 1.5686, 6.857, -0.4389, 0.7824, 7.1818, -0.4789, 0.9515, 6.8097, -1.0683, 2.4165, 5.7335, -0.6508, 1.4379, 5.2295, -1.8152, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 1.1494, 2.3687, -2.734, 0.0399, 0.8971, -3.3453, -0.1803, 5.4522, -1.6664, -0.2609, 6.3493, -0.8464, -1.544, 3.8438, -1.471, -3.1045, -0.0443, -0.8667, -2.9635, 2.0631, -0.0469, -2.9895, -0.0523, 0.8233, -2.8218, -0.0908, -2.3316, -2.8949, 1.7465, -1.4628, -3.1045, -0.0443, -0.8667, -1.137, 5.0901, -0.2048, -0.2548, 6.3653, 0.3115, -0.7059, 4.7856, 0.8682, -1.8973, 4.0438, -0.0231, -1.6818, 3.3981, 1.0198, -2.3926, 3.3478, 0.3255, -2.9895, -0.0523, 0.8233, -2.7222, 1.5834, 1.3251, -2.9289, -0.1232, 2.5316, -0.7059, 4.7856, 0.8682, -0.2548, 6.3653, 0.3115, 0.4487, 4.8708, 1.6514, -0.7059, 4.7856, 0.8682, 0.4487, 4.8708, 1.6514, -0.2637, 3.5029, 1.8153, 3.3947, 0.8197, -1.419, 3.6143, 2.6541, -0.7787, 3.0814, 2.2747, -1.5878, 3.3017, 4.3689, -0.5397, 2.4165, 5.7335, -0.6508, 2.3543, 3.9299, -1.7328, -2.1314, 0.7603, 3.0046, -1.0522, 1.986, 2.2721, 0.0817, 0.7564, 3.232, 0.0399, 0.8971, -3.3453, 1.1494, 2.3687, -2.734, -1.2285, 2.4662, -2.4325, 0.549, 3.3793, -2.5003, 0.4374, 4.5707, -2.224, -0.1459, 3.4745, -2.3857, 1.4341, 6.0081, 1.2447, 2.3528, 6.0999, 0.4838, 2.8818, 4.7444, 1.3483, -3.1045, -0.0443, -0.8667, -2.8949, 1.7465, -1.4628, -2.9635, 2.0631, -0.0469, -2.7186, 2.9035, -1.3821, -2.3162, 3.6228, -0.9721, -2.7435, 3.293, -0.5137, -0.1803, 5.4522, -1.6664, -1.544, 3.8438, -1.471, -1.2285, 2.4662, -2.4325, -2.1314, 0.7603, 3.0046, -2.0691, 2.5964, 1.5185, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -1.6818, 3.3981, 1.0198, -1.0522, 1.986, 2.2721, 0.0399, 0.8971, -3.3453, -1.2285, 2.4662, -2.4325, -2.0395, 0.8474, -2.9039, 1.9677, 0.877, 2.8315, 1.5121, 2.8416, 2.3106, 2.2766, 3.3307, 2.1367, 1.9677, 0.877, 2.8315, 1.3747, 1.7762, 2.618, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 2.2766, 3.3307, 2.1367, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 0.4487, 4.8708, 1.6514, 2.2766, 3.3307, 2.1367, 3.3947, 0.8197, -1.419, 3.0814, 2.2747, -1.5878, 2.9911, 0.177, -2.5055, 2.8818, 4.7444, 1.3483, 2.3528, 6.0999, 0.4838, 3.481, 4.5391, 0.4718, 2.0681, 1.0413, -2.9849, 2.3543, 3.9299, -1.7328, 1.1494, 2.3687, -2.734, 2.5729, -0.0463, 2.323, 2.8449, 1.8063, 1.8788, 2.6862, -0.2733, 1.344, -1.4897, -2.0239, -2.8933, -2.3373, -3.6333, -2.2624, -1.6189, -3.1121, -2.5666, -1.4897, -2.0239, -2.8933, -2.0489, -0.9812, -3.014, -2.3373, -3.6333, -2.2624, -2.0395, 0.8474, -2.9039, -1.2285, 2.4662, -2.4325, -2.3777, 2.1645, -2.0413, 2.1308, -0.8122, -3.0291, 0.0444, -0.8451, -3.3989, 1.2123, -2.2074, -2.6786, -3.2503, -1.9438, -0.0444, -2.9895, -0.0523, 0.8233, -3.048, -2.4163, 1.5053, -2.9895, -0.0523, 0.8233, -2.9635, 2.0631, -0.0469, -2.7222, 1.5834, 1.3251, -2.8218, -0.0908, -2.3316, -3.1045, -0.0443, -0.8667, -3.1353, -2.4738, -1.533, -0.6296, -2.1168, 3.0274, -0.0319, -0.907, 3.3884, 0.6607, -2.1929, 2.7904, 0.0817, 0.7564, 3.232, -1.0522, 1.986, 2.2721, 0.5064, 1.8556, 2.7003, 3.0694, 0.3768, 0.2917, 3.39, -0.1345, -0.5433, 3.0799, -1.877, -0.2485, 2.332, -3.2513, 1.4491, 1.8096, -3.8731, 0.517, 1.7532, -3.8773, 1.1862, 2.332, -3.2513, 1.4491, 2.4159, -3.3908, 0.0683, 1.8096, -3.8731, 0.517, 3.0694, 0.3768, 0.2917, 3.2927, 2.696, 1.0915, 3.6836, 2.3906, 0.1427, 0.8528, -5.1659, -1.2387, 0.5989, -4.5033, -1.836, -0.129, -6.0155, -1.6627, 1.6723, -4.1113, -1.5062, 2.2303, -3.7001, -1.8804, 1.5835, -3.611, -1.9401, 2.6485, -2.4319, -2.56, 2.1308, -0.8122, -3.0291, 1.2123, -2.2074, -2.6786, -1.2211, -6.6432, -1.3733, -2.3738, -5.4707, -1.3892, -1.8677, -6.7222, 0.0053, 1.2123, -2.2074, -2.6786, 0.0444, -0.8451, -3.3989, -0.5191, -2.1018, -2.9939, -0.9423, -5.2303, -2.2174, -0.0313, -3.8492, -2.3572, -0.9004, -3.6647, -2.5803, -2.9895, -0.0523, 0.8233, -2.9289, -0.1232, 2.5316, -3.048, -2.4163, 1.5053, -3.1353, -2.4738, -1.533, -3.1045, -0.0443, -0.8667, -3.2503, -1.9438, -0.0444, -2.8602, -5.2902, -0.0034, -3.0733, -4.4498, -0.8737, -3.2063, -3.9919, -0.0118, -0.1504, -6.0468, 1.6347, 0.6052, -4.4095, 1.8566, 0.8249, -4.8431, 1.2373, -2.3213, -3.5933, 2.2613, -1.5434, -2.0626, 2.9798, -1.6095, -3.1051, 2.5592, -2.3213, -3.5933, 2.2613, -2.2391, -1.0013, 3.2378, -1.5434, -2.0626, 2.9798, 3.0799, -1.877, -0.2485, 3.39, -0.1345, -0.5433, 3.4106, -2.2767, -1.2285, -0.0319, -0.907, 3.3884, 1.8364, -0.871, 2.91, 0.6607, -2.1929, 2.7904, 1.7532, -3.8773, 1.1862, 1.5121, -3.4321, 1.801, 2.332, -3.2513, 1.4491, -0.9484, -5.3598, 2.2535, -1.6095, -3.1051, 2.5592, -0.8975, -3.7031, 2.6018, -0.9484, -5.3598, 2.2535, -2.3213, -3.5933, 2.2613, -1.6095, -3.1051, 2.5592, 2.4159, -3.3908, 0.0683, 1.3907, -4.3898, 0.264, 1.8096, -3.8731, 0.517, 2.4159, -3.3908, 0.0683, 1.5973, -4.4989, -0.523, 1.3907, -4.3898, 0.264, 0.6607, -2.1929, 2.7904, 1.8364, -0.871, 2.91, 2.1556, -2.2057, 2.3132, -0.6892, -7.3327, -0.0456, 0.3707, -6.5026, 0.2358, 0.1584, -6.6788, -0.712, 2.7182, -1.9153, 1.6333, 2.332, -3.2513, 1.4491, 2.1556, -2.2057, 2.3132, -0.8975, -3.7031, 2.6018, -1.5434, -2.0626, 2.9798, -0.6296, -2.1168, 3.0274, -0.8975, -3.7031, 2.6018, -1.6095, -3.1051, 2.5592, -1.5434, -2.0626, 2.9798, -0.5191, -2.1018, -2.9939, -1.6189, -3.1121, -2.5666, -0.9004, -3.6647, -2.5803, -0.5191, -2.1018, -2.9939, -1.4897, -2.0239, -2.8933, -1.6189, -3.1121, -2.5666, 3.4106, -2.2767, -1.2285, 2.2303, -3.7001, -1.8804, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 3.4189, -1.5098, -2.1519, 2.6485, -2.4319, -2.56, 2.2303, -3.7001, -1.8804, 3.4106, -2.2767, -1.2285, 3.4189, -1.5098, -2.1519, 0.6919, 3.2703, 2.2188, 1.3747, 1.7762, 2.618, 0.5064, 1.8556, 2.7003, 0.6919, 3.2703, 2.2188, 1.5121, 2.8416, 2.3106, 1.3747, 1.7762, 2.618, -2.7186, 2.9035, -1.3821, -2.8949, 1.7465, -1.4628, -2.3777, 2.1645, -2.0413, 3.6143, 2.6541, -0.7787, 3.481, 4.5391, 0.4718, 3.3017, 4.3689, -0.5397, 3.6143, 2.6541, -0.7787, 3.6836, 2.3906, 0.1427, 3.481, 4.5391, 0.4718, 0.7824, 7.1818, -0.4789, -0.2548, 6.3653, 0.3115, -0.2609, 6.3493, -0.8464, 0.7824, 7.1818, -0.4789, 0.9325, 6.924, 0.4365, -0.2548, 6.3653, 0.3115, -0.1504, -6.0468, 1.6347, -1.2352, -6.7568, 1.4079, -0.9484, -5.3598, 2.2535, 1.8096, -3.8731, 0.517, 0.8249, -4.8431, 1.2373, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 1.3907, -4.3898, 0.264, 0.9197, -5.2139, 0.5803, 0.8249, -4.8431, 1.2373, 1.8096, -3.8731, 0.517, 1.3907, -4.3898, 0.264, -0.1302, -3.9225, 2.4564, 1.5121, -3.4321, 1.801, 0.6052, -4.4095, 1.8566, -0.1302, -3.9225, 2.4564, 0.6607, -2.1929, 2.7904, 1.5121, -3.4321, 1.801, 2.4159, -3.3908, 0.0683, 2.791, -2.0828, 0.6971, 3.0799, -1.877, -0.2485, 1.5973, -4.4989, -0.523, 1.6723, -4.1113, -1.5062, 0.8528, -5.1659, -1.2387, -3.0246, -4.4027, 0.8468, -2.3213, -3.5933, 2.2613, -2.3434, -5.4566, 1.3758, -3.0246, -4.4027, 0.8468, -3.048, -2.4163, 1.5053, -2.3213, -3.5933, 2.2613, -1.2211, -6.6432, -1.3733, -0.129, -6.0155, -1.6627, -0.9423, -5.2303, -2.2174, -3.0733, -4.4498, -0.8737, -2.3373, -3.6333, -2.2624, -3.1353, -2.4738, -1.533, -3.0733, -4.4498, -0.8737, -2.3738, -5.4707, -1.3892, -2.3373, -3.6333, -2.2624, 1.5835, -3.611, -1.9401, -0.0313, -3.8492, -2.3572, 0.5989, -4.5033, -1.836, 1.5835, -3.611, -1.9401, 1.2123, -2.2074, -2.6786, -0.0313, -3.8492, -2.3572, 2.5729, -0.0463, 2.323, 1.8364, -0.871, 2.91, 1.9677, 0.877, 2.8315, -2.9289, -0.1232, 2.5316, -2.1314, 0.7603, 3.0046, -2.2391, -1.0013, 3.2378, -2.0395, 0.8474, -2.9039, -2.8218, -0.0908, -2.3316, -2.0489, -0.9812, -3.014, 2.1308, -0.8122, -3.0291, 2.9911, 0.177, -2.5055, 2.0681, 1.0413, -2.9849, 3.2927, 2.696, 1.0915, 2.2766, 3.3307, 2.1367, 2.8818, 4.7444, 1.3483, 3.2927, 2.696, 1.0915, 2.8449, 1.8063, 1.8788, 2.2766, 3.3307, 2.1367, -2.9635, 2.0631, -0.0469, -2.7435, 3.293, -0.5137, -2.3926, 3.3478, 0.3255, 2.3528, 6.0999, 0.4838, 1.5686, 6.857, -0.4389, 2.4165, 5.7335, -0.6508, -2.3162, 3.6228, -0.9721, -1.137, 5.0901, -0.2048, -1.8973, 4.0438, -0.0231, -2.3162, 3.6228, -0.9721, -1.544, 3.8438, -1.471, -1.137, 5.0901, -0.2048, 0.4374, 4.5707, -2.224, 0.9515, 6.8097, -1.0683, -0.1803, 5.4522, -1.6664, 0.4374, 4.5707, -2.224, 1.4379, 5.2295, -1.8152, 0.9515, 6.8097, -1.0683, -1.2352, -6.7568, 1.4079, 0.3707, -6.5026, 0.2358, -0.6892, -7.3327, -0.0456, -1.2352, -6.7568, 1.4079, -0.1504, -6.0468, 1.6347, 0.3707, -6.5026, 0.2358, 0.6607, -2.1929, 2.7904, -0.8975, -3.7031, 2.6018, -0.6296, -2.1168, 3.0274, 0.6607, -2.1929, 2.7904, -0.1302, -3.9225, 2.4564, -0.8975, -3.7031, 2.6018, 2.791, -2.0828, 0.6971, 2.332, -3.2513, 1.4491, 2.7182, -1.9153, 1.6333, 2.791, -2.0828, 0.6971, 2.4159, -3.3908, 0.0683, 2.332, -3.2513, 1.4491, 1.6723, -4.1113, -1.5062, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 1.6723, -4.1113, -1.5062, 1.5973, -4.4989, -0.523, 2.6374, -3.7219, -1.0723, -3.048, -2.4163, 1.5053, -3.2063, -3.9919, -0.0118, -3.2503, -1.9438, -0.0444, -3.048, -2.4163, 1.5053, -3.0246, -4.4027, 0.8468, -3.2063, -3.9919, -0.0118, -0.129, -6.0155, -1.6627, -0.6892, -7.3327, -0.0456, 0.1584, -6.6788, -0.712, -0.129, -6.0155, -1.6627, -1.2211, -6.6432, -1.3733, -0.6892, -7.3327, -0.0456, 1.2123, -2.2074, -2.6786, 2.2303, -3.7001, -1.8804, 2.6485, -2.4319, -2.56, 1.2123, -2.2074, -2.6786, 1.5835, -3.611, -1.9401, 2.2303, -3.7001, -1.8804, 3.3947, 0.8197, -1.419, 3.6836, 2.3906, 0.1427, 3.6143, 2.6541, -0.7787, 3.3947, 0.8197, -1.419, 3.39, -0.1345, -0.5433, 3.6836, 2.3906, 0.1427, -2.1314, 0.7603, 3.0046, -2.7222, 1.5834, 1.3251, -2.0691, 2.5964, 1.5185, -2.1314, 0.7603, 3.0046, -2.9289, -0.1232, 2.5316, -2.7222, 1.5834, 1.3251, 2.9911, 0.177, -2.5055, 2.6485, -2.4319, -2.56, 3.4189, -1.5098, -2.1519, 2.9911, 0.177, -2.5055, 2.1308, -0.8122, -3.0291, 2.6485, -2.4319, -2.56, -2.7435, 3.293, -0.5137, -2.8949, 1.7465, -1.4628, -2.7186, 2.9035, -1.3821, -2.7435, 3.293, -0.5137, -2.9635, 2.0631, -0.0469, -2.8949, 1.7465, -1.4628, -0.1459, 3.4745, -2.3857, 1.1494, 2.3687, -2.734, 0.549, 3.3793, -2.5003, -0.1459, 3.4745, -2.3857, -1.2285, 2.4662, -2.4325, 1.1494, 2.3687, -2.734, 2.3543, 3.9299, -1.7328, 3.6143, 2.6541, -0.7787, 3.3017, 4.3689, -0.5397, 2.3543, 3.9299, -1.7328, 3.0814, 2.2747, -1.5878, 3.6143, 2.6541, -0.7787, 1.5686, 6.857, -0.4389, 0.9325, 6.924, 0.4365, 0.7824, 7.1818, -0.4789, 1.5686, 6.857, -0.4389, 2.3528, 6.0999, 0.4838, 0.9325, 6.924, 0.4365, -1.544, 3.8438, -1.471, -2.7186, 2.9035, -1.3821, -2.3777, 2.1645, -2.0413, -1.544, 3.8438, -1.471, -2.3162, 3.6228, -0.9721, -2.7186, 2.9035, -1.3821, 1.4379, 5.2295, -1.8152, 0.549, 3.3793, -2.5003, 1.1494, 2.3687, -2.734, 1.4379, 5.2295, -1.8152, 0.4374, 4.5707, -2.224, 0.549, 3.3793, -2.5003, 2.1556, -2.2057, 2.3132, 1.5121, -3.4321, 1.801, 0.6607, -2.1929, 2.7904, 2.1556, -2.2057, 2.3132, 2.332, -3.2513, 1.4491, 1.5121, -3.4321, 1.801, 3.4106, -2.2767, -1.2285, 2.4159, -3.3908, 0.0683, 3.0799, -1.877, -0.2485, 3.4106, -2.2767, -1.2285, 2.6374, -3.7219, -1.0723, 2.4159, -3.3908, 0.0683, 0.1584, -6.6788, -0.712, 1.5973, -4.4989, -0.523, 0.8528, -5.1659, -1.2387, 0.1584, -6.6788, -0.712, 0.3707, -6.5026, 0.2358, 1.5973, -4.4989, -0.523, -3.2503, -1.9438, -0.0444, -3.0733, -4.4498, -0.8737, -3.1353, -2.4738, -1.533, -3.2503, -1.9438, -0.0444, -3.2063, -3.9919, -0.0118, -3.0733, -4.4498, -0.8737, -0.5191, -2.1018, -2.9939, -0.0313, -3.8492, -2.3572, 1.2123, -2.2074, -2.6786, -0.5191, -2.1018, -2.9939, -0.9004, -3.6647, -2.5803, -0.0313, -3.8492, -2.3572, 3.4189, -1.5098, -2.1519, 3.39, -0.1345, -0.5433, 3.3947, 0.8197, -1.419, 3.4189, -1.5098, -2.1519, 3.4106, -2.2767, -1.2285, 3.39, -0.1345, -0.5433, 2.7182, -1.9153, 1.6333, 1.8364, -0.871, 2.91, 2.5729, -0.0463, 2.323, 2.7182, -1.9153, 1.6333, 2.1556, -2.2057, 2.3132, 1.8364, -0.871, 2.91, -2.3777, 2.1645, -2.0413, -2.8218, -0.0908, -2.3316, -2.0395, 0.8474, -2.9039, -2.3777, 2.1645, -2.0413, -2.8949, 1.7465, -1.4628, -2.8218, -0.0908, -2.3316, 3.481, 4.5391, 0.4718, 3.2927, 2.696, 1.0915, 2.8818, 4.7444, 1.3483, 3.481, 4.5391, 0.4718, 3.6836, 2.3906, 0.1427, 3.2927, 2.696, 1.0915, 0.6919, 3.2703, 2.2188, -1.0522, 1.986, 2.2721, -0.2637, 3.5029, 1.8153, 0.6919, 3.2703, 2.2188, 0.5064, 1.8556, 2.7003, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -2.9635, 2.0631, -0.0469, -2.3926, 3.3478, 0.3255, -2.0691, 2.5964, 1.5185, -2.7222, 1.5834, 1.3251, -2.9635, 2.0631, -0.0469, -0.2548, 6.3653, 0.3115, 1.4341, 6.0081, 1.2447, 0.4487, 4.8708, 1.6514, -0.2548, 6.3653, 0.3115, 0.9325, 6.924, 0.4365, 1.4341, 6.0081, 1.2447, 3.3017, 4.3689, -0.5397, 2.3528, 6.0999, 0.4838, 2.4165, 5.7335, -0.6508, 3.3017, 4.3689, -0.5397, 3.481, 4.5391, 0.4718, 2.3528, 6.0999, 0.4838, -0.2609, 6.3493, -0.8464, -1.137, 5.0901, -0.2048, -1.544, 3.8438, -1.471, -0.2609, 6.3493, -0.8464, -0.2548, 6.3653, 0.3115, -1.137, 5.0901, -0.2048, 0.7824, 7.1818, -0.4789, -0.1803, 5.4522, -1.6664, 0.9515, 6.8097, -1.0683, 0.7824, 7.1818, -0.4789, -0.2609, 6.3493, -0.8464, -0.1803, 5.4522, -1.6664, 2.4165, 5.7335, -0.6508, 0.9515, 6.8097, -1.0683, 1.4379, 5.2295, -1.8152, 2.4165, 5.7335, -0.6508, 1.5686, 6.857, -0.4389, 0.9515, 6.8097, -1.0683, 0.4374, 4.5707, -2.224, -1.2285, 2.4662, -2.4325, -0.1459, 3.4745, -2.3857, 0.4374, 4.5707, -2.224, -0.1803, 5.4522, -1.6664, -1.2285, 2.4662, -2.4325, -1.8973, 4.0438, -0.0231, -0.7059, 4.7856, 0.8682, -1.6818, 3.3981, 1.0198, -1.8973, 4.0438, -0.0231, -1.137, 5.0901, -0.2048, -0.7059, 4.7856, 0.8682, -2.3162, 3.6228, -0.9721, -2.3926, 3.3478, 0.3255, -2.7435, 3.293, -0.5137, -2.3162, 3.6228, -0.9721, -1.8973, 4.0438, -0.0231, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -0.2637, 3.5029, 1.8153, -1.0522, 1.986, 2.2721, -1.6818, 3.3981, 1.0198, -0.7059, 4.7856, 0.8682, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 2.8818, 4.7444, 1.3483, 2.2766, 3.3307, 2.1367, 0.4487, 4.8708, 1.6514, 1.4341, 6.0081, 1.2447, 2.8818, 4.7444, 1.3483, 3.0814, 2.2747, -1.5878, 2.0681, 1.0413, -2.9849, 2.9911, 0.177, -2.5055, 3.0814, 2.2747, -1.5878, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 0.0444, -0.8451, -3.3989, -2.0395, 0.8474, -2.9039, -2.0489, -0.9812, -3.014, 0.0444, -0.8451, -3.3989, 0.0399, 0.8971, -3.3453, -2.0395, 0.8474, -2.9039, -2.2391, -1.0013, 3.2378, 0.0817, 0.7564, 3.232, -0.0319, -0.907, 3.3884, -2.2391, -1.0013, 3.2378, -2.1314, 0.7603, 3.0046, 0.0817, 0.7564, 3.232, 2.5729, -0.0463, 2.323, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 2.5729, -0.0463, 2.323, 1.9677, 0.877, 2.8315, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 3.0694, 0.3768, 0.2917, 2.6862, -0.2733, 1.344, 2.8449, 1.8063, 1.8788, 3.2927, 2.696, 1.0915, 3.0694, 0.3768, 0.2917, 2.1308, -0.8122, -3.0291, 0.0399, 0.8971, -3.3453, 0.0444, -0.8451, -3.3989, 2.1308, -0.8122, -3.0291, 2.0681, 1.0413, -2.9849, 0.0399, 0.8971, -3.3453, -2.0489, -0.9812, -3.014, -3.1353, -2.4738, -1.533, -2.3373, -3.6333, -2.2624, -2.0489, -0.9812, -3.014, -2.8218, -0.0908, -2.3316, -3.1353, -2.4738, -1.533, -2.9289, -0.1232, 2.5316, -2.3213, -3.5933, 2.2613, -3.048, -2.4163, 1.5053, -2.9289, -0.1232, 2.5316, -2.2391, -1.0013, 3.2378, -2.3213, -3.5933, 2.2613, -0.0319, -0.907, 3.3884, 1.9677, 0.877, 2.8315, 1.8364, -0.871, 2.91, -0.0319, -0.907, 3.3884, 0.0817, 0.7564, 3.232, 1.9677, 0.877, 2.8315, 2.6862, -0.2733, 1.344, 3.0799, -1.877, -0.2485, 2.791, -2.0828, 0.6971, 2.6862, -0.2733, 1.344, 3.0694, 0.3768, 0.2917, 3.0799, -1.877, -0.2485, 0.8528, -5.1659, -1.2387, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, 0.8528, -5.1659, -1.2387, 1.6723, -4.1113, -1.5062, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, -0.9423, -5.2303, -2.2174, -0.129, -6.0155, -1.6627, 0.5989, -4.5033, -1.836, -0.0313, -3.8492, -2.3572, -0.9423, -5.2303, -2.2174, -1.2211, -6.6432, -1.3733, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -1.2211, -6.6432, -1.3733, -0.9423, -5.2303, -2.2174, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -2.3738, -5.4707, -1.3892, -3.0733, -4.4498, -0.8737, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -1.8677, -6.7222, 0.0053, -2.8602, -5.2902, -0.0034, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -0.9484, -5.3598, 2.2535, -1.2352, -6.7568, 1.4079, -2.3434, -5.4566, 1.3758, -2.3213, -3.5933, 2.2613, -0.9484, -5.3598, 2.2535, -0.1504, -6.0468, 1.6347, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, -0.1504, -6.0468, 1.6347, -0.9484, -5.3598, 2.2535, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 0.6052, -4.4095, 1.8566, 1.5121, -3.4321, 1.801, 1.7532, -3.8773, 1.1862, -0.6892, -7.3327, -0.0456, -1.8677, -6.7222, 0.0053, -1.2352, -6.7568, 1.4079) [node name="RockC" instance=ExtResource("1_pb27A")] collision_mask = 3 [node name="Rock3" parent="." index="0"] surface_material_override/0 = ExtResource("2_pcex8") [node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"] shape = SubResource("ConcavePolygonShape3D_ycsjC") ================================================ FILE: project/demo/assets/models/Tunnel.glb.import ================================================ [remap] importer="scene" importer_version=1 type="PackedScene" uid="uid://jib546bbort5" path="res://.godot/imported/Tunnel.glb-09729773791c3078e933c866961e1b60.scn" [deps] source_file="res://demo/assets/models/Tunnel.glb" dest_files=["res://.godot/imported/Tunnel.glb-09729773791c3078e933c866961e1b60.scn"] [params] nodes/root_type="StaticBody3D" nodes/root_name="Scene Root" nodes/apply_root_scale=true nodes/root_scale=1.0 nodes/import_as_skeleton_bones=false nodes/use_node_type_suffixes=true meshes/ensure_tangents=true meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 meshes/force_disable_compression=true skins/use_named_skins=true animation/import=true animation/fps=30 animation/trimming=false animation/remove_immutable_tracks=true animation/import_rest_as_RESET=false import_script/path="" _subresources={ "materials": { "material_0": { "use_external/enabled": false, "use_external/path": "uid://nbbdrx8vma80" } } } gltf/naming_version=0 gltf/embedded_image_handling=1 ================================================ FILE: project/demo/assets/models/Tunnel.tscn ================================================ [gd_scene load_steps=4 format=3 uid="uid://vvayjv3rbx1d"] [ext_resource type="PackedScene" uid="uid://jib546bbort5" path="res://demo/assets/models/Tunnel.glb" id="1_1sr0i"] [ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_lkb1r"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_bh6xx"] data = PackedVector3Array(266.67, 5.3424, -18.2649, 266.427, 5.495, -16.0799, 266.642, 5.495, -18.2666, 267.292, 1.9054, -18.2263, 267.068, 1.9054, -15.9524, 266.67, 5.3424, -18.2649, 267.068, 1.9054, -15.9524, 266.427, 5.495, -16.0799, 266.67, 5.3424, -18.2649, 264.802, 8.7484, -18.3807, 264.54, 8.8761, -16.4552, 264.73, 8.8761, -18.3851, 266.642, 5.495, -18.2666, 266.427, 5.495, -16.0799, 264.802, 8.7484, -18.3807, 266.427, 5.495, -16.0799, 264.54, 8.8761, -16.4552, 264.802, 8.7484, -18.3807, 261.76, 11.7626, -18.5693, 261.518, 11.852, -17.0563, 261.668, 11.852, -18.575, 264.73, 8.8761, -18.3851, 264.54, 8.8761, -16.4552, 261.76, 11.7626, -18.5693, 264.54, 8.8761, -16.4552, 261.518, 11.852, -17.0563, 261.76, 11.7626, -18.5693, 257.712, 14.2028, -18.8203, 257.536, 14.2498, -17.8483, 257.633, 14.2498, -18.8252, 261.668, 11.852, -18.575, 261.518, 11.852, -17.0563, 257.712, 14.2028, -18.8203, 261.518, 11.852, -17.0563, 257.536, 14.2498, -17.8483, 257.712, 14.2028, -18.8203, 252.892, 15.9187, -19.1192, 252.826, 15.9302, -18.7853, 252.859, 15.9302, -19.1212, 257.633, 14.2498, -18.8252, 257.536, 14.2498, -17.8483, 252.892, 15.9187, -19.1192, 257.536, 14.2498, -17.8483, 252.826, 15.9302, -18.7853, 252.892, 15.9187, -19.1192, 252.859, 15.9302, -19.1212, 252.826, 15.9302, -18.7853, 250.357, 16.3438, -19.2763, 252.823, -12.1253, -19.1234, 252.826, -12.1193, -18.7853, 252.859, -12.1193, -19.1212, 250.357, -12.5329, -19.2763, 252.826, -12.1193, -18.7853, 252.823, -12.1253, -19.1234, 257.538, -10.4722, -18.8311, 257.536, -10.4389, -17.8483, 257.633, -10.4389, -18.8252, 252.859, -12.1193, -19.1212, 252.826, -12.1193, -18.7853, 257.538, -10.4722, -18.8311, 252.826, -12.1193, -18.7853, 257.536, -10.4389, -17.8483, 257.538, -10.4722, -18.8311, 261.546, -8.1137, -18.5826, 261.518, -8.0411, -17.0563, 261.668, -8.0411, -18.575, 257.633, -10.4389, -18.8252, 257.536, -10.4389, -17.8483, 261.546, -8.1137, -18.5826, 257.536, -10.4389, -17.8483, 261.518, -8.0411, -17.0563, 261.546, -8.1137, -18.5826, 264.614, -5.1782, -18.3923, 264.54, -5.0652, -16.4552, 264.73, -5.0652, -18.3851, 261.668, -8.0411, -18.575, 261.518, -8.0411, -17.0563, 264.614, -5.1782, -18.3923, 261.518, -8.0411, -17.0563, 264.54, -5.0652, -16.4552, 264.614, -5.1782, -18.3923, 266.56, -1.8284, -18.2717, 266.427, -1.6842, -16.0799, 266.642, -1.6842, -18.2666, 264.73, -5.0652, -18.3851, 264.54, -5.0652, -16.4552, 266.56, -1.8284, -18.2717, 264.54, -5.0652, -16.4552, 266.427, -1.6842, -16.0799, 266.56, -1.8284, -18.2717, 267.263, 1.7469, -18.2281, 267.068, 1.9054, -15.9524, 267.292, 1.9054, -18.2263, 266.642, -1.6842, -18.2666, 266.427, -1.6842, -16.0799, 267.263, 1.7469, -18.2281, 266.427, -1.6842, -16.0799, 267.068, 1.9054, -15.9524, 267.263, 1.7469, -18.2281, 267.068, 1.9054, -15.9524, 251.436, 5.495, 33.3375, 266.427, 5.495, -16.0799, 252.04, 1.9054, 33.5877, 251.436, 5.495, 33.3375, 267.068, 1.9054, -15.9524, 266.427, 5.495, -16.0799, 249.659, 8.876, 32.6014, 264.54, 8.8761, -16.4552, 251.436, 5.495, 33.3375, 249.659, 8.876, 32.6014, 266.427, 5.495, -16.0799, 264.54, 8.8761, -16.4552, 246.812, 11.8519, 31.4223, 261.518, 11.852, -17.0563, 249.659, 8.876, 32.6014, 246.812, 11.8519, 31.4223, 264.54, 8.8761, -16.4552, 261.518, 11.852, -17.0563, 243.061, 14.2498, 29.8687, 257.536, 14.2498, -17.8483, 246.812, 11.8519, 31.4223, 243.061, 14.2498, 29.8687, 261.518, 11.852, -17.0563, 257.536, 14.2498, -17.8483, 238.624, 15.9302, 28.0308, 252.826, 15.9302, -18.7853, 243.061, 14.2498, 29.8687, 238.624, 15.9302, 28.0308, 257.536, 14.2498, -17.8483, 250.357, 16.3438, -19.2763, 252.826, 15.9302, -18.7853, 247.551, 16.7956, -19.4503, 252.826, 15.9302, -18.7853, 233.759, 16.7956, 26.0154, 247.551, 16.7956, -19.4503, 238.624, 15.9302, 28.0308, 233.759, 16.7956, 26.0154, 252.826, 15.9302, -18.7853, 247.505, 16.7956, -19.4531, 228.748, 16.7956, 23.9398, 242.014, 16.7956, -19.7936, 247.551, 16.7956, -19.4503, 233.759, 16.7956, 26.0154, 247.505, 16.7956, -19.4531, 233.759, 16.7956, 26.0154, 228.748, 16.7956, 23.9398, 247.505, 16.7956, -19.4531, 241.88, 16.774, -19.8019, 223.882, 15.9302, 21.9244, 236.638, 15.9302, -20.1269, 242.014, 16.7956, -19.7936, 228.748, 16.7956, 23.9398, 241.88, 16.774, -19.8019, 228.748, 16.7956, 23.9398, 223.882, 15.9302, 21.9244, 241.88, 16.774, -19.8019, 236.433, 15.8598, -20.1396, 219.445, 14.2498, 20.0865, 231.736, 14.2498, -20.4309, 236.638, 15.9302, -20.1269, 223.882, 15.9302, 21.9244, 236.433, 15.8598, -20.1396, 223.882, 15.9302, 21.9244, 219.445, 14.2498, 20.0865, 236.433, 15.8598, -20.1396, 231.497, 14.1113, -20.4457, 215.694, 11.8519, 18.5328, 227.592, 11.8519, -20.6878, 231.736, 14.2498, -20.4309, 219.445, 14.2498, 20.0865, 231.497, 14.1113, -20.4457, 219.445, 14.2498, 20.0865, 215.694, 11.8519, 18.5328, 231.497, 14.1113, -20.4457, 227.367, 11.6395, -20.7018, 212.848, 8.876, 17.3537, 224.447, 8.876, -20.8828, 227.592, 11.8519, -20.6878, 215.694, 11.8519, 18.5328, 227.367, 11.6395, -20.7018, 215.694, 11.8519, 18.5328, 212.848, 8.876, 17.3537, 227.367, 11.6395, -20.7018, 224.286, 8.6, -20.8928, 211.071, 5.4951, 16.6177, 222.483, 5.4951, -21.0046, 224.447, 8.876, -20.8828, 212.848, 8.876, 17.3537, 224.286, 8.6, -20.8928, 212.848, 8.876, 17.3537, 211.071, 5.495, 16.6177, 224.286, 8.6, -20.8928, 222.425, 5.18, -21.0082, 210.467, 1.9054, 16.3675, 221.816, 1.9054, -21.046, 222.483, 5.4951, -21.0046, 211.071, 5.495, 16.6177, 222.425, 5.18, -21.0082, 211.071, 5.495, 16.6177, 210.467, 1.9054, 16.3675, 222.425, 5.18, -21.0082, 221.876, 1.5852, -21.0423, 211.071, -1.6842, 16.6177, 222.483, -1.6842, -21.0046, 221.816, 1.9054, -21.046, 210.467, 1.9054, 16.3675, 221.876, 1.5852, -21.0423, 210.467, 1.9054, 16.3675, 211.071, -1.6842, 16.6177, 221.876, 1.5852, -21.0423, 222.652, -1.975, -20.9941, 212.848, -5.0652, 17.3537, 224.447, -5.0652, -20.8828, 222.483, -1.6842, -21.0046, 211.071, -1.6842, 16.6177, 222.652, -1.975, -20.9941, 211.071, -1.6842, 16.6177, 212.848, -5.0652, 17.3537, 222.652, -1.975, -20.9941, 224.694, -5.2991, -20.8675, 215.694, -8.0411, 18.5329, 227.592, -8.0411, -20.6878, 224.447, -5.0652, -20.8828, 212.848, -5.0652, 17.3537, 224.694, -5.2991, -20.8675, 212.848, -5.0652, 17.3537, 215.694, -8.0411, 18.5329, 224.694, -5.2991, -20.8675, 227.872, -8.2033, -20.6705, 219.445, -10.4389, 20.0865, 231.736, -10.4389, -20.4309, 227.592, -8.0411, -20.6878, 215.694, -8.0411, 18.5329, 227.872, -8.2033, -20.6705, 215.694, -8.0411, 18.5329, 219.445, -10.4389, 20.0865, 227.872, -8.2033, -20.6705, 232.001, -10.5298, -20.4144, 223.882, -12.1193, 21.9244, 236.638, -12.1193, -20.1269, 231.736, -10.4389, -20.4309, 219.445, -10.4389, 20.0865, 232.001, -10.5298, -20.4144, 219.445, -10.4389, 20.0865, 223.882, -12.1193, 21.9244, 232.001, -10.5298, -20.4144, 236.848, -12.153, -20.1139, 228.748, -12.9847, 23.9398, 242.014, -12.9847, -19.7936, 236.638, -12.1193, -20.1269, 223.882, -12.1193, 21.9244, 236.848, -12.153, -20.1139, 223.882, -12.1193, 21.9244, 228.748, -12.9847, 23.9398, 236.848, -12.153, -20.1139, 242.142, -12.9847, -19.7857, 233.759, -12.9847, 26.0154, 247.551, -12.9847, -19.4503, 242.014, -12.9847, -19.7936, 228.748, -12.9847, 23.9398, 242.142, -12.9847, -19.7857, 228.748, -12.9847, 23.9398, 233.759, -12.9847, 26.0154, 242.142, -12.9847, -19.7857, 247.592, -12.9781, -19.4478, 238.624, -12.1193, 28.0308, 252.826, -12.1193, -18.7853, 247.592, -12.9781, -19.4478, 252.826, -12.1193, -18.7853, 250.357, -12.5329, -19.2763, 247.551, -12.9847, -19.4503, 233.759, -12.9847, 26.0154, 247.592, -12.9781, -19.4478, 233.759, -12.9847, 26.0154, 238.624, -12.1193, 28.0308, 247.592, -12.9781, -19.4478, 252.826, -12.1193, -18.7853, 243.061, -10.4389, 29.8687, 257.536, -10.4389, -17.8483, 238.624, -12.1193, 28.0308, 243.061, -10.4389, 29.8687, 252.826, -12.1193, -18.7853, 257.536, -10.4389, -17.8483, 246.812, -8.0411, 31.4223, 261.518, -8.0411, -17.0563, 243.061, -10.4389, 29.8687, 246.812, -8.0411, 31.4223, 257.536, -10.4389, -17.8483, 261.518, -8.0411, -17.0563, 249.659, -5.0652, 32.6014, 264.54, -5.0652, -16.4552, 246.812, -8.0411, 31.4223, 249.659, -5.0652, 32.6014, 261.518, -8.0411, -17.0563, 264.54, -5.0652, -16.4552, 251.436, -1.6842, 33.3375, 266.427, -1.6842, -16.0799, 249.659, -5.0652, 32.6014, 251.436, -1.6842, 33.3375, 264.54, -5.0652, -16.4552, 266.427, -1.6842, -16.0799, 252.04, 1.9054, 33.5877, 267.068, 1.9054, -15.9524, 251.436, -1.6842, 33.3375, 252.04, 1.9054, 33.5877, 266.427, -1.6842, -16.0799, 252.04, 1.9054, 33.5877, 227.092, 5.495, 78.8808, 251.436, 5.495, 33.3375, 227.636, 1.9054, 79.2441, 227.092, 5.495, 78.8808, 252.04, 1.9054, 33.5877, 251.436, 5.495, 33.3375, 225.493, 8.876, 77.8123, 249.659, 8.876, 32.6014, 227.092, 5.495, 78.8808, 225.493, 8.876, 77.8123, 251.436, 5.495, 33.3375, 249.659, 8.876, 32.6014, 222.931, 11.8519, 76.1005, 246.812, 11.8519, 31.4223, 225.493, 8.876, 77.8123, 222.931, 11.8519, 76.1005, 249.659, 8.876, 32.6014, 246.812, 11.8519, 31.4223, 219.556, 14.2498, 73.8449, 243.061, 14.2498, 29.8687, 222.931, 11.8519, 76.1005, 219.556, 14.2498, 73.8449, 246.812, 11.8519, 31.4223, 243.061, 14.2498, 29.8687, 215.562, 15.9302, 71.1767, 238.624, 15.9302, 28.0308, 219.556, 14.2498, 73.8449, 215.562, 15.9302, 71.1767, 243.061, 14.2498, 29.8687, 238.624, 15.9302, 28.0308, 211.184, 16.7956, 68.2508, 233.759, 16.7956, 26.0154, 215.562, 15.9302, 71.1767, 211.184, 16.7956, 68.2508, 238.624, 15.9302, 28.0308, 233.759, 16.7956, 26.0154, 206.674, 16.7956, 65.2375, 228.748, 16.7956, 23.9398, 211.184, 16.7956, 68.2508, 206.674, 16.7956, 65.2375, 233.759, 16.7956, 26.0154, 228.748, 16.7956, 23.9398, 202.295, 15.9302, 62.3116, 223.882, 15.9302, 21.9244, 206.674, 16.7956, 65.2375, 202.295, 15.9302, 62.3116, 228.748, 16.7956, 23.9398, 223.882, 15.9302, 21.9244, 198.302, 14.2498, 59.6434, 219.445, 14.2498, 20.0865, 202.295, 15.9302, 62.3116, 198.302, 14.2498, 59.6434, 223.882, 15.9302, 21.9244, 219.445, 14.2498, 20.0865, 194.926, 11.8519, 57.3878, 215.694, 11.8519, 18.5328, 198.302, 14.2498, 59.6434, 194.926, 11.8519, 57.3878, 219.445, 14.2498, 20.0865, 215.694, 11.8519, 18.5328, 192.364, 8.876, 55.676, 212.848, 8.876, 17.3537, 194.926, 11.8519, 57.3878, 192.364, 8.876, 55.676, 215.694, 11.8519, 18.5328, 212.848, 8.876, 17.3537, 190.765, 5.495, 54.6075, 211.071, 5.495, 16.6177, 192.364, 8.876, 55.676, 190.765, 5.495, 54.6075, 212.848, 8.876, 17.3537, 211.071, 5.495, 16.6177, 190.221, 1.9054, 54.2442, 210.467, 1.9054, 16.3675, 190.765, 5.495, 54.6075, 190.221, 1.9054, 54.2442, 211.071, 5.495, 16.6177, 210.467, 1.9054, 16.3675, 190.765, -1.6842, 54.6075, 211.071, -1.6842, 16.6177, 190.221, 1.9054, 54.2442, 190.765, -1.6842, 54.6075, 210.467, 1.9054, 16.3675, 211.071, -1.6842, 16.6177, 192.364, -5.0652, 55.676, 212.848, -5.0652, 17.3537, 190.765, -1.6842, 54.6075, 192.364, -5.0652, 55.676, 211.071, -1.6842, 16.6177, 212.848, -5.0652, 17.3537, 194.926, -8.0411, 57.3878, 215.694, -8.0411, 18.5329, 192.364, -5.0652, 55.676, 194.926, -8.0411, 57.3878, 212.848, -5.0652, 17.3537, 215.694, -8.0411, 18.5329, 198.302, -10.4389, 59.6434, 219.445, -10.4389, 20.0865, 194.926, -8.0411, 57.3878, 198.302, -10.4389, 59.6434, 215.694, -8.0411, 18.5329, 219.445, -10.4389, 20.0865, 202.295, -12.1193, 62.3116, 223.882, -12.1193, 21.9244, 198.302, -10.4389, 59.6434, 202.295, -12.1193, 62.3116, 219.445, -10.4389, 20.0865, 223.882, -12.1193, 21.9244, 206.674, -12.9847, 65.2375, 228.748, -12.9847, 23.9398, 202.295, -12.1193, 62.3116, 206.674, -12.9847, 65.2375, 223.882, -12.1193, 21.9244, 228.748, -12.9847, 23.9398, 211.184, -12.9847, 68.2508, 233.759, -12.9847, 26.0154, 206.674, -12.9847, 65.2375, 211.184, -12.9847, 68.2508, 228.748, -12.9847, 23.9398, 233.759, -12.9847, 26.0154, 215.562, -12.1193, 71.1767, 238.624, -12.1193, 28.0308, 211.184, -12.9847, 68.2508, 215.562, -12.1193, 71.1767, 233.759, -12.9847, 26.0154, 238.624, -12.1193, 28.0308, 219.556, -10.4389, 73.8449, 243.061, -10.4389, 29.8687, 215.562, -12.1193, 71.1767, 219.556, -10.4389, 73.8449, 238.624, -12.1193, 28.0308, 243.061, -10.4389, 29.8687, 222.931, -8.0411, 76.1005, 246.812, -8.0411, 31.4223, 219.556, -10.4389, 73.8449, 222.931, -8.0411, 76.1005, 243.061, -10.4389, 29.8687, 246.812, -8.0411, 31.4223, 225.493, -5.0652, 77.8123, 249.659, -5.0652, 32.6014, 222.931, -8.0411, 76.1005, 225.493, -5.0652, 77.8123, 246.812, -8.0411, 31.4223, 249.659, -5.0652, 32.6014, 227.092, -1.6842, 78.8808, 251.436, -1.6842, 33.3375, 225.493, -5.0652, 77.8123, 227.092, -1.6842, 78.8808, 249.659, -5.0652, 32.6014, 251.436, -1.6842, 33.3375, 227.636, 1.9054, 79.2441, 252.04, 1.9054, 33.5877, 227.092, -1.6842, 78.8808, 227.636, 1.9054, 79.2441, 251.436, -1.6842, 33.3375, 227.636, 1.9054, 79.2441, 194.332, 5.495, 118.8, 227.092, 5.495, 78.8808, 194.794, 1.9054, 119.262, 194.332, 5.495, 118.8, 227.636, 1.9054, 79.2441, 227.092, 5.495, 78.8808, 192.972, 8.876, 117.44, 225.493, 8.876, 77.8123, 194.332, 5.495, 118.8, 192.972, 8.876, 117.44, 227.092, 5.495, 78.8808, 225.493, 8.876, 77.8123, 190.793, 11.8519, 115.261, 222.931, 11.8519, 76.1005, 192.972, 8.876, 117.44, 190.793, 11.8519, 115.261, 225.493, 8.876, 77.8123, 222.931, 11.8519, 76.1005, 187.922, 14.2498, 112.39, 219.556, 14.2498, 73.8449, 190.793, 11.8519, 115.261, 187.922, 14.2498, 112.39, 222.931, 11.8519, 76.1005, 219.556, 14.2498, 73.8449, 184.526, 15.9302, 108.994, 215.562, 15.9302, 71.1767, 187.922, 14.2498, 112.39, 184.526, 15.9302, 108.994, 219.556, 14.2498, 73.8449, 215.562, 15.9302, 71.1767, 180.802, 16.7956, 105.271, 211.184, 16.7956, 68.2508, 184.526, 15.9302, 108.994, 180.802, 16.7956, 105.271, 215.562, 15.9302, 71.1767, 211.184, 16.7956, 68.2508, 176.967, 16.7956, 101.435, 206.674, 16.7956, 65.2375, 180.802, 16.7956, 105.271, 176.967, 16.7956, 101.435, 211.184, 16.7956, 68.2508, 206.674, 16.7956, 65.2375, 173.243, 15.9302, 97.7113, 202.295, 15.9302, 62.3116, 176.967, 16.7956, 101.435, 173.243, 15.9302, 97.7113, 206.674, 16.7956, 65.2375, 202.295, 15.9302, 62.3116, 169.847, 14.2498, 94.3153, 198.302, 14.2498, 59.6434, 173.243, 15.9302, 97.7113, 169.847, 14.2498, 94.3153, 202.295, 15.9302, 62.3116, 198.302, 14.2498, 59.6434, 166.976, 11.8519, 91.4445, 194.926, 11.8519, 57.3878, 169.847, 14.2498, 94.3153, 166.976, 11.8519, 91.4445, 198.302, 14.2498, 59.6434, 194.926, 11.8519, 57.3878, 164.798, 8.876, 89.2658, 192.364, 8.876, 55.676, 166.976, 11.8519, 91.4445, 164.798, 8.876, 89.2658, 194.926, 11.8519, 57.3878, 192.364, 8.876, 55.676, 163.438, 5.495, 87.9057, 190.765, 5.495, 54.6075, 164.798, 8.876, 89.2658, 163.438, 5.495, 87.9057, 192.364, 8.876, 55.676, 190.765, 5.495, 54.6075, 162.975, 1.9054, 87.4435, 190.221, 1.9054, 54.2442, 163.438, 5.495, 87.9057, 162.975, 1.9054, 87.4435, 190.765, 5.495, 54.6075, 190.221, 1.9054, 54.2442, 163.438, -1.6842, 87.9057, 190.765, -1.6842, 54.6075, 162.975, 1.9054, 87.4435, 163.438, -1.6842, 87.9057, 190.221, 1.9054, 54.2442, 190.765, -1.6842, 54.6075, 164.798, -5.0652, 89.2658, 192.364, -5.0652, 55.676, 163.438, -1.6842, 87.9057, 164.798, -5.0652, 89.2658, 190.765, -1.6842, 54.6075, 192.364, -5.0652, 55.676, 166.976, -8.0411, 91.4445, 194.926, -8.0411, 57.3878, 164.798, -5.0652, 89.2658, 166.976, -8.0411, 91.4445, 192.364, -5.0652, 55.676, 194.926, -8.0411, 57.3878, 169.847, -10.4389, 94.3153, 198.302, -10.4389, 59.6434, 166.976, -8.0411, 91.4445, 169.847, -10.4389, 94.3153, 194.926, -8.0411, 57.3878, 198.302, -10.4389, 59.6434, 173.243, -12.1193, 97.7113, 202.295, -12.1193, 62.3116, 169.847, -10.4389, 94.3153, 173.243, -12.1193, 97.7113, 198.302, -10.4389, 59.6434, 202.295, -12.1193, 62.3116, 176.967, -12.9847, 101.435, 206.674, -12.9847, 65.2375, 173.243, -12.1193, 97.7113, 176.967, -12.9847, 101.435, 202.295, -12.1193, 62.3116, 206.674, -12.9847, 65.2375, 180.802, -12.9847, 105.271, 211.184, -12.9847, 68.2508, 176.967, -12.9847, 101.435, 180.802, -12.9847, 105.271, 206.674, -12.9847, 65.2375, 211.184, -12.9847, 68.2508, 184.526, -12.1193, 108.994, 215.562, -12.1193, 71.1767, 180.802, -12.9847, 105.271, 184.526, -12.1193, 108.994, 211.184, -12.9847, 68.2508, 215.562, -12.1193, 71.1767, 187.922, -10.4389, 112.39, 219.556, -10.4389, 73.8449, 184.526, -12.1193, 108.994, 187.922, -10.4389, 112.39, 215.562, -12.1193, 71.1767, 219.556, -10.4389, 73.8449, 190.793, -8.0411, 115.261, 222.931, -8.0411, 76.1005, 187.922, -10.4389, 112.39, 190.793, -8.0411, 115.261, 219.556, -10.4389, 73.8449, 222.931, -8.0411, 76.1005, 192.972, -5.0652, 117.44, 225.493, -5.0652, 77.8123, 190.793, -8.0411, 115.261, 192.972, -5.0652, 117.44, 222.931, -8.0411, 76.1005, 225.493, -5.0652, 77.8123, 194.332, -1.6842, 118.8, 227.092, -1.6842, 78.8808, 192.972, -5.0652, 117.44, 194.332, -1.6842, 118.8, 225.493, -5.0652, 77.8123, 227.092, -1.6842, 78.8808, 194.794, 1.9054, 119.262, 227.636, 1.9054, 79.2441, 194.332, -1.6842, 118.8, 194.794, 1.9054, 119.262, 227.092, -1.6842, 78.8808, 194.794, 1.9054, 119.262, 154.413, 5.495, 151.561, 194.332, 5.495, 118.8, 154.776, 1.9054, 152.104, 154.413, 5.495, 151.561, 194.794, 1.9054, 119.262, 194.332, 5.495, 118.8, 153.344, 8.876, 149.961, 192.972, 8.876, 117.44, 154.413, 5.495, 151.561, 153.344, 8.876, 149.961, 194.332, 5.495, 118.8, 192.972, 8.876, 117.44, 151.632, 11.8519, 147.399, 190.793, 11.8519, 115.261, 153.344, 8.876, 149.961, 151.632, 11.8519, 147.399, 192.972, 8.876, 117.44, 190.793, 11.8519, 115.261, 149.377, 14.2498, 144.024, 187.922, 14.2498, 112.39, 151.632, 11.8519, 147.399, 149.377, 14.2498, 144.024, 190.793, 11.8519, 115.261, 187.922, 14.2498, 112.39, 146.709, 15.9302, 140.031, 184.526, 15.9302, 108.994, 149.377, 14.2498, 144.024, 146.709, 15.9302, 140.031, 187.922, 14.2498, 112.39, 184.526, 15.9302, 108.994, 143.783, 16.7956, 135.652, 180.802, 16.7956, 105.271, 146.709, 15.9302, 140.031, 143.783, 16.7956, 135.652, 184.526, 15.9302, 108.994, 180.802, 16.7956, 105.271, 140.769, 16.7956, 131.142, 176.967, 16.7956, 101.435, 143.783, 16.7956, 135.652, 140.769, 16.7956, 131.142, 180.802, 16.7956, 105.271, 176.967, 16.7956, 101.435, 137.843, 15.9302, 126.763, 173.243, 15.9302, 97.7113, 140.769, 16.7956, 131.142, 137.843, 15.9302, 126.763, 176.967, 16.7956, 101.435, 173.243, 15.9302, 97.7113, 135.175, 14.2498, 122.77, 169.847, 14.2498, 94.3153, 137.843, 15.9302, 126.763, 135.175, 14.2498, 122.77, 173.243, 15.9302, 97.7113, 169.847, 14.2498, 94.3153, 132.92, 11.8519, 119.394, 166.976, 11.8519, 91.4445, 135.175, 14.2498, 122.77, 132.92, 11.8519, 119.394, 169.847, 14.2498, 94.3153, 166.976, 11.8519, 91.4445, 131.208, 8.876, 116.832, 164.798, 8.876, 89.2658, 132.92, 11.8519, 119.394, 131.208, 8.876, 116.832, 166.976, 11.8519, 91.4445, 164.798, 8.876, 89.2658, 130.139, 5.495, 115.233, 163.438, 5.495, 87.9057, 131.208, 8.876, 116.832, 130.139, 5.495, 115.233, 164.798, 8.876, 89.2658, 163.438, 5.495, 87.9057, 129.776, 1.9054, 114.689, 162.975, 1.9054, 87.4435, 130.139, 5.495, 115.233, 129.776, 1.9054, 114.689, 163.438, 5.495, 87.9057, 162.975, 1.9054, 87.4435, 130.139, -1.6842, 115.233, 163.438, -1.6842, 87.9057, 129.776, 1.9054, 114.689, 130.139, -1.6842, 115.233, 162.975, 1.9054, 87.4435, 163.438, -1.6842, 87.9057, 131.208, -5.0652, 116.832, 164.798, -5.0652, 89.2658, 130.139, -1.6842, 115.233, 131.208, -5.0652, 116.832, 163.438, -1.6842, 87.9057, 164.798, -5.0652, 89.2658, 132.92, -8.0411, 119.394, 166.976, -8.0411, 91.4445, 131.208, -5.0652, 116.832, 132.92, -8.0411, 119.394, 164.798, -5.0652, 89.2658, 166.976, -8.0411, 91.4445, 135.175, -10.4389, 122.77, 169.847, -10.4389, 94.3153, 132.92, -8.0411, 119.394, 135.175, -10.4389, 122.77, 166.976, -8.0411, 91.4445, 169.847, -10.4389, 94.3153, 137.843, -12.1193, 126.763, 173.243, -12.1193, 97.7113, 135.175, -10.4389, 122.77, 137.843, -12.1193, 126.763, 169.847, -10.4389, 94.3153, 173.243, -12.1193, 97.7113, 140.769, -12.9847, 131.142, 176.967, -12.9847, 101.435, 137.843, -12.1193, 126.763, 140.769, -12.9847, 131.142, 173.243, -12.1193, 97.7113, 176.967, -12.9847, 101.435, 143.783, -12.9847, 135.652, 180.802, -12.9847, 105.271, 140.769, -12.9847, 131.142, 143.783, -12.9847, 135.652, 176.967, -12.9847, 101.435, 180.802, -12.9847, 105.271, 146.709, -12.1193, 140.031, 184.526, -12.1193, 108.994, 143.783, -12.9847, 135.652, 146.709, -12.1193, 140.031, 180.802, -12.9847, 105.271, 184.526, -12.1193, 108.994, 149.377, -10.4389, 144.024, 187.922, -10.4389, 112.39, 146.709, -12.1193, 140.031, 149.377, -10.4389, 144.024, 184.526, -12.1193, 108.994, 187.922, -10.4389, 112.39, 151.632, -8.0411, 147.399, 190.793, -8.0411, 115.261, 149.377, -10.4389, 144.024, 151.632, -8.0411, 147.399, 187.922, -10.4389, 112.39, 190.793, -8.0411, 115.261, 153.344, -5.0652, 149.961, 192.972, -5.0652, 117.44, 151.632, -8.0411, 147.399, 153.344, -5.0652, 149.961, 190.793, -8.0411, 115.261, 192.972, -5.0652, 117.44, 154.413, -1.6842, 151.561, 194.332, -1.6842, 118.8, 153.344, -5.0652, 149.961, 154.413, -1.6842, 151.561, 192.972, -5.0652, 117.44, 194.332, -1.6842, 118.8, 154.776, 1.9054, 152.104, 194.794, 1.9054, 119.262, 154.413, -1.6842, 151.561, 154.776, 1.9054, 152.104, 194.332, -1.6842, 118.8, 154.776, 1.9054, 152.104, 108.869, 5.495, 175.904, 154.413, 5.495, 151.561, 109.12, 1.9054, 176.508, 108.869, 5.495, 175.904, 154.776, 1.9054, 152.104, 154.413, 5.495, 151.561, 108.133, 8.876, 174.127, 153.344, 8.876, 149.961, 108.869, 5.495, 175.904, 108.133, 8.876, 174.127, 154.413, 5.495, 151.561, 153.344, 8.876, 149.961, 106.954, 11.8519, 171.281, 151.632, 11.8519, 147.399, 108.133, 8.876, 174.127, 106.954, 11.8519, 171.281, 153.344, 8.876, 149.961, 151.632, 11.8519, 147.399, 105.401, 14.2498, 167.53, 149.377, 14.2498, 144.024, 106.954, 11.8519, 171.281, 105.401, 14.2498, 167.53, 151.632, 11.8519, 147.399, 149.377, 14.2498, 144.024, 103.563, 15.9302, 163.092, 146.709, 15.9302, 140.031, 105.401, 14.2498, 167.53, 103.563, 15.9302, 163.092, 149.377, 14.2498, 144.024, 146.709, 15.9302, 140.031, 101.547, 16.7956, 158.227, 143.783, 16.7956, 135.652, 103.563, 15.9302, 163.092, 101.547, 16.7956, 158.227, 146.709, 15.9302, 140.031, 143.783, 16.7956, 135.652, 99.4717, 16.7956, 153.216, 140.769, 16.7956, 131.142, 101.547, 16.7956, 158.227, 99.4717, 16.7956, 153.216, 143.783, 16.7956, 135.652, 140.769, 16.7956, 131.142, 97.4563, 15.9302, 148.35, 137.843, 15.9302, 126.763, 99.4717, 16.7956, 153.216, 97.4563, 15.9302, 148.35, 140.769, 16.7956, 131.142, 137.843, 15.9302, 126.763, 95.6184, 14.2498, 143.913, 135.175, 14.2498, 122.77, 97.4563, 15.9302, 148.35, 95.6184, 14.2498, 143.913, 137.843, 15.9302, 126.763, 135.175, 14.2498, 122.77, 94.0647, 11.8519, 140.162, 132.92, 11.8519, 119.394, 95.6184, 14.2498, 143.913, 94.0647, 11.8519, 140.162, 135.175, 14.2498, 122.77, 132.92, 11.8519, 119.394, 92.8856, 8.876, 137.316, 131.208, 8.876, 116.832, 94.0647, 11.8519, 140.162, 92.8856, 8.876, 137.316, 132.92, 11.8519, 119.394, 131.208, 8.876, 116.832, 92.1496, 5.495, 135.539, 130.139, 5.495, 115.233, 92.8856, 8.876, 137.316, 92.1496, 5.495, 135.539, 131.208, 8.876, 116.832, 130.139, 5.495, 115.233, 91.8994, 1.9054, 134.935, 129.776, 1.9054, 114.689, 92.1496, 5.495, 135.539, 91.8994, 1.9054, 134.935, 130.139, 5.495, 115.233, 129.776, 1.9054, 114.689, 92.1496, -1.6842, 135.539, 130.139, -1.6842, 115.233, 91.8994, 1.9054, 134.935, 92.1496, -1.6842, 135.539, 129.776, 1.9054, 114.689, 130.139, -1.6842, 115.233, 92.8856, -5.0652, 137.316, 131.208, -5.0652, 116.832, 92.1496, -1.6842, 135.539, 92.8856, -5.0652, 137.316, 130.139, -1.6842, 115.233, 131.208, -5.0652, 116.832, 94.0647, -8.0411, 140.162, 132.92, -8.0411, 119.394, 92.8856, -5.0652, 137.316, 94.0647, -8.0411, 140.162, 131.208, -5.0652, 116.832, 132.92, -8.0411, 119.394, 95.6184, -10.4389, 143.913, 135.175, -10.4389, 122.77, 94.0647, -8.0411, 140.162, 95.6184, -10.4389, 143.913, 132.92, -8.0411, 119.394, 135.175, -10.4389, 122.77, 97.4563, -12.1193, 148.35, 137.843, -12.1193, 126.763, 95.6184, -10.4389, 143.913, 97.4563, -12.1193, 148.35, 135.175, -10.4389, 122.77, 137.843, -12.1193, 126.763, 99.4717, -12.9847, 153.216, 140.769, -12.9847, 131.142, 97.4563, -12.1193, 148.35, 99.4717, -12.9847, 153.216, 137.843, -12.1193, 126.763, 140.769, -12.9847, 131.142, 101.547, -12.9847, 158.227, 143.783, -12.9847, 135.652, 99.4717, -12.9847, 153.216, 101.547, -12.9847, 158.227, 140.769, -12.9847, 131.142, 143.783, -12.9847, 135.652, 103.563, -12.1193, 163.092, 146.709, -12.1193, 140.031, 101.547, -12.9847, 158.227, 103.563, -12.1193, 163.092, 143.783, -12.9847, 135.652, 146.709, -12.1193, 140.031, 105.401, -10.4389, 167.53, 149.377, -10.4389, 144.024, 103.563, -12.1193, 163.092, 105.401, -10.4389, 167.53, 146.709, -12.1193, 140.031, 149.377, -10.4389, 144.024, 106.954, -8.0411, 171.281, 151.632, -8.0411, 147.399, 105.401, -10.4389, 167.53, 106.954, -8.0411, 171.281, 149.377, -10.4389, 144.024, 151.632, -8.0411, 147.399, 108.133, -5.0652, 174.127, 153.344, -5.0652, 149.961, 106.954, -8.0411, 171.281, 108.133, -5.0652, 174.127, 151.632, -8.0411, 147.399, 153.344, -5.0652, 149.961, 108.869, -1.6842, 175.904, 154.413, -1.6842, 151.561, 108.133, -5.0652, 174.127, 108.869, -1.6842, 175.904, 153.344, -5.0652, 149.961, 154.413, -1.6842, 151.561, 109.12, 1.9054, 176.508, 154.776, 1.9054, 152.104, 108.869, -1.6842, 175.904, 109.12, 1.9054, 176.508, 154.413, -1.6842, 151.561, 109.12, 1.9054, 176.508, 59.452, 5.495, 190.895, 108.869, 5.495, 175.904, 59.5795, 1.9054, 191.536, 59.452, 5.495, 190.895, 109.12, 1.9054, 176.508, 108.869, 5.495, 175.904, 59.0767, 8.876, 189.008, 108.133, 8.876, 174.127, 59.452, 5.495, 190.895, 59.0767, 8.876, 189.008, 108.869, 5.495, 175.904, 108.133, 8.876, 174.127, 58.4756, 11.8519, 185.986, 106.954, 11.8519, 171.281, 59.0767, 8.876, 189.008, 58.4756, 11.8519, 185.986, 108.133, 8.876, 174.127, 106.954, 11.8519, 171.281, 57.6836, 14.2498, 182.004, 105.401, 14.2498, 167.53, 58.4756, 11.8519, 185.986, 57.6836, 14.2498, 182.004, 106.954, 11.8519, 171.281, 105.401, 14.2498, 167.53, 56.7466, 15.9302, 177.294, 103.563, 15.9302, 163.092, 57.6836, 14.2498, 182.004, 56.7466, 15.9302, 177.294, 105.401, 14.2498, 167.53, 103.563, 15.9302, 163.092, 55.7192, 16.7956, 172.129, 101.547, 16.7956, 158.227, 56.7466, 15.9302, 177.294, 55.7192, 16.7956, 172.129, 103.563, 15.9302, 163.092, 101.547, 16.7956, 158.227, 54.6611, 16.7956, 166.809, 99.4717, 16.7956, 153.216, 55.7192, 16.7956, 172.129, 54.6611, 16.7956, 166.809, 101.547, 16.7956, 158.227, 99.4717, 16.7956, 153.216, 53.6336, 15.9302, 161.644, 97.4563, 15.9302, 148.35, 54.6611, 16.7956, 166.809, 53.6336, 15.9302, 161.644, 99.4717, 16.7956, 153.216, 97.4563, 15.9302, 148.35, 52.6967, 14.2498, 156.934, 95.6184, 14.2498, 143.913, 53.6336, 15.9302, 161.644, 52.6967, 14.2498, 156.934, 97.4563, 15.9302, 148.35, 95.6184, 14.2498, 143.913, 51.9046, 11.8519, 152.952, 94.0647, 11.8519, 140.162, 52.6967, 14.2498, 156.934, 51.9046, 11.8519, 152.952, 95.6184, 14.2498, 143.913, 94.0647, 11.8519, 140.162, 51.3035, 8.876, 149.93, 92.8856, 8.876, 137.316, 51.9046, 11.8519, 152.952, 51.3035, 8.876, 149.93, 94.0647, 11.8519, 140.162, 92.8856, 8.876, 137.316, 50.9283, 5.495, 148.043, 92.1496, 5.495, 135.539, 51.3035, 8.876, 149.93, 50.9283, 5.495, 148.043, 92.8856, 8.876, 137.316, 92.1496, 5.495, 135.539, 50.8007, 1.9054, 147.402, 91.8994, 1.9054, 134.935, 50.9283, 5.495, 148.043, 50.8007, 1.9054, 147.402, 92.1496, 5.495, 135.539, 91.8994, 1.9054, 134.935, 50.9283, -1.6842, 148.043, 92.1496, -1.6842, 135.539, 50.8007, 1.9054, 147.402, 50.9283, -1.6842, 148.043, 91.8994, 1.9054, 134.935, 92.1496, -1.6842, 135.539, 51.3035, -5.0652, 149.93, 92.8856, -5.0652, 137.316, 50.9283, -1.6842, 148.043, 51.3035, -5.0652, 149.93, 92.1496, -1.6842, 135.539, 92.8856, -5.0652, 137.316, 51.9046, -8.0411, 152.952, 94.0647, -8.0411, 140.162, 51.3035, -5.0652, 149.93, 51.9046, -8.0411, 152.952, 92.8856, -5.0652, 137.316, 94.0647, -8.0411, 140.162, 52.6967, -10.4389, 156.934, 95.6184, -10.4389, 143.913, 51.9046, -8.0411, 152.952, 52.6967, -10.4389, 156.934, 94.0647, -8.0411, 140.162, 95.6184, -10.4389, 143.913, 53.6336, -12.1193, 161.644, 97.4563, -12.1193, 148.35, 52.6967, -10.4389, 156.934, 53.6336, -12.1193, 161.644, 95.6184, -10.4389, 143.913, 97.4563, -12.1193, 148.35, 54.6611, -12.9847, 166.809, 99.4717, -12.9847, 153.216, 53.6336, -12.1193, 161.644, 54.6611, -12.9847, 166.809, 97.4563, -12.1193, 148.35, 99.4717, -12.9847, 153.216, 55.7192, -12.9847, 172.129, 101.547, -12.9847, 158.227, 54.6611, -12.9847, 166.809, 55.7192, -12.9847, 172.129, 99.4717, -12.9847, 153.216, 101.547, -12.9847, 158.227, 56.7466, -12.1193, 177.294, 103.563, -12.1193, 163.092, 55.7192, -12.9847, 172.129, 56.7466, -12.1193, 177.294, 101.547, -12.9847, 158.227, 103.563, -12.1193, 163.092, 57.6836, -10.4389, 182.004, 105.401, -10.4389, 167.53, 56.7466, -12.1193, 177.294, 57.6836, -10.4389, 182.004, 103.563, -12.1193, 163.092, 105.401, -10.4389, 167.53, 58.4756, -8.0411, 185.986, 106.954, -8.0411, 171.281, 57.6836, -10.4389, 182.004, 58.4756, -8.0411, 185.986, 105.401, -10.4389, 167.53, 106.954, -8.0411, 171.281, 59.0767, -5.0652, 189.008, 108.133, -5.0652, 174.127, 58.4756, -8.0411, 185.986, 59.0767, -5.0652, 189.008, 106.954, -8.0411, 171.281, 108.133, -5.0652, 174.127, 59.452, -1.6842, 190.895, 108.869, -1.6842, 175.904, 59.0767, -5.0652, 189.008, 59.452, -1.6842, 190.895, 108.133, -5.0652, 174.127, 108.869, -1.6842, 175.904, 59.5795, 1.9054, 191.536, 109.12, 1.9054, 176.508, 59.452, -1.6842, 190.895, 59.5795, 1.9054, 191.536, 108.869, -1.6842, 175.904, 59.5795, 1.9054, 191.536, 8.0596, 5.495, 195.956, 59.452, 5.495, 190.895, 8.0596, 1.9054, 196.61, 8.0596, 5.495, 195.956, 59.5795, 1.9054, 191.536, 59.452, 5.495, 190.895, 8.0596, 8.876, 194.033, 59.0767, 8.876, 189.008, 8.0596, 5.495, 195.956, 8.0596, 8.876, 194.033, 59.452, 5.495, 190.895, 59.0767, 8.876, 189.008, 8.0596, 11.8519, 190.952, 58.4756, 11.8519, 185.986, 8.0596, 8.876, 194.033, 8.0596, 11.8519, 190.952, 59.0767, 8.876, 189.008, 58.4756, 11.8519, 185.986, 8.0596, 14.2498, 186.892, 57.6836, 14.2498, 182.004, 8.0596, 11.8519, 190.952, 8.0596, 14.2498, 186.892, 58.4756, 11.8519, 185.986, 57.6836, 14.2498, 182.004, 8.0596, 15.9302, 182.089, 56.7466, 15.9302, 177.294, 8.0596, 14.2498, 186.892, 8.0596, 15.9302, 182.089, 57.6836, 14.2498, 182.004, 56.7466, 15.9302, 177.294, 8.0596, 16.7956, 176.823, 55.7192, 16.7956, 172.129, 8.0596, 15.9302, 182.089, 8.0596, 16.7956, 176.823, 56.7466, 15.9302, 177.294, 55.7192, 16.7956, 172.129, 8.0596, 16.7956, 171.399, 54.6611, 16.7956, 166.809, 8.0596, 16.7956, 176.823, 8.0596, 16.7956, 171.399, 55.7192, 16.7956, 172.129, 54.6611, 16.7956, 166.809, 8.0596, 15.9302, 166.133, 53.6336, 15.9302, 161.644, 8.0596, 16.7956, 171.399, 8.0596, 15.9302, 166.133, 54.6611, 16.7956, 166.809, 53.6336, 15.9302, 161.644, 8.0596, 14.2498, 161.33, 52.6967, 14.2498, 156.934, 8.0596, 15.9302, 166.133, 8.0596, 14.2498, 161.33, 53.6336, 15.9302, 161.644, 52.6967, 14.2498, 156.934, 8.0596, 11.8519, 157.27, 51.9046, 11.8519, 152.952, 8.0596, 14.2498, 161.33, 8.0596, 11.8519, 157.27, 52.6967, 14.2498, 156.934, 51.9046, 11.8519, 152.952, 8.0596, 8.876, 154.189, 51.3035, 8.876, 149.93, 8.0596, 11.8519, 157.27, 8.0596, 8.876, 154.189, 51.9046, 11.8519, 152.952, 51.3035, 8.876, 149.93, 8.0596, 5.495, 152.266, 50.9283, 5.495, 148.043, 8.0596, 8.876, 154.189, 8.0596, 5.495, 152.266, 51.3035, 8.876, 149.93, 50.9283, 5.495, 148.043, 8.0596, 1.9054, 151.612, 50.8007, 1.9054, 147.402, 8.0596, 5.495, 152.266, 8.0596, 1.9054, 151.612, 50.9283, 5.495, 148.043, 50.8007, 1.9054, 147.402, 8.0596, -1.6842, 152.266, 50.9283, -1.6842, 148.043, 8.0596, 1.9054, 151.612, 8.0596, -1.6842, 152.266, 50.8007, 1.9054, 147.402, 50.9283, -1.6842, 148.043, 8.0596, -5.0652, 154.189, 51.3035, -5.0652, 149.93, 8.0596, -1.6842, 152.266, 8.0596, -5.0652, 154.189, 50.9283, -1.6842, 148.043, 51.3035, -5.0652, 149.93, 8.0596, -8.0411, 157.27, 51.9046, -8.0411, 152.952, 8.0596, -5.0652, 154.189, 8.0596, -8.0411, 157.27, 51.3035, -5.0652, 149.93, 51.9046, -8.0411, 152.952, 8.0596, -10.4389, 161.33, 52.6967, -10.4389, 156.934, 8.0596, -8.0411, 157.27, 8.0596, -10.4389, 161.33, 51.9046, -8.0411, 152.952, 52.6967, -10.4389, 156.934, 8.0596, -12.1193, 166.133, 53.6336, -12.1193, 161.644, 8.0596, -10.4389, 161.33, 8.0596, -12.1193, 166.133, 52.6967, -10.4389, 156.934, 53.6336, -12.1193, 161.644, 8.0596, -12.9847, 171.399, 54.6611, -12.9847, 166.809, 8.0596, -12.1193, 166.133, 8.0596, -12.9847, 171.399, 53.6336, -12.1193, 161.644, 54.6611, -12.9847, 166.809, 8.0596, -12.9847, 176.823, 55.7192, -12.9847, 172.129, 8.0596, -12.9847, 171.399, 8.0596, -12.9847, 176.823, 54.6611, -12.9847, 166.809, 55.7192, -12.9847, 172.129, 8.0596, -12.1193, 182.089, 56.7466, -12.1193, 177.294, 8.0596, -12.9847, 176.823, 8.0596, -12.1193, 182.089, 55.7192, -12.9847, 172.129, 56.7466, -12.1193, 177.294, 8.0596, -10.4389, 186.892, 57.6836, -10.4389, 182.004, 8.0596, -12.1193, 182.089, 8.0596, -10.4389, 186.892, 56.7466, -12.1193, 177.294, 57.6836, -10.4389, 182.004, 8.0596, -8.0411, 190.952, 58.4756, -8.0411, 185.986, 8.0596, -10.4389, 186.892, 8.0596, -8.0411, 190.952, 57.6836, -10.4389, 182.004, 58.4756, -8.0411, 185.986, 8.0596, -5.0652, 194.033, 59.0767, -5.0652, 189.008, 8.0596, -8.0411, 190.952, 8.0596, -5.0652, 194.033, 58.4756, -8.0411, 185.986, 59.0767, -5.0652, 189.008, 8.0596, -1.6842, 195.956, 59.452, -1.6842, 190.895, 8.0596, -5.0652, 194.033, 8.0596, -1.6842, 195.956, 59.0767, -5.0652, 189.008, 59.452, -1.6842, 190.895, 8.0596, 1.9054, 196.61, 59.5795, 1.9054, 191.536, 8.0596, -1.6842, 195.956, 8.0596, 1.9054, 196.61, 59.452, -1.6842, 190.895, 8.0596, 1.9054, 196.61, -43.3328, 5.495, 190.895, 8.0596, 5.495, 195.956, -43.4604, 1.9054, 191.536, -43.3328, 5.495, 190.895, 8.0596, 1.9054, 196.61, 8.0596, 5.495, 195.956, -42.9576, 8.876, 189.008, 8.0596, 8.876, 194.033, -43.3328, 5.495, 190.895, -42.9576, 8.876, 189.008, 8.0596, 5.495, 195.956, 8.0596, 8.876, 194.033, -42.3565, 11.8519, 185.986, 8.0596, 11.8519, 190.952, -42.9576, 8.876, 189.008, -42.3565, 11.8519, 185.986, 8.0596, 8.876, 194.033, 8.0596, 11.8519, 190.952, -41.5644, 14.2498, 182.004, 8.0596, 14.2498, 186.892, -42.3565, 11.8519, 185.986, -41.5644, 14.2498, 182.004, 8.0596, 11.8519, 190.952, 8.0596, 14.2498, 186.892, -40.6275, 15.9302, 177.294, 8.0596, 15.9302, 182.089, -41.5644, 14.2498, 182.004, -40.6275, 15.9302, 177.294, 8.0596, 14.2498, 186.892, 8.0596, 15.9302, 182.089, -39.6001, 16.7956, 172.129, 8.0596, 16.7956, 176.823, -40.6275, 15.9302, 177.294, -39.6001, 16.7956, 172.129, 8.0596, 15.9302, 182.089, 8.0596, 16.7956, 176.823, -38.5419, 16.7956, 166.809, 8.0596, 16.7956, 171.399, -39.6001, 16.7956, 172.129, -38.5419, 16.7956, 166.809, 8.0596, 16.7956, 176.823, 8.0596, 16.7956, 171.399, -37.5145, 15.9302, 161.644, 8.0596, 15.9302, 166.133, -38.5419, 16.7956, 166.809, -37.5145, 15.9302, 161.644, 8.0596, 16.7956, 171.399, 8.0596, 15.9302, 166.133, -36.5775, 14.2498, 156.934, 8.0596, 14.2498, 161.33, -37.5145, 15.9302, 161.644, -36.5775, 14.2498, 156.934, 8.0596, 15.9302, 166.133, 8.0596, 14.2498, 161.33, -35.7855, 11.8519, 152.952, 8.0596, 11.8519, 157.27, -36.5775, 14.2498, 156.934, -35.7855, 11.8519, 152.952, 8.0596, 14.2498, 161.33, 8.0596, 11.8519, 157.27, -35.1844, 8.876, 149.93, 8.0596, 8.876, 154.189, -35.7855, 11.8519, 152.952, -35.1844, 8.876, 149.93, 8.0596, 11.8519, 157.27, 8.0596, 8.876, 154.189, -34.8091, 5.495, 148.043, 8.0596, 5.495, 152.266, -35.1844, 8.876, 149.93, -34.8091, 5.495, 148.043, 8.0596, 8.876, 154.189, 8.0596, 5.495, 152.266, -34.6816, 1.9054, 147.402, 8.0596, 1.9054, 151.612, -34.8091, 5.495, 148.043, -34.6816, 1.9054, 147.402, 8.0596, 5.495, 152.266, 8.0596, 1.9054, 151.612, -34.8091, -1.6842, 148.043, 8.0596, -1.6842, 152.266, -34.6816, 1.9054, 147.402, -34.8091, -1.6842, 148.043, 8.0596, 1.9054, 151.612, 8.0596, -1.6842, 152.266, -35.1844, -5.0652, 149.93, 8.0596, -5.0652, 154.189, -34.8091, -1.6842, 148.043, -35.1844, -5.0652, 149.93, 8.0596, -1.6842, 152.266, 8.0596, -5.0652, 154.189, -35.7855, -8.0411, 152.952, 8.0596, -8.0411, 157.27, -35.1844, -5.0652, 149.93, -35.7855, -8.0411, 152.952, 8.0596, -5.0652, 154.189, 8.0596, -8.0411, 157.27, -36.5775, -10.4389, 156.934, 8.0596, -10.4389, 161.33, -35.7855, -8.0411, 152.952, -36.5775, -10.4389, 156.934, 8.0596, -8.0411, 157.27, 8.0596, -10.4389, 161.33, -37.5145, -12.1193, 161.644, 8.0596, -12.1193, 166.133, -36.5775, -10.4389, 156.934, -37.5145, -12.1193, 161.644, 8.0596, -10.4389, 161.33, 8.0596, -12.1193, 166.133, -38.5419, -12.9847, 166.809, 8.0596, -12.9847, 171.399, -37.5145, -12.1193, 161.644, -38.5419, -12.9847, 166.809, 8.0596, -12.1193, 166.133, 8.0596, -12.9847, 171.399, -39.6001, -12.9847, 172.129, 8.0596, -12.9847, 176.823, -38.5419, -12.9847, 166.809, -39.6001, -12.9847, 172.129, 8.0596, -12.9847, 171.399, 8.0596, -12.9847, 176.823, -40.6275, -12.1193, 177.294, 8.0596, -12.1193, 182.089, -39.6001, -12.9847, 172.129, -40.6275, -12.1193, 177.294, 8.0596, -12.9847, 176.823, 8.0596, -12.1193, 182.089, -41.5644, -10.4389, 182.004, 8.0596, -10.4389, 186.892, -40.6275, -12.1193, 177.294, -41.5644, -10.4389, 182.004, 8.0596, -12.1193, 182.089, 8.0596, -10.4389, 186.892, -42.3565, -8.0411, 185.986, 8.0596, -8.0411, 190.952, -41.5644, -10.4389, 182.004, -42.3565, -8.0411, 185.986, 8.0596, -10.4389, 186.892, 8.0596, -8.0411, 190.952, -42.9576, -5.0652, 189.008, 8.0596, -5.0652, 194.033, -42.3565, -8.0411, 185.986, -42.9576, -5.0652, 189.008, 8.0596, -8.0411, 190.952, 8.0596, -5.0652, 194.033, -43.3328, -1.6842, 190.895, 8.0596, -1.6842, 195.956, -42.9576, -5.0652, 189.008, -43.3328, -1.6842, 190.895, 8.0596, -5.0652, 194.033, 8.0596, -1.6842, 195.956, -43.4604, 1.9054, 191.536, 8.0596, 1.9054, 196.61, -43.3328, -1.6842, 190.895, -43.4604, 1.9054, 191.536, 8.0596, -1.6842, 195.956, -43.4604, 1.9054, 191.536, -92.7502, 5.495, 175.904, -43.3328, 5.495, 190.895, -93.0004, 1.9054, 176.508, -92.7502, 5.495, 175.904, -43.4604, 1.9054, 191.536, -43.3328, 5.495, 190.895, -92.0142, 8.876, 174.127, -42.9576, 8.876, 189.008, -92.7502, 5.495, 175.904, -92.0142, 8.876, 174.127, -43.3328, 5.495, 190.895, -42.9576, 8.876, 189.008, -90.8351, 11.8519, 171.281, -42.3565, 11.8519, 185.986, -92.0142, 8.876, 174.127, -90.8351, 11.8519, 171.281, -42.9576, 8.876, 189.008, -42.3565, 11.8519, 185.986, -89.2814, 14.2498, 167.53, -41.5644, 14.2498, 182.004, -90.8351, 11.8519, 171.281, -89.2814, 14.2498, 167.53, -42.3565, 11.8519, 185.986, -41.5644, 14.2498, 182.004, -87.4435, 15.9302, 163.092, -40.6275, 15.9302, 177.294, -89.2814, 14.2498, 167.53, -87.4435, 15.9302, 163.092, -41.5644, 14.2498, 182.004, -40.6275, 15.9302, 177.294, -85.4282, 16.7956, 158.227, -39.6001, 16.7956, 172.129, -87.4435, 15.9302, 163.092, -85.4282, 16.7956, 158.227, -40.6275, 15.9302, 177.294, -39.6001, 16.7956, 172.129, -83.3525, 16.7956, 153.216, -38.5419, 16.7956, 166.809, -85.4282, 16.7956, 158.227, -83.3525, 16.7956, 153.216, -39.6001, 16.7956, 172.129, -38.5419, 16.7956, 166.809, -81.3372, 15.9302, 148.35, -37.5145, 15.9302, 161.644, -83.3525, 16.7956, 153.216, -81.3372, 15.9302, 148.35, -38.5419, 16.7956, 166.809, -37.5145, 15.9302, 161.644, -79.4992, 14.2498, 143.913, -36.5775, 14.2498, 156.934, -81.3372, 15.9302, 148.35, -79.4992, 14.2498, 143.913, -37.5145, 15.9302, 161.644, -36.5775, 14.2498, 156.934, -77.9456, 11.8519, 140.162, -35.7855, 11.8519, 152.952, -79.4992, 14.2498, 143.913, -77.9456, 11.8519, 140.162, -36.5775, 14.2498, 156.934, -35.7855, 11.8519, 152.952, -76.7665, 8.876, 137.316, -35.1844, 8.876, 149.93, -77.9456, 11.8519, 140.162, -76.7665, 8.876, 137.316, -35.7855, 11.8519, 152.952, -35.1844, 8.876, 149.93, -76.0304, 5.495, 135.539, -34.8091, 5.495, 148.043, -76.7665, 8.876, 137.316, -76.0304, 5.495, 135.539, -35.1844, 8.876, 149.93, -34.8091, 5.495, 148.043, -75.7802, 1.9054, 134.935, -34.6816, 1.9054, 147.402, -76.0304, 5.495, 135.539, -75.7802, 1.9054, 134.935, -34.8091, 5.495, 148.043, -34.6816, 1.9054, 147.402, -76.0304, -1.6842, 135.539, -34.8091, -1.6842, 148.043, -75.7802, 1.9054, 134.935, -76.0304, -1.6842, 135.539, -34.6816, 1.9054, 147.402, -34.8091, -1.6842, 148.043, -76.7665, -5.0652, 137.316, -35.1844, -5.0652, 149.93, -76.0304, -1.6842, 135.539, -76.7665, -5.0652, 137.316, -34.8091, -1.6842, 148.043, -35.1844, -5.0652, 149.93, -77.9456, -8.0411, 140.162, -35.7855, -8.0411, 152.952, -76.7665, -5.0652, 137.316, -77.9456, -8.0411, 140.162, -35.1844, -5.0652, 149.93, -35.7855, -8.0411, 152.952, -79.4992, -10.4389, 143.913, -36.5775, -10.4389, 156.934, -77.9456, -8.0411, 140.162, -79.4992, -10.4389, 143.913, -35.7855, -8.0411, 152.952, -36.5775, -10.4389, 156.934, -81.3372, -12.1193, 148.35, -37.5145, -12.1193, 161.644, -79.4992, -10.4389, 143.913, -81.3372, -12.1193, 148.35, -36.5775, -10.4389, 156.934, -37.5145, -12.1193, 161.644, -83.3525, -12.9847, 153.216, -38.5419, -12.9847, 166.809, -81.3372, -12.1193, 148.35, -83.3525, -12.9847, 153.216, -37.5145, -12.1193, 161.644, -38.5419, -12.9847, 166.809, -85.4282, -12.9847, 158.227, -39.6001, -12.9847, 172.129, -83.3525, -12.9847, 153.216, -85.4282, -12.9847, 158.227, -38.5419, -12.9847, 166.809, -39.6001, -12.9847, 172.129, -87.4435, -12.1193, 163.092, -40.6275, -12.1193, 177.294, -85.4282, -12.9847, 158.227, -87.4435, -12.1193, 163.092, -39.6001, -12.9847, 172.129, -40.6275, -12.1193, 177.294, -89.2814, -10.4389, 167.53, -41.5644, -10.4389, 182.004, -87.4435, -12.1193, 163.092, -89.2814, -10.4389, 167.53, -40.6275, -12.1193, 177.294, -41.5644, -10.4389, 182.004, -90.8351, -8.0411, 171.281, -42.3565, -8.0411, 185.986, -89.2814, -10.4389, 167.53, -90.8351, -8.0411, 171.281, -41.5644, -10.4389, 182.004, -42.3565, -8.0411, 185.986, -92.0142, -5.0652, 174.127, -42.9576, -5.0652, 189.008, -90.8351, -8.0411, 171.281, -92.0142, -5.0652, 174.127, -42.3565, -8.0411, 185.986, -42.9576, -5.0652, 189.008, -92.7502, -1.6842, 175.904, -43.3328, -1.6842, 190.895, -92.0142, -5.0652, 174.127, -92.7502, -1.6842, 175.904, -42.9576, -5.0652, 189.008, -43.3328, -1.6842, 190.895, -93.0004, 1.9054, 176.508, -43.4604, 1.9054, 191.536, -92.7502, -1.6842, 175.904, -93.0004, 1.9054, 176.508, -43.3328, -1.6842, 190.895, -93.0004, 1.9054, 176.508, -138.294, 5.495, 151.561, -92.7502, 5.495, 175.904, -138.657, 1.9054, 152.104, -138.294, 5.495, 151.561, -93.0004, 1.9054, 176.508, -92.7502, 5.495, 175.904, -137.225, 8.876, 149.961, -92.0142, 8.876, 174.127, -138.294, 5.495, 151.561, -137.225, 8.876, 149.961, -92.7502, 5.495, 175.904, -92.0142, 8.876, 174.127, -135.513, 11.8519, 147.399, -90.8351, 11.8519, 171.281, -137.225, 8.876, 149.961, -135.513, 11.8519, 147.399, -92.0142, 8.876, 174.127, -90.8351, 11.8519, 171.281, -133.258, 14.2498, 144.024, -89.2814, 14.2498, 167.53, -135.513, 11.8519, 147.399, -133.258, 14.2498, 144.024, -90.8351, 11.8519, 171.281, -89.2814, 14.2498, 167.53, -130.589, 15.9302, 140.031, -87.4435, 15.9302, 163.092, -133.258, 14.2498, 144.024, -130.589, 15.9302, 140.031, -89.2814, 14.2498, 167.53, -87.4435, 15.9302, 163.092, -127.664, 16.7956, 135.652, -85.4282, 16.7956, 158.227, -130.589, 15.9302, 140.031, -127.664, 16.7956, 135.652, -87.4435, 15.9302, 163.092, -85.4282, 16.7956, 158.227, -124.65, 16.7956, 131.142, -83.3525, 16.7956, 153.216, -127.664, 16.7956, 135.652, -124.65, 16.7956, 131.142, -85.4282, 16.7956, 158.227, -83.3525, 16.7956, 153.216, -121.724, 15.9302, 126.763, -81.3372, 15.9302, 148.35, -124.65, 16.7956, 131.142, -121.724, 15.9302, 126.763, -83.3525, 16.7956, 153.216, -81.3372, 15.9302, 148.35, -119.056, 14.2498, 122.77, -79.4992, 14.2498, 143.913, -121.724, 15.9302, 126.763, -119.056, 14.2498, 122.77, -81.3372, 15.9302, 148.35, -79.4992, 14.2498, 143.913, -116.801, 11.8519, 119.394, -77.9456, 11.8519, 140.162, -119.056, 14.2498, 122.77, -116.801, 11.8519, 119.394, -79.4992, 14.2498, 143.913, -77.9456, 11.8519, 140.162, -115.089, 8.876, 116.832, -76.7665, 8.876, 137.316, -116.801, 11.8519, 119.394, -115.089, 8.876, 116.832, -77.9456, 11.8519, 140.162, -76.7665, 8.876, 137.316, -114.02, 5.495, 115.233, -76.0304, 5.495, 135.539, -115.089, 8.876, 116.832, -114.02, 5.495, 115.233, -76.7665, 8.876, 137.316, -76.0304, 5.495, 135.539, -113.657, 1.9054, 114.689, -75.7802, 1.9054, 134.935, -114.02, 5.495, 115.233, -113.657, 1.9054, 114.689, -76.0304, 5.495, 135.539, -75.7802, 1.9054, 134.935, -114.02, -1.6842, 115.233, -76.0304, -1.6842, 135.539, -113.657, 1.9054, 114.689, -114.02, -1.6842, 115.233, -75.7802, 1.9054, 134.935, -76.0304, -1.6842, 135.539, -115.089, -5.0652, 116.832, -76.7665, -5.0652, 137.316, -114.02, -1.6842, 115.233, -115.089, -5.0652, 116.832, -76.0304, -1.6842, 135.539, -76.7665, -5.0652, 137.316, -116.801, -8.0411, 119.394, -77.9456, -8.0411, 140.162, -115.089, -5.0652, 116.832, -116.801, -8.0411, 119.394, -76.7665, -5.0652, 137.316, -77.9456, -8.0411, 140.162, -119.056, -10.4389, 122.77, -79.4992, -10.4389, 143.913, -116.801, -8.0411, 119.394, -119.056, -10.4389, 122.77, -77.9456, -8.0411, 140.162, -79.4992, -10.4389, 143.913, -121.724, -12.1193, 126.763, -81.3372, -12.1193, 148.35, -119.056, -10.4389, 122.77, -121.724, -12.1193, 126.763, -79.4992, -10.4389, 143.913, -81.3372, -12.1193, 148.35, -124.65, -12.9847, 131.142, -83.3525, -12.9847, 153.216, -121.724, -12.1193, 126.763, -124.65, -12.9847, 131.142, -81.3372, -12.1193, 148.35, -83.3525, -12.9847, 153.216, -127.664, -12.9847, 135.652, -85.4282, -12.9847, 158.227, -124.65, -12.9847, 131.142, -127.664, -12.9847, 135.652, -83.3525, -12.9847, 153.216, -85.4282, -12.9847, 158.227, -130.589, -12.1193, 140.031, -87.4435, -12.1193, 163.092, -127.664, -12.9847, 135.652, -130.589, -12.1193, 140.031, -85.4282, -12.9847, 158.227, -87.4435, -12.1193, 163.092, -133.258, -10.4389, 144.024, -89.2814, -10.4389, 167.53, -130.589, -12.1193, 140.031, -133.258, -10.4389, 144.024, -87.4435, -12.1193, 163.092, -89.2814, -10.4389, 167.53, -135.513, -8.0411, 147.399, -90.8351, -8.0411, 171.281, -133.258, -10.4389, 144.024, -135.513, -8.0411, 147.399, -89.2814, -10.4389, 167.53, -90.8351, -8.0411, 171.281, -137.225, -5.0652, 149.961, -92.0142, -5.0652, 174.127, -135.513, -8.0411, 147.399, -137.225, -5.0652, 149.961, -90.8351, -8.0411, 171.281, -92.0142, -5.0652, 174.127, -138.294, -1.6842, 151.561, -92.7502, -1.6842, 175.904, -137.225, -5.0652, 149.961, -138.294, -1.6842, 151.561, -92.0142, -5.0652, 174.127, -92.7502, -1.6842, 175.904, -138.657, 1.9054, 152.104, -93.0004, 1.9054, 176.508, -138.294, -1.6842, 151.561, -138.657, 1.9054, 152.104, -92.7502, -1.6842, 175.904, -138.657, 1.9054, 152.104, -178.213, 5.495, 118.8, -138.294, 5.495, 151.561, -178.675, 1.9054, 119.262, -178.213, 5.495, 118.8, -138.657, 1.9054, 152.104, -138.294, 5.495, 151.561, -176.853, 8.876, 117.44, -137.225, 8.876, 149.961, -178.213, 5.495, 118.8, -176.853, 8.876, 117.44, -138.294, 5.495, 151.561, -137.225, 8.876, 149.961, -174.674, 11.8519, 115.261, -135.513, 11.8519, 147.399, -176.853, 8.876, 117.44, -174.674, 11.8519, 115.261, -137.225, 8.876, 149.961, -135.513, 11.8519, 147.399, -171.803, 14.2498, 112.39, -133.258, 14.2498, 144.024, -174.674, 11.8519, 115.261, -171.803, 14.2498, 112.39, -135.513, 11.8519, 147.399, -133.258, 14.2498, 144.024, -168.407, 15.9302, 108.994, -130.589, 15.9302, 140.031, -171.803, 14.2498, 112.39, -168.407, 15.9302, 108.994, -133.258, 14.2498, 144.024, -130.589, 15.9302, 140.031, -164.683, 16.7956, 105.271, -127.664, 16.7956, 135.652, -168.407, 15.9302, 108.994, -164.683, 16.7956, 105.271, -130.589, 15.9302, 140.031, -127.664, 16.7956, 135.652, -160.848, 16.7956, 101.435, -124.65, 16.7956, 131.142, -164.683, 16.7956, 105.271, -160.848, 16.7956, 101.435, -127.664, 16.7956, 135.652, -124.65, 16.7956, 131.142, -157.124, 15.9302, 97.7113, -121.724, 15.9302, 126.763, -160.848, 16.7956, 101.435, -157.124, 15.9302, 97.7113, -124.65, 16.7956, 131.142, -121.724, 15.9302, 126.763, -153.728, 14.2498, 94.3153, -119.056, 14.2498, 122.77, -157.124, 15.9302, 97.7113, -153.728, 14.2498, 94.3153, -121.724, 15.9302, 126.763, -119.056, 14.2498, 122.77, -150.857, 11.8519, 91.4445, -116.801, 11.8519, 119.394, -153.728, 14.2498, 94.3153, -150.857, 11.8519, 91.4445, -119.056, 14.2498, 122.77, -116.801, 11.8519, 119.394, -148.678, 8.876, 89.2658, -115.089, 8.876, 116.832, -150.857, 11.8519, 91.4445, -148.678, 8.876, 89.2658, -116.801, 11.8519, 119.394, -115.089, 8.876, 116.832, -147.318, 5.495, 87.9057, -114.02, 5.495, 115.233, -148.678, 8.876, 89.2658, -147.318, 5.495, 87.9057, -115.089, 8.876, 116.832, -114.02, 5.495, 115.233, -146.856, 1.9054, 87.4435, -113.657, 1.9054, 114.689, -147.318, 5.495, 87.9057, -146.856, 1.9054, 87.4435, -114.02, 5.495, 115.233, -113.657, 1.9054, 114.689, -147.318, -1.6842, 87.9057, -114.02, -1.6842, 115.233, -146.856, 1.9054, 87.4435, -147.318, -1.6842, 87.9057, -113.657, 1.9054, 114.689, -114.02, -1.6842, 115.233, -148.678, -5.0652, 89.2658, -115.089, -5.0652, 116.832, -147.318, -1.6842, 87.9057, -148.678, -5.0652, 89.2658, -114.02, -1.6842, 115.233, -115.089, -5.0652, 116.832, -150.857, -8.0411, 91.4445, -116.801, -8.0411, 119.394, -148.678, -5.0652, 89.2658, -150.857, -8.0411, 91.4445, -115.089, -5.0652, 116.832, -116.801, -8.0411, 119.394, -153.728, -10.4389, 94.3153, -119.056, -10.4389, 122.77, -150.857, -8.0411, 91.4445, -153.728, -10.4389, 94.3153, -116.801, -8.0411, 119.394, -119.056, -10.4389, 122.77, -157.124, -12.1193, 97.7113, -121.724, -12.1193, 126.763, -153.728, -10.4389, 94.3153, -157.124, -12.1193, 97.7113, -119.056, -10.4389, 122.77, -121.724, -12.1193, 126.763, -160.848, -12.9847, 101.435, -124.65, -12.9847, 131.142, -157.124, -12.1193, 97.7113, -160.848, -12.9847, 101.435, -121.724, -12.1193, 126.763, -124.65, -12.9847, 131.142, -164.683, -12.9847, 105.271, -127.664, -12.9847, 135.652, -160.848, -12.9847, 101.435, -164.683, -12.9847, 105.271, -124.65, -12.9847, 131.142, -127.664, -12.9847, 135.652, -168.407, -12.1193, 108.994, -130.589, -12.1193, 140.031, -164.683, -12.9847, 105.271, -168.407, -12.1193, 108.994, -127.664, -12.9847, 135.652, -130.589, -12.1193, 140.031, -171.803, -10.4389, 112.39, -133.258, -10.4389, 144.024, -168.407, -12.1193, 108.994, -171.803, -10.4389, 112.39, -130.589, -12.1193, 140.031, -133.258, -10.4389, 144.024, -174.674, -8.0411, 115.261, -135.513, -8.0411, 147.399, -171.803, -10.4389, 112.39, -174.674, -8.0411, 115.261, -133.258, -10.4389, 144.024, -135.513, -8.0411, 147.399, -176.853, -5.0652, 117.44, -137.225, -5.0652, 149.961, -174.674, -8.0411, 115.261, -176.853, -5.0652, 117.44, -135.513, -8.0411, 147.399, -137.225, -5.0652, 149.961, -178.213, -1.6842, 118.8, -138.294, -1.6842, 151.561, -176.853, -5.0652, 117.44, -178.213, -1.6842, 118.8, -137.225, -5.0652, 149.961, -138.294, -1.6842, 151.561, -178.675, 1.9054, 119.262, -138.657, 1.9054, 152.104, -178.213, -1.6842, 118.8, -178.675, 1.9054, 119.262, -138.294, -1.6842, 151.561, -178.675, 1.9054, 119.262, -210.973, 5.495, 78.8808, -178.213, 5.495, 118.8, -211.517, 1.9054, 79.244, -210.973, 5.495, 78.8808, -178.675, 1.9054, 119.262, -178.213, 5.495, 118.8, -209.374, 8.876, 77.8122, -176.853, 8.876, 117.44, -210.973, 5.495, 78.8808, -209.374, 8.876, 77.8122, -178.213, 5.495, 118.8, -176.853, 8.876, 117.44, -206.812, 11.8519, 76.1004, -174.674, 11.8519, 115.261, -209.374, 8.876, 77.8122, -206.812, 11.8519, 76.1004, -176.853, 8.876, 117.44, -174.674, 11.8519, 115.261, -203.437, 14.2498, 73.8449, -171.803, 14.2498, 112.39, -206.812, 11.8519, 76.1004, -203.437, 14.2498, 73.8449, -174.674, 11.8519, 115.261, -171.803, 14.2498, 112.39, -199.443, 15.9302, 71.1767, -168.407, 15.9302, 108.994, -203.437, 14.2498, 73.8449, -199.443, 15.9302, 71.1767, -171.803, 14.2498, 112.39, -168.407, 15.9302, 108.994, -195.064, 16.7956, 68.2508, -164.683, 16.7956, 105.271, -199.443, 15.9302, 71.1767, -195.064, 16.7956, 68.2508, -168.407, 15.9302, 108.994, -164.683, 16.7956, 105.271, -190.555, 16.7956, 65.2374, -160.848, 16.7956, 101.435, -195.064, 16.7956, 68.2508, -190.555, 16.7956, 65.2374, -164.683, 16.7956, 105.271, -160.848, 16.7956, 101.435, -186.176, 15.9302, 62.3116, -157.124, 15.9302, 97.7113, -190.555, 16.7956, 65.2374, -186.176, 15.9302, 62.3116, -160.848, 16.7956, 101.435, -157.124, 15.9302, 97.7113, -182.182, 14.2498, 59.6434, -153.728, 14.2498, 94.3153, -186.176, 15.9302, 62.3116, -182.182, 14.2498, 59.6434, -157.124, 15.9302, 97.7113, -153.728, 14.2498, 94.3153, -178.807, 11.8519, 57.3878, -150.857, 11.8519, 91.4445, -182.182, 14.2498, 59.6434, -178.807, 11.8519, 57.3878, -153.728, 14.2498, 94.3153, -150.857, 11.8519, 91.4445, -176.245, 8.876, 55.676, -148.678, 8.876, 89.2658, -178.807, 11.8519, 57.3878, -176.245, 8.876, 55.676, -150.857, 11.8519, 91.4445, -148.678, 8.876, 89.2658, -174.646, 5.495, 54.6074, -147.318, 5.495, 87.9057, -176.245, 8.876, 55.676, -174.646, 5.495, 54.6074, -148.678, 8.876, 89.2658, -147.318, 5.495, 87.9057, -174.102, 1.9054, 54.2442, -146.856, 1.9054, 87.4435, -174.646, 5.495, 54.6074, -174.102, 1.9054, 54.2442, -147.318, 5.495, 87.9057, -146.856, 1.9054, 87.4435, -174.646, -1.6842, 54.6074, -147.318, -1.6842, 87.9057, -174.102, 1.9054, 54.2442, -174.646, -1.6842, 54.6074, -146.856, 1.9054, 87.4435, -147.318, -1.6842, 87.9057, -176.245, -5.0652, 55.676, -148.678, -5.0652, 89.2658, -174.646, -1.6842, 54.6074, -176.245, -5.0652, 55.676, -147.318, -1.6842, 87.9057, -148.678, -5.0652, 89.2658, -178.807, -8.0411, 57.3878, -150.857, -8.0411, 91.4445, -176.245, -5.0652, 55.676, -178.807, -8.0411, 57.3878, -148.678, -5.0652, 89.2658, -150.857, -8.0411, 91.4445, -182.182, -10.4389, 59.6434, -153.728, -10.4389, 94.3153, -178.807, -8.0411, 57.3878, -182.182, -10.4389, 59.6434, -150.857, -8.0411, 91.4445, -153.728, -10.4389, 94.3153, -186.176, -12.1193, 62.3116, -157.124, -12.1193, 97.7113, -182.182, -10.4389, 59.6434, -186.176, -12.1193, 62.3116, -153.728, -10.4389, 94.3153, -157.124, -12.1193, 97.7113, -190.555, -12.9847, 65.2374, -160.848, -12.9847, 101.435, -186.176, -12.1193, 62.3116, -190.555, -12.9847, 65.2374, -157.124, -12.1193, 97.7113, -160.848, -12.9847, 101.435, -195.064, -12.9847, 68.2508, -164.683, -12.9847, 105.271, -190.555, -12.9847, 65.2374, -195.064, -12.9847, 68.2508, -160.848, -12.9847, 101.435, -164.683, -12.9847, 105.271, -199.443, -12.1193, 71.1767, -168.407, -12.1193, 108.994, -195.064, -12.9847, 68.2508, -199.443, -12.1193, 71.1767, -164.683, -12.9847, 105.271, -168.407, -12.1193, 108.994, -203.437, -10.4389, 73.8449, -171.803, -10.4389, 112.39, -199.443, -12.1193, 71.1767, -203.437, -10.4389, 73.8449, -168.407, -12.1193, 108.994, -171.803, -10.4389, 112.39, -206.812, -8.0411, 76.1004, -174.674, -8.0411, 115.261, -203.437, -10.4389, 73.8449, -206.812, -8.0411, 76.1004, -171.803, -10.4389, 112.39, -174.674, -8.0411, 115.261, -209.374, -5.0652, 77.8122, -176.853, -5.0652, 117.44, -206.812, -8.0411, 76.1004, -209.374, -5.0652, 77.8122, -174.674, -8.0411, 115.261, -176.853, -5.0652, 117.44, -210.973, -1.6842, 78.8808, -178.213, -1.6842, 118.8, -209.374, -5.0652, 77.8122, -210.973, -1.6842, 78.8808, -176.853, -5.0652, 117.44, -178.213, -1.6842, 118.8, -211.517, 1.9054, 79.244, -178.675, 1.9054, 119.262, -210.973, -1.6842, 78.8808, -211.517, 1.9054, 79.244, -178.213, -1.6842, 118.8, -211.517, 1.9054, 79.244, -235.317, 5.495, 33.3375, -210.973, 5.495, 78.8808, -235.921, 1.9054, 33.5877, -235.317, 5.495, 33.3375, -211.517, 1.9054, 79.244, -210.973, 5.495, 78.8808, -233.54, 8.876, 32.6014, -209.374, 8.876, 77.8122, -235.317, 5.495, 33.3375, -233.54, 8.876, 32.6014, -210.973, 5.495, 78.8808, -209.374, 8.876, 77.8122, -230.693, 11.8519, 31.4223, -206.812, 11.8519, 76.1004, -233.54, 8.876, 32.6014, -230.693, 11.8519, 31.4223, -209.374, 8.876, 77.8122, -206.812, 11.8519, 76.1004, -226.942, 14.2498, 29.8687, -203.437, 14.2498, 73.8449, -230.693, 11.8519, 31.4223, -226.942, 14.2498, 29.8687, -206.812, 11.8519, 76.1004, -203.437, 14.2498, 73.8449, -222.505, 15.9302, 28.0308, -199.443, 15.9302, 71.1767, -226.942, 14.2498, 29.8687, -222.505, 15.9302, 28.0308, -203.437, 14.2498, 73.8449, -199.443, 15.9302, 71.1767, -217.64, 16.7956, 26.0154, -195.064, 16.7956, 68.2508, -222.505, 15.9302, 28.0308, -217.64, 16.7956, 26.0154, -199.443, 15.9302, 71.1767, -195.064, 16.7956, 68.2508, -212.629, 16.7956, 23.9398, -190.555, 16.7956, 65.2374, -217.64, 16.7956, 26.0154, -212.629, 16.7956, 23.9398, -195.064, 16.7956, 68.2508, -190.555, 16.7956, 65.2374, -207.763, 15.9302, 21.9244, -186.176, 15.9302, 62.3116, -212.629, 16.7956, 23.9398, -207.763, 15.9302, 21.9244, -190.555, 16.7956, 65.2374, -186.176, 15.9302, 62.3116, -203.326, 14.2498, 20.0865, -182.182, 14.2498, 59.6434, -207.763, 15.9302, 21.9244, -203.326, 14.2498, 20.0865, -186.176, 15.9302, 62.3116, -182.182, 14.2498, 59.6434, -199.575, 11.8519, 18.5329, -178.807, 11.8519, 57.3878, -203.326, 14.2498, 20.0865, -199.575, 11.8519, 18.5329, -182.182, 14.2498, 59.6434, -178.807, 11.8519, 57.3878, -196.729, 8.876, 17.3537, -176.245, 8.876, 55.676, -199.575, 11.8519, 18.5329, -196.729, 8.876, 17.3537, -178.807, 11.8519, 57.3878, -176.245, 8.876, 55.676, -194.952, 5.495, 16.6177, -174.646, 5.495, 54.6074, -196.729, 8.876, 17.3537, -194.952, 5.495, 16.6177, -176.245, 8.876, 55.676, -174.646, 5.495, 54.6074, -194.348, 1.9054, 16.3675, -174.102, 1.9054, 54.2442, -194.952, 5.495, 16.6177, -194.348, 1.9054, 16.3675, -174.646, 5.495, 54.6074, -174.102, 1.9054, 54.2442, -194.952, -1.6842, 16.6177, -174.646, -1.6842, 54.6074, -194.348, 1.9054, 16.3675, -194.952, -1.6842, 16.6177, -174.102, 1.9054, 54.2442, -174.646, -1.6842, 54.6074, -196.729, -5.0652, 17.3537, -176.245, -5.0652, 55.676, -194.952, -1.6842, 16.6177, -196.729, -5.0652, 17.3537, -174.646, -1.6842, 54.6074, -176.245, -5.0652, 55.676, -199.575, -8.0411, 18.5329, -178.807, -8.0411, 57.3878, -196.729, -5.0652, 17.3537, -199.575, -8.0411, 18.5329, -176.245, -5.0652, 55.676, -178.807, -8.0411, 57.3878, -203.326, -10.4389, 20.0865, -182.182, -10.4389, 59.6434, -199.575, -8.0411, 18.5329, -203.326, -10.4389, 20.0865, -178.807, -8.0411, 57.3878, -182.182, -10.4389, 59.6434, -207.763, -12.1193, 21.9244, -186.176, -12.1193, 62.3116, -203.326, -10.4389, 20.0865, -207.763, -12.1193, 21.9244, -182.182, -10.4389, 59.6434, -186.176, -12.1193, 62.3116, -212.629, -12.9847, 23.9398, -190.555, -12.9847, 65.2374, -207.763, -12.1193, 21.9244, -212.629, -12.9847, 23.9398, -186.176, -12.1193, 62.3116, -190.555, -12.9847, 65.2374, -217.64, -12.9847, 26.0154, -195.064, -12.9847, 68.2508, -212.629, -12.9847, 23.9398, -217.64, -12.9847, 26.0154, -190.555, -12.9847, 65.2374, -195.064, -12.9847, 68.2508, -222.505, -12.1193, 28.0308, -199.443, -12.1193, 71.1767, -217.64, -12.9847, 26.0154, -222.505, -12.1193, 28.0308, -195.064, -12.9847, 68.2508, -199.443, -12.1193, 71.1767, -226.942, -10.4389, 29.8687, -203.437, -10.4389, 73.8449, -222.505, -12.1193, 28.0308, -226.942, -10.4389, 29.8687, -199.443, -12.1193, 71.1767, -203.437, -10.4389, 73.8449, -230.693, -8.0411, 31.4223, -206.812, -8.0411, 76.1004, -226.942, -10.4389, 29.8687, -230.693, -8.0411, 31.4223, -203.437, -10.4389, 73.8449, -206.812, -8.0411, 76.1004, -233.54, -5.0652, 32.6014, -209.374, -5.0652, 77.8122, -230.693, -8.0411, 31.4223, -233.54, -5.0652, 32.6014, -206.812, -8.0411, 76.1004, -209.374, -5.0652, 77.8122, -235.317, -1.6842, 33.3375, -210.973, -1.6842, 78.8808, -233.54, -5.0652, 32.6014, -235.317, -1.6842, 33.3375, -209.374, -5.0652, 77.8122, -210.973, -1.6842, 78.8808, -235.921, 1.9054, 33.5877, -211.517, 1.9054, 79.244, -235.317, -1.6842, 33.3375, -235.921, 1.9054, 33.5877, -210.973, -1.6842, 78.8808, -235.921, 1.9054, 33.5877, -250.307, 5.495, -16.0799, -235.317, 5.495, 33.3375, -250.949, 1.9054, -15.9524, -250.307, 5.495, -16.0799, -235.921, 1.9054, 33.5877, -235.317, 5.495, 33.3375, -248.421, 8.876, -16.4552, -233.54, 8.876, 32.6014, -250.307, 5.495, -16.0799, -248.421, 8.876, -16.4552, -235.317, 5.495, 33.3375, -233.54, 8.876, 32.6014, -245.399, 11.8519, -17.0563, -230.693, 11.8519, 31.4223, -248.421, 8.876, -16.4552, -245.399, 11.8519, -17.0563, -233.54, 8.876, 32.6014, -230.693, 11.8519, 31.4223, -241.417, 14.2498, -17.8483, -226.942, 14.2498, 29.8687, -245.399, 11.8519, -17.0563, -241.417, 14.2498, -17.8483, -230.693, 11.8519, 31.4223, -226.942, 14.2498, 29.8687, -236.707, 15.9302, -18.7853, -222.505, 15.9302, 28.0308, -241.417, 14.2498, -17.8483, -236.707, 15.9302, -18.7853, -226.942, 14.2498, 29.8687, -222.505, 15.9302, 28.0308, -231.542, 16.7956, -19.8127, -217.64, 16.7956, 26.0154, -236.707, 15.9302, -18.7853, -231.542, 16.7956, -19.8127, -222.505, 15.9302, 28.0308, -217.64, 16.7956, 26.0154, -226.222, 16.7956, -20.8709, -212.629, 16.7956, 23.9398, -231.542, 16.7956, -19.8127, -226.222, 16.7956, -20.8709, -217.64, 16.7956, 26.0154, -212.629, 16.7956, 23.9398, -221.057, 15.9302, -21.8983, -207.763, 15.9302, 21.9244, -226.222, 16.7956, -20.8709, -221.057, 15.9302, -21.8983, -212.629, 16.7956, 23.9398, -207.763, 15.9302, 21.9244, -216.346, 14.2498, -22.8352, -203.326, 14.2498, 20.0865, -221.057, 15.9302, -21.8983, -216.346, 14.2498, -22.8352, -207.763, 15.9302, 21.9244, -203.326, 14.2498, 20.0865, -212.364, 11.8519, -23.6273, -199.575, 11.8519, 18.5329, -216.346, 14.2498, -22.8352, -212.364, 11.8519, -23.6273, -203.326, 14.2498, 20.0865, -199.575, 11.8519, 18.5329, -209.342, 8.876, -24.2284, -196.729, 8.876, 17.3537, -212.364, 11.8519, -23.6273, -209.342, 8.876, -24.2284, -199.575, 11.8519, 18.5329, -196.729, 8.876, 17.3537, -207.456, 5.495, -24.6036, -194.952, 5.495, 16.6177, -209.342, 8.876, -24.2284, -207.456, 5.495, -24.6036, -196.729, 8.876, 17.3537, -194.952, 5.495, 16.6177, -206.815, 1.9054, -24.7312, -194.348, 1.9054, 16.3675, -207.456, 5.495, -24.6036, -206.815, 1.9054, -24.7312, -194.952, 5.495, 16.6177, -194.348, 1.9054, 16.3675, -207.456, -1.6842, -24.6036, -194.952, -1.6842, 16.6177, -206.815, 1.9054, -24.7312, -207.456, -1.6842, -24.6036, -194.348, 1.9054, 16.3675, -194.952, -1.6842, 16.6177, -209.342, -5.0652, -24.2284, -196.729, -5.0652, 17.3537, -207.456, -1.6842, -24.6036, -209.342, -5.0652, -24.2284, -194.952, -1.6842, 16.6177, -196.729, -5.0652, 17.3537, -212.364, -8.0411, -23.6273, -199.575, -8.0411, 18.5329, -209.342, -5.0652, -24.2284, -212.364, -8.0411, -23.6273, -196.729, -5.0652, 17.3537, -199.575, -8.0411, 18.5329, -216.346, -10.4389, -22.8352, -203.326, -10.4389, 20.0865, -212.364, -8.0411, -23.6273, -216.346, -10.4389, -22.8352, -199.575, -8.0411, 18.5329, -203.326, -10.4389, 20.0865, -221.057, -12.1193, -21.8983, -207.763, -12.1193, 21.9244, -216.346, -10.4389, -22.8352, -221.057, -12.1193, -21.8983, -203.326, -10.4389, 20.0865, -207.763, -12.1193, 21.9244, -226.222, -12.9847, -20.8709, -212.629, -12.9847, 23.9398, -221.057, -12.1193, -21.8983, -226.222, -12.9847, -20.8709, -207.763, -12.1193, 21.9244, -212.629, -12.9847, 23.9398, -231.542, -12.9847, -19.8127, -217.64, -12.9847, 26.0154, -226.222, -12.9847, -20.8709, -231.542, -12.9847, -19.8127, -212.629, -12.9847, 23.9398, -217.64, -12.9847, 26.0154, -236.707, -12.1193, -18.7853, -222.505, -12.1193, 28.0308, -231.542, -12.9847, -19.8127, -236.707, -12.1193, -18.7853, -217.64, -12.9847, 26.0154, -222.505, -12.1193, 28.0308, -241.417, -10.4389, -17.8483, -226.942, -10.4389, 29.8687, -236.707, -12.1193, -18.7853, -241.417, -10.4389, -17.8483, -222.505, -12.1193, 28.0308, -226.942, -10.4389, 29.8687, -245.399, -8.0411, -17.0563, -230.693, -8.0411, 31.4223, -241.417, -10.4389, -17.8483, -245.399, -8.0411, -17.0563, -226.942, -10.4389, 29.8687, -230.693, -8.0411, 31.4223, -248.421, -5.0652, -16.4552, -233.54, -5.0652, 32.6014, -245.399, -8.0411, -17.0563, -248.421, -5.0652, -16.4552, -230.693, -8.0411, 31.4223, -233.54, -5.0652, 32.6014, -250.307, -1.6842, -16.0799, -235.317, -1.6842, 33.3375, -248.421, -5.0652, -16.4552, -250.307, -1.6842, -16.0799, -233.54, -5.0652, 32.6014, -235.317, -1.6842, 33.3375, -250.949, 1.9054, -15.9524, -235.921, 1.9054, 33.5877, -250.307, -1.6842, -16.0799, -250.949, 1.9054, -15.9524, -235.317, -1.6842, 33.3375, -253.7, 5.495, -50.5297, -250.307, 5.495, -16.0799, -253.917, 4.3155, -50.5431, -250.307, 5.495, -16.0799, -250.949, 1.9054, -15.9524, -253.917, 4.3155, -50.5431, -253.917, 4.3155, -50.5431, -250.949, 1.9054, -15.9524, -254.358, 1.9054, -50.5705, -251.765, 8.8761, -50.4097, -248.421, 8.876, -16.4552, -252.406, 7.7562, -50.4494, -248.421, 8.876, -16.4552, -250.307, 5.495, -16.0799, -252.406, 7.7562, -50.4494, -252.406, 7.7562, -50.4494, -250.307, 5.495, -16.0799, -253.7, 5.495, -50.5297, -248.665, 11.8519, -50.2175, -245.399, 11.8519, -17.0563, -249.71, 10.8492, -50.2822, -245.399, 11.8519, -17.0563, -248.421, 8.876, -16.4552, -249.71, 10.8492, -50.2822, -249.71, 10.8492, -50.2822, -248.421, 8.876, -16.4552, -251.765, 8.8761, -50.4097, -244.58, 14.2498, -49.9642, -241.417, 14.2498, -17.8483, -245.992, 13.4212, -50.0517, -241.417, 14.2498, -17.8483, -245.399, 11.8519, -17.0563, -245.992, 13.4212, -50.0517, -245.992, 13.4212, -50.0517, -245.399, 11.8519, -17.0563, -248.665, 11.8519, -50.2175, -239.748, 15.9302, -49.6646, -236.707, 15.9302, -18.7853, -241.472, 15.3308, -49.7714, -236.707, 15.9302, -18.7853, -241.417, 14.2498, -17.8483, -241.472, 15.3308, -49.7714, -241.472, 15.3308, -49.7714, -241.417, 14.2498, -17.8483, -244.58, 14.2498, -49.9642, -234.449, 16.7956, -49.336, -231.542, 16.7956, -19.8127, -236.41, 16.4754, -49.4576, -231.542, 16.7956, -19.8127, -236.707, 15.9302, -18.7853, -236.41, 16.4754, -49.4576, -236.41, 16.4754, -49.4576, -236.707, 15.9302, -18.7853, -239.748, 15.9302, -49.6646, -228.992, 16.7956, -48.9977, -226.222, 16.7956, -20.8709, -231.093, 16.7956, -49.1279, -226.222, 16.7956, -20.8709, -231.542, 16.7956, -19.8127, -231.093, 16.7956, -49.1279, -231.093, 16.7956, -49.1279, -231.542, 16.7956, -19.8127, -234.449, 16.7956, -49.336, -223.693, 15.9302, -48.6691, -221.057, 15.9302, -21.8983, -225.816, 16.2769, -48.8008, -221.057, 15.9302, -21.8983, -226.222, 16.7956, -20.8709, -225.816, 16.2769, -48.8008, -225.816, 16.2769, -48.8008, -226.222, 16.7956, -20.8709, -228.992, 16.7956, -48.9977, -218.861, 14.2498, -48.3695, -216.346, 14.2498, -22.8352, -220.873, 14.9495, -48.4943, -216.346, 14.2498, -22.8352, -221.057, 15.9302, -21.8983, -220.873, 14.9495, -48.4943, -220.873, 14.9495, -48.4943, -221.057, 15.9302, -21.8983, -223.693, 15.9302, -48.6691, -214.776, 11.8519, -48.1162, -212.364, 11.8519, -23.6273, -216.538, 12.8859, -48.2254, -212.364, 11.8519, -23.6273, -216.346, 14.2498, -22.8352, -216.538, 12.8859, -48.2254, -216.538, 12.8859, -48.2254, -216.346, 14.2498, -22.8352, -218.861, 14.2498, -48.3695, -211.676, 8.8761, -47.924, -209.342, 8.876, -24.2284, -213.052, 10.1971, -48.0094, -209.342, 8.876, -24.2284, -212.364, 11.8519, -23.6273, -213.052, 10.1971, -48.0094, -213.052, 10.1971, -48.0094, -212.364, 11.8519, -23.6273, -214.776, 11.8519, -48.1162, -209.741, 5.495, -47.804, -207.456, 5.495, -24.6036, -210.619, 7.0285, -47.8585, -207.456, 5.495, -24.6036, -209.342, 8.876, -24.2284, -210.619, 7.0285, -47.8585, -210.619, 7.0285, -47.8585, -209.342, 8.876, -24.2284, -211.676, 8.8761, -47.924, -209.083, 1.9054, -47.7632, -206.815, 1.9054, -24.7312, -209.385, 3.5542, -47.782, -206.815, 1.9054, -24.7312, -207.456, 5.495, -24.6036, -209.385, 3.5542, -47.782, -209.385, 3.5542, -47.782, -207.456, 5.495, -24.6036, -209.741, 5.495, -47.804, -209.741, -1.6842, -47.804, -207.456, -1.6842, -24.6036, -209.438, -0.0308, -47.7852, -207.456, -1.6842, -24.6036, -206.815, 1.9054, -24.7312, -209.438, -0.0308, -47.7852, -209.438, -0.0308, -47.7852, -206.815, 1.9054, -24.7312, -209.083, 1.9054, -47.7632, -211.676, -5.0652, -47.924, -209.342, -5.0652, -24.2284, -210.791, -3.5191, -47.8691, -209.342, -5.0652, -24.2284, -207.456, -1.6842, -24.6036, -210.791, -3.5191, -47.8691, -210.791, -3.5191, -47.8691, -207.456, -1.6842, -24.6036, -209.741, -1.6842, -47.804, -214.776, -8.0411, -48.1162, -212.364, -8.0411, -23.6273, -213.383, -6.7031, -48.0298, -212.364, -8.0411, -23.6273, -209.342, -5.0652, -24.2284, -213.383, -6.7031, -48.0298, -213.383, -6.7031, -48.0298, -209.342, -5.0652, -24.2284, -211.676, -5.0652, -47.924, -218.861, -10.4389, -48.3695, -216.346, -10.4389, -22.8352, -217.071, -9.3881, -48.2585, -216.346, -10.4389, -22.8352, -212.364, -8.0411, -23.6273, -217.071, -9.3881, -48.2585, -217.071, -9.3881, -48.2585, -212.364, -8.0411, -23.6273, -214.776, -8.0411, -48.1162, -223.693, -12.1193, -48.6691, -221.057, -12.1193, -21.8983, -221.644, -11.4067, -48.5421, -221.057, -12.1193, -21.8983, -216.346, -10.4389, -22.8352, -221.644, -11.4067, -48.5421, -221.644, -11.4067, -48.5421, -216.346, -10.4389, -22.8352, -218.861, -10.4389, -48.3695, -228.992, -12.9847, -48.9977, -226.222, -12.9847, -20.8709, -226.828, -12.6314, -48.8635, -226.222, -12.9847, -20.8709, -221.057, -12.1193, -21.8983, -226.828, -12.6314, -48.8635, -226.828, -12.6314, -48.8635, -221.057, -12.1193, -21.8983, -223.693, -12.1193, -48.6691, -234.449, -12.9847, -49.336, -231.542, -12.9847, -19.8127, -232.31, -12.9847, -49.2034, -231.542, -12.9847, -19.8127, -226.222, -12.9847, -20.8709, -232.31, -12.9847, -49.2034, -232.31, -12.9847, -49.2034, -226.222, -12.9847, -20.8709, -228.992, -12.9847, -48.9977, -239.748, -12.1193, -49.6646, -236.707, -12.1193, -18.7853, -237.755, -12.4449, -49.541, -236.707, -12.1193, -18.7853, -231.542, -12.9847, -19.8127, -237.755, -12.4449, -49.541, -237.755, -12.4449, -49.541, -231.542, -12.9847, -19.8127, -234.449, -12.9847, -49.336, -244.58, -10.4389, -49.9642, -241.417, -10.4389, -17.8483, -242.832, -11.0469, -49.8558, -241.417, -10.4389, -17.8483, -236.707, -12.1193, -18.7853, -242.832, -11.0469, -49.8558, -242.832, -11.0469, -49.8558, -236.707, -12.1193, -18.7853, -239.748, -12.1193, -49.6646, -248.665, -8.0411, -50.2175, -245.399, -8.0411, -17.0563, -247.237, -8.8791, -50.129, -245.399, -8.0411, -17.0563, -241.417, -10.4389, -17.8483, -247.237, -8.8791, -50.129, -247.237, -8.8791, -50.129, -241.417, -10.4389, -17.8483, -244.58, -10.4389, -49.9642, -251.765, -5.0652, -50.4097, -248.421, -5.0652, -16.4552, -250.712, -6.0762, -50.3444, -248.421, -5.0652, -16.4552, -245.399, -8.0411, -17.0563, -250.712, -6.0762, -50.3444, -250.712, -6.0762, -50.3444, -245.399, -8.0411, -17.0563, -248.665, -8.0411, -50.2175, -253.7, -1.6842, -50.5297, -250.307, -1.6842, -16.0799, -253.056, -2.8097, -50.4897, -250.307, -1.6842, -16.0799, -248.421, -5.0652, -16.4552, -253.056, -2.8097, -50.4897, -253.056, -2.8097, -50.4897, -248.421, -5.0652, -16.4552, -251.765, -5.0652, -50.4097, -254.358, 1.9054, -50.5705, -250.949, 1.9054, -15.9524, -254.142, 0.7239, -50.557, -250.949, 1.9054, -15.9524, -250.307, -1.6842, -16.0799, -254.142, 0.7239, -50.557, -254.142, 0.7239, -50.557, -250.307, -1.6842, -16.0799, -253.7, -1.6842, -50.5297, 260.638, 4.4899, -18.6388, 260.502, 4.4899, -17.2583, 260.65, 4.417, -18.6381, 260.65, 4.417, -18.6381, 260.502, 4.4899, -17.2583, 261.075, 1.8384, -18.6118, 260.502, 4.4899, -17.2583, 260.933, 1.8384, -17.1727, 261.075, 1.8384, -18.6118, 259.355, 6.9872, -18.7184, 259.236, 6.9872, -17.5103, 259.386, 6.9269, -18.7165, 259.386, 6.9269, -18.7165, 259.236, 6.9872, -17.5103, 260.638, 4.4899, -18.6388, 259.236, 6.9872, -17.5103, 260.502, 4.4899, -17.2583, 260.638, 4.4899, -18.6388, 257.298, 9.1853, -18.8459, 257.206, 9.1853, -17.9139, 257.337, 9.1441, -18.8435, 257.337, 9.1441, -18.8435, 257.206, 9.1853, -17.9139, 259.355, 6.9872, -18.7184, 257.206, 9.1853, -17.9139, 259.236, 6.9872, -17.5103, 259.355, 6.9872, -18.7184, 254.589, 10.9564, -19.014, 254.533, 10.9564, -18.4458, 254.62, 10.936, -19.012, 254.62, 10.936, -19.012, 254.533, 10.9564, -18.4458, 257.298, 9.1853, -18.8459, 254.533, 10.9564, -18.4458, 257.206, 9.1853, -17.9139, 257.298, 9.1853, -18.8459, 251.383, 12.1977, -19.2127, 251.37, 12.1977, -19.0749, 251.392, 12.1941, -19.2121, 251.392, 12.1941, -19.2121, 251.37, 12.1977, -19.0749, 254.589, 10.9564, -19.014, 251.37, 12.1977, -19.0749, 254.533, 10.9564, -18.4458, 254.589, 10.9564, -19.014, 250.357, 12.3842, -19.2763, 251.37, 12.1977, -19.0749, 251.383, 12.1977, -19.2127, 251.383, -8.5208, -19.2127, 251.37, -8.5208, -19.0749, 251.373, -8.5226, -19.2133, 251.373, -8.5226, -19.2133, 251.37, -8.5208, -19.0749, 250.357, -8.7074, -19.2763, 254.589, -7.2796, -19.0139, 254.533, -7.2796, -18.4458, 254.551, -7.294, -19.0163, 254.551, -7.294, -19.0163, 254.533, -7.2796, -18.4458, 251.383, -8.5208, -19.2127, 254.533, -7.2796, -18.4458, 251.37, -8.5208, -19.0749, 251.383, -8.5208, -19.2127, 257.298, -5.5084, -18.8459, 257.206, -5.5084, -17.9139, 257.247, -5.5419, -18.8491, 257.247, -5.5419, -18.8491, 257.206, -5.5084, -17.9139, 254.589, -7.2796, -19.0139, 257.206, -5.5084, -17.9139, 254.533, -7.2796, -18.4458, 254.589, -7.2796, -19.0139, 259.355, -3.3103, -18.7184, 259.236, -3.3103, -17.5103, 259.305, -3.3636, -18.7215, 259.305, -3.3636, -18.7215, 259.236, -3.3103, -17.5103, 257.298, -5.5084, -18.8459, 259.236, -3.3103, -17.5103, 257.206, -5.5084, -17.9139, 257.298, -5.5084, -18.8459, 260.638, -0.813, -18.6388, 260.502, -0.813, -17.2583, 260.603, -0.8818, -18.641, 260.603, -0.8818, -18.641, 260.502, -0.813, -17.2583, 259.355, -3.3103, -18.7184, 260.502, -0.813, -17.2583, 259.236, -3.3103, -17.5103, 259.355, -3.3103, -18.7184, 261.075, 1.8384, -18.6118, 260.933, 1.8384, -17.1727, 261.062, 1.7625, -18.6126, 261.062, 1.7625, -18.6126, 260.933, 1.8384, -17.1727, 260.638, -0.813, -18.6388, 260.933, 1.8384, -17.1727, 260.502, -0.813, -17.2583, 260.638, -0.813, -18.6388, 260.933, 1.8384, -17.1727, 260.502, 4.4899, -17.2583, 245.855, 4.4899, 31.026, 246.261, 1.8384, 31.194, 260.933, 1.8384, -17.1727, 245.855, 4.4899, 31.026, 260.502, 4.4899, -17.2583, 259.236, 6.9872, -17.5103, 244.662, 6.9872, 30.5317, 245.855, 4.4899, 31.026, 260.502, 4.4899, -17.2583, 244.662, 6.9872, 30.5317, 259.236, 6.9872, -17.5103, 257.206, 9.1853, -17.9139, 242.751, 9.1853, 29.74, 244.662, 6.9872, 30.5317, 259.236, 6.9872, -17.5103, 242.751, 9.1853, 29.74, 257.206, 9.1853, -17.9139, 254.533, 10.9564, -18.4458, 240.232, 10.9564, 28.6967, 242.751, 9.1853, 29.74, 257.206, 9.1853, -17.9139, 240.232, 10.9564, 28.6967, 254.533, 10.9564, -18.4458, 251.37, 12.1977, -19.0749, 237.253, 12.1977, 27.4626, 240.232, 10.9564, 28.6967, 254.533, 10.9564, -18.4458, 237.253, 12.1977, 27.4626, 247.801, 12.8368, -19.4348, 233.986, 12.8369, 26.1093, 250.357, 12.3842, -19.2763, 233.986, 12.8369, 26.1093, 251.37, 12.1977, -19.0749, 250.357, 12.3842, -19.2763, 237.253, 12.1977, 27.4626, 251.37, 12.1977, -19.0749, 233.986, 12.8369, 26.1093, 244.083, 12.8369, -19.6653, 230.621, 12.8369, 24.7155, 247.774, 12.8369, -19.4365, 247.774, 12.8369, -19.4365, 230.621, 12.8369, 24.7155, 247.801, 12.8368, -19.4348, 230.621, 12.8369, 24.7155, 233.986, 12.8369, 26.1093, 247.801, 12.8368, -19.4348, 240.474, 12.1977, -19.8891, 227.354, 12.1977, 23.3622, 244.017, 12.8251, -19.6694, 244.017, 12.8251, -19.6694, 227.354, 12.1977, 23.3622, 244.083, 12.8369, -19.6653, 227.354, 12.1977, 23.3622, 230.621, 12.8369, 24.7155, 244.083, 12.8369, -19.6653, 237.182, 10.9564, -20.0932, 224.374, 10.9564, 22.1281, 240.377, 12.1612, -19.8951, 240.377, 12.1612, -19.8951, 224.374, 10.9564, 22.1281, 240.474, 12.1977, -19.8891, 224.374, 10.9564, 22.1281, 227.354, 12.1977, 23.3622, 240.474, 12.1977, -19.8891, 234.399, 9.1853, -20.2658, 221.855, 9.1853, 21.0848, 237.072, 10.8864, -20.1, 237.072, 10.8864, -20.1, 221.855, 9.1853, 21.0848, 237.182, 10.9564, -20.0932, 221.855, 9.1853, 21.0848, 224.374, 10.9564, 22.1281, 237.182, 10.9564, -20.0932, 232.287, 6.9872, -20.3967, 219.944, 6.9872, 20.2931, 234.297, 9.0794, -20.2721, 234.297, 9.0794, -20.2721, 219.944, 6.9872, 20.2931, 234.399, 9.1853, -20.2658, 219.944, 6.9872, 20.2931, 221.855, 9.1853, 21.0848, 234.399, 9.1853, -20.2658, 230.969, 4.4899, -20.4785, 218.751, 4.4899, 19.7988, 232.215, 6.8504, -20.4012, 232.215, 6.8504, -20.4012, 218.751, 4.4899, 19.7988, 232.287, 6.9872, -20.3967, 218.751, 4.4899, 19.7988, 219.944, 6.9872, 20.2931, 232.287, 6.9872, -20.3967, 230.521, 1.8384, -20.5062, 218.345, 1.8384, 19.6308, 230.942, 4.3342, -20.4801, 230.942, 4.3342, -20.4801, 218.345, 1.8384, 19.6308, 230.969, 4.4899, -20.4785, 218.345, 1.8384, 19.6308, 218.751, 4.4899, 19.7988, 230.969, 4.4899, -20.4785, 230.969, -0.813, -20.4785, 218.751, -0.813, 19.7988, 230.547, 1.6799, -20.5046, 230.547, 1.6799, -20.5046, 218.751, -0.813, 19.7988, 230.521, 1.8384, -20.5062, 218.751, -0.813, 19.7988, 218.345, 1.8384, 19.6308, 230.521, 1.8384, -20.5062, 232.287, -3.3103, -20.3967, 219.944, -3.3103, 20.2931, 231.045, -0.9577, -20.4737, 231.045, -0.9577, -20.4737, 219.944, -3.3103, 20.2931, 230.969, -0.813, -20.4785, 219.944, -3.3103, 20.2931, 218.751, -0.813, 19.7988, 230.969, -0.813, -20.4785, 234.399, -5.5084, -20.2658, 221.855, -5.5085, 21.0848, 232.4, -3.4277, -20.3897, 232.4, -3.4277, -20.3897, 221.855, -5.5085, 21.0848, 232.287, -3.3103, -20.3967, 221.855, -5.5085, 21.0848, 219.944, -3.3103, 20.2931, 232.287, -3.3103, -20.3967, 237.182, -7.2796, -20.0932, 224.374, -7.2796, 22.1281, 234.528, -5.5908, -20.2577, 234.528, -5.5908, -20.2577, 224.374, -7.2796, 22.1281, 234.399, -5.5084, -20.2658, 224.374, -7.2796, 22.1281, 221.855, -5.5085, 21.0848, 234.399, -5.5084, -20.2658, 240.474, -8.5208, -19.8891, 227.354, -8.5208, 23.3622, 237.306, -7.3265, -20.0855, 237.306, -7.3265, -20.0855, 227.354, -8.5208, 23.3622, 237.182, -7.2796, -20.0932, 227.354, -8.5208, 23.3622, 224.374, -7.2796, 22.1281, 237.182, -7.2796, -20.0932, 244.083, -9.16, -19.6653, 230.621, -9.16, 24.7155, 240.575, -8.5387, -19.8829, 240.575, -8.5387, -19.8829, 230.621, -9.16, 24.7155, 240.474, -8.5208, -19.8891, 230.621, -9.16, 24.7155, 227.354, -8.5208, 23.3622, 240.474, -8.5208, -19.8891, 247.801, -9.16, -19.4348, 233.986, -9.16, 26.1093, 244.148, -9.16, -19.6613, 244.148, -9.16, -19.6613, 233.986, -9.16, 26.1093, 244.083, -9.16, -19.6653, 233.986, -9.16, 26.1093, 230.621, -9.16, 24.7155, 244.083, -9.16, -19.6653, 250.357, -8.7074, -19.2763, 251.37, -8.5208, -19.0749, 237.253, -8.5208, 27.4626, 250.357, -8.7074, -19.2763, 237.253, -8.5208, 27.4626, 247.827, -9.1555, -19.4332, 247.827, -9.1555, -19.4332, 237.253, -8.5208, 27.4626, 247.801, -9.16, -19.4348, 237.253, -8.5208, 27.4626, 233.986, -9.16, 26.1093, 247.801, -9.16, -19.4348, 251.37, -8.5208, -19.0749, 254.533, -7.2796, -18.4458, 240.232, -7.2796, 28.6967, 237.253, -8.5208, 27.4626, 251.37, -8.5208, -19.0749, 240.232, -7.2796, 28.6967, 254.533, -7.2796, -18.4458, 257.206, -5.5084, -17.9139, 242.751, -5.5084, 29.74, 240.232, -7.2796, 28.6967, 254.533, -7.2796, -18.4458, 242.751, -5.5084, 29.74, 257.206, -5.5084, -17.9139, 259.236, -3.3103, -17.5103, 244.662, -3.3103, 30.5317, 242.751, -5.5084, 29.74, 257.206, -5.5084, -17.9139, 244.662, -3.3103, 30.5317, 259.236, -3.3103, -17.5103, 260.502, -0.813, -17.2583, 245.855, -0.813, 31.026, 244.662, -3.3103, 30.5317, 259.236, -3.3103, -17.5103, 245.855, -0.813, 31.026, 260.502, -0.813, -17.2583, 260.933, 1.8384, -17.1727, 246.261, 1.8384, 31.194, 245.855, -0.813, 31.026, 260.502, -0.813, -17.2583, 246.261, 1.8384, 31.194, 246.261, 1.8384, 31.194, 245.855, 4.4899, 31.026, 222.07, 4.4899, 75.5251, 222.435, 1.8384, 75.769, 246.261, 1.8384, 31.194, 222.07, 4.4899, 75.5251, 245.855, 4.4899, 31.026, 244.662, 6.9872, 30.5317, 220.996, 6.9872, 74.8075, 222.07, 4.4899, 75.5251, 245.855, 4.4899, 31.026, 220.996, 6.9872, 74.8075, 244.662, 6.9872, 30.5317, 242.751, 9.1853, 29.74, 219.276, 9.1853, 73.6581, 220.996, 6.9872, 74.8075, 244.662, 6.9872, 30.5317, 219.276, 9.1853, 73.6581, 242.751, 9.1853, 29.74, 240.232, 10.9564, 28.6967, 217.009, 10.9564, 72.1435, 219.276, 9.1853, 73.6581, 242.751, 9.1853, 29.74, 217.009, 10.9564, 72.1435, 240.232, 10.9564, 28.6967, 237.253, 12.1977, 27.4626, 214.328, 12.1977, 70.3518, 217.009, 10.9564, 72.1435, 240.232, 10.9564, 28.6967, 214.328, 12.1977, 70.3518, 237.253, 12.1977, 27.4626, 233.986, 12.8369, 26.1093, 211.388, 12.8369, 68.3871, 214.328, 12.1977, 70.3518, 237.253, 12.1977, 27.4626, 211.388, 12.8369, 68.3871, 233.986, 12.8369, 26.1093, 230.621, 12.8369, 24.7155, 208.359, 12.8369, 66.3637, 211.388, 12.8369, 68.3871, 233.986, 12.8369, 26.1093, 208.359, 12.8369, 66.3637, 230.621, 12.8369, 24.7155, 227.354, 12.1977, 23.3622, 205.419, 12.1977, 64.399, 208.359, 12.8369, 66.3637, 230.621, 12.8369, 24.7155, 205.419, 12.1977, 64.399, 227.354, 12.1977, 23.3622, 224.374, 10.9564, 22.1281, 202.737, 10.9564, 62.6073, 205.419, 12.1977, 64.399, 227.354, 12.1977, 23.3622, 202.737, 10.9564, 62.6073, 224.374, 10.9564, 22.1281, 221.855, 9.1853, 21.0848, 200.471, 9.1853, 61.0927, 202.737, 10.9564, 62.6073, 224.374, 10.9564, 22.1281, 200.471, 9.1853, 61.0927, 221.855, 9.1853, 21.0848, 219.944, 6.9872, 20.2931, 198.75, 6.9872, 59.9433, 200.471, 9.1853, 61.0927, 221.855, 9.1853, 21.0848, 198.75, 6.9872, 59.9433, 219.944, 6.9872, 20.2931, 218.751, 4.4899, 19.7988, 197.677, 4.4899, 59.2257, 198.75, 6.9872, 59.9433, 219.944, 6.9872, 20.2931, 197.677, 4.4899, 59.2257, 218.751, 4.4899, 19.7988, 218.345, 1.8384, 19.6308, 197.312, 1.8384, 58.9819, 197.677, 4.4899, 59.2257, 218.751, 4.4899, 19.7988, 197.312, 1.8384, 58.9819, 218.345, 1.8384, 19.6308, 218.751, -0.813, 19.7988, 197.677, -0.813, 59.2257, 197.312, 1.8384, 58.9819, 218.345, 1.8384, 19.6308, 197.677, -0.813, 59.2257, 218.751, -0.813, 19.7988, 219.944, -3.3103, 20.2931, 198.75, -3.3103, 59.9433, 197.677, -0.813, 59.2257, 218.751, -0.813, 19.7988, 198.75, -3.3103, 59.9433, 219.944, -3.3103, 20.2931, 221.855, -5.5085, 21.0848, 200.471, -5.5084, 61.0927, 198.75, -3.3103, 59.9433, 219.944, -3.3103, 20.2931, 200.471, -5.5084, 61.0927, 221.855, -5.5085, 21.0848, 224.374, -7.2796, 22.1281, 202.737, -7.2796, 62.6073, 200.471, -5.5084, 61.0927, 221.855, -5.5085, 21.0848, 202.737, -7.2796, 62.6073, 224.374, -7.2796, 22.1281, 227.354, -8.5208, 23.3622, 205.419, -8.5208, 64.399, 202.737, -7.2796, 62.6073, 224.374, -7.2796, 22.1281, 205.419, -8.5208, 64.399, 227.354, -8.5208, 23.3622, 230.621, -9.16, 24.7155, 208.359, -9.16, 66.3637, 205.419, -8.5208, 64.399, 227.354, -8.5208, 23.3622, 208.359, -9.16, 66.3637, 230.621, -9.16, 24.7155, 233.986, -9.16, 26.1093, 211.388, -9.16, 68.3871, 208.359, -9.16, 66.3637, 230.621, -9.16, 24.7155, 211.388, -9.16, 68.3871, 233.986, -9.16, 26.1093, 237.253, -8.5208, 27.4626, 214.328, -8.5208, 70.3518, 211.388, -9.16, 68.3871, 233.986, -9.16, 26.1093, 214.328, -8.5208, 70.3518, 237.253, -8.5208, 27.4626, 240.232, -7.2796, 28.6967, 217.009, -7.2796, 72.1435, 214.328, -8.5208, 70.3518, 237.253, -8.5208, 27.4626, 217.009, -7.2796, 72.1435, 240.232, -7.2796, 28.6967, 242.751, -5.5084, 29.74, 219.276, -5.5084, 73.6581, 217.009, -7.2796, 72.1435, 240.232, -7.2796, 28.6967, 219.276, -5.5084, 73.6581, 242.751, -5.5084, 29.74, 244.662, -3.3103, 30.5317, 220.996, -3.3103, 74.8075, 219.276, -5.5084, 73.6581, 242.751, -5.5084, 29.74, 220.996, -3.3103, 74.8075, 244.662, -3.3103, 30.5317, 245.855, -0.813, 31.026, 222.07, -0.813, 75.5251, 220.996, -3.3103, 74.8075, 244.662, -3.3103, 30.5317, 222.07, -0.813, 75.5251, 245.855, -0.813, 31.026, 246.261, 1.8384, 31.194, 222.435, 1.8384, 75.769, 222.07, -0.813, 75.5251, 245.855, -0.813, 31.026, 222.435, 1.8384, 75.769, 222.435, 1.8384, 75.769, 222.07, 4.4899, 75.5251, 190.061, 4.4899, 114.529, 190.371, 1.8384, 114.839, 222.435, 1.8384, 75.769, 190.061, 4.4899, 114.529, 222.07, 4.4899, 75.5251, 220.996, 6.9872, 74.8075, 189.147, 6.9872, 113.616, 190.061, 4.4899, 114.529, 222.07, 4.4899, 75.5251, 189.147, 6.9872, 113.616, 220.996, 6.9872, 74.8075, 219.276, 9.1853, 73.6581, 187.684, 9.1853, 112.153, 189.147, 6.9872, 113.616, 220.996, 6.9872, 74.8075, 187.684, 9.1853, 112.153, 219.276, 9.1853, 73.6581, 217.009, 10.9564, 72.1435, 185.757, 10.9564, 110.225, 187.684, 9.1853, 112.153, 219.276, 9.1853, 73.6581, 185.757, 10.9564, 110.225, 217.009, 10.9564, 72.1435, 214.328, 12.1977, 70.3518, 183.476, 12.1977, 107.944, 185.757, 10.9564, 110.225, 217.009, 10.9564, 72.1435, 183.476, 12.1977, 107.944, 214.328, 12.1977, 70.3518, 211.388, 12.8369, 68.3871, 180.976, 12.8369, 105.444, 183.476, 12.1977, 107.944, 214.328, 12.1977, 70.3518, 180.976, 12.8369, 105.444, 211.388, 12.8369, 68.3871, 208.359, 12.8369, 66.3637, 178.4, 12.8369, 102.869, 180.976, 12.8369, 105.444, 211.388, 12.8369, 68.3871, 178.4, 12.8369, 102.869, 208.359, 12.8369, 66.3637, 205.419, 12.1977, 64.399, 175.9, 12.1977, 100.368, 178.4, 12.8369, 102.869, 208.359, 12.8369, 66.3637, 175.9, 12.1977, 100.368, 205.419, 12.1977, 64.399, 202.737, 10.9564, 62.6073, 173.62, 10.9564, 98.0877, 175.9, 12.1977, 100.368, 205.419, 12.1977, 64.399, 173.62, 10.9564, 98.0877, 202.737, 10.9564, 62.6073, 200.471, 9.1853, 61.0927, 171.692, 9.1853, 96.1599, 173.62, 10.9564, 98.0877, 202.737, 10.9564, 62.6073, 171.692, 9.1853, 96.1599, 200.471, 9.1853, 61.0927, 198.75, 6.9872, 59.9433, 170.229, 6.9872, 94.697, 171.692, 9.1853, 96.1599, 200.471, 9.1853, 61.0927, 170.229, 6.9872, 94.697, 198.75, 6.9872, 59.9433, 197.677, 4.4899, 59.2257, 169.316, 4.4899, 93.7837, 170.229, 6.9872, 94.697, 198.75, 6.9872, 59.9433, 169.316, 4.4899, 93.7837, 197.677, 4.4899, 59.2257, 197.312, 1.8384, 58.9819, 169.005, 1.8384, 93.4733, 169.316, 4.4899, 93.7837, 197.677, 4.4899, 59.2257, 169.005, 1.8384, 93.4733, 197.312, 1.8384, 58.9819, 197.677, -0.813, 59.2257, 169.316, -0.813, 93.7837, 169.005, 1.8384, 93.4733, 197.312, 1.8384, 58.9819, 169.316, -0.813, 93.7837, 197.677, -0.813, 59.2257, 198.75, -3.3103, 59.9433, 170.229, -3.3103, 94.697, 169.316, -0.813, 93.7837, 197.677, -0.813, 59.2257, 170.229, -3.3103, 94.697, 198.75, -3.3103, 59.9433, 200.471, -5.5084, 61.0927, 171.692, -5.5084, 96.1599, 170.229, -3.3103, 94.697, 198.75, -3.3103, 59.9433, 171.692, -5.5084, 96.1599, 200.471, -5.5084, 61.0927, 202.737, -7.2796, 62.6073, 173.62, -7.2796, 98.0877, 171.692, -5.5084, 96.1599, 200.471, -5.5084, 61.0927, 173.62, -7.2796, 98.0877, 202.737, -7.2796, 62.6073, 205.419, -8.5208, 64.399, 175.9, -8.5208, 100.368, 173.62, -7.2796, 98.0877, 202.737, -7.2796, 62.6073, 175.9, -8.5208, 100.368, 205.419, -8.5208, 64.399, 208.359, -9.16, 66.3637, 178.4, -9.16, 102.869, 175.9, -8.5208, 100.368, 205.419, -8.5208, 64.399, 178.4, -9.16, 102.869, 208.359, -9.16, 66.3637, 211.388, -9.16, 68.3871, 180.976, -9.16, 105.444, 178.4, -9.16, 102.869, 208.359, -9.16, 66.3637, 180.976, -9.16, 105.444, 211.388, -9.16, 68.3871, 214.328, -8.5208, 70.3518, 183.476, -8.5208, 107.944, 180.976, -9.16, 105.444, 211.388, -9.16, 68.3871, 183.476, -8.5208, 107.944, 214.328, -8.5208, 70.3518, 217.009, -7.2796, 72.1435, 185.757, -7.2796, 110.225, 183.476, -8.5208, 107.944, 214.328, -8.5208, 70.3518, 185.757, -7.2796, 110.225, 217.009, -7.2796, 72.1435, 219.276, -5.5084, 73.6581, 187.684, -5.5084, 112.153, 185.757, -7.2796, 110.225, 217.009, -7.2796, 72.1435, 187.684, -5.5084, 112.153, 219.276, -5.5084, 73.6581, 220.996, -3.3103, 74.8075, 189.147, -3.3103, 113.616, 187.684, -5.5084, 112.153, 219.276, -5.5084, 73.6581, 189.147, -3.3103, 113.616, 220.996, -3.3103, 74.8075, 222.07, -0.813, 75.5251, 190.061, -0.813, 114.529, 189.147, -3.3103, 113.616, 220.996, -3.3103, 74.8075, 190.061, -0.813, 114.529, 222.07, -0.813, 75.5251, 222.435, 1.8384, 75.769, 190.371, 1.8384, 114.839, 190.061, -0.813, 114.529, 222.07, -0.813, 75.5251, 190.371, 1.8384, 114.839, 190.371, 1.8384, 114.839, 190.061, 4.4899, 114.529, 151.057, 4.4899, 146.538, 151.301, 1.8384, 146.903, 190.371, 1.8384, 114.839, 151.057, 4.4899, 146.538, 190.061, 4.4899, 114.529, 189.147, 6.9872, 113.616, 150.339, 6.9872, 145.464, 151.057, 4.4899, 146.538, 190.061, 4.4899, 114.529, 150.339, 6.9872, 145.464, 189.147, 6.9872, 113.616, 187.684, 9.1853, 112.153, 149.19, 9.1853, 143.744, 150.339, 6.9872, 145.464, 189.147, 6.9872, 113.616, 149.19, 9.1853, 143.744, 187.684, 9.1853, 112.153, 185.757, 10.9564, 110.225, 147.675, 10.9564, 141.477, 149.19, 9.1853, 143.744, 187.684, 9.1853, 112.153, 147.675, 10.9564, 141.477, 185.757, 10.9564, 110.225, 183.476, 12.1977, 107.944, 145.884, 12.1977, 138.796, 147.675, 10.9564, 141.477, 185.757, 10.9564, 110.225, 145.884, 12.1977, 138.796, 183.476, 12.1977, 107.944, 180.976, 12.8369, 105.444, 143.919, 12.8369, 135.856, 145.884, 12.1977, 138.796, 183.476, 12.1977, 107.944, 143.919, 12.8369, 135.856, 180.976, 12.8369, 105.444, 178.4, 12.8369, 102.869, 141.896, 12.8369, 132.827, 143.919, 12.8369, 135.856, 180.976, 12.8369, 105.444, 141.896, 12.8369, 132.827, 178.4, 12.8369, 102.869, 175.9, 12.1977, 100.368, 139.931, 12.1977, 129.887, 141.896, 12.8369, 132.827, 178.4, 12.8369, 102.869, 139.931, 12.1977, 129.887, 175.9, 12.1977, 100.368, 173.62, 10.9564, 98.0877, 138.139, 10.9564, 127.206, 139.931, 12.1977, 129.887, 175.9, 12.1977, 100.368, 138.139, 10.9564, 127.206, 173.62, 10.9564, 98.0877, 171.692, 9.1853, 96.1599, 136.625, 9.1853, 124.939, 138.139, 10.9564, 127.206, 173.62, 10.9564, 98.0877, 136.625, 9.1853, 124.939, 171.692, 9.1853, 96.1599, 170.229, 6.9872, 94.697, 135.475, 6.9872, 123.219, 136.625, 9.1853, 124.939, 171.692, 9.1853, 96.1599, 135.475, 6.9872, 123.219, 170.229, 6.9872, 94.697, 169.316, 4.4899, 93.7837, 134.758, 4.4899, 122.145, 135.475, 6.9872, 123.219, 170.229, 6.9872, 94.697, 134.758, 4.4899, 122.145, 169.316, 4.4899, 93.7837, 169.005, 1.8384, 93.4733, 134.514, 1.8384, 121.78, 134.758, 4.4899, 122.145, 169.316, 4.4899, 93.7837, 134.514, 1.8384, 121.78, 169.005, 1.8384, 93.4733, 169.316, -0.813, 93.7837, 134.758, -0.813, 122.145, 134.514, 1.8384, 121.78, 169.005, 1.8384, 93.4733, 134.758, -0.813, 122.145, 169.316, -0.813, 93.7837, 170.229, -3.3103, 94.697, 135.475, -3.3103, 123.219, 134.758, -0.813, 122.145, 169.316, -0.813, 93.7837, 135.475, -3.3103, 123.219, 170.229, -3.3103, 94.697, 171.692, -5.5084, 96.1599, 136.625, -5.5084, 124.939, 135.475, -3.3103, 123.219, 170.229, -3.3103, 94.697, 136.625, -5.5084, 124.939, 171.692, -5.5084, 96.1599, 173.62, -7.2796, 98.0877, 138.139, -7.2796, 127.206, 136.625, -5.5084, 124.939, 171.692, -5.5084, 96.1599, 138.139, -7.2796, 127.206, 173.62, -7.2796, 98.0877, 175.9, -8.5208, 100.368, 139.931, -8.5208, 129.887, 138.139, -7.2796, 127.206, 173.62, -7.2796, 98.0877, 139.931, -8.5208, 129.887, 175.9, -8.5208, 100.368, 178.4, -9.16, 102.869, 141.896, -9.16, 132.827, 139.931, -8.5208, 129.887, 175.9, -8.5208, 100.368, 141.896, -9.16, 132.827, 178.4, -9.16, 102.869, 180.976, -9.16, 105.444, 143.919, -9.16, 135.856, 141.896, -9.16, 132.827, 178.4, -9.16, 102.869, 143.919, -9.16, 135.856, 180.976, -9.16, 105.444, 183.476, -8.5208, 107.944, 145.884, -8.5208, 138.796, 143.919, -9.16, 135.856, 180.976, -9.16, 105.444, 145.884, -8.5208, 138.796, 183.476, -8.5208, 107.944, 185.757, -7.2796, 110.225, 147.675, -7.2796, 141.477, 145.884, -8.5208, 138.796, 183.476, -8.5208, 107.944, 147.675, -7.2796, 141.477, 185.757, -7.2796, 110.225, 187.684, -5.5084, 112.153, 149.19, -5.5084, 143.744, 147.675, -7.2796, 141.477, 185.757, -7.2796, 110.225, 149.19, -5.5084, 143.744, 187.684, -5.5084, 112.153, 189.147, -3.3103, 113.616, 150.339, -3.3103, 145.464, 149.19, -5.5084, 143.744, 187.684, -5.5084, 112.153, 150.339, -3.3103, 145.464, 189.147, -3.3103, 113.616, 190.061, -0.813, 114.529, 151.057, -0.813, 146.538, 150.339, -3.3103, 145.464, 189.147, -3.3103, 113.616, 151.057, -0.813, 146.538, 190.061, -0.813, 114.529, 190.371, 1.8384, 114.839, 151.301, 1.8384, 146.903, 151.057, -0.813, 146.538, 190.061, -0.813, 114.529, 151.301, 1.8384, 146.903, 151.301, 1.8384, 146.903, 151.057, 4.4899, 146.538, 106.558, 4.4899, 170.324, 106.726, 1.8384, 170.729, 151.301, 1.8384, 146.903, 106.558, 4.4899, 170.324, 151.057, 4.4899, 146.538, 150.339, 6.9872, 145.464, 106.064, 6.9872, 169.13, 106.558, 4.4899, 170.324, 151.057, 4.4899, 146.538, 106.064, 6.9872, 169.13, 150.339, 6.9872, 145.464, 149.19, 9.1853, 143.744, 105.272, 9.1853, 167.219, 106.064, 6.9872, 169.13, 150.339, 6.9872, 145.464, 105.272, 9.1853, 167.219, 149.19, 9.1853, 143.744, 147.675, 10.9564, 141.477, 104.229, 10.9564, 164.7, 105.272, 9.1853, 167.219, 149.19, 9.1853, 143.744, 104.229, 10.9564, 164.7, 147.675, 10.9564, 141.477, 145.884, 12.1977, 138.796, 102.994, 12.1977, 161.721, 104.229, 10.9564, 164.7, 147.675, 10.9564, 141.477, 102.994, 12.1977, 161.721, 145.884, 12.1977, 138.796, 143.919, 12.8369, 135.856, 101.641, 12.8369, 158.454, 102.994, 12.1977, 161.721, 145.884, 12.1977, 138.796, 101.641, 12.8369, 158.454, 143.919, 12.8369, 135.856, 141.896, 12.8369, 132.827, 100.247, 12.8369, 155.089, 101.641, 12.8369, 158.454, 143.919, 12.8369, 135.856, 100.247, 12.8369, 155.089, 141.896, 12.8369, 132.827, 139.931, 12.1977, 129.887, 98.8941, 12.1977, 151.822, 100.247, 12.8369, 155.089, 141.896, 12.8369, 132.827, 98.8941, 12.1977, 151.822, 139.931, 12.1977, 129.887, 138.139, 10.9564, 127.206, 97.66, 10.9564, 148.842, 98.8941, 12.1977, 151.822, 139.931, 12.1977, 129.887, 97.66, 10.9564, 148.842, 138.139, 10.9564, 127.206, 136.625, 9.1853, 124.939, 96.6167, 9.1853, 146.324, 97.66, 10.9564, 148.842, 138.139, 10.9564, 127.206, 96.6167, 9.1853, 146.324, 136.625, 9.1853, 124.939, 135.475, 6.9872, 123.219, 95.825, 6.9872, 144.412, 96.6167, 9.1853, 146.324, 136.625, 9.1853, 124.939, 95.825, 6.9872, 144.412, 135.475, 6.9872, 123.219, 134.758, 4.4899, 122.145, 95.3307, 4.4899, 143.219, 95.825, 6.9872, 144.412, 135.475, 6.9872, 123.219, 95.3307, 4.4899, 143.219, 134.758, 4.4899, 122.145, 134.514, 1.8384, 121.78, 95.1627, 1.8384, 142.813, 95.3307, 4.4899, 143.219, 134.758, 4.4899, 122.145, 95.1627, 1.8384, 142.813, 134.514, 1.8384, 121.78, 134.758, -0.813, 122.145, 95.3307, -0.813, 143.219, 95.1627, 1.8384, 142.813, 134.514, 1.8384, 121.78, 95.3307, -0.813, 143.219, 134.758, -0.813, 122.145, 135.475, -3.3103, 123.219, 95.825, -3.3103, 144.412, 95.3307, -0.813, 143.219, 134.758, -0.813, 122.145, 95.825, -3.3103, 144.412, 135.475, -3.3103, 123.219, 136.625, -5.5084, 124.939, 96.6167, -5.5084, 146.324, 95.825, -3.3103, 144.412, 135.475, -3.3103, 123.219, 96.6167, -5.5084, 146.324, 136.625, -5.5084, 124.939, 138.139, -7.2796, 127.206, 97.66, -7.2796, 148.842, 96.6167, -5.5084, 146.324, 136.625, -5.5084, 124.939, 97.66, -7.2796, 148.842, 138.139, -7.2796, 127.206, 139.931, -8.5208, 129.887, 98.8941, -8.5208, 151.822, 97.66, -7.2796, 148.842, 138.139, -7.2796, 127.206, 98.8941, -8.5208, 151.822, 139.931, -8.5208, 129.887, 141.896, -9.16, 132.827, 100.247, -9.16, 155.089, 98.8941, -8.5208, 151.822, 139.931, -8.5208, 129.887, 100.247, -9.16, 155.089, 141.896, -9.16, 132.827, 143.919, -9.16, 135.856, 101.641, -9.16, 158.454, 100.247, -9.16, 155.089, 141.896, -9.16, 132.827, 101.641, -9.16, 158.454, 143.919, -9.16, 135.856, 145.884, -8.5208, 138.796, 102.994, -8.5208, 161.721, 101.641, -9.16, 158.454, 143.919, -9.16, 135.856, 102.994, -8.5208, 161.721, 145.884, -8.5208, 138.796, 147.675, -7.2796, 141.477, 104.229, -7.2796, 164.7, 102.994, -8.5208, 161.721, 145.884, -8.5208, 138.796, 104.229, -7.2796, 164.7, 147.675, -7.2796, 141.477, 149.19, -5.5084, 143.744, 105.272, -5.5084, 167.219, 104.229, -7.2796, 164.7, 147.675, -7.2796, 141.477, 105.272, -5.5084, 167.219, 149.19, -5.5084, 143.744, 150.339, -3.3103, 145.464, 106.064, -3.3103, 169.13, 105.272, -5.5084, 167.219, 149.19, -5.5084, 143.744, 106.064, -3.3103, 169.13, 150.339, -3.3103, 145.464, 151.057, -0.813, 146.538, 106.558, -0.813, 170.324, 106.064, -3.3103, 169.13, 150.339, -3.3103, 145.464, 106.558, -0.813, 170.324, 151.057, -0.813, 146.538, 151.301, 1.8384, 146.903, 106.726, 1.8384, 170.729, 106.558, -0.813, 170.324, 151.057, -0.813, 146.538, 106.726, 1.8384, 170.729, 106.726, 1.8384, 170.729, 106.558, 4.4899, 170.324, 58.2736, 4.4899, 184.97, 58.3592, 1.8384, 185.401, 106.726, 1.8384, 170.729, 58.2736, 4.4899, 184.97, 106.558, 4.4899, 170.324, 106.064, 6.9872, 169.13, 58.0216, 6.9872, 183.704, 58.2736, 4.4899, 184.97, 106.558, 4.4899, 170.324, 58.0216, 6.9872, 183.704, 106.064, 6.9872, 169.13, 105.272, 9.1853, 167.219, 57.618, 9.1853, 181.675, 58.0216, 6.9872, 183.704, 106.064, 6.9872, 169.13, 57.618, 9.1853, 181.675, 105.272, 9.1853, 167.219, 104.229, 10.9564, 164.7, 57.0861, 10.9564, 179.001, 57.618, 9.1853, 181.675, 105.272, 9.1853, 167.219, 57.0861, 10.9564, 179.001, 104.229, 10.9564, 164.7, 102.994, 12.1977, 161.721, 56.457, 12.1977, 175.838, 57.0861, 10.9564, 179.001, 104.229, 10.9564, 164.7, 56.457, 12.1977, 175.838, 102.994, 12.1977, 161.721, 101.641, 12.8369, 158.454, 55.7671, 12.8369, 172.369, 56.457, 12.1977, 175.838, 102.994, 12.1977, 161.721, 55.7671, 12.8369, 172.369, 101.641, 12.8369, 158.454, 100.247, 12.8369, 155.089, 55.0565, 12.8369, 168.797, 55.7671, 12.8369, 172.369, 101.641, 12.8369, 158.454, 55.0565, 12.8369, 168.797, 100.247, 12.8369, 155.089, 98.8941, 12.1977, 151.822, 54.3666, 12.1977, 165.329, 55.0565, 12.8369, 168.797, 100.247, 12.8369, 155.089, 54.3666, 12.1977, 165.329, 98.8941, 12.1977, 151.822, 97.66, 10.9564, 148.842, 53.7375, 10.9564, 162.166, 54.3666, 12.1977, 165.329, 98.8941, 12.1977, 151.822, 53.7375, 10.9564, 162.166, 97.66, 10.9564, 148.842, 96.6167, 9.1853, 146.324, 53.2056, 9.1853, 159.492, 53.7375, 10.9564, 162.166, 97.66, 10.9564, 148.842, 53.2056, 9.1853, 159.492, 96.6167, 9.1853, 146.324, 95.825, 6.9872, 144.412, 52.802, 6.9872, 157.463, 53.2056, 9.1853, 159.492, 96.6167, 9.1853, 146.324, 52.802, 6.9872, 157.463, 95.825, 6.9872, 144.412, 95.3307, 4.4899, 143.219, 52.55, 4.4899, 156.196, 52.802, 6.9872, 157.463, 95.825, 6.9872, 144.412, 52.55, 4.4899, 156.196, 95.3307, 4.4899, 143.219, 95.1627, 1.8384, 142.813, 52.4644, 1.8384, 155.766, 52.55, 4.4899, 156.196, 95.3307, 4.4899, 143.219, 52.4644, 1.8384, 155.766, 95.1627, 1.8384, 142.813, 95.3307, -0.813, 143.219, 52.55, -0.813, 156.196, 52.4644, 1.8384, 155.766, 95.1627, 1.8384, 142.813, 52.55, -0.813, 156.196, 95.3307, -0.813, 143.219, 95.825, -3.3103, 144.412, 52.802, -3.3103, 157.463, 52.55, -0.813, 156.196, 95.3307, -0.813, 143.219, 52.802, -3.3103, 157.463, 95.825, -3.3103, 144.412, 96.6167, -5.5084, 146.324, 53.2056, -5.5084, 159.492, 52.802, -3.3103, 157.463, 95.825, -3.3103, 144.412, 53.2056, -5.5084, 159.492, 96.6167, -5.5084, 146.324, 97.66, -7.2796, 148.842, 53.7375, -7.2796, 162.166, 53.2056, -5.5084, 159.492, 96.6167, -5.5084, 146.324, 53.7375, -7.2796, 162.166, 97.66, -7.2796, 148.842, 98.8941, -8.5208, 151.822, 54.3666, -8.5208, 165.329, 53.7375, -7.2796, 162.166, 97.66, -7.2796, 148.842, 54.3666, -8.5208, 165.329, 98.8941, -8.5208, 151.822, 100.247, -9.16, 155.089, 55.0565, -9.16, 168.797, 54.3666, -8.5208, 165.329, 98.8941, -8.5208, 151.822, 55.0565, -9.16, 168.797, 100.247, -9.16, 155.089, 101.641, -9.16, 158.454, 55.7671, -9.16, 172.369, 55.0565, -9.16, 168.797, 100.247, -9.16, 155.089, 55.7671, -9.16, 172.369, 101.641, -9.16, 158.454, 102.994, -8.5208, 161.721, 56.457, -8.5208, 175.838, 55.7671, -9.16, 172.369, 101.641, -9.16, 158.454, 56.457, -8.5208, 175.838, 102.994, -8.5208, 161.721, 104.229, -7.2796, 164.7, 57.0861, -7.2796, 179.001, 56.457, -8.5208, 175.838, 102.994, -8.5208, 161.721, 57.0861, -7.2796, 179.001, 104.229, -7.2796, 164.7, 105.272, -5.5084, 167.219, 57.618, -5.5084, 181.675, 57.0861, -7.2796, 179.001, 104.229, -7.2796, 164.7, 57.618, -5.5084, 181.675, 105.272, -5.5084, 167.219, 106.064, -3.3103, 169.13, 58.0216, -3.3103, 183.704, 57.618, -5.5084, 181.675, 105.272, -5.5084, 167.219, 58.0216, -3.3103, 183.704, 106.064, -3.3103, 169.13, 106.558, -0.813, 170.324, 58.2736, -0.813, 184.97, 58.0216, -3.3103, 183.704, 106.064, -3.3103, 169.13, 58.2736, -0.813, 184.97, 106.558, -0.813, 170.324, 106.726, 1.8384, 170.729, 58.3592, 1.8384, 185.401, 58.2736, -0.813, 184.97, 106.558, -0.813, 170.324, 58.3592, 1.8384, 185.401, 58.3592, 1.8384, 185.401, 58.2736, 4.4899, 184.97, 8.0596, 4.4899, 189.916, 8.0596, 1.8384, 190.355, 58.3592, 1.8384, 185.401, 8.0596, 4.4899, 189.916, 58.2736, 4.4899, 184.97, 58.0216, 6.9872, 183.704, 8.0596, 6.9872, 188.625, 8.0596, 4.4899, 189.916, 58.2736, 4.4899, 184.97, 8.0596, 6.9872, 188.625, 58.0216, 6.9872, 183.704, 57.618, 9.1853, 181.675, 8.0596, 9.1853, 186.556, 8.0596, 6.9872, 188.625, 58.0216, 6.9872, 183.704, 8.0596, 9.1853, 186.556, 57.618, 9.1853, 181.675, 57.0861, 10.9564, 179.001, 8.0596, 10.9564, 183.829, 8.0596, 9.1853, 186.556, 57.618, 9.1853, 181.675, 8.0596, 10.9564, 183.829, 57.0861, 10.9564, 179.001, 56.457, 12.1977, 175.838, 8.0596, 12.1977, 180.604, 8.0596, 10.9564, 183.829, 57.0861, 10.9564, 179.001, 8.0596, 12.1977, 180.604, 56.457, 12.1977, 175.838, 55.7671, 12.8369, 172.369, 8.0596, 12.8369, 177.068, 8.0596, 12.1977, 180.604, 56.457, 12.1977, 175.838, 8.0596, 12.8369, 177.068, 55.7671, 12.8369, 172.369, 55.0565, 12.8369, 168.797, 8.0596, 12.8369, 173.426, 8.0596, 12.8369, 177.068, 55.7671, 12.8369, 172.369, 8.0596, 12.8369, 173.426, 55.0565, 12.8369, 168.797, 54.3666, 12.1977, 165.329, 8.0596, 12.1977, 169.89, 8.0596, 12.8369, 173.426, 55.0565, 12.8369, 168.797, 8.0596, 12.1977, 169.89, 54.3666, 12.1977, 165.329, 53.7375, 10.9564, 162.166, 8.0596, 10.9564, 166.665, 8.0596, 12.1977, 169.89, 54.3666, 12.1977, 165.329, 8.0596, 10.9564, 166.665, 53.7375, 10.9564, 162.166, 53.2056, 9.1853, 159.492, 8.0596, 9.1853, 163.939, 8.0596, 10.9564, 166.665, 53.7375, 10.9564, 162.166, 8.0596, 9.1853, 163.939, 53.2056, 9.1853, 159.492, 52.802, 6.9872, 157.463, 8.0596, 6.9872, 161.87, 8.0596, 9.1853, 163.939, 53.2056, 9.1853, 159.492, 8.0596, 6.9872, 161.87, 52.802, 6.9872, 157.463, 52.55, 4.4899, 156.196, 8.0596, 4.4899, 160.578, 8.0596, 6.9872, 161.87, 52.802, 6.9872, 157.463, 8.0596, 4.4899, 160.578, 52.55, 4.4899, 156.196, 52.4644, 1.8384, 155.766, 8.0596, 1.8384, 160.139, 8.0596, 4.4899, 160.578, 52.55, 4.4899, 156.196, 8.0596, 1.8384, 160.139, 52.4644, 1.8384, 155.766, 52.55, -0.813, 156.196, 8.0596, -0.813, 160.578, 8.0596, 1.8384, 160.139, 52.4644, 1.8384, 155.766, 8.0596, -0.813, 160.578, 52.55, -0.813, 156.196, 52.802, -3.3103, 157.463, 8.0596, -3.3103, 161.87, 8.0596, -0.813, 160.578, 52.55, -0.813, 156.196, 8.0596, -3.3103, 161.87, 52.802, -3.3103, 157.463, 53.2056, -5.5084, 159.492, 8.0596, -5.5084, 163.939, 8.0596, -3.3103, 161.87, 52.802, -3.3103, 157.463, 8.0596, -5.5084, 163.939, 53.2056, -5.5084, 159.492, 53.7375, -7.2796, 162.166, 8.0596, -7.2796, 166.665, 8.0596, -5.5084, 163.939, 53.2056, -5.5084, 159.492, 8.0596, -7.2796, 166.665, 53.7375, -7.2796, 162.166, 54.3666, -8.5208, 165.329, 8.0596, -8.5208, 169.89, 8.0596, -7.2796, 166.665, 53.7375, -7.2796, 162.166, 8.0596, -8.5208, 169.89, 54.3666, -8.5208, 165.329, 55.0565, -9.16, 168.797, 8.0596, -9.16, 173.426, 8.0596, -8.5208, 169.89, 54.3666, -8.5208, 165.329, 8.0596, -9.16, 173.426, 55.0565, -9.16, 168.797, 55.7671, -9.16, 172.369, 8.0596, -9.16, 177.068, 8.0596, -9.16, 173.426, 55.0565, -9.16, 168.797, 8.0596, -9.16, 177.068, 55.7671, -9.16, 172.369, 56.457, -8.5208, 175.838, 8.0596, -8.5208, 180.604, 8.0596, -9.16, 177.068, 55.7671, -9.16, 172.369, 8.0596, -8.5208, 180.604, 56.457, -8.5208, 175.838, 57.0861, -7.2796, 179.001, 8.0596, -7.2796, 183.829, 8.0596, -8.5208, 180.604, 56.457, -8.5208, 175.838, 8.0596, -7.2796, 183.829, 57.0861, -7.2796, 179.001, 57.618, -5.5084, 181.675, 8.0596, -5.5084, 186.556, 8.0596, -7.2796, 183.829, 57.0861, -7.2796, 179.001, 8.0596, -5.5084, 186.556, 57.618, -5.5084, 181.675, 58.0216, -3.3103, 183.704, 8.0596, -3.3103, 188.625, 8.0596, -5.5084, 186.556, 57.618, -5.5084, 181.675, 8.0596, -3.3103, 188.625, 58.0216, -3.3103, 183.704, 58.2736, -0.813, 184.97, 8.0596, -0.813, 189.916, 8.0596, -3.3103, 188.625, 58.0216, -3.3103, 183.704, 8.0596, -0.813, 189.916, 58.2736, -0.813, 184.97, 58.3592, 1.8384, 185.401, 8.0596, 1.8384, 190.355, 8.0596, -0.813, 189.916, 58.2736, -0.813, 184.97, 8.0596, 1.8384, 190.355, 8.0596, 1.8384, 190.355, 8.0596, 4.4899, 189.916, -42.1544, 4.4899, 184.97, -42.2401, 1.8384, 185.401, 8.0596, 1.8384, 190.355, -42.1544, 4.4899, 184.97, 8.0596, 4.4899, 189.916, 8.0596, 6.9872, 188.625, -41.9025, 6.9872, 183.704, -42.1544, 4.4899, 184.97, 8.0596, 4.4899, 189.916, -41.9025, 6.9872, 183.704, 8.0596, 6.9872, 188.625, 8.0596, 9.1853, 186.556, -41.4988, 9.1853, 181.675, -41.9025, 6.9872, 183.704, 8.0596, 6.9872, 188.625, -41.4988, 9.1853, 181.675, 8.0596, 9.1853, 186.556, 8.0596, 10.9564, 183.829, -40.967, 10.9564, 179.001, -41.4988, 9.1853, 181.675, 8.0596, 9.1853, 186.556, -40.967, 10.9564, 179.001, 8.0596, 10.9564, 183.829, 8.0596, 12.1977, 180.604, -40.3378, 12.1977, 175.838, -40.967, 10.9564, 179.001, 8.0596, 10.9564, 183.829, -40.3378, 12.1977, 175.838, 8.0596, 12.1977, 180.604, 8.0596, 12.8369, 177.068, -39.6479, 12.8369, 172.369, -40.3378, 12.1977, 175.838, 8.0596, 12.1977, 180.604, -39.6479, 12.8369, 172.369, 8.0596, 12.8369, 177.068, 8.0596, 12.8369, 173.426, -38.9374, 12.8369, 168.797, -39.6479, 12.8369, 172.369, 8.0596, 12.8369, 177.068, -38.9374, 12.8369, 168.797, 8.0596, 12.8369, 173.426, 8.0596, 12.1977, 169.89, -38.2475, 12.1977, 165.329, -38.9374, 12.8369, 168.797, 8.0596, 12.8369, 173.426, -38.2475, 12.1977, 165.329, 8.0596, 12.1977, 169.89, 8.0596, 10.9564, 166.665, -37.6183, 10.9564, 162.166, -38.2475, 12.1977, 165.329, 8.0596, 12.1977, 169.89, -37.6183, 10.9564, 162.166, 8.0596, 10.9564, 166.665, 8.0596, 9.1853, 163.939, -37.0865, 9.1853, 159.492, -37.6183, 10.9564, 162.166, 8.0596, 10.9564, 166.665, -37.0865, 9.1853, 159.492, 8.0596, 9.1853, 163.939, 8.0596, 6.9872, 161.87, -36.6828, 6.9872, 157.463, -37.0865, 9.1853, 159.492, 8.0596, 9.1853, 163.939, -36.6828, 6.9872, 157.463, 8.0596, 6.9872, 161.87, 8.0596, 4.4899, 160.578, -36.4309, 4.4899, 156.196, -36.6828, 6.9872, 157.463, 8.0596, 6.9872, 161.87, -36.4309, 4.4899, 156.196, 8.0596, 4.4899, 160.578, 8.0596, 1.8384, 160.139, -36.3452, 1.8384, 155.766, -36.4309, 4.4899, 156.196, 8.0596, 4.4899, 160.578, -36.3452, 1.8384, 155.766, 8.0596, 1.8384, 160.139, 8.0596, -0.813, 160.578, -36.4309, -0.813, 156.196, -36.3452, 1.8384, 155.766, 8.0596, 1.8384, 160.139, -36.4309, -0.813, 156.196, 8.0596, -0.813, 160.578, 8.0596, -3.3103, 161.87, -36.6828, -3.3103, 157.463, -36.4309, -0.813, 156.196, 8.0596, -0.813, 160.578, -36.6828, -3.3103, 157.463, 8.0596, -3.3103, 161.87, 8.0596, -5.5084, 163.939, -37.0865, -5.5084, 159.492, -36.6828, -3.3103, 157.463, 8.0596, -3.3103, 161.87, -37.0865, -5.5084, 159.492, 8.0596, -5.5084, 163.939, 8.0596, -7.2796, 166.665, -37.6183, -7.2796, 162.166, -37.0865, -5.5084, 159.492, 8.0596, -5.5084, 163.939, -37.6183, -7.2796, 162.166, 8.0596, -7.2796, 166.665, 8.0596, -8.5208, 169.89, -38.2475, -8.5208, 165.329, -37.6183, -7.2796, 162.166, 8.0596, -7.2796, 166.665, -38.2475, -8.5208, 165.329, 8.0596, -8.5208, 169.89, 8.0596, -9.16, 173.426, -38.9374, -9.16, 168.797, -38.2475, -8.5208, 165.329, 8.0596, -8.5208, 169.89, -38.9374, -9.16, 168.797, 8.0596, -9.16, 173.426, 8.0596, -9.16, 177.068, -39.6479, -9.16, 172.369, -38.9374, -9.16, 168.797, 8.0596, -9.16, 173.426, -39.6479, -9.16, 172.369, 8.0596, -9.16, 177.068, 8.0596, -8.5208, 180.604, -40.3378, -8.5208, 175.838, -39.6479, -9.16, 172.369, 8.0596, -9.16, 177.068, -40.3378, -8.5208, 175.838, 8.0596, -8.5208, 180.604, 8.0596, -7.2796, 183.829, -40.967, -7.2796, 179.001, -40.3378, -8.5208, 175.838, 8.0596, -8.5208, 180.604, -40.967, -7.2796, 179.001, 8.0596, -7.2796, 183.829, 8.0596, -5.5084, 186.556, -41.4988, -5.5084, 181.675, -40.967, -7.2796, 179.001, 8.0596, -7.2796, 183.829, -41.4988, -5.5084, 181.675, 8.0596, -5.5084, 186.556, 8.0596, -3.3103, 188.625, -41.9025, -3.3103, 183.704, -41.4988, -5.5084, 181.675, 8.0596, -5.5084, 186.556, -41.9025, -3.3103, 183.704, 8.0596, -3.3103, 188.625, 8.0596, -0.813, 189.916, -42.1544, -0.813, 184.97, -41.9025, -3.3103, 183.704, 8.0596, -3.3103, 188.625, -42.1544, -0.813, 184.97, 8.0596, -0.813, 189.916, 8.0596, 1.8384, 190.355, -42.2401, 1.8384, 185.401, -42.1544, -0.813, 184.97, 8.0596, -0.813, 189.916, -42.2401, 1.8384, 185.401, -42.2401, 1.8384, 185.401, -42.1544, 4.4899, 184.97, -90.4387, 4.4899, 170.324, -90.6067, 1.8384, 170.729, -42.2401, 1.8384, 185.401, -90.4387, 4.4899, 170.324, -42.1544, 4.4899, 184.97, -41.9025, 6.9872, 183.704, -89.9445, 6.9872, 169.13, -90.4387, 4.4899, 170.324, -42.1544, 4.4899, 184.97, -89.9445, 6.9872, 169.13, -41.9025, 6.9872, 183.704, -41.4988, 9.1853, 181.675, -89.1527, 9.1853, 167.219, -89.9445, 6.9872, 169.13, -41.9025, 6.9872, 183.704, -89.1527, 9.1853, 167.219, -41.4988, 9.1853, 181.675, -40.967, 10.9564, 179.001, -88.1095, 10.9564, 164.7, -89.1527, 9.1853, 167.219, -41.4988, 9.1853, 181.675, -88.1095, 10.9564, 164.7, -40.967, 10.9564, 179.001, -40.3378, 12.1977, 175.838, -86.8753, 12.1977, 161.721, -88.1095, 10.9564, 164.7, -40.967, 10.9564, 179.001, -86.8753, 12.1977, 161.721, -40.3378, 12.1977, 175.838, -39.6479, 12.8369, 172.369, -85.522, 12.8369, 158.454, -86.8753, 12.1977, 161.721, -40.3378, 12.1977, 175.838, -85.522, 12.8369, 158.454, -39.6479, 12.8369, 172.369, -38.9374, 12.8369, 168.797, -84.1283, 12.8369, 155.089, -85.522, 12.8369, 158.454, -39.6479, 12.8369, 172.369, -84.1283, 12.8369, 155.089, -38.9374, 12.8369, 168.797, -38.2475, 12.1977, 165.329, -82.775, 12.1977, 151.822, -84.1283, 12.8369, 155.089, -38.9374, 12.8369, 168.797, -82.775, 12.1977, 151.822, -38.2475, 12.1977, 165.329, -37.6183, 10.9564, 162.166, -81.5408, 10.9564, 148.842, -82.775, 12.1977, 151.822, -38.2475, 12.1977, 165.329, -81.5408, 10.9564, 148.842, -37.6183, 10.9564, 162.166, -37.0865, 9.1853, 159.492, -80.4976, 9.1853, 146.324, -81.5408, 10.9564, 148.842, -37.6183, 10.9564, 162.166, -80.4976, 9.1853, 146.324, -37.0865, 9.1853, 159.492, -36.6828, 6.9872, 157.463, -79.7058, 6.9872, 144.412, -80.4976, 9.1853, 146.324, -37.0865, 9.1853, 159.492, -79.7058, 6.9872, 144.412, -36.6828, 6.9872, 157.463, -36.4309, 4.4899, 156.196, -79.2116, 4.4899, 143.219, -79.7058, 6.9872, 144.412, -36.6828, 6.9872, 157.463, -79.2116, 4.4899, 143.219, -36.4309, 4.4899, 156.196, -36.3452, 1.8384, 155.766, -79.0436, 1.8384, 142.813, -79.2116, 4.4899, 143.219, -36.4309, 4.4899, 156.196, -79.0436, 1.8384, 142.813, -36.3452, 1.8384, 155.766, -36.4309, -0.813, 156.196, -79.2116, -0.813, 143.219, -79.0436, 1.8384, 142.813, -36.3452, 1.8384, 155.766, -79.2116, -0.813, 143.219, -36.4309, -0.813, 156.196, -36.6828, -3.3103, 157.463, -79.7058, -3.3103, 144.412, -79.2116, -0.813, 143.219, -36.4309, -0.813, 156.196, -79.7058, -3.3103, 144.412, -36.6828, -3.3103, 157.463, -37.0865, -5.5084, 159.492, -80.4976, -5.5084, 146.324, -79.7058, -3.3103, 144.412, -36.6828, -3.3103, 157.463, -80.4976, -5.5084, 146.324, -37.0865, -5.5084, 159.492, -37.6183, -7.2796, 162.166, -81.5408, -7.2796, 148.842, -80.4976, -5.5084, 146.324, -37.0865, -5.5084, 159.492, -81.5408, -7.2796, 148.842, -37.6183, -7.2796, 162.166, -38.2475, -8.5208, 165.329, -82.775, -8.5208, 151.822, -81.5408, -7.2796, 148.842, -37.6183, -7.2796, 162.166, -82.775, -8.5208, 151.822, -38.2475, -8.5208, 165.329, -38.9374, -9.16, 168.797, -84.1283, -9.16, 155.089, -82.775, -8.5208, 151.822, -38.2475, -8.5208, 165.329, -84.1283, -9.16, 155.089, -38.9374, -9.16, 168.797, -39.6479, -9.16, 172.369, -85.522, -9.16, 158.454, -84.1283, -9.16, 155.089, -38.9374, -9.16, 168.797, -85.522, -9.16, 158.454, -39.6479, -9.16, 172.369, -40.3378, -8.5208, 175.838, -86.8753, -8.5208, 161.721, -85.522, -9.16, 158.454, -39.6479, -9.16, 172.369, -86.8753, -8.5208, 161.721, -40.3378, -8.5208, 175.838, -40.967, -7.2796, 179.001, -88.1095, -7.2796, 164.7, -86.8753, -8.5208, 161.721, -40.3378, -8.5208, 175.838, -88.1095, -7.2796, 164.7, -40.967, -7.2796, 179.001, -41.4988, -5.5084, 181.675, -89.1527, -5.5084, 167.219, -88.1095, -7.2796, 164.7, -40.967, -7.2796, 179.001, -89.1527, -5.5084, 167.219, -41.4988, -5.5084, 181.675, -41.9025, -3.3103, 183.704, -89.9445, -3.3103, 169.13, -89.1527, -5.5084, 167.219, -41.4988, -5.5084, 181.675, -89.9445, -3.3103, 169.13, -41.9025, -3.3103, 183.704, -42.1544, -0.813, 184.97, -90.4387, -0.813, 170.324, -89.9445, -3.3103, 169.13, -41.9025, -3.3103, 183.704, -90.4387, -0.813, 170.324, -42.1544, -0.813, 184.97, -42.2401, 1.8384, 185.401, -90.6067, 1.8384, 170.729, -90.4387, -0.813, 170.324, -42.1544, -0.813, 184.97, -90.6067, 1.8384, 170.729, -90.6067, 1.8384, 170.729, -90.4387, 4.4899, 170.324, -134.938, 4.4899, 146.538, -135.182, 1.8384, 146.903, -90.6067, 1.8384, 170.729, -134.938, 4.4899, 146.538, -90.4387, 4.4899, 170.324, -89.9445, 6.9872, 169.13, -134.22, 6.9872, 145.464, -134.938, 4.4899, 146.538, -90.4387, 4.4899, 170.324, -134.22, 6.9872, 145.464, -89.9445, 6.9872, 169.13, -89.1527, 9.1853, 167.219, -133.071, 9.1853, 143.744, -134.22, 6.9872, 145.464, -89.9445, 6.9872, 169.13, -133.071, 9.1853, 143.744, -89.1527, 9.1853, 167.219, -88.1095, 10.9564, 164.7, -131.556, 10.9564, 141.477, -133.071, 9.1853, 143.744, -89.1527, 9.1853, 167.219, -131.556, 10.9564, 141.477, -88.1095, 10.9564, 164.7, -86.8753, 12.1977, 161.721, -129.764, 12.1977, 138.796, -131.556, 10.9564, 141.477, -88.1095, 10.9564, 164.7, -129.764, 12.1977, 138.796, -86.8753, 12.1977, 161.721, -85.522, 12.8369, 158.454, -127.8, 12.8369, 135.856, -129.764, 12.1977, 138.796, -86.8753, 12.1977, 161.721, -127.8, 12.8369, 135.856, -85.522, 12.8369, 158.454, -84.1283, 12.8369, 155.089, -125.776, 12.8369, 132.827, -127.8, 12.8369, 135.856, -85.522, 12.8369, 158.454, -125.776, 12.8369, 132.827, -84.1283, 12.8369, 155.089, -82.775, 12.1977, 151.822, -123.812, 12.1977, 129.887, -125.776, 12.8369, 132.827, -84.1283, 12.8369, 155.089, -123.812, 12.1977, 129.887, -82.775, 12.1977, 151.822, -81.5408, 10.9564, 148.842, -122.02, 10.9564, 127.206, -123.812, 12.1977, 129.887, -82.775, 12.1977, 151.822, -122.02, 10.9564, 127.206, -81.5408, 10.9564, 148.842, -80.4976, 9.1853, 146.324, -120.505, 9.1853, 124.939, -122.02, 10.9564, 127.206, -81.5408, 10.9564, 148.842, -120.505, 9.1853, 124.939, -80.4976, 9.1853, 146.324, -79.7058, 6.9872, 144.412, -119.356, 6.9872, 123.219, -120.505, 9.1853, 124.939, -80.4976, 9.1853, 146.324, -119.356, 6.9872, 123.219, -79.7058, 6.9872, 144.412, -79.2116, 4.4899, 143.219, -118.638, 4.4899, 122.145, -119.356, 6.9872, 123.219, -79.7058, 6.9872, 144.412, -118.638, 4.4899, 122.145, -79.2116, 4.4899, 143.219, -79.0436, 1.8384, 142.813, -118.395, 1.8384, 121.78, -118.638, 4.4899, 122.145, -79.2116, 4.4899, 143.219, -118.395, 1.8384, 121.78, -79.0436, 1.8384, 142.813, -79.2116, -0.813, 143.219, -118.638, -0.813, 122.145, -118.395, 1.8384, 121.78, -79.0436, 1.8384, 142.813, -118.638, -0.813, 122.145, -79.2116, -0.813, 143.219, -79.7058, -3.3103, 144.412, -119.356, -3.3103, 123.219, -118.638, -0.813, 122.145, -79.2116, -0.813, 143.219, -119.356, -3.3103, 123.219, -79.7058, -3.3103, 144.412, -80.4976, -5.5084, 146.324, -120.505, -5.5084, 124.939, -119.356, -3.3103, 123.219, -79.7058, -3.3103, 144.412, -120.505, -5.5084, 124.939, -80.4976, -5.5084, 146.324, -81.5408, -7.2796, 148.842, -122.02, -7.2796, 127.206, -120.505, -5.5084, 124.939, -80.4976, -5.5084, 146.324, -122.02, -7.2796, 127.206, -81.5408, -7.2796, 148.842, -82.775, -8.5208, 151.822, -123.812, -8.5208, 129.887, -122.02, -7.2796, 127.206, -81.5408, -7.2796, 148.842, -123.812, -8.5208, 129.887, -82.775, -8.5208, 151.822, -84.1283, -9.16, 155.089, -125.776, -9.16, 132.827, -123.812, -8.5208, 129.887, -82.775, -8.5208, 151.822, -125.776, -9.16, 132.827, -84.1283, -9.16, 155.089, -85.522, -9.16, 158.454, -127.8, -9.16, 135.856, -125.776, -9.16, 132.827, -84.1283, -9.16, 155.089, -127.8, -9.16, 135.856, -85.522, -9.16, 158.454, -86.8753, -8.5208, 161.721, -129.764, -8.5208, 138.796, -127.8, -9.16, 135.856, -85.522, -9.16, 158.454, -129.764, -8.5208, 138.796, -86.8753, -8.5208, 161.721, -88.1095, -7.2796, 164.7, -131.556, -7.2796, 141.477, -129.764, -8.5208, 138.796, -86.8753, -8.5208, 161.721, -131.556, -7.2796, 141.477, -88.1095, -7.2796, 164.7, -89.1527, -5.5084, 167.219, -133.071, -5.5084, 143.744, -131.556, -7.2796, 141.477, -88.1095, -7.2796, 164.7, -133.071, -5.5084, 143.744, -89.1527, -5.5084, 167.219, -89.9445, -3.3103, 169.13, -134.22, -3.3103, 145.464, -133.071, -5.5084, 143.744, -89.1527, -5.5084, 167.219, -134.22, -3.3103, 145.464, -89.9445, -3.3103, 169.13, -90.4387, -0.813, 170.324, -134.938, -0.813, 146.538, -134.22, -3.3103, 145.464, -89.9445, -3.3103, 169.13, -134.938, -0.813, 146.538, -90.4387, -0.813, 170.324, -90.6067, 1.8384, 170.729, -135.182, 1.8384, 146.903, -134.938, -0.813, 146.538, -90.4387, -0.813, 170.324, -135.182, 1.8384, 146.903, -135.182, 1.8384, 146.903, -134.938, 4.4899, 146.538, -173.942, 4.4899, 114.529, -174.252, 1.8384, 114.839, -135.182, 1.8384, 146.903, -173.942, 4.4899, 114.529, -134.938, 4.4899, 146.538, -134.22, 6.9872, 145.464, -173.028, 6.9872, 113.616, -173.942, 4.4899, 114.529, -134.938, 4.4899, 146.538, -173.028, 6.9872, 113.616, -134.22, 6.9872, 145.464, -133.071, 9.1853, 143.744, -171.565, 9.1853, 112.153, -173.028, 6.9872, 113.616, -134.22, 6.9872, 145.464, -171.565, 9.1853, 112.153, -133.071, 9.1853, 143.744, -131.556, 10.9564, 141.477, -169.638, 10.9564, 110.225, -171.565, 9.1853, 112.153, -133.071, 9.1853, 143.744, -169.638, 10.9564, 110.225, -131.556, 10.9564, 141.477, -129.764, 12.1977, 138.796, -167.357, 12.1977, 107.944, -169.638, 10.9564, 110.225, -131.556, 10.9564, 141.477, -167.357, 12.1977, 107.944, -129.764, 12.1977, 138.796, -127.8, 12.8369, 135.856, -164.857, 12.8369, 105.444, -167.357, 12.1977, 107.944, -129.764, 12.1977, 138.796, -164.857, 12.8369, 105.444, -127.8, 12.8369, 135.856, -125.776, 12.8369, 132.827, -162.281, 12.8369, 102.869, -164.857, 12.8369, 105.444, -127.8, 12.8369, 135.856, -162.281, 12.8369, 102.869, -125.776, 12.8369, 132.827, -123.812, 12.1977, 129.887, -159.781, 12.1977, 100.368, -162.281, 12.8369, 102.869, -125.776, 12.8369, 132.827, -159.781, 12.1977, 100.368, -123.812, 12.1977, 129.887, -122.02, 10.9564, 127.206, -157.5, 10.9564, 98.0877, -159.781, 12.1977, 100.368, -123.812, 12.1977, 129.887, -157.5, 10.9564, 98.0877, -122.02, 10.9564, 127.206, -120.505, 9.1853, 124.939, -155.573, 9.1853, 96.1599, -157.5, 10.9564, 98.0877, -122.02, 10.9564, 127.206, -155.573, 9.1853, 96.1599, -120.505, 9.1853, 124.939, -119.356, 6.9872, 123.219, -154.11, 6.9872, 94.697, -155.573, 9.1853, 96.1599, -120.505, 9.1853, 124.939, -154.11, 6.9872, 94.697, -119.356, 6.9872, 123.219, -118.638, 4.4899, 122.145, -153.197, 4.4899, 93.7837, -154.11, 6.9872, 94.697, -119.356, 6.9872, 123.219, -153.197, 4.4899, 93.7837, -118.638, 4.4899, 122.145, -118.395, 1.8384, 121.78, -152.886, 1.8384, 93.4733, -153.197, 4.4899, 93.7837, -118.638, 4.4899, 122.145, -152.886, 1.8384, 93.4733, -118.395, 1.8384, 121.78, -118.638, -0.813, 122.145, -153.197, -0.813, 93.7837, -152.886, 1.8384, 93.4733, -118.395, 1.8384, 121.78, -153.197, -0.813, 93.7837, -118.638, -0.813, 122.145, -119.356, -3.3103, 123.219, -154.11, -3.3103, 94.697, -153.197, -0.813, 93.7837, -118.638, -0.813, 122.145, -154.11, -3.3103, 94.697, -119.356, -3.3103, 123.219, -120.505, -5.5084, 124.939, -155.573, -5.5084, 96.1599, -154.11, -3.3103, 94.697, -119.356, -3.3103, 123.219, -155.573, -5.5084, 96.1599, -120.505, -5.5084, 124.939, -122.02, -7.2796, 127.206, -157.5, -7.2796, 98.0877, -155.573, -5.5084, 96.1599, -120.505, -5.5084, 124.939, -157.5, -7.2796, 98.0877, -122.02, -7.2796, 127.206, -123.812, -8.5208, 129.887, -159.781, -8.5208, 100.368, -157.5, -7.2796, 98.0877, -122.02, -7.2796, 127.206, -159.781, -8.5208, 100.368, -123.812, -8.5208, 129.887, -125.776, -9.16, 132.827, -162.281, -9.16, 102.869, -159.781, -8.5208, 100.368, -123.812, -8.5208, 129.887, -162.281, -9.16, 102.869, -125.776, -9.16, 132.827, -127.8, -9.16, 135.856, -164.857, -9.16, 105.444, -162.281, -9.16, 102.869, -125.776, -9.16, 132.827, -164.857, -9.16, 105.444, -127.8, -9.16, 135.856, -129.764, -8.5208, 138.796, -167.357, -8.5208, 107.944, -164.857, -9.16, 105.444, -127.8, -9.16, 135.856, -167.357, -8.5208, 107.944, -129.764, -8.5208, 138.796, -131.556, -7.2796, 141.477, -169.638, -7.2796, 110.225, -167.357, -8.5208, 107.944, -129.764, -8.5208, 138.796, -169.638, -7.2796, 110.225, -131.556, -7.2796, 141.477, -133.071, -5.5084, 143.744, -171.565, -5.5084, 112.153, -169.638, -7.2796, 110.225, -131.556, -7.2796, 141.477, -171.565, -5.5084, 112.153, -133.071, -5.5084, 143.744, -134.22, -3.3103, 145.464, -173.028, -3.3103, 113.616, -171.565, -5.5084, 112.153, -133.071, -5.5084, 143.744, -173.028, -3.3103, 113.616, -134.22, -3.3103, 145.464, -134.938, -0.813, 146.538, -173.942, -0.813, 114.529, -173.028, -3.3103, 113.616, -134.22, -3.3103, 145.464, -173.942, -0.813, 114.529, -134.938, -0.813, 146.538, -135.182, 1.8384, 146.903, -174.252, 1.8384, 114.839, -173.942, -0.813, 114.529, -134.938, -0.813, 146.538, -174.252, 1.8384, 114.839, -174.252, 1.8384, 114.839, -173.942, 4.4899, 114.529, -205.951, 4.4899, 75.525, -206.316, 1.8384, 75.7689, -174.252, 1.8384, 114.839, -205.951, 4.4899, 75.525, -173.942, 4.4899, 114.529, -173.028, 6.9872, 113.616, -204.877, 6.9872, 74.8075, -205.951, 4.4899, 75.525, -173.942, 4.4899, 114.529, -204.877, 6.9872, 74.8075, -173.028, 6.9872, 113.616, -171.565, 9.1853, 112.153, -203.157, 9.1853, 73.6581, -204.877, 6.9872, 74.8075, -173.028, 6.9872, 113.616, -203.157, 9.1853, 73.6581, -171.565, 9.1853, 112.153, -169.638, 10.9564, 110.225, -200.89, 10.9564, 72.1435, -203.157, 9.1853, 73.6581, -171.565, 9.1853, 112.153, -200.89, 10.9564, 72.1435, -169.638, 10.9564, 110.225, -167.357, 12.1977, 107.944, -198.209, 12.1977, 70.3518, -200.89, 10.9564, 72.1435, -169.638, 10.9564, 110.225, -198.209, 12.1977, 70.3518, -167.357, 12.1977, 107.944, -164.857, 12.8369, 105.444, -195.268, 12.8369, 68.3871, -198.209, 12.1977, 70.3518, -167.357, 12.1977, 107.944, -195.268, 12.8369, 68.3871, -164.857, 12.8369, 105.444, -162.281, 12.8369, 102.869, -192.24, 12.8369, 66.3636, -195.268, 12.8369, 68.3871, -164.857, 12.8369, 105.444, -192.24, 12.8369, 66.3636, -162.281, 12.8369, 102.869, -159.781, 12.1977, 100.368, -189.3, 12.1977, 64.399, -192.24, 12.8369, 66.3636, -162.281, 12.8369, 102.869, -189.3, 12.1977, 64.399, -159.781, 12.1977, 100.368, -157.5, 10.9564, 98.0877, -186.618, 10.9564, 62.6073, -189.3, 12.1977, 64.399, -159.781, 12.1977, 100.368, -186.618, 10.9564, 62.6073, -157.5, 10.9564, 98.0877, -155.573, 9.1853, 96.1599, -184.352, 9.1853, 61.0927, -186.618, 10.9564, 62.6073, -157.5, 10.9564, 98.0877, -184.352, 9.1853, 61.0927, -155.573, 9.1853, 96.1599, -154.11, 6.9872, 94.697, -182.631, 6.9872, 59.9433, -184.352, 9.1853, 61.0927, -155.573, 9.1853, 96.1599, -182.631, 6.9872, 59.9433, -154.11, 6.9872, 94.697, -153.197, 4.4899, 93.7837, -181.557, 4.4899, 59.2257, -182.631, 6.9872, 59.9433, -154.11, 6.9872, 94.697, -181.557, 4.4899, 59.2257, -153.197, 4.4899, 93.7837, -152.886, 1.8384, 93.4733, -181.192, 1.8384, 58.9818, -181.557, 4.4899, 59.2257, -153.197, 4.4899, 93.7837, -181.192, 1.8384, 58.9818, -152.886, 1.8384, 93.4733, -153.197, -0.813, 93.7837, -181.557, -0.813, 59.2257, -181.192, 1.8384, 58.9818, -152.886, 1.8384, 93.4733, -181.557, -0.813, 59.2257, -153.197, -0.813, 93.7837, -154.11, -3.3103, 94.697, -182.631, -3.3103, 59.9433, -181.557, -0.813, 59.2257, -153.197, -0.813, 93.7837, -182.631, -3.3103, 59.9433, -154.11, -3.3103, 94.697, -155.573, -5.5084, 96.1599, -184.352, -5.5084, 61.0927, -182.631, -3.3103, 59.9433, -154.11, -3.3103, 94.697, -184.352, -5.5084, 61.0927, -155.573, -5.5084, 96.1599, -157.5, -7.2796, 98.0877, -186.618, -7.2796, 62.6073, -184.352, -5.5084, 61.0927, -155.573, -5.5084, 96.1599, -186.618, -7.2796, 62.6073, -157.5, -7.2796, 98.0877, -159.781, -8.5208, 100.368, -189.3, -8.5208, 64.399, -186.618, -7.2796, 62.6073, -157.5, -7.2796, 98.0877, -189.3, -8.5208, 64.399, -159.781, -8.5208, 100.368, -162.281, -9.16, 102.869, -192.24, -9.16, 66.3636, -189.3, -8.5208, 64.399, -159.781, -8.5208, 100.368, -192.24, -9.16, 66.3636, -162.281, -9.16, 102.869, -164.857, -9.16, 105.444, -195.268, -9.16, 68.3871, -192.24, -9.16, 66.3636, -162.281, -9.16, 102.869, -195.268, -9.16, 68.3871, -164.857, -9.16, 105.444, -167.357, -8.5208, 107.944, -198.209, -8.5208, 70.3518, -195.268, -9.16, 68.3871, -164.857, -9.16, 105.444, -198.209, -8.5208, 70.3518, -167.357, -8.5208, 107.944, -169.638, -7.2796, 110.225, -200.89, -7.2796, 72.1435, -198.209, -8.5208, 70.3518, -167.357, -8.5208, 107.944, -200.89, -7.2796, 72.1435, -169.638, -7.2796, 110.225, -171.565, -5.5084, 112.153, -203.157, -5.5084, 73.6581, -200.89, -7.2796, 72.1435, -169.638, -7.2796, 110.225, -203.157, -5.5084, 73.6581, -171.565, -5.5084, 112.153, -173.028, -3.3103, 113.616, -204.877, -3.3103, 74.8075, -203.157, -5.5084, 73.6581, -171.565, -5.5084, 112.153, -204.877, -3.3103, 74.8075, -173.028, -3.3103, 113.616, -173.942, -0.813, 114.529, -205.951, -0.813, 75.525, -204.877, -3.3103, 74.8075, -173.028, -3.3103, 113.616, -205.951, -0.813, 75.525, -173.942, -0.813, 114.529, -174.252, 1.8384, 114.839, -206.316, 1.8384, 75.7689, -205.951, -0.813, 75.525, -173.942, -0.813, 114.529, -206.316, 1.8384, 75.7689, -206.316, 1.8384, 75.7689, -205.951, 4.4899, 75.525, -229.736, 4.4899, 31.026, -230.142, 1.8384, 31.194, -206.316, 1.8384, 75.7689, -229.736, 4.4899, 31.026, -205.951, 4.4899, 75.525, -204.877, 6.9872, 74.8075, -228.543, 6.9872, 30.5317, -229.736, 4.4899, 31.026, -205.951, 4.4899, 75.525, -228.543, 6.9872, 30.5317, -204.877, 6.9872, 74.8075, -203.157, 9.1853, 73.6581, -226.632, 9.1853, 29.74, -228.543, 6.9872, 30.5317, -204.877, 6.9872, 74.8075, -226.632, 9.1853, 29.74, -203.157, 9.1853, 73.6581, -200.89, 10.9564, 72.1435, -224.113, 10.9564, 28.6967, -226.632, 9.1853, 29.74, -203.157, 9.1853, 73.6581, -224.113, 10.9564, 28.6967, -200.89, 10.9564, 72.1435, -198.209, 12.1977, 70.3518, -221.134, 12.1977, 27.4626, -224.113, 10.9564, 28.6967, -200.89, 10.9564, 72.1435, -221.134, 12.1977, 27.4626, -198.209, 12.1977, 70.3518, -195.268, 12.8369, 68.3871, -217.866, 12.8369, 26.1093, -221.134, 12.1977, 27.4626, -198.209, 12.1977, 70.3518, -217.866, 12.8369, 26.1093, -195.268, 12.8369, 68.3871, -192.24, 12.8369, 66.3636, -214.501, 12.8369, 24.7155, -217.866, 12.8369, 26.1093, -195.268, 12.8369, 68.3871, -214.501, 12.8369, 24.7155, -192.24, 12.8369, 66.3636, -189.3, 12.1977, 64.399, -211.234, 12.1977, 23.3622, -214.501, 12.8369, 24.7155, -192.24, 12.8369, 66.3636, -211.234, 12.1977, 23.3622, -189.3, 12.1977, 64.399, -186.618, 10.9564, 62.6073, -208.255, 10.9564, 22.1281, -211.234, 12.1977, 23.3622, -189.3, 12.1977, 64.399, -208.255, 10.9564, 22.1281, -186.618, 10.9564, 62.6073, -184.352, 9.1853, 61.0927, -205.736, 9.1853, 21.0848, -208.255, 10.9564, 22.1281, -186.618, 10.9564, 62.6073, -205.736, 9.1853, 21.0848, -184.352, 9.1853, 61.0927, -182.631, 6.9872, 59.9433, -203.825, 6.9872, 20.2931, -205.736, 9.1853, 21.0848, -184.352, 9.1853, 61.0927, -203.825, 6.9872, 20.2931, -182.631, 6.9872, 59.9433, -181.557, 4.4899, 59.2257, -202.632, 4.4899, 19.7988, -203.825, 6.9872, 20.2931, -182.631, 6.9872, 59.9433, -202.632, 4.4899, 19.7988, -181.557, 4.4899, 59.2257, -181.192, 1.8384, 58.9818, -202.226, 1.8384, 19.6308, -202.632, 4.4899, 19.7988, -181.557, 4.4899, 59.2257, -202.226, 1.8384, 19.6308, -181.192, 1.8384, 58.9818, -181.557, -0.813, 59.2257, -202.632, -0.813, 19.7988, -202.226, 1.8384, 19.6308, -181.192, 1.8384, 58.9818, -202.632, -0.813, 19.7988, -181.557, -0.813, 59.2257, -182.631, -3.3103, 59.9433, -203.825, -3.3103, 20.2931, -202.632, -0.813, 19.7988, -181.557, -0.813, 59.2257, -203.825, -3.3103, 20.2931, -182.631, -3.3103, 59.9433, -184.352, -5.5084, 61.0927, -205.736, -5.5084, 21.0848, -203.825, -3.3103, 20.2931, -182.631, -3.3103, 59.9433, -205.736, -5.5084, 21.0848, -184.352, -5.5084, 61.0927, -186.618, -7.2796, 62.6073, -208.255, -7.2796, 22.1281, -205.736, -5.5084, 21.0848, -184.352, -5.5084, 61.0927, -208.255, -7.2796, 22.1281, -186.618, -7.2796, 62.6073, -189.3, -8.5208, 64.399, -211.234, -8.5208, 23.3622, -208.255, -7.2796, 22.1281, -186.618, -7.2796, 62.6073, -211.234, -8.5208, 23.3622, -189.3, -8.5208, 64.399, -192.24, -9.16, 66.3636, -214.501, -9.16, 24.7155, -211.234, -8.5208, 23.3622, -189.3, -8.5208, 64.399, -214.501, -9.16, 24.7155, -192.24, -9.16, 66.3636, -195.268, -9.16, 68.3871, -217.866, -9.16, 26.1093, -214.501, -9.16, 24.7155, -192.24, -9.16, 66.3636, -217.866, -9.16, 26.1093, -195.268, -9.16, 68.3871, -198.209, -8.5208, 70.3518, -221.134, -8.5208, 27.4626, -217.866, -9.16, 26.1093, -195.268, -9.16, 68.3871, -221.134, -8.5208, 27.4626, -198.209, -8.5208, 70.3518, -200.89, -7.2796, 72.1435, -224.113, -7.2796, 28.6967, -221.134, -8.5208, 27.4626, -198.209, -8.5208, 70.3518, -224.113, -7.2796, 28.6967, -200.89, -7.2796, 72.1435, -203.157, -5.5084, 73.6581, -226.632, -5.5084, 29.74, -224.113, -7.2796, 28.6967, -200.89, -7.2796, 72.1435, -226.632, -5.5084, 29.74, -203.157, -5.5084, 73.6581, -204.877, -3.3103, 74.8075, -228.543, -3.3103, 30.5317, -226.632, -5.5084, 29.74, -203.157, -5.5084, 73.6581, -228.543, -3.3103, 30.5317, -204.877, -3.3103, 74.8075, -205.951, -0.813, 75.525, -229.736, -0.813, 31.026, -228.543, -3.3103, 30.5317, -204.877, -3.3103, 74.8075, -229.736, -0.813, 31.026, -205.951, -0.813, 75.525, -206.316, 1.8384, 75.7689, -230.142, 1.8384, 31.194, -229.736, -0.813, 31.026, -205.951, -0.813, 75.525, -230.142, 1.8384, 31.194, -230.142, 1.8384, 31.194, -229.736, 4.4899, 31.026, -244.383, 4.4899, -17.2583, -244.814, 1.8384, -17.1727, -230.142, 1.8384, 31.194, -244.383, 4.4899, -17.2583, -229.736, 4.4899, 31.026, -228.543, 6.9872, 30.5317, -243.117, 6.9872, -17.5103, -244.383, 4.4899, -17.2583, -229.736, 4.4899, 31.026, -243.117, 6.9872, -17.5103, -228.543, 6.9872, 30.5317, -226.632, 9.1853, 29.74, -241.087, 9.1853, -17.9139, -243.117, 6.9872, -17.5103, -228.543, 6.9872, 30.5317, -241.087, 9.1853, -17.9139, -226.632, 9.1853, 29.74, -224.113, 10.9564, 28.6967, -238.413, 10.9564, -18.4458, -241.087, 9.1853, -17.9139, -226.632, 9.1853, 29.74, -238.413, 10.9564, -18.4458, -224.113, 10.9564, 28.6967, -221.134, 12.1977, 27.4626, -235.251, 12.1977, -19.0749, -238.413, 10.9564, -18.4458, -224.113, 10.9564, 28.6967, -235.251, 12.1977, -19.0749, -221.134, 12.1977, 27.4626, -217.866, 12.8369, 26.1093, -231.782, 12.8369, -19.7648, -235.251, 12.1977, -19.0749, -221.134, 12.1977, 27.4626, -231.782, 12.8369, -19.7648, -217.866, 12.8369, 26.1093, -214.501, 12.8369, 24.7155, -228.21, 12.8369, -20.4754, -231.782, 12.8369, -19.7648, -217.866, 12.8369, 26.1093, -228.21, 12.8369, -20.4754, -214.501, 12.8369, 24.7155, -211.234, 12.1977, 23.3622, -224.742, 12.1977, -21.1653, -228.21, 12.8369, -20.4754, -214.501, 12.8369, 24.7155, -224.742, 12.1977, -21.1653, -211.234, 12.1977, 23.3622, -208.255, 10.9564, 22.1281, -221.579, 10.9564, -21.7944, -224.742, 12.1977, -21.1653, -211.234, 12.1977, 23.3622, -221.579, 10.9564, -21.7944, -208.255, 10.9564, 22.1281, -205.736, 9.1853, 21.0848, -218.905, 9.1853, -22.3263, -221.579, 10.9564, -21.7944, -208.255, 10.9564, 22.1281, -218.905, 9.1853, -22.3263, -205.736, 9.1853, 21.0848, -203.825, 6.9872, 20.2931, -216.876, 6.9872, -22.7299, -218.905, 9.1853, -22.3263, -205.736, 9.1853, 21.0848, -216.876, 6.9872, -22.7299, -203.825, 6.9872, 20.2931, -202.632, 4.4899, 19.7988, -215.609, 4.4899, -22.9819, -216.876, 6.9872, -22.7299, -203.825, 6.9872, 20.2931, -215.609, 4.4899, -22.9819, -202.632, 4.4899, 19.7988, -202.226, 1.8384, 19.6308, -215.178, 1.8384, -23.0675, -215.609, 4.4899, -22.9819, -202.632, 4.4899, 19.7988, -215.178, 1.8384, -23.0675, -202.226, 1.8384, 19.6308, -202.632, -0.813, 19.7988, -215.609, -0.813, -22.9819, -215.178, 1.8384, -23.0675, -202.226, 1.8384, 19.6308, -215.609, -0.813, -22.9819, -202.632, -0.813, 19.7988, -203.825, -3.3103, 20.2931, -216.876, -3.3103, -22.7299, -215.609, -0.813, -22.9819, -202.632, -0.813, 19.7988, -216.876, -3.3103, -22.7299, -203.825, -3.3103, 20.2931, -205.736, -5.5084, 21.0848, -218.905, -5.5084, -22.3263, -216.876, -3.3103, -22.7299, -203.825, -3.3103, 20.2931, -218.905, -5.5084, -22.3263, -205.736, -5.5084, 21.0848, -208.255, -7.2796, 22.1281, -221.579, -7.2796, -21.7944, -218.905, -5.5084, -22.3263, -205.736, -5.5084, 21.0848, -221.579, -7.2796, -21.7944, -208.255, -7.2796, 22.1281, -211.234, -8.5208, 23.3622, -224.742, -8.5208, -21.1653, -221.579, -7.2796, -21.7944, -208.255, -7.2796, 22.1281, -224.742, -8.5208, -21.1653, -211.234, -8.5208, 23.3622, -214.501, -9.16, 24.7155, -228.21, -9.16, -20.4754, -224.742, -8.5208, -21.1653, -211.234, -8.5208, 23.3622, -228.21, -9.16, -20.4754, -214.501, -9.16, 24.7155, -217.866, -9.16, 26.1093, -231.782, -9.16, -19.7648, -228.21, -9.16, -20.4754, -214.501, -9.16, 24.7155, -231.782, -9.16, -19.7648, -217.866, -9.16, 26.1093, -221.134, -8.5208, 27.4626, -235.251, -8.5208, -19.0749, -231.782, -9.16, -19.7648, -217.866, -9.16, 26.1093, -235.251, -8.5208, -19.0749, -221.134, -8.5208, 27.4626, -224.113, -7.2796, 28.6967, -238.413, -7.2796, -18.4458, -235.251, -8.5208, -19.0749, -221.134, -8.5208, 27.4626, -238.413, -7.2796, -18.4458, -224.113, -7.2796, 28.6967, -226.632, -5.5084, 29.74, -241.087, -5.5084, -17.9139, -238.413, -7.2796, -18.4458, -224.113, -7.2796, 28.6967, -241.087, -5.5084, -17.9139, -226.632, -5.5084, 29.74, -228.543, -3.3103, 30.5317, -243.117, -3.3103, -17.5103, -241.087, -5.5084, -17.9139, -226.632, -5.5084, 29.74, -243.117, -3.3103, -17.5103, -228.543, -3.3103, 30.5317, -229.736, -0.813, 31.026, -244.383, -0.813, -17.2583, -243.117, -3.3103, -17.5103, -228.543, -3.3103, 30.5317, -244.383, -0.813, -17.2583, -229.736, -0.813, 31.026, -230.142, 1.8384, 31.194, -244.814, 1.8384, -17.1727, -244.383, -0.813, -17.2583, -229.736, -0.813, 31.026, -244.814, 1.8384, -17.1727, -247.775, 3.5774, -50.1623, -244.814, 1.8384, -17.1727, -247.623, 4.4899, -50.1529, -244.814, 1.8384, -17.1727, -244.383, 4.4899, -17.2583, -247.623, 4.4899, -50.1529, -248.065, 1.8384, -50.1802, -244.814, 1.8384, -17.1727, -247.775, 3.5774, -50.1623, -246.773, 6.1232, -50.1002, -244.383, 4.4899, -17.2583, -246.324, 6.9872, -50.0723, -244.383, 4.4899, -17.2583, -243.117, 6.9872, -17.5103, -246.324, 6.9872, -50.0723, -247.623, 4.4899, -50.1529, -244.383, 4.4899, -17.2583, -246.773, 6.1232, -50.1002, -244.97, 8.4161, -49.9884, -243.117, 6.9872, -17.5103, -244.242, 9.1853, -49.9432, -243.117, 6.9872, -17.5103, -241.087, 9.1853, -17.9139, -244.242, 9.1853, -49.9432, -246.324, 6.9872, -50.0723, -243.117, 6.9872, -17.5103, -244.97, 8.4161, -49.9884, -242.475, 10.3261, -49.8337, -241.087, 9.1853, -17.9139, -241.499, 10.9564, -49.7731, -241.087, 9.1853, -17.9139, -238.413, 10.9564, -18.4458, -241.499, 10.9564, -49.7731, -244.242, 9.1853, -49.9432, -241.087, 9.1853, -17.9139, -242.475, 10.3261, -49.8337, -239.434, 11.7463, -49.6451, -238.413, 10.9564, -18.4458, -238.254, 12.1977, -49.572, -238.413, 10.9564, -18.4458, -235.251, 12.1977, -19.0749, -238.254, 12.1977, -49.572, -241.499, 10.9564, -49.7731, -238.413, 10.9564, -18.4458, -239.434, 11.7463, -49.6451, -236.022, 12.5986, -49.4336, -235.251, 12.1977, -19.0749, -234.696, 12.8369, -49.3513, -235.251, 12.1977, -19.0749, -231.782, 12.8369, -19.7648, -234.696, 12.8369, -49.3513, -238.254, 12.1977, -49.572, -235.251, 12.1977, -19.0749, -236.022, 12.5986, -49.4336, -232.434, 12.8369, -49.2111, -231.782, 12.8369, -19.7648, -231.032, 12.8369, -49.1241, -231.782, 12.8369, -19.7648, -228.21, 12.8369, -20.4754, -231.032, 12.8369, -49.1241, -234.696, 12.8369, -49.3513, -231.782, 12.8369, -19.7648, -232.434, 12.8369, -49.2111, -228.873, 12.449, -48.9903, -228.21, 12.8369, -20.4754, -227.474, 12.1977, -48.9035, -228.21, 12.8369, -20.4754, -224.742, 12.1977, -21.1653, -227.474, 12.1977, -48.9035, -231.032, 12.8369, -49.1241, -228.21, 12.8369, -20.4754, -228.873, 12.449, -48.9903, -225.538, 11.4574, -48.7835, -224.742, 12.1977, -21.1653, -224.229, 10.9564, -48.7023, -224.742, 12.1977, -21.1653, -221.579, 10.9564, -21.7944, -224.229, 10.9564, -48.7023, -227.474, 12.1977, -48.9035, -224.742, 12.1977, -21.1653, -225.538, 11.4574, -48.7835, -222.619, 9.917, -48.6025, -221.579, 10.9564, -21.7944, -221.486, 9.1853, -48.5323, -221.579, 10.9564, -21.7944, -218.905, 9.1853, -22.3263, -221.486, 9.1853, -48.5323, -224.229, 10.9564, -48.7023, -221.579, 10.9564, -21.7944, -222.619, 9.917, -48.6025, -220.281, 7.913, -48.4576, -218.905, 9.1853, -22.3263, -219.404, 6.9872, -48.4032, -218.905, 9.1853, -22.3263, -216.876, 6.9872, -22.7299, -219.404, 6.9872, -48.4032, -221.486, 9.1853, -48.5323, -218.905, 9.1853, -22.3263, -220.281, 7.913, -48.4576, -218.66, 5.5568, -48.357, -216.876, 6.9872, -22.7299, -218.105, 4.4899, -48.3226, -216.876, 6.9872, -22.7299, -215.609, 4.4899, -22.9819, -218.105, 4.4899, -48.3226, -219.404, 6.9872, -48.4032, -216.876, 6.9872, -22.7299, -218.66, 5.5568, -48.357, -217.853, 2.9806, -48.307, -215.609, 4.4899, -22.9819, -217.663, 1.8384, -48.2952, -215.609, 4.4899, -22.9819, -215.178, 1.8384, -23.0675, -217.663, 1.8384, -48.2952, -218.105, 4.4899, -48.3226, -215.609, 4.4899, -22.9819, -217.853, 2.9806, -48.307, -217.914, 0.3311, -48.3108, -215.178, 1.8384, -23.0675, -218.105, -0.813, -48.3226, -215.178, 1.8384, -23.0675, -215.609, -0.813, -22.9819, -218.105, -0.813, -48.3226, -217.663, 1.8384, -48.2952, -215.178, 1.8384, -23.0675, -217.914, 0.3311, -48.3108, -218.846, -2.238, -48.3686, -215.609, -0.813, -22.9819, -219.404, -3.3103, -48.4032, -215.609, -0.813, -22.9819, -216.876, -3.3103, -22.7299, -219.404, -3.3103, -48.4032, -218.105, -0.813, -48.3226, -215.609, -0.813, -22.9819, -218.846, -2.238, -48.3686, -220.602, -4.5753, -48.4775, -216.876, -3.3103, -22.7299, -221.486, -5.5084, -48.5323, -216.876, -3.3103, -22.7299, -218.905, -5.5084, -22.3263, -221.486, -5.5084, -48.5323, -219.404, -3.3103, -48.4032, -216.876, -3.3103, -22.7299, -220.602, -4.5753, -48.4775, -223.084, -6.5404, -48.6314, -218.905, -5.5084, -22.3263, -224.229, -7.2796, -48.7023, -218.905, -5.5084, -22.3263, -221.579, -7.2796, -21.7944, -224.229, -7.2796, -48.7023, -221.486, -5.5084, -48.5323, -218.905, -5.5084, -22.3263, -223.084, -6.5404, -48.6314, -226.149, -8.014, -48.8214, -221.579, -7.2796, -21.7944, -227.474, -8.5208, -48.9035, -221.579, -7.2796, -21.7944, -224.742, -8.5208, -21.1653, -227.474, -8.5208, -48.9035, -224.229, -7.2796, -48.7023, -221.579, -7.2796, -21.7944, -226.149, -8.014, -48.8214, -229.615, -8.9055, -49.0363, -224.742, -8.5208, -21.1653, -231.032, -9.16, -49.1241, -224.742, -8.5208, -21.1653, -228.21, -9.16, -20.4754, -231.032, -9.16, -49.1241, -227.474, -8.5208, -48.9035, -224.742, -8.5208, -21.1653, -229.615, -8.9055, -49.0363, -233.276, -9.16, -49.2633, -228.21, -9.16, -20.4754, -234.696, -9.16, -49.3514, -228.21, -9.16, -20.4754, -231.782, -9.16, -19.7648, -234.696, -9.16, -49.3514, -231.032, -9.16, -49.1241, -228.21, -9.16, -20.4754, -233.276, -9.16, -49.2633, -236.913, -8.7617, -49.4888, -231.782, -9.16, -19.7648, -238.254, -8.5208, -49.572, -231.782, -9.16, -19.7648, -235.251, -8.5208, -19.0749, -238.254, -8.5208, -49.572, -234.696, -9.16, -49.3514, -231.782, -9.16, -19.7648, -236.913, -8.7617, -49.4888, -240.307, -7.7354, -49.6993, -235.251, -8.5208, -19.0749, -241.499, -7.2796, -49.7731, -235.251, -8.5208, -19.0749, -238.413, -7.2796, -18.4458, -241.499, -7.2796, -49.7731, -238.254, -8.5208, -49.572, -235.251, -8.5208, -19.0749, -240.307, -7.7354, -49.6993, -243.258, -6.1439, -49.8822, -238.413, -7.2796, -18.4458, -244.242, -5.5084, -49.9432, -238.413, -7.2796, -18.4458, -241.087, -5.5084, -17.9139, -244.242, -5.5084, -49.9432, -241.499, -7.2796, -49.7731, -238.413, -7.2796, -18.4458, -243.258, -6.1439, -49.8822, -245.591, -4.0841, -50.0268, -241.087, -5.5084, -17.9139, -246.324, -3.3103, -50.0723, -241.087, -5.5084, -17.9139, -243.117, -3.3103, -17.5103, -246.324, -3.3103, -50.0723, -244.242, -5.5084, -49.9432, -241.087, -5.5084, -17.9139, -245.591, -4.0841, -50.0268, -247.172, -1.6801, -50.1249, -243.117, -3.3103, -17.5103, -247.623, -0.813, -50.1528, -243.117, -3.3103, -17.5103, -244.383, -0.813, -17.2583, -247.623, -0.813, -50.1528, -246.324, -3.3103, -50.0723, -243.117, -3.3103, -17.5103, -247.172, -1.6801, -50.1249, -247.913, 0.9249, -50.1708, -244.383, -0.813, -17.2583, -248.065, 1.8384, -50.1802, -244.383, -0.813, -17.2583, -244.814, 1.8384, -17.1727, -248.065, 1.8384, -50.1802, -247.623, -0.813, -50.1528, -244.383, -0.813, -17.2583, -247.913, 0.9249, -50.1708, 252.823, -12.1253, -19.1234, 252.859, -12.1193, -19.1212, 267.292, 1.9054, -18.2263, 252.859, -12.1193, -19.1212, 257.538, -10.4722, -18.8311, 267.292, 1.9054, -18.2263, 257.538, -10.4722, -18.8311, 257.633, -10.4389, -18.8252, 267.292, 1.9054, -18.2263, 257.633, -10.4389, -18.8252, 261.546, -8.1137, -18.5826, 267.292, 1.9054, -18.2263, 261.546, -8.1137, -18.5826, 261.668, -8.0411, -18.575, 267.292, 1.9054, -18.2263, 261.668, -8.0411, -18.575, 264.614, -5.1782, -18.3923, 267.292, 1.9054, -18.2263, 264.614, -5.1782, -18.3923, 264.73, -5.0652, -18.3851, 267.292, 1.9054, -18.2263, 264.73, -5.0652, -18.3851, 266.56, -1.8284, -18.2717, 267.292, 1.9054, -18.2263, 267.263, 1.7469, -18.2281, 266.56, -1.8284, -18.2717, 266.642, -1.6842, -18.2666, 266.56, -1.8284, -18.2717, 267.263, 1.7469, -18.2281, 267.292, 1.9054, -18.2263, 236.638, 15.9302, -20.1269, 236.433, 15.8598, -20.1396, 241.88, 16.774, -19.8019, 242.014, 16.7956, -19.7936, 231.736, 14.2498, -20.4309, 247.505, 16.7956, -19.4531, 231.736, 14.2498, -20.4309, 242.014, 16.7956, -19.7936, 241.88, 16.774, -19.8019, 236.433, 15.8598, -20.1396, 231.736, 14.2498, -20.4309, 241.88, 16.774, -19.8019, 231.736, 14.2498, -20.4309, 231.497, 14.1113, -20.4457, 247.505, 16.7956, -19.4531, 247.551, 16.7956, -19.4503, 227.592, 11.8519, -20.6878, 250.357, 16.3438, -19.2763, 227.592, 11.8519, -20.6878, 247.551, 16.7956, -19.4503, 247.505, 16.7956, -19.4531, 231.497, 14.1113, -20.4457, 227.592, 11.8519, -20.6878, 247.505, 16.7956, -19.4531, 227.592, 11.8519, -20.6878, 227.367, 11.6395, -20.7018, 250.357, 16.3438, -19.2763, 224.447, 8.876, -20.8828, 252.892, 15.9187, -19.1192, 252.859, 15.9302, -19.1212, 224.447, 8.876, -20.8828, 252.859, 15.9302, -19.1212, 250.357, 16.3438, -19.2763, 227.367, 11.6395, -20.7018, 224.447, 8.876, -20.8828, 250.357, 16.3438, -19.2763, 257.633, 14.2498, -18.8252, 250.357, 12.3842, -19.2763, 257.712, 14.2028, -18.8203, 227.592, -8.0411, -20.6878, 227.872, -8.2033, -20.6705, 252.823, -12.1253, -19.1234, 227.872, -8.2033, -20.6705, 231.736, -10.4389, -20.4309, 252.823, -12.1253, -19.1234, 231.736, -10.4389, -20.4309, 232.001, -10.5298, -20.4144, 252.823, -12.1253, -19.1234, 232.001, -10.5298, -20.4144, 236.638, -12.1193, -20.1269, 252.823, -12.1253, -19.1234, 236.638, -12.1193, -20.1269, 236.848, -12.153, -20.1139, 252.823, -12.1253, -19.1234, 236.848, -12.153, -20.1139, 242.014, -12.9847, -19.7936, 252.823, -12.1253, -19.1234, 242.014, -12.9847, -19.7936, 242.142, -12.9847, -19.7857, 252.823, -12.1253, -19.1234, 242.142, -12.9847, -19.7857, 250.357, -12.5329, -19.2763, 252.823, -12.1253, -19.1234, 247.592, -12.9781, -19.4478, 242.142, -12.9847, -19.7857, 247.551, -12.9847, -19.4503, 242.142, -12.9847, -19.7857, 247.592, -12.9781, -19.4478, 250.357, -12.5329, -19.2763, 260.638, 4.4899, -18.6388, 260.65, 4.417, -18.6381, 266.642, 5.495, -18.2666, 266.67, 5.3424, -18.2649, 261.075, 1.8384, -18.6118, 267.292, 1.9054, -18.2263, 260.65, 4.417, -18.6381, 266.67, 5.3424, -18.2649, 266.642, 5.495, -18.2666, 261.075, 1.8384, -18.6118, 266.67, 5.3424, -18.2649, 260.65, 4.417, -18.6381, 264.802, 8.7484, -18.3807, 259.355, 6.9872, -18.7184, 266.642, 5.495, -18.2666, 259.355, 6.9872, -18.7184, 259.386, 6.9269, -18.7165, 266.642, 5.495, -18.2666, 259.386, 6.9269, -18.7165, 260.638, 4.4899, -18.6388, 266.642, 5.495, -18.2666, 261.76, 11.7626, -18.5693, 257.298, 9.1853, -18.8459, 264.73, 8.8761, -18.3851, 257.298, 9.1853, -18.8459, 257.337, 9.1441, -18.8435, 264.73, 8.8761, -18.3851, 259.355, 6.9872, -18.7184, 264.802, 8.7484, -18.3807, 264.73, 8.8761, -18.3851, 259.355, 6.9872, -18.7184, 264.73, 8.8761, -18.3851, 257.337, 9.1441, -18.8435, 257.712, 14.2028, -18.8203, 254.589, 10.9564, -19.014, 261.668, 11.852, -18.575, 254.589, 10.9564, -19.014, 254.62, 10.936, -19.012, 261.668, 11.852, -18.575, 257.298, 9.1853, -18.8459, 261.76, 11.7626, -18.5693, 261.668, 11.852, -18.575, 257.298, 9.1853, -18.8459, 261.668, 11.852, -18.575, 254.62, 10.936, -19.012, 250.357, 12.3842, -19.2763, 251.383, 12.1977, -19.2127, 257.712, 14.2028, -18.8203, 251.392, 12.1941, -19.2121, 257.712, 14.2028, -18.8203, 251.383, 12.1977, -19.2127, 257.712, 14.2028, -18.8203, 251.392, 12.1941, -19.2121, 254.589, 10.9564, -19.014, 251.383, -8.5208, -19.2127, 251.373, -8.5226, -19.2133, 252.823, -12.1253, -19.1234, 251.373, -8.5226, -19.2133, 250.357, -8.7074, -19.2763, 252.823, -12.1253, -19.1234, 254.551, -7.294, -19.0163, 252.823, -12.1253, -19.1234, 254.589, -7.2796, -19.0139, 252.823, -12.1253, -19.1234, 254.551, -7.294, -19.0163, 251.383, -8.5208, -19.2127, 267.292, 1.9054, -18.2263, 257.298, -5.5084, -18.8459, 252.823, -12.1253, -19.1234, 257.247, -5.5419, -18.8491, 252.823, -12.1253, -19.1234, 257.298, -5.5084, -18.8459, 252.823, -12.1253, -19.1234, 257.247, -5.5419, -18.8491, 254.589, -7.2796, -19.0139, 259.355, -3.3103, -18.7184, 259.305, -3.3636, -18.7215, 267.292, 1.9054, -18.2263, 257.298, -5.5084, -18.8459, 267.292, 1.9054, -18.2263, 259.305, -3.3636, -18.7215, 260.638, -0.813, -18.6388, 260.603, -0.8818, -18.641, 267.292, 1.9054, -18.2263, 260.603, -0.8818, -18.641, 259.355, -3.3103, -18.7184, 267.292, 1.9054, -18.2263, 261.075, 1.8384, -18.6118, 261.062, 1.7625, -18.6126, 267.292, 1.9054, -18.2263, 260.638, -0.813, -18.6388, 267.292, 1.9054, -18.2263, 261.062, 1.7625, -18.6126, 252.892, 15.9187, -19.1192, 247.801, 12.8368, -19.4348, 257.633, 14.2498, -18.8252, 250.357, 12.3842, -19.2763, 257.633, 14.2498, -18.8252, 247.801, 12.8368, -19.4348, 247.774, 12.8369, -19.4365, 252.892, 15.9187, -19.1192, 244.083, 12.8369, -19.6653, 252.892, 15.9187, -19.1192, 247.774, 12.8369, -19.4365, 247.801, 12.8368, -19.4348, 224.447, 8.876, -20.8828, 240.474, 12.1977, -19.8891, 252.892, 15.9187, -19.1192, 244.017, 12.8251, -19.6694, 252.892, 15.9187, -19.1192, 240.474, 12.1977, -19.8891, 252.892, 15.9187, -19.1192, 244.017, 12.8251, -19.6694, 244.083, 12.8369, -19.6653, 224.286, 8.6, -20.8928, 237.182, 10.9564, -20.0932, 224.447, 8.876, -20.8828, 224.447, 8.876, -20.8828, 240.377, 12.1612, -19.8951, 240.474, 12.1977, -19.8891, 240.377, 12.1612, -19.8951, 224.447, 8.876, -20.8828, 237.182, 10.9564, -20.0932, 222.483, 5.4951, -21.0046, 234.399, 9.1853, -20.2658, 224.286, 8.6, -20.8928, 234.399, 9.1853, -20.2658, 237.072, 10.8864, -20.1, 224.286, 8.6, -20.8928, 237.072, 10.8864, -20.1, 237.182, 10.9564, -20.0932, 224.286, 8.6, -20.8928, 222.425, 5.18, -21.0082, 232.287, 6.9872, -20.3967, 222.483, 5.4951, -21.0046, 222.483, 5.4951, -21.0046, 234.297, 9.0794, -20.2721, 234.399, 9.1853, -20.2658, 232.287, 6.9872, -20.3967, 234.297, 9.0794, -20.2721, 222.483, 5.4951, -21.0046, 221.816, 1.9054, -21.046, 230.969, 4.4899, -20.4785, 222.425, 5.18, -21.0082, 230.969, 4.4899, -20.4785, 232.215, 6.8504, -20.4012, 222.425, 5.18, -21.0082, 232.287, 6.9872, -20.3967, 222.425, 5.18, -21.0082, 232.215, 6.8504, -20.4012, 222.483, -1.6842, -21.0046, 230.521, 1.8384, -20.5062, 221.876, 1.5852, -21.0423, 230.521, 1.8384, -20.5062, 221.816, 1.9054, -21.046, 221.876, 1.5852, -21.0423, 230.521, 1.8384, -20.5062, 230.942, 4.3342, -20.4801, 221.816, 1.9054, -21.046, 230.969, 4.4899, -20.4785, 221.816, 1.9054, -21.046, 230.942, 4.3342, -20.4801, 224.447, -5.0652, -20.8828, 230.969, -0.813, -20.4785, 222.652, -1.975, -20.9941, 222.483, -1.6842, -21.0046, 230.547, 1.6799, -20.5046, 230.521, 1.8384, -20.5062, 230.969, -0.813, -20.4785, 222.483, -1.6842, -21.0046, 222.652, -1.975, -20.9941, 230.969, -0.813, -20.4785, 230.547, 1.6799, -20.5046, 222.483, -1.6842, -21.0046, 224.447, -5.0652, -20.8828, 231.045, -0.9577, -20.4737, 230.969, -0.813, -20.4785, 231.045, -0.9577, -20.4737, 224.447, -5.0652, -20.8828, 232.287, -3.3103, -20.3967, 227.592, -8.0411, -20.6878, 234.399, -5.5084, -20.2658, 224.694, -5.2991, -20.8675, 224.447, -5.0652, -20.8828, 232.4, -3.4277, -20.3897, 232.287, -3.3103, -20.3967, 234.399, -5.5084, -20.2658, 224.447, -5.0652, -20.8828, 224.694, -5.2991, -20.8675, 234.399, -5.5084, -20.2658, 232.4, -3.4277, -20.3897, 224.447, -5.0652, -20.8828, 227.592, -8.0411, -20.6878, 234.528, -5.5908, -20.2577, 234.399, -5.5084, -20.2658, 237.182, -7.2796, -20.0932, 234.528, -5.5908, -20.2577, 227.592, -8.0411, -20.6878, 252.823, -12.1253, -19.1234, 240.474, -8.5208, -19.8891, 227.592, -8.0411, -20.6878, 227.592, -8.0411, -20.6878, 237.306, -7.3265, -20.0855, 237.182, -7.2796, -20.0932, 240.474, -8.5208, -19.8891, 237.306, -7.3265, -20.0855, 227.592, -8.0411, -20.6878, 240.575, -8.5387, -19.8829, 252.823, -12.1253, -19.1234, 244.083, -9.16, -19.6653, 252.823, -12.1253, -19.1234, 240.575, -8.5387, -19.8829, 240.474, -8.5208, -19.8891, 247.827, -9.1555, -19.4332, 252.823, -12.1253, -19.1234, 250.357, -8.7074, -19.2763, 252.823, -12.1253, -19.1234, 247.827, -9.1555, -19.4332, 247.801, -9.16, -19.4348, 244.148, -9.16, -19.6613, 252.823, -12.1253, -19.1234, 247.801, -9.16, -19.4348, 252.823, -12.1253, -19.1234, 244.148, -9.16, -19.6613, 244.083, -9.16, -19.6653, -244.58, 14.2498, -49.9642, -231.093, 16.7956, -49.1279, -241.472, 15.3308, -49.7714, -244.58, 14.2498, -49.9642, -228.992, 16.7956, -48.9977, -231.093, 16.7956, -49.1279, -231.093, 16.7956, -49.1279, -239.748, 15.9302, -49.6646, -241.472, 15.3308, -49.7714, -231.093, 16.7956, -49.1279, -236.41, 16.4754, -49.4576, -239.748, 15.9302, -49.6646, -234.449, 16.7956, -49.336, -236.41, 16.4754, -49.4576, -231.093, 16.7956, -49.1279, -248.665, 11.8519, -50.2175, -225.816, 16.2769, -48.8008, -245.992, 13.4212, -50.0517, -248.665, 11.8519, -50.2175, -223.693, 15.9302, -48.6691, -225.816, 16.2769, -48.8008, -225.816, 16.2769, -48.8008, -244.58, 14.2498, -49.9642, -245.992, 13.4212, -50.0517, -228.992, 16.7956, -48.9977, -244.58, 14.2498, -49.9642, -225.816, 16.2769, -48.8008, -220.873, 14.9495, -48.4943, -248.665, 11.8519, -50.2175, -249.71, 10.8492, -50.2822, -223.693, 15.9302, -48.6691, -248.665, 11.8519, -50.2175, -220.873, 14.9495, -48.4943, -210.791, -3.5191, -47.8691, -213.383, -6.7031, -48.0298, -211.676, -5.0652, -47.924, -210.791, -3.5191, -47.8691, -214.776, -8.0411, -48.1162, -213.383, -6.7031, -48.0298, -209.741, -1.6842, -47.804, -217.071, -9.3881, -48.2585, -210.791, -3.5191, -47.8691, -209.438, -0.0308, -47.7852, -218.861, -10.4389, -48.3695, -209.741, -1.6842, -47.804, -217.071, -9.3881, -48.2585, -209.741, -1.6842, -47.804, -218.861, -10.4389, -48.3695, -214.776, -8.0411, -48.1162, -210.791, -3.5191, -47.8691, -217.071, -9.3881, -48.2585, -209.083, 1.9054, -47.7632, -221.644, -11.4067, -48.5421, -209.438, -0.0308, -47.7852, -209.385, 3.5542, -47.782, -223.693, -12.1193, -48.6691, -209.083, 1.9054, -47.7632, -221.644, -11.4067, -48.5421, -209.083, 1.9054, -47.7632, -223.693, -12.1193, -48.6691, -218.861, -10.4389, -48.3695, -209.438, -0.0308, -47.7852, -221.644, -11.4067, -48.5421, -210.619, 7.0285, -47.8585, -226.828, -12.6314, -48.8635, -209.741, 5.495, -47.804, -226.828, -12.6314, -48.8635, -209.385, 3.5542, -47.782, -209.741, 5.495, -47.804, -226.828, -12.6314, -48.8635, -223.693, -12.1193, -48.6691, -209.385, 3.5542, -47.782, -254.358, 1.9054, -50.5705, -237.755, -12.4449, -49.541, -234.449, -12.9847, -49.336, -254.358, 1.9054, -50.5705, -239.748, -12.1193, -49.6646, -237.755, -12.4449, -49.541, -254.358, 1.9054, -50.5705, -242.832, -11.0469, -49.8558, -239.748, -12.1193, -49.6646, -254.358, 1.9054, -50.5705, -244.58, -10.4389, -49.9642, -242.832, -11.0469, -49.8558, -254.358, 1.9054, -50.5705, -247.237, -8.8791, -50.129, -244.58, -10.4389, -49.9642, -254.358, 1.9054, -50.5705, -248.665, -8.0411, -50.2175, -247.237, -8.8791, -50.129, -254.358, 1.9054, -50.5705, -250.712, -6.0762, -50.3444, -248.665, -8.0411, -50.2175, -254.358, 1.9054, -50.5705, -251.765, -5.0652, -50.4097, -250.712, -6.0762, -50.3444, -254.358, 1.9054, -50.5705, -253.056, -2.8097, -50.4897, -251.765, -5.0652, -50.4097, -254.142, 0.7239, -50.557, -253.056, -2.8097, -50.4897, -254.358, 1.9054, -50.5705, -253.056, -2.8097, -50.4897, -254.142, 0.7239, -50.557, -253.7, -1.6842, -50.5297, -254.358, 1.9054, -50.5705, -247.623, 4.4899, -50.1529, -253.917, 4.3155, -50.5431, -254.358, 1.9054, -50.5705, -247.775, 3.5774, -50.1623, -247.623, 4.4899, -50.1529, -247.775, 3.5774, -50.1623, -254.358, 1.9054, -50.5705, -248.065, 1.8384, -50.1802, -253.7, 5.495, -50.5297, -246.324, 6.9872, -50.0723, -252.406, 7.7562, -50.4494, -253.917, 4.3155, -50.5431, -246.773, 6.1232, -50.1002, -253.7, 5.495, -50.5297, -246.324, 6.9872, -50.0723, -253.7, 5.495, -50.5297, -246.773, 6.1232, -50.1002, -247.623, 4.4899, -50.1529, -246.773, 6.1232, -50.1002, -253.917, 4.3155, -50.5431, -252.406, 7.7562, -50.4494, -244.242, 9.1853, -49.9432, -251.765, 8.8761, -50.4097, -246.324, 6.9872, -50.0723, -244.97, 8.4161, -49.9884, -252.406, 7.7562, -50.4494, -244.97, 8.4161, -49.9884, -244.242, 9.1853, -49.9432, -252.406, 7.7562, -50.4494, -251.765, 8.8761, -50.4097, -241.499, 10.9564, -49.7731, -249.71, 10.8492, -50.2822, -242.475, 10.3261, -49.8337, -241.499, 10.9564, -49.7731, -251.765, 8.8761, -50.4097, -242.475, 10.3261, -49.8337, -251.765, 8.8761, -50.4097, -244.242, 9.1853, -49.9432, -239.434, 11.7463, -49.6451, -249.71, 10.8492, -50.2822, -241.499, 10.9564, -49.7731, -249.71, 10.8492, -50.2822, -239.434, 11.7463, -49.6451, -238.254, 12.1977, -49.572, -249.71, 10.8492, -50.2822, -234.696, 12.8369, -49.3513, -220.873, 14.9495, -48.4943, -236.022, 12.5986, -49.4336, -249.71, 10.8492, -50.2822, -238.254, 12.1977, -49.572, -249.71, 10.8492, -50.2822, -236.022, 12.5986, -49.4336, -234.696, 12.8369, -49.3513, -220.873, 14.9495, -48.4943, -231.032, 12.8369, -49.1241, -218.861, 14.2498, -48.3695, -231.032, 12.8369, -49.1241, -220.873, 14.9495, -48.4943, -232.434, 12.8369, -49.2111, -234.696, 12.8369, -49.3513, -232.434, 12.8369, -49.2111, -220.873, 14.9495, -48.4943, -218.861, 14.2498, -48.3695, -227.474, 12.1977, -48.9035, -216.538, 12.8859, -48.2254, -218.861, 14.2498, -48.3695, -228.873, 12.449, -48.9903, -227.474, 12.1977, -48.9035, -231.032, 12.8369, -49.1241, -228.873, 12.449, -48.9903, -218.861, 14.2498, -48.3695, -216.538, 12.8859, -48.2254, -224.229, 10.9564, -48.7023, -214.776, 11.8519, -48.1162, -224.229, 10.9564, -48.7023, -216.538, 12.8859, -48.2254, -225.538, 11.4574, -48.7835, -227.474, 12.1977, -48.9035, -225.538, 11.4574, -48.7835, -216.538, 12.8859, -48.2254, -214.776, 11.8519, -48.1162, -221.486, 9.1853, -48.5323, -213.052, 10.1971, -48.0094, -214.776, 11.8519, -48.1162, -222.619, 9.917, -48.6025, -221.486, 9.1853, -48.5323, -224.229, 10.9564, -48.7023, -222.619, 9.917, -48.6025, -214.776, 11.8519, -48.1162, -211.676, 8.8761, -47.924, -219.404, 6.9872, -48.4032, -210.619, 7.0285, -47.8585, -213.052, 10.1971, -48.0094, -220.281, 7.913, -48.4576, -211.676, 8.8761, -47.924, -220.281, 7.913, -48.4576, -219.404, 6.9872, -48.4032, -211.676, 8.8761, -47.924, -221.486, 9.1853, -48.5323, -220.281, 7.913, -48.4576, -213.052, 10.1971, -48.0094, -210.619, 7.0285, -47.8585, -218.66, 5.5568, -48.357, -218.105, 4.4899, -48.3226, -218.66, 5.5568, -48.357, -210.619, 7.0285, -47.8585, -219.404, 6.9872, -48.4032, -217.853, 2.9806, -48.307, -210.619, 7.0285, -47.8585, -218.105, 4.4899, -48.3226, -210.619, 7.0285, -47.8585, -217.853, 2.9806, -48.307, -217.663, 1.8384, -48.2952, -217.914, 0.3311, -48.3108, -210.619, 7.0285, -47.8585, -217.663, 1.8384, -48.2952, -210.619, 7.0285, -47.8585, -217.914, 0.3311, -48.3108, -218.105, -0.813, -48.3226, -210.619, 7.0285, -47.8585, -219.404, -3.3103, -48.4032, -226.828, -12.6314, -48.8635, -210.619, 7.0285, -47.8585, -218.846, -2.238, -48.3686, -219.404, -3.3103, -48.4032, -218.846, -2.238, -48.3686, -210.619, 7.0285, -47.8585, -218.105, -0.813, -48.3226, -226.828, -12.6314, -48.8635, -221.486, -5.5084, -48.5323, -228.992, -12.9847, -48.9977, -220.602, -4.5753, -48.4775, -226.828, -12.6314, -48.8635, -219.404, -3.3103, -48.4032, -226.828, -12.6314, -48.8635, -220.602, -4.5753, -48.4775, -221.486, -5.5084, -48.5323, -223.084, -6.5404, -48.6314, -224.229, -7.2796, -48.7023, -228.992, -12.9847, -48.9977, -223.084, -6.5404, -48.6314, -228.992, -12.9847, -48.9977, -221.486, -5.5084, -48.5323, -228.992, -12.9847, -48.9977, -226.149, -8.014, -48.8214, -227.474, -8.5208, -48.9035, -226.149, -8.014, -48.8214, -228.992, -12.9847, -48.9977, -224.229, -7.2796, -48.7023, -229.615, -8.9055, -49.0363, -228.992, -12.9847, -48.9977, -227.474, -8.5208, -48.9035, -228.992, -12.9847, -48.9977, -229.615, -8.9055, -49.0363, -231.032, -9.16, -49.1241, -231.032, -9.16, -49.1241, -233.276, -9.16, -49.2633, -228.992, -12.9847, -48.9977, -233.276, -9.16, -49.2633, -234.696, -9.16, -49.3514, -228.992, -12.9847, -48.9977, -228.992, -12.9847, -48.9977, -238.254, -8.5208, -49.572, -232.31, -12.9847, -49.2034, -228.992, -12.9847, -48.9977, -236.913, -8.7617, -49.4888, -238.254, -8.5208, -49.572, -234.696, -9.16, -49.3514, -236.913, -8.7617, -49.4888, -228.992, -12.9847, -48.9977, -232.31, -12.9847, -49.2034, -241.499, -7.2796, -49.7731, -234.449, -12.9847, -49.336, -232.31, -12.9847, -49.2034, -240.307, -7.7354, -49.6993, -241.499, -7.2796, -49.7731, -238.254, -8.5208, -49.572, -240.307, -7.7354, -49.6993, -232.31, -12.9847, -49.2034, -234.449, -12.9847, -49.336, -244.242, -5.5084, -49.9432, -254.358, 1.9054, -50.5705, -243.258, -6.1439, -49.8822, -234.449, -12.9847, -49.336, -241.499, -7.2796, -49.7731, -234.449, -12.9847, -49.336, -243.258, -6.1439, -49.8822, -244.242, -5.5084, -49.9432, -254.358, 1.9054, -50.5705, -245.591, -4.0841, -50.0268, -246.324, -3.3103, -50.0723, -245.591, -4.0841, -50.0268, -254.358, 1.9054, -50.5705, -244.242, -5.5084, -49.9432, -247.913, 0.9249, -50.1708, -254.358, 1.9054, -50.5705, -247.623, -0.813, -50.1528, -254.358, 1.9054, -50.5705, -247.913, 0.9249, -50.1708, -248.065, 1.8384, -50.1802, -254.358, 1.9054, -50.5705, -247.172, -1.6801, -50.1249, -247.623, -0.813, -50.1528, -247.172, -1.6801, -50.1249, -254.358, 1.9054, -50.5705, -246.324, -3.3103, -50.0723) [node name="Tunnel" instance=ExtResource("1_1sr0i")] collision_mask = 3 [node name="Tunnel2" parent="." index="0"] surface_material_override/0 = ExtResource("2_lkb1r") [node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"] shape = SubResource("ConcavePolygonShape3D_bh6xx") ================================================ FILE: project/demo/assets/textures/asset_licenses.txt ================================================ All ambientCG assets are provided under the Creative Commons CC0 1.0 Universal License. https://docs.ambientcg.com/license/ Grass: https://ambientcg.com/view?id=Ground037 Rock: https://ambientcg.com/view?id=Rock030 Cliff: https://ambientcg.com/view?id=Rock023 ================================================ FILE: project/demo/assets/textures/ground037_alb_ht.png.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://ddprscrpsofah" path.bptc="res://.godot/imported/ground037_alb_ht.png-d854c3c88beba9927351b95063edffa2.bptc.ctex" path.astc="res://.godot/imported/ground037_alb_ht.png-d854c3c88beba9927351b95063edffa2.astc.ctex" metadata={ "imported_formats": ["s3tc_bptc", "etc2_astc"], "vram_texture": true } [deps] source_file="res://demo/assets/textures/ground037_alb_ht.png" dest_files=["res://.godot/imported/ground037_alb_ht.png-d854c3c88beba9927351b95063edffa2.bptc.ctex", "res://.godot/imported/ground037_alb_ht.png-d854c3c88beba9927351b95063edffa2.astc.ctex"] [params] compress/mode=2 compress/high_quality=true compress/lossy_quality=0.7 compress/uastc_level=0 compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=2 compress/channel_pack=0 mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/channel_remap/red=0 process/channel_remap/green=1 process/channel_remap/blue=2 process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 ================================================ FILE: project/demo/assets/textures/ground037_nrm_rgh.png.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://g80pbqtklcws" path.bptc="res://.godot/imported/ground037_nrm_rgh.png-02372c72ee87f1844ee708d952e43e96.bptc.ctex" path.astc="res://.godot/imported/ground037_nrm_rgh.png-02372c72ee87f1844ee708d952e43e96.astc.ctex" metadata={ "imported_formats": ["s3tc_bptc", "etc2_astc"], "vram_texture": true } [deps] source_file="res://demo/assets/textures/ground037_nrm_rgh.png" dest_files=["res://.godot/imported/ground037_nrm_rgh.png-02372c72ee87f1844ee708d952e43e96.bptc.ctex", "res://.godot/imported/ground037_nrm_rgh.png-02372c72ee87f1844ee708d952e43e96.astc.ctex"] [params] compress/mode=2 compress/high_quality=true compress/lossy_quality=0.7 compress/uastc_level=0 compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=2 compress/channel_pack=0 mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/channel_remap/red=0 process/channel_remap/green=1 process/channel_remap/blue=2 process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 ================================================ FILE: project/demo/assets/textures/rock023_alb_ht.png.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://c88j3oj0lf6om" path.bptc="res://.godot/imported/rock023_alb_ht.png-9c06b8a5f11d940d1c46d8928f6d6a5c.bptc.ctex" path.astc="res://.godot/imported/rock023_alb_ht.png-9c06b8a5f11d940d1c46d8928f6d6a5c.astc.ctex" metadata={ "imported_formats": ["s3tc_bptc", "etc2_astc"], "vram_texture": true } [deps] source_file="res://demo/assets/textures/rock023_alb_ht.png" dest_files=["res://.godot/imported/rock023_alb_ht.png-9c06b8a5f11d940d1c46d8928f6d6a5c.bptc.ctex", "res://.godot/imported/rock023_alb_ht.png-9c06b8a5f11d940d1c46d8928f6d6a5c.astc.ctex"] [params] compress/mode=2 compress/high_quality=true compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=2 compress/channel_pack=0 mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=0 ================================================ FILE: project/demo/assets/textures/rock023_nrm_rgh.png.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://dabyathlpy04p" path.bptc="res://.godot/imported/rock023_nrm_rgh.png-dd524905a15817dac8149681cddc8975.bptc.ctex" path.astc="res://.godot/imported/rock023_nrm_rgh.png-dd524905a15817dac8149681cddc8975.astc.ctex" metadata={ "imported_formats": ["s3tc_bptc", "etc2_astc"], "vram_texture": true } [deps] source_file="res://demo/assets/textures/rock023_nrm_rgh.png" dest_files=["res://.godot/imported/rock023_nrm_rgh.png-dd524905a15817dac8149681cddc8975.bptc.ctex", "res://.godot/imported/rock023_nrm_rgh.png-dd524905a15817dac8149681cddc8975.astc.ctex"] [params] compress/mode=2 compress/high_quality=true compress/lossy_quality=0.7 compress/hdr_compression=1 compress/normal_map=2 compress/channel_pack=0 mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=0 ================================================ FILE: project/demo/components/DemoBenchmark.tscn ================================================ [gd_scene load_steps=8 format=3 uid="uid://dyt8c2xqmddo2"] [ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_2gjn4"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_3obxr"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_b7ioy"] [ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="5_bwggt"] [ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="6_3r2au"] [sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_klrp5"] _shader_parameters = { "bias_distance": 512.0, "blend_sharpness": 0.87, "depth_blur": 0.0, &"flat_terrain_normals": false, &"ground_level": 0.0, "mipmap_bias": 1.0, &"region_blend": 0.25, &"shader_uniforms": null, &"shader_uniforms::general": null, &"shader_uniforms::mipmaps": null } [node name="Demo" type="Node"] script = ExtResource("1_2gjn4") [node name="UI" parent="." instance=ExtResource("2_3obxr")] [node name="World" parent="." instance=ExtResource("3_b7ioy")] [node name="Player" parent="." instance=ExtResource("5_bwggt")] transform = Transform3D(0.134125, 0, -0.990965, 0, 1, 0, 0.990965, 0, 0.134125, 216.455, 103.968, -1835.34) [node name="Terrain3D" type="Terrain3D" parent="."] data_directory = "res://demo/data" material = SubResource("Terrain3DMaterial_klrp5") assets = ExtResource("6_3r2au") collision_mask = 3 displacement_sharpness = 0.5 top_level = true metadata/_edit_lock_ = true ================================================ FILE: project/demo/components/Enemy.tscn ================================================ [gd_scene load_steps=6 format=3 uid="uid://di5fovhcyd7re"] [ext_resource type="Script" uid="uid://6j2rrp5f1gjs" path="res://demo/src/Enemy.gd" id="1_yudyn"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_lwhhq"] height = 1.5 [sub_resource type="SeparationRayShape3D" id="SeparationRayShape3D_i8f01"] [sub_resource type="CapsuleMesh" id="CapsuleMesh_lsqiy"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_d4cor"] albedo_color = Color(1, 0, 0, 1) [node name="Enemy" type="CharacterBody3D"] collision_layer = 2 script = ExtResource("1_yudyn") [node name="CollisionShapeBody" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0) shape = SubResource("CapsuleShape3D_lwhhq") [node name="CollisionShapeRay" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 1, 0) shape = SubResource("SeparationRayShape3D_i8f01") [node name="Body" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) mesh = SubResource("CapsuleMesh_lsqiy") surface_material_override/0 = SubResource("StandardMaterial3D_d4cor") [node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] path_desired_distance = 2.0 debug_enabled = true ================================================ FILE: project/demo/components/Environment.tscn ================================================ [gd_scene load_steps=5 format=3 uid="uid://bb2lp50sjndus"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_fhs52"] sky_top_color = Color(0.192157, 0.282353, 0.509804, 1) sky_horizon_color = Color(0.505882, 0.615686, 0.709804, 1) ground_bottom_color = Color(0.211765, 0.313726, 0.552941, 1) ground_horizon_color = Color(0.505882, 0.615686, 0.709804, 1) ground_curve = 0.13 sun_angle_max = 15.0 [sub_resource type="Sky" id="Sky_jkl3n"] sky_material = SubResource("ProceduralSkyMaterial_fhs52") [sub_resource type="Environment" id="Environment_877j2"] background_mode = 2 sky = SubResource("Sky_jkl3n") ambient_light_source = 3 ambient_light_color = Color(0.55, 0.55, 0.55, 1) ambient_light_sky_contribution = 0.7 tonemap_mode = 3 [sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_0gcl0"] [node name="Environment" type="Node3D"] [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_877j2") camera_attributes = SubResource("CameraAttributesPractical_0gcl0") [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] transform = Transform3D(1.0000001, 0, 0, 0, 0.6414489, 0.7671647, 0, -0.76716435, 0.6414493, 0, 0, 0) shadow_enabled = true shadow_blur = 2.0 directional_shadow_blend_splits = true directional_shadow_max_distance = 1024.0 ================================================ FILE: project/demo/components/Player.tscn ================================================ [gd_scene load_steps=8 format=3 uid="uid://domhm87hbhbg1"] [ext_resource type="Script" uid="uid://dajlr3n5wjwmb" path="res://demo/src/Player.gd" id="1_nm1yx"] [ext_resource type="Script" uid="uid://b62ppvc03a6b1" path="res://demo/src/CameraManager.gd" id="2_loos7"] [sub_resource type="SphereShape3D" id="SphereShape3D_smq6u"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_lwhhq"] height = 1.5 [sub_resource type="SeparationRayShape3D" id="SeparationRayShape3D_twc2s"] [sub_resource type="CapsuleMesh" id="CapsuleMesh_lsqiy"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mox7b"] [node name="Player" type="CharacterBody3D"] collision_layer = 2 script = ExtResource("1_nm1yx") [node name="CameraManager" type="Node3D" parent="."] script = ExtResource("2_loos7") [node name="Arm" type="SpringArm3D" parent="CameraManager"] unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 0.906308, 0.422618, 0, -0.422618, 0.906308, 0, 2.32515, -0.0321627) shape = SubResource("SphereShape3D_smq6u") spring_length = 6.0 margin = 0.5 [node name="Camera3D" type="Camera3D" parent="CameraManager/Arm"] unique_name_in_owner = true near = 0.25 far = 16384.0 [node name="CollisionShapeBody" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0) shape = SubResource("CapsuleShape3D_lwhhq") [node name="CollisionShapeRay" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 1, 0) shape = SubResource("SeparationRayShape3D_twc2s") [node name="Body" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) mesh = SubResource("CapsuleMesh_lsqiy") surface_material_override/0 = SubResource("StandardMaterial3D_mox7b") ================================================ FILE: project/demo/components/Tunnel.tscn ================================================ [gd_scene load_steps=10 format=3 uid="uid://djhl3foqkj4e2"] [ext_resource type="PackedScene" uid="uid://be6nrf0b8j4l0" path="res://demo/assets/models/RockA.tscn" id="1_m1xck"] [ext_resource type="PackedScene" uid="uid://bwvtgwartxt0g" path="res://demo/assets/models/RockB.tscn" id="2_hybky"] [ext_resource type="PackedScene" uid="uid://lsvs8a7urkca" path="res://demo/assets/models/RockC.tscn" id="3_nbn1a"] [ext_resource type="PackedScene" uid="uid://vvayjv3rbx1d" path="res://demo/assets/models/Tunnel.tscn" id="4_klbpo"] [ext_resource type="PackedScene" uid="uid://cribhhvg03u8g" path="res://demo/assets/models/CrystalC.tscn" id="5_bb2w0"] [ext_resource type="Material" uid="uid://cso4f2iyuxpmc" path="res://demo/assets/materials/M_crystal_purple.tres" id="6_s6twx"] [ext_resource type="Material" uid="uid://ickkffutwcvo" path="res://demo/assets/materials/M_crystal_red.tres" id="7_7fkm2"] [ext_resource type="Script" uid="uid://c444j1ucmv5ti" path="res://demo/src/CaveEntrance.gd" id="9_fn2ke"] [sub_resource type="BoxShape3D" id="BoxShape3D_goiy4"] size = Vector3(530.482, 38.6343, 235.603) [node name="Tunnel" type="Node3D"] [node name="EntranceL" type="Node3D" parent="."] [node name="RockA1" parent="EntranceL" instance=ExtResource("1_m1xck")] transform = Transform3D(5.82988, -5.4374, -4.00004, 6.04396, 6.55839, -0.106264, 3.00604, -2.64109, 7.9713, 807.937, 137.298, 467.411) [node name="RockA2" parent="EntranceL" instance=ExtResource("1_m1xck")] transform = Transform3D(4.67905, 4.22076, -5.5926, 6.98724, -3.31022, 3.34764, -0.52024, -6.49718, -5.3387, 766.019, 104.95, 452.605) [node name="RockA3" parent="EntranceL" instance=ExtResource("1_m1xck")] transform = Transform3D(1.19782, 11.0889, 4.42738, -11.9291, 0.921057, 0.9205, 0.510788, -4.49312, 11.1153, 793.166, 76.9378, 454.834) [node name="RockB1" parent="EntranceL" instance=ExtResource("2_hybky")] transform = Transform3D(-2.10522, -4.25988, 3.15298, 3.05166, -3.74811, -3.02637, 4.33304, 0.570028, 3.66329, 824.574, 112.54, 446.946) [node name="RockB3" parent="EntranceL" instance=ExtResource("2_hybky")] transform = Transform3D(4.29222, 4.57142, 1.24961, -2.49948, 3.61605, -4.6432, -4.02641, 2.62846, 4.21447, 773.937, 140.911, 469.972) [node name="RockC1" parent="EntranceL" instance=ExtResource("3_nbn1a")] transform = Transform3D(-1.86839, -3.85072, -1.99813, -0.0474326, -2.1573, 4.20181, -4.33801, 1.6821, 0.814656, 786.081, 135.376, 453.531) [node name="RockC2" parent="EntranceL" instance=ExtResource("3_nbn1a")] transform = Transform3D(-3.90856, -4.56077, 3.35866, 3.81036, 0.902037, 5.65911, -4.19074, 5.07383, 2.01294, 791.859, 164.408, 508.107) [node name="EntranceR" type="Node3D" parent="."] [node name="RockA1" parent="EntranceR" instance=ExtResource("1_m1xck")] transform = Transform3D(-0.271255, 1.28607, -2.97471, -1.25761, 2.70942, 1.28605, 2.98685, 1.25759, 0.271335, 299.644, 127.203, 424.309) [node name="RockA2" parent="EntranceR" instance=ExtResource("1_m1xck")] transform = Transform3D(2.29164, -2.44454, 3.0731, 3.84134, 0.657286, -2.34167, 0.814771, 3.77671, 2.39666, 318.179, 95.8599, 421.731) [node name="RockA5" parent="EntranceR" instance=ExtResource("1_m1xck")] transform = Transform3D(1.09929, -4.37397, 0.575562, 3.84134, 0.657286, -2.34167, 2.16957, 1.05247, 3.85443, 321.814, 95.8599, 436.931) [node name="RockA3" parent="EntranceR" instance=ExtResource("1_m1xck")] transform = Transform3D(3.70822, 1.86229, 1.85804, 2.19079, -0.408409, -3.96295, -1.45634, 4.12752, -1.23046, 338.177, 116.66, 430.367) [node name="RockA4" parent="EntranceR" instance=ExtResource("1_m1xck")] transform = Transform3D(1.9779, 0.270409, 4.08487, -4.00125, 1.0869, 1.86547, -0.865577, -4.40646, 0.710812, 320.977, 134.583, 441.012) [node name="RockB1" parent="EntranceR" instance=ExtResource("2_hybky")] transform = Transform3D(-1.64956, -1.63286, 1.75857, 0.196316, 2.03499, 2.07367, -2.39171, 1.29322, -1.04267, 295.848, 109.828, 419.45) [node name="RockB3" parent="EntranceR" instance=ExtResource("2_hybky")] transform = Transform3D(-2.03718, 1.29729, 2.88404, -2.82928, 0.784888, -2.35156, -1.41272, -3.44263, 0.550663, 306.288, 128.712, 434.042) [node name="RockB4" parent="EntranceR" instance=ExtResource("2_hybky")] transform = Transform3D(1.97436, 2.87133, -1.41708, -2.82928, 0.784888, -2.35156, -1.49926, 2.30004, 2.57153, 328.686, 129.709, 427.63) [node name="RockB2" parent="EntranceR" instance=ExtResource("2_hybky")] transform = Transform3D(-2.17746, 0.476253, -1.87396, -1.61495, 1.104, 2.15708, 1.06324, 2.65221, -0.561397, 303.461, 107.797, 428.826) [node name="RockC1" parent="EntranceR" instance=ExtResource("3_nbn1a")] transform = Transform3D(1.74949, 1.15588, 0.576742, -1.01213, 1.82987, -0.597152, -0.802676, 0.21197, 2.01002, 335.27, 110.419, 418.362) [node name="RockC2" parent="EntranceR" instance=ExtResource("3_nbn1a")] transform = Transform3D(4.31956, 5.88829, -1.29353, -3.67086, 1.30662, -6.31041, -4.78226, 4.31561, 3.67549, 345.465, 146.581, 480.747) [node name="TunnelMesh" parent="." instance=ExtResource("4_klbpo")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 546.733, 112.043, 468.686) [node name="CrystalGroup1" type="Node3D" parent="."] transform = Transform3D(0.567756, 0.0788585, -0.00345403, 0.0729824, -0.514888, 0.241124, 0.0300693, -0.239266, -0.520024, 415.722, 122.719, 611.385) [node name="CrystalC" parent="CrystalGroup1" instance=ExtResource("5_bb2w0")] transform = Transform3D(0.990472, -0.134216, 0.0307963, 0.132893, 0.873058, -0.469155, 0.0360812, 0.468779, 0.882577, 0.050354, 1.72128, 4.54956) [node name="CrystalC3" parent="CrystalGroup1" instance=ExtResource("5_bb2w0")] transform = Transform3D(0.999999, 2.98023e-08, 0, 1.40402e-08, 0.999999, 1.19209e-07, 6.89635e-09, -1.49012e-07, 0.999999, 4.00897, 3.69431, -0.617554) [node name="CrystalC2" parent="CrystalGroup1" instance=ExtResource("5_bb2w0")] transform = Transform3D(-0.513116, -0.374576, 0.95887, -0.130632, 1.08641, 0.354495, -1.02111, 0.0492404, -0.527189, -1.70374, 4.03528, -0.568237) [node name="OmniLight3D" type="OmniLight3D" parent="CrystalGroup1"] transform = Transform3D(1, 3.21278e-08, -1.96807e-08, -3.21278e-08, 1, 1.02499e-07, 1.96807e-08, -1.02499e-07, 1, 1.11737, 4.79865, 1.8949) light_color = Color(0.4, 0.760784, 1, 1) light_energy = 16.0 shadow_enabled = true omni_range = 83.054 [node name="CrystalGroup2" type="Node3D" parent="."] transform = Transform3D(-0.536793, -0.0213862, -0.199931, -0.116106, 0.498308, 0.25843, 0.164163, 0.282504, -0.470976, 553.318, 103.571, 647.425) [node name="CrystalC" parent="CrystalGroup2" instance=ExtResource("5_bb2w0")] transform = Transform3D(0.704558, -0.579782, 0.130258, 0.554941, 0.56972, -0.465804, 0.212495, 0.434496, 0.784585, 0.815979, 1.24854, 2.5437) [node name="Rock3" parent="CrystalGroup2/CrystalC" index="0"] surface_material_override/0 = ExtResource("6_s6twx") [node name="CrystalC3" parent="CrystalGroup2" instance=ExtResource("5_bb2w0")] transform = Transform3D(0.127208, -0.0568556, -1.06836, -0.317718, 1.02534, -0.0923969, 1.02161, 0.32596, 0.104294, 4.00861, 3.69458, -0.617188) [node name="Rock3" parent="CrystalGroup2/CrystalC3" index="0"] surface_material_override/0 = ExtResource("6_s6twx") [node name="CrystalC2" parent="CrystalGroup2" instance=ExtResource("5_bb2w0")] transform = Transform3D(-0.642172, -0.213226, 0.774009, 0.484154, 0.687794, 0.591163, -0.640428, 0.733767, -0.329203, 7.28876, 1.71277, 4.52783) [node name="Rock3" parent="CrystalGroup2/CrystalC2" index="0"] surface_material_override/0 = ExtResource("6_s6twx") [node name="OmniLight3D" type="OmniLight3D" parent="CrystalGroup2"] transform = Transform3D(1.74454, 2.98023e-08, -2.98023e-08, -5.60482e-08, 1.74454, 1.49012e-07, 3.43338e-08, -1.78814e-07, 1.74454, 1.11737, 4.79865, 1.8949) light_color = Color(0.396078, 0.333333, 0.878431, 1) light_energy = 16.0 shadow_enabled = true omni_range = 83.054 [node name="CrystalGroup3" type="Node3D" parent="."] transform = Transform3D(-0.536793, -0.0213862, -0.199931, 0.0789312, -0.546626, -0.153451, -0.184932, -0.17123, 0.514837, 711.291, 124.136, 579.252) [node name="CrystalC" parent="CrystalGroup3" instance=ExtResource("5_bb2w0")] transform = Transform3D(0.898105, -0.188271, 0.0864646, 0.196692, 0.654036, -0.618921, 0.0650697, 0.621535, 0.677476, 0.589844, 2.22089, 3.04785) [node name="Rock3" parent="CrystalGroup3/CrystalC" index="0"] surface_material_override/0 = ExtResource("7_7fkm2") [node name="CrystalC3" parent="CrystalGroup3" instance=ExtResource("5_bb2w0")] transform = Transform3D(0.15544, 0.0752453, -1.06348, -0.370935, 1.01139, 0.0173435, 0.999527, 0.363637, 0.171821, 4.00867, 3.69458, -0.617432) [node name="Rock3" parent="CrystalGroup3/CrystalC3" index="0"] surface_material_override/0 = ExtResource("7_7fkm2") [node name="CrystalC2" parent="CrystalGroup3" instance=ExtResource("5_bb2w0")] transform = Transform3D(-0.573134, -0.100696, 0.847536, 0.285048, 0.939695, 0.304405, -0.804489, 0.404691, -0.495942, 7.2887, 1.71283, 4.52771) [node name="Rock3" parent="CrystalGroup3/CrystalC2" index="0"] surface_material_override/0 = ExtResource("7_7fkm2") [node name="OmniLight3D" type="OmniLight3D" parent="CrystalGroup3"] transform = Transform3D(1.74454, 2.98023e-08, -2.98023e-08, -5.60482e-08, 1.74454, 1.49012e-07, 3.43338e-08, -1.78814e-07, 1.74454, 1.11737, 4.79865, 1.8949) light_color = Color(0.388235, 0.101961, 0.2, 1) light_energy = 16.0 shadow_enabled = true omni_range = 83.054 [node name="CaveArea3D" type="Area3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 549.961, 110.763, 526.656) collision_mask = 2 script = ExtResource("9_fn2ke") [node name="CollisionShape3D" type="CollisionShape3D" parent="CaveArea3D"] transform = Transform3D(0.998441, 0, -0.0558215, 0, 1, 0, 0.0558215, 0, 0.998441, -8.36676, 3.78637, 30.7709) shape = SubResource("BoxShape3D_goiy4") [editable path="CrystalGroup2/CrystalC"] [editable path="CrystalGroup2/CrystalC3"] [editable path="CrystalGroup2/CrystalC2"] [editable path="CrystalGroup3/CrystalC"] [editable path="CrystalGroup3/CrystalC3"] [editable path="CrystalGroup3/CrystalC2"] ================================================ FILE: project/demo/components/UI.tscn ================================================ [gd_scene load_steps=2 format=3 uid="uid://d2jihfohphuue"] [ext_resource type="Script" uid="uid://dne6na1m4xku8" path="res://demo/src/UI.gd" id="1_why5e"] [node name="UI" type="Control"] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_why5e") [node name="Label" type="Label" parent="."] unique_name_in_owner = true layout_mode = 1 offset_left = 5.0 offset_top = 5.0 offset_right = 275.0 offset_bottom = 340.0 theme_override_colors/font_shadow_color = Color(0, 0, 0, 0.662745) theme_override_constants/shadow_offset_x = 1 theme_override_constants/shadow_offset_y = 1 text = "FPS: 100 Position: (100, 100, 100) Move Speed: 10 Player Move: WASDEQ,Space,Mouse Move speed: Wheel,+/-,Shift Camera view: V Gravity toggle: G Collision toggle: C Window Quit: F8 UI toggle: F9 Render mode: F10 Full screen: F11 Mouse toggle: Escape " [node name="Panel" type="Panel" parent="Label"] modulate = Color(1, 1, 1, 0.392157) show_behind_parent = true layout_mode = 0 offset_left = -5.0 offset_top = -5.0 offset_right = 248.0 offset_bottom = 444.0 [node name="HSeparator" type="HSeparator" parent="Label/Panel"] top_level = true layout_mode = 0 offset_left = 6.0 offset_top = 129.0 offset_right = 246.0 offset_bottom = 138.0 [node name="HSeparator2" type="HSeparator" parent="Label/Panel"] top_level = true layout_mode = 0 offset_left = 6.0 offset_top = 310.0 offset_right = 246.0 offset_bottom = 319.0 ================================================ FILE: project/demo/csharp/CodeGenerated.cs ================================================ using Godot; using System; using System.Threading.Tasks; using TokisanGames; using System.Linq; public partial class CodeGenerated : Node { public Terrain3D terrain; public override void _Ready() { var ui = GetNode("UI"); var player = GetNode("Player"); ui.Set("player", player); if (HasNode("RunThisSceneLabel3D")) { GetNode("RunThisSceneLabel3D").QueueFree(); } _ = CreateTerrainAsync(); } private async Task CreateTerrainAsync() { await CreateTerrain(); var baker = GetNode("RuntimeNavigationBaker"); baker.Set("terrain", terrain); baker.Set("enabled", true); } private async Task CreateTerrain() { var greenGr = new Gradient(); greenGr.SetColor(0, Color.FromHsv(100f / 360f, 0.35f, 0.3f)); greenGr.SetColor(1, Color.FromHsv(120f / 360f, 0.4f, 0.37f)); var greenTa = await CreateTextureAsset("Grass", greenGr, 1024); greenTa.UvScale = 0.02f; var brownGr = new Gradient(); brownGr.SetColor(0, Color.FromHsv(30f / 360f, 0.4f, 0.3f)); brownGr.SetColor(1, Color.FromHsv(30f / 360f, 0.4f, 0.4f)); var brownTa = await CreateTextureAsset("Dirt", brownGr, 1024); brownTa.UvScale = 0.03f; var grassMa = CreateMeshAsset("Grass", Color.FromHsv(120f / 360f, 0.4f, 0.37f)); terrain = Terrain3D.Instantiate(); terrain.Name = "Terrain3D"; // Optionally log to the console. Use the console version of Godot. See Troubleshooting doc. //terrain.DebugLevel = Variant.From(Terrain3D.DebugLevelEnum.Debug); AddChild(terrain, true); terrain.Owner = GetTree().GetCurrentScene(); var material = terrain.Material; material.WorldBackground = Terrain3DMaterial.WorldBackgroundEnum.None; material.AutoShaderEnabled = true; material.SetShaderParam("auto_slope", 10f); material.SetShaderParam("blend_sharpness", 0.975f); var assets = terrain.Assets; assets.SetTextureAsset(0, greenTa); assets.SetTextureAsset(1, brownTa); assets.SetMeshAsset(0, grassMa); var noise = new FastNoiseLite { Frequency = 0.0005f }; var img = Image.CreateEmpty(2048, 2048, false, Image.Format.Rf); for (int x = 0; x < img.GetWidth(); x++) { for (int y = 0; y < img.GetHeight(); y++) { img.SetPixel(x, y, new Color(noise.GetNoise2D(x, y), 0f, 0f, 1f)); } } terrain.RegionSize = 1024; var data = terrain.Data; var images = new Godot.Collections.Array { img, new Variant(), new Variant() }; data.ImportImages(images, new Vector3(-1024, 0, -1024), 0.0, 150.0); // Setup Instancer var instancer = terrain.Instancer; var xforms = new Godot.Collections.Array(); int width = 100; int step = 2; for (int x = 0; x < width; x += step) { for (int z = 0; z < width; z += step) { var pos = new Vector3(x, 0, z) - new Vector3(width, 0, width) * 0.5f; pos.Y = (float)data.GetHeight(pos); xforms.Add(new Transform3D(Basis.Identity, pos)); } } instancer.AddTransforms(0, xforms); } private async Task CreateTextureAsset(string assetName, Gradient gradient, int textureSize = 512) { var fnl = new FastNoiseLite { Frequency = 0.004f }; var albNoiseTex = new NoiseTexture2D { Width = textureSize, Height = textureSize, Seamless = true, Noise = fnl, ColorRamp = gradient }; await ToSignal(albNoiseTex, NoiseTexture2D.SignalName.Changed); var albNoiseImg = albNoiseTex.GetImage(); for (int x = 0; x < albNoiseImg.GetWidth(); x++) { for (int y = 0; y < albNoiseImg.GetHeight(); y++) { var clr = albNoiseImg.GetPixel(x, y); clr.A = clr.V; albNoiseImg.SetPixel(x, y, clr); } } albNoiseImg.GenerateMipmaps(); var albedo = ImageTexture.CreateFromImage(albNoiseImg); var nrmNoiseTex = new NoiseTexture2D { Width = textureSize, Height = textureSize, AsNormalMap = true, Seamless = true, Noise = fnl }; await ToSignal(nrmNoiseTex, NoiseTexture2D.SignalName.Changed); var nrmNoiseImg = nrmNoiseTex.GetImage(); for (int x = 0; x < nrmNoiseImg.GetWidth(); x++) { for (int y = 0; y < nrmNoiseImg.GetHeight(); y++) { var normalRgh = nrmNoiseImg.GetPixel(x, y); normalRgh.A = 0.8f; nrmNoiseImg.SetPixel(x, y, normalRgh); } } nrmNoiseImg.GenerateMipmaps(); var normal = ImageTexture.CreateFromImage(nrmNoiseImg); var ta = Terrain3DTextureAsset.Instantiate(); ta.Name = assetName; ta.AlbedoTexture = albedo; ta.NormalTexture = normal; return ta; } private Terrain3DMeshAsset CreateMeshAsset(string assetName, Color color) { var ma = Terrain3DMeshAsset.Instantiate(); ma.Name = assetName; ma.GeneratedType = Variant.From(Terrain3DMeshAsset.GenType.TextureCard); ma.HeightOffset = 0.5f; ma.Lod0Range = 128.0f; (ma.MaterialOverride as StandardMaterial3D).AlbedoColor = color; return ma; } } ================================================ FILE: project/demo/csharp/CodeGenerated.cs.uid ================================================ uid://3cpwhtusnhk4 ================================================ FILE: project/demo/csharp/CodeGeneratedCSDemo.tscn ================================================ [gd_scene load_steps=3 format=3 uid="uid://w4eisl2c6xal"] [ext_resource type="PackedScene" uid="uid://cofnhdcclon1w" path="res://demo/CodeGeneratedDemo.tscn" id="1_qdypl"] [ext_resource type="Script" uid="uid://3cpwhtusnhk4" path="res://demo/csharp/CodeGenerated.cs" id="2_qdypl"] [node name="CodeGenerated" instance=ExtResource("1_qdypl")] script = ExtResource("2_qdypl") ================================================ FILE: project/demo/data/M_terrain.tres ================================================ [gd_resource type="Terrain3DMaterial" load_steps=4 format=3 uid="uid://ccvn15t8km0ft"] [sub_resource type="Gradient" id="Gradient_vr1m7"] offsets = PackedFloat32Array(0.2, 1) colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1) [sub_resource type="FastNoiseLite" id="FastNoiseLite_d8lcj"] noise_type = 2 frequency = 0.03 cellular_jitter = 3.0 cellular_return_type = 0 domain_warp_enabled = true domain_warp_type = 1 domain_warp_amplitude = 50.0 domain_warp_fractal_type = 2 domain_warp_fractal_lacunarity = 1.5 domain_warp_fractal_gain = 1.0 [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_bov7h"] noise = SubResource("FastNoiseLite_d8lcj") color_ramp = SubResource("Gradient_vr1m7") seamless = true [resource] _shader_parameters = { "_mouse_layer": 2147483648, "auto_base_texture": 0, "auto_height_reduction": 0.05, "auto_overlay_texture": 1, "auto_slope": 0.45, "bias_distance": 512.0, "blend_sharpness": 0.5, "depth_blur": 0.0, "dual_scale_far": 170.0, "dual_scale_near": 100.0, "dual_scale_reduction": 0.3, "dual_scale_texture": 0, &"flat_terrain_normals": false, &"ground_level": -618.9519819002201, "macro_variation1": Color(0.855, 0.8625, 0.9, 1), "macro_variation2": Color(0.9, 0.885, 0.81, 1), "macro_variation_slope": 0.333, "mipmap_bias": 1.0, "noise1_angle": 0.1, "noise1_offset": Vector2(0.5, 0.5), "noise1_scale": 0.04, "noise2_scale": 0.076, "noise_texture": SubResource("NoiseTexture2D_bov7h"), &"region_blend": 0.95600004541, &"shader_uniforms": null, &"shader_uniforms::auto_shader": null, &"shader_uniforms::displacement": null, &"shader_uniforms::dual_scaling": null, &"shader_uniforms::general": null, &"shader_uniforms::macro_variation": null, &"shader_uniforms::mipmaps": null, &"shader_uniforms::world_background_noise": null, "tri_scale_reduction": 0.3, &"world_noise_fragment_normals": false, &"world_noise_height": 75.00001601878989, &"world_noise_lod_distance": 7500.0, &"world_noise_max_octaves": 4, &"world_noise_min_octaves": 2, &"world_noise_offset": Vector3(0, 0, 0), &"world_noise_scale": 5.0 } world_background = 2 auto_shader_enabled = true dual_scaling_enabled = true macro_variation_enabled = true projection_enabled = true displacement_sharpness = 0.25 ================================================ FILE: project/demo/data/assets.tres ================================================ [gd_resource type="Terrain3DAssets" load_steps=11 format=3 uid="uid://dal3jhw6241qg"] [ext_resource type="PackedScene" uid="uid://bn5nf4esciwex" path="res://demo/assets/models/LOD5Example.tscn" id="1_4jrdu"] [ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="2_pog6b"] [ext_resource type="Texture2D" uid="uid://ddprscrpsofah" path="res://demo/assets/textures/ground037_alb_ht.png" id="3_g8f2m"] [ext_resource type="Texture2D" uid="uid://dabyathlpy04p" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="3_wncaf"] [ext_resource type="Texture2D" uid="uid://g80pbqtklcws" path="res://demo/assets/textures/ground037_nrm_rgh.png" id="4_aw5y1"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b2vqk"] transparency = 4 cull_mode = 2 vertex_color_use_as_albedo = true backlight_enabled = true backlight = Color(0.5, 0.5, 0.5, 1) distance_fade_mode = 1 distance_fade_min_distance = 128.0 distance_fade_max_distance = 96.0 [sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_2qf8x"] name = "TextureCard" generated_type = 1 height_offset = 0.5 density = 10.0 material_override = SubResource("StandardMaterial3D_b2vqk") last_lod = 0 last_shadow_lod = 0 lod0_range = 128.0 [sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_y3ibi"] name = "LODExample" id = 1 scene_file = ExtResource("1_4jrdu") height_offset = 0.5 density = 10.0 last_lod = 4 last_shadow_lod = 4 lod4_range = 256.0 [sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_lha57"] name = "Cliff" albedo_texture = ExtResource("2_pog6b") normal_texture = ExtResource("3_wncaf") normal_depth = 1.5 ao_strength = 1.0 ao_light_affect = 0.8 roughness = -0.05 displacement_scale = 0.65 [sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_od0q7"] name = "Grass" id = 1 albedo_color = Color(0.67451, 0.74902, 0.686275, 1) albedo_texture = ExtResource("3_g8f2m") normal_texture = ExtResource("4_aw5y1") normal_depth = 1.4 ao_strength = 1.0 displacement_scale = 0.2 detiling_rotation = 0.161 [resource] mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_2qf8x"), SubResource("Terrain3DMeshAsset_y3ibi")]) texture_list = Array[Terrain3DTextureAsset]([SubResource("Terrain3DTextureAsset_lha57"), SubResource("Terrain3DTextureAsset_od0q7")]) ================================================ FILE: project/demo/src/CameraManager.gd ================================================ extends Node3D const CAMERA_MAX_PITCH: float = deg_to_rad(70) const CAMERA_MIN_PITCH: float = deg_to_rad(-89.9) const CAMERA_RATIO: float = .625 @export var mouse_sensitivity: float = .002 @export var mouse_y_inversion: float = -1.0 @onready var _camera_yaw: Node3D = self @onready var _camera_pitch: Node3D = %Arm func _ready() -> void: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _input(p_event: InputEvent) -> void: if p_event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: rotate_camera(p_event.relative) get_viewport().set_input_as_handled() return func rotate_camera(p_relative:Vector2) -> void: _camera_yaw.rotation.y -= p_relative.x * mouse_sensitivity _camera_yaw.orthonormalize() _camera_pitch.rotation.x += p_relative.y * mouse_sensitivity * CAMERA_RATIO * mouse_y_inversion _camera_pitch.rotation.x = clamp(_camera_pitch.rotation.x, CAMERA_MIN_PITCH, CAMERA_MAX_PITCH) ================================================ FILE: project/demo/src/CameraManager.gd.uid ================================================ uid://b62ppvc03a6b1 ================================================ FILE: project/demo/src/CaveEntrance.gd ================================================ extends Area3D func _ready() -> void: body_entered.connect(_on_body_entered) body_exited.connect(_on_body_exited) func _on_body_entered(body: Node3D) -> void: if body.name == "Player": var env: WorldEnvironment = get_node_or_null("../../Environment/WorldEnvironment") if env: var tween: Tween = get_tree().create_tween() tween.tween_property(env.environment, "ambient_light_energy", .1, .33) func _on_body_exited(body: Node3D) -> void: if body.name == "Player": var env: WorldEnvironment = get_node_or_null("../../Environment/WorldEnvironment") if env: var tween: Tween = get_tree().create_tween() tween.tween_property(env.environment, "ambient_light_energy", 1., .33) ================================================ FILE: project/demo/src/CaveEntrance.gd.uid ================================================ uid://c444j1ucmv5ti ================================================ FILE: project/demo/src/CodeGenerated.gd ================================================ extends Node var terrain: Terrain3D func _ready() -> void: $UI.player = $Player if has_node("RunThisSceneLabel3D"): $RunThisSceneLabel3D.queue_free() terrain = await create_terrain() # Enable runtime navigation baking using the terrain # Enable `Debug/Visible Navigation` if you wish to see it $RuntimeNavigationBaker.terrain = terrain $RuntimeNavigationBaker.enabled = true func create_terrain() -> Terrain3D: # Create textures var green_gr := Gradient.new() green_gr.set_color(0, Color.from_hsv(100./360., .35, .3)) green_gr.set_color(1, Color.from_hsv(120./360., .4, .37)) var green_ta: Terrain3DTextureAsset = await create_texture_asset("Grass", green_gr, 1024) green_ta.uv_scale = 0.02 var brown_gr := Gradient.new() brown_gr.set_color(0, Color.from_hsv(30./360., .4, .3)) brown_gr.set_color(1, Color.from_hsv(30./360., .4, .4)) var brown_ta: Terrain3DTextureAsset = await create_texture_asset("Dirt", brown_gr, 1024) brown_ta.uv_scale = 0.03 var grass_ma: Terrain3DMeshAsset = create_mesh_asset("Grass", Color.from_hsv(120./360., .4, .37)) # Create a terrain terrain = Terrain3D.new() terrain.name = "Terrain3D" # Optionally log to the console. Use the console version of Godot. See Troubleshooting doc. #terrain.debug_level = Terrain3D.DEBUG add_child(terrain, true) terrain.owner = get_tree().get_current_scene() # Set material and assets terrain.material.world_background = Terrain3DMaterial.NONE terrain.material.auto_shader_enabled = true terrain.material.set_shader_param("auto_slope", 10) terrain.material.set_shader_param("blend_sharpness", .975) terrain.assets.set_texture_asset(0, green_ta) terrain.assets.set_texture_asset(1, brown_ta) terrain.assets.set_mesh_asset(0, grass_ma) # Generate height map w/ 32-bit noise and import it with scale var noise := FastNoiseLite.new() noise.frequency = 0.0005 var img: Image = Image.create_empty(2048, 2048, false, Image.FORMAT_RF) for x in img.get_width(): for y in img.get_height(): img.set_pixel(x, y, Color(noise.get_noise_2d(x, y), 0., 0., 1.)) terrain.region_size = Terrain3D.SIZE_1024 terrain.data.import_images([img, null, null], Vector3(-1024, 0, -1024), 0.0, 150.0) # Instance foliage var xforms: Array[Transform3D] var width: int = 100 var step: int = 2 for x in range(0, width, step): for z in range(0, width, step): var pos := Vector3(x, 0, z) - Vector3(width, 0, width) * .5 pos.y = terrain.data.get_height(pos) xforms.push_back(Transform3D(Basis(), pos)) terrain.instancer.add_transforms(0, xforms) # Enable the next line and `Debug/Visible Collision Shapes` to see collision #terrain.collision.mode = Terrain3DCollision.DYNAMIC_EDITOR return terrain func create_texture_asset(asset_name: String, gradient: Gradient, texture_size: int = 512) -> Terrain3DTextureAsset: # Create noise map var fnl := FastNoiseLite.new() fnl.frequency = 0.004 # Create albedo noise texture var alb_noise_tex := NoiseTexture2D.new() alb_noise_tex.width = texture_size alb_noise_tex.height = texture_size alb_noise_tex.seamless = true alb_noise_tex.noise = fnl alb_noise_tex.color_ramp = gradient await alb_noise_tex.changed var alb_noise_img: Image = alb_noise_tex.get_image() # Create albedo + height texture for x in alb_noise_img.get_width(): for y in alb_noise_img.get_height(): var clr: Color = alb_noise_img.get_pixel(x, y) clr.a = clr.v # Noise as height alb_noise_img.set_pixel(x, y, clr) alb_noise_img.generate_mipmaps() var albedo := ImageTexture.create_from_image(alb_noise_img) # Create normal + rough texture var nrm_noise_tex := NoiseTexture2D.new() nrm_noise_tex.width = texture_size nrm_noise_tex.height = texture_size nrm_noise_tex.as_normal_map = true nrm_noise_tex.seamless = true nrm_noise_tex.noise = fnl await nrm_noise_tex.changed var nrm_noise_img = nrm_noise_tex.get_image() for x in nrm_noise_img.get_width(): for y in nrm_noise_img.get_height(): var normal_rgh: Color = nrm_noise_img.get_pixel(x, y) normal_rgh.a = 0.8 # Roughness nrm_noise_img.set_pixel(x, y, normal_rgh) nrm_noise_img.generate_mipmaps() var normal := ImageTexture.create_from_image(nrm_noise_img) var ta := Terrain3DTextureAsset.new() ta.name = asset_name ta.albedo_texture = albedo ta.normal_texture = normal return ta func create_mesh_asset(asset_name: String, color: Color) -> Terrain3DMeshAsset: var ma := Terrain3DMeshAsset.new() ma.name = asset_name ma.set_generated_type(Terrain3DMeshAsset.TYPE_TEXTURE_CARD) ma.height_offset = 0.5 ma.lod0_range = 128.0 ma.material_override.albedo_color = color return ma ================================================ FILE: project/demo/src/CodeGenerated.gd.uid ================================================ uid://dakis6gu8b7nm ================================================ FILE: project/demo/src/DemoScene.gd ================================================ @tool extends Node @onready var terrain: Terrain3D = find_child("Terrain3D") func _ready(): if not Engine.is_editor_hint() and has_node("UI"): $UI.player = $Player # Load Sky3D into the demo environment if enabled if Engine.is_editor_hint() and has_node("Environment") and \ Engine.get_singleton(&"EditorInterface").is_plugin_enabled("sky_3d"): $Environment.queue_free() var sky3d = load("res://addons/sky_3d/src/Sky3D.gd").new() sky3d.name = "Sky3D" add_child(sky3d, true) move_child(sky3d, 1) sky3d.owner = self sky3d.current_time = 10 sky3d.enable_editor_time = false ================================================ FILE: project/demo/src/DemoScene.gd.uid ================================================ uid://chstoagn42gbr ================================================ FILE: project/demo/src/Enemy.gd ================================================ extends CharacterBody3D const RETARGET_COOLDOWN: float = 1.0 @export var MOVE_SPEED: float = 50.0 @export var target: Node3D @onready var nav_agent: NavigationAgent3D = $NavigationAgent3D var _retarget_timer: float = 1.0 func _ready() -> void: nav_agent.velocity_computed.connect(_on_velocity_computed) func _process(p_delta: float) -> void: _retarget_timer += p_delta if _retarget_timer > RETARGET_COOLDOWN and target: # Don't reset the target position every frame. It triggers an A* search, which is expensive. _retarget_timer = 0.0 nav_agent.set_target_position(target.global_position) func is_on_nav_mesh() -> bool: var closest_point := NavigationServer3D.map_get_closest_point(nav_agent.get_navigation_map(), global_position) return global_position.distance_squared_to(closest_point) < nav_agent.path_max_distance ** 2 func _physics_process(p_delta: float) -> void: if nav_agent.is_navigation_finished(): velocity.x = 0.0 velocity.z = 0.0 else: var next_path_position: Vector3 = nav_agent.get_next_path_position() var current_agent_position: Vector3 = global_position var velocity_xz := (next_path_position - current_agent_position).normalized() * MOVE_SPEED velocity.x = velocity_xz.x velocity.z = velocity_xz.z velocity.y -= 40 * p_delta if nav_agent.avoidance_enabled: nav_agent.set_velocity(velocity) else: _on_velocity_computed(velocity) # Ensure enemy doesn't fall through terrain when collision absent if get_parent().terrain: var height: float = get_parent().terrain.data.get_height(global_position) if not is_nan(height): global_position.y = maxf(global_position.y, height) func _on_velocity_computed(p_safe_velocity: Vector3) -> void: velocity.x = p_safe_velocity.x velocity.z = p_safe_velocity.z move_and_slide() ================================================ FILE: project/demo/src/Enemy.gd.uid ================================================ uid://6j2rrp5f1gjs ================================================ FILE: project/demo/src/Player.gd ================================================ extends CharacterBody3D @export var MOVE_SPEED: float = 50.0 @export var JUMP_SPEED: float = 2.0 @export var first_person: bool = false : set(p_value): first_person = p_value if first_person: var tween: Tween = create_tween() tween.tween_property($CameraManager/Arm, "spring_length", 0.0, .33) tween.tween_callback($Body.set_visible.bind(false)) else: $Body.visible = true create_tween().tween_property($CameraManager/Arm, "spring_length", 6.0, .33) @export var gravity_enabled: bool = true : set(p_value): gravity_enabled = p_value if not gravity_enabled: velocity.y = 0 @export var collision_enabled: bool = true : set(p_value): collision_enabled = p_value $CollisionShapeBody.disabled = ! collision_enabled $CollisionShapeRay.disabled = ! collision_enabled func _physics_process(p_delta) -> void: var direction: Vector3 = get_camera_relative_input() var h_veloc: Vector2 = Vector2(direction.x, direction.z).normalized() * MOVE_SPEED if Input.is_key_pressed(KEY_SHIFT): h_veloc *= 2 velocity.x = h_veloc.x velocity.z = h_veloc.y if gravity_enabled: velocity.y -= 40 * p_delta move_and_slide() # Allow player to walk on waves in the ocean if get_parent().terrain.ocean_enabled: position.y = max(3, position.y) # Returns the input vector relative to the camera. Forward is always the direction the camera is facing func get_camera_relative_input() -> Vector3: var input_dir: Vector3 = Vector3.ZERO if Input.is_key_pressed(KEY_A): # Left input_dir -= %Camera3D.global_transform.basis.x if Input.is_key_pressed(KEY_D): # Right input_dir += %Camera3D.global_transform.basis.x if Input.is_key_pressed(KEY_W): # Forward input_dir -= %Camera3D.global_transform.basis.z if Input.is_key_pressed(KEY_S): # Backward input_dir += %Camera3D.global_transform.basis.z if Input.is_key_pressed(KEY_E) or Input.is_key_pressed(KEY_SPACE): # Up velocity.y += JUMP_SPEED + MOVE_SPEED*.016 if Input.is_key_pressed(KEY_Q): # Down velocity.y -= JUMP_SPEED + MOVE_SPEED*.016 if Input.is_key_pressed(KEY_KP_ADD) or Input.is_key_pressed(KEY_EQUAL): MOVE_SPEED = clamp(MOVE_SPEED + .5, 5, 9999) if Input.is_key_pressed(KEY_KP_SUBTRACT) or Input.is_key_pressed(KEY_MINUS): MOVE_SPEED = clamp(MOVE_SPEED - .5, 5, 9999) return input_dir func _input(p_event: InputEvent) -> void: if p_event is InputEventMouseButton and p_event.pressed: if p_event.button_index == MOUSE_BUTTON_WHEEL_UP: MOVE_SPEED = clamp(MOVE_SPEED + 5, 5, 9999) elif p_event.button_index == MOUSE_BUTTON_WHEEL_DOWN: MOVE_SPEED = clamp(MOVE_SPEED - 5, 5, 9999) elif p_event is InputEventKey: if p_event.pressed: if p_event.keycode == KEY_V: first_person = ! first_person elif p_event.keycode == KEY_G: gravity_enabled = ! gravity_enabled elif p_event.keycode == KEY_C: collision_enabled = ! collision_enabled # Else if up/down released elif p_event.keycode in [ KEY_Q, KEY_E, KEY_SPACE ]: velocity.y = 0 ================================================ FILE: project/demo/src/Player.gd.uid ================================================ uid://dajlr3n5wjwmb ================================================ FILE: project/demo/src/RuntimeNavigationBaker.gd ================================================ extends Node signal bake_finished @export var enabled: bool = true : set = set_enabled @export var enter_cost: float = 0.0 : set = set_enter_cost @export var travel_cost: float = 1.0 : set = set_travel_cost @export_flags_3d_navigation var navigation_layers: int = 1 : set = set_navigation_layers @export var template: NavigationMesh : set = set_template @export var terrain: Terrain3D @export var player: Node3D @export var mesh_size := Vector3(256, 512, 256) @export var min_rebake_distance: float = 64.0 @export var bake_cooldown: float = 1.0 @export_group("Debug") @export var log_timing: bool = false var _scene_geometry: NavigationMeshSourceGeometryData3D var _current_center := Vector3(INF,INF,INF) var _bake_task_id: int = -1 var _bake_task_timer: float = 0.0 var _bake_cooldown_timer: float = 0.0 var _nav_region: NavigationRegion3D func _ready(): _nav_region = NavigationRegion3D.new() _nav_region.navigation_layers = navigation_layers _nav_region.enabled = enabled _nav_region.enter_cost = enter_cost _nav_region.travel_cost = travel_cost # Enabling edge connections comes with a performance penalty that causes hitches whenever # the nav mesh is updated. The navigation server has to compare each edge, and it does this on # the main thread. _nav_region.use_edge_connections = false add_child(_nav_region) _update_map_cell_size() # If you're using ProtonScatter, you will want to delay this next call until after all # your scatter nodes have finished setting up. Here, we just defer one frame so that nodes # after this one in the tree get set up first parse_scene.call_deferred() func set_enabled(p_value: bool) -> void: enabled = p_value if _nav_region: _nav_region.enabled = enabled set_process(enabled and template) func set_enter_cost(p_value: bool) -> void: enter_cost = p_value if _nav_region: _nav_region.enter_cost = enter_cost func set_travel_cost(p_value: bool) -> void: travel_cost = p_value if _nav_region: _nav_region.travel_cost = travel_cost func set_navigation_layers(p_value: int) -> void: navigation_layers = p_value if _nav_region: _nav_region.navigation_layers = navigation_layers func set_template(p_value: NavigationMesh) -> void: template = p_value set_process(enabled and template) _update_map_cell_size() func parse_scene() -> void: _scene_geometry = NavigationMeshSourceGeometryData3D.new() NavigationServer3D.parse_source_geometry_data(template, _scene_geometry, self) func _update_map_cell_size() -> void: if get_viewport() and template: var map := get_viewport().find_world_3d().navigation_map NavigationServer3D.map_set_cell_size(map, template.cell_size) NavigationServer3D.map_set_cell_height(map, template.cell_height) func _process(p_delta: float) -> void: if _bake_task_id != -1: _bake_task_timer += p_delta if not player or _bake_task_id != -1: return if _bake_cooldown_timer > 0.0: _bake_cooldown_timer -= p_delta return var track_pos := player.global_position if player is CharacterBody3D: # Center on where the player is likely _going to be_: track_pos += player.velocity * bake_cooldown if track_pos.distance_squared_to(_current_center) >= min_rebake_distance * min_rebake_distance: _current_center = track_pos _rebake(_current_center) func _rebake(p_center: Vector3) -> void: assert(template != null) _bake_task_id = WorkerThreadPool.add_task(_task_bake.bind(p_center), false, "RuntimeNavigationBaker") _bake_task_timer = 0.0 _bake_cooldown_timer = bake_cooldown func _task_bake(p_center: Vector3) -> void: var nav_mesh: NavigationMesh = template.duplicate() nav_mesh.filter_baking_aabb = AABB(-mesh_size * 0.5, mesh_size) nav_mesh.filter_baking_aabb_offset = p_center var source_geometry: NavigationMeshSourceGeometryData3D source_geometry = _scene_geometry.duplicate() if terrain: var aabb: AABB = nav_mesh.filter_baking_aabb aabb.position += nav_mesh.filter_baking_aabb_offset var faces: PackedVector3Array = terrain.generate_nav_mesh_source_geometry(aabb, false) source_geometry.add_faces(faces, Transform3D.IDENTITY) if source_geometry.has_data(): NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry) _bake_finished.call_deferred(nav_mesh) else: _bake_finished.call_deferred(null) func _bake_finished(p_nav_mesh: NavigationMesh) -> void: if log_timing: print("Navigation bake took ", _bake_task_timer, "s") _bake_task_timer = 0.0 _bake_task_id = -1 if p_nav_mesh: _nav_region.navigation_mesh = p_nav_mesh bake_finished.emit() assert(!NavigationServer3D.region_get_use_edge_connections(_nav_region.get_region_rid())) ================================================ FILE: project/demo/src/RuntimeNavigationBaker.gd.uid ================================================ uid://brh8x1wnycrl5 ================================================ FILE: project/demo/src/UI.gd ================================================ extends Control var player: Node var visible_mode: int = 1 func _init() -> void: RenderingServer.set_debug_generate_wireframes(true) func _process(_p_delta: float) -> void: $Label.text = "FPS: %d\n" % Engine.get_frames_per_second() if(visible_mode == 1): $Label.text += "Move Speed: %.1f\n" % player.MOVE_SPEED if player else "" $Label.text += "Position: %.1v\n" % player.global_position if player else "" $Label.text += """ Player Move: WASDEQ,Space,Mouse Move speed: Wheel,+/-,Shift Camera View: V Gravity toggle: G Collision toggle: C Window Quit: F8 UI toggle: F9 Render mode: F10 Full screen: F11 Mouse toggle: Escape / F12 """ func _unhandled_key_input(p_event: InputEvent) -> void: if p_event is InputEventKey and p_event.pressed: match p_event.keycode: KEY_F8: get_tree().quit() KEY_F9: visible_mode = (visible_mode + 1 ) % 3 $Label/Panel.visible = (visible_mode == 1) visible = visible_mode > 0 KEY_F10: var vp = get_viewport() vp.debug_draw = (vp.debug_draw + 1 ) % 6 get_viewport().set_input_as_handled() KEY_F11: toggle_fullscreen() get_viewport().set_input_as_handled() KEY_ESCAPE, KEY_F12: if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) else: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) get_viewport().set_input_as_handled() func toggle_fullscreen() -> void: if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN or \ DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) DisplayServer.window_set_size(Vector2(1280, 720)) else: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN) ================================================ FILE: project/demo/src/UI.gd.uid ================================================ uid://dne6na1m4xku8 ================================================ FILE: project/icon.png.import ================================================ [remap] importer="texture" type="CompressedTexture2D" uid="uid://h68cek51vakm" path.bptc="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.bptc.ctex" path.astc="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.astc.ctex" metadata={ "imported_formats": ["s3tc_bptc", "etc2_astc"], "vram_texture": true } [deps] source_file="res://icon.png" dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.bptc.ctex", "res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.astc.ctex"] [params] compress/mode=2 compress/high_quality=true compress/lossy_quality=0.7 compress/uastc_level=0 compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=2 compress/channel_pack=0 mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" process/channel_remap/red=0 process/channel_remap/green=1 process/channel_remap/blue=2 process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 ================================================ FILE: project/project.godot ================================================ ; Engine configuration file. ; It's best edited using the editor UI and not directly, ; since the parameters that go here are not all obvious. ; ; Format: ; [section] ; section goes between [] ; param=value ; assign values to parameters config_version=5 [application] config/name="Terrain3D" run/main_scene="res://demo/Demo.tscn" config/features=PackedStringArray("4.5") config/icon="res://icon.png" [display] window/vsync/vsync_mode=0 [dotnet] project/assembly_name="Terrain3D" [editor_plugins] enabled=PackedStringArray("res://addons/terrain_3d/plugin.cfg") [filesystem] import/blender/enabled=false [layer_names] 3d_physics/layer_1="Environment" 3d_physics/layer_2="Player" [rendering] textures/vram_compression/import_etc2_astc=true occlusion_culling/use_occlusion_culling=true ================================================ FILE: src/constants.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef CONSTANTS_CLASS_H #define CONSTANTS_CLASS_H #include // GDExtension uses the godot namespace, custom modules do not. #if defined(GDEXTENSION) && !defined(GODOT_MODULE) using namespace godot; #endif // Engine Shortcuts #define RS RenderingServer::get_singleton() #define PS PhysicsServer3D::get_singleton() #define IS_EDITOR Engine::get_singleton()->is_editor_hint() // Constants static const Color COLOR_NAN{ NAN, NAN, NAN, NAN }; static const Color COLOR_WHITE{ 1.0f, 1.0f, 1.0f, 1.0f }; #define COLOR_BLACK Color(0.0f, 0.0f, 0.0f, 1.0f) #define COLOR_ROUGHNESS Color(1.0f, 1.0f, 1.0f, 0.5f) #define COLOR_CHECKED Color(1.f, 1.f, 1.0f, -1.0f) #define COLOR_NORMAL Color(0.5f, 0.5f, 1.0f, 1.0f) #define COLOR_CONTROL Color(as_float(enc_auto(true)), 0.f, 0.f, 1.0f) // For consistency between MSVC, gcc, clang #ifndef FLT_MAX #define FLT_MAX __FLT_MAX__ #endif // Terrain3D::_warnings is uint8_t #define WARN_MISMATCHED_SIZE 0x01 #define WARN_MISMATCHED_FORMAT 0x02 #define WARN_MISMATCHED_MIPMAPS 0x04 #define WARN_ALL 0xFF // Global Types #define V2(x) Vector2(x, x) #define V2I(x) Vector2i(x, x) #define V3(x) Vector3(x, x, x) static const Vector2 V2_ZERO{ 0.f, 0.f }; static const Vector2 V2_MAX{ FLT_MAX, FLT_MAX }; static const Vector2i V2I_ZERO{ 0, 0 }; static const Vector2i V2I_MAX{ INT32_MAX, INT32_MAX }; static const Vector3 V3_ZERO{ 0.f, 0.f, 0.f }; static const Vector3 V3_MAX{ FLT_MAX, FLT_MAX, FLT_MAX }; static const Vector3 V3_NAN{ NAN, NAN, NAN }; static const Vector3 V3_UP{ 0.f, 1.f, 0.f }; //static const Vector3 V3_DOWN{ 0.f, -1.f, 0.f }; struct Vector2iHash { std::size_t operator()(const Vector2i &v) const { std::size_t h1 = std::hash()(v.x); std::size_t h2 = std::hash()(v.y); return h1 ^ (h2 << 1); } }; struct PairVector2iIntHash { std::size_t operator()(const std::pair &p) const { std::size_t h1 = Vector2iHash{}(p.first); std::size_t h2 = std::hash{}(p.second); return h1 ^ (h2 << 1); } }; struct Vector3Hash { std::size_t operator()(const Vector3 &v) const { std::size_t h1 = std::hash()(v.x); std::size_t h2 = std::hash()(v.y); std::size_t h3 = std::hash()(v.z); return h1 ^ (h2 << 1) ^ (h3 << 2); } }; // Set class name for logger.h #define CLASS_NAME() const String __class__ = get_class_static() + \ String("#") + String::num_uint64(get_instance_id()).right(4); #define CLASS_NAME_STATIC(p_name) static inline const char *__class__ = p_name; // Validation macros #define ASSERT(cond, ret) \ if (!(cond)) { \ UtilityFunctions::push_error("Assertion '", #cond, "' failed at ", __FILE__, ":", __LINE__); \ return ret; \ } #define VOID // a return value for void, to avoid compiler warnings #define IS_INIT(ret) \ if (!_terrain) { \ return ret; \ } #define IS_INIT_MESG(mesg, ret) \ if (!_terrain) { \ LOG(ERROR, mesg); \ return ret; \ } #define IS_INIT_COND(cond, ret) \ if (!_terrain || cond) { \ return ret; \ } #define IS_INIT_COND_MESG(cond, mesg, ret) \ if (!_terrain || cond) { \ LOG(ERROR, mesg); \ return ret; \ } #define IS_INSTANCER_INIT(ret) \ if (!_terrain || !_terrain->get_instancer()) { \ return ret; \ } #define IS_INSTANCER_INIT_MESG(mesg, ret) \ if (!_terrain || !_terrain->get_instancer()) { \ LOG(ERROR, mesg); \ return ret; \ } #define IS_DATA_INIT(ret) \ if (!_terrain || !_terrain->get_data()) { \ return ret; \ } #define IS_DATA_INIT_MESG(mesg, ret) \ if (!_terrain || !_terrain->get_data()) { \ LOG(ERROR, mesg); \ return ret; \ } #endif // CONSTANTS_CLASS_H ================================================ FILE: src/generated_texture.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include "generated_texture.h" #include "logger.h" #include "terrain_3d.h" /////////////////////////// // Public Functions /////////////////////////// void GeneratedTexture::clear() { if (_rid.is_valid()) { LOG(EXTREME, "GeneratedTexture freeing ", _rid); RS->free_rid(_rid); } if (_image.is_valid()) { LOG(EXTREME, "GeneratedTexture unref image", _image); _image.unref(); } _rid = RID(); _dirty = true; _size = 0; } RID GeneratedTexture::create(const TypedArray &p_layers) { if (!p_layers.is_empty()) { if (Terrain3D::debug_level >= DEBUG) { LOG(EXTREME, "RenderingServer creating Texture2DArray, layers size: ", p_layers.size()); for (int i = 0; i < p_layers.size(); i++) { Ref img = p_layers[i]; LOG(EXTREME, i, ": ", img, ", empty: ", img->is_empty(), ", size: ", img->get_size(), ", format: ", img->get_format()); } } _rid = RS->texture_2d_layered_create(p_layers, RenderingServer::TEXTURE_LAYERED_2D_ARRAY); _dirty = false; _size = p_layers.size(); } else { clear(); } return _rid; } void GeneratedTexture::update(const Ref &p_image, const int p_layer) { LOG(EXTREME, "RenderingServer updating Texture2DArray at index: ", p_layer); RS->texture_2d_update(_rid, p_image, p_layer); } RID GeneratedTexture::create(const Ref &p_image) { LOG(EXTREME, "RenderingServer creating Texture2D"); _image = p_image; _rid = RS->texture_2d_create(_image); _dirty = false; return _rid; } ================================================ FILE: src/generated_texture.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef GENERATEDTEXTURE_CLASS_H #define GENERATEDTEXTURE_CLASS_H #include #include "constants.h" class GeneratedTexture { CLASS_NAME_STATIC("Terrain3DGenTex"); private: RID _rid = RID(); Ref _image; bool _dirty = false; int _size = 0; public: void clear(); bool is_dirty() const { return _dirty; } RID create(const TypedArray &p_layers); void update(const Ref &p_image, const int p_layer); RID create(const Ref &p_image); Ref get_image() const { return _image; } RID get_rid() const { return _rid; } int size() const { return _size; } }; #endif // GENERATEDTEXTURE_CLASS_H ================================================ FILE: src/logger.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef LOGGER_CLASS_H #define LOGGER_CLASS_H #include #include "constants.h" #include "terrain_3d.h" /** * Prints warnings, errors, and messages to the console. * Regular messages are filtered based on the user specified debug level. * Warnings and errors always print except in release builds. * EXTREME is for continuously called prints like inside snapping. * See Terrain3D::DebugLevel and Terrain3D::debug_level. * * Note that in DEBUG mode Godot will crash on quit due to an * access violation in editor_log.cpp EditorLog::_process_message(). * This is most likely caused by us printing messages as Godot is * attempting to quit. */ #ifdef DEBUG_ENABLED #define LOG(level, ...) \ do { \ if (level == ERROR) \ UtilityFunctions::push_error(__class__, ":", __func__, ":", __LINE__, ": ", __VA_ARGS__); \ else if (level == WARN) \ UtilityFunctions::push_warning(__class__, ":", __func__, ":", __LINE__, ": ", __VA_ARGS__); \ else if (level <= Terrain3D::debug_level) \ UtilityFunctions::print(__class__, ":", __func__, ":", __LINE__, ": ", __VA_ARGS__); \ } while (false); // Macro safety #else #define LOG(...) #endif #endif // LOGGER_CLASS_H ================================================ FILE: src/register_types.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifdef GDEXTENSION #include #endif #include #include "register_types.h" #include "terrain_3d.h" #include "terrain_3d_editor.h" void initialize_terrain_3d_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); } void uninitialize_terrain_3d_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } } #ifdef GDEXTENSION extern "C" { // Initialization. GDExtensionBool GDE_EXPORT terrain_3d_init( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); init_obj.register_initializer(initialize_terrain_3d_module); init_obj.register_terminator(uninitialize_terrain_3d_module); init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SERVERS); return init_obj.init(); } } #endif /* GDEXTENSION */ ================================================ FILE: src/register_types.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_REGISTER_TYPES_H #define TERRAIN3D_REGISTER_TYPES_H #ifdef GDEXTENSION #include using namespace godot; #else #include "modules/register_module_types.h" #endif // NOTE: These have module ending for custom module build compatibility. void initialize_terrain_3d_module(ModuleInitializationLevel p_level); void uninitialize_terrain_3d_module(ModuleInitializationLevel p_level); #endif // TERRAIN3D_REGISTER_TYPES_H ================================================ FILE: src/shaders/auto_shader.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: AUTO_SHADER_UNIFORMS #define AUTO_SHADER group_uniforms shader_uniforms.auto_shader; uniform float auto_slope : hint_range(0, 10) = 1.0; uniform float auto_height_reduction : hint_range(0, 1) = 0.1; uniform int auto_base_texture : hint_range(0, 31) = 0; uniform int auto_overlay_texture : hint_range(0, 31) = 1; group_uniforms; //INSERT: AUTO_SHADER { // Auto blend calculation float auto_blend = clamp(fma(auto_slope * 2.0, (w_normal.y - 1.0), 1.0) - auto_height_reduction * 0.01 * v_vertex.y, 0.0, 1.0); // Enable Autoshader if outside regions or painted in regions, otherwise manual painted uvec4 is_auto = (control & uvec4(0x1u)) | uvec4(lessThan(ivec4(index[0].z, index[1].z, index[2].z, index[3].z), ivec4(0))); uint u_auto = ((uint(auto_base_texture) & 0x1Fu) << 27u) | ((uint(auto_overlay_texture) & 0x1Fu) << 22u) | ((uint(fma(auto_blend, 255.0 , 0.5)) & 0xFFu) << 14u); control = control * (1u - is_auto) + u_auto * is_auto; } )" ================================================ FILE: src/shaders/backgrounds.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: NONE_FUNCTIONS // Takes in world space XZ (UV) and returns if the coordinate should be part of the NONE background. bool is_none_bg(const vec2 uv) { ivec4 regions = ivec4( get_index_coord(uv - vec2(0.5, 0.0)).z, get_index_coord(uv - vec2(0.0, 0.5)).z, get_index_coord(uv + vec2(1.0, 0.0)).z, get_index_coord(uv + vec2(0.0, 1.0)).z); return any(equal(regions, ivec4(-1))); } //INSERT: NONE_CHECK || (_background_mode == 0u && is_none_bg(UV)) //INSERT: FLAT_UNIFORMS uniform float ground_level : hint_range(-1000., 1000.) = -20.0; uniform float region_blend : hint_range(.001, 1., 0.001) = 0.25; //INSERT: FLAT_FUNCTIONS // Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not. float check_region(const vec2 uv2) { ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); int layer_index = 0; if (uint(pos.x | pos.y) < uint(_region_map_size)) { layer_index = clamp(_region_map[ pos.y * _region_map_size + pos.x ] - 1, -1, 0) + 1; } return float(layer_index); } // Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions float get_region_blend(vec2 uv2) { uv2 -= 0.5011; // correct for floating point error const vec2 offset = vec2(0.0, 1.0); float a = check_region(uv2 + offset.xy); float b = check_region(uv2 + offset.yy); float c = check_region(uv2 + offset.yx); float d = check_region(uv2 + offset.xx); vec2 blend_factor = vec2(2.0 + 126.0 * (1.0 - region_blend)); vec2 f = fract(uv2); vec2 w = 1.0 / (1.0 + exp(blend_factor * log((1.0 - f) / f))); float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y); return (1.0 - blend) * 2.0; } //INSERT: FLAT_VERTEX // Apply background ground level and region blend, dont include texel offset h = mix(h, ground_level, smoothstep(0.0, 1.0, get_region_blend(UV * _region_texel_size))); //INSERT: FLAT_FRAGMENT if (_background_mode != 0u) { float blend = get_region_blend(index_id * _region_texel_size + offset * _region_texel_size); height = mix(height, ground_level, smoothstep(0., 1., blend)); } //INSERT: WORLD_NOISE_UNIFORMS group_uniforms shader_uniforms.world_background_noise; uniform bool world_noise_fragment_normals = false; uniform int world_noise_max_octaves : hint_range(0, 15) = 4; uniform int world_noise_min_octaves : hint_range(0, 15) = 2; uniform float world_noise_lod_distance : hint_range(0., 40000., 1.) = 7500.; uniform float world_noise_scale : hint_range(0.25, 20, 0.01) = 5.0; uniform float world_noise_height : hint_range(-1000., 1000., 0.1) = 32.0; uniform vec3 world_noise_offset = vec3(0.0); group_uniforms; varying vec2 world_noise_ddxy; //INSERT: WORLD_NOISE_FUNCTIONS // World Noise Functions Start // Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions float get_noise_region_blend(vec2 uv2) { uv2 -= 0.5011; // correct for floating point error const vec2 offset = vec2(0.0, 1.0); float a = check_region(uv2 + offset.xy); float b = check_region(uv2 + offset.yy); float c = check_region(uv2 + offset.yx); float d = check_region(uv2 + offset.xx); vec2 w = smoothstep(vec2(0.0), vec2(1.0), fract(uv2)); float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y); return 1.0 - blend * 2.0; } float hashf(float f) { return fract(sin(f) * 1e4); } float hashv2(vec2 v) { return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x))))); } // https://iquilezles.org/articles/morenoise/ vec3 noise2D(vec2 x) { vec2 f = fract(x); // Quintic Hermine Curve. Similar to SmoothStep() vec2 f2 = f * f, f3 = f2 * f, s = f - 1.0, s2 = s * s; vec2 u = f3 * fma(vec2(6.0), f2, fma(vec2(-15.0), f, vec2(10.0))); vec2 du = 30.0 * f2 * s2; vec2 p = floor(x); // Four corners in 2D of a tile float a = hashv2( p+vec2(0,0) ); float b = hashv2( p+vec2(1,0) ); float c = hashv2( p+vec2(0,1) ); float d = hashv2( p+vec2(1,1) ); // Mix 4 corner percentages float k0 = a; float k1 = b - a; float k2 = c - a; float k3 = d - (b + k2); return vec3(fma(k2, u.y, fma(u.x, fma(k3, u.y, k1), k0)), du * fma(vec2(k3), u.yx, vec2(k1, k2))); } float world_noise(vec2 p) { float a = 0.0; float b = 1.0; vec2 d = vec2(0.0); int octaves = int( clamp( float(world_noise_max_octaves) - floor(v_vertex_xz_dist/(world_noise_lod_distance)), float(world_noise_min_octaves), float(world_noise_max_octaves)) ); for( int i=0; i < octaves; i++ ) { vec3 n = noise2D(p); d += n.yz; a += b * n.x / (1.0 + dot(d,d)); b *= 0.5; p = mat2( vec2(0.8, -0.6), vec2(0.6, 0.8) ) * p * 2.0; } return a; } float get_noise_height(const vec2 uv) { float weight = get_noise_region_blend(uv); // Only calculate world noise when it would be visible. if (weight <= 0.5) { return 0.0; } //TODO: Offset/scale UVs are semi-dependent upon region size 1024. Base on v_vertex.xz instead float noise = world_noise((uv + world_noise_offset.xz * 1024. / _region_size) * world_noise_scale * _region_size / 1024. * .1) * world_noise_height * 10. + world_noise_offset.y * 100.; weight = smoothstep(0.5, 1.0, weight); return mix(0.0, noise, weight); } // World Noise Functions End //INSERT: WORLD_NOISE_VERTEX // World Noise if (_background_mode == 2u) { vec2 nuv_a = start_pos * _region_texel_size; vec2 nuv_b = end_pos * _region_texel_size; float nh = mix(get_noise_height(nuv_a), get_noise_height(nuv_b), vertex_lerp); float nu = mix(get_noise_height(nuv_a + vec2(_region_texel_size, 0.0)), get_noise_height(nuv_b + vec2(_region_texel_size, 0.0)), vertex_lerp); float nv = mix(get_noise_height(nuv_a + vec2(0.0, _region_texel_size)), get_noise_height(nuv_b + vec2(0.0, _region_texel_size)), vertex_lerp); world_noise_ddxy = vec2(nh - nu, nh - nv); h += nh; } //INSERT: WORLD_NOISE_FRAGMENT // World Noise if (_background_mode == 2u && world_noise_fragment_normals) { float noise_height = get_noise_height(uv2); u += noise_height - get_noise_height(uv2 + vec2(_region_texel_size, 0.0)); v += noise_height - get_noise_height(uv2 + vec2(0.0, _region_texel_size)); } if (_background_mode == 2u && !world_noise_fragment_normals) { u += world_noise_ddxy.x; v += world_noise_ddxy.y; } )" ================================================ FILE: src/shaders/debug_views.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // These special inserts are injected into the shader code at the end of fragment(). // Variables should be prefaced with __ to avoid name conflicts. R"( //INSERT: DEBUG_CHECKERED // Show a checkered grid { vec2 __p = uv * 1.0; // scale vec2 __ddx = dFdx(__p); vec2 __ddy = dFdy(__p); vec2 __w = max(abs(__ddx), abs(__ddy)) + 0.01; vec2 __i = 2.0 * (abs(fract((__p - 0.5 * __w) / 2.0) - 0.5) - abs(fract((__p + 0.5 * __w) / 2.0) - 0.5)) / __w; ALBEDO = vec3((0.5 - 0.5 * __i.x * __i.y) * 0.2 + 0.2); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_GREY // Show all grey { ALBEDO = vec3(0.2); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_HEIGHTMAP_SETUP group_uniforms shader_uniforms.debug_heightmap; uniform float heightmap_black_height: hint_range(-2048.,2048.,.5) = -100.0; uniform float heightmap_white_height: hint_range(-2048.,2048.,.5) = 300.0; group_uniforms; //INSERT: DEBUG_HEIGHTMAP // Show heightmap { float __lo = min(heightmap_black_height, heightmap_white_height); float __hi = max(heightmap_black_height, heightmap_white_height); float __factor = clamp((v_vertex.y - __lo) / max(__hi - __lo, 1e-6), 0.0, 1.0); __factor = mix(__factor, 1.0 - __factor, float(heightmap_white_height < heightmap_black_height)); ALBEDO = vec3(smoothstep(0.0, 1.0, __factor)); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_JAGGEDNESS // Show turbulent areas of the terrain surface { const vec3 __offsets = vec3(0, 1, 2); vec2 __index_id = floor((INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xz); float __h[6]; __h[0] = texelFetch(_height_maps, get_index_coord(__index_id + __offsets.xy), 0).r; __h[1] = texelFetch(_height_maps, get_index_coord(__index_id + __offsets.yy), 0).r; __h[2] = texelFetch(_height_maps, get_index_coord(__index_id + __offsets.yx), 0).r; __h[3] = texelFetch(_height_maps, get_index_coord(__index_id + __offsets.xx), 0).r; __h[4] = texelFetch(_height_maps, get_index_coord(__index_id + __offsets.zx), 0).r; __h[5] = texelFetch(_height_maps, get_index_coord(__index_id + __offsets.xz), 0).r; vec3 __normal[3]; __normal[0] = normalize(vec3(__h[0] - __h[1], _vertex_spacing, __h[0] - __h[5])); __normal[1] = normalize(vec3(__h[2] - __h[4], _vertex_spacing, __h[2] - __h[1])); __normal[2] = normalize(vec3(__h[3] - __h[2], _vertex_spacing, __h[3] - __h[0])); float __jaggedness = max(length(__normal[2] - __normal[1]),length(__normal[2] - __normal[0])); ALBEDO = vec3(0.01 + pow(__jaggedness, 8.)); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_AUTOSHADER // Show where autoshader enabled { ivec3 __ruv = get_index_coord(floor(uv)); uint __control = floatBitsToUint(texelFetch(_control_maps, __ruv, 0).r); float __autoshader = float( bool(__control & 0x1u) || __ruv.z<0 ); ALBEDO = vec3(__autoshader); ROUGHNESS = 1.; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_CONTROL_TEXTURE // Show control map texture selection { vec3 __t_colors[32]; __t_colors[0] = vec3(1.0, 0.0, 0.0); __t_colors[1] = vec3(0.0, 1.0, 0.0); __t_colors[2] = vec3(0.0, 0.0, 1.0); __t_colors[3] = vec3(1.0, 0.0, 1.0); __t_colors[4] = vec3(0.0, 1.0, 1.0); __t_colors[5] = vec3(1.0, 1.0, 0.0); __t_colors[6] = vec3(0.2, 0.0, 0.0); __t_colors[7] = vec3(0.0, 0.2, 0.0); __t_colors[8] = vec3(0.0, 0.0, 0.35); __t_colors[9] = vec3(0.2, 0.0, 0.2); __t_colors[10] = vec3(0.0, 0.2, 0.2); __t_colors[11] = vec3(0.2, 0.2, 0.0); __t_colors[12] = vec3(0.1, 0.0, 0.0); __t_colors[13] = vec3(0.0, 0.1, 0.0); __t_colors[14] = vec3(0.0, 0.0, 0.15); __t_colors[15] = vec3(0.1, 0.0, 0.1); __t_colors[16] = vec3(0.0, 0.1, 0.1); __t_colors[17] = vec3(0.1, 0.1, 0.0); __t_colors[18] = vec3(0.2, 0.05, 0.05); __t_colors[19] = vec3(0.1, 0.3, 0.1); __t_colors[20] = vec3(0.05, 0.05, 0.2); __t_colors[21] = vec3(0.1, 0.05, 0.2); __t_colors[22] = vec3(0.05, 0.15, 0.2); __t_colors[23] = vec3(0.2, 0.2, 0.1); __t_colors[24] = vec3(1.0); __t_colors[25] = vec3(0.5); __t_colors[26] = vec3(0.35); __t_colors[27] = vec3(0.25); __t_colors[28] = vec3(0.15); __t_colors[29] = vec3(0.1); __t_colors[30] = vec3(0.05); __t_colors[31] = vec3(0.0125); ivec3 __uv = get_index_coord(floor(uv)); uint __control = floatBitsToUint(texelFetch(_control_maps, __uv, 0).r); vec3 __ctrl_base = __t_colors[int(__control >>27u & 0x1Fu)]; vec3 __ctrl_over = __t_colors[int(__control >>22u & 0x1Fu)]; float __blend = float(__control >>14u & 0xFFu) * 0.003921568627450; // 1.0/255.0 float base_over = (length(fract(uv) - 0.5) < fma(__blend, 0.45, 0.1) ? 1.0 : 0.0); ALBEDO = mix(__ctrl_base, __ctrl_over, base_over); ROUGHNESS = 1.0; SPECULAR = 0.0; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_CONTROL_BLEND // Show control map blend values { ivec3 __uv = get_index_coord(floor(uv)); uint __control = floatBitsToUint(texelFetch(_control_maps, __uv, 0).r); float __ctrl_blend = float(__control >>14u & 0xFFu) * 0.003921568627450; // 1.0/255.0 float __is_auto = 0.; #ifdef AUTO_SHADER __is_auto = float( bool(__control & 0x1u) || __uv.z < 0 ); #endif ALBEDO = vec3(__ctrl_blend) * (1.0 - __is_auto) + vec3(0.2, 0.0, 0.0) * __is_auto; ROUGHNESS = 1.; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_CONTROL_ANGLE // Show control map texture angle { ivec3 __auv = get_index_coord(floor(uv)); uint __a_control = floatBitsToUint(texelFetch(_control_maps, __auv, 0)).r; uint __angle = (__a_control >>10u & 0xFu); vec3 __a_colors[16] = { vec3(1., .2, .0), vec3(.8, 0., .2), vec3(.6, .0, .4), vec3(.4, .0, .6), vec3(.2, 0., .8), vec3(.1, .1, .8), vec3(0., .2, .8), vec3(0., .4, .6), vec3(0., .6, .4), vec3(0., .8, .2), vec3(0., 1., 0.), vec3(.2, 1., 0.), vec3(.4, 1., 0.), vec3(.6, 1., 0.), vec3(.8, .6, 0.), vec3(1., .4, 0.) }; ALBEDO = __a_colors[__angle]; ROUGHNESS = 1.; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_CONTROL_SCALE // Show control map texture scale { ivec3 __suv = get_index_coord(floor(uv)); uint __s_control = floatBitsToUint(texelFetch(_control_maps, __suv, 0)).r; uint __scale = (__s_control >>7u & 0x7u); vec3 __s_colors[8] = { vec3(.5, .5, .5), vec3(.675, .25, .375), vec3(.75, .125, .25), vec3(.875, .0, .125), vec3(1., 0., 0.), vec3(0., 0., 1.), vec3(.0, .166, .833), vec3(.166, .333, .666) }; ALBEDO = __s_colors[__scale]; ROUGHNESS = 1.; SPECULAR = 0.; NORMAL_MAP = vec3(0.5 ,0.5 ,1.0); AO = 1.0; } //INSERT: DEBUG_COLORMAP // Show colormap { ALBEDO = color_map.rgb; ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_ROUGHMAP // Show roughness map { ALBEDO = vec3(color_map.a); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: DEBUG_DISPLACEMENT_BUFFER // Show displacement buffer #ifdef DISPLACEMENT { float scale = MODEL_MATRIX[0][0]; float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0)); float disp = dot(w_normal, mix(get_displacement(uv, scale), get_displacement(uv, scale * 2.0), vertex_lerp)); // Red values lower than collision, Green values above. Black if no deviation. ALBEDO = vec3(-disp, disp, 0.); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } #endif )" ================================================ FILE: src/shaders/displacement.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: DISPLACEMENT_UNIFORMS #define DISPLACEMENT uniform float _displacement_scale : hint_range(0.0, 2.0, 0.01) = 1.0; uniform highp sampler2D _displacement_buffer : repeat_disable, filter_linear; //INSERT: DISPLACEMENT_FUNCTIONS vec3 get_displacement(vec2 pos, float scale) { float s = floor(log2(1.0 / (scale * _vertex_density))); scale = pow(2.0, s) * 0.5; vec2 d_uv = (scale * pos - round(_target_pos.xz * _vertex_density * scale)) / (_mesh_size * 2.0) + 0.5; d_uv.x += s - 1.; d_uv.x /= log2(_subdiv); highp vec3 disp = vec3(0.); if (all(greaterThanEqual(d_uv, vec2(0.0))) && all(lessThanEqual(d_uv, vec2(1.0)))) { disp = textureLod(_displacement_buffer, d_uv, 0.).rgb * 2.0 - 1.0; disp *= _displacement_scale; } return disp; } //INSERT: DISPLACEMENT_VERTEX if (!(CAMERA_VISIBLE_LAYERS == _mouse_layer)) { displacement = mix(get_displacement(start_pos, scale), get_displacement(end_pos, scale * 2.0), vertex_lerp); } )" ================================================ FILE: src/shaders/displacement_buffer.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"(shader_type canvas_item; // Displacement buffer shader, mimics the main shader in 2d and outputs // xyz offset stored in RGB channels. A is unusuable when passing // the buffer directly via RID (avoids GPU > CPU > GPU copies) as alpha // is premultiplied by the renderer. #define IS_DISPLACEMENT_BUFFER // Defined Constants #define SKIP_PASS 0 #define VERTEX_PASS 1 #define FRAGMENT_PASS 2 #define COLOR_MAP_DEF vec4(1.0, 1.0, 1.0, 0.5) #define DIV_255 0.003921568627450 // 1. / 255. #define DIV_1024 0.0009765625 // 1. / 1024. #define TAU_16TH -0.392699081698724 // -TAU / 16. // Inline Functions #define DECODE_BLEND(control) float(control >>14u & 0xFFu) * DIV_255 #define DECODE_AUTO(control) bool(control & 0x1u) #define DECODE_BASE(control) int(control >>27u & 0x1Fu) #define DECODE_OVER(control) int(control >>22u & 0x1Fu) #define DECODE_ANGLE(control) float(control >>10u & 0xFu) * TAU_16TH // This math recreates the scale value directly rather than using an 8 float const array. #define DECODE_SCALE(control) (0.9 - float(((control >>7u & 0x7u) + 3u) % 8u + 1u) * 0.1) #define DECODE_HOLE(control) bool(control >>2u & 0x1u) #define TEXTURE_ID_PROJECTED(id) bool((_texture_vertical_projections >> uint(id)) & 0x1u) #if CURRENT_RENDERER == RENDERER_COMPATIBILITY #define fma(a, b, c) ((a) * (b) + (c)) #define dFdxCoarse(a) dFdx(a) #define dFdyCoarse(a) dFdy(a) #endif // Private uniforms uniform float _tessellation_level = 0.; uniform vec3 _target_pos = vec3(0.f); uniform float _mesh_size = 48.f; uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2 uniform float _vertex_spacing = 1.0; uniform float _vertex_density = 1.0; // = 1./_vertex_spacing uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1./region_size uniform int _region_map_size = 32; uniform int _region_map[1024]; uniform vec2 _region_locations[1024]; uniform float _texture_uv_scale_array[32]; uniform vec2 _texture_detile_array[32]; uniform vec2 _texture_displacement_array[32]; uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; //INSERT: TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC //INSERT: TEXTURE_SAMPLERS_LINEAR //INSERT: TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC //INSERT: TEXTURE_SAMPLERS_NEAREST uniform highp sampler2DArray _texture_array_albedo : source_color, FILTER_METHOD, repeat_enable; uniform highp sampler2DArray _texture_array_normal : hint_normal, FILTER_METHOD, repeat_enable; // Public uniforms group_uniforms shader_uniforms.general; uniform float blend_sharpness : hint_range(0, 1) = 0.5; group_uniforms; //INSERT: AUTO_SHADER_UNIFORMS //INSERT: FLAT_UNIFORMS //INSERT: FLAT_FUNCTIONS // Uniquely named displacement uniforms should be in this group. // Uniforms that are shared with the main shader are automatically synchronised. // Subgroups should work as expected. group_uniforms shader_uniforms.displacement; uniform float _displacement_sharpness : hint_range(0.0, 1.0, 0.01) = 0.25; group_uniforms; // Varyings & Types // We only care about texture height value. struct material { float height; float total_weight; }; //////////////////////// // Vertex //////////////////////// // Takes in world space XZ (UV) coordinates // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region ivec3 get_index_coord(const vec2 uv) { vec2 r_uv = round(uv); ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1; return ivec3(ivec2(mod(r_uv, _region_size)), layer_index); } // Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with: // XY: (0. to 1.) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region vec3 get_index_uv(const vec2 uv2) { ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1; return vec3(uv2 - _region_locations[layer_index], float(layer_index)); } //////////////////////// // Fragment //////////////////////// float random(in vec2 xy) { return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453); } vec2 rotate_vec2(const vec2 v, const vec2 cs) { return vec2(fma(cs.x, v.x, cs.y * v.y), fma(cs.x, v.y, -cs.y * v.x)); } // 2-4 lookups ( 2-6 with dual scaling ) void accumulate_material(const mat3 TNB, const float weight, const ivec3 index, const uint control, const vec2 texture_weight, const ivec2 texture_id, const vec3 i_normal, float h, inout material mat, const vec3 v_vertex) { // Applying scaling before projection reduces the number of multiplys ops required. vec3 i_vertex = v_vertex; // Control map scale float control_scale = DECODE_SCALE(control); i_vertex *= control_scale; h *= control_scale; // Index position for detiling. vec2 i_pos = fma(_region_locations[index.z], vec2(_region_size), vec2(index.xy)); i_pos *= _vertex_spacing * control_scale; // Projection vec2 i_uv = i_vertex.xz; mat2 p_align = mat2(1.); //INSERT: PROJECTION // Control map rotation. Must be applied seperatley from detiling to maintain UV continuity. float c_angle = DECODE_ANGLE(control); vec2 c_cs_angle = vec2(cos(c_angle), sin(c_angle)); i_uv = rotate_vec2(i_uv, c_cs_angle); i_pos = rotate_vec2(i_pos, c_cs_angle); // Blend adjustment of Higher ID from Lower ID normal map in world space. float world_normal = 1.; // mat3 multiply, reduced to 2x fma and 1x mult. #define FAST_WORLD_NORMAL(n) fma(TNB[0], vec3(n.x), fma(TNB[2], vec3(n.z), TNB[1] * vec3(n.y))) float blend = DECODE_BLEND(control); // only used for branching. float sharpness = fma(60., blend_sharpness * _displacement_sharpness, 4.); // 1st Texture Asset ID if (blend < 1.0) { int id = texture_id[0]; float id_w = texture_weight[0]; float id_scale = _texture_uv_scale_array[id]; // Detiling and Control map rotation vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5))); vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU; vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x)); // Apply UV rotation and shift around pivot. vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5; // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also // including control map rotation. avoids extra matrix op, and sin/cos calls. id_cs_angle = vec2( fma(id_cs_angle.x, c_cs_angle.x, -id_cs_angle.y * c_cs_angle.y), fma(id_cs_angle.y, c_cs_angle.x, id_cs_angle.x * c_cs_angle.y)); float h = textureLod(_texture_array_albedo, vec3(id_uv, float(id)), 0.).a; vec4 nrm = textureLod(_texture_array_normal, vec3(id_uv, float(id)), 0.); // Unpack and rotate normal map. nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0)); nrm.xz = rotate_vec2(nrm.xz, id_cs_angle) * p_align; world_normal = FAST_WORLD_NORMAL(nrm).y; float id_weight = exp2(sharpness * log2(weight + id_w + h)) * weight; // height is modified after weight calculation so that asset offset and scale values do not interfear with material blending. float height_scale = (_texture_displacement_array[id].y * 0.04) / (control_scale * id_scale); float height_offset = 0.5 + _texture_displacement_array[id].x * height_scale - 0.5 * height_scale; h = clamp(fma(h, height_scale, height_offset), 0., 1.); mat.height = fma(h, id_weight, mat.height); mat.total_weight += id_weight; } // 2nd Texture Asset ID if (blend > 0.0 && texture_id[1] != texture_id[0]) { int id = texture_id[1]; float id_w = texture_weight[1]; float id_scale = _texture_uv_scale_array[id]; // Detiling and Control map rotation vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5))); vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU; vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x)); // Apply UV rotation and shift around pivot. vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5; // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also // including control map rotation. avoids extra matrix op, and sin/cos calls. id_cs_angle = vec2( fma(id_cs_angle.x, c_cs_angle.x, -id_cs_angle.y * c_cs_angle.y), fma(id_cs_angle.y, c_cs_angle.x, id_cs_angle.x * c_cs_angle.y)); float h = textureLod(_texture_array_albedo, vec3(id_uv, float(id)), 0.).a; // Normals are not required for 2nd ID as they are not used to adjust the weights. float id_weight = exp2(sharpness * log2(weight + id_w + h * clamp(world_normal, 0., 1.))) * weight; // height is modified after weight calculation so that asset offset and scale values do not interfear with material blending. float height_scale = (_texture_displacement_array[id].y * 0.04) / (control_scale * id_scale); float height_offset = 0.5 + _texture_displacement_array[id].x * height_scale - 0.5 * height_scale; h = clamp(fma(h, height_scale, height_offset), 0., 1.); mat.height = fma(h, id_weight, mat.height); mat.total_weight += id_weight; } } float get_height(vec2 index_id, vec2 offset) { float height = texelFetch(_height_maps, get_index_coord(index_id + offset), 0).r; //INSERT: FLAT_FRAGMENT return height; } )" R"( void fragment() { // Calculate Tiled UVs float scale = floor(UV.x * (_tessellation_level)); float p_scale = pow(2.0, scale); vec2 uv = (vec2(fract(UV.x * _tessellation_level), UV.y) - 0.5) * (_mesh_size * 2.0) / p_scale; uv += round(_target_pos.xz * _vertex_density * p_scale) / p_scale; vec2 uv2 = uv * _region_texel_size; // Lookup offsets, ID and blend weight vec3 region_uv = get_index_uv(uv2); const vec3 offsets = vec3(0, 1, 2); vec2 index_id = floor(uv); vec2 weight = fract(uv); vec2 invert = 1.0 - weight; vec4 weights = vec4( invert.x * weight.y, // 0 weight.x * weight.y, // 1 weight.x * invert.y, // 2 invert.x * invert.y // 3 ); ivec3 index[4]; // control map lookups, used for some normal lookups as well index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); // Terrain normals vec3 index_normal[4]; float h[4]; // allows additional derivatives, eg world noise, brush previews etc float u = 0.0; float v = 0.0; // Re-use index[] for the first lookups, skipping some math. 3 lookups h[3] = get_height(index_id, offsets.xx); // 0 (0, 0) h[2] = get_height(index_id, offsets.yx); // 1 (1, 0) h[0] = get_height(index_id, offsets.xy); // 2 (0, 1) index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v)); // 5 lookups // Fetch the additional required height values for smooth normals h[1] = get_height(index_id, offsets.yy); // 3 (1, 1) float h_4 = get_height(index_id, offsets.yz); // 4 (1, 2) float h_5 = get_height(index_id, offsets.zy); // 5 (2, 1) float h_6 = get_height(index_id, offsets.zx); // 6 (2, 0) float h_7 = get_height(index_id, offsets.xz); // 7 (0, 2) // Calculate the normal for the remaining index ids. index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v)); index_normal[1] = normalize(vec3(h[1] - h_5 + u, _vertex_spacing, h[1] - h_4 + v)); index_normal[2] = normalize(vec3(h[2] - h_6 + u, _vertex_spacing, h[2] - h[1] + v)); // Set interpolated world normal vec3 w_normal = index_normal[0] * weights[0] + index_normal[1] * weights[1] + index_normal[2] * weights[2] + index_normal[3] * weights[3] ; vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0))); vec3 w_binormal = normalize(cross(w_normal, w_tangent)); mat3 TNB = mat3(w_tangent, w_normal, w_binormal); // We have to construct interpolted height value here, as we are operating solely in texture space. float terrain_height = h[0] * weights[0] + h[1] * weights[1] + h[2] * weights[2] + h[3] * weights[3]; vec3 v_vertex = vec3(uv.x * _vertex_spacing, terrain_height, uv.y * _vertex_spacing); // Get index control data // 1 - 4 lookups uvec4 control = uvec4( floatBitsToUint(texelFetch(_control_maps, index[0], 0).r), floatBitsToUint(texelFetch(_control_maps, index[1], 0).r), floatBitsToUint(texelFetch(_control_maps, index[2], 0).r), floatBitsToUint(texelFetch(_control_maps, index[3], 0).r)); //INSERT: AUTO_SHADER // Vectorised Deocode of all texture IDs, then swizzle to per index mapping. // Passed to accumulate_material to avoid repeated decoding. ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)), ivec4(control >> uvec4(22u) & uvec4(0x1Fu))}; ivec2 texture_ids[4] = ivec2[4]( ivec2(t_id[0].x, t_id[1].x), ivec2(t_id[0].y, t_id[1].y), ivec2(t_id[0].z, t_id[1].z), ivec2(t_id[0].w, t_id[1].w)); // uninterpolated weights. vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255; vec4 weights_id_0 = 1.0 - weights_id_1; vec2 t_weights[4] = vec2[4]( vec2(weights_id_0[0], weights_id_1[0]), vec2(weights_id_0[1], weights_id_1[1]), vec2(weights_id_0[2], weights_id_1[2]), vec2(weights_id_0[3], weights_id_1[3])); // interpolated weights t_weights = {vec2(0), vec2(0), vec2(0), vec2(0)}; weights_id_0 *= weights; weights_id_1 *= weights; for (int i = 0; i < 4; i++) { vec2 w_0 = vec2(weights_id_0[i]); vec2 w_1 = vec2(weights_id_1[i]); ivec2 id_0 = texture_ids[i].xx; ivec2 id_1 = texture_ids[i].yy; t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1))); t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1))); t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1))); t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1))); } // Struct to accumulate all texture data. material mat = material(0., 0.); accumulate_material(TNB, weights[3], index[3], control[3], t_weights[3], texture_ids[3], index_normal[3], h[3], mat, v_vertex); accumulate_material(TNB, weights[2], index[2], control[2], t_weights[2], texture_ids[2], index_normal[2], h[2], mat, v_vertex); accumulate_material(TNB, weights[1], index[1], control[1], t_weights[1], texture_ids[1], index_normal[1], h[1], mat, v_vertex); accumulate_material(TNB, weights[0], index[0], control[0], t_weights[0], texture_ids[0], index_normal[0], h[0], mat, v_vertex); // normalize accumulated values back to 0.0 - 1.0 range. float weight_inv = 1.0 / max(mat.total_weight, 1e-8); mat.height *= weight_inv; // Output COLOR.rgb = clamp(fma(w_normal * (mat.height - 0.5) * 2.0, vec3(0.5), vec3(0.5)), 0.0, 1.0); } )" ================================================ FILE: src/shaders/dual_scaling.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: DUAL_SCALING_UNIFORMS group_uniforms shader_uniforms.dual_scaling; uniform int dual_scale_texture : hint_range(0,31) = 0; uniform float dual_scale_reduction : hint_range(0.001,1) = 0.3; uniform float tri_scale_reduction : hint_range(0.001,1) = 0.3; uniform float dual_scale_far : hint_range(0,1000) = 170.0; uniform float dual_scale_near : hint_range(0,1000) = 100.0; group_uniforms; //INSERT: DUAL_SCALING // dual scaling float far_factor = clamp(smoothstep(dual_scale_near, dual_scale_far, length(v_vertex - v_camera_pos)), 0.0, 1.0); vec4 far_alb = vec4(0.); vec4 far_nrm = vec4(0.); float far_ao = 1.0; if (far_factor > 0. && any(equal(texture_id, ivec2(dual_scale_texture)))) { float far_scale = _texture_uv_scale_array[dual_scale_texture] * dual_scale_reduction; if (index.z < 0) { far_scale *= tri_scale_reduction; } vec4 far_dd = i_dd * far_scale; // Detiling and Control map rotation vec2 uv_center = floor(fma(i_pos, vec2(far_scale), vec2(0.5))); vec2 far_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[dual_scale_texture] * TAU; vec2 far_cs_angle = vec2(cos(far_detile.x), sin(far_detile.x)); // Apply UV rotation and shift around pivot. vec2 far_uv = rotate_vec2(fma(i_uv, vec2(far_scale), -uv_center), far_cs_angle) + uv_center + far_detile.y - 0.5; // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also // including control map rotation. avoids extra matrix op, and sin/cos calls. far_cs_angle = vec2( fma(far_cs_angle.x, c_cs_angle.x, -far_cs_angle.y * c_cs_angle.y), fma(far_cs_angle.y, c_cs_angle.x, far_cs_angle.x * c_cs_angle.y)); // Align derivatives for correct anisotropic filtering far_dd.xy = rotate_vec2(far_dd.xy, far_cs_angle); far_dd.zw = rotate_vec2(far_dd.zw, far_cs_angle); far_alb = textureGrad(_texture_array_albedo, vec3(far_uv, float(dual_scale_texture)), far_dd.xy, far_dd.zw); far_nrm = textureGrad(_texture_array_normal, vec3(far_uv, float(dual_scale_texture)), far_dd.xy, far_dd.zw); far_alb.rgb *= _texture_color_array[dual_scale_texture].rgb; far_nrm.a = clamp(far_nrm.a + _texture_roughness_mod_array[dual_scale_texture], 0., 1.); // Unpack and rotate normal map. far_nrm.xyz = fma(far_nrm.xzy, vec3(2.0), vec3(-1.0)); far_ao = length(far_nrm.xyz) * 2.0 - 1.0; far_ao = mix(far_ao * far_ao * _texture_ao_strength_array[dual_scale_texture] + 1.0 - _texture_ao_strength_array[dual_scale_texture], 1.0, far_alb.a * far_alb.a); far_nrm.xyz = normalize(far_nrm.xyz); far_nrm.xz = rotate_vec2(far_nrm.xz, far_cs_angle) * p_align; // apply weighting when far_factor == 1.0 as the later lookup will be skipped. if (far_factor == 1.0) { float id_w = texture_id[0] == dual_scale_texture ? texture_weight[0] : texture_weight[1]; float id_weight = exp2(sharpness * log2(weight + id_w + far_alb.a)) * weight; world_normal = FAST_WORLD_NORMAL(far_nrm).y; mat.albedo_height = fma(far_alb, vec4(id_weight), mat.albedo_height); mat.normal_rough = fma(far_nrm, vec4(id_weight), mat.normal_rough); mat.normal_map_depth = fma(_texture_normal_depth_array[dual_scale_texture], id_weight, mat.normal_map_depth); mat.ao = fma(far_ao, id_weight, mat.ao); mat.ao_affect = fma(_texture_ao_affect_array[dual_scale_texture], id_weight, mat.ao_affect); mat.total_weight += id_weight; } } //INSERT: DUAL_SCALING_CONDITION_0 && !(far_factor == 1.0 && texture_id[0] == dual_scale_texture) //INSERT: DUAL_SCALING_CONDITION_1 && !(far_factor == 1.0 && texture_id[1] == dual_scale_texture) //INSERT: DUAL_SCALING_MIX // If dual scaling, apply to overlay texture if (id == dual_scale_texture && far_factor > 0.) { alb = mix(alb, far_alb, far_factor); nrm = mix(nrm, far_nrm, far_factor); ao = mix(ao, far_ao, far_factor); world_normal = mix(world_normal, 1.0, far_factor); } )" ================================================ FILE: src/shaders/editor_functions.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // These special inserts are injected into the shader code at the end of fragment(). R"( //INSERT: EDITOR_NAVIGATION // Show navigation { if(bool(floatBitsToUint(texelFetch(_control_maps, get_index_coord(floor(uv + 0.5)), 0)).r >>1u & 0x1u)) { ALBEDO *= vec3(.5, .0, .85); } } //INSERT: EDITOR_REGION_GRID // Show region grid { vec3 __boundary_color = pow(vec3(0.095, 0.328, 0.56), vec3(2.2)); // Medium dark blue, hue 210, converted to linear vec3 __active_color = vec3(1.0); vec3 __inactive_color = vec3(0.1); float __line_thickness = 0.05 * sqrt(-VERTEX.z); vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz * _vertex_density; vec2 __p = __pixel_pos.xz; // Region Grid vec2 __g = abs(fract((__p + _region_size * 0.5) / _region_size) - 0.5) * _region_size; float __grid_d = min(__g.x, __g.y); float __grid_mask = 1.0 - smoothstep(__line_thickness - fwidth(__grid_d), __line_thickness + fwidth(__grid_d), __grid_d); // Region Map Boundry float __hmap = _region_size * 16.0; vec2 __bp = abs(__p) - __hmap; float __box_d = abs(max(__bp.x, __bp.y)); float __box_mask = 1.0 - smoothstep( __line_thickness - fwidth(__box_d), __line_thickness + fwidth(__box_d), __box_d); // Clip Grid at Boundary float __b_line = __hmap - __line_thickness - fwidth(__box_d); __grid_mask *= step(abs(__p.x), __b_line) * step(abs(__p.y), __b_line); vec3 __grid_color = mix(__inactive_color, __active_color, float(clamp(get_index_coord(__pixel_pos.xz - 0.5).z + 1, 0, 1))); ALBEDO = mix(ALBEDO, __grid_mask * __grid_color + __box_mask * __boundary_color, max(__grid_mask, __box_mask)); } //INSERT: EDITOR_DECAL_SETUP uniform highp sampler2D _editor_brush_texture : source_color, filter_linear, repeat_disable; uniform vec2 _editor_decal_position[3]; uniform float _editor_decal_size[3]; uniform float _editor_decal_rotation[3]; uniform vec4 _editor_decal_color[3] : source_color; uniform bool _editor_decal_visible[3]; // show decal: brush, slope point1, point2 uniform bool _editor_decal_part[2]; // show decal[0] component: texture, reticle float get_reticle(vec2 uv, int index) { float cam_dist = clamp(length(v_camera_pos - v_vertex), 0., 4000.); float sq_cam_dist = sqrt(cam_dist); float view_scale = 16.0 / sq_cam_dist; vec2 cross_uv = (uv * _vertex_spacing - _editor_decal_position[index]) * view_scale; float brush_radius = max((_editor_decal_size[index] * 0.5) * view_scale, 1.); float line_start = brush_radius + log2(cam_dist); float line_end = 2.5 * line_start; float line_thickness = .03 * length(cross_uv) + .03 * sq_cam_dist; // flanged lines + distant thickness float cursor = 0.; float h, v; // Crosshair vec2 d = abs(cross_uv); h = smoothstep(line_thickness, 0.0, d.y) * step(d.x, line_end) * step(line_start, d.x); v = smoothstep(line_thickness, 0.0, d.x) * step(d.y, line_end) * step(line_start, d.y); cursor = h + v; // Circle float dist = length(cross_uv); h = smoothstep(brush_radius + line_thickness, brush_radius, dist); v = 1.0 - smoothstep(brush_radius, brush_radius - line_thickness, dist); cursor += h * v; return clamp(cursor, 0.0, 1.0); } // Expects uv (Texture/world space 0 to +/- inf 1m units). vec3 get_decal(vec3 albedo, vec2 uv) { for (int i = 0; i < 3; ++i) { if (!_editor_decal_visible[i]) { continue; } float size = 1.0 / _editor_decal_size[i]; float cosa = cos(_editor_decal_rotation[i]); float sina = sin(_editor_decal_rotation[i]); vec2 decal_uv = (vec2(cosa * uv.x - sina * uv.y, sina * uv.x + cosa * uv.y) - vec2(cosa * _editor_decal_position[i].x - sina * _editor_decal_position[i].y, sina * _editor_decal_position[i].x + cosa * _editor_decal_position[i].y) * _vertex_density) * size * _vertex_spacing; float decal = 0.0; if (i == 0 && abs(decal_uv.x) <= 0.499 && abs(decal_uv.y) <= 0.499 && _editor_decal_part[0]) { decal = texture(_editor_brush_texture, decal_uv + 0.5).r; } if (_editor_decal_part[1]) { decal += get_reticle(uv, i); } // Blend in decal; square for better visual blend albedo = mix(albedo, _editor_decal_color[i].rgb, decal * decal * _editor_decal_color[i].a); } return albedo; } //INSERT: EDITOR_DECAL_RENDER // Render decal { ALBEDO = get_decal(ALBEDO, uv); } )" ================================================ FILE: src/shaders/gpu_depth.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // This shader reads the screen and returns absolute depth encoded in Albedo.rg // It is not used as an INSERT R"( shader_type spatial; render_mode unshaded; uniform highp sampler2D depth_texture : source_color, hint_depth_texture, filter_nearest, repeat_disable; void fragment() { float depth = textureLod(depth_texture, SCREEN_UV, 0.).x; #if CURRENT_RENDERER == RENDERER_COMPATIBILITY depth = depth * 2.0 - 1.0; #endif vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth); vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0); view.xyz /= view.w; float depth_linear = -view.z; // Normalize depth to the range 0 - 1. Divided by camera.get_far() highp float scaledDepth = clamp(depth_linear / 100000.0, 0.0, 1.0); // Encode using 127 steps, which map to the 128 - 255 range. // Avoids precision loss for compatability and mobile renderer // 21bit depth value highp float r = (floor(scaledDepth * 127.0) + 128.0) / 255.0; highp float g = (floor(fract(scaledDepth * 127.0) * 127.0) + 128.0) / 255.0; highp float b = (floor(fract(scaledDepth * 127.0 * 127.0) * 127.0) + 128.0) / 255.0; ALBEDO = vec3(r, g, b); // Return encoded value, required for Mobile & Compatibility renderers } )" ================================================ FILE: src/shaders/macro_variation.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: MACRO_VARIATION_UNIFORMS group_uniforms shader_uniforms.macro_variation; uniform vec3 macro_variation1 : source_color = vec3(1.); uniform vec3 macro_variation2 : source_color = vec3(1.); uniform float macro_variation_slope : hint_range(0., 1.) = 0.333; uniform highp sampler2D noise_texture : source_color, FILTER_METHOD, repeat_enable; uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x uniform float noise1_angle : hint_range(0, 6.283) = 0.; uniform vec2 noise1_offset = vec2(0.5); uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x group_uniforms; //INSERT: MACRO_VARIATION // Macro variation. 2 lookups { float noise1 = texture(noise_texture, rotate_vec2(fma(uv, vec2(noise1_scale * .1), noise1_offset) , vec2(cos(noise1_angle), sin(noise1_angle)))).r; float noise2 = texture(noise_texture, uv * noise2_scale * .1).r; vec3 macrov = mix(macro_variation1, vec3(1.), noise1); macrov *= mix(macro_variation2, vec3(1.), noise2); mat.albedo_height.rgb *= mix(vec3(1.0), macrov, clamp(w_normal.y + macro_variation_slope, 0., 1.)); } )" ================================================ FILE: src/shaders/main.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // Raw strings have a limit of 64k, but MSVC has a limit of 2k in a string literal. This file is split into // multiple raw strings that are concatenated by the compiler. R"(shader_type spatial; render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx, skip_vertex_transform; /* The terrain depends on this shader to function. Don't change most things in vertex() or * terrain normal calculations in fragment(). You probably only want to customize the * material calculation and PBR application in fragment(). * * Uniforms that begin with _ are private and will not display in the inspector. However, * you can set them via code. You are welcome to create more of your own hidden uniforms. * * This system only supports albedo, height, normal, roughness. Most textures don't need the other * PBR channels. Height can be used as an approximation for AO. For the rare textures do need * additional channels, you can add maps for that one texture. e.g. an emissive map for lava. * */ // Defined Constants #define COLOR_MAP_DEF vec4(1.0, 1.0, 1.0, 0.5) #define DIV_255 0.003921568627450 // 1. / 255. #define DIV_1024 0.0009765625 // 1. / 1024. #define TAU_16TH -0.392699081698724 // -TAU / 16. // Inline Functions #define DECODE_BLEND(control) float(control >>14u & 0xFFu) * DIV_255 #define DECODE_AUTO(control) bool(control & 0x1u) #define DECODE_BASE(control) int(control >>27u & 0x1Fu) #define DECODE_OVER(control) int(control >>22u & 0x1Fu) #define DECODE_ANGLE(control) float(control >>10u & 0xFu) * TAU_16TH // This math recreates the scale value directly rather than using an 8 float const array. #define DECODE_SCALE(control) (0.9 - float(((control >>7u & 0x7u) + 3u) % 8u + 1u) * 0.1) #define DECODE_HOLE(control) bool(control >>2u & 0x1u) #if CURRENT_RENDERER == RENDERER_COMPATIBILITY #define fma(a, b, c) ((a) * (b) + (c)) #define dFdxCoarse(a) dFdx(a) #define dFdyCoarse(a) dFdy(a) #endif // Private uniforms group_uniforms shader_uniforms; uniform vec3 _target_pos = vec3(0.f); uniform float _mesh_size = 48.f; uniform float _subdiv = 1.f; uniform float _tessellation_level = 0.f; uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2 uniform uint _mouse_layer = 0x80000000u; // Layer 32 uniform float _vertex_spacing = 1.0; uniform float _vertex_density = 1.0; // = 1./_vertex_spacing uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1./region_size uniform int _region_map_size = 32; uniform int _region_map[1024]; uniform vec2 _region_locations[1024]; uniform float _texture_normal_depth_array[32]; uniform float _texture_ao_strength_array[32]; uniform float _texture_ao_affect_array[32]; uniform float _texture_roughness_mod_array[32]; uniform float _texture_uv_scale_array[32]; uniform vec2 _texture_detile_array[32]; uniform vec4 _texture_color_array[32]; uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; //INSERT: TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC //INSERT: TEXTURE_SAMPLERS_LINEAR //INSERT: TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC //INSERT: TEXTURE_SAMPLERS_NEAREST uniform highp sampler2DArray _color_maps : source_color, FILTER_METHOD, repeat_disable; uniform highp sampler2DArray _texture_array_albedo : source_color, FILTER_METHOD, repeat_enable; uniform highp sampler2DArray _texture_array_normal : hint_normal, FILTER_METHOD, repeat_enable; group_uniforms; // Public uniforms group_uniforms shader_uniforms.general; //INSERT: FLAT_UNIFORMS uniform bool flat_terrain_normals = false; uniform float blend_sharpness : hint_range(0, 1) = 0.5; //INSERT: PROJECTION_UNIFORMS group_uniforms; //INSERT: AUTO_SHADER_UNIFORMS //INSERT: DISPLACEMENT_UNIFORMS //INSERT: DUAL_SCALING_UNIFORMS //INSERT: MACRO_VARIATION_UNIFORMS group_uniforms shader_uniforms.mipmaps; uniform float bias_distance : hint_range(0.0, 16384.0, 0.1) = 512.0; uniform float mipmap_bias : hint_range(0.5, 1.5, 0.01) = 1.0; uniform float depth_blur : hint_range(0.0, 35.0, 0.1) = 0.0; group_uniforms; //INSERT: WORLD_NOISE_UNIFORMS // Varyings & Types struct material { vec4 albedo_height; vec4 normal_rough; float normal_map_depth; float ao; float ao_affect; float total_weight; }; varying vec3 v_vertex; varying float v_vertex_xz_dist; varying vec3 v_camera_pos; )" R"( //////////////////////// // Vertex //////////////////////// // Takes in world space XZ (UV) coordinates // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region ivec3 get_index_coord(const vec2 uv) { vec2 r_uv = round(uv); ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1; return ivec3(ivec2(mod(r_uv, _region_size)), layer_index); } // Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with: // XY: (0. to 1.) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region vec3 get_index_uv(const vec2 uv2) { ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1; return vec3(uv2 - _region_locations[layer_index], float(layer_index)); } float interpolated_height(vec2 pos) { const vec2 offsets = vec2(0, 1); vec2 index_id = floor(pos); ivec3 index[4]; index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); float h0 = texelFetch(_height_maps, index[0], 0).r; float h1 = texelFetch(_height_maps, index[1], 0).r; float h2 = texelFetch(_height_maps, index[2], 0).r; float h3 = texelFetch(_height_maps, index[3], 0).r; vec2 f = fract(pos); vec2 i = 1.0 - f; vec4 w = vec4(i.x * f.y, f.x * f.y, f.x * i.y, i.x * i.y); float h = h0 * w[0] + h1 * w[1] + h2 * w[2] + h3 * w[3]; return h; } //INSERT: DISPLACEMENT_FUNCTIONS //INSERT: NONE_FUNCTIONS //INSERT: FLAT_FUNCTIONS //INSERT: WORLD_NOISE_FUNCTIONS void vertex() { // Save Camera Position to varying for access in later functions v_camera_pos = MAIN_CAM_INV_VIEW_MATRIX[3].xyz; // Get vertex of flat plane in world coordinates and set world UV v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // Distance from target node to vertex on a flat plane v_vertex_xz_dist = length(v_vertex.xz - _target_pos.xz); // Geomorph vertex across clipmap LODs, set end and start for linear height interpolate float scale = MODEL_MATRIX[0][0]; float inv_scale = 1.0 / scale; float max_xz = max(abs(v_vertex.x - _target_pos.x), abs(v_vertex.z - _target_pos.z)); float vertex_lerp = smoothstep(0.0, 1.0, (max_xz * inv_scale - _mesh_size - 4.0) / (_mesh_size - 4.0)); vec2 vertex_fract = fract(VERTEX.xz * 0.5) * 2.0; // For LOD0 morph from a regular grid to an alternating grid to align with LOD1+ vec2 shift = (scale < _vertex_spacing / _subdiv + 1e-6) ? // LOD0 or not // Shift from regular to symmetric mix(vertex_fract, vec2(vertex_fract.x, -vertex_fract.y), round(fract(round(mod(v_vertex.z * inv_scale, 4.0)) * round(mod(v_vertex.x * inv_scale, 4.0)) * 0.25))) : // Symmetric shift vertex_fract * round((fract(v_vertex.xz * 0.25 * inv_scale) - 0.5) * 4.0); vec2 start_pos = v_vertex.xz * _vertex_density; vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density; v_vertex.xz -= shift * scale * vertex_lerp; // UV coordinates in region space. 0-1 covers 1 region, 1-2 is the next region, etc. UV = v_vertex.xz * _vertex_density; // UV coordinates in region space + texel offset. Values are 0 to 1 within regions UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size)); // Discard vertices for Holes. 1 lookup ivec3 v_region = get_index_coord(start_pos); uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r; bool hole = DECODE_HOLE(control); vec3 displacement = vec3(0.); // Show holes to all cameras except mouse camera (on exactly 1 layer) if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) && (hole //INSERT: NONE_CHECK )){ v_vertex.x = 0. / 0.; } else { // Set final vertex height. float h; // This branch is static for each of the clipmap segments // Interpolated reads only occur where sub-texel values are required. if (scale < _vertex_spacing) { h = interpolated_height(UV); } else { ivec3 coord_a = get_index_coord(start_pos); ivec3 coord_b = get_index_coord(end_pos); h = mix(texelFetch(_height_maps, coord_a, 0).r, texelFetch(_height_maps, coord_b, 0).r, vertex_lerp); } //INSERT: FLAT_VERTEX //INSERT: WORLD_NOISE_VERTEX //INSERT: DISPLACEMENT_VERTEX v_vertex.y = h; } // Convert model space to view space w/ skip_vertex_transform render mode // Include displacement without modifying v_vertex. VERTEX = (VIEW_MATRIX * vec4(v_vertex + displacement, 1.0)).xyz; NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz); TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz); } )" R"( //////////////////////// // Fragment //////////////////////// float random(in vec2 xy) { return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453); } vec2 rotate_vec2(const vec2 v, const vec2 cs) { return vec2(fma(cs.x, v.x, cs.y * v.y), fma(cs.x, v.y, -cs.y * v.x)); } // 2-4 lookups ( 2-6 with dual scaling ) void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const float weight, const ivec3 index, const uint control, const vec2 texture_weight, const ivec2 texture_id, const vec3 i_normal, float h, inout material mat) { // Applying scaling before projection reduces the number of multiplys ops required. vec3 i_vertex = v_vertex; // Control map scale float control_scale = DECODE_SCALE(control); base_ddx *= control_scale; base_ddy *= control_scale; i_vertex *= control_scale; h *= control_scale; // Index position for detiling. vec2 i_pos = fma(_region_locations[index.z], vec2(_region_size), vec2(index.xy)); i_pos *= _vertex_spacing * control_scale; // Projection vec2 i_uv = i_vertex.xz; vec4 i_dd = vec4(base_ddx.xz, base_ddy.xz); mat2 p_align = mat2(1.); //INSERT: PROJECTION // Control map rotation. Must be applied seperatley from detiling to maintain UV continuity. float c_angle = DECODE_ANGLE(control); vec2 c_cs_angle = vec2(cos(c_angle), sin(c_angle)); i_uv = rotate_vec2(i_uv, c_cs_angle); i_pos = rotate_vec2(i_pos, c_cs_angle); // Blend adjustment of Higher ID from Lower ID normal map in world space. float world_normal = 1.; // mat3 multiply, reduced to 2x fma and 1x mult. #define FAST_WORLD_NORMAL(n) fma(TNB[0], vec3(n.x), fma(TNB[2], vec3(n.z), TNB[1] * vec3(n.y))) float blend = DECODE_BLEND(control); // only used for branching. float sharpness = fma(60., blend_sharpness, 4.); //INSERT: DUAL_SCALING // 1st Texture Asset ID if (blend < 1.0 //INSERT: DUAL_SCALING_CONDITION_0 ) { int id = texture_id[0]; float id_w = texture_weight[0]; float id_scale = _texture_uv_scale_array[id]; vec4 id_dd = i_dd * id_scale; // Detiling and Control map rotation vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5))); vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU; vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x)); // Apply UV rotation and shift around pivot. vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5; // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also // including control map rotation. avoids extra matrix op, and sin/cos calls. id_cs_angle = vec2( fma(id_cs_angle.x, c_cs_angle.x, -id_cs_angle.y * c_cs_angle.y), fma(id_cs_angle.y, c_cs_angle.x, id_cs_angle.x * c_cs_angle.y)); // Align derivatives for correct anisotropic filtering id_dd.xy = rotate_vec2(id_dd.xy, id_cs_angle); id_dd.zw = rotate_vec2(id_dd.zw, id_cs_angle); vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); vec4 nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); alb.rgb *= _texture_color_array[id].rgb; nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.); // Unpack and rotate normal map. nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0)); float ao = length(nrm.xyz) * 2.0 - 1.0; ao = mix(ao * ao * _texture_ao_strength_array[id] + 1.0 - _texture_ao_strength_array[id], 1.0, alb.a * alb.a); nrm.xyz = normalize(nrm.xyz); nrm.xz = rotate_vec2(nrm.xz, id_cs_angle) * p_align; //INSERT: DUAL_SCALING_MIX world_normal = FAST_WORLD_NORMAL(nrm).y; float id_weight = exp2(sharpness * log2(weight + id_w + alb.a)) * weight; mat.albedo_height = fma(alb, vec4(id_weight), mat.albedo_height); mat.normal_rough = fma(nrm, vec4(id_weight), mat.normal_rough); mat.normal_map_depth = fma(_texture_normal_depth_array[id], id_weight, mat.normal_map_depth); mat.ao = fma(ao, id_weight, mat.ao); mat.ao_affect = fma(_texture_ao_affect_array[id], id_weight, mat.ao_affect); mat.total_weight += id_weight; } // 2nd Texture Asset ID if (blend > 0.0 && texture_id[1] != texture_id[0] //INSERT: DUAL_SCALING_CONDITION_1 ) { int id = texture_id[1]; float id_w = texture_weight[1]; float id_scale = _texture_uv_scale_array[id]; vec4 id_dd = i_dd * id_scale; // Detiling and Control map rotation vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5))); vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU; vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x)); // Apply UV rotation and shift around pivot. vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5; // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also // including control map rotation. avoids extra matrix op, and sin/cos calls. id_cs_angle = vec2( fma(id_cs_angle.x, c_cs_angle.x, -id_cs_angle.y * c_cs_angle.y), fma(id_cs_angle.y, c_cs_angle.x, id_cs_angle.x * c_cs_angle.y)); // Align derivatives for correct anisotropic filtering id_dd.xy = rotate_vec2(id_dd.xy, id_cs_angle); id_dd.zw = rotate_vec2(id_dd.zw, id_cs_angle); vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); vec4 nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); alb.rgb *= _texture_color_array[id].rgb; nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.); // Unpack and rotate normal map. nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0)); float ao = length(nrm.xyz) * 2.0 - 1.0; ao = mix(ao * ao * _texture_ao_strength_array[id] + 1.0 - _texture_ao_strength_array[id], 1.0, alb.a * alb.a); nrm.xyz = normalize(nrm.xyz); nrm.xz = rotate_vec2(nrm.xz, id_cs_angle) * p_align; //INSERT: DUAL_SCALING_MIX float id_weight = exp2(sharpness * log2(weight + id_w + alb.a * clamp(world_normal, 0., 1.))) * weight; mat.albedo_height = fma(alb, vec4(id_weight), mat.albedo_height); mat.normal_rough = fma(nrm, vec4(id_weight), mat.normal_rough); mat.normal_map_depth = fma(_texture_normal_depth_array[id], id_weight, mat.normal_map_depth); mat.ao = fma(ao, id_weight, mat.ao); mat.ao_affect = fma(_texture_ao_affect_array[id], id_weight, mat.ao_affect); mat.total_weight += id_weight; } } float get_height(vec2 index_id, vec2 offset) { float height = texelFetch(_height_maps, get_index_coord(index_id + offset), 0).r; //INSERT: FLAT_FRAGMENT return height; } )" R"( void fragment() { // Recover UVs vec2 uv = UV; vec2 uv2 = UV2; // Lookup offsets, ID and blend weight vec3 region_uv = get_index_uv(uv2); const vec3 offsets = vec3(0, 1, 2); vec2 index_id = floor(uv); vec2 weight = fract(uv); vec2 invert = 1.0 - weight; vec4 weights = vec4( invert.x * weight.y, // 0 weight.x * weight.y, // 1 weight.x * invert.y, // 2 invert.x * invert.y // 3 ); ivec3 index[4]; // control map lookups index[0] = get_index_coord(index_id + offsets.xy); index[1] = get_index_coord(index_id + offsets.yy); index[2] = get_index_coord(index_id + offsets.yx); index[3] = get_index_coord(index_id + offsets.xx); vec3 base_ddx = dFdxCoarse(v_vertex); vec3 base_ddy = dFdyCoarse(v_vertex); // Calculate the effective mipmap for regionspace, and when less than 0, // skip all extra lookups required for bilinear blend. float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density); bool bilerp = region_mip < 4.0 && any(greaterThan(ivec4(index[0].z, index[1].z, index[2].z, index[3].z), ivec4(-1))); // Terrain normals vec3 index_normal[4]; float h[4]; // Allows for additional derivatives, eg world background, brush previews etc float u = 0.0; float v = 0.0; //INSERT: WORLD_NOISE_FRAGMENT h[3] = get_height(index_id, offsets.xx); // 0 (0, 0) h[2] = get_height(index_id, offsets.yx); // 1 (1, 0) h[0] = get_height(index_id, offsets.xy); // 2 (0, 1) index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v)); // Set flat world normal - overwritten if bilerp is true vec3 w_normal = index_normal[3]; // Adjust derivatives for mipmap bias and depth blur effect float bias = mix(mipmap_bias, depth_blur + 1., smoothstep(0.0, 1.0, (v_vertex_xz_dist - bias_distance) * DIV_1024)); base_ddx *= bias; base_ddy *= bias; // Color map vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF; // Branching smooth normals and manually interpolated color map - fixes cross region artifacts if (bilerp) { // 4 lookups if linear filtering, else 1 lookup. vec4 col_map[4]; col_map[3] = index[3].z > -1 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP_DEF; #ifdef FILTER_LINEAR col_map[0] = index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF; col_map[1] = index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF; col_map[2] = index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF; color_map = col_map[0] * weights[0] + col_map[1] * weights[1] + col_map[2] * weights[2] + col_map[3] * weights[3] ; #else color_map = col_map[3]; #endif // 5 lookups // Fetch the additional required height values for smooth normals h[1] = get_height(index_id, offsets.yy); // 3 (1, 1) float h_4 = get_height(index_id, offsets.yz); // 4 (1, 2) float h_5 = get_height(index_id, offsets.zy); // 5 (2, 1) float h_6 = get_height(index_id, offsets.zx); // 6 (2, 0) float h_7 = get_height(index_id, offsets.xz); // 7 (0, 2) // Calculate the normal for the remaining index ids. index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v)); index_normal[1] = normalize(vec3(h[1] - h_5 + u, _vertex_spacing, h[1] - h_4 + v)); index_normal[2] = normalize(vec3(h[2] - h_6 + u, _vertex_spacing, h[2] - h[1] + v)); // Set interpolated world normal w_normal = index_normal[0] * weights[0] + index_normal[1] * weights[1] + index_normal[2] * weights[2] + index_normal[3] * weights[3] ; } vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0))); vec3 w_binormal = normalize(cross(w_normal, w_tangent)); mat3 TNB = mat3(w_tangent, w_normal, w_binormal); // Apply terrain normals if (flat_terrain_normals) { NORMAL = normalize(cross(dFdyCoarse(VERTEX), dFdxCoarse(VERTEX))); TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz)); BINORMAL = normalize(cross(NORMAL, TANGENT)); } else { NORMAL = mat3(VIEW_MATRIX) * w_normal; TANGENT = mat3(VIEW_MATRIX) * w_tangent; BINORMAL = mat3(VIEW_MATRIX) * w_binormal; } // Get index control data // 1 - 4 lookups uvec4 control = uvec4(floatBitsToUint(texelFetch(_control_maps, index[3], 0).r)); if (bilerp) { control = uvec4( floatBitsToUint(texelFetch(_control_maps, index[0], 0).r), floatBitsToUint(texelFetch(_control_maps, index[1], 0).r), floatBitsToUint(texelFetch(_control_maps, index[2], 0).r), control[3]); } //INSERT: AUTO_SHADER // Texture weights // Vectorised Deocode of all texture IDs, then swizzle to per index mapping. // Passed to accumulate_material to avoid repeated decoding. ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)), ivec4(control >> uvec4(22u) & uvec4(0x1Fu))}; ivec2 texture_ids[4] = ivec2[4]( ivec2(t_id[0].x, t_id[1].x), ivec2(t_id[0].y, t_id[1].y), ivec2(t_id[0].z, t_id[1].z), ivec2(t_id[0].w, t_id[1].w)); // uninterpolated weights. vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255; vec4 weights_id_0 = 1.0 - weights_id_1; vec2 t_weights[4] = vec2[4]( vec2(weights_id_0[0], weights_id_1[0]), vec2(weights_id_0[1], weights_id_1[1]), vec2(weights_id_0[2], weights_id_1[2]), vec2(weights_id_0[3], weights_id_1[3])); // interpolated weights if (bilerp) { t_weights = {vec2(0), vec2(0), vec2(0), vec2(0)}; weights_id_0 *= weights; weights_id_1 *= weights; for (int i = 0; i < 4; i++) { vec2 w_0 = vec2(weights_id_0[i]); vec2 w_1 = vec2(weights_id_1[i]); ivec2 id_0 = texture_ids[i].xx; ivec2 id_1 = texture_ids[i].yy; t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1))); t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1))); t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1))); t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1))); } } // Struct to accumulate all texture data. material mat = material(vec4(0.0), vec4(0.0), 0., 0., 0., 0.); // 2 - 4 lookups, 2 - 6 if dual scale texture accumulate_material(base_ddx, base_ddy, TNB, weights[3], index[3], control[3], t_weights[3], texture_ids[3], index_normal[3], h[3], mat); // 6 - 12 lookups, 6 - 18 if dual scale texture if (bilerp) { accumulate_material(base_ddx, base_ddy, TNB, weights[2], index[2], control[2], t_weights[2], texture_ids[2], index_normal[2], h[2], mat); accumulate_material(base_ddx, base_ddy, TNB, weights[1], index[1], control[1], t_weights[1], texture_ids[1], index_normal[1], h[1], mat); accumulate_material(base_ddx, base_ddy, TNB, weights[0], index[0], control[0], t_weights[0], texture_ids[0], index_normal[0], h[0], mat); } // normalize accumulated values back to 0.0 - 1.0 range. float weight_inv = 1.0 / max(mat.total_weight, 1e-8); mat.albedo_height *= weight_inv; mat.normal_rough *= weight_inv; mat.normal_map_depth *= weight_inv; mat.ao *= weight_inv; mat.ao_affect *= weight_inv; //INSERT: MACRO_VARIATION // Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range, clamped to Godot roughness values float roughness = clamp(fma(color_map.a - 0.5, 2.0, mat.normal_rough.a), 0., 1.); // Apply PBR //INSERT: OUTPUT_ALBEDO //INSERT: OUTPUT_ALBEDO_GREY //INSERT: OUTPUT_ROUGHNESS //INSERT: OUTPUT_NORMAL_MAP //INSERT: OUTPUT_AMBIENT_OCCLUSION } )" ================================================ FILE: src/shaders/overlays.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // These special inserts are injected into the shader code at the end of fragment(). // Variables should be prefaced with __ to avoid name conflicts. R"( //INSERT: OVERLAY_INSTANCER_GRID // Show instancer grid { vec3 __grid_color = vec3(.05); float __line_thickness = 0.01 * sqrt(-VERTEX.z); vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz * _vertex_density; vec2 __p = __pixel_pos.xz; // Instancer Grid #define CELL_SIZE 32.0 vec2 __g = abs(fract((__p + CELL_SIZE * 0.5) / CELL_SIZE) - 0.5) * CELL_SIZE; float __grid_d = min(__g.x, __g.y); float __grid_mask = 1.0 - smoothstep(__line_thickness - fwidth(__grid_d), __line_thickness + fwidth(__grid_d), __grid_d); // Clip Grid outside regions __grid_mask *= float(clamp(get_index_coord(__pixel_pos.xz - 0.5).z + 1, 0, 1)); ALBEDO = mix(ALBEDO, __grid_mask * __grid_color, __grid_mask); } //INSERT: OVERLAY_VERTEX_GRID // Show vertex grids { vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz; vec3 __camera_pos = INV_VIEW_MATRIX[3].xyz; float __grid_line = 0.05; // Vertex grid line thickness float __grid_step = 1.0; // Vertex grid size, 1.0 == integer units float __vertex_size = 4.; // Size of vertices float __view_distance = 300.0; // Visible distance of grid vec3 __vertex_mul = vec3(0.); vec3 __vertex_add = vec3(0.); float __distance_factor = clamp(1.-length(__camera_pos - __pixel_pos)/__view_distance, 0., 1.); // Draw vertex grid if ( mod(__pixel_pos.x * _vertex_density + __grid_line*.5, __grid_step) < __grid_line || mod(__pixel_pos.z * _vertex_density + __grid_line*.5, __grid_step) < __grid_line ) { __vertex_mul = vec3(0.5) * __distance_factor; } // Draw Vertices if ( mod(UV.x + __grid_line*__vertex_size*.5, __grid_step) < __grid_line*__vertex_size && mod(UV.y + __grid_line*__vertex_size*.5, __grid_step) < __grid_line*__vertex_size ) { __vertex_add = vec3(0.15) * __distance_factor; } ALBEDO = fma(ALBEDO, 1.-__vertex_mul, __vertex_add); } //INSERT: OVERLAY_CONTOURS_SETUP group_uniforms shader_uniforms.contour_lines; uniform float contour_interval: hint_range(0.25, 100.0, 0.001) = 1.0; uniform float contour_thickness : hint_range(0.0, 10.0, 0.001) = 1.0; uniform vec4 contour_color : source_color = vec4(.85, .85, .19, 1.); group_uniforms; float fractal_contour_lines(float thickness, float interval, vec3 spatial_coords, vec3 normal, vec3 base_ddx, vec3 base_ddy, vec3 __camera_pos) { float depth = max(log(length(spatial_coords - __camera_pos) / interval) * (1.0 / log2(2.0)) - 1.0, 1.0); float interval_a = interval * exp2(max(floor(depth) - 1.0, 1.0)) * 0.5; float interval_b = interval * exp2(max(floor(depth), 1.0)) * 0.5; float interval_c = interval * exp2(max(floor(depth + 0.5) - 1.0, 1.0)) * 0.5; float y = spatial_coords.y; float y_fwidth = abs(base_ddx.y) + abs(base_ddy.y); thickness *= smoothstep(0., 0.0125, clamp(1.0 - normal.y, 0., 1.)); float mi = max(0.0, thickness - 1.0); float ma = max(1.0, thickness); float mx = max(0.0, 1.0 - thickness); // Line A float inv_interval_a = 1.0 / interval_a; float f_a = abs(fract((y + interval_a * 0.5) * inv_interval_a) - 0.5); float df_a = y_fwidth * inv_interval_a; float line_a = clamp((f_a - df_a * mi) / (df_a * (ma - mi)), mx, 1.0); // Line B float inv_interval_b = 1.0 / interval_b; float f_b = abs(fract((y + interval_b * 0.5) * inv_interval_b) - 0.5); float df_b = y_fwidth * inv_interval_b; float line_b = clamp((f_b - df_b * mi) / (df_b * (ma - mi)), mx, 1.0); // Line C float inv_interval_c = 1.0 / interval_c; float f_c = abs(fract((y + interval_c * 0.5) * inv_interval_c) - 0.5); float df_c = y_fwidth * inv_interval_c; float line_c = clamp((f_c - df_c * mi) / (df_c * (ma - mi)), mx, 1.0); // Blend out float p = fract(depth - 0.5); float line = mix(mix(line_a, line_b, fract(depth)), line_c, (4.0 * p * (1.0 - p))); return line; } //INSERT: OVERLAY_CONTOURS_RENDER // Show contour lines { vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz; vec3 __camera_pos = INV_VIEW_MATRIX[3].xyz; vec3 __base_ddx = dFdxCoarse(__pixel_pos); vec3 __base_ddy = dFdyCoarse(__pixel_pos); vec3 __w_normal = normalize(cross(__base_ddy, __base_ddx)); float __line = fractal_contour_lines(contour_thickness, contour_interval, __pixel_pos, __w_normal, __base_ddx, __base_ddy, __camera_pos); ALBEDO = mix(ALBEDO, contour_color.rgb, (1.-__line) * contour_color.a); } )" ================================================ FILE: src/shaders/pbr_views.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. // These special inserts are injected into the shader code at the end of fragment(). // Variables should be prefaced with __ to avoid name conflicts. R"( //INSERT: PBR_TEXTURE_ALBEDO // Show albedo textures { ALBEDO = mat.albedo_height.rgb; ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: PBR_TEXTURE_HEIGHT // Show height textures { ALBEDO = vec3(mat.albedo_height.a); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: PBR_TEXTURE_NORMAL // Show normal map textures { ALBEDO = fma(normalize(mat.normal_rough.xzy), vec3(0.5), vec3(0.5)); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: PBR_TEXTURE_ROUGHNESS // Show roughness textures { ALBEDO = vec3(mat.normal_rough.a); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: PBR_TEXTURE_AO // Show normal map decoded AO value { ALBEDO = vec3(mat.ao); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); AO = 1.0; } //INSERT: OUTPUT_ALBEDO ALBEDO = mat.albedo_height.rgb * color_map.rgb; //INSERT: OUTPUT_ALBEDO_GREY ALBEDO = vec3(0.2); //INSERT: OUTPUT_ROUGHNESS ROUGHNESS = roughness; SPECULAR = 1. - mat.normal_rough.a; //INSERT: OUTPUT_NORMAL_MAP // Repack final normal map value. NORMAL_MAP = fma(normalize(mat.normal_rough.xzy), vec3(0.5), vec3(0.5)); NORMAL_MAP_DEPTH = mat.normal_map_depth; //INSERT: OUTPUT_AMBIENT_OCCLUSION AO = clamp(mat.ao, 0., 1.); AO_LIGHT_AFFECT = mat.ao_affect; )" ================================================ FILE: src/shaders/projection.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: PROJECTION if (i_normal.y <= 0.7071067811865475) { // sqrt(0.5) // Projected normal map alignment matrix p_align = mat2(vec2(i_normal.z, -i_normal.x), vec2(i_normal.x, i_normal.z)); // Fast 45 degree snapping https://iquilezles.org/articles/noatan/ vec2 xz = round(normalize(-i_normal.xz) * 1.3065629648763765); // sqrt(1.0 + sqrt(0.5)) xz *= abs(xz.x) + abs(xz.y) > 1.5 ? 0.7071067811865475 : 1.0; // sqrt(0.5) xz = vec2(-xz.y, xz.x); i_pos = floor(vec2(dot(i_pos, xz), -h)); i_uv = vec2(dot(i_vertex.xz, xz), -i_vertex.y); #ifndef IS_DISPLACEMENT_BUFFER i_dd.xy = vec2(dot(base_ddx.xz, xz), -base_ddx.y); i_dd.zw = vec2(dot(base_ddy.xz, xz), -base_ddy.y); #endif } )" ================================================ FILE: src/shaders/samplers.glsl ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. R"( //INSERT: TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC #define FILTER_LINEAR #define FILTER_METHOD filter_linear_mipmap_anisotropic //INSERT: TEXTURE_SAMPLERS_LINEAR #define FILTER_LINEAR #define FILTER_METHOD filter_linear_mipmap //INSERT: TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC #define FILTER_NEAREST #define FILTER_METHOD filter_nearest_mipmap_anisotropic //INSERT: TEXTURE_SAMPLERS_NEAREST #define FILTER_NEAREST #define FILTER_METHOD filter_nearest_mipmap )" ================================================ FILE: src/target_node_3d.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TARGET_NODE3D_CLASS_H #define TARGET_NODE3D_CLASS_H #include #include "constants.h" class TargetNode3D { CLASS_NAME_STATIC("Terrain3DTargetNode3D"); private: uint64_t _instance_id = 0; public: void clear() { _instance_id = 0; } void set_target(Node3D *p_node) { if (p_node && !p_node->is_queued_for_deletion()) { _instance_id = p_node->get_instance_id(); } else { clear(); } } Node3D *get_target() const { if (_instance_id == 0) { return nullptr; } Object *obj = ObjectDB::get_instance(_instance_id); return obj ? Object::cast_to(obj) : nullptr; } Node3D *ptr() const { return get_target(); } bool is_valid() const { Node3D *node = get_target(); return node && node->is_inside_tree() && !node->is_queued_for_deletion(); } bool is_null() const { return !is_valid(); } }; #endif // TARGET_NODE3D_CLASS_H ================================================ FILE: src/terrain_3d.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logger.h" #include "terrain_3d.h" #include "terrain_3d_util.h" // Initialize static member variable Terrain3D::DebugLevel Terrain3D::debug_level{ ERROR }; /////////////////////////// // Private Functions /////////////////////////// void Terrain3D::_initialize() { LOG(INFO, "Instantiating main subsystems"); // Make blank objects if needed if (!_data) { LOG(DEBUG, "Creating blank data object"); _data = memnew(Terrain3DData); } if (_material.is_null()) { LOG(DEBUG, "Creating blank material"); _material.instantiate(); } if (_assets.is_null()) { LOG(DEBUG, "Creating blank texture list"); _assets.instantiate(); } if (!_collision) { LOG(DEBUG, "Creating collision manager"); _collision = memnew(Terrain3DCollision); } if (!_instancer) { LOG(DEBUG, "Creating instancer"); _instancer = memnew(Terrain3DInstancer); } // Connect signals // Any region was changed, update region labels if (!_data->is_connected("region_map_changed", callable_mp(this, &Terrain3D::update_region_labels))) { LOG(DEBUG, "Connecting _data::region_map_changed signal to set_show_region_locations()"); _data->connect("region_map_changed", callable_mp(this, &Terrain3D::update_region_labels)); } // Any region was changed, regenerate collision if enabled if (!_data->is_connected("region_map_changed", callable_mp(_collision, &Terrain3DCollision::build))) { LOG(DEBUG, "Connecting _data::region_map_changed signal to build()"); _data->connect("region_map_changed", callable_mp(_collision, &Terrain3DCollision::build)); } // Any map was regenerated or regions changed, update material uniforms without rebuilding shaders if (!_data->is_connected("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::REGION_ARRAYS))) { LOG(DEBUG, "Connecting _data::maps_changed signal to _material->_update()"); _data->connect("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::REGION_ARRAYS)); } // Height map was regenerated, update aabbs if (!_data->is_connected("height_maps_changed", callable_mp(this, &Terrain3D::_update_mesher_aabbs))) { LOG(DEBUG, "Connecting _data::height_maps_changed signal to update_aabbs()"); _data->connect("height_maps_changed", callable_mp(this, &Terrain3D::_update_mesher_aabbs)); } // Texture assets changed, update material uniforms without rebuilding shaders if (!_assets->is_connected("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::TEXTURE_ARRAYS))) { LOG(DEBUG, "Connecting _assets.textures_changed to _material->update()"); _assets->connect("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::TEXTURE_ARRAYS)); } // Initialize the system if (!_initialized && _is_inside_world && is_inside_tree()) { LOG(INFO, "Initializing main subsystems"); _data->initialize(this); _material->initialize(this); _assets->initialize(this); _collision->initialize(this); _instancer->initialize(this); _setup_terrain_mesher(); _setup_ocean_mesher(); _update_displacement_buffer(); _initialized = true; snap(); } update_configuration_warnings(); } /** * This is a proxy for _process(delta) called by _notification() due to * https://github.com/godotengine/godot-cpp/issues/1022 */ void Terrain3D::__physics_process(const double p_delta) { if (!_initialized) return; if (!_camera.is_valid()) { LOG(DEBUG, "Camera is null, getting the current one"); _grab_camera(); } if (_tessellation_level > 0) { if (_terrain_mesher && _d_buffer_vp && _material.is_valid()) { // If clipmap target has moved enough, re-center buffer on the target. Vector2 target_pos_2d = v3v2(get_clipmap_target_position()); real_t tessellation_density = 1.f / pow(2.f, _tessellation_level); real_t vertex_spacing = _vertex_spacing * tessellation_density; if (!(MAX(std::abs(_last_buffer_position.x - target_pos_2d.x), std::abs(_last_buffer_position.y - target_pos_2d.y)) < vertex_spacing)) { _last_buffer_position = target_pos_2d; RS->material_set_param(_material->get_buffer_material_rid(), "_target_pos", get_clipmap_target_position()); _d_buffer_vp->set_update_mode(SubViewport::UPDATE_ONCE); // Only call snap on _mesher if the buffer has snapped, prevents stuttering. _terrain_mesher->snap(); } } } else if (_terrain_mesher) { _terrain_mesher->snap(); } if (_ocean_enabled && _ocean_mesher) { _ocean_mesher->snap(); if (_ocean_material.is_valid() && _ocean_light_target.is_valid()) { DirectionalLight3D *light = cast_to(_ocean_light_target.ptr()); ShaderMaterial *ocean_shader_mat = Object::cast_to(_ocean_material.ptr()); if (light && ocean_shader_mat) { Color color = COLOR_WHITE; color = light->get_color() * light->get_param(DirectionalLight3D::PARAM_ENERGY); ocean_shader_mat->set_shader_parameter("_light_color", color); Vector3 direction = light->get_global_basis().get_column(2); ocean_shader_mat->set_shader_parameter("_light_direction", direction); } } } if (_collision && _collision->is_dynamic_mode()) { _collision->update(); } } /** * If running in the editor, grab the first editor viewport camera. * The edited_scene_root is excluded in case the user already has a Camera3D in their scene. */ void Terrain3D::_grab_camera() { if (IS_EDITOR) { _camera.set_target(EditorInterface::get_singleton()->get_editor_viewport_3d(0)->get_camera_3d()); LOG(DEBUG, "Grabbing the first editor viewport camera: ", _camera.get_target()); } else { _camera.set_target(get_viewport()->get_camera_3d()); LOG(DEBUG, "Grabbing the in-game viewport camera: ", _camera.get_target()); } if (!_camera.is_valid() && !_clipmap_target.is_valid()) { set_physics_process(false); // No target to follow, disable snapping until one set LOG(ERROR, "Cannot find clipmap target or active camera. LODs won't be updated. Set manually with set_clipmap_target() or set_camera()"); } } void Terrain3D::_destroy_collision(const bool p_final) { LOG(INFO, "Destroying Collision"); if (_collision) { _collision->destroy(); } if (p_final) { memdelete_safely(_collision); } } void Terrain3D::_setup_terrain_mesher() { if (!_terrain_mesher) { LOG(DEBUG, "Creating mesher"); _terrain_mesher = new Terrain3DMesher(); } _terrain_mesher->initialize(this, _mesh_size, _mesh_lods, _tessellation_level, _vertex_spacing, _material->get_material_rid(), _render_layers); } void Terrain3D::_destroy_terrain_mesher(const bool p_final) { LOG(INFO, "Destroying terrain mesher"); if (_terrain_mesher) { _terrain_mesher->destroy(); if (p_final) { delete _terrain_mesher; _terrain_mesher = nullptr; } } } void Terrain3D::_setup_ocean_mesher() { if (_ocean_enabled) { if (!_ocean_mesher) { LOG(DEBUG, "Creating mesher"); _ocean_mesher = new Terrain3DMesher(); } _ocean_mesher->initialize(this, _ocean_mesh_size, _ocean_mesh_lods, _ocean_tessellation_level, _ocean_vertex_spacing, _ocean_material.is_valid() ? _ocean_material->get_rid() : RID(), _ocean_render_layers); _ocean_mesher->update_aabbs(_ocean_cull_margin, V2_ZERO); if (_ocean_material.is_valid()) { ShaderMaterial *ocean_shader_mat = Object::cast_to(_ocean_material.ptr()); if (ocean_shader_mat) { ocean_shader_mat->set_shader_parameter("_mesh_size", _ocean_mesh_size); ocean_shader_mat->set_shader_parameter("_vertex_spacing", _ocean_vertex_spacing); ocean_shader_mat->set_shader_parameter("_vertex_density", 1.0f / _ocean_vertex_spacing); ocean_shader_mat->set_shader_parameter("_subdiv", pow(2.f, real_t(_ocean_tessellation_level))); } } } } void Terrain3D::_destroy_ocean_mesher(const bool p_final) { LOG(INFO, "Destroying ocean mesher"); if (_ocean_mesher) { _ocean_mesher->destroy(); if (p_final) { delete _ocean_mesher; _ocean_mesher = nullptr; } } } void Terrain3D::_setup_displacement_buffer() { if (!is_inside_tree()) { LOG(ERROR, "Not inside the tree, skipping displacement buffer setup"); return; } _destroy_displacement_buffer(); LOG(INFO, "Setting up displacement buffer"); _d_buffer_vp = memnew(SubViewport); _d_buffer_vp->set_name("DBufferViewport"); add_child(_d_buffer_vp, true); _d_buffer_vp->set_size(Vector2i(2, 2)); _d_buffer_vp->set_disable_3d(true); _d_buffer_vp->set_update_mode(SubViewport::UPDATE_ONCE); _d_buffer_vp->set_disable_input(true); _d_buffer_vp->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST); _d_buffer_rect = memnew(ColorRect); _d_buffer_rect->set_name("DBufferRect"); _d_buffer_vp->add_child(_d_buffer_rect, true); _d_buffer_rect->set_anchors_preset(Control::PRESET_FULL_RECT); } void Terrain3D::_update_displacement_buffer() { if (!_d_buffer_vp) { return; } if (_tessellation_level == 0) { _d_buffer_vp->set_size(V2I_ZERO); _d_buffer_rect->set_size(V2I_ZERO); } else { _d_buffer_vp->set_size(Vector2i(_mesh_size * 4 * _tessellation_level, _mesh_size * 4)); _d_buffer_rect->set_size(Vector2i(_mesh_size * 4 * _tessellation_level, _mesh_size * 4)); LOG(INFO, "Updating displacement buffer to Size: ", _d_buffer_vp->get_size()); if (_material.is_valid() && _material->get_material_rid().is_valid()) { RS->canvas_item_set_material(_d_buffer_rect->get_canvas_item(), _material->get_buffer_material_rid()); RS->material_set_param(_material->get_material_rid(), "_displacement_buffer", _d_buffer_vp->get_texture()->get_rid()); } } } void Terrain3D::_build_containers() { _label_parent = memnew(Node3D); _label_parent->set_name("Labels"); add_child(_label_parent, true); } void Terrain3D::_destroy_containers() { memdelete_safely(_label_parent); } void Terrain3D::_destroy_labels() { Array labels = _label_parent->get_children(); LOG(DEBUG, "Destroying ", labels.size(), " region labels"); for (const Variant &var : labels) { Node *label = cast_to(var); memdelete_safely(label); } } void Terrain3D::_destroy_displacement_buffer() { LOG(DEBUG, "Freeing d_buffer_rect"); memdelete_safely(_d_buffer_rect); LOG(DEBUG, "Freeing d_buffer_vp"); memdelete_safely(_d_buffer_vp); } void Terrain3D::_setup_mouse_picking() { if (!is_inside_tree()) { LOG(ERROR, "Not inside the tree, skipping mouse setup"); return; } LOG(INFO, "Setting up mouse picker and get_intersection viewport, camera & screen quad"); _mouse_vp = memnew(SubViewport); _mouse_vp->set_name("MouseViewport"); add_child(_mouse_vp, true); _mouse_vp->set_size(V2I(2)); _mouse_vp->set_scaling_3d_mode(Viewport::SCALING_3D_MODE_BILINEAR); _mouse_vp->set_update_mode(SubViewport::UPDATE_ONCE); _mouse_vp->set_disable_input(true); _mouse_vp->set_canvas_cull_mask(0); _mouse_vp->set_use_hdr_2d(true); _mouse_vp->set_anisotropic_filtering_level(Viewport::ANISOTROPY_DISABLED); _mouse_vp->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST); _mouse_vp->set_positional_shadow_atlas_size(0); _mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); _mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); _mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); _mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); _mouse_cam = memnew(Camera3D); _mouse_cam->set_name("MouseCamera"); _mouse_vp->add_child(_mouse_cam, true); Ref env; env.instantiate(); env->set_tonemapper(Environment::TONE_MAPPER_LINEAR); _mouse_cam->set_environment(env); Ref comp; comp.instantiate(); _mouse_cam->set_compositor(comp); _mouse_cam->set_projection(Camera3D::PROJECTION_ORTHOGONAL); _mouse_cam->set_size(0.1f); _mouse_cam->set_far(100000.f); _mouse_quad = memnew(MeshInstance3D); _mouse_quad->set_name("MouseQuad"); _mouse_cam->add_child(_mouse_quad, true); Ref quad; quad.instantiate(); quad->set_size(V2(0.1f)); _mouse_quad->set_mesh(quad); String shader_code = String( #include "shaders/gpu_depth.glsl" ); Ref shader; shader.instantiate(); shader->set_code(shader_code); Ref shader_material; shader_material.instantiate(); shader_material->set_shader(shader); _mouse_quad->set_surface_override_material(0, shader_material); _mouse_quad->set_position(Vector3(0.f, 0.f, -0.5f)); // Set terrain, terrain shader, mouse camera, and screen quad to mouse layer uint32_t force_update_layer = _mouse_layer; _mouse_layer = 0u; set_mouse_layer(force_update_layer); } void Terrain3D::_destroy_mouse_picking() { LOG(DEBUG, "Freeing mouse_quad"); memdelete_safely(_mouse_quad); LOG(DEBUG, "Freeing mouse_cam"); memdelete_safely(_mouse_cam); LOG(DEBUG, "Freeing mouse_vp"); memdelete_safely(_mouse_vp); } void Terrain3D::_destroy_instancer() { LOG(INFO, "Destroying Instancer"); memdelete_safely(_instancer); } void Terrain3D::_generate_triangles(PackedVector3Array &p_vertices, PackedVector2Array *p_uvs, const int32_t p_lod, const Terrain3DData::HeightFilter p_filter, const bool p_require_nav, const AABB &p_global_aabb) const { ERR_FAIL_COND(_data == nullptr); int32_t step = 1 << CLAMP(p_lod, 0, 8); // Bake whole mesh, e.g. bake_mesh and painted navigation if (!p_global_aabb.has_volume()) { int32_t region_size = (int32_t)_region_size; TypedArray region_locations = _data->get_region_locations(); for (const Vector2i ®ion_loc : region_locations) { Vector2i region_pos = region_loc * region_size; for (int32_t z = region_pos.y; z < region_pos.y + region_size; z += step) { for (int32_t x = region_pos.x; x < region_pos.x + region_size; x += step) { _generate_triangle_pair(p_vertices, p_uvs, p_lod, p_filter, p_require_nav, x, z); } } } } else { // Bake within an AABB, e.g. runtime navigation baker int32_t z_start = (int32_t)Math::ceil(p_global_aabb.position.z / _vertex_spacing); int32_t z_end = (int32_t)Math::floor(p_global_aabb.get_end().z / _vertex_spacing) + 1; int32_t x_start = (int32_t)Math::ceil(p_global_aabb.position.x / _vertex_spacing); int32_t x_end = (int32_t)Math::floor(p_global_aabb.get_end().x / _vertex_spacing) + 1; for (int32_t z = z_start; z < z_end; ++z) { for (int32_t x = x_start; x < x_end; ++x) { real_t height = _data->get_height(Vector3(x, 0.f, z)); if (height >= p_global_aabb.position.y && height <= p_global_aabb.get_end().y) { _generate_triangle_pair(p_vertices, p_uvs, p_lod, p_filter, p_require_nav, x, z); } } } } } // Generates two triangles: Top 124, Bottom 143 // 1 __ 2 // |\ | // | \| // 3 -- 4 // p_vertices is assumed to exist and the destination for data // p_uvs might not exist, so a pointer is fine // p_require_nav is false for the runtime baker, which ignores navigation void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVector2Array *p_uvs, const int32_t p_lod, const Terrain3DData::HeightFilter p_filter, const bool p_require_nav, const int32_t x, const int32_t z) const { int32_t step = 1 << CLAMP(p_lod, 0, 8); Vector3 xz = Vector3(x, 0.0f, z) * _vertex_spacing; Vector3 xsz = Vector3(x + step, 0.0f, z) * _vertex_spacing; Vector3 xzs = Vector3(x, 0.0f, z + step) * _vertex_spacing; Vector3 xszs = Vector3(x + step, 0.0f, z + step) * _vertex_spacing; Vector3 v1 = _data->get_mesh_vertex(p_lod, p_filter, xz); bool nan1 = std::isnan(v1.y); if (nan1) { return; } Vector3 v2 = _data->get_mesh_vertex(p_lod, p_filter, xsz); Vector3 v3 = _data->get_mesh_vertex(p_lod, p_filter, xzs); Vector3 v4 = _data->get_mesh_vertex(p_lod, p_filter, xszs); bool nan2 = std::isnan(v2.y); bool nan3 = std::isnan(v3.y); bool nan4 = std::isnan(v4.y); // If on the region edge, duplicate the edge pixels // Check #2 upper right if (nan2) { v2.y = v1.y; } // Check #3 lower left if (nan3) { v3.y = v1.y; } // Check #4 lower right if (nan4) { if (!nan2) { v4.y = v2.y; } else if (!nan3) { v4.y = v3.y; } else { v4.y = v1.y; } } uint32_t ctrl1 = _data->get_control(xz); uint32_t ctrl2 = _data->get_control(xsz); uint32_t ctrl3 = _data->get_control(xzs); uint32_t ctrl4 = _data->get_control(xszs); // Holes are only where the control map is valid and the bit is set bool hole1 = ctrl1 != UINT32_MAX && is_hole(ctrl1); bool hole2 = ctrl2 != UINT32_MAX && is_hole(ctrl2); bool hole3 = ctrl3 != UINT32_MAX && is_hole(ctrl3); bool hole4 = ctrl4 != UINT32_MAX && is_hole(ctrl4); // Navigation is where the control map is valid and the bit is set, or it's the region edge and nav1 is set bool nav1 = ctrl1 != UINT32_MAX && is_nav(ctrl1); bool nav2 = ctrl2 != UINT32_MAX && is_nav(ctrl2) || nan2 && nav1; bool nav3 = ctrl3 != UINT32_MAX && is_nav(ctrl3) || nan3 && nav1; bool nav4 = ctrl4 != UINT32_MAX && is_nav(ctrl4) || nan4 && nav1; //Bottom 143 triangle if (!(hole1 || hole4 || hole3) && (!p_require_nav || (nav1 && nav4 && nav3))) { p_vertices.push_back(v1); p_vertices.push_back(v4); p_vertices.push_back(v3); if (p_uvs) { p_uvs->push_back(Vector2(v1.x, v1.z)); p_uvs->push_back(Vector2(v4.x, v4.z)); p_uvs->push_back(Vector2(v3.x, v3.z)); } } // Top 124 triangle if (!(hole1 || hole2 || hole4) && (!p_require_nav || (nav1 && nav2 && nav4))) { p_vertices.push_back(v1); p_vertices.push_back(v2); p_vertices.push_back(v4); if (p_uvs) { p_uvs->push_back(Vector2(v1.x, v1.z)); p_uvs->push_back(Vector2(v2.x, v2.z)); p_uvs->push_back(Vector2(v4.x, v4.z)); } } } /////////////////////////// // Public Functions /////////////////////////// Terrain3D::Terrain3D() { // Process the command line PackedStringArray args = OS::get_singleton()->get_cmdline_args(); for (int i = args.size() - 1; i >= 0; i--) { String arg = args[i]; if (arg.begins_with("--terrain3d-debug=")) { String value = arg.rsplit("=")[1]; if (value == "ERROR") { set_debug_level(ERROR); } else if (value == "INFO") { set_debug_level(INFO); } else if (value == "DEBUG") { set_debug_level(DEBUG); } else if (value == "EXTREME") { set_debug_level(EXTREME); } } } } void Terrain3D::set_debug_level(const DebugLevel p_level) { SET_IF_DIFF(debug_level, CLAMP(p_level, ERROR, EXTREME)); LOG(INFO, "Setting debug level: ", debug_level); } void Terrain3D::set_data_directory(String p_dir) { String old_dir = _data_directory; SET_IF_DIFF(_data_directory, p_dir); LOG(INFO, "Setting data directory to ", _data_directory); // If _data_directory was empty and now specified, and has no data // assume we want to retain the current data. // Otherwise, clear data and reload dir if (!old_dir.is_empty() || Util::get_files(p_dir, "terrain3d*.res").size() > 0) { _initialized = false; _destroy_labels(); _destroy_collision(); _destroy_instancer(); memdelete_safely(_data); _initialize(); } update_configuration_warnings(); } void Terrain3D::set_assets(const Ref &p_assets) { SET_IF_DIFF(_assets, p_assets); LOG(INFO, "Setting asset list"); _initialized = false; _initialize(); LOG(DEBUG, "Emitting assets_changed"); emit_signal("assets_changed"); } void Terrain3D::set_editor(Terrain3DEditor *p_editor) { if (p_editor && p_editor->is_queued_for_deletion()) { LOG(ERROR, "Attempted to set a node queued for deletion"); return; } SET_IF_DIFF(_editor, p_editor); LOG(INFO, "Setting Terrain3DEditor: ", _editor); if (_material.is_valid()) { _material->update(Terrain3DMaterial::FULL_REBUILD); } } void Terrain3D::set_plugin(Object *p_plugin) { if (p_plugin && p_plugin->is_queued_for_deletion()) { LOG(ERROR, "Attempted to set a node queued for deletion"); return; } SET_IF_DIFF(_editor_plugin, p_plugin); LOG(INFO, "Setting Editor Plugin: ", _editor_plugin); } void Terrain3D::set_region_size(const RegionSize p_size) { if (!is_valid_region_size(p_size)) { LOG(ERROR, "Invalid region size: ", p_size, ". Must be power of 2, 64-2048"); return; } SET_IF_DIFF(_region_size, p_size); LOG(INFO, "Setting region size: ", _region_size); if (_data) { _data->_region_size = _region_size; _data->_region_sizev = V2I(_region_size); } if (_material.is_valid()) { _material->update(); } _update_displacement_buffer(); } void Terrain3D::set_save_16_bit(const bool p_enabled) { SET_IF_DIFF(_save_16_bit, p_enabled); LOG(INFO, "Save heightmaps as 16-bit: ", _save_16_bit); TypedArray regions = _data->get_regions_active(); for (Ref region : regions) { region->set_modified(true); } } void Terrain3D::set_label_distance(const real_t p_distance) { SET_IF_DIFF(_label_distance, CLAMP(p_distance, 0.f, 100000.f)); LOG(INFO, "Setting region label distance: ", _label_distance); update_region_labels(); } void Terrain3D::set_label_size(const int p_size) { SET_IF_DIFF(_label_size, CLAMP(p_size, 24, 128)); LOG(INFO, "Setting region label size: ", _label_size); update_region_labels(); } void Terrain3D::update_region_labels() { _destroy_labels(); if (_label_distance > 0.f && _data) { TypedArray region_locations = _data->get_region_locations(); LOG(DEBUG, "Creating ", region_locations.size(), " region labels"); for (const Vector2i ®ion_loc : region_locations) { Label3D *label = memnew(Label3D); String text = region_loc; label->set_name("Label3D" + text.replace(" ", "")); label->set_pixel_size(.001f); label->set_billboard_mode(BaseMaterial3D::BILLBOARD_ENABLED); label->set_draw_flag(Label3D::FLAG_DOUBLE_SIDED, true); label->set_draw_flag(Label3D::FLAG_DISABLE_DEPTH_TEST, true); label->set_draw_flag(Label3D::FLAG_FIXED_SIZE, true); label->set_render_priority(127); label->set_outline_render_priority(126); label->set_text(text); label->set_modulate(Color(1.f, 1.f, 1.f, .5f)); label->set_outline_modulate(Color(0.f, 0.f, 0.f, .5f)); label->set_font_size(_label_size); label->set_outline_size(_label_size / 6); label->set_visibility_range_end(_label_distance); label->set_visibility_range_end_margin(_label_distance / 10.f); label->set_visibility_range_fade_mode(GeometryInstance3D::VISIBILITY_RANGE_FADE_SELF); _label_parent->add_child(label, true); Vector3 pos = Vector3(real_t(region_loc.x) + .5f, 0.f, real_t(region_loc.y) + .5f) * _region_size * _vertex_spacing; real_t height = _data->get_height(pos); pos.y = (std::isnan(height)) ? 0.f : height; label->set_position(pos); } } } void Terrain3D::set_camera(Camera3D *p_camera) { if (_camera.ptr() != p_camera) { LOG(EXTREME, "Setting camera: ", p_camera); _camera.set_target(p_camera); if (_clipmap_target.is_valid()) { set_physics_process(true); } } } void Terrain3D::set_clipmap_target(Node3D *p_node) { if (_clipmap_target.ptr() != p_node) { LOG(INFO, "Setting clipmap target: ", p_node); _clipmap_target.set_target(p_node); if (_clipmap_target.is_valid()) { set_physics_process(true); } } } Vector3 Terrain3D::get_clipmap_target_position() const { // In Editor, or no clipmap target, use camera if (IS_EDITOR || !_clipmap_target.get_target()) { if (Node3D *cam = _camera.get_target()) { return cam->get_global_position(); } } if (Node3D *target = _clipmap_target.get_target()) { return target->get_global_position(); } return V3_ZERO; } void Terrain3D::set_collision_target(Node3D *p_node) { if (_collision_target.ptr() != p_node) { LOG(INFO, "Setting collision target: ", p_node); _collision_target.set_target(p_node); if (_collision_target.is_valid()) { set_physics_process(true); } } } Vector3 Terrain3D::get_collision_target_position() const { // In Editor, always prefer camera if (IS_EDITOR) { if (Node3D *cam = _camera.get_target()) { return cam->get_global_position(); } } if (Node3D *target = _collision_target.get_target()) { return target->get_global_position(); } if (Node3D *target = _clipmap_target.get_target()) { return target->get_global_position(); } if (Node3D *cam = _camera.get_target()) { return cam->get_global_position(); } return V3_ZERO; } void Terrain3D::set_ocean_light_target(Node3D *p_node) { if (_ocean_light_target.ptr() != p_node) { LOG(INFO, "Setting directional light target: ", p_node); _ocean_light_target.set_target(p_node); if (_ocean_light_target.is_valid()) { set_physics_process(true); } } } void Terrain3D::snap() { if (_terrain_mesher) { _terrain_mesher->reset_target_position(); } if (_ocean_enabled && _ocean_mesher) { _ocean_mesher->reset_target_position(); } if (_collision) { _collision->reset_target_position(); } if (_tessellation_level > 0) { _last_buffer_position = V2_MAX; } } void Terrain3D::set_material(const Ref &p_material) { SET_IF_DIFF(_material, p_material); LOG(INFO, "Setting material"); _initialized = false; _initialize(); LOG(DEBUG, "Emitting material_changed"); emit_signal("material_changed"); } void Terrain3D::set_mesh_lods(const int p_count) { SET_IF_DIFF(_mesh_lods, CLAMP(p_count, 1, 10)); LOG(INFO, "Setting mesh levels: ", _mesh_lods); if (_terrain_mesher && _material.is_valid()) { _material->update(); _setup_terrain_mesher(); } } void Terrain3D::set_tessellation_level(const int p_level) { SET_IF_DIFF(_tessellation_level, CLAMP(p_level, 0, 6)); LOG(INFO, "Setting tessellation level: ", p_level); if (_terrain_mesher && _material.is_valid()) { _material->update(Terrain3DMaterial::FULL_REBUILD); _setup_terrain_mesher(); _update_displacement_buffer(); } notify_property_list_changed(); } void Terrain3D::set_mesh_size(const int p_size) { SET_IF_DIFF(_mesh_size, CLAMP(p_size & ~1, 8, 256)); // Ensure even LOG(INFO, "Setting mesh size: ", _mesh_size); if (_terrain_mesher && _material.is_valid()) { _material->update(); _setup_terrain_mesher(); _update_displacement_buffer(); } } void Terrain3D::set_vertex_spacing(const real_t p_spacing) { SET_IF_DIFF(_vertex_spacing, CLAMP(p_spacing, 0.25f, 100.0f)); LOG(INFO, "Setting vertex spacing: ", _vertex_spacing); if (_collision && _data && _instancer && _material.is_valid()) { _instancer->_update_vertex_spacing(_vertex_spacing); _data->_vertex_spacing = _vertex_spacing; update_region_labels(); _material->update(); _setup_terrain_mesher(); _collision->destroy(); _collision->build(); _update_displacement_buffer(); } } void Terrain3D::set_cull_margin(const real_t p_margin) { SET_IF_DIFF(_cull_margin, CLAMP(p_margin, 0.f, 100000.f)); LOG(INFO, "Setting extra cull margin: ", _cull_margin); if (_terrain_mesher) { _terrain_mesher->update_aabbs(); } } void Terrain3D::set_cast_shadows(const RenderingServer::ShadowCastingSetting p_cast_shadows) { SET_IF_DIFF(_cast_shadows, p_cast_shadows); if (_terrain_mesher) { _terrain_mesher->update(); } } void Terrain3D::set_gi_mode(const GeometryInstance3D::GIMode p_gi_mode) { SET_IF_DIFF(_gi_mode, p_gi_mode); if (_terrain_mesher) { _terrain_mesher->update(); } } void Terrain3D::set_render_layers(const uint32_t p_layers) { SET_IF_DIFF(_render_layers, p_layers); LOG(INFO, "Setting terrain render layers to: ", p_layers); if (_terrain_mesher) { _terrain_mesher->update(); } } void Terrain3D::set_ocean_enabled(const bool p_enabled) { SET_IF_DIFF(_ocean_enabled, p_enabled); LOG(INFO, "Setting ocean enabled: ", _ocean_enabled); if (_ocean_enabled) { _setup_ocean_mesher(); } else { _destroy_ocean_mesher(false); } } void Terrain3D::set_ocean_mesh_lods(const int p_count) { SET_IF_DIFF(_ocean_mesh_lods, CLAMP(p_count, 1, 10)); LOG(INFO, "Setting ocean mesh levels: ", _ocean_mesh_lods); if (_ocean_enabled) { _setup_ocean_mesher(); } } void Terrain3D::set_ocean_tessellation_level(const int p_level) { SET_IF_DIFF(_ocean_tessellation_level, CLAMP(p_level, 0, 6)); LOG(INFO, "Setting ocean tessellation level: ", p_level); if (_ocean_enabled) { _setup_ocean_mesher(); } } void Terrain3D::set_ocean_mesh_size(const int p_size) { SET_IF_DIFF(_ocean_mesh_size, CLAMP(p_size & ~1, 8, 256)); // Ensure even LOG(INFO, "Setting ocean mesh size: ", _ocean_mesh_size); if (_ocean_enabled) { _setup_ocean_mesher(); } } void Terrain3D::set_ocean_vertex_spacing(const real_t p_spacing) { SET_IF_DIFF(_ocean_vertex_spacing, CLAMP(p_spacing, 0.25f, 100.0f)); LOG(INFO, "Setting ocean vertex spacing: ", _ocean_vertex_spacing); if (_ocean_enabled) { _setup_ocean_mesher(); } } void Terrain3D::set_ocean_cull_margin(const real_t p_margin) { SET_IF_DIFF(_ocean_cull_margin, CLAMP(p_margin, 0.f, 100000.f)); LOG(INFO, "Setting extra cull margin: ", _ocean_cull_margin); if (_ocean_mesher) { _ocean_mesher->update_aabbs(_ocean_cull_margin, V2_ZERO); } } void Terrain3D::set_ocean_cast_shadows(const RenderingServer::ShadowCastingSetting p_cast_shadows) { SET_IF_DIFF(_ocean_cast_shadows, p_cast_shadows); if (_ocean_mesher) { _ocean_mesher->update(); } } void Terrain3D::set_ocean_gi_mode(const GeometryInstance3D::GIMode p_gi_mode) { SET_IF_DIFF(_ocean_gi_mode, p_gi_mode); if (_ocean_mesher) { _ocean_mesher->update(); } } void Terrain3D::set_ocean_render_layers(const uint32_t p_layers) { SET_IF_DIFF(_ocean_render_layers, p_layers); LOG(INFO, "Setting ocean render layers to: ", p_layers); if (_ocean_enabled) { _setup_ocean_mesher(); } } void Terrain3D::set_ocean_material(const Ref &p_material) { SET_IF_DIFF(_ocean_material, p_material); LOG(INFO, "Setting ocean material"); if (_ocean_enabled) { _setup_ocean_mesher(); } } void Terrain3D::set_mouse_layer(const uint32_t p_layer) { SET_IF_DIFF(_mouse_layer, CLAMP(p_layer, 21, 32)); uint32_t mouse_mask = 1 << (_mouse_layer - 1); LOG(INFO, "Setting mouse layer: ", _mouse_layer, " (", mouse_mask, ") on terrain mesh, material, mouse camera, mouse quad"); // Set terrain meshes to mouse layer // Mask off editor render layers by ORing user layers 1-20 and current mouse layer set_render_layers((_render_layers & 0xFFFFF) | mouse_mask); // Set terrain shader to exclude mouse camera from showing holes if (_material.is_valid()) { _material->set_shader_param("_mouse_layer", mouse_mask); } // Set mouse camera to see only mouse layer if (_mouse_cam) { _mouse_cam->set_cull_mask(mouse_mask); } // Set screenquad to mouse layer if (_mouse_quad) { _mouse_quad->set_layer_mask(mouse_mask); } } /* Returns the point a ray intersects the ground using either raymarching or the GPU depth texture * p_src_pos (camera position) * p_direction (camera direction looking at the terrain) * p_gpu_mode - false: use raymarching, true: use GPU mode * Returns Vec3(NAN) on error or vec3(3.402823466e+38F) on no intersection. Test w/ if (var.x < 3.4e38) */ Vector3 Terrain3D::get_intersection(const Vector3 &p_src_pos, const Vector3 &p_direction, const bool p_gpu_mode) { if (p_direction.is_zero_approx() || !p_direction.is_finite()) { LOG(ERROR, "Invalid direction vector: ", p_direction); return V3_NAN; } if (!p_src_pos.is_finite()) { LOG(ERROR, "Invalid source vector: ", p_src_pos); return V3_NAN; } Vector3 direction = p_direction.normalized(); // If looking straight down in a region, use get_height if (direction.y < -.99999f) { real_t height = _data->get_height(p_src_pos); if (std ::isfinite(height)) { return Vector3(p_src_pos.x, height, p_src_pos.z); } } // Raymarching mode if (!p_gpu_mode) { // Must start above terrain if in a region real_t height = _data->get_height(p_src_pos); if (height > p_src_pos.y) { // False if Nan return V3_MAX; } // Raymarch down the ray in small increments until we find the terrain height Vector3 point = p_src_pos; for (int i = 0; i < 4000; i++) { height = _data->get_height(point); if (point.y - height <= 0.f) { // Nan comparison is false, which continues loop return point; } point += direction; } return V3_MAX; } else { // Get depth from perspective camera snapshot if (!_mouse_cam) { LOG(ERROR, "Invalid mouse camera"); return V3_NAN; } // Position mouse cam one unit behind the requested position _mouse_cam->set_global_position(p_src_pos - direction); // If looking straight down, then we're not in a region, set rotation directly as look_at() doesn't work if (direction.y < -.99999f) { _mouse_cam->set_rotation_degrees(Vector3(-90.f, 0.f, 0.f)); } else { _mouse_cam->look_at(_mouse_cam->get_global_position() + direction, V3_UP); } _mouse_vp->set_update_mode(SubViewport::UPDATE_ONCE); Ref vp_tex = _mouse_vp->get_texture(); Ref vp_img = vp_tex->get_image(); // Read the depth pixel from the camera viewport Color screen_depth = vp_img->get_pixel(0, 0); // Get position from depth packed in RGB - unpack back to float. // Forward+ is 16bit, mobile and compatibility is 10bit. // Compatibility also has precision loss for values below 0.5, so // we use only the top half of the range, for 21bit depth encoded. real_t r = floor((screen_depth.r * 256.f) - 128.f); real_t g = floor((screen_depth.g * 256.f) - 128.f); real_t b = floor((screen_depth.b * 256.f) - 128.f); // Decode the full depth value real_t decoded_depth = (r + g / 127.f + b / (127.f * 127.f)) / 127.f; // Near-plane noise filter, or no hit (sky, underside, far clip) if (decoded_depth < 0.00001f || decoded_depth > 1.f) { // Catch editor ortho camera with src_pos.y at some random value around 500k if (direction.y < -.99999f && p_src_pos.y >= 100000.f) { return Vector3(p_src_pos.x, 0.f, p_src_pos.z); } return V3_MAX; } // Necessary for a near-far precision on hits if (decoded_depth > 0.99999f) { decoded_depth = 1.f; } // Denormalize distance to get real depth and terrain position. decoded_depth *= _mouse_cam->get_far(); // Project the camera position by the depth value to get the intersection point. return _mouse_cam->get_global_position() + direction * decoded_depth; } } /* Returns the results of a physics raycast, optionally excluding the terrain * p_src_pos (ray start position) * p_direction (ray direction * magnitude), relative to src_pos */ Dictionary Terrain3D::get_raycast_result(const Vector3 &p_src_pos, const Vector3 &p_direction, const uint32_t p_col_mask, const bool p_exclude_self) const { if (!_is_inside_world) { return Dictionary(); } PhysicsDirectSpaceState3D *space_state = get_world_3d()->get_direct_space_state(); if (!space_state) { LOG(ERROR, "Invalid PhysicsDirectSpaceState3D"); return Dictionary(); } Ref query = PhysicsRayQueryParameters3D::create(p_src_pos, p_src_pos + p_direction, p_col_mask); if (_collision && p_exclude_self) { query->set_exclude(TypedArray(_collision->get_rid())); } return space_state->intersect_ray(query); } /** * Generates a static ArrayMesh for the terrain. * p_lod (0-8): Determines the granularity of the generated mesh. * p_filter: Controls how vertices' Y coordinates are generated from the height map. * HEIGHT_FILTER_NEAREST: Samples the height map in a 'nearest neighbour' fashion. * HEIGHT_FILTER_MINIMUM: Samples a range of heights around each vertex and returns the lowest. * This takes longer than ..._NEAREST, but can be used to create occluders, since it can guarantee the * generated mesh will not extend above or outside the clipmap at any LOD. */ Ref Terrain3D::bake_mesh(const int p_lod, const Terrain3DData::HeightFilter p_filter) const { LOG(INFO, "Baking mesh at lod: ", p_lod, " with filter: ", p_filter); Ref result; ERR_FAIL_COND_V(_data == nullptr, result); Ref st; st.instantiate(); st->begin(Mesh::PRIMITIVE_TRIANGLES); PackedVector3Array vertices; PackedVector2Array uvs; _generate_triangles(vertices, &uvs, p_lod, p_filter, false, AABB()); ERR_FAIL_COND_V(vertices.size() != uvs.size(), result); for (int i = 0; i < vertices.size(); ++i) { st->set_uv(uvs[i]); st->add_vertex(vertices[i]); } st->index(); st->generate_normals(); st->generate_tangents(); st->optimize_indices_for_cache(); result = st->commit(); return result; } /** * Generates source geometry faces for input to nav mesh baking. Geometry is only generated where there * are no holes and the terrain has been painted as navigable. * p_global_aabb: If non-empty, geometry will be generated only within this AABB. If empty, geometry * will be generated for the entire terrain. * p_require_nav: If true, this function will only generate geometry for terrain marked navigable. * Otherwise, geometry is generated for the entire terrain within the AABB (which can be useful for * dynamic and/or runtime nav mesh baking). */ PackedVector3Array Terrain3D::generate_nav_mesh_source_geometry(const AABB &p_global_aabb, const bool p_require_nav) const { LOG(INFO, "Generating NavMesh source geometry from terrain"); PackedVector3Array faces; _generate_triangles(faces, nullptr, 0, Terrain3DData::HEIGHT_FILTER_NEAREST, p_require_nav, p_global_aabb); return faces; } void Terrain3D::set_warning(const uint8_t p_warning, const bool p_enabled) { if (p_enabled) { _warnings |= p_warning; } else { _warnings &= ~p_warning; } update_configuration_warnings(); } PackedStringArray Terrain3D::_get_configuration_warnings() const { PackedStringArray psa; if (_data_directory.is_empty()) { psa.push_back("No data directory specified. Select a directory then save the scene to write data."); } if (_warnings & WARN_MISMATCHED_SIZE) { psa.push_back("Texture dimensions don't match. Double-click a texture in the FileSystem panel to see its size. Read Texture Prep in docs."); } if (_warnings & WARN_MISMATCHED_FORMAT) { psa.push_back("Texture formats don't match. Double-click a texture in the FileSystem panel to see its format. Check Import panel. Read Texture Prep in docs."); } if (_warnings & WARN_MISMATCHED_MIPMAPS) { psa.push_back("Texture mipmap settings don't match. Change on the Import panel."); } return psa; } /////////////////////////// // Protected Functions /////////////////////////// // Notifications are defined in individual classes: Object, Node, Node3D // Listed below in order of operation void Terrain3D::_notification(const int p_what) { switch (p_what) { /// Startup notifications case NOTIFICATION_POSTINITIALIZE: { // Object initialized, before script is attached LOG(INFO, "NOTIFICATION_POSTINITIALIZE"); _build_containers(); break; } case NOTIFICATION_ENTER_WORLD: { // Node3D registered to new World3D resource // Sent on scene changes LOG(INFO, "NOTIFICATION_ENTER_WORLD"); _is_inside_world = true; if (_terrain_mesher) { _terrain_mesher->update(); } break; } case NOTIFICATION_ENTER_TREE: { // Node entered a SceneTree // Sent on scene changes LOG(INFO, "NOTIFICATION_ENTER_TREE"); set_as_top_level(true); // Don't inherit transforms from parent. Global only. set_notify_transform(true); set_meta("_edit_lock_", true); _setup_mouse_picking(); _setup_displacement_buffer(); // Reload editor textures - Also see READY if (_free_editor_textures && !IS_EDITOR && _assets.is_valid() && !_assets->get_path().contains("Terrain3DAssets")) { LOG(INFO, "free_editor_textures enabled, reloading Assets path: ", _assets->get_path()); _assets = ResourceLoader::get_singleton()->load(_assets->get_path(), "", ResourceLoader::CACHE_MODE_IGNORE); } _initialize(); // Rebuild anything freed: meshes, collision, instancer set_physics_process(true); break; } case NOTIFICATION_READY: { // Node is ready LOG(INFO, "NOTIFICATION_READY"); // Optional: Run the testing suite //#include "unit_testing.h" //test_differs(); // Clear editor textures - also see ENTER_TREE if (_free_editor_textures && !IS_EDITOR && _assets.is_valid()) { if (_assets->get_path().contains("Terrain3DAssets")) { LOG(WARN, "free_editor_textures requires `Assets` be saved to a file. Do so, or disable the former to turn off this warning"); } else { LOG(INFO, "free_editor_textures enabled, clearing texture assets"); _assets->clear_textures(); } } break; } /// Game Loop notifications case NOTIFICATION_PHYSICS_PROCESS: { // Node is processing one physics frame __physics_process(get_physics_process_delta_time()); break; } case NOTIFICATION_TRANSFORM_CHANGED: { // Node3D or parent transform changed if (get_transform() != Transform3D()) { set_transform(Transform3D()); } break; } case NOTIFICATION_VISIBILITY_CHANGED: { // Node3D visibility changed LOG(INFO, "NOTIFICATION_VISIBILITY_CHANGED"); if (_terrain_mesher) { _terrain_mesher->update(); } if (_instancer) { if (!is_visible_in_tree()) { _instancer->destroy(); } else { _instancer->update_mmis(-1, V2I_MAX, true); } } break; } case NOTIFICATION_EXTENSION_RELOADED: { // Object finished hot reloading LOG(INFO, "NOTIFICATION_EXTENSION_RELOADED"); break; } case NOTIFICATION_EDITOR_PRE_SAVE: { // Editor Node is about to save the current scene LOG(INFO, "NOTIFICATION_EDITOR_PRE_SAVE"); if (_data_directory.is_empty()) { LOG(ERROR, "Data directory is empty. Set it to save regions to disk."); } else if (!_data) { LOG(DEBUG, "Save requested, but no valid data object. Skipping"); } else { _data->save_directory(_data_directory); } if (!_material.is_valid()) { LOG(DEBUG, "Save requested, but no valid material. Skipping"); } else { _material->save(); } if (!_assets.is_valid()) { LOG(DEBUG, "Save requested, but no valid texture list. Skipping"); } else { _assets->save(); } break; } case NOTIFICATION_EDITOR_POST_SAVE: { // Editor Node finished saving current scene break; } case NOTIFICATION_CRASH: { // Godot's crash handler reports engine is about to crash // Only works on desktop if the crash handler is enabled LOG(INFO, "NOTIFICATION_CRASH"); break; } /// Shut down notifications case NOTIFICATION_EXIT_TREE: { // Node is about to exit a SceneTree // Sent on scene changes LOG(INFO, "NOTIFICATION_EXIT_TREE"); set_physics_process(false); _destroy_terrain_mesher(); _destroy_ocean_mesher(); _destroy_instancer(); _destroy_mouse_picking(); _destroy_displacement_buffer(); if (_assets.is_valid()) { _assets->uninitialize(); } if (_material.is_valid()) { _material->uninitialize(); } _initialized = false; break; } case NOTIFICATION_EXIT_WORLD: { // Node3D unregistered from current World3D // Sent on scene changes LOG(INFO, "NOTIFICATION_EXIT_WORLD"); _is_inside_world = false; break; } case NOTIFICATION_PREDELETE: { // Object is about to be deleted LOG(INFO, "NOTIFICATION_PREDELETE"); _destroy_terrain_mesher(true); _destroy_ocean_mesher(true); _destroy_instancer(); _destroy_collision(true); _assets.unref(); _material.unref(); memdelete_safely(_data); _destroy_labels(); _destroy_containers(); break; } default: break; } } void Terrain3D::_validate_property(PropertyInfo &p_property) const { if (_tessellation_level == 0) { // Hide all displacement properties if (p_property.name == StringName("displacement_scale") || p_property.name == StringName("displacement_sharpness") || p_property.name == StringName("buffer_shader_override_enabled") || p_property.name == StringName("buffer_shader_override")) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } } if (!_ocean_enabled) { // Hide all ocean properties if (p_property.name == StringName("ocean_mesh_lods") || p_property.name == StringName("ocean_mesh_size") || p_property.name == StringName("ocean_tessellation_level") || p_property.name == StringName("ocean_material") || p_property.name == StringName("ocean_vertex_spacing")) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } } } void Terrain3D::_bind_methods() { BIND_ENUM_CONSTANT(ERROR); BIND_ENUM_CONSTANT(INFO); BIND_ENUM_CONSTANT(DEBUG); BIND_ENUM_CONSTANT(EXTREME); BIND_ENUM_CONSTANT(SIZE_64); BIND_ENUM_CONSTANT(SIZE_128); BIND_ENUM_CONSTANT(SIZE_256); BIND_ENUM_CONSTANT(SIZE_512); BIND_ENUM_CONSTANT(SIZE_1024); BIND_ENUM_CONSTANT(SIZE_2048); ClassDB::bind_method(D_METHOD("get_version"), &Terrain3D::get_version); ClassDB::bind_method(D_METHOD("set_debug_level", "level"), &Terrain3D::set_debug_level); ClassDB::bind_method(D_METHOD("get_debug_level"), &Terrain3D::get_debug_level); ClassDB::bind_method(D_METHOD("set_data_directory", "directory"), &Terrain3D::set_data_directory); ClassDB::bind_method(D_METHOD("get_data_directory"), &Terrain3D::get_data_directory); // Object references ClassDB::bind_method(D_METHOD("get_data"), &Terrain3D::get_data); ClassDB::bind_method(D_METHOD("set_material", "material"), &Terrain3D::set_material); ClassDB::bind_method(D_METHOD("get_material"), &Terrain3D::get_material); ClassDB::bind_method(D_METHOD("set_assets", "assets"), &Terrain3D::set_assets); ClassDB::bind_method(D_METHOD("get_assets"), &Terrain3D::get_assets); ClassDB::bind_method(D_METHOD("get_collision"), &Terrain3D::get_collision); ClassDB::bind_method(D_METHOD("get_instancer"), &Terrain3D::get_instancer); ClassDB::bind_method(D_METHOD("set_editor", "editor"), &Terrain3D::set_editor); ClassDB::bind_method(D_METHOD("get_editor"), &Terrain3D::get_editor); ClassDB::bind_method(D_METHOD("set_plugin", "plugin"), &Terrain3D::set_plugin); ClassDB::bind_method(D_METHOD("get_plugin"), &Terrain3D::get_plugin); // Regions ClassDB::bind_method(D_METHOD("change_region_size", "size"), &Terrain3D::change_region_size); ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3D::get_region_size); ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3D::set_save_16_bit); ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3D::get_save_16_bit); ClassDB::bind_method(D_METHOD("set_label_distance", "distance"), &Terrain3D::set_label_distance); ClassDB::bind_method(D_METHOD("get_label_distance"), &Terrain3D::get_label_distance); ClassDB::bind_method(D_METHOD("set_label_size", "size"), &Terrain3D::set_label_size); ClassDB::bind_method(D_METHOD("get_label_size"), &Terrain3D::get_label_size); // Target Tracking ClassDB::bind_method(D_METHOD("set_camera", "camera"), &Terrain3D::set_camera); ClassDB::bind_method(D_METHOD("get_camera"), &Terrain3D::get_camera); ClassDB::bind_method(D_METHOD("set_clipmap_target", "node"), &Terrain3D::set_clipmap_target); ClassDB::bind_method(D_METHOD("get_clipmap_target"), &Terrain3D::get_clipmap_target); ClassDB::bind_method(D_METHOD("get_clipmap_target_position"), &Terrain3D::get_clipmap_target_position); ClassDB::bind_method(D_METHOD("set_collision_target", "node"), &Terrain3D::set_collision_target); ClassDB::bind_method(D_METHOD("get_collision_target"), &Terrain3D::get_collision_target); ClassDB::bind_method(D_METHOD("get_collision_target_position"), &Terrain3D::get_collision_target_position); ClassDB::bind_method(D_METHOD("set_ocean_light_target", "node"), &Terrain3D::set_ocean_light_target); ClassDB::bind_method(D_METHOD("get_ocean_light_target"), &Terrain3D::get_ocean_light_target); ClassDB::bind_method(D_METHOD("snap"), &Terrain3D::snap); // Collision ClassDB::bind_method(D_METHOD("set_collision_mode", "mode"), &Terrain3D::set_collision_mode); ClassDB::bind_method(D_METHOD("get_collision_mode"), &Terrain3D::get_collision_mode); ClassDB::bind_method(D_METHOD("set_collision_shape_size", "size"), &Terrain3D::set_collision_shape_size); ClassDB::bind_method(D_METHOD("get_collision_shape_size"), &Terrain3D::get_collision_shape_size); ClassDB::bind_method(D_METHOD("set_collision_radius", "radius"), &Terrain3D::set_collision_radius); ClassDB::bind_method(D_METHOD("get_collision_radius"), &Terrain3D::get_collision_radius); ClassDB::bind_method(D_METHOD("set_collision_layer", "layers"), &Terrain3D::set_collision_layer); ClassDB::bind_method(D_METHOD("get_collision_layer"), &Terrain3D::get_collision_layer); ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &Terrain3D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &Terrain3D::get_collision_mask); ClassDB::bind_method(D_METHOD("set_collision_priority", "priority"), &Terrain3D::set_collision_priority); ClassDB::bind_method(D_METHOD("get_collision_priority"), &Terrain3D::get_collision_priority); ClassDB::bind_method(D_METHOD("set_physics_material", "material"), &Terrain3D::set_physics_material); ClassDB::bind_method(D_METHOD("get_physics_material"), &Terrain3D::get_physics_material); // Terrain Mesh ClassDB::bind_method(D_METHOD("set_mesh_lods", "count"), &Terrain3D::set_mesh_lods); ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); ClassDB::bind_method(D_METHOD("set_mesh_size", "size"), &Terrain3D::set_mesh_size); ClassDB::bind_method(D_METHOD("get_mesh_size"), &Terrain3D::get_mesh_size); ClassDB::bind_method(D_METHOD("set_tessellation_level", "size"), &Terrain3D::set_tessellation_level); ClassDB::bind_method(D_METHOD("get_tessellation_level"), &Terrain3D::get_tessellation_level); ClassDB::bind_method(D_METHOD("set_vertex_spacing", "scale"), &Terrain3D::set_vertex_spacing); ClassDB::bind_method(D_METHOD("get_vertex_spacing"), &Terrain3D::get_vertex_spacing); ClassDB::bind_method(D_METHOD("set_cull_margin", "margin"), &Terrain3D::set_cull_margin); ClassDB::bind_method(D_METHOD("get_cull_margin"), &Terrain3D::get_cull_margin); ClassDB::bind_method(D_METHOD("set_cast_shadows", "shadow_casting_setting"), &Terrain3D::set_cast_shadows); ClassDB::bind_method(D_METHOD("get_cast_shadows"), &Terrain3D::get_cast_shadows); ClassDB::bind_method(D_METHOD("set_gi_mode", "gi_mode"), &Terrain3D::set_gi_mode); ClassDB::bind_method(D_METHOD("get_gi_mode"), &Terrain3D::get_gi_mode); ClassDB::bind_method(D_METHOD("set_render_layers", "layers"), &Terrain3D::set_render_layers); ClassDB::bind_method(D_METHOD("get_render_layers"), &Terrain3D::get_render_layers); // Terrain Displacement ClassDB::bind_method(D_METHOD("set_displacement_scale", "scale"), &Terrain3D::set_displacement_scale); ClassDB::bind_method(D_METHOD("get_displacement_scale"), &Terrain3D::get_displacement_scale); ClassDB::bind_method(D_METHOD("set_displacement_sharpness", "sharpness"), &Terrain3D::set_displacement_sharpness); ClassDB::bind_method(D_METHOD("get_displacement_sharpness"), &Terrain3D::get_displacement_sharpness); ClassDB::bind_method(D_METHOD("set_buffer_shader_override_enabled", "enabled"), &Terrain3D::set_buffer_shader_override_enabled); ClassDB::bind_method(D_METHOD("is_buffer_shader_override_enabled"), &Terrain3D::is_buffer_shader_override_enabled); ClassDB::bind_method(D_METHOD("set_buffer_shader_override", "shader"), &Terrain3D::set_buffer_shader_override); ClassDB::bind_method(D_METHOD("get_buffer_shader_override"), &Terrain3D::get_buffer_shader_override); // Ocean Mesh ClassDB::bind_method(D_METHOD("set_ocean_enabled", "enabled"), &Terrain3D::set_ocean_enabled); ClassDB::bind_method(D_METHOD("is_ocean_enabled"), &Terrain3D::is_ocean_enabled); ClassDB::bind_method(D_METHOD("set_ocean_mesh_lods", "count"), &Terrain3D::set_ocean_mesh_lods); ClassDB::bind_method(D_METHOD("get_ocean_mesh_lods"), &Terrain3D::get_ocean_mesh_lods); ClassDB::bind_method(D_METHOD("set_ocean_tessellation_level", "size"), &Terrain3D::set_ocean_tessellation_level); ClassDB::bind_method(D_METHOD("get_ocean_tessellation_level"), &Terrain3D::get_ocean_tessellation_level); ClassDB::bind_method(D_METHOD("set_ocean_mesh_size", "size"), &Terrain3D::set_ocean_mesh_size); ClassDB::bind_method(D_METHOD("get_ocean_mesh_size"), &Terrain3D::get_ocean_mesh_size); ClassDB::bind_method(D_METHOD("set_ocean_vertex_spacing", "scale"), &Terrain3D::set_ocean_vertex_spacing); ClassDB::bind_method(D_METHOD("get_ocean_vertex_spacing"), &Terrain3D::get_ocean_vertex_spacing); ClassDB::bind_method(D_METHOD("set_ocean_cull_margin", "margin"), &Terrain3D::set_ocean_cull_margin); ClassDB::bind_method(D_METHOD("get_ocean_cull_margin"), &Terrain3D::get_ocean_cull_margin); ClassDB::bind_method(D_METHOD("set_ocean_cast_shadows", "shadow_casting_setting"), &Terrain3D::set_ocean_cast_shadows); ClassDB::bind_method(D_METHOD("get_ocean_cast_shadows"), &Terrain3D::get_ocean_cast_shadows); ClassDB::bind_method(D_METHOD("set_ocean_gi_mode", "gi_mode"), &Terrain3D::set_ocean_gi_mode); ClassDB::bind_method(D_METHOD("get_ocean_gi_mode"), &Terrain3D::get_ocean_gi_mode); ClassDB::bind_method(D_METHOD("set_ocean_render_layers", "layers"), &Terrain3D::set_ocean_render_layers); ClassDB::bind_method(D_METHOD("get_ocean_render_layers"), &Terrain3D::get_ocean_render_layers); ClassDB::bind_method(D_METHOD("set_ocean_material", "material"), &Terrain3D::set_ocean_material); ClassDB::bind_method(D_METHOD("get_ocean_material"), &Terrain3D::get_ocean_material); // Rendering ClassDB::bind_method(D_METHOD("set_mouse_layer", "layer"), &Terrain3D::set_mouse_layer); ClassDB::bind_method(D_METHOD("get_mouse_layer"), &Terrain3D::get_mouse_layer); ClassDB::bind_method(D_METHOD("set_free_editor_textures"), &Terrain3D::set_free_editor_textures); ClassDB::bind_method(D_METHOD("get_free_editor_textures"), &Terrain3D::get_free_editor_textures); ClassDB::bind_method(D_METHOD("set_instancer_mode", "mode"), &Terrain3D::set_instancer_mode); ClassDB::bind_method(D_METHOD("get_instancer_mode"), &Terrain3D::get_instancer_mode); // Overlays ClassDB::bind_method(D_METHOD("set_show_region_grid", "enabled"), &Terrain3D::set_show_region_grid); ClassDB::bind_method(D_METHOD("get_show_region_grid"), &Terrain3D::get_show_region_grid); ClassDB::bind_method(D_METHOD("set_show_instancer_grid", "enabled"), &Terrain3D::set_show_instancer_grid); ClassDB::bind_method(D_METHOD("get_show_instancer_grid"), &Terrain3D::get_show_instancer_grid); ClassDB::bind_method(D_METHOD("set_show_vertex_grid", "enabled"), &Terrain3D::set_show_vertex_grid); ClassDB::bind_method(D_METHOD("get_show_vertex_grid"), &Terrain3D::get_show_vertex_grid); ClassDB::bind_method(D_METHOD("set_show_contours", "enabled"), &Terrain3D::set_show_contours); ClassDB::bind_method(D_METHOD("get_show_contours"), &Terrain3D::get_show_contours); ClassDB::bind_method(D_METHOD("set_show_navigation", "enabled"), &Terrain3D::set_show_navigation); ClassDB::bind_method(D_METHOD("get_show_navigation"), &Terrain3D::get_show_navigation); // Debug Views ClassDB::bind_method(D_METHOD("set_show_checkered", "enabled"), &Terrain3D::set_show_checkered); ClassDB::bind_method(D_METHOD("get_show_checkered"), &Terrain3D::get_show_checkered); ClassDB::bind_method(D_METHOD("set_show_grey", "enabled"), &Terrain3D::set_show_grey); ClassDB::bind_method(D_METHOD("get_show_grey"), &Terrain3D::get_show_grey); ClassDB::bind_method(D_METHOD("set_show_heightmap", "enabled"), &Terrain3D::set_show_heightmap); ClassDB::bind_method(D_METHOD("get_show_heightmap"), &Terrain3D::get_show_heightmap); ClassDB::bind_method(D_METHOD("set_show_jaggedness", "enabled"), &Terrain3D::set_show_jaggedness); ClassDB::bind_method(D_METHOD("get_show_jaggedness"), &Terrain3D::get_show_jaggedness); ClassDB::bind_method(D_METHOD("set_show_autoshader", "enabled"), &Terrain3D::set_show_autoshader); ClassDB::bind_method(D_METHOD("get_show_autoshader"), &Terrain3D::get_show_autoshader); ClassDB::bind_method(D_METHOD("set_show_control_texture", "enabled"), &Terrain3D::set_show_control_texture); ClassDB::bind_method(D_METHOD("get_show_control_texture"), &Terrain3D::get_show_control_texture); ClassDB::bind_method(D_METHOD("set_show_control_blend", "enabled"), &Terrain3D::set_show_control_blend); ClassDB::bind_method(D_METHOD("get_show_control_blend"), &Terrain3D::get_show_control_blend); ClassDB::bind_method(D_METHOD("set_show_control_angle", "enabled"), &Terrain3D::set_show_control_angle); ClassDB::bind_method(D_METHOD("get_show_control_angle"), &Terrain3D::get_show_control_angle); ClassDB::bind_method(D_METHOD("set_show_control_scale", "enabled"), &Terrain3D::set_show_control_scale); ClassDB::bind_method(D_METHOD("get_show_control_scale"), &Terrain3D::get_show_control_scale); ClassDB::bind_method(D_METHOD("set_show_colormap", "enabled"), &Terrain3D::set_show_colormap); ClassDB::bind_method(D_METHOD("get_show_colormap"), &Terrain3D::get_show_colormap); ClassDB::bind_method(D_METHOD("set_show_roughmap", "enabled"), &Terrain3D::set_show_roughmap); ClassDB::bind_method(D_METHOD("get_show_roughmap"), &Terrain3D::get_show_roughmap); ClassDB::bind_method(D_METHOD("set_show_displacement_buffer", "enabled"), &Terrain3D::set_show_displacement_buffer); ClassDB::bind_method(D_METHOD("get_show_displacement_buffer"), &Terrain3D::get_show_displacement_buffer); // PBR Views ClassDB::bind_method(D_METHOD("set_show_texture_albedo", "enabled"), &Terrain3D::set_show_texture_albedo); ClassDB::bind_method(D_METHOD("get_show_texture_albedo"), &Terrain3D::get_show_texture_albedo); ClassDB::bind_method(D_METHOD("set_show_texture_height", "enabled"), &Terrain3D::set_show_texture_height); ClassDB::bind_method(D_METHOD("get_show_texture_height"), &Terrain3D::get_show_texture_height); ClassDB::bind_method(D_METHOD("set_show_texture_normal", "enabled"), &Terrain3D::set_show_texture_normal); ClassDB::bind_method(D_METHOD("get_show_texture_normal"), &Terrain3D::get_show_texture_normal); ClassDB::bind_method(D_METHOD("set_show_texture_rough", "enabled"), &Terrain3D::set_show_texture_rough); ClassDB::bind_method(D_METHOD("get_show_texture_rough"), &Terrain3D::get_show_texture_rough); ClassDB::bind_method(D_METHOD("set_show_texture_ao", "enabled"), &Terrain3D::set_show_texture_ao); ClassDB::bind_method(D_METHOD("get_show_texture_ao"), &Terrain3D::get_show_texture_ao); // Utility ClassDB::bind_method(D_METHOD("get_intersection", "src_pos", "direction", "gpu_mode"), &Terrain3D::get_intersection, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_raycast_result", "src_pos", "direction", "collision_mask", "exclude_terrain"), &Terrain3D::get_raycast_result, DEFVAL(0xFFFFFFFF), DEFVAL(false)); ClassDB::bind_method(D_METHOD("bake_mesh", "lod", "filter"), &Terrain3D::bake_mesh, DEFVAL(Terrain3DData::HEIGHT_FILTER_NEAREST)); ClassDB::bind_method(D_METHOD("generate_nav_mesh_source_geometry", "global_aabb", "require_nav"), &Terrain3D::generate_nav_mesh_source_geometry, DEFVAL(true)); ADD_PROPERTY(PropertyInfo(Variant::STRING, "version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_level", PROPERTY_HINT_ENUM, "Errors,Info,Debug,Extreme"), "set_debug_level", "get_debug_level"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "data_directory", PROPERTY_HINT_DIR), "set_data_directory", "get_data_directory"); // Object references ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "assets", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DAssets"), "set_assets", "get_assets"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE, "Terrain3DData"), "", "get_data"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collision", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE, "Terrain3DCollision"), "", "get_collision"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "instancer", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE, "Terrain3DInstancer"), "", "get_instancer"); ADD_GROUP("Regions", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64,128:128,256:256,512:512,1024:1024,2048:2048", PROPERTY_USAGE_EDITOR), "change_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit"), "set_save_16_bit", "get_save_16_bit"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "label_distance", PROPERTY_HINT_RANGE, "0.0,10000.0,0.5,or_greater"), "set_label_distance", "get_label_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "label_size", PROPERTY_HINT_RANGE, "24,128,1"), "set_label_size", "get_label_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grid"), "set_show_region_grid", "get_show_region_grid"); ADD_GROUP("Collision", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mode", PROPERTY_HINT_ENUM, "Disabled,Dynamic / Game,Dynamic / Editor,Full / Game,Full / Editor"), "set_collision_mode", "get_collision_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_shape_size", PROPERTY_HINT_RANGE, "8,64,8"), "set_collision_shape_size", "get_collision_shape_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_radius", PROPERTY_HINT_RANGE, "16,256,16"), "set_collision_radius", "get_collision_radius"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collision_target", PROPERTY_HINT_NODE_TYPE, "Node3D", PROPERTY_USAGE_DEFAULT, "Node3D"), "set_collision_target", "get_collision_target"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_priority", PROPERTY_HINT_RANGE, "0.1,256,.1"), "set_collision_priority", "get_collision_priority"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material", "get_physics_material"); ADD_GROUP("Terrain Mesh", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "clipmap_target", PROPERTY_HINT_NODE_TYPE, "Node3D", PROPERTY_USAGE_DEFAULT, "Node3D"), "set_clipmap_target", "get_clipmap_target"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_lods", PROPERTY_HINT_RANGE, "1,10,1"), "set_mesh_lods", "get_mesh_lods"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tessellation_level", PROPERTY_HINT_RANGE, "0,6,1"), "set_tessellation_level", "get_tessellation_level"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_size", PROPERTY_HINT_RANGE, "8,256,2"), "set_mesh_size", "get_mesh_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vertex_spacing", PROPERTY_HINT_RANGE, "0.25,10.0,or_greater"), "set_vertex_spacing", "get_vertex_spacing"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cull_margin", PROPERTY_HINT_RANGE, "0.0,10000.0,.5,or_greater"), "set_cull_margin", "get_cull_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadows", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows", "get_cast_shadows"); ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_gi_mode", "get_gi_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "render_layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_render_layers", "get_render_layers"); ADD_SUBGROUP("Displacement", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_scale", PROPERTY_HINT_RANGE, "0.0, 2.0, 0.01"), "set_displacement_scale", "get_displacement_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_sharpness", PROPERTY_HINT_RANGE, "0.0, 1.0, 0.01"), "set_displacement_sharpness", "get_displacement_sharpness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "buffer_shader_override_enabled"), "set_buffer_shader_override_enabled", "is_buffer_shader_override_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "buffer_shader_override", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_buffer_shader_override", "get_buffer_shader_override"); ADD_GROUP("Ocean Mesh", "ocean_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ocean_enabled"), "set_ocean_enabled", "is_ocean_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ocean_mesh_lods", PROPERTY_HINT_RANGE, "1,10,1"), "set_ocean_mesh_lods", "get_ocean_mesh_lods"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ocean_tessellation_level", PROPERTY_HINT_RANGE, "0,6,1"), "set_ocean_tessellation_level", "get_ocean_tessellation_level"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ocean_mesh_size", PROPERTY_HINT_RANGE, "8,256,2"), "set_ocean_mesh_size", "get_ocean_mesh_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ocean_vertex_spacing", PROPERTY_HINT_RANGE, "0.25,10.0,0.05,or_greater"), "set_ocean_vertex_spacing", "get_ocean_vertex_spacing"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ocean_cull_margin", PROPERTY_HINT_RANGE, "0.0,10000.0,.5,or_greater"), "set_ocean_cull_margin", "get_ocean_cull_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ocean_cast_shadows", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_ocean_cast_shadows", "get_ocean_cast_shadows"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ocean_gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_ocean_gi_mode", "get_ocean_gi_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ocean_render_layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_ocean_render_layers", "get_ocean_render_layers"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ocean_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,BaseMaterial3D"), "set_ocean_material", "get_ocean_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ocean_light_target", PROPERTY_HINT_NODE_TYPE, "DirectionalLight3D", PROPERTY_USAGE_DEFAULT, "Node3D"), "set_ocean_light_target", "get_ocean_light_target"); ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_layer", PROPERTY_HINT_RANGE, "21, 32"), "set_mouse_layer", "get_mouse_layer"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "free_editor_textures"), "set_free_editor_textures", "get_free_editor_textures"); ADD_PROPERTY(PropertyInfo(Variant::INT, "instancer_mode", PROPERTY_HINT_ENUM, "Disabled,Normal"), "set_instancer_mode", "get_instancer_mode"); ADD_GROUP("Overlays", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_region_grid"), "set_show_region_grid", "get_show_region_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_instancer_grid"), "set_show_instancer_grid", "get_show_instancer_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_vertex_grid"), "set_show_vertex_grid", "get_show_vertex_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_contours"), "set_show_contours", "get_show_contours"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_navigation"), "set_show_navigation", "get_show_navigation"); ADD_GROUP("Debug Views", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_checkered"), "set_show_checkered", "get_show_checkered"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grey"), "set_show_grey", "get_show_grey"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_heightmap"), "set_show_heightmap", "get_show_heightmap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_jaggedness"), "set_show_jaggedness", "get_show_jaggedness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_autoshader"), "set_show_autoshader", "get_show_autoshader"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_texture"), "set_show_control_texture", "get_show_control_texture"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_blend"), "set_show_control_blend", "get_show_control_blend"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_angle"), "set_show_control_angle", "get_show_control_angle"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_scale"), "set_show_control_scale", "get_show_control_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_colormap"), "set_show_colormap", "get_show_colormap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_roughmap"), "set_show_roughmap", "get_show_roughmap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_displacement_buffer"), "set_show_displacement_buffer", "get_show_displacement_buffer"); ADD_SUBGROUP("PBR Maps", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_albedo"), "set_show_texture_albedo", "get_show_texture_albedo"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_height"), "set_show_texture_height", "get_show_texture_height"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_normal"), "set_show_texture_normal", "get_show_texture_normal"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_rough"), "set_show_texture_rough", "get_show_texture_rough"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_ao"), "set_show_texture_ao", "get_show_texture_ao"); ADD_SIGNAL(MethodInfo("material_changed")); ADD_SIGNAL(MethodInfo("assets_changed")); } ================================================ FILE: src/terrain_3d.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_CLASS_H #define TERRAIN3D_CLASS_H #include #include #include #include #include #include #include #include #include "constants.h" #include "target_node_3d.h" #include "terrain_3d_assets.h" #include "terrain_3d_collision.h" #include "terrain_3d_data.h" #include "terrain_3d_editor.h" #include "terrain_3d_instancer.h" #include "terrain_3d_material.h" #include "terrain_3d_mesher.h" class Terrain3D : public Node3D { GDCLASS(Terrain3D, Node3D); CLASS_NAME(); public: // Constants enum DebugLevel { MESG = -2, // Always print except in release builds WARN = -1, // Always print except in release builds ERROR = 0, // Always print except in release builds INFO = 1, // Print every function call and important entries DEBUG = 2, // Print details within functions EXTREME = 3, // Continuous operations like snapping }; enum RegionSize { SIZE_64 = 64, SIZE_128 = 128, SIZE_256 = 256, SIZE_512 = 512, SIZE_1024 = 1024, SIZE_2048 = 2048, }; private: String _version = "1.1.0-dev"; String _data_directory; bool _is_inside_world = false; bool _initialized = false; uint8_t _warnings = 0u; // Object references Terrain3DData *_data = nullptr; Ref _assets; Terrain3DCollision *_collision = nullptr; Terrain3DInstancer *_instancer = nullptr; Terrain3DEditor *_editor = nullptr; Object *_editor_plugin = nullptr; // Regions RegionSize _region_size = SIZE_256; bool _save_16_bit = false; real_t _label_distance = 0.f; int _label_size = 48; // Tracked Targets TargetNode3D _clipmap_target; TargetNode3D _collision_target; TargetNode3D _ocean_light_target; TargetNode3D _camera; // Fallback target for clipmap and collision // Terrain Mesh Terrain3DMesher *_terrain_mesher = nullptr; Ref _material; int _mesh_lods = 7; int _tessellation_level = 0; int _mesh_size = 48; real_t _vertex_spacing = 1.0f; real_t _cull_margin = 0.0f; RenderingServer::ShadowCastingSetting _cast_shadows = RenderingServer::SHADOW_CASTING_SETTING_ON; GeometryInstance3D::GIMode _gi_mode = GeometryInstance3D::GI_MODE_STATIC; uint32_t _render_layers = 1u | (1u << 31u); // Bit 1 and 32 for the cursor // Displacement Buffer SubViewport *_d_buffer_vp = nullptr; ColorRect *_d_buffer_rect = nullptr; Vector2 _last_buffer_position = V2_MAX; // Ocean Mesh Terrain3DMesher *_ocean_mesher = nullptr; bool _ocean_enabled = false; int _ocean_mesh_lods = 7; int _ocean_tessellation_level = 0; int _ocean_mesh_size = 32; real_t _ocean_vertex_spacing = 4.0f; real_t _ocean_cull_margin = 20.0f; RenderingServer::ShadowCastingSetting _ocean_cast_shadows = RenderingServer::SHADOW_CASTING_SETTING_OFF; GeometryInstance3D::GIMode _ocean_gi_mode = GeometryInstance3D::GI_MODE_DISABLED; uint32_t _ocean_render_layers = 1u; Ref _ocean_material; // Rendering bool _free_editor_textures = true; // Mouse cursor SubViewport *_mouse_vp = nullptr; Camera3D *_mouse_cam = nullptr; MeshInstance3D *_mouse_quad = nullptr; uint32_t _mouse_layer = 32u; // Parent containers for child nodes Node3D *_label_parent; void _initialize(); void __physics_process(const double p_delta); void _grab_camera(); void _destroy_collision(const bool p_final = false); void _setup_terrain_mesher(); void _update_mesher_aabbs() { _terrain_mesher ? _terrain_mesher->update_aabbs() : void(); } void _destroy_terrain_mesher(const bool p_final = false); void _setup_ocean_mesher(); void _update_ocean_aabbs() { _ocean_mesher ? _ocean_mesher->update_aabbs() : void(); } void _destroy_ocean_mesher(const bool p_final = false); void _setup_displacement_buffer(); void _update_displacement_buffer(); void _destroy_displacement_buffer(); void _build_containers(); void _destroy_containers(); void _destroy_labels(); void _setup_mouse_picking(); void _destroy_mouse_picking(); void _destroy_instancer(); void _generate_triangles(PackedVector3Array &p_vertices, PackedVector2Array *p_uvs, const int32_t p_lod, const Terrain3DData::HeightFilter p_filter, const bool require_nav, const AABB &p_global_aabb) const; void _generate_triangle_pair(PackedVector3Array &p_vertices, PackedVector2Array *p_uvs, const int32_t p_lod, const Terrain3DData::HeightFilter p_filter, const bool require_nav, const int32_t x, const int32_t z) const; public: static DebugLevel debug_level; // Initialized in terrain_3d.cpp Terrain3D(); ~Terrain3D() {} bool is_inside_world() const { return _is_inside_world; } // Terrain String get_version() const { return _version; } void set_debug_level(const DebugLevel p_level); DebugLevel get_debug_level() const { return debug_level; } void set_data_directory(String p_dir); String get_data_directory() const { return _data ? _data_directory : ""; } // Object references Terrain3DData *get_data() const { return _data; } void set_assets(const Ref &p_assets); Ref get_assets() const { return _assets; } Terrain3DCollision *get_collision() const { return _collision; } Terrain3DInstancer *get_instancer() const { return _instancer; } void set_editor(Terrain3DEditor *p_editor); Terrain3DEditor *get_editor() const { return _editor; } void set_plugin(Object *p_plugin); Object *get_plugin() const { return _editor_plugin; } // Regions void set_region_size(const RegionSize p_size); RegionSize get_region_size() const { return _region_size; } void change_region_size(const RegionSize p_size) { _data ? _data->change_region_size(p_size) : void(); } void set_save_16_bit(const bool p_enabled); bool get_save_16_bit() const { return _save_16_bit; } void set_label_distance(const real_t p_distance); real_t get_label_distance() const { return _label_distance; } void set_label_size(const int p_size); int get_label_size() const { return _label_size; } void update_region_labels(); // Target Tracking void set_camera(Camera3D *p_camera); Camera3D *get_camera() const { return cast_to(_camera.ptr()); } void set_clipmap_target(Node3D *p_node); Node3D *get_clipmap_target() const { return _clipmap_target.ptr(); } Vector3 get_clipmap_target_position() const; void set_collision_target(Node3D *p_node); Node3D *get_collision_target() const { return _collision_target.ptr(); } Vector3 get_collision_target_position() const; void set_ocean_light_target(Node3D *p_node); Node3D *get_ocean_light_target() const { return _ocean_light_target.ptr(); } void snap(); // Collision Aliases void set_collision_mode(const CollisionMode p_mode) { _collision ? _collision->set_mode(p_mode) : void(); } CollisionMode get_collision_mode() const { return _collision ? _collision->get_mode() : CollisionMode::DYNAMIC_GAME; } void set_collision_shape_size(const uint16_t p_size) { _collision ? _collision->set_shape_size(p_size) : void(); } uint16_t get_collision_shape_size() const { return _collision ? _collision->get_shape_size() : 16; } void set_collision_radius(const uint16_t p_radius) { _collision ? _collision->set_radius(p_radius) : void(); } uint16_t get_collision_radius() const { return _collision ? _collision->get_radius() : 64; } void set_collision_layer(const uint32_t p_layers) { _collision ? _collision->set_layer(p_layers) : void(); } uint32_t get_collision_layer() const { return _collision ? _collision->get_layer() : 1; } void set_collision_mask(const uint32_t p_mask) { _collision ? _collision->set_mask(p_mask) : void(); } uint32_t get_collision_mask() const { return _collision ? _collision->get_mask() : 1; } void set_collision_priority(const real_t p_priority) { _collision ? _collision->set_priority(p_priority) : void(); } real_t get_collision_priority() const { return _collision ? _collision->get_priority() : 1.f; } void set_physics_material(const Ref &p_mat) { _collision ? _collision->set_physics_material(p_mat) : void(); } Ref get_physics_material() const { return _collision ? _collision->get_physics_material() : Ref(); } // Terrain Mesh Terrain3DMesher *get_mesher() const { return _terrain_mesher; } void set_material(const Ref &p_material); Ref get_material() const { return _material; } void set_mesh_lods(const int p_count); int get_mesh_lods() const { return _mesh_lods; } void set_tessellation_level(const int p_level); int get_tessellation_level() const { return _tessellation_level; } void set_mesh_size(const int p_size); int get_mesh_size() const { return _mesh_size; } void set_vertex_spacing(const real_t p_spacing); real_t get_vertex_spacing() const { return _vertex_spacing; } void set_cull_margin(const real_t p_margin); real_t get_cull_margin() const { return _cull_margin; }; void set_cast_shadows(const RenderingServer::ShadowCastingSetting p_cast_shadows); RenderingServer::ShadowCastingSetting get_cast_shadows() const { return _cast_shadows; }; void set_gi_mode(const GeometryInstance3D::GIMode p_gi_mode); GeometryInstance3D::GIMode get_gi_mode() const { return _gi_mode; } void set_render_layers(const uint32_t p_layers); uint32_t get_render_layers() const { return _render_layers; }; // Material Displacement Aliases void set_displacement_scale(const real_t p_displacement_scale) { _material.is_valid() ? _material->set_displacement_scale(p_displacement_scale) : void(); } real_t get_displacement_scale() const { return _material.is_valid() ? _material->get_displacement_scale() : 1.f; } void set_displacement_sharpness(const real_t p_displacement_sharpness) { _material.is_valid() ? _material->set_displacement_sharpness(p_displacement_sharpness) : void(); } real_t get_displacement_sharpness() const { return _material.is_valid() ? _material->get_displacement_sharpness() : 0.25f; } void set_buffer_shader_override_enabled(const bool p_enabled) { _material.is_valid() ? _material->set_buffer_shader_override_enabled(p_enabled) : void(); } bool is_buffer_shader_override_enabled() const { return _material.is_valid() ? _material->is_buffer_shader_override_enabled() : false; } void set_buffer_shader_override(const Ref &p_shader) { return _material.is_valid() ? _material->set_buffer_shader_override(p_shader) : void(); } Ref get_buffer_shader_override() const { return _material.is_valid() ? _material->get_buffer_shader_override() : Ref(); } // Ocean Mesh Terrain3DMesher *get_ocean_mesher() const { return _ocean_mesher; } void set_ocean_enabled(const bool p_enabled); bool is_ocean_enabled() const { return _ocean_enabled; } void set_ocean_mesh_lods(const int p_count); int get_ocean_mesh_lods() const { return _ocean_mesh_lods; } void set_ocean_tessellation_level(const int p_level); int get_ocean_tessellation_level() const { return _ocean_tessellation_level; } void set_ocean_mesh_size(const int p_size); int get_ocean_mesh_size() const { return _ocean_mesh_size; } void set_ocean_vertex_spacing(const real_t p_spacing); real_t get_ocean_vertex_spacing() const { return _ocean_vertex_spacing; } void set_ocean_cull_margin(const real_t p_margin); real_t get_ocean_cull_margin() const { return _ocean_cull_margin; }; void set_ocean_cast_shadows(const RenderingServer::ShadowCastingSetting p_cast_shadows); RenderingServer::ShadowCastingSetting get_ocean_cast_shadows() const { return _ocean_cast_shadows; }; void set_ocean_gi_mode(const GeometryInstance3D::GIMode p_gi_mode); GeometryInstance3D::GIMode get_ocean_gi_mode() const { return _ocean_gi_mode; } void set_ocean_render_layers(const uint32_t p_layers); uint32_t get_ocean_render_layers() const { return _ocean_render_layers; }; void set_ocean_material(const Ref &p_material); Ref get_ocean_material() const { return _ocean_material; } // Rendering void set_mouse_layer(const uint32_t p_layer); uint32_t get_mouse_layer() const { return _mouse_layer; }; void set_free_editor_textures(const bool p_free_textures) { _free_editor_textures = p_free_textures; } bool get_free_editor_textures() const { return _free_editor_textures; }; void set_instancer_mode(const InstancerMode p_mode) { _instancer ? _instancer->set_mode(p_mode) : void(); } InstancerMode get_instancer_mode() const { return _instancer ? _instancer->get_mode() : InstancerMode::NORMAL; } // Utility Vector3 get_intersection(const Vector3 &p_src_pos, const Vector3 &p_direction, const bool p_gpu_mode = false); Dictionary get_raycast_result(const Vector3 &p_src_pos, const Vector3 &p_direction, const uint32_t p_col_mask = 0xFFFFFFFF, const bool p_exclude_self = false) const; Ref bake_mesh(const int p_lod, const Terrain3DData::HeightFilter p_filter = Terrain3DData::HEIGHT_FILTER_NEAREST) const; PackedVector3Array generate_nav_mesh_source_geometry(const AABB &p_global_aabb, const bool p_require_nav = true) const; // Warnings void set_warning(const uint8_t p_warning, const bool p_enabled); uint8_t get_warnings() const { return _warnings; } PackedStringArray _get_configuration_warnings() const override; // Overlay Aliases void set_show_region_grid(const bool p_enabled) { _material.is_valid() ? _material->set_show_region_grid(p_enabled) : void(); } bool get_show_region_grid() const { return _material.is_valid() ? _material->get_show_region_grid() : false; } void set_show_instancer_grid(const bool p_enabled) { _material.is_valid() ? _material->set_show_instancer_grid(p_enabled) : void(); } bool get_show_instancer_grid() const { return _material.is_valid() ? _material->get_show_instancer_grid() : false; } void set_show_vertex_grid(const bool p_enabled) { _material.is_valid() ? _material->set_show_vertex_grid(p_enabled) : void(); } bool get_show_vertex_grid() const { return _material.is_valid() ? _material->get_show_vertex_grid() : false; } void set_show_contours(const bool p_enabled) { _material.is_valid() ? _material->set_show_contours(p_enabled) : void(); } bool get_show_contours() const { return _material.is_valid() ? _material->get_show_contours() : false; } void set_show_navigation(const bool p_enabled) { _material.is_valid() ? _material->set_show_navigation(p_enabled) : void(); } bool get_show_navigation() const { return _material.is_valid() ? _material->get_show_navigation() : false; } // Debug View Aliases void set_show_checkered(const bool p_enabled) { _material.is_valid() ? _material->set_show_checkered(p_enabled) : void(); } bool get_show_checkered() const { return _material.is_valid() ? _material->get_show_checkered() : false; } void set_show_grey(const bool p_enabled) { _material.is_valid() ? _material->set_show_grey(p_enabled) : void(); } bool get_show_grey() const { return _material.is_valid() ? _material->get_show_grey() : false; } void set_show_heightmap(const bool p_enabled) { _material.is_valid() ? _material->set_show_heightmap(p_enabled) : void(); } bool get_show_heightmap() const { return _material.is_valid() ? _material->get_show_heightmap() : false; } void set_show_jaggedness(const bool p_enabled) { _material.is_valid() ? _material->set_show_jaggedness(p_enabled) : void(); } bool get_show_jaggedness() const { return _material.is_valid() ? _material->get_show_jaggedness() : false; } void set_show_autoshader(const bool p_enabled) { _material.is_valid() ? _material->set_show_autoshader(p_enabled) : void(); } bool get_show_autoshader() const { return _material.is_valid() ? _material->get_show_autoshader() : false; } void set_show_control_texture(const bool p_enabled) { _material.is_valid() ? _material->set_show_control_texture(p_enabled) : void(); } bool get_show_control_texture() const { return _material.is_valid() ? _material->get_show_control_texture() : false; } void set_show_control_blend(const bool p_enabled) { _material.is_valid() ? _material->set_show_control_blend(p_enabled) : void(); } bool get_show_control_blend() const { return _material.is_valid() ? _material->get_show_control_blend() : false; } void set_show_control_angle(const bool p_enabled) { _material.is_valid() ? _material->set_show_control_angle(p_enabled) : void(); } bool get_show_control_angle() const { return _material.is_valid() ? _material->get_show_control_angle() : false; } void set_show_control_scale(const bool p_enabled) { _material.is_valid() ? _material->set_show_control_scale(p_enabled) : void(); } bool get_show_control_scale() const { return _material.is_valid() ? _material->get_show_control_scale() : false; } void set_show_colormap(const bool p_enabled) { _material.is_valid() ? _material->set_show_colormap(p_enabled) : void(); } bool get_show_colormap() const { return _material.is_valid() ? _material->get_show_colormap() : false; } void set_show_roughmap(const bool p_enabled) { _material.is_valid() ? _material->set_show_roughmap(p_enabled) : void(); } bool get_show_roughmap() const { return _material.is_valid() ? _material->get_show_roughmap() : false; } void set_show_displacement_buffer(const bool p_enabled) { _material.is_valid() ? _material->set_show_displacement_buffer(p_enabled) : void(); } bool get_show_displacement_buffer() const { return _material.is_valid() ? _material->get_show_displacement_buffer() : false; } // PBR View Aliases void set_show_texture_albedo(const bool p_enabled) { _material.is_valid() ? _material->set_show_texture_albedo(p_enabled) : void(); } bool get_show_texture_albedo() const { return _material.is_valid() ? _material->get_show_texture_albedo() : false; } void set_show_texture_height(const bool p_enabled) { _material.is_valid() ? _material->set_show_texture_height(p_enabled) : void(); } bool get_show_texture_height() const { return _material.is_valid() ? _material->get_show_texture_height() : false; } void set_show_texture_normal(const bool p_enabled) { _material.is_valid() ? _material->set_show_texture_normal(p_enabled) : void(); } bool get_show_texture_normal() const { return _material.is_valid() ? _material->get_show_texture_normal() : false; } void set_show_texture_rough(const bool p_enabled) { _material.is_valid() ? _material->set_show_texture_rough(p_enabled) : void(); } bool get_show_texture_rough() const { return _material.is_valid() ? _material->get_show_texture_rough() : false; } void set_show_texture_ao(const bool p_enabled) { _material.is_valid() ? _material->set_show_texture_ao(p_enabled) : void(); } bool get_show_texture_ao() const { return _material.is_valid() ? _material->get_show_texture_ao() : false; } protected: void _notification(const int p_what); void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); }; VARIANT_ENUM_CAST(Terrain3D::RegionSize); VARIANT_ENUM_CAST(Terrain3D::DebugLevel); constexpr Terrain3D::DebugLevel MESG = Terrain3D::DebugLevel::MESG; constexpr Terrain3D::DebugLevel WARN = Terrain3D::DebugLevel::WARN; constexpr Terrain3D::DebugLevel ERROR = Terrain3D::DebugLevel::ERROR; constexpr Terrain3D::DebugLevel INFO = Terrain3D::DebugLevel::INFO; constexpr Terrain3D::DebugLevel DEBUG = Terrain3D::DebugLevel::DEBUG; constexpr Terrain3D::DebugLevel EXTREME = Terrain3D::DebugLevel::EXTREME; #endif // TERRAIN3D_CLASS_H ================================================ FILE: src/terrain_3d_asset_resource.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_ASSET_RESOURCE_CLASS_H #define TERRAIN3D_ASSET_RESOURCE_CLASS_H #include #include "constants.h" class Terrain3DAssets; // Parent class of Terrain3DMeshAsset and Terrain3DTextureAsset class Terrain3DAssetResource : public Resource { friend class Terrain3DAssets; public: Terrain3DAssetResource() {} ~Terrain3DAssetResource() {} virtual void initialize() = 0; virtual void clear() = 0; virtual void set_name(const String &p_name) = 0; virtual String get_name() const = 0; virtual void set_id(const int p_id) = 0; virtual int get_id() const = 0; virtual void set_highlighted(const bool p_highlighted) = 0; virtual bool is_highlighted() const = 0; virtual Color get_highlight_color() const = 0; virtual Ref get_thumbnail() const = 0; protected: String _name; int _id = 0; bool _highlighted = false; Ref _thumbnail; }; #endif // TERRAIN3D_ASSET_RESOURCE_CLASS_H ================================================ FILE: src/terrain_3d_assets.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include #include "logger.h" #include "terrain_3d_assets.h" #include "terrain_3d_util.h" /////////////////////////// // Private Functions /////////////////////////// void Terrain3DAssets::_swap_ids(const AssetType p_type, const int p_src_id, const int p_dst_id) { LOG(INFO, "Swapping asset ID: ", p_src_id, " and ID: ", p_dst_id); Array list; switch (p_type) { case TYPE_TEXTURE: list = _texture_list; break; case TYPE_MESH: list = _mesh_list; break; default: return; } if (p_src_id < 0 || p_src_id >= list.size()) { LOG(ERROR, "Source ID out of range: ", p_src_id); return; } Ref res_src = list[p_src_id]; if (res_src.is_null()) { LOG(ERROR, "Source Asset is null at ID: ", p_src_id); return; } // User can type in any value, -1 means swap with first, >size means swap with last int dst_id = CLAMP(p_dst_id, 0, list.size() - 1); if (dst_id == p_src_id) { // They're likely the same due to the clamp, when new ID is <0 or >= array_size) res_src->_id = p_src_id; return; } Ref res_dst = list[dst_id]; if (res_dst.is_valid()) { res_dst->_id = p_src_id; } res_src->_id = dst_id; list[dst_id] = res_src; list[p_src_id] = res_dst; switch (p_type) { case TYPE_TEXTURE: update_texture_list(); break; case TYPE_MESH: if (_terrain) { _terrain->get_instancer()->swap_ids(p_src_id, dst_id); } else { LOG(ERROR, "Changing IDs before the terrain is initialized. Meshes on the ground may be wrong."); } update_mesh_list(); break; default: return; } } /** * _set_asset_list attempts to keep the asset ID as saved in the resource file. * But if an ID is invalid or already taken, the new ID is changed to the next available one */ void Terrain3DAssets::_set_asset_list(const AssetType p_type, const TypedArray &p_list) { Array list; int max_size; switch (p_type) { case TYPE_TEXTURE: list = _texture_list; max_size = MAX_TEXTURES; break; case TYPE_MESH: list = _mesh_list; max_size = MAX_MESHES; break; default: return; } int array_size = CLAMP(p_list.size(), 0, max_size); list.resize(array_size); int filled_id = -1; // For all provided textures up to MAX SIZE for (int i = 0; i < array_size; i++) { Ref res = p_list[i]; if (res.is_null()) { LOG(ERROR, "Asset ID: ", i, " is null"); continue; } int id = res->get_id(); // If saved texture ID is in range and doesn't exist, add it if (id >= 0 && id < array_size && !list[id]) { list[id] = res; } else { // Else texture ID is invalid or slot is already taken, insert in first available for (int j = filled_id + 1; j < array_size; j++) { if (!list[j]) { LOG(ERROR, res->get_class(), " ID ", id, " already exists. Setting '", res->get_name(), "' to ID ", j, ". Textures/Meshes on the ground may be wrong. Review Asset list to ensure each have unique IDs."); res->set_id(j); list[j] = res; filled_id = j; break; } } } if (!res->is_connected("id_changed", callable_mp(this, &Terrain3DAssets::_swap_ids))) { LOG(DEBUG, "Connecting to id_changed, ID: ", id); res->connect("id_changed", callable_mp(this, &Terrain3DAssets::_swap_ids)); } res->initialize(); } if (Terrain3D::debug_level >= DEBUG) { for (int i = 0; i < list.size(); i++) { Ref res = list[i]; int id = res.is_valid() ? res->get_id() : -1; String name = res.is_valid() ? res->get_name() : ""; LOG(DEBUG, "Asset ", i, ": ", name, ", ", res, ", stored ID: ", id); } } } void Terrain3DAssets::_set_asset(const AssetType p_type, const int p_id, const Ref &p_asset) { LOG(INFO, "Setting asset type: ", p_type, ", ID: ", p_id, ", asset: ", p_asset); Array list; int max_size; switch (p_type) { case TYPE_TEXTURE: list = _texture_list; max_size = MAX_TEXTURES; break; case TYPE_MESH: list = _mesh_list; max_size = MAX_MESHES; break; default: return; } if (p_id < 0 || p_id >= max_size) { LOG(ERROR, "Invalid asset id: ", p_id, " range is 0-", max_size); return; } int id = CLAMP(p_id, 0, list.size()); // Delete asset if null if (p_asset.is_null()) { // If final asset, remove it if (id == list.size() - 1) { LOG(DEBUG, "Deleting asset id: ", id); list.pop_back(); } else { // Else just clear it Ref res = list[id]; res->clear(); res->_id = id; } } else { // Else Insert/Add Asset at end if a high number if (id == list.size()) { p_asset->_id = id; list.push_back(p_asset); } else { // Else overwrite an existing slot p_asset->_id = id; list[id] = p_asset; } if (!p_asset->is_connected("id_changed", callable_mp(this, &Terrain3DAssets::_swap_ids))) { LOG(DEBUG, "Connecting to id_changed"); p_asset->connect("id_changed", callable_mp(this, &Terrain3DAssets::_swap_ids)); } p_asset->initialize(); } } void Terrain3DAssets::_update_texture_files() { IS_INIT(VOID); LOG(DEBUG, "Received texture_changed signal"); _generated_albedo_textures.clear(); _generated_normal_textures.clear(); if (_texture_list.is_empty()) { LOG(DEBUG, "Emitting textures_changed"); emit_signal("textures_changed"); return; } // Detect image sizes and formats LOG(DEBUG, "Validating texture sizes"); Vector2i albedo_size = V2I_ZERO; Vector2i normal_size = V2I_ZERO; Image::Format albedo_format = Image::FORMAT_MAX; Image::Format normal_format = Image::FORMAT_MAX; bool albedo_mipmaps = true; bool normal_mipmaps = true; _terrain->set_warning(WARN_ALL, false); for (const Ref &ta : _texture_list) { if (ta.is_null()) { continue; } Ref albedo_tex = ta->_albedo_texture; if (albedo_tex.is_valid()) { Vector2i tex_size = albedo_tex->get_size(); Ref img = albedo_tex->get_image(); Image::Format format = img->get_format(); bool mipmaps = img->has_mipmaps(); // If this is the first valid texture, set expected size and format for the arrays if (albedo_format == Image::FORMAT_MAX) { albedo_size = tex_size; albedo_format = format; albedo_mipmaps = mipmaps; } else { // else validate against first texture if (tex_size != albedo_size) { _terrain->set_warning(WARN_MISMATCHED_SIZE, true); LOG(ERROR, "Texture ID ", ta->get_id(), " albedo size: ", tex_size, " doesn't match size of first texture: ", albedo_size, ". They must be identical. Read Texture Prep in docs."); } if (format != albedo_format) { _terrain->set_warning(WARN_MISMATCHED_FORMAT, true); LOG(ERROR, "Texture ID ", ta->get_id(), " albedo format: ", format, " doesn't match format of first texture: ", albedo_format, ". They must be identical. Read Texture Prep in docs."); } if (mipmaps != albedo_mipmaps) { _terrain->set_warning(WARN_MISMATCHED_MIPMAPS, true); LOG(ERROR, "Texture ID ", ta->get_id(), " albedo mipmap setting (", mipmaps, ") doesn't match first texture (", albedo_mipmaps, "). They must be identical. Read Texture Prep in docs."); } } } Ref normal_tex = ta->_normal_texture; if (normal_tex.is_valid()) { Vector2i tex_size = normal_tex->get_size(); Ref img = normal_tex->get_image(); Image::Format format = img->get_format(); bool mipmaps = img->has_mipmaps(); // If this is the first valid texture, set expected size and format for the arrays if (normal_format == Image::FORMAT_MAX) { normal_size = tex_size; normal_format = format; normal_mipmaps = mipmaps; } else { // else validate against first texture if (tex_size != normal_size) { _terrain->set_warning(WARN_MISMATCHED_SIZE, true); LOG(ERROR, "Texture ID ", ta->get_id(), " normal size: ", tex_size, " doesn't match size of first texture: ", normal_size, ". They must be identical. Read Texture Prep in docs."); } if (format != normal_format) { _terrain->set_warning(WARN_MISMATCHED_FORMAT, true); LOG(ERROR, "Texture ID ", ta->get_id(), " normal format: ", format, " doesn't match format of first texture: ", normal_format, ". They must be identical. Read Texture Prep in docs."); } if (mipmaps != normal_mipmaps) { _terrain->set_warning(WARN_MISMATCHED_MIPMAPS, true); LOG(ERROR, "Texture ID ", ta->get_id(), " normal mipmap setting (", mipmaps, ") doesn't match first texture (", albedo_mipmaps, "). They must be identical. Read Texture Prep in docs."); } } } } if (_terrain->get_warnings()) { return; } // Setup defaults for generated texture if (normal_size == V2I_ZERO) { normal_size = albedo_size; } else if (albedo_size == V2I_ZERO) { albedo_size = normal_size; } if (albedo_size == V2I_ZERO) { albedo_size = V2I(1024); normal_size = albedo_size; } // Generate TextureArrays and replace nulls with a empty image if (_generated_albedo_textures.is_dirty() && albedo_size != V2I_ZERO) { LOG(INFO, "Regenerating albedo texture array"); Array albedo_texture_array; for (const Ref &ta : _texture_list) { if (ta.is_null()) { continue; } Ref tex = ta->_albedo_texture; Ref img; if (tex.is_null()) { img = Util::get_filled_image(albedo_size, COLOR_CHECKED, albedo_mipmaps, albedo_format); LOG(DEBUG, "Texture ID ", ta->get_id(), " albedo is null. Creating a new one. Format: ", img->get_format()); ta->set_albedo_texture(ImageTexture::create_from_image(img)); } else { img = tex->get_image(); LOG(EXTREME, "Texture ID ", ta->get_id(), " albedo is valid. Format: ", img->get_format()); if (!IS_EDITOR && tex->get_path().contains("ImageTexture")) { LOG(WARN, "Texture ID ", ta->get_id(), " albedo is saved in the scene. Save it as a file and link it."); } } albedo_texture_array.push_back(img); } if (!albedo_texture_array.is_empty()) { _generated_albedo_textures.create(albedo_texture_array); } } if (_generated_normal_textures.is_dirty() && normal_size != V2I_ZERO) { LOG(INFO, "Regenerating normal texture arrays"); Array normal_texture_array; for (const Ref &ta : _texture_list) { if (ta.is_null()) { continue; } Ref tex = ta->_normal_texture; Ref img; if (tex.is_null()) { img = Util::get_filled_image(normal_size, COLOR_NORMAL, normal_mipmaps, normal_format); LOG(DEBUG, "Texture ID ", ta->get_id(), " normal is null. Creating a new one. Format: ", img->get_format()); ta->_normal_texture = ImageTexture::create_from_image(img); } else { img = tex->get_image(); LOG(EXTREME, "Texture ID ", ta->get_id(), " normal is valid. Format: ", img->get_format()); if (!IS_EDITOR && tex->get_path().contains("ImageTexture")) { LOG(WARN, "Texture ID ", ta->get_id(), " normal is saved in the scene. Save it as a file and link it."); } } normal_texture_array.push_back(img); } if (!normal_texture_array.is_empty()) { _generated_normal_textures.create(normal_texture_array); } } LOG(DEBUG, "Emitting textures_changed"); emit_signal("textures_changed"); } void Terrain3DAssets::_update_texture_settings() { LOG(DEBUG, "Received setting_changed signal"); if (!_texture_list.is_empty()) { LOG(INFO, "Updating texture asset settings arrays"); _texture_colors.clear(); _texture_normal_depths.clear(); _texture_ao_strengths.clear(); _texture_ao_light_affects.clear(); _texture_roughness_mods.clear(); _texture_uv_scales.clear(); _texture_detiles.clear(); _texture_displacements.clear(); for (const Ref &ta : _texture_list) { if (ta.is_null()) { continue; } if (ta->is_highlighted()) { _texture_colors.push_back(ta->get_highlight_color()); } else { _texture_colors.push_back(ta->get_albedo_color()); } _texture_normal_depths.push_back(ta->get_normal_depth()); _texture_ao_strengths.push_back(ta->get_ao_strength()); _texture_ao_light_affects.push_back(ta->get_ao_light_affect()); _texture_roughness_mods.push_back(ta->get_roughness()); _texture_uv_scales.push_back(ta->get_uv_scale()); _texture_detiles.push_back(Vector2(ta->get_detiling_rotation(), ta->get_detiling_shift())); _texture_displacements.push_back(Vector2(ta->get_displacement_offset(), ta->get_displacement_scale())); } } LOG(DEBUG, "Emitting textures_changed"); emit_signal("textures_changed"); } void Terrain3DAssets::_update_mesh(const int p_id) { if (p_id < 0 || p_id >= _mesh_list.size()) { LOG(ERROR, "Invalid mesh ID: ", p_id); return; } Ref ma = _mesh_list[p_id]; create_mesh_thumbnails(p_id, V2I(512), true); IS_INSTANCER_INIT(VOID); Terrain3DInstancer *instancer = _terrain->get_instancer(); instancer->update_mmis(p_id, V2I_MAX, false); } void Terrain3DAssets::_setup_thumbnail_creation() { IS_INIT(VOID); if (_scenario.is_valid()) { return; } LOG(INFO, "Setting up mesh thumbnail creation viewports"); // Setup Mesh preview environment _scenario = RS->scenario_create(); _viewport = RS->viewport_create(); RS->viewport_set_update_mode(_viewport, RenderingServer::VIEWPORT_UPDATE_DISABLED); RS->viewport_set_scenario(_viewport, _scenario); RS->viewport_set_size(_viewport, 128, 128); RS->viewport_set_transparent_background(_viewport, true); RS->viewport_set_active(_viewport, true); _viewport_texture = RS->viewport_get_texture(_viewport); _camera = RS->camera_create(); RS->viewport_attach_camera(_viewport, _camera); RS->camera_set_transform(_camera, Transform3D(Basis(), Vector3(0, 0, 3))); RS->camera_set_orthogonal(_camera, 1.0, 0.01, 1000.0); _key_light = RS->directional_light_create(); _key_light_instance = RS->instance_create2(_key_light, _scenario); RS->instance_set_transform(_key_light_instance, Transform3D().looking_at(V3(-1), V3_UP)); _fill_light = RS->directional_light_create(); RS->light_set_color(_fill_light, Color(0.3, 0.3, 0.3)); _fill_light_instance = RS->instance_create2(_fill_light, _scenario); RS->instance_set_transform(_fill_light_instance, Transform3D().looking_at(V3_UP, Vector3(0, 0, 1))); _mesh_instance = RS->instance_create(); RS->instance_set_scenario(_mesh_instance, _scenario); } /////////////////////////// // Public Functions /////////////////////////// void Terrain3DAssets::initialize(Terrain3D *p_terrain) { if (p_terrain) { _terrain = p_terrain; } else { LOG(ERROR, "Initialization failed, p_terrain is null"); return; } LOG(INFO, "Initializing assets"); if (IS_EDITOR) { _setup_thumbnail_creation(); } // Update assets update_texture_list(); update_mesh_list(); } void Terrain3DAssets::uninitialize() { LOG(INFO, "Uninitializing assets"); _terrain = nullptr; } void Terrain3DAssets::destroy() { LOG(INFO, "Destroying assets"); _terrain = nullptr; _generated_albedo_textures.clear(); _generated_normal_textures.clear(); _texture_list.clear(); _mesh_list.clear(); _texture_colors.clear(); _texture_normal_depths.clear(); _texture_ao_strengths.clear(); _texture_ao_light_affects.clear(); _texture_roughness_mods.clear(); _texture_uv_scales.clear(); _texture_detiles.clear(); if (_scenario.is_valid()) { RS->free_rid(_mesh_instance); RS->free_rid(_fill_light_instance); RS->free_rid(_fill_light); RS->free_rid(_key_light_instance); RS->free_rid(_key_light); RS->free_rid(_camera); RS->free_rid(_viewport); RS->free_rid(_scenario); _mesh_instance = RID(); _fill_light_instance = RID(); _fill_light = RID(); _key_light_instance = RID(); _key_light = RID(); _camera = RID(); _viewport = RID(); _scenario = RID(); } } // Called when creating a new asset void Terrain3DAssets::set_texture_asset(const int p_id, const Ref &p_texture) { if (p_id < 0 || p_id >= MAX_TEXTURES) { LOG(ERROR, "Invalid texture id: ", p_id, " range is 0-", MAX_TEXTURES - 1); return; } if (p_id < _texture_list.size() && _texture_list[p_id] == p_texture) { return; } LOG(INFO, "Setting texture id: ", p_id); _set_asset(TYPE_TEXTURE, p_id, p_texture); update_texture_list(); } // Called when loading a list of assets from an assets resource file on disk void Terrain3DAssets::set_texture_list(const TypedArray &p_texture_list) { LOG(INFO, "Setting texture list with ", p_texture_list.size(), " entries"); if (!differs(_texture_list, p_texture_list)) { return; } _set_asset_list(TYPE_TEXTURE, p_texture_list); update_texture_list(); } void Terrain3DAssets::clear_textures(const bool p_update) { LOG(INFO, "Clearing texture list"); _texture_list.clear(); if (p_update) { update_texture_list(); } } void Terrain3DAssets::update_texture_list() { LOG(INFO, "Reconnecting texture signals"); for (const Ref &ta : _texture_list) { if (ta.is_null()) { LOG(ERROR, "Null TextureAsset found at index: ", _texture_list.find(ta)); continue; } if (!ta->is_connected("file_changed", callable_mp(this, &Terrain3DAssets::_update_texture_files))) { LOG(DEBUG, "Connecting file_changed signal"); ta->connect("file_changed", callable_mp(this, &Terrain3DAssets::_update_texture_files)); } if (!ta->is_connected("setting_changed", callable_mp(this, &Terrain3DAssets::_update_texture_settings))) { LOG(DEBUG, "Connecting setting_changed signal"); ta->connect("setting_changed", callable_mp(this, &Terrain3DAssets::_update_texture_settings)); } } _update_texture_files(); _update_texture_settings(); } // Called when creating a new asset void Terrain3DAssets::set_mesh_asset(const int p_id, const Ref &p_mesh_asset) { if (p_id < 0 || p_id >= MAX_MESHES) { LOG(ERROR, "Invalid mesh id: ", p_id, " range is 0-", MAX_MESHES - 1); return; } if (p_id < _mesh_list.size() && _mesh_list[p_id] == p_mesh_asset) { return; } LOG(INFO, "Setting mesh id: ", p_id, ", ", p_mesh_asset); if (p_mesh_asset.is_null()) { IS_INSTANCER_INIT(VOID); _terrain->get_instancer()->clear_by_mesh(p_id); } _set_asset(TYPE_MESH, p_id, p_mesh_asset); update_mesh_list(); } // Called when loading a list of assets from an assets resource file on disk void Terrain3DAssets::set_mesh_list(const TypedArray &p_mesh_list) { LOG(INFO, "Setting mesh list with ", p_mesh_list.size(), " entries"); if (!differs(_mesh_list, p_mesh_list)) { return; } _set_asset_list(TYPE_MESH, p_mesh_list); update_mesh_list(); } // p_id = -1 for all meshes // Adapted from godot\editor\plugins\editor_preview_plugins.cpp:EditorMeshPreviewPlugin void Terrain3DAssets::create_mesh_thumbnails(const int p_id, const Vector2i &p_size, const bool p_force) { LOG(INFO, "Creating mesh thumbnails for ID: ", p_id, ", size: ", p_size); int max = get_mesh_count(); if (p_id < -1 || p_id >= max) { return; } if (!_mesh_instance.is_valid()) { _setup_thumbnail_creation(); } int start, end; if (p_id < 0) { start = 0; end = max; } else { start = CLAMP(p_id, 0, max - 1); end = CLAMP(p_id + 1, 0, max); } Vector2i size = CLAMP(p_size, V2I(2), V2I(4096)); LOG(DEBUG, "Creating thumbnails for ids: ", start, " through ", end - 1); for (int i = start; i < end; i++) { Ref ma = get_mesh_asset(i); LOG(EXTREME, i, ": Getting Terrain3DMeshAsset: ", ptr_to_str(*ma)); if (ma.is_null()) { LOG(ERROR, i, ": Terrain3DMeshAsset is null, skipping"); continue; } if (!p_force && ma->get_thumbnail().is_valid()) { LOG(EXTREME, "Thumbnail already generated, skipping"); continue; } // Setup mesh Ref mesh = ma->get_mesh(0); LOG(EXTREME, i, ": Getting Mesh 0: ", mesh); if (mesh.is_null()) { LOG(ERROR, i, ": Mesh is null, skipping"); continue; } RS->instance_set_base(_mesh_instance, mesh->get_rid()); // Setup material Ref mat = ma->get_material_override(); RID rid = mat.is_valid() ? mat->get_rid() : RID(); RS->instance_geometry_set_material_override(_mesh_instance, rid); mat = ma->get_material_overlay(); rid = mat.is_valid() ? mat->get_rid() : RID(); RS->instance_geometry_set_material_overlay(_mesh_instance, rid); // Setup scene AABB aabb = mesh->get_aabb(); Vector3 ofs = aabb.get_center(); aabb.position -= ofs; Transform3D xform; xform.basis = Basis().rotated(V3_UP, -Math_PI * 0.125f); xform.basis = Basis().rotated(Vector3(1.f, 0.f, 0.f), Math_PI * 0.125f) * xform.basis; AABB rot_aabb = xform.xform(aabb); real_t m = MAX(rot_aabb.size.x, rot_aabb.size.y) * 0.5f; if (m == 0.f) { m = 1.f; } m = .5f / m; xform.basis.scale(V3(m)); xform.origin = -xform.basis.xform(ofs); xform.origin.z -= rot_aabb.size.z * 2.f; RS->instance_set_transform(_mesh_instance, xform); // Capture image RS->viewport_set_size(_viewport, size.x, size.y); RS->viewport_set_update_mode(_viewport, RenderingServer::VIEWPORT_UPDATE_ONCE); RS->force_draw(); Ref img = RS->texture_2d_get(_viewport_texture); RS->instance_set_base(_mesh_instance, RID()); // Clear mesh if (img.is_valid()) { LOG(EXTREME, i, ": Retrieving image: ", img, " size: ", img->get_size(), " format: ", img->get_format()); ma->set_thumbnail(ImageTexture::create_from_image(img)); } else { LOG(ERROR, "_viewport_texture is null. Couldn't create thumbnail picture"); } } return; } void Terrain3DAssets::update_mesh_list() { IS_INSTANCER_INIT(VOID); LOG(INFO, "Updating mesh list"); if (_mesh_list.size() == 0) { LOG(DEBUG, "Mesh list empty, clearing instancer and adding a default mesh"); _terrain->get_instancer()->destroy(); Ref new_ma; new_ma.instantiate(); set_mesh_asset(0, new_ma); } LOG(DEBUG, "Reconnecting mesh instance signals"); for (Ref ma : _mesh_list) { if (ma.is_null()) { LOG(ERROR, "Null Terrain3DMeshAsset found at index ", _mesh_list.find(ma)); continue; } if (ma->get_mesh().is_null()) { LOG(ERROR, "Terrain3DMeshAsset has null mesh at index ", _mesh_list.find(ma)); continue; } if (!ma->is_connected("instancer_setting_changed", callable_mp(this, &Terrain3DAssets::_update_mesh))) { LOG(DEBUG, "Connecting instancer_setting_changed signal to _update_mesh"); ma->connect("instancer_setting_changed", callable_mp(this, &Terrain3DAssets::_update_mesh)); } } LOG(DEBUG, "Emitting meshes_changed"); emit_signal("meshes_changed"); } void Terrain3DAssets::load_pending_meshes() { // Reload meshes for all mesh assets that have changed. Used by the instancer after clearing for (const Ref &ma : _mesh_list) { if (ma.is_valid() && ma->is_scene_file_pending()) { ma->commit_meshes(); } } } Error Terrain3DAssets::save(const String &p_path) { if (p_path.is_empty() && get_path().is_empty()) { return ERR_FILE_NOT_FOUND; } if (!p_path.is_empty()) { LOG(DEBUG, "Setting file path to ", p_path); take_over_path(p_path); } // Save to external resource file if specified Error err = OK; String path = get_path(); if (path.get_extension() == "tres" || path.get_extension() == "res") { LOG(DEBUG, "Attempting to save external file: " + path); err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); if (err == OK) { LOG(INFO, "File saved successfully: ", path); } else { LOG(ERROR, "Cannot save file: ", path, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); } } return err; } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DAssets::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_TEXTURE); BIND_ENUM_CONSTANT(TYPE_MESH); BIND_CONSTANT(MAX_TEXTURES); BIND_CONSTANT(MAX_MESHES); ClassDB::bind_method(D_METHOD("set_texture_asset", "id", "texture"), &Terrain3DAssets::set_texture_asset); ClassDB::bind_method(D_METHOD("get_texture_asset", "id"), &Terrain3DAssets::get_texture_asset); ClassDB::bind_method(D_METHOD("set_texture_list", "texture_list"), &Terrain3DAssets::set_texture_list); ClassDB::bind_method(D_METHOD("get_texture_list"), &Terrain3DAssets::get_texture_list); ClassDB::bind_method(D_METHOD("get_texture_count"), &Terrain3DAssets::get_texture_count); ClassDB::bind_method(D_METHOD("get_albedo_array_rid"), &Terrain3DAssets::get_albedo_array_rid); ClassDB::bind_method(D_METHOD("get_normal_array_rid"), &Terrain3DAssets::get_normal_array_rid); ClassDB::bind_method(D_METHOD("get_texture_colors"), &Terrain3DAssets::get_texture_colors); ClassDB::bind_method(D_METHOD("get_texture_normal_depths"), &Terrain3DAssets::get_texture_normal_depths); ClassDB::bind_method(D_METHOD("get_texture_ao_strengths"), &Terrain3DAssets::get_texture_ao_strengths); ClassDB::bind_method(D_METHOD("get_texture_ao_light_affects"), &Terrain3DAssets::get_texture_ao_light_affects); ClassDB::bind_method(D_METHOD("get_texture_roughness_mods"), &Terrain3DAssets::get_texture_roughness_mods); ClassDB::bind_method(D_METHOD("get_texture_uv_scales"), &Terrain3DAssets::get_texture_uv_scales); ClassDB::bind_method(D_METHOD("get_texture_detiles"), &Terrain3DAssets::get_texture_detiles); ClassDB::bind_method(D_METHOD("get_texture_displacements"), &Terrain3DAssets::get_texture_displacements); ClassDB::bind_method(D_METHOD("clear_textures", "update"), &Terrain3DAssets::clear_textures, DEFVAL(false)); ClassDB::bind_method(D_METHOD("update_texture_list"), &Terrain3DAssets::update_texture_list); ClassDB::bind_method(D_METHOD("set_mesh_asset", "id", "mesh"), &Terrain3DAssets::set_mesh_asset); ClassDB::bind_method(D_METHOD("get_mesh_asset", "id"), &Terrain3DAssets::get_mesh_asset); ClassDB::bind_method(D_METHOD("set_mesh_list", "mesh_list"), &Terrain3DAssets::set_mesh_list); ClassDB::bind_method(D_METHOD("get_mesh_list"), &Terrain3DAssets::get_mesh_list); ClassDB::bind_method(D_METHOD("get_mesh_count"), &Terrain3DAssets::get_mesh_count); ClassDB::bind_method(D_METHOD("create_mesh_thumbnails", "id", "size", "force"), &Terrain3DAssets::create_mesh_thumbnails, DEFVAL(-1), DEFVAL(V2I(512)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("update_mesh_list"), &Terrain3DAssets::update_mesh_list); ClassDB::bind_method(D_METHOD("save", "path"), &Terrain3DAssets::save, DEFVAL("")); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "mesh_list", PROPERTY_HINT_ARRAY_TYPE, "Terrain3DMeshAsset", ro_flags), "set_mesh_list", "get_mesh_list"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "texture_list", PROPERTY_HINT_ARRAY_TYPE, "Terrain3DTextureAsset", ro_flags), "set_texture_list", "get_texture_list"); ADD_SIGNAL(MethodInfo("meshes_changed")); ADD_SIGNAL(MethodInfo("textures_changed")); } ================================================ FILE: src/terrain_3d_assets.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_ASSETS_CLASS_H #define TERRAIN3D_ASSETS_CLASS_H #include "constants.h" #include "generated_texture.h" #include "terrain_3d.h" #include "terrain_3d_mesh_asset.h" #include "terrain_3d_texture_asset.h" class Terrain3D; class Terrain3DInstancer; class Terrain3DAssets : public Resource { GDCLASS(Terrain3DAssets, Resource); CLASS_NAME(); public: // Constants enum AssetType { TYPE_TEXTURE, TYPE_MESH, }; static inline const int MAX_TEXTURES = 32; static inline const int MAX_MESHES = 256; private: Terrain3D *_terrain = nullptr; TypedArray _texture_list; TypedArray _mesh_list; GeneratedTexture _generated_albedo_textures; GeneratedTexture _generated_normal_textures; PackedColorArray _texture_colors; PackedFloat32Array _texture_normal_depths; PackedFloat32Array _texture_ao_strengths; PackedFloat32Array _texture_ao_light_affects; PackedFloat32Array _texture_roughness_mods; PackedFloat32Array _texture_uv_scales; PackedVector2Array _texture_detiles; PackedVector2Array _texture_displacements; // Mesh Thumbnail Generation RID _scenario; RID _viewport; RID _viewport_texture; RID _camera; RID _key_light; RID _key_light_instance; RID _fill_light; RID _fill_light_instance; RID _mesh_instance; void _swap_ids(const AssetType p_type, const int p_src_id, const int p_dst_id); void _set_asset_list(const AssetType p_type, const TypedArray &p_list); void _set_asset(const AssetType p_type, const int p_id, const Ref &p_asset); void _update_texture_files(); void _update_texture_settings(); void _update_mesh(const int p_id); void _setup_thumbnail_creation(); public: Terrain3DAssets() {} ~Terrain3DAssets() { destroy(); } void initialize(Terrain3D *p_terrain); bool is_initialized() { return _terrain != nullptr; } void uninitialize(); void destroy(); void set_texture_asset(const int p_id, const Ref &p_texture); Ref get_texture_asset(const int p_id) const; void set_texture_list(const TypedArray &p_texture_list); TypedArray get_texture_list() const { return _texture_list; } int get_texture_count() const { return _texture_list.size(); } int get_generated_array_size() const { return _generated_albedo_textures.size(); } RID get_albedo_array_rid() const { return _generated_albedo_textures.get_rid(); } RID get_normal_array_rid() const { return _generated_normal_textures.get_rid(); } PackedColorArray get_texture_colors() const { return _texture_colors; } PackedFloat32Array get_texture_normal_depths() const { return _texture_normal_depths; } PackedFloat32Array get_texture_ao_strengths() const { return _texture_ao_strengths; } PackedFloat32Array get_texture_ao_light_affects() const { return _texture_ao_light_affects; } PackedFloat32Array get_texture_roughness_mods() const { return _texture_roughness_mods; } PackedFloat32Array get_texture_uv_scales() const { return _texture_uv_scales; } PackedVector2Array get_texture_detiles() const { return _texture_detiles; } PackedVector2Array get_texture_displacements() const { return _texture_displacements; } void clear_textures(const bool p_update = false); void update_texture_list(); void set_mesh_asset(const int p_id, const Ref &p_mesh_asset); Ref get_mesh_asset(const int p_id) const; void set_mesh_list(const TypedArray &p_mesh_list); TypedArray get_mesh_list() const { return _mesh_list; } int get_mesh_count() const { return _mesh_list.size(); } void create_mesh_thumbnails(const int p_id = -1, const Vector2i &p_size = V2I(512), const bool p_force = false); void update_mesh_list(); void load_pending_meshes(); Error save(const String &p_path = ""); protected: static void _bind_methods(); }; VARIANT_ENUM_CAST(Terrain3DAssets::AssetType); inline Ref Terrain3DAssets::get_texture_asset(const int p_id) const { if (p_id >= 0 && p_id < _texture_list.size()) { return _texture_list[p_id]; } return Ref(); } inline Ref Terrain3DAssets::get_mesh_asset(const int p_id) const { if (p_id >= 0 && p_id < _mesh_list.size()) { return _mesh_list[p_id]; } return Ref(); } #endif // TERRAIN3D_ASSETS_CLASS_H ================================================ FILE: src/terrain_3d_collision.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include #include "constants.h" #include "logger.h" #include "terrain_3d.h" #include "terrain_3d_collision.h" #include "terrain_3d_data.h" #include "terrain_3d_util.h" /////////////////////////// // Private Functions /////////////////////////// // Calculates shape data from top left position. Assumes descaled and snapped. Dictionary Terrain3DCollision::_get_shape_data(const Vector2i &p_position, const int p_size) { IS_DATA_INIT_MESG("Terrain not initialized", Dictionary()); const Terrain3DData *data = _terrain->get_data(); const Ref material = _terrain->get_material(); if (!material.is_valid()) { return Dictionary(); } const Terrain3DMaterial::WorldBackground bg_mode = material->get_world_background(); const bool is_bg_flat_or_noise = bg_mode == Terrain3DMaterial::WorldBackground::FLAT || bg_mode == Terrain3DMaterial::WorldBackground::NOISE; const real_t ground_level = material->get("ground_level"); const real_t region_blend = material->get("region_blend"); const int region_map_size = Terrain3DData::REGION_MAP_SIZE; const PackedInt32Array region_map = data->get_region_map(); const int region_size = _terrain->get_region_size(); const real_t region_texel_size = 1.f / real_t(region_size); auto check_region = [&](const Vector2 &uv2) -> real_t { Vector2i pos = Vector2i(Math::floor(uv2.x), Math::floor(uv2.y)) + Vector2i(region_map_size / 2, region_map_size / 2); int layer_index = 0; if ((uint32_t)(pos.x | pos.y) < (uint32_t)region_map_size) { int v = region_map[pos.y * region_map_size + pos.x]; layer_index = Math::clamp(v - 1, -1, 0) + 1; } return real_t(layer_index); }; auto get_region_blend = [&](Vector2 uv2) -> real_t { // Floating point bias (must match shader) uv2 -= Vector2(0.5011f, 0.5011f); real_t a = check_region(uv2 + Vector2(0.0f, 1.0f)); real_t b = check_region(uv2 + Vector2(1.0f, 1.0f)); real_t c = check_region(uv2 + Vector2(1.0f, 0.0f)); real_t d = check_region(uv2 + Vector2(0.0f, 0.0f)); real_t blend_factor = 2.0f + 126.0f * (1.0f - region_blend); Vector2 f = Vector2(uv2.x - Math::floor(uv2.x), uv2.y - Math::floor(uv2.y)); f.x = Math::clamp(f.x, 1e-8f, 1.f - 1e-8f); f.y = Math::clamp(f.y, 1e-8f, 1.f - 1e-8f); Vector2 w = Vector2(1.f / (1.f + Math::exp(blend_factor * Math::log((1.f - f.x) / f.x))), 1.f / (1.f + Math::exp(blend_factor * Math::log((1.f - f.y) / f.y)))); real_t blend = Math::lerp(Math::lerp(d, c, w.x), Math::lerp(a, b, w.x), w.y); return (1.f - blend) * 2.f; }; int hshape_size = p_size + 1; // Calculate last vertex at end PackedRealArray map_data = PackedRealArray(); map_data.resize(hshape_size * hshape_size); real_t min_height = FLT_MAX; real_t max_height = -FLT_MAX; Ref map, map_x, map_z, map_xz; // height maps Ref cmap, cmap_x, cmap_z, cmap_xz; // control maps w/ holes // Get region_loc of top left corner of descaled and grid snapped collision shape position Vector2i region_loc = V2I_DIVIDE_FLOOR(p_position, region_size); const Terrain3DRegion *region = data->get_region_ptr(region_loc); if (!region || region->is_deleted()) { LOG(EXTREME, "Region not found at: ", region_loc, ". Returning blank"); return Dictionary(); } map = region->get_map(TYPE_HEIGHT); cmap = region->get_map(TYPE_CONTROL); // Get +X, +Z adjacent regions in case we run over region = data->get_region_ptr(region_loc + Vector2i(1, 0)); if (region && !region->is_deleted()) { map_x = region->get_map(TYPE_HEIGHT); cmap_x = region->get_map(TYPE_CONTROL); } region = data->get_region_ptr(region_loc + Vector2i(0, 1)); if (region && !region->is_deleted()) { map_z = region->get_map(TYPE_HEIGHT); cmap_z = region->get_map(TYPE_CONTROL); } region = data->get_region_ptr(region_loc + Vector2i(1, 1)); if (region && !region->is_deleted()) { map_xz = region->get_map(TYPE_HEIGHT); cmap_xz = region->get_map(TYPE_CONTROL); } for (int z = 0; z < hshape_size; z++) { for (int x = 0; x < hshape_size; x++) { // Choose array indexing to match triangulation of heightmapshape with the mesh // https://stackoverflow.com/questions/16684856/rotating-a-2d-pixel-array-by-90-degrees // Normal array index rotated Y=0 - shape rotation Y=0 (xform below) // int index = z * hshape_size + x; // Array Index Rotated Y=-90 - must rotate shape Y=+90 (xform below) int index = hshape_size - 1 - z + x * hshape_size; Vector2i shape_pos = p_position + Vector2i(x, z); Vector2i shape_region_loc = V2I_DIVIDE_FLOOR(shape_pos, region_size); int img_x = Math::posmod(shape_pos.x, region_size); bool next_x = shape_region_loc.x > region_loc.x; int img_y = Math::posmod(shape_pos.y, region_size); bool next_z = shape_region_loc.y > region_loc.y; // Set heights on local map, or adjacent maps if on the last row/col real_t height = NAN; if (!next_x && !next_z && map.is_valid()) { height = is_hole(cmap->get_pixel(img_x, img_y).r) ? NAN : map->get_pixel(img_x, img_y).r; } else if (next_x && !next_z && map_x.is_valid()) { height = is_hole(cmap_x->get_pixel(img_x, img_y).r) ? NAN : map_x->get_pixel(img_x, img_y).r; } else if (!next_x && next_z && map_z.is_valid()) { height = is_hole(cmap_z->get_pixel(img_x, img_y).r) ? NAN : map_z->get_pixel(img_x, img_y).r; } else if (next_x && next_z && map_xz.is_valid()) { height = is_hole(cmap_xz->get_pixel(img_x, img_y).r) ? NAN : map_xz->get_pixel(img_x, img_y).r; } if (!std::isnan(height) && is_bg_flat_or_noise) { Vector2 uv2 = Vector2(shape_pos) * region_texel_size; height = Math::lerp(height, ground_level, smoothstep(0.f, 1.f, get_region_blend(uv2))); } map_data[index] = height; if (!std::isnan(height)) { min_height = MIN(min_height, height); max_height = MAX(max_height, height); } } } // Non rotated shape for normal array index above //Transform3D xform = Transform3D(Basis(), global_pos); // Rotated shape Y=90 for -90 rotated array index Transform3D xform = Transform3D(Basis(V3_UP, Math_PI * .5), v2iv3(p_position + V2I(p_size / 2))); Dictionary shape_data; shape_data["width"] = hshape_size; shape_data["depth"] = hshape_size; shape_data["heights"] = map_data; shape_data["xform"] = xform; shape_data["min_height"] = min_height; shape_data["max_height"] = max_height; return shape_data; } void Terrain3DCollision::_shape_set_disabled(const int p_shape_id, const bool p_disabled) { if (is_editor_mode()) { CollisionShape3D *shape = _shapes[p_shape_id]; shape->set_disabled(p_disabled); shape->set_visible(!p_disabled); } else { PS->body_set_shape_disabled(_static_body_rid, p_shape_id, p_disabled); } } void Terrain3DCollision::_shape_set_transform(const int p_shape_id, const Transform3D &p_xform) { if (is_editor_mode()) { CollisionShape3D *shape = _shapes[p_shape_id]; shape->set_transform(p_xform); } else { PS->body_set_shape_transform(_static_body_rid, p_shape_id, p_xform); } } Vector3 Terrain3DCollision::_shape_get_position(const int p_shape_id) const { if (is_editor_mode()) { CollisionShape3D *shape = _shapes[p_shape_id]; return shape->get_global_position(); } else { return PS->body_get_shape_transform(_static_body_rid, p_shape_id).origin; } } void Terrain3DCollision::_shape_set_data(const int p_shape_id, const Dictionary &p_dict) { if (is_editor_mode()) { CollisionShape3D *shape = _shapes[p_shape_id]; Ref hshape = shape->get_shape(); hshape->set_map_data(p_dict["heights"]); } else { RID shape_rid = PS->body_get_shape(_static_body_rid, p_shape_id); PS->shape_set_data(shape_rid, p_dict); } } void Terrain3DCollision::_reload_physics_material() { if (is_editor_mode()) { if (_static_body) { _static_body->set_physics_material_override(_physics_material); } } else { if (_static_body_rid.is_valid()) { if (_physics_material.is_null()) { PS->body_set_param(_static_body_rid, PhysicsServer3D::BODY_PARAM_BOUNCE, 0.f); PS->body_set_param(_static_body_rid, PhysicsServer3D::BODY_PARAM_FRICTION, 1.f); } else { real_t computed_bounce = _physics_material->get_bounce() * (_physics_material->is_absorbent() ? -1.f : 1.f); real_t computed_friction = _physics_material->get_friction() * (_physics_material->is_rough() ? -1.f : 1.f); PS->body_set_param(_static_body_rid, PhysicsServer3D::BODY_PARAM_BOUNCE, computed_bounce); PS->body_set_param(_static_body_rid, PhysicsServer3D::BODY_PARAM_FRICTION, computed_friction); } } } if (_physics_material.is_valid()) { LOG(DEBUG, "Setting PhysicsMaterial bounce: ", _physics_material->get_bounce(), ", friction: ", _physics_material->get_friction()); } } /////////////////////////// // Public Functions /////////////////////////// void Terrain3DCollision::initialize(Terrain3D *p_terrain) { if (p_terrain) { _terrain = p_terrain; } else { return; } if (!IS_EDITOR && is_editor_mode()) { LOG(WARN, "Change collision mode to a non-editor mode for releases"); } build(); } void Terrain3DCollision::build() { IS_DATA_INIT(VOID); if (!_terrain->is_inside_world()) { LOG(ERROR, "Terrain isn't inside world. Returning."); return; } // Clear collision as the user might change modes in the editor destroy(); // Build only in applicable modes if (!is_enabled() || (IS_EDITOR && !is_editor_mode())) { return; } // Create StaticBody3D if (is_editor_mode()) { LOG(INFO, "Building editor collision"); _static_body = memnew(StaticBody3D); _static_body->set_name("StaticBody3D"); _static_body->set_as_top_level(true); _terrain->add_child(_static_body, true); _static_body->set_owner(_terrain); _static_body->set_collision_mask(_mask); _static_body->set_collision_layer(_layer); _static_body->set_collision_priority(_priority); } else { LOG(INFO, "Building collision with Physics Server"); _static_body_rid = PS->body_create(); PS->body_set_mode(_static_body_rid, PhysicsServer3D::BODY_MODE_STATIC); PS->body_set_space(_static_body_rid, _terrain->get_world_3d()->get_space()); PS->body_attach_object_instance_id(_static_body_rid, _terrain->get_instance_id()); PS->body_set_collision_mask(_static_body_rid, _mask); PS->body_set_collision_layer(_static_body_rid, _layer); PS->body_set_collision_priority(_static_body_rid, _priority); } _reload_physics_material(); // Create CollisionShape3Ds int shape_count; int hshape_size; if (is_dynamic_mode()) { int grid_width = _radius * 2 / _shape_size; grid_width = int_ceil_pow2(grid_width, 4); shape_count = grid_width * grid_width; hshape_size = _shape_size + 1; LOG(DEBUG, "Grid width: ", grid_width); } else { shape_count = _terrain->get_data()->get_region_count(); hshape_size = _terrain->get_region_size() + 1; } // Preallocate memory for push_back() if (is_editor_mode()) { _shapes.reserve(shape_count); } LOG(DEBUG, "Shape count: ", shape_count); LOG(DEBUG, "Shape size: ", _shape_size, ", hshape_size: ", hshape_size); Transform3D xform(Basis(), V3_MAX); for (int i = 0; i < shape_count; i++) { if (is_editor_mode()) { CollisionShape3D *col_shape = memnew(CollisionShape3D); _shapes.push_back(col_shape); col_shape->set_name("CollisionShape3D"); col_shape->set_disabled(true); col_shape->set_visible(true); col_shape->set_enable_debug_fill(false); Ref hshape; hshape.instantiate(); hshape->set_map_width(hshape_size); hshape->set_map_depth(hshape_size); col_shape->set_shape(hshape); _static_body->add_child(col_shape, true); col_shape->set_owner(_static_body); col_shape->set_transform(xform); } else { RID shape_rid = PS->heightmap_shape_create(); PS->body_add_shape(_static_body_rid, shape_rid, xform, true); LOG(DEBUG, "Adding shape: ", i, ", rid: ", shape_rid.get_id(), " pos: ", _shape_get_position(i)); } } _initialized = true; update(); } void Terrain3DCollision::update(const Vector2i &p_region_loc, const bool p_rebuild) { IS_INIT(VOID); if (!_initialized) { return; } if (p_rebuild && !is_dynamic_mode()) { build(); return; } int time = Time::get_singleton()->get_ticks_usec(); real_t spacing = _terrain->get_vertex_spacing(); if (is_dynamic_mode()) { // Snap descaled position to a _shape_size grid (eg. multiples of 16) Vector2i snapped_pos = _snap_to_grid(_terrain->get_collision_target_position() / spacing); LOG(EXTREME, "Updating collision at ", snapped_pos); // Skip if location hasn't moved to next step if (!p_rebuild && (_last_snapped_pos - snapped_pos).length_squared() < (_shape_size * _shape_size)) { return; } LOG(EXTREME, "---- 1. Defining area as a radius on a grid ----"); // Create a 0-N grid, center on snapped_pos PackedInt32Array grid; int grid_width = _radius * 2 / _shape_size; // 64*2/16 = 8 grid_width = int_ceil_pow2(grid_width, 4); grid.resize(grid_width * grid_width); grid.fill(-1); Vector2i grid_offset = -V2I(grid_width / 2); // offset # cells to center of grid Vector2i shape_offset = V2I(_shape_size / 2); // offset meters to top left corner of shape Vector2i grid_pos = snapped_pos + grid_offset * _shape_size; // Top left of grid LOG(EXTREME, "New Snapped position: ", snapped_pos); LOG(EXTREME, "Grid_pos: ", grid_pos); LOG(EXTREME, "Radius: ", _radius, ", Grid_width: ", grid_width, ", Grid_offset: ", grid_offset, ", # cells: ", grid.size()); LOG(EXTREME, "Shape_size: ", _shape_size, ", shape_offset: ", shape_offset); LOG(EXTREME, "---- 2. Checking existing shapes ----"); // If shape is within area, skip // Else, mark unused // Stores index into _shapes array TypedArray inactive_shape_ids; real_t radius_sqr = real_t(_radius * _radius); int shape_count = is_editor_mode() ? _shapes.size() : PS->body_get_shape_count(_static_body_rid); for (int i = 0; i < shape_count; i++) { // Descaled global position of shape center Vector3 shape_center = _shape_get_position(i) / spacing; // Unique key: Top left corner of shape, snapped to grid Vector2i shape_pos = _snap_to_grid(v3v2i(shape_center) - shape_offset); // Optionally could adjust radius to account for corner (sqrt(_shape_size*2)) if (!p_rebuild && (shape_center.x < FLT_MAX && v3v2i(shape_center).distance_squared_to(snapped_pos) <= radius_sqr)) { // Get index into shape array Vector2i grid_loc = (shape_pos - grid_pos) / _shape_size; grid[grid_loc.y * grid_width + grid_loc.x] = i; _shape_set_disabled(i, false); LOG(EXTREME, "Shape ", i, ": shape_center: ", shape_center.x < FLT_MAX ? shape_center : V3(-999), ", shape_pos: ", shape_pos, ", grid_loc: ", grid_loc, ", index: ", (grid_loc.y * grid_width + grid_loc.x), " active"); } else { inactive_shape_ids.push_back(i); _shape_set_disabled(i, true); LOG(EXTREME, "Shape ", i, ": shape_center: ", shape_center.x < FLT_MAX ? shape_center : V3(-999), ", shape_pos: ", shape_pos, " out of bounds, marking inactive"); } } LOG(EXTREME, "_inactive_shapes size: ", inactive_shape_ids.size()); LOG(EXTREME, "---- 3. Review grid cells in area ----"); // If cell is full, skip // Else assign shape and form it for (int i = 0; i < grid.size(); i++) { Vector2i grid_loc(i % grid_width, i / grid_width); // Unique key: Top left corner of shape, snapped to grid Vector2i shape_pos = grid_pos + grid_loc * _shape_size; if ((shape_pos + shape_offset).distance_squared_to(snapped_pos) > radius_sqr) { LOG(EXTREME, "grid[", i, ":", grid_loc, "] shape_pos : ", shape_pos, " out of circle, skipping"); continue; } if (!p_rebuild && grid[i] >= 0) { Vector2i center_pos = v3v2i(_shape_get_position(i)); LOG(EXTREME, "grid[", i, ":", grid_loc, "] shape_pos : ", shape_pos, " act ", center_pos - shape_offset, " Has active shape id: ", grid[i]); continue; } else { if (inactive_shape_ids.size() == 0) { LOG(ERROR, "No more unused shapes! Aborting!"); break; } Dictionary shape_data = _get_shape_data(shape_pos, _shape_size); if (shape_data.is_empty()) { LOG(EXTREME, "grid[", i, ":", grid_loc, "] shape_pos : ", shape_pos, " No region found"); continue; } int shape_id = inactive_shape_ids.pop_back(); Transform3D xform = shape_data["xform"]; LOG(EXTREME, "grid[", i, ":", grid_loc, "] shape_pos : ", shape_pos, " act ", v3v2i(xform.origin) - shape_offset, " placing shape id ", shape_id); xform.scale(Vector3(spacing, 1.f, spacing)); _shape_set_transform(shape_id, xform); _shape_set_disabled(shape_id, false); _shape_set_data(shape_id, shape_data); } } _last_snapped_pos = snapped_pos; LOG(EXTREME, "Setting _last_snapped_pos: ", _last_snapped_pos); LOG(EXTREME, "inactive_shape_ids size: ", inactive_shape_ids.size()); } else { // Full collision int shape_count = _terrain->get_data()->get_region_count(); int region_size = _terrain->get_region_size(); TypedArray region_locs = _terrain->get_data()->get_region_locations(); for (int i = 0; i < region_locs.size(); i++) { Vector2i region_loc = region_locs[i]; if (p_region_loc != V2I_MAX && region_loc != p_region_loc) { continue; } Vector2i shape_pos = region_loc * region_size; Dictionary shape_data = _get_shape_data(shape_pos, region_size); if (shape_data.is_empty()) { LOG(ERROR, "Can't get shape data for ", region_loc); continue; } Transform3D xform = shape_data["xform"]; xform.scale(Vector3(spacing, 1.f, spacing)); _shape_set_transform(i, xform); _shape_set_disabled(i, false); _shape_set_data(i, shape_data); } } LOG(EXTREME, "Collision update time: ", Time::get_singleton()->get_ticks_usec() - time, " us"); } void Terrain3DCollision::destroy() { _initialized = false; _last_snapped_pos = V2I_MAX; // Physics Server if (_static_body_rid.is_valid()) { // Shape IDs change as they are freed, so it's not safe to iterate over them while freeing. while (PS->body_get_shape_count(_static_body_rid) > 0) { RID rid = PS->body_get_shape(_static_body_rid, 0); LOG(DEBUG, "Freeing CollisionShape RID ", rid); PS->free_rid(rid); } LOG(DEBUG, "Freeing StaticBody RID"); PS->free_rid(_static_body_rid); _static_body_rid = RID(); } // Scene Tree for (int i = 0; i < _shapes.size(); i++) { CollisionShape3D *shape = _shapes[i]; LOG(DEBUG, "Freeing CollisionShape3D ", i, " ", shape->get_name()); remove_from_tree(shape); memdelete_safely(shape); } _shapes.clear(); if (_static_body) { LOG(DEBUG, "Freeing StaticBody3D"); remove_from_tree(_static_body); memdelete_safely(_static_body); } } void Terrain3DCollision::set_mode(const CollisionMode p_mode) { SET_IF_DIFF(_mode, p_mode); LOG(INFO, "Setting collision mode: ", p_mode); if (is_enabled()) { build(); } else { destroy(); } } void Terrain3DCollision::set_shape_size(const uint16_t p_size) { uint16_t size = CLAMP(p_size, 8, 64); size = int_round_mult(size, uint16_t(8)); SET_IF_DIFF(_shape_size, size); LOG(INFO, "Setting collision dynamic shape size: ", _shape_size); // Ensure size:radius always results in at least one valid shape if (_shape_size > _radius - 8) { set_radius(_shape_size + 16); } else if (is_dynamic_mode()) { build(); } } void Terrain3DCollision::set_radius(const uint16_t p_radius) { uint16_t radius = CLAMP(p_radius, 16, 256); radius = int_ceil_pow2(radius, uint16_t(16)); SET_IF_DIFF(_radius, radius); LOG(INFO, "Setting collision dynamic radius: ", _radius); // Ensure size:radius always results in at least one valid shape if (_radius < _shape_size + 8) { set_shape_size(_radius - 8); } else if (_shape_size < 16 && _radius > 128) { set_shape_size(16); } else if (is_dynamic_mode()) { build(); } } void Terrain3DCollision::set_layer(const uint32_t p_layers) { SET_IF_DIFF(_layer, p_layers); LOG(INFO, "Setting collision layers: ", p_layers); if (is_editor_mode()) { if (_static_body) { _static_body->set_collision_layer(_layer); } } else { if (_static_body_rid.is_valid()) { PS->body_set_collision_layer(_static_body_rid, _layer); } } } void Terrain3DCollision::set_mask(const uint32_t p_mask) { SET_IF_DIFF(_mask, p_mask); LOG(INFO, "Setting collision mask: ", p_mask); if (is_editor_mode()) { if (_static_body) { _static_body->set_collision_mask(_mask); } } else { if (_static_body_rid.is_valid()) { PS->body_set_collision_mask(_static_body_rid, _mask); } } } void Terrain3DCollision::set_priority(const real_t p_priority) { SET_IF_DIFF(_priority, p_priority); LOG(INFO, "Setting collision priority: ", p_priority); if (is_editor_mode()) { if (_static_body) { _static_body->set_collision_priority(_priority); } } else { if (_static_body_rid.is_valid()) { PS->body_set_collision_priority(_static_body_rid, _priority); } } } void Terrain3DCollision::set_physics_material(const Ref &p_mat) { if (_physics_material == p_mat) { return; } if (_physics_material.is_valid()) { if (_physics_material->is_connected("changed", callable_mp(this, &Terrain3DCollision::_reload_physics_material))) { LOG(DEBUG, "Disconnecting _physics_material::changed signal to _reload_physics_material()"); _physics_material->disconnect("changed", callable_mp(this, &Terrain3DCollision::_reload_physics_material)); } } _physics_material = p_mat; LOG(INFO, "Setting physics material: ", p_mat); if (_physics_material.is_valid()) { LOG(DEBUG, "Connecting _physics_material::changed signal to _reload_physics_material()"); _physics_material->connect("changed", callable_mp(this, &Terrain3DCollision::_reload_physics_material)); } _reload_physics_material(); } RID Terrain3DCollision::get_rid() const { if (!is_editor_mode()) { return _static_body_rid; } else { if (_static_body) { return _static_body->get_rid(); } } return RID(); } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DCollision::_bind_methods() { BIND_ENUM_CONSTANT(DISABLED); BIND_ENUM_CONSTANT(DYNAMIC_GAME); BIND_ENUM_CONSTANT(DYNAMIC_EDITOR); BIND_ENUM_CONSTANT(FULL_GAME); BIND_ENUM_CONSTANT(FULL_EDITOR); ClassDB::bind_method(D_METHOD("build"), &Terrain3DCollision::build); ClassDB::bind_method(D_METHOD("update", "region_location", "rebuild"), &Terrain3DCollision::update, DEFVAL(V2I_MAX), DEFVAL(false)); ClassDB::bind_method(D_METHOD("destroy"), &Terrain3DCollision::destroy); ClassDB::bind_method(D_METHOD("set_mode", "mode"), &Terrain3DCollision::set_mode); ClassDB::bind_method(D_METHOD("get_mode"), &Terrain3DCollision::get_mode); ClassDB::bind_method(D_METHOD("is_enabled"), &Terrain3DCollision::is_enabled); ClassDB::bind_method(D_METHOD("is_editor_mode"), &Terrain3DCollision::is_editor_mode); ClassDB::bind_method(D_METHOD("is_dynamic_mode"), &Terrain3DCollision::is_dynamic_mode); ClassDB::bind_method(D_METHOD("set_shape_size", "size"), &Terrain3DCollision::set_shape_size); ClassDB::bind_method(D_METHOD("get_shape_size"), &Terrain3DCollision::get_shape_size); ClassDB::bind_method(D_METHOD("set_radius", "radius"), &Terrain3DCollision::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &Terrain3DCollision::get_radius); ClassDB::bind_method(D_METHOD("set_layer", "layers"), &Terrain3DCollision::set_layer); ClassDB::bind_method(D_METHOD("get_layer"), &Terrain3DCollision::get_layer); ClassDB::bind_method(D_METHOD("set_mask", "mask"), &Terrain3DCollision::set_mask); ClassDB::bind_method(D_METHOD("get_mask"), &Terrain3DCollision::get_mask); ClassDB::bind_method(D_METHOD("set_priority", "priority"), &Terrain3DCollision::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &Terrain3DCollision::get_priority); ClassDB::bind_method(D_METHOD("set_physics_material", "material"), &Terrain3DCollision::set_physics_material); ClassDB::bind_method(D_METHOD("get_physics_material"), &Terrain3DCollision::get_physics_material); ClassDB::bind_method(D_METHOD("get_rid"), &Terrain3DCollision::get_rid); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Disabled,Dynamic / Game,Dynamic / Editor,Full / Game,Full / Editor"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "shape_size", PROPERTY_HINT_RANGE, "8,64,8"), "set_shape_size", "get_shape_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radius", PROPERTY_HINT_RANGE, "16,256,16"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_layer", "get_layer"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_mask", "get_mask"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "priority", PROPERTY_HINT_RANGE, "0.1,256,.1"), "set_priority", "get_priority"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material", "get_physics_material"); } ================================================ FILE: src/terrain_3d_collision.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_COLLISION_CLASS_H #define TERRAIN3D_COLLISION_CLASS_H #include #include #include #include #include "constants.h" #include "terrain_3d_util.h" class Terrain3D; class Terrain3DCollision : public Object { GDCLASS(Terrain3DCollision, Object); CLASS_NAME(); public: // Constants enum CollisionMode { DISABLED, DYNAMIC_GAME, DYNAMIC_EDITOR, FULL_GAME, FULL_EDITOR, }; private: Terrain3D *_terrain = nullptr; // Public settings CollisionMode _mode = DYNAMIC_GAME; uint16_t _shape_size = 16; uint16_t _radius = 64; uint32_t _layer = 1; uint32_t _mask = 1; real_t _priority = 1.f; Ref _physics_material; // Work data RID _static_body_rid; // Physics Server Static Body StaticBody3D *_static_body = nullptr; // Editor mode StaticBody3D std::vector _shapes; // All CollisionShape3Ds bool _initialized = false; Vector2i _last_snapped_pos = V2I_MAX; Vector2i _snap_to_grid(const Vector2i &p_pos) const; Vector2i _snap_to_grid(const Vector3 &p_pos) const; Dictionary _get_shape_data(const Vector2i &p_position, const int p_size); void _shape_set_disabled(const int p_shape_id, const bool p_disabled); void _shape_set_transform(const int p_shape_id, const Transform3D &p_xform); Vector3 _shape_get_position(const int p_shape_id) const; void _shape_set_data(const int p_shape_id, const Dictionary &p_dict); void _reload_physics_material(); public: Terrain3DCollision() {} ~Terrain3DCollision() { destroy(); } void initialize(Terrain3D *p_terrain); void build(); void reset_target_position() { _last_snapped_pos = V2I_MAX; } void update(const Vector2i &p_region_loc = V2I_MAX, const bool p_rebuild = false); void destroy(); void set_mode(const CollisionMode p_mode); CollisionMode get_mode() const { return _mode; } bool is_enabled() const { return _mode > DISABLED; } bool is_editor_mode() const { return _mode == DYNAMIC_EDITOR || _mode == FULL_EDITOR; } bool is_dynamic_mode() const { return _mode == DYNAMIC_GAME || _mode == DYNAMIC_EDITOR; } void set_shape_size(const uint16_t p_size); uint16_t get_shape_size() const { return _shape_size; } void set_radius(const uint16_t p_radius); uint16_t get_radius() const { return _radius; } void set_layer(const uint32_t p_layers); uint32_t get_layer() const { return _layer; }; void set_mask(const uint32_t p_mask); uint32_t get_mask() const { return _mask; }; void set_priority(const real_t p_priority); real_t get_priority() const { return _priority; } void set_physics_material(const Ref &p_mat); Ref get_physics_material() { return _physics_material; } RID get_rid() const; protected: static void _bind_methods(); }; using CollisionMode = Terrain3DCollision::CollisionMode; VARIANT_ENUM_CAST(Terrain3DCollision::CollisionMode); inline Vector2i Terrain3DCollision::_snap_to_grid(const Vector2i &p_pos) const { return Vector2i(int_round_mult(p_pos.x, int32_t(_shape_size)), int_round_mult(p_pos.y, int32_t(_shape_size))); } inline Vector2i Terrain3DCollision::_snap_to_grid(const Vector3 &p_pos) const { return Vector2i(Math::floor(p_pos.x / real_t(_shape_size) + 0.5f), Math::floor(p_pos.z / real_t(_shape_size) + 0.5f)) * _shape_size; } #endif // TERRAIN3D_COLLISION_CLASS_H ================================================ FILE: src/terrain_3d_data.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include #include #include "logger.h" #include "terrain_3d_data.h" /////////////////////////// // Private Functions /////////////////////////// void Terrain3DData::_clear() { LOG(INFO, "Clearing data"); _region_map_dirty = true; _region_map.clear(); _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _regions.clear(); _region_locations.clear(); _master_height_range = V2_ZERO; _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); } // Structured to work with do_for_regions. Should be renamed when copy_paste is expanded void Terrain3DData::_copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2i &p_src_rect, const Rect2i &p_dst_rect, const Terrain3DRegion *p_dst_region) { if (!p_src_region || !p_dst_region) { return; } TypedArray src_maps = p_src_region->get_maps(); TypedArray dst_maps = p_dst_region->get_maps(); for (int i = 0; i < dst_maps.size(); i++) { Image *img = cast_to(dst_maps[i]); if (img) { img->blit_rect(src_maps[i], p_src_rect, p_dst_rect.position); } } _terrain->get_instancer()->copy_paste_dfr(p_src_region, p_src_rect, p_dst_region); } /////////////////////////// // Public Functions /////////////////////////// void Terrain3DData::initialize(Terrain3D *p_terrain) { if (!p_terrain) { LOG(ERROR, "Initialization failed, p_terrain is null"); return; } LOG(INFO, "Initializing data"); bool prev_initialized = _terrain != nullptr; _terrain = p_terrain; _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _vertex_spacing = _terrain->get_vertex_spacing(); if (!prev_initialized && !_terrain->get_data_directory().is_empty()) { load_directory(_terrain->get_data_directory()); } _region_size = _terrain->get_region_size(); _region_sizev = V2I(_region_size); } void Terrain3DData::set_region_locations(const TypedArray &p_locations) { SET_IF_DIFF(_region_locations, p_locations); LOG(INFO, "Setting _region_locations with array sized: ", p_locations.size()); _region_map_dirty = true; update_maps(TYPE_MAX, false, false); // only rebuild region map } // Returns an array of active regions, optionally a shallow or deep copy TypedArray Terrain3DData::get_regions_active(const bool p_copy, const bool p_deep) const { TypedArray region_arr; for (const Vector2i ®ion_loc : _region_locations) { Ref region = get_region(region_loc); if (region.is_valid()) { region_arr.push_back(p_copy ? region->duplicate(p_deep) : region); } } return region_arr; } // Calls the callback function for every region within the given (descaled) area // The callable receives: source Terrain3DRegion, source Rect2i, dest Rect2i, (bindings) // Used with change_region_size, dest Terrain3DRegion is bound as the 4th parameter void Terrain3DData::do_for_regions(const Rect2i &p_area, const Callable &p_callback) { Rect2i location_bounds(V2I_DIVIDE_FLOOR(p_area.position, _region_size), V2I_DIVIDE_CEIL(p_area.size, _region_size)); LOG(DEBUG, "Processing global area: ", p_area, " -> ", location_bounds); Point2i current_region_loc; for (int y = location_bounds.position.y; y < location_bounds.get_end().y; y++) { current_region_loc.y = y; for (int x = location_bounds.position.x; x < location_bounds.get_end().x; x++) { current_region_loc.x = x; const Terrain3DRegion *region = get_region_ptr(current_region_loc); if (region && !region->is_deleted()) { LOG(DEBUG, "Current region: ", current_region_loc); Rect2i region_area = p_area.intersection(Rect2i(current_region_loc * _region_size, _region_sizev)); LOG(DEBUG, "Region bounds: ", Rect2i(current_region_loc * _region_size, _region_sizev)); LOG(DEBUG, "Region area: ", region_area); Rect2i dst_coords(region_area.position - p_area.position, region_area.size); Rect2i src_coords(region_area.position - (region->get_location() * _region_sizev), dst_coords.size); LOG(DEBUG, "src map coords: ", src_coords); LOG(DEBUG, "dst map coords: ", dst_coords); p_callback.call(region, src_coords, dst_coords); } } } } void Terrain3DData::change_region_size(int p_new_size) { LOG(INFO, "Changing region size from: ", _region_size, " to ", p_new_size); if (!is_valid_region_size(p_new_size)) { LOG(ERROR, "Invalid region size: ", p_new_size, ". Must be power of 2, 64-2048"); return; } if (p_new_size == _region_size) { return; } // Get current region corners expressed in new region_size coordinates Dictionary new_region_locations; Array region_locations = _regions.keys(); for (const Vector2i ®ion_loc : region_locations) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (region && !region->is_deleted()) { Vector2i region_position = region->get_location() * _region_size; Rect2i location_bounds(V2I_DIVIDE_FLOOR(region_position, p_new_size), V2I_DIVIDE_CEIL(_region_sizev, p_new_size)); for (int y = location_bounds.position.y; y < location_bounds.get_end().y; y++) { for (int x = location_bounds.position.x; x < location_bounds.get_end().x; x++) { new_region_locations[Vector2i(x, y)] = 1; } } } } // Make new regions to receive copied data TypedArray new_regions; Array new_locations = new_region_locations.keys(); for (const Vector2i ®ion_loc : new_locations) { Ref new_region; new_region.instantiate(); new_region->set_location(region_loc); new_region->set_region_size(p_new_size); new_region->set_vertex_spacing(_vertex_spacing); new_region->set_modified(true); new_region->sanitize_maps(); // Copy current data from current into new region, up to new region size Rect2i area; area.position = region_loc * p_new_size; area.size = V2I(p_new_size); do_for_regions(area, callable_mp(this, &Terrain3DData::_copy_paste_dfr).bind(new_region.ptr())); new_regions.push_back(new_region); } // Remove old data _terrain->get_instancer()->destroy(); TypedArray old_regions = get_regions_active(); for (const Ref ®ion : old_regions) { remove_region(region, false); } // Change region size _terrain->set_region_size((Terrain3D::RegionSize)p_new_size); // Add new regions and rebuild for (const Ref ®ion : new_regions) { add_region(region, false); } calc_height_range(true); update_maps(TYPE_MAX, true, true); _terrain->get_instancer()->update_mmis(-1, V2I_MAX, true); } void Terrain3DData::set_region_modified(const Vector2i &p_region_loc, const bool p_modified) { Terrain3DRegion *region = get_region_ptr(p_region_loc); if (!region) { LOG(ERROR, "Region not found at: ", p_region_loc); return; } return region->set_modified(p_modified); } bool Terrain3DData::is_region_modified(const Vector2i &p_region_loc) const { Terrain3DRegion *region = get_region_ptr(p_region_loc); if (!region) { LOG(ERROR, "Region not found at: ", p_region_loc); return false; } return region->is_modified(); } void Terrain3DData::set_region_deleted(const Vector2i &p_region_loc, const bool p_deleted) { Terrain3DRegion *region = get_region_ptr(p_region_loc); if (!region) { LOG(ERROR, "Region not found at: ", p_region_loc); return; } return region->set_deleted(p_deleted); } bool Terrain3DData::is_region_deleted(const Vector2i &p_region_loc) const { const Terrain3DRegion *region = get_region_ptr(p_region_loc); if (!region) { LOG(ERROR, "Region not found at: ", p_region_loc); return true; } return region->is_deleted(); } Ref Terrain3DData::add_region_blankp(const Vector3 &p_global_position, const bool p_update) { return add_region_blank(get_region_location(p_global_position)); } Ref Terrain3DData::add_region_blank(const Vector2i &p_region_loc, const bool p_update) { Ref region; region.instantiate(); region->set_location(p_region_loc); region->set_region_size(_region_size); region->set_vertex_spacing(_vertex_spacing); if (add_region(region, p_update) == OK) { region->set_modified(true); return region; } return Ref(); } /** Adds a Terrain3DRegion to the terrain * Marks region as modified * p_update - rebuild the maps if true. Set to false if bulk adding many regions. */ Error Terrain3DData::add_region(const Ref &p_region, const bool p_update) { if (p_region.is_null()) { LOG(ERROR, "Provided region is null. Returning"); return FAILED; } Vector2i region_loc = p_region->get_location(); LOG(INFO, "Adding region at location ", region_loc, ", update maps: ", p_update ? "yes" : "no"); // Check bounds and slow report errors if (get_region_map_index(region_loc) < 0) { LOG(ERROR, "Location ", region_loc, " out of bounds. Max: ", -REGION_MAP_SIZE / 2, " to ", REGION_MAP_SIZE / 2 - 1); return FAILED; } p_region->sanitize_maps(); p_region->set_deleted(false); if (!_region_locations.has(region_loc)) { _region_locations.push_back(region_loc); } else { LOG(INFO, "Overwriting ", (_regions.has(region_loc)) ? "deleted" : "existing", " region at ", region_loc); } _regions[region_loc] = p_region; _region_map_dirty = true; LOG(DEBUG, "Storing region ", region_loc, " version ", vformat("%.3f", p_region->get_version()), " id: ", _region_locations.size()); if (p_update) { update_maps(TYPE_MAX, true, false); _terrain->get_instancer()->update_mmis(-1, V2I_MAX, true); } return OK; } void Terrain3DData::remove_regionp(const Vector3 &p_global_position, const bool p_update) { Ref region = get_region(get_region_location(p_global_position)); remove_region(region, p_update); } void Terrain3DData::remove_regionl(const Vector2i &p_region_loc, const bool p_update) { Ref region = get_region(p_region_loc); remove_region(region, p_update); } // Remove region marks the region for deletion, and removes it from the active arrays indexed by ID // It remains stored in _regions and the file remains on disk until saved, when both are removed void Terrain3DData::remove_region(const Ref &p_region, const bool p_update) { if (p_region.is_null()) { LOG(ERROR, "Region not found or is null. Returning"); return; } Vector2i region_loc = p_region->get_location(); int region_id = _region_locations.find(region_loc); LOG(INFO, "Marking region ", region_loc, " for deletion. update_maps: ", p_update ? "yes" : "no"); if (region_id < 0) { LOG(ERROR, "Region ", region_loc, " not found in region_locations. Returning"); return; } p_region->set_deleted(true); _region_locations.remove_at(region_id); _region_map_dirty = true; LOG(DEBUG, "Removing from region_locations, new size: ", _region_locations.size()); if (p_update) { LOG(DEBUG, "Updating generated maps"); update_maps(TYPE_MAX, true, false); _terrain->get_instancer()->update_mmis(-1, V2I_MAX, true); } } void Terrain3DData::save_directory(const String &p_dir) { LOG(INFO, "Saving data files to ", p_dir); Array locations = _regions.keys(); for (const Vector2i ®ion_loc : locations) { save_region(region_loc, p_dir, _terrain->get_save_16_bit()); } if (IS_EDITOR && !EditorInterface::get_singleton()->get_resource_filesystem()->is_scanning()) { EditorInterface::get_singleton()->get_resource_filesystem()->scan(); } } // You may need to do a file system scan to update FileSystem panel void Terrain3DData::save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit) { Ref region = get_region(p_region_loc); if (region.is_null()) { LOG(ERROR, "No region found at: ", p_region_loc); return; } String fname = Util::location_to_filename(p_region_loc); String path = p_dir + String("/") + fname; // If region marked for deletion, remove from disk and from _regions, but don't free in case stored in undo if (region->is_deleted()) { LOG(DEBUG, "Removing ", p_region_loc, " from _regions"); _regions.erase(p_region_loc); LOG(DEBUG, "File to be deleted: ", path); if (!FileAccess::file_exists(path)) { LOG(INFO, "File to delete ", path, " doesn't exist. (Maybe from add, undo, save)"); return; } Ref da = DirAccess::open(p_dir); if (da.is_null()) { LOG(ERROR, "Cannot open directory for writing: ", p_dir, " error: ", DirAccess::get_open_error()); return; } Error err = da->remove(fname); if (err != OK) { LOG(ERROR, "Could not remove file: ", fname, ", error code: ", err); } LOG(INFO, "File ", path, " deleted"); return; } Error err = region->save(path, p_16_bit); if (!(err == OK || err == ERR_SKIP)) { LOG(ERROR, "Could not save file: ", path, ", error: ", UtilityFunctions::error_string(err), " (", err, ")"); } } void Terrain3DData::load_directory(const String &p_dir) { if (p_dir.is_empty()) { LOG(ERROR, "Specified directory name is blank"); return; } LOG(INFO, "Loading region files from ", p_dir); PackedStringArray files = Util::get_files(p_dir, "terrain3d*.res"); if (files.size() == 0) { LOG(INFO, "No Terrain3D region files found in: ", p_dir); return; } _clear(); for (const String &fname : files) { String path = p_dir + String("/") + fname; LOG(DEBUG, "Loading region from ", path); Vector2i loc = Util::filename_to_location(fname); if (loc.x == INT32_MAX) { LOG(ERROR, "Cannot get region location from file name: ", fname); continue; } Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); if (region.is_null()) { LOG(ERROR, "Cannot load region at ", path); continue; } LOG(INFO, "Loaded region: ", loc, " size: ", region->get_region_size()); if (_regions.is_empty()) { _terrain->set_region_size((Terrain3D::RegionSize)region->get_region_size()); } else { if (_terrain->get_region_size() != (Terrain3D::RegionSize)region->get_region_size()) { LOG(ERROR, "Region size mismatch. First loaded: ", _terrain->get_region_size(), " next: ", region->get_region_size(), " in file: ", path); return; } } region->take_over_path(path); region->set_location(loc); region->set_version(CURRENT_DATA_VERSION); // Sends upgrade warning if old version add_region(region, false); } update_maps(TYPE_MAX, true, false); } //TODO have load_directory call load_region, or make a load_file that loads a specific path void Terrain3DData::load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update) { LOG(INFO, "Loading region from location ", p_region_loc); String path = p_dir + String("/") + Util::location_to_filename(p_region_loc); if (!FileAccess::file_exists(path)) { LOG(ERROR, "File ", path, " doesn't exist"); return; } Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); if (region.is_null()) { LOG(ERROR, "Cannot load region at ", path); return; } if (_regions.is_empty()) { _terrain->set_region_size((Terrain3D::RegionSize)region->get_region_size()); } else { if (_terrain->get_region_size() != (Terrain3D::RegionSize)region->get_region_size()) { LOG(ERROR, "Region size mismatch. First loaded: ", _terrain->get_region_size(), " next: ", region->get_region_size(), " in file: ", path); return; } } region->take_over_path(path); region->set_location(p_region_loc); region->set_version(CURRENT_DATA_VERSION); // Sends upgrade warning if old version add_region(region, p_update); } TypedArray Terrain3DData::get_maps(const MapType p_map_type) const { if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); return TypedArray(); } switch (p_map_type) { case TYPE_HEIGHT: return get_height_maps(); break; case TYPE_CONTROL: return get_control_maps(); break; case TYPE_COLOR: return get_color_maps(); break; default: break; } return TypedArray(); } void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regions, const bool p_generate_mipmaps) { // Generate region color mipmaps if (p_generate_mipmaps && (p_map_type == TYPE_COLOR || p_map_type == TYPE_MAX)) { LOG(EXTREME, "Regenerating color mipmaps"); for (const Vector2i ®ion_loc : _regions.keys()) { Terrain3DRegion *region = get_region_ptr(region_loc); // Generate all or only those marked edited if (region && !region->is_deleted() && (p_all_regions || region->is_edited())) { region->get_color_map()->generate_mipmaps(); } } } // Mark texture arrays dirty for rebuilding if (p_all_regions) { LOG(EXTREME, "Marking dirty maps of type: ", p_map_type); switch (p_map_type) { case TYPE_HEIGHT: _generated_height_maps.clear(); break; case TYPE_CONTROL: _generated_control_maps.clear(); break; case TYPE_COLOR: _generated_color_maps.clear(); break; default: _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); _region_map_dirty = true; break; } } bool any_changed = false; // Rebuild region map if dirty if (_region_map_dirty) { LOG(EXTREME, "Regenerating ", REGION_MAP_VSIZE, " region map array from active regions"); _region_map.clear(); _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _region_map_dirty = false; _region_locations = TypedArray(); // enforce new pointer int region_id = 0; for (const Vector2i ®ion_loc : _regions.keys()) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (region && !region->is_deleted()) { region_id += 1; // Begin at 1 since 0 = no region int map_index = get_region_map_index(region_loc); if (map_index >= 0) { _region_map[map_index] = region_id; _region_locations.push_back(region_loc); } } } any_changed = true; LOG(DEBUG, "Emitting region_map_changed"); emit_signal("region_map_changed"); } // Rebuild height maps if dirty if (_generated_height_maps.is_dirty()) { LOG(EXTREME, "Regenerating height texture array from regions"); _height_maps.clear(); for (const Vector2i ®ion_loc : _region_locations) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (region) { _height_maps.push_back(region->get_height_map()); } else { LOG(ERROR, "Can't find region ", region_loc, ", _regions: ", _regions, ", locations: ", _region_locations, ". Please report this error."); return; } } _generated_height_maps.create(_height_maps); calc_height_range(); any_changed = true; LOG(DEBUG, "Emitting height_maps_changed"); emit_signal("height_maps_changed"); } // Rebulid control maps if dirty if (_generated_control_maps.is_dirty()) { LOG(EXTREME, "Regenerating control texture array from regions"); _control_maps.clear(); for (const Vector2i ®ion_loc : _region_locations) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (region) { _control_maps.push_back(region->get_control_map()); } } _generated_control_maps.create(_control_maps); any_changed = true; LOG(DEBUG, "Emitting control_maps_changed"); emit_signal("control_maps_changed"); } // Rebulid color maps if dirty if (_generated_color_maps.is_dirty()) { LOG(EXTREME, "Regenerating color texture array from regions"); _color_maps.clear(); for (const Vector2i ®ion_loc : _region_locations) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (region) { _color_maps.push_back(region->get_color_map()); } } _generated_color_maps.create(_color_maps); any_changed = true; LOG(DEBUG, "Emitting color_maps_changed"); emit_signal("color_maps_changed"); } // If no maps have been rebuilt, update only individual regions in the array. // Regions marked Edited have been changed by Terrain3DEditor::_operate_map or undo / redo processing. if (!any_changed) { for (const Vector2i ®ion_loc : _region_locations) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (region && region->is_edited()) { int region_id = get_region_id(region_loc); switch (p_map_type) { case TYPE_HEIGHT: _generated_height_maps.update(region->get_height_map(), region_id); LOG(DEBUG, "Emitting height_maps_changed"); emit_signal("height_maps_changed"); break; case TYPE_CONTROL: _generated_control_maps.update(region->get_control_map(), region_id); LOG(DEBUG, "Emitting control_maps_changed"); emit_signal("control_maps_changed"); break; case TYPE_COLOR: _generated_color_maps.update(region->get_color_map(), region_id); LOG(DEBUG, "Emitting color_maps_changed"); emit_signal("color_maps_changed"); break; default: _generated_height_maps.update(region->get_height_map(), region_id); _generated_control_maps.update(region->get_control_map(), region_id); _generated_color_maps.update(region->get_color_map(), region_id); LOG(DEBUG, "Emitting height_maps_changed"); emit_signal("height_maps_changed"); LOG(DEBUG, "Emitting control_maps_changed"); emit_signal("control_maps_changed"); LOG(DEBUG, "Emitting color_maps_changed"); emit_signal("color_maps_changed"); break; } } } } if (any_changed) { LOG(DEBUG, "Emitting maps_changed"); emit_signal("maps_changed"); _terrain->snap(); } } void Terrain3DData::set_pixel(const MapType p_map_type, const Vector3 &p_global_position, const Color &p_pixel) { if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); return; } Vector2i region_loc = get_region_location(p_global_position); Terrain3DRegion *region = get_region_ptr(region_loc); if (!region) { LOG(ERROR, "No active region found at: ", p_global_position); return; } if (region->is_deleted()) { LOG(ERROR, "No active region found at: ", p_global_position); return; } Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); img_pos = img_pos.clamp(V2I_ZERO, V2I(_region_size - 1)); Image *map = region->get_map_ptr(p_map_type); if (map) { map->set_pixelv(img_pos, p_pixel); region->set_modified(true); } } Color Terrain3DData::get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const { if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); return COLOR_NAN; } Vector2i region_loc = get_region_location(p_global_position); const Terrain3DRegion *region = get_region_ptr(region_loc); if (!region) { return COLOR_NAN; } if (region->is_deleted()) { return COLOR_NAN; } Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); img_pos = img_pos.clamp(V2I_ZERO, V2I(_region_size - 1)); Image *map = region->get_map_ptr(p_map_type); if (map) { return map->get_pixelv(img_pos); } else { return COLOR_NAN; } } real_t Terrain3DData::get_height(const Vector3 &p_global_position) const { if (is_hole(get_control(p_global_position))) { return NAN; } Vector3 pos = p_global_position; const real_t &step = _vertex_spacing; pos.y = 0.f; // Round to nearest vertex Vector3 pos_round = pos.snapped(Vector3(step, 0.f, step)); // If requested position is close to a vertex, return its height if ((pos - pos_round).length_squared() < 0.0001f) { return get_pixel(TYPE_HEIGHT, pos).r; } else { // Otherwise, bilinearly interpolate 4 surrounding vertices Vector3 pos00 = Vector3(floor(pos.x / step) * step, 0.f, floor(pos.z / step) * step); real_t ht00 = get_pixel(TYPE_HEIGHT, pos00).r; Vector3 pos01 = pos00 + Vector3(0.f, 0.f, step); real_t ht01 = get_pixel(TYPE_HEIGHT, pos01).r; Vector3 pos10 = pos00 + Vector3(step, 0.f, 0.f); real_t ht10 = get_pixel(TYPE_HEIGHT, pos10).r; Vector3 pos11 = pos00 + Vector3(step, 0.f, step); real_t ht11 = get_pixel(TYPE_HEIGHT, pos11).r; return bilerp(ht00, ht01, ht10, ht11, pos00, pos11, pos); } } Vector3 Terrain3DData::get_normal(const Vector3 &p_global_position) const { if (get_region_idp(p_global_position) < 0 || is_hole(get_control(p_global_position))) { return V3_NAN; } real_t height = get_height(p_global_position); real_t u = height - get_height(p_global_position + Vector3(_vertex_spacing, 0.0f, 0.0f)); real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, _vertex_spacing)); Vector3 normal = Vector3(u, _vertex_spacing, v); normal.normalize(); return normal; } bool Terrain3DData::is_in_slope(const Vector3 &p_global_position, const Vector2 &p_slope_range, const Vector3 &p_normal) const { // If slope is full range, nothing to do here const Vector2 slope_range = CLAMP(p_slope_range, V2_ZERO, V2(90.f)); if (slope_range.y - slope_range.x > 89.99f) { return true; } // Use custom normal if provided Vector3 slope_normal = p_normal; if (!slope_normal.is_zero_approx()) { slope_normal.normalize(); } else { // Else, compute terrain normal if (get_region_idp(p_global_position) < 0) { return false; } // Adapted from get_height() to work with holes auto get_height = [&](Vector3 pos) -> real_t { real_t step = _terrain->get_vertex_spacing(); // Round to nearest vertex Vector3 pos_round = pos.snapped(Vector3(step, 0.f, step)); real_t height = get_pixel(TYPE_HEIGHT, pos_round).r; return std::isnan(height) ? 0.f : height; }; const real_t vertex_spacing = _terrain->get_vertex_spacing(); const real_t height = get_height(p_global_position); const real_t u = height - get_height(p_global_position + Vector3(vertex_spacing, 0.0f, 0.0f)); const real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, vertex_spacing)); slope_normal = Vector3(u, vertex_spacing, v); slope_normal.normalize(); } const real_t slope_angle = Math::acos(slope_normal.dot(V3_UP)); const real_t slope_angle_degrees = Math::rad_to_deg(slope_angle); return (slope_range.x <= slope_angle_degrees) && (slope_angle_degrees <= slope_range.y); } /** * Returns: * X = base index * Y = overlay index * Z = percentage blend between X and Y. Limited to the fixed values in RANGE. * Interpretation of this data is up to the gamedev. Unfortunately due to blending, this isn't * pixel perfect. I would have your player print this location as you walk around to see how the * blending values look, then consider that the overlay texture is visible starting at a blend * value of .3-.5, otherwise it's the base texture. **/ Vector3 Terrain3DData::get_texture_id(const Vector3 &p_global_position) const { // Verify in a region int region_id = get_region_idp(p_global_position); if (region_id < 0) { return V3_NAN; } // Verify not in a hole float src = get_pixel(TYPE_CONTROL, p_global_position).r; // 32-bit float, not double/real if (is_hole(src)) { return V3_NAN; } // If material available, autoshader enabled, and pixel set to auto if (_terrain) { Ref t_material = _terrain->get_material(); bool auto_enabled = t_material->get_auto_shader_enabled(); bool control_auto = is_auto(src); if (auto_enabled && control_auto) { real_t auto_slope = real_t(t_material->get_shader_param("auto_slope")); real_t auto_height_reduction = real_t(t_material->get_shader_param("auto_height_reduction")); real_t height = get_height(p_global_position); Vector3 normal = get_normal(p_global_position); uint32_t base_id = t_material->get_shader_param("auto_base_texture"); uint32_t overlay_id = t_material->get_shader_param("auto_overlay_texture"); real_t blend = CLAMP((auto_slope * 2.f * (normal.y - 1.f) + 1.f) - auto_height_reduction * .01f * height, 0.f, 1.f); return Vector3(real_t(base_id), real_t(overlay_id), blend); } } // Else, just get textures from control map uint32_t base_id = get_base(src); uint32_t overlay_id = get_overlay(src); real_t blend = real_t(get_blend(src)) / 255.0f; return Vector3(real_t(base_id), real_t(overlay_id), blend); } /** * Returns the location of a terrain vertex at a certain LOD. If there is a hole at the position, it returns * NAN in the vector's Y coordinate. * p_lod (0-8): Determines how many heights around the given global position will be sampled. * p_filter: * HEIGHT_FILTER_NEAREST: Samples the height map at the exact coordinates given. * HEIGHT_FILTER_MINIMUM: Samples (1 << p_lod) ** 2 heights around the given coordinates and returns the lowest. * p_global_position: X and Z coordinates of the vertex. Heights will be sampled around these coordinates. */ Vector3 Terrain3DData::get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const { LOG(INFO, "Calculating vertex location"); int32_t step = 1 << CLAMP(p_lod, 0, 8); real_t height = 0.0f; switch (p_filter) { case HEIGHT_FILTER_NEAREST: { if (is_hole(get_control(p_global_position))) { height = NAN; } else { height = get_height(p_global_position); } } break; case HEIGHT_FILTER_MINIMUM: { height = get_height(p_global_position); for (int32_t dx = -step / 2; dx < step / 2; dx += 1) { for (int32_t dz = -step / 2; dz < step / 2; dz += 1) { Vector3 position = p_global_position + Vector3(dx, 0.f, dz) * _vertex_spacing; if (is_hole(get_control(position))) { height = NAN; break; } real_t h = get_height(position); if (h < height) { height = h; } } } } break; } return Vector3(p_global_position.x, height, p_global_position.z); } void Terrain3DData::add_edited_area(const AABB &p_area) { if (_edited_area.has_surface()) { _edited_area = _edited_area.merge(p_area); } else { _edited_area = p_area; } LOG(DEBUG, "Emitting maps_edited"); emit_signal("maps_edited", p_area); } // Recalculates master height range from all active regions current height ranges // Recursive mode has all regions to recalculate from each heightmap pixel void Terrain3DData::calc_height_range(const bool p_recursive) { _master_height_range = V2_ZERO; for (const Vector2i ®ion_loc : _region_locations) { Terrain3DRegion *region = get_region_ptr(region_loc); if (!region) { continue; } if (p_recursive) { region->calc_height_range(); } update_master_heights(region->get_height_range()); } LOG(EXTREME, "Accumulated height range for all regions: ", _master_height_range); } /** * Imports an Image set (Height, Control, Color) into Terrain3DData * It does NOT normalize values to 0-1. You must do that using get_min_max() and adjusting scale and offset. * Parameters: * p_images - MapType.TYPE_MAX sized array of Images for Height, Control, Color. Images can be blank or null * p_global_position - X,0,Z location on the region map. Valid range is ~ (+/-8192, +/-8192) * p_offset - Add this factor to all height values, can be negative * p_scale - Scale all height values by this factor (applied after offset) */ void Terrain3DData::import_images(const TypedArray &p_images, const Vector3 &p_global_position, const real_t p_offset, const real_t p_scale) { IS_INIT_MESG("Data not initialized", VOID); if (p_images.size() != TYPE_MAX) { LOG(ERROR, "p_images.size() is ", p_images.size(), ". It should be ", TYPE_MAX, " even if some Images are blank or null"); return; } Vector2i img_size = V2I_ZERO; for (int i = 0; i < TYPE_MAX; i++) { Ref img = p_images[i]; if (img.is_valid() && !img->is_empty()) { LOG(INFO, "Importing image type ", TYPESTR[i], ", size: ", img->get_size(), ", format: ", img->get_format()); if (i == TYPE_HEIGHT) { LOG(INFO, "Applying offset: ", p_offset, ", scale: ", p_scale); } if (img_size == V2I_ZERO) { img_size = img->get_size(); } else if (img_size != img->get_size()) { LOG(ERROR, "Included Images in p_images have different dimensions. Aborting import"); return; } } } if (img_size == V2I_ZERO) { LOG(ERROR, "All images are empty. Nothing to import"); return; } Vector3 descaled_position = p_global_position / _vertex_spacing; int max_dimension = _region_size * REGION_MAP_SIZE / 2; if ((std::abs(descaled_position.x) > max_dimension) || (std::abs(descaled_position.z) > max_dimension)) { LOG(ERROR, "Specify a position within +/-", Vector3(max_dimension, 0.f, max_dimension) * _vertex_spacing); return; } if ((descaled_position.x + img_size.x > max_dimension) || (descaled_position.z + img_size.y > max_dimension)) { LOG(ERROR, img_size, " image will not fit at ", p_global_position, ". Try ", -(img_size * _vertex_spacing) / 2.f, " to center, or increase region_size"); return; } TypedArray src_images; src_images.resize(TYPE_MAX); for (int i = 0; i < TYPE_MAX; i++) { Ref img = p_images[i]; src_images[i] = img; if (img.is_null()) { continue; } // Apply scale and offsets to the heightmap and filter out invalid data if (i == TYPE_HEIGHT) { LOG(DEBUG, "Creating new temp image to adjust scale: ", p_scale, " offset: ", p_offset); Ref newimg = Image::create_empty(img->get_size().x, img->get_size().y, false, FORMAT[TYPE_HEIGHT]); for (int y = 0; y < img->get_height(); y++) { for (int x = 0; x < img->get_width(); x++) { Color clr = img->get_pixel(x, y); if (std::isnormal(clr.r)) { clr.r = (clr.r * p_scale) + p_offset; } else { clr.r = p_offset; } newimg->set_pixel(x, y, clr); } } src_images[i] = newimg; } } // Calculate regions this image will span int img_start_x = (int)Math::floor(descaled_position.x); int img_start_z = (int)Math::floor(descaled_position.z); int img_end_x = img_start_x + img_size.x - 1; int img_end_z = img_start_z + img_size.y - 1; int start_region_x = (int)Math::floor(real_t(img_start_x) / real_t(_region_size)); int start_region_z = (int)Math::floor(real_t(img_start_z) / real_t(_region_size)); int end_region_x = (int)Math::floor(real_t(img_end_x) / real_t(_region_size)); int end_region_z = (int)Math::floor(real_t(img_end_z) / real_t(_region_size)); // Clamp region indices to valid range int half_region_map = REGION_MAP_SIZE / 2; start_region_x = CLAMP(start_region_x, -half_region_map, half_region_map - 1); start_region_z = CLAMP(start_region_z, -half_region_map, half_region_map - 1); end_region_x = CLAMP(end_region_x, -half_region_map, half_region_map - 1); end_region_z = CLAMP(end_region_z, -half_region_map, half_region_map - 1); LOG(DEBUG, "Image spans regions (", start_region_x, ",", start_region_z, ") to (", end_region_x, ",", end_region_z, ")"); bool generate_mipmaps = false; for (int rz = start_region_z; rz <= end_region_z; rz++) { for (int rx = start_region_x; rx <= end_region_x; rx++) { Vector2i region_loc = Vector2i(rx, rz); int region_start_x = rx * _region_size; int region_start_z = rz * _region_size; int region_end_x = region_start_x + _region_size - 1; int region_end_z = region_start_z + _region_size - 1; int overlap_start_x = MAX(region_start_x, img_start_x); int overlap_start_z = MAX(region_start_z, img_start_z); int overlap_end_x = MIN(region_end_x, img_end_x); int overlap_end_z = MIN(region_end_z, img_end_z); // Skip if no overlap if (overlap_end_x < overlap_start_x || overlap_end_z < overlap_start_z) { continue; } int copy_width = overlap_end_x - overlap_start_x + 1; int copy_height = overlap_end_z - overlap_start_z + 1; int src_x = overlap_start_x - img_start_x; int src_z = overlap_start_z - img_start_z; int dst_x = overlap_start_x - region_start_x; int dst_z = overlap_start_z - region_start_z; LOG(DEBUG, "Region ", region_loc, ": copying ", Vector2i(copy_width, copy_height), " from img(", src_x, ",", src_z, ") to region(", dst_x, ",", dst_z, ")"); Ref region = get_region(region_loc); if (region.is_null()) { region.instantiate(); region->set_location(region_loc); region->set_region_size(_region_size); region->set_vertex_spacing(_vertex_spacing); add_region(region, false); } else if (region->is_deleted()) { region->clear(); region->set_location(region_loc); region->set_region_size(_region_size); region->set_vertex_spacing(_vertex_spacing); } for (int i = 0; i < TYPE_MAX; i++) { Ref img = src_images[i]; if (img.is_valid() && !img->is_empty()) { Ref region_map; Ref existing_map = region->get_map(static_cast(i)); if (existing_map.is_valid() && !existing_map->is_empty()) { region_map.instantiate(); region_map->copy_from(existing_map); if (region_map->get_format() != img->get_format()) { region_map->convert(img->get_format()); } } else { region_map = Util::get_filled_image(_region_sizev, COLOR[i], false, img->get_format()); } region_map->blit_rect(img, Rect2i(src_x, src_z, copy_width, copy_height), Vector2i(dst_x, dst_z)); region->set_map(static_cast(i), region_map); if (i == TYPE_COLOR) { generate_mipmaps = true; } } } region->set_modified(true); region->sanitize_maps(); } } update_maps(TYPE_MAX, true, generate_mipmaps); } /** Exports a specified map as one of r16/raw, exr, jpg, png, webp, res, tres * r16 or exr are recommended for roundtrip external editing * r16 can be edited by Krita, however you must know the dimensions and min/max before reimporting * res/tres stores in Godot's native format. */ Error Terrain3DData::export_image(const String &p_file_name, const MapType p_map_type) const { if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Invalid map type specified: ", p_map_type, " max: ", TYPE_MAX - 1); return FAILED; } if (p_file_name.is_empty()) { LOG(ERROR, "No file specified. Nothing to export"); return FAILED; } if (get_region_count() == 0) { LOG(ERROR, "No valid regions. Nothing to export"); return FAILED; } // Simple file name validation static const String bad_chars = "?*|%<>\""; for (int i = 0; i < bad_chars.length(); ++i) { for (int j = 0; j < p_file_name.length(); ++j) { if (bad_chars[i] == p_file_name[j]) { LOG(ERROR, "Invalid file path '" + p_file_name + "'"); return FAILED; } } } // Update path delimeter String file_name = p_file_name.replace("\\", "/"); // Check if p_file_name has a path and prepend "res://" if not bool is_simple_filename = true; for (int i = 0; i < file_name.length(); ++i) { char32_t c = file_name[i]; if (c == '/' || c == ':') { is_simple_filename = false; break; } } if (is_simple_filename) { file_name = "res://" + file_name; } // Check if the file can be opened for writing Ref file_ref = FileAccess::open(file_name, FileAccess::ModeFlags::WRITE); if (file_ref.is_null()) { LOG(ERROR, "Cannot open file '" + file_name + "' for writing"); return FAILED; } file_ref->close(); // Filename is validated. Begin export image generation Ref img = layered_to_image(p_map_type); if (img.is_null() || img->is_empty()) { LOG(ERROR, "Cannot create an export image for map type: ", TYPESTR[p_map_type]); return FAILED; } String ext = file_name.get_extension().to_lower(); LOG(MESG, "Saving ", img->get_size(), " sized ", TYPESTR[p_map_type], " map in format ", img->get_format(), " as ", ext, " to: ", file_name); Vector2i minmax = Util::get_min_max(img); LOG(MESG, "Minimum height: ", minmax.x, ", Maximum height: ", minmax.y); if (ext == "r16" || ext == "raw") { Ref file = FileAccess::open(file_name, FileAccess::WRITE); real_t height_min = minmax.x; real_t height_max = minmax.y; real_t hscale = 65535.0 / (height_max - height_min); for (int y = 0; y < img->get_height(); y++) { for (int x = 0; x < img->get_width(); x++) { int h = int((img->get_pixel(x, y).r - height_min) * hscale); h = CLAMP(h, 0, 65535); file->store_16(h); } } return file->get_error(); } else if (ext == "exr") { return img->save_exr(file_name, (p_map_type == TYPE_HEIGHT) ? true : false); } else if (ext == "png") { return img->save_png(file_name); } else if (ext == "jpg") { return img->save_jpg(file_name); } else if (ext == "webp") { return img->save_webp(file_name); } else if ((ext == "res") || (ext == "tres")) { return ResourceSaver::get_singleton()->save(img, file_name, ResourceSaver::FLAG_COMPRESS); } LOG(ERROR, "No recognized file type. See docs for valid extensions"); return FAILED; } Ref Terrain3DData::layered_to_image(const MapType p_map_type) const { LOG(INFO, "Generating a full sized image for all regions including empty regions"); MapType map_type = p_map_type; if (map_type >= TYPE_MAX) { map_type = TYPE_HEIGHT; } Vector2i top_left = V2I_ZERO; Vector2i bottom_right = V2I_ZERO; for (const Vector2i ®ion_loc : _region_locations) { LOG(DEBUG, "Region location: ", region_loc); if (region_loc.x < top_left.x) { top_left.x = region_loc.x; } else if (region_loc.x > bottom_right.x) { bottom_right.x = region_loc.x; } if (region_loc.y < top_left.y) { top_left.y = region_loc.y; } else if (region_loc.y > bottom_right.y) { bottom_right.y = region_loc.y; } } LOG(DEBUG, "Full range to cover all regions: ", top_left, " to ", bottom_right); Vector2i img_size = Vector2i(1 + bottom_right.x - top_left.x, 1 + bottom_right.y - top_left.y) * _region_size; LOG(DEBUG, "Image size: ", img_size); Ref img = Util::get_filled_image(img_size, COLOR[map_type], false, FORMAT[map_type]); for (const Vector2i ®ion_loc : _region_locations) { Vector2i img_location = (region_loc - top_left) * _region_size; LOG(DEBUG, "Region to blit: ", region_loc, " Export image coords: ", img_location); const Terrain3DRegion *region = get_region_ptr(region_loc); if (region) { img->blit_rect(region->get_map(map_type), Rect2i(V2I_ZERO, _region_sizev), img_location); } } return img; } void Terrain3DData::dump(const bool verbose) const { LOG(MESG, "_region_locations (", _region_locations.size(), "): ", _region_locations); Array keys = _regions.keys(); LOG(MESG, "_regions (", keys.size(), "):"); for (const Vector2i ®ion_loc : keys) { const Terrain3DRegion *region = get_region_ptr(region_loc); if (!region) { LOG(WARN, "No region found at: ", region_loc); continue; } region->dump(verbose); } if (verbose) { for (int i = 0; i < _region_map.size(); i++) { if (_region_map[i]) { LOG(MESG, "Region map array index: ", i, " / ", _region_map.size() - 1, ", Region id: ", _region_map[i]); } } Util::dump_maps(_height_maps, "Height maps"); Util::dump_gentex(_generated_height_maps, "height"); Util::dump_maps(_control_maps, "Control maps"); Util::dump_gentex(_generated_control_maps, "control"); Util::dump_maps(_color_maps, "Color maps"); Util::dump_gentex(_generated_color_maps, "color"); } } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DData::_bind_methods() { BIND_ENUM_CONSTANT(HEIGHT_FILTER_NEAREST); BIND_ENUM_CONSTANT(HEIGHT_FILTER_MINIMUM); BIND_CONSTANT(REGION_MAP_SIZE); ClassDB::bind_method(D_METHOD("get_region_count"), &Terrain3DData::get_region_count); ClassDB::bind_method(D_METHOD("set_region_locations", "region_locations"), &Terrain3DData::set_region_locations); ClassDB::bind_method(D_METHOD("get_region_locations"), &Terrain3DData::get_region_locations); ClassDB::bind_method(D_METHOD("get_regions_active", "copy", "deep"), &Terrain3DData::get_regions_active, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_regions_all"), &Terrain3DData::get_regions_all); ClassDB::bind_method(D_METHOD("get_region_map"), &Terrain3DData::get_region_map); ClassDB::bind_static_method("Terrain3DData", D_METHOD("get_region_map_index", "region_location"), &Terrain3DData::get_region_map_index); ClassDB::bind_method(D_METHOD("do_for_regions", "area", "callback"), &Terrain3DData::do_for_regions); ClassDB::bind_method(D_METHOD("change_region_size", "region_size"), &Terrain3DData::change_region_size); ClassDB::bind_method(D_METHOD("get_region_location", "global_position"), &Terrain3DData::get_region_location); ClassDB::bind_method(D_METHOD("get_region_id", "region_location"), &Terrain3DData::get_region_id); ClassDB::bind_method(D_METHOD("get_region_idp", "global_position"), &Terrain3DData::get_region_idp); ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DData::has_region); ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DData::has_regionp); ClassDB::bind_method(D_METHOD("get_region", "region_location"), &Terrain3DData::get_region); ClassDB::bind_method(D_METHOD("get_regionp", "global_position"), &Terrain3DData::get_regionp); ClassDB::bind_method(D_METHOD("set_region_modified", "region_location", "modified"), &Terrain3DData::set_region_modified); ClassDB::bind_method(D_METHOD("is_region_modified", "region_location"), &Terrain3DData::is_region_modified); ClassDB::bind_method(D_METHOD("set_region_deleted", "region_location", "deleted"), &Terrain3DData::set_region_deleted); ClassDB::bind_method(D_METHOD("is_region_deleted", "region_location"), &Terrain3DData::is_region_deleted); ClassDB::bind_method(D_METHOD("add_region_blankp", "global_position", "update"), &Terrain3DData::add_region_blankp, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_region_blank", "region_location", "update"), &Terrain3DData::add_region_blank, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_region", "region", "update"), &Terrain3DData::add_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("remove_regionp", "global_position", "update"), &Terrain3DData::remove_regionp, DEFVAL(true)); ClassDB::bind_method(D_METHOD("remove_regionl", "region_location", "update"), &Terrain3DData::remove_regionl, DEFVAL(true)); ClassDB::bind_method(D_METHOD("remove_region", "region", "update"), &Terrain3DData::remove_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("save_directory", "directory"), &Terrain3DData::save_directory); ClassDB::bind_method(D_METHOD("save_region", "region_location", "directory", "save_16_bit"), &Terrain3DData::save_region, DEFVAL(false)); ClassDB::bind_method(D_METHOD("load_directory", "directory"), &Terrain3DData::load_directory); ClassDB::bind_method(D_METHOD("load_region", "region_location", "directory", "update"), &Terrain3DData::load_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_height_maps"), &Terrain3DData::get_height_maps); ClassDB::bind_method(D_METHOD("get_control_maps"), &Terrain3DData::get_control_maps); ClassDB::bind_method(D_METHOD("get_color_maps"), &Terrain3DData::get_color_maps); ClassDB::bind_method(D_METHOD("get_maps", "map_type"), &Terrain3DData::get_maps); ClassDB::bind_method(D_METHOD("update_maps", "map_type", "all_regions", "generate_mipmaps"), &Terrain3DData::update_maps, DEFVAL(TYPE_MAX), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_height_maps_rid"), &Terrain3DData::get_height_maps_rid); ClassDB::bind_method(D_METHOD("get_control_maps_rid"), &Terrain3DData::get_control_maps_rid); ClassDB::bind_method(D_METHOD("get_color_maps_rid"), &Terrain3DData::get_color_maps_rid); ClassDB::bind_method(D_METHOD("set_pixel", "map_type", "global_position", "pixel"), &Terrain3DData::set_pixel); ClassDB::bind_method(D_METHOD("get_pixel", "map_type", "global_position"), &Terrain3DData::get_pixel); ClassDB::bind_method(D_METHOD("set_height", "global_position", "height"), &Terrain3DData::set_height); ClassDB::bind_method(D_METHOD("get_height", "global_position"), &Terrain3DData::get_height); ClassDB::bind_method(D_METHOD("set_color", "global_position", "color"), &Terrain3DData::set_color); ClassDB::bind_method(D_METHOD("get_color", "global_position"), &Terrain3DData::get_color); ClassDB::bind_method(D_METHOD("set_control", "global_position", "control"), &Terrain3DData::set_control); ClassDB::bind_method(D_METHOD("get_control", "global_position"), &Terrain3DData::get_control); ClassDB::bind_method(D_METHOD("set_roughness", "global_position", "roughness"), &Terrain3DData::set_roughness); ClassDB::bind_method(D_METHOD("get_roughness", "global_position"), &Terrain3DData::get_roughness); ClassDB::bind_method(D_METHOD("set_control_base_id", "global_position", "texture_id"), &Terrain3DData::set_control_base_id); ClassDB::bind_method(D_METHOD("get_control_base_id", "global_position"), &Terrain3DData::get_control_base_id); ClassDB::bind_method(D_METHOD("set_control_overlay_id", "global_position", "texture_id"), &Terrain3DData::set_control_overlay_id); ClassDB::bind_method(D_METHOD("get_control_overlay_id", "global_position"), &Terrain3DData::get_control_overlay_id); ClassDB::bind_method(D_METHOD("set_control_blend", "global_position", "blend_value"), &Terrain3DData::set_control_blend); ClassDB::bind_method(D_METHOD("get_control_blend", "global_position"), &Terrain3DData::get_control_blend); ClassDB::bind_method(D_METHOD("set_control_angle", "global_position", "degrees"), &Terrain3DData::set_control_angle); ClassDB::bind_method(D_METHOD("get_control_angle", "global_position"), &Terrain3DData::get_control_angle); ClassDB::bind_method(D_METHOD("set_control_scale", "global_position", "percentage_modifier"), &Terrain3DData::set_control_scale); ClassDB::bind_method(D_METHOD("get_control_scale", "global_position"), &Terrain3DData::get_control_scale); ClassDB::bind_method(D_METHOD("set_control_hole", "global_position", "enable"), &Terrain3DData::set_control_hole); ClassDB::bind_method(D_METHOD("get_control_hole", "global_position"), &Terrain3DData::get_control_hole); ClassDB::bind_method(D_METHOD("set_control_navigation", "global_position", "enable"), &Terrain3DData::set_control_navigation); ClassDB::bind_method(D_METHOD("get_control_navigation", "global_position"), &Terrain3DData::get_control_navigation); ClassDB::bind_method(D_METHOD("set_control_auto", "global_position", "enable"), &Terrain3DData::set_control_auto); ClassDB::bind_method(D_METHOD("get_control_auto", "global_position"), &Terrain3DData::get_control_auto); ClassDB::bind_method(D_METHOD("get_normal", "global_position"), &Terrain3DData::get_normal); ClassDB::bind_method(D_METHOD("is_in_slope", "global_position", "slope_range", "normal"), &Terrain3DData::is_in_slope, DEFVAL(V3_ZERO)); ClassDB::bind_method(D_METHOD("get_texture_id", "global_position"), &Terrain3DData::get_texture_id); ClassDB::bind_method(D_METHOD("get_mesh_vertex", "lod", "filter", "global_position"), &Terrain3DData::get_mesh_vertex); ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DData::get_height_range); ClassDB::bind_method(D_METHOD("calc_height_range", "recursive"), &Terrain3DData::calc_height_range, DEFVAL(false)); ClassDB::bind_method(D_METHOD("import_images", "images", "global_position", "offset", "scale"), &Terrain3DData::import_images, DEFVAL(V3_ZERO), DEFVAL(0.f), DEFVAL(1.f)); ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DData::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DData::layered_to_image); ClassDB::bind_method(D_METHOD("dump", "verbose"), &Terrain3DData::dump, DEFVAL(false)); int ro_flags = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "region_locations", PROPERTY_HINT_ARRAY_TYPE, "Vector2i", ro_flags), "set_region_locations", "get_region_locations"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "height_maps", PROPERTY_HINT_ARRAY_TYPE, "Image", ro_flags), "", "get_height_maps"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "control_maps", PROPERTY_HINT_ARRAY_TYPE, "Image", ro_flags), "", "get_control_maps"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "color_maps", PROPERTY_HINT_ARRAY_TYPE, "Image", ro_flags), "", "get_color_maps"); ADD_SIGNAL(MethodInfo("maps_changed")); ADD_SIGNAL(MethodInfo("region_map_changed")); ADD_SIGNAL(MethodInfo("height_maps_changed")); ADD_SIGNAL(MethodInfo("control_maps_changed")); ADD_SIGNAL(MethodInfo("color_maps_changed")); ADD_SIGNAL(MethodInfo("maps_edited", PropertyInfo(Variant::AABB, "edited_area"))); } ================================================ FILE: src/terrain_3d_data.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_DATA_CLASS_H #define TERRAIN3D_DATA_CLASS_H #include "constants.h" #include "generated_texture.h" #include "terrain_3d.h" #include "terrain_3d_region.h" class Terrain3D; class Terrain3DData : public Object { GDCLASS(Terrain3DData, Object); CLASS_NAME(); friend Terrain3D; public: // Constants static inline const real_t CURRENT_DATA_VERSION = 0.93f; // Current Data format version static inline const int REGION_MAP_SIZE = 32; static inline const Vector2i REGION_MAP_VSIZE = V2I(REGION_MAP_SIZE); enum HeightFilter { HEIGHT_FILTER_NEAREST, HEIGHT_FILTER_MINIMUM }; private: Terrain3D *_terrain = nullptr; // Data Settings & flags int _region_size = 0; // Set by Terrain3D::set_region_size Vector2i _region_sizev = V2I(_region_size); real_t _vertex_spacing = 1.f; // Set by Terrain3D::set_vertex_spacing AABB _edited_area; Vector2 _master_height_range = V2_ZERO; ///////// // Terrain3DRegions house the maps, instances, and other data for each region. // Regions are dual indexed: // 1) By `region_location:Vector2i` as the primary key. This is the only stable index // so should be the main index for users. // 2) By `region_id:int`. This index changes on every add/remove, depends on load order, // and is not stable. It should not be relied on by users and is primarily for internal use. // Private functions should be indexed by region_id or region_location // Public functions by region_location or global_position // `_regions` stores all loaded Terrain3DRegions, indexed by region_location. If marked for // deletion they are removed from here upon saving, however they may stay in memory if tracked // by the Undo system. Dictionary _regions; // Dict[region_location:Vector2i] -> Terrain3DRegion // All _active_ region maps are maintained in these secondary indices. // Regions are considered active if and only if they exist in `_region_locations`. The other // arrays are built off of this index; its order defines region_id. // The image arrays are converted to TextureArrays for the shader. TypedArray _region_locations; TypedArray _height_maps; TypedArray _control_maps; TypedArray _color_maps; // Editing occurs on the Image arrays above, which are converted to Texture arrays // below for the shader. // 32x32 grid with region_id:int at its location, no region = 0, region_ids >= 1 PackedInt32Array _region_map; bool _region_map_dirty = true; // These contain the TextureArray RIDs from the RenderingServer GeneratedTexture _generated_height_maps; GeneratedTexture _generated_control_maps; GeneratedTexture _generated_color_maps; // Functions void _clear(); void _copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2i &p_src_rect, const Rect2i &p_dst_rect, const Terrain3DRegion *p_dst_region); public: Terrain3DData() {} void initialize(Terrain3D *p_terrain); ~Terrain3DData() { _clear(); } // Regions int get_region_count() const { return _region_locations.size(); } void set_region_locations(const TypedArray &p_locations); TypedArray get_region_locations() const { return _region_locations; } TypedArray get_regions_active(const bool p_copy = false, const bool p_deep = false) const; Dictionary get_regions_all() const { return _regions; } PackedInt32Array get_region_map() const { return _region_map; } static int get_region_map_index(const Vector2i &p_region_loc); void do_for_regions(const Rect2i &p_area, const Callable &p_callback); void change_region_size(int region_size); Vector2i get_region_location(const Vector3 &p_global_position) const; int get_region_id(const Vector2i &p_region_loc) const; int get_region_idp(const Vector3 &p_global_position) const; bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } Ref get_region(const Vector2i &p_region_loc) const; Terrain3DRegion *get_region_ptr(const Vector2i &p_region_loc) const; template // Catch invalid types. See note below in implementation. Terrain3DRegion *get_region_ptr(const T &p_region_loc) const = delete; Ref get_regionp(const Vector3 &p_global_position) const; void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); bool is_region_modified(const Vector2i &p_region_loc) const; void set_region_deleted(const Vector2i &p_region_loc, const bool p_deleted = true); bool is_region_deleted(const Vector2i &p_region_loc) const; Ref add_region_blankp(const Vector3 &p_global_position, const bool p_update = true); Ref add_region_blank(const Vector2i &p_region_loc, const bool p_update = true); Error add_region(const Ref &p_region, const bool p_update = true); void remove_regionp(const Vector3 &p_global_position, const bool p_update = true); void remove_regionl(const Vector2i &p_region_loc, const bool p_update = true); void remove_region(const Ref &p_region, const bool p_update = true); // File I/O void save_directory(const String &p_dir); void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false); void load_directory(const String &p_dir); void load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update = true); // Maps TypedArray get_height_maps() const { return _height_maps; } TypedArray get_control_maps() const { return _control_maps; } TypedArray get_color_maps() const { return _color_maps; } TypedArray get_maps(const MapType p_map_type) const; void update_maps(const MapType p_map_type = TYPE_MAX, const bool p_all_regions = true, const bool p_generate_mipmaps = false); RID get_height_maps_rid() const { return _generated_height_maps.get_rid(); } RID get_control_maps_rid() const { return _generated_control_maps.get_rid(); } RID get_color_maps_rid() const { return _generated_color_maps.get_rid(); } void set_pixel(const MapType p_map_type, const Vector3 &p_global_position, const Color &p_pixel); Color get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const; void set_height(const Vector3 &p_global_position, const real_t p_height); real_t get_height(const Vector3 &p_global_position) const; void set_color(const Vector3 &p_global_position, const Color &p_color); Color get_color(const Vector3 &p_global_position) const; void set_control(const Vector3 &p_global_position, const uint32_t p_control); uint32_t get_control(const Vector3 &p_global_position) const; void set_roughness(const Vector3 &p_global_position, const real_t p_roughness); real_t get_roughness(const Vector3 &p_global_position) const; // Control Map void set_control_base_id(const Vector3 &p_global_position, const uint8_t p_base); uint32_t get_control_base_id(const Vector3 &p_global_position) const; void set_control_overlay_id(const Vector3 &p_global_position, const uint8_t p_overlay); uint32_t get_control_overlay_id(const Vector3 &p_global_position) const; void set_control_blend(const Vector3 &p_global_position, const real_t p_blend); real_t get_control_blend(const Vector3 &p_global_position) const; void set_control_angle(const Vector3 &p_global_position, const real_t p_angle); real_t get_control_angle(const Vector3 &p_global_position) const; void set_control_scale(const Vector3 &p_global_position, const real_t p_scale); real_t get_control_scale(const Vector3 &p_global_position) const; void set_control_hole(const Vector3 &p_global_position, const bool p_hole); bool get_control_hole(const Vector3 &p_global_position) const; void set_control_navigation(const Vector3 &p_global_position, const bool p_navigation); bool get_control_navigation(const Vector3 &p_global_position) const; void set_control_auto(const Vector3 &p_global_position, const bool p_auto); bool get_control_auto(const Vector3 &p_global_position) const; Vector3 get_normal(const Vector3 &p_global_position) const; bool is_in_slope(const Vector3 &p_global_position, const Vector2 &p_slope_range, const Vector3 &p_normal = V3_ZERO) const; Vector3 get_texture_id(const Vector3 &p_global_position) const; Vector3 get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const; void add_edited_area(const AABB &p_area); void clear_edited_area() { _edited_area = AABB(); } AABB get_edited_area() const { return _edited_area; } Vector2 get_height_range() const { return _master_height_range; } void update_master_height(const real_t p_height); void update_master_heights(const Vector2 &p_low_high); void calc_height_range(const bool p_recursive = false); void import_images(const TypedArray &p_images, const Vector3 &p_global_position = V3_ZERO, const real_t p_offset = 0.f, const real_t p_scale = 1.f); Error export_image(const String &p_file_name, const MapType p_map_type = TYPE_HEIGHT) const; Ref layered_to_image(const MapType p_map_type) const; // Utility void dump(const bool verbose = false) const; protected: static void _bind_methods(); }; VARIANT_ENUM_CAST(Terrain3DData::HeightFilter); // Inline Region Functions // Verifies the location is within the bounds of the _region_map array and // the world, returning the _region_map index, which contains the region_id. // Valid region locations are -16, -16 to 15, 15, or when offset: 0, 0 to 31, 31 // If any bits other than 0x1F are set, it's out of bounds and returns -1 inline int Terrain3DData::get_region_map_index(const Vector2i &p_region_loc) { // Offset world to positive values only Vector2i loc = p_region_loc + (REGION_MAP_VSIZE / 2); // Catch values > 31 if ((uint32_t(loc.x | loc.y) & uint32_t(~0x1F)) > 0) { return -1; } return loc.y * REGION_MAP_SIZE + loc.x; } // Returns a region location given a global position. No bounds checking nor data access. inline Vector2i Terrain3DData::get_region_location(const Vector3 &p_global_position) const { Vector2 descaled_position = v3v2(p_global_position) / _vertex_spacing; return Vector2i((descaled_position / real_t(_region_size)).floor()); } // Returns id of any active region. -1 if out of bounds or no region, or region id inline int Terrain3DData::get_region_id(const Vector2i &p_region_loc) const { int map_index = get_region_map_index(p_region_loc); if (map_index >= 0) { int region_id = _region_map[map_index] - 1; // 0 = no region if (region_id >= 0 && region_id < _region_locations.size()) { return region_id; } } return -1; } inline int Terrain3DData::get_region_idp(const Vector3 &p_global_position) const { return get_region_id(get_region_location(p_global_position)); } // This function is slower than the version below, but safer when interacting with Godot, which requires // References. This includes backing up regions in the UndoRedoManager. // Ref<> has a pointer constructor, so a reference can be created with Ref<>(ptr). Godot detects the // pointer is already tracked and increments the reference counter. // Passing the pointer to a function with a Ref<> parameter works, and there's an implicit conversion to Ref. // However, let's require explicit conversions for clarity, so wrap a Ref around it: // eg. backup_region(Ref(raw_ptr)); // Should be used for most functions in Editor and Instancer. inline Ref Terrain3DData::get_region(const Vector2i &p_region_loc) const { return _regions.get(p_region_loc, Ref()); } // Using the raw pointer is faster than creating a Ref<>. It can also safely be converted to a Ref as needed // with Ref<>(ptr). Use this function when retreiving regions frequently, eg looping over get_pixel(). // Should be used for most data processing in Data, but not region handling. // Re overloaded template // get_region_ptr(region_locs[i]) worked with an implicit conversion of Variant::Vector2i to Vector2i. // However it also worked for Variant::Object, which silently sent invalid data. // The overloaded template was added to catch this. Pulling out of a dictionary/array gives a Variant, // so now explicit conversion is required, eg. get_region_ptr(Vector2i(locs[i])). inline Terrain3DRegion *Terrain3DData::get_region_ptr(const Vector2i &p_region_loc) const { if (_regions.has(p_region_loc)) { return cast_to(_regions[p_region_loc]); } return nullptr; } inline Ref Terrain3DData::get_regionp(const Vector3 &p_global_position) const { return _regions.get(get_region_location(p_global_position), Ref()); } // Inline Map Functions inline void Terrain3DData::set_height(const Vector3 &p_global_position, const real_t p_height) { set_pixel(TYPE_HEIGHT, p_global_position, Color(p_height, 0.f, 0.f, 1.f)); } inline void Terrain3DData::set_color(const Vector3 &p_global_position, const Color &p_color) { Color clr = p_color; clr.a = get_roughness(p_global_position); set_pixel(TYPE_COLOR, p_global_position, clr); } inline Color Terrain3DData::get_color(const Vector3 &p_global_position) const { Color clr = get_pixel(TYPE_COLOR, p_global_position); clr.a = 1.0f; return clr; } inline void Terrain3DData::set_control(const Vector3 &p_global_position, const uint32_t p_control) { set_pixel(TYPE_CONTROL, p_global_position, Color(as_float(p_control), 0.f, 0.f, 1.f)); } inline uint32_t Terrain3DData::get_control(const Vector3 &p_global_position) const { real_t val = get_pixel(TYPE_CONTROL, p_global_position).r; return (std::isnan(val)) ? UINT32_MAX : as_uint(val); } inline void Terrain3DData::set_control_base_id(const Vector3 &p_global_position, const uint8_t p_base) { uint32_t control = get_control(p_global_position); uint8_t base = CLAMP(p_base, uint8_t(0), uint8_t(31)); set_control(p_global_position, (control & ~(0x1F << 27)) | enc_base(base)); } inline uint32_t Terrain3DData::get_control_base_id(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); return control == UINT32_MAX ? UINT32_MAX : get_base(control); } inline void Terrain3DData::set_control_overlay_id(const Vector3 &p_global_position, const uint8_t p_overlay) { uint32_t control = get_control(p_global_position); uint8_t overlay = CLAMP(p_overlay, uint8_t(0), uint8_t(31)); set_control(p_global_position, (control & ~(0x1F << 22)) | enc_overlay(overlay)); } inline uint32_t Terrain3DData::get_control_overlay_id(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); return control == UINT32_MAX ? UINT32_MAX : get_overlay(control); } // Expects 0.0 to 1.0 range inline void Terrain3DData::set_control_blend(const Vector3 &p_global_position, const real_t p_blend) { uint32_t control = get_control(p_global_position); uint8_t blend = uint8_t(CLAMP(Math::round(p_blend * 255.f), 0.f, 255.f)); set_control(p_global_position, (control & ~(0xFF << 14)) | enc_blend(blend)); } inline real_t Terrain3DData::get_control_blend(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); return control == UINT32_MAX ? NAN : real_t(get_blend(control)) / 255.f; } // Expects angle in degrees inline void Terrain3DData::set_control_angle(const Vector3 &p_global_position, const real_t p_angle) { uint32_t control = get_control(p_global_position); uint8_t uvrotation = uint8_t(CLAMP(Math::round(p_angle / 22.5f), 0.f, 15.f)); set_control(p_global_position, (control & ~(0xF << 10)) | enc_uv_rotation(uvrotation)); } // returns angle in degrees inline real_t Terrain3DData::get_control_angle(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); real_t angle = real_t(get_uv_rotation(control)) * 22.5f; return control == UINT32_MAX ? NAN : angle; } // Expects scale as a percentage modifier inline void Terrain3DData::set_control_scale(const Vector3 &p_global_position, const real_t p_scale) { uint32_t control = get_control(p_global_position); std::array scale_align = { 5, 6, 7, 0, 1, 2, 3, 4 }; uint8_t uvscale = scale_align[uint8_t(CLAMP(Math::round((p_scale + 60.f) / 20.f), 0.f, 7.f))]; set_control(p_global_position, (control & ~(0x7 << 7)) | enc_uv_scale(uvscale)); } inline real_t Terrain3DData::get_control_scale(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); std::array scale_values = { 0.0f, 20.0f, 40.0f, 60.0f, 80.0f, -60.0f, -40.0f, -20.0f }; real_t scale = scale_values[get_uv_scale(control)]; //select from array UI return values return control == UINT32_MAX ? NAN : scale; } inline void Terrain3DData::set_control_hole(const Vector3 &p_global_position, const bool p_hole) { uint32_t control = get_control(p_global_position); set_control(p_global_position, (control & ~(0x1 << 2)) | enc_hole(p_hole)); } inline bool Terrain3DData::get_control_hole(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); return control == UINT32_MAX ? false : is_hole(control); } inline void Terrain3DData::set_control_navigation(const Vector3 &p_global_position, const bool p_navigation) { uint32_t control = get_control(p_global_position); set_control(p_global_position, (control & ~(0x1 << 1)) | enc_nav(p_navigation)); } inline bool Terrain3DData::get_control_navigation(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); return control == UINT32_MAX ? false : is_nav(control); } inline void Terrain3DData::set_control_auto(const Vector3 &p_global_position, const bool p_auto) { uint32_t control = get_control(p_global_position); set_control(p_global_position, (control & ~(0x1)) | enc_auto(p_auto)); } inline bool Terrain3DData::get_control_auto(const Vector3 &p_global_position) const { uint32_t control = get_control(p_global_position); return control == UINT32_MAX ? false : is_auto(control); } inline void Terrain3DData::set_roughness(const Vector3 &p_global_position, const real_t p_roughness) { Color clr = get_pixel(TYPE_COLOR, p_global_position); clr.a = p_roughness; set_pixel(TYPE_COLOR, p_global_position, clr); } inline real_t Terrain3DData::get_roughness(const Vector3 &p_global_position) const { return get_pixel(TYPE_COLOR, p_global_position).a; } inline void Terrain3DData::update_master_height(const real_t p_height) { if (p_height < _master_height_range.x) { _master_height_range.x = p_height; } else if (p_height > _master_height_range.y) { _master_height_range.y = p_height; } } inline void Terrain3DData::update_master_heights(const Vector2 &p_low_high) { if (p_low_high.x < _master_height_range.x) { _master_height_range.x = p_low_high.x; } if (p_low_high.y > _master_height_range.y) { _master_height_range.y = p_low_high.y; } } #endif // TERRAIN3D_DATA_CLASS_H ================================================ FILE: src/terrain_3d_editor.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include "constants.h" #include "logger.h" #include "terrain_3d.h" #include "terrain_3d_data.h" #include "terrain_3d_editor.h" #include "terrain_3d_util.h" /////////////////////////// // Private Functions /////////////////////////// // Sends the whole region aabb to edited_area void Terrain3DEditor::_send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range) { Terrain3D::RegionSize region_size = _terrain->get_region_size(); AABB edited_area; edited_area.position = Vector3(p_region_loc.x * region_size, p_height_range.x, p_region_loc.y * region_size); edited_area.size = Vector3(region_size, p_height_range.y - p_height_range.x, region_size); edited_area.position *= _terrain->get_vertex_spacing(); edited_area.size *= _terrain->get_vertex_spacing(); _terrain->get_data()->add_edited_area(edited_area); } // Process location to add new region, mark as deleted, or just retrieve Ref Terrain3DEditor::_operate_region(const Vector2i &p_region_loc) { bool changed = false; Vector2 height_range; Terrain3DData *data = _terrain->get_data(); // Check if in bounds, limiting errors bool can_print = false; uint64_t ticks = Time::get_singleton()->get_ticks_msec(); if (ticks - _last_region_bounds_error > 1000) { _last_region_bounds_error = ticks; can_print = true; } if (data->get_region_map_index(p_region_loc) < 0) { if (can_print) { LOG(INFO, "Location ", p_region_loc, " out of bounds. Max: ", -Terrain3DData::REGION_MAP_SIZE / 2, " to ", Terrain3DData::REGION_MAP_SIZE / 2 - 1); } return Ref(); } // Get Region & dump data if debug Ref region = data->get_region(p_region_loc); if (can_print) { LOG(DEBUG, "Tool: ", _tool, " Op: ", _operation, " processing region ", p_region_loc, ": ", ptr_to_str(*region)); } // Create new region if location is null or deleted if (region.is_null() || (region.is_valid() && region->is_deleted())) { // And tool is Add Region, or Height + auto_regions if ((_tool == REGION && _operation == ADD) || ((_tool == SCULPT || _tool == HEIGHT) && _brush_data["auto_regions"])) { LOG(DEBUG, "Adding blank region at: ", p_region_loc, ", ptr: ", ptr_to_str(*region)); region = data->add_region_blank(p_region_loc); if (region.is_null()) { LOG(ERROR, "A new region cannot be created"); return region; } _edited_regions.push_back(region); // Ensure new region is added to the redo set changed = true; } } // If removing region else if (region.is_valid() && _tool == REGION && _operation == SUBTRACT) { LOG(DEBUG, "Removing region at: ", p_region_loc, ", ptr: ", ptr_to_str(*region)); _original_regions.push_back(region); height_range = region->get_height_range(); _terrain->get_data()->remove_region(region); changed = true; } if (changed) { _added_removed_locations.push_back(p_region_loc); region->set_modified(true); _send_region_aabb(p_region_loc, height_range); } return region; } void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_t p_camera_direction) { LOG(EXTREME, "Operating at ", p_global_position, " tool type ", _tool, " op ", _operation); MapType map_type = _get_map_type(); if (map_type == TYPE_MAX) { LOG(ERROR, "Invalid tool selected"); return; } int region_size = _terrain->get_region_size(); Vector2i region_vsize = V2I(region_size); // If no region and can't add one, skip whole function. Checked again later Terrain3DData *data = _terrain->get_data(); if (!data->has_regionp(p_global_position) && (!_brush_data["auto_regions"] || (_tool != SCULPT && _tool != HEIGHT))) { return; } bool modifier_alt = _brush_data["modifier_alt"]; bool modifier_ctrl = _brush_data["modifier_ctrl"]; //bool modifier_shift = _brush_data["modifier_shift"]; Image *brush_image = cast_to(_brush_data["brush_image"]); if (!brush_image) { LOG(ERROR, "Invalid brush image. Returning"); return; } Vector2i img_size = _brush_data["brush_image_size"]; real_t brush_size = CLAMP(real_t(_brush_data.get("size", 10.f)), 2.f, 4096.f); // Meters // Typicall we multiply mouse pressure & strength setting, but // * Mouse movement w/ button down has a pressure of 1 // * Mouse clicks always have pressure of 0 // * Pen movement pressure varies, sometimes lifting or clicking has a pressure of 0 // If we're operating with a pressure of 0.001-.999 it's a pen // So if there's a 0 pressure operation >100ms after a pen operation, we assume it's // a mouse click. This occasionally catches a pen click, but avoids most pen lifts. real_t mouse_pressure = CLAMP(real_t(_brush_data.get("mouse_pressure", 0.f)), 0.f, 1.f); if (mouse_pressure > CMP_EPSILON && mouse_pressure < 1.f) { _last_pen_tick = Time::get_singleton()->get_ticks_msec(); } uint64_t ticks = Time::get_singleton()->get_ticks_msec(); if (mouse_pressure < CMP_EPSILON && ticks - _last_pen_tick >= 100) { mouse_pressure = 1.f; } real_t strength = mouse_pressure * (real_t)_brush_data["strength"]; real_t height = _brush_data["height"]; Color color = _brush_data["color"]; real_t roughness = _brush_data["roughness"]; bool enable_texture = _brush_data["enable_texture"]; bool texture_filter = _brush_data["texture_filter"]; int margin = _brush_data["margin"]; int asset_id = _brush_data["asset_id"]; Vector2 slope_range = _brush_data["slope"]; bool enable_angle = _brush_data["enable_angle"]; bool dynamic_angle = _brush_data["dynamic_angle"]; real_t angle = _brush_data["angle"]; bool enable_scale = _brush_data["enable_scale"]; real_t scale = _brush_data["scale"]; real_t gamma = _brush_data["gamma"]; PackedVector3Array gradient_points = _brush_data["gradient_points"]; real_t randf = UtilityFunctions::randf(); real_t rot = randf * Math_PI * real_t(_brush_data["brush_spin_speed"]); if (_brush_data["align_to_view"]) { rot += p_camera_direction; } // Rotate the decal to align with the brush if (_terrain->get_plugin()) { Node *node = cast_to(_terrain->get_plugin()->get("ui")); if (node->has_method("set_decal_rotation")) { node->call("set_decal_rotation", rot); } } AABB edited_area; edited_area.position = p_global_position - Vector3(brush_size, 0.f, brush_size) * .5f; edited_area.size = Vector3(brush_size, 0.f, brush_size); if (_tool == INSTANCER) { if (modifier_ctrl) { _terrain->get_instancer()->remove_instances(p_global_position, _brush_data); } else { _terrain->get_instancer()->add_instances(p_global_position, _brush_data); } return; } // MAP Operations real_t vertex_spacing = _terrain->get_vertex_spacing(); // save region count before brush pixel loop. Any regions added will have caused an Array // rebuild at the end of the last _operate() call, but until painting is finished we only // need to track if _added_removed_locations has changed between now and the end of the loop int regions_added_removed = _added_removed_locations.size(); for (real_t x = 0.f; x < brush_size; x += vertex_spacing) { for (real_t y = 0.f; y < brush_size; y += vertex_spacing) { Vector2 brush_offset = Vector2(x, y) - (V2(brush_size) * .5f); Vector3 brush_global_position = Vector3(p_global_position.x + brush_offset.x + .5f, p_global_position.y, p_global_position.z + brush_offset.y + .5f); // Get region for current brush pixel global position Vector2i region_loc = data->get_region_location(brush_global_position); Ref region = _operate_region(region_loc); // If no region and can't make one, skip if (region.is_null()) { continue; } // Get map for this region and tool Image *map = region->get_map_ptr(map_type); if (!map) { continue; } // Identify position on map image Vector2 uv_position = _get_uv_position(brush_global_position, region_size, vertex_spacing); Vector2i map_pixel_position = Vector2i(uv_position * region_size); if (!_is_in_bounds(map_pixel_position, region_vsize)) { continue; } Vector2 brush_uv = Vector2(x, y) / brush_size; Vector2i brush_pixel_position = Vector2i(_get_rotated_uv(brush_uv, rot) * img_size); if (!_is_in_bounds(brush_pixel_position, img_size)) { continue; } Vector3 edited_position = brush_global_position; edited_position.y = data->get_height(edited_position); edited_area = edited_area.expand(edited_position); // Start brushing on the map real_t brush_alpha = brush_image->get_pixelv(brush_pixel_position).r; brush_alpha = real_t(Math::pow(double(brush_alpha), double(gamma))); brush_alpha = std::isnan(brush_alpha) ? 0.f : brush_alpha; Color src = map->get_pixelv(map_pixel_position); Color dest = src; if (map_type == TYPE_HEIGHT) { real_t srcf = src.r; // In case data in existing map has nan or inf saved, check, and reset to real number if required. srcf = std::isnan(srcf) ? 0.f : srcf; real_t destf = srcf; switch (_operation) { case ADD: { if (_tool == HEIGHT) { // Height destf = Math::lerp(srcf, height, CLAMP(brush_alpha * strength, 0.f, 1.f)); } else if (modifier_alt && !std::isnan(p_global_position.y)) { // Lift troughs real_t brush_center_y = p_global_position.y + brush_alpha * strength; destf = Math::clamp(brush_center_y, srcf, srcf + brush_alpha * strength); } else { // Raise destf = srcf + (brush_alpha * strength); } break; } case SUBTRACT: { if (_tool == HEIGHT) { // Height, but GDScript has already picked height at cursor destf = Math::lerp(srcf, height, CLAMP(brush_alpha * strength, 0.f, 1.f)); } else if (modifier_alt && !std::isnan(p_global_position.y)) { // Flatten peaks real_t brush_center_y = p_global_position.y - brush_alpha * strength; destf = Math::clamp(brush_center_y, srcf - brush_alpha * strength, srcf); } else { // Lower destf = srcf - (brush_alpha * strength); } break; } case AVERAGE: { real_t avg_default = _terrain->get_material()->get_world_background() == 0u ? srcf : 0.f; real_t avg = _average(AVG_HEIGHT, brush_global_position, srcf, avg_default); destf = Math::lerp(srcf, avg, CLAMP(brush_alpha * strength * 2.f, .02f, 1.f)); break; } case GRADIENT: { if (gradient_points.size() == 2) { Vector3 point_1 = gradient_points[0]; Vector3 point_2 = gradient_points[1]; Vector2 point_1_xz = Vector2(point_1.x, point_1.z); Vector2 point_2_xz = Vector2(point_2.x, point_2.z); Vector2 dir = point_2_xz - point_1_xz; if (dir.length_squared() < 0.01f) { return; } Vector2 brush_xz = Vector2(brush_global_position.x, brush_global_position.z); if (_operation_movement.length_squared() > 0.f) { // Ramp up/down only in the direction of movement, to avoid giving winding // paths one edge higher than the other. Vector2 movement_xz = Vector2(_operation_movement.x, _operation_movement.z).normalized(); Vector2 offset = movement_xz * Vector2(brush_offset).dot(movement_xz); brush_xz = Vector2(p_global_position.x + offset.x, p_global_position.z + offset.y); } real_t weight = dir.normalized().dot(brush_xz - point_1_xz) / dir.length(); weight = Math::clamp(weight, (real_t)0.0f, (real_t)1.0f); real_t height = Math::lerp(point_1.y, point_2.y, weight); destf = Math::lerp(srcf, height, CLAMP(brush_alpha * strength, 0.f, 1.f)); } break; } default: break; } dest = Color(destf, 0.f, 0.f, 1.f); region->update_height(destf); data->update_master_height(destf); edited_position.y = destf; edited_area = edited_area.expand(edited_position); } else if (map_type == TYPE_CONTROL) { // Get current bit field from pixel uint32_t base_id = get_base(src.r); uint32_t overlay_id = get_overlay(src.r); real_t blend = real_t(get_blend(src.r)) / 255.f; uint32_t uvrotation = get_uv_rotation(src.r); uint32_t uvscale = get_uv_scale(src.r); bool hole = is_hole(src.r); bool navigation = is_nav(src.r); bool autoshader = is_auto(src.r); // Lookup to shift values saved to control map so that 0 (default) is the first entry // Shader scale array is aligned to match this. std::array scale_align = { 5, 6, 7, 0, 1, 2, 3, 4 }; switch (_tool) { case TEXTURE: { if (!data->is_in_slope(brush_global_position, slope_range)) { continue; } switch (_operation) { // Base Paint case REPLACE: { if (brush_alpha > 0.5f) { if (enable_texture) { // Set base & overlay texture base_id = asset_id; overlay_id = asset_id; // Erase blend value blend = 0.f; autoshader = false; } // Set angle & scale if (base_id == asset_id && enable_angle && !autoshader) { if (dynamic_angle) { // Angle from mouse movement. angle = Vector2(-_operation_movement.x, _operation_movement.z).angle(); // Avoid negative, align texture "up" with mouse direction. angle = real_t(Math::fmod(Math::rad_to_deg(angle) + 450.f, real_t(360.f))); } // Convert from degrees to 0 - 15 value range uvrotation = uint32_t(CLAMP(Math::round(angle / 22.5f), 0.f, 15.f)); } if (base_id == asset_id && enable_scale && !autoshader) { // Offset negative and convert from percentage to 0 - 7 bit value range // Maintain 0 = 0, remap negatives to end. uvscale = scale_align[uint8_t(CLAMP(Math::round((scale + 60.f) / 20.f), 0.f, 7.f))]; } } break; } // Add asset id, and increase weighting case ADD: { real_t spray_strength = CLAMP(strength * 0.05f, 0.004f, .25f); real_t brush_value = CLAMP(brush_alpha * spray_strength, 0.f, 1.f); if (enable_texture && brush_alpha * strength * 11.f > 0.1f) { // Pick lowest weighted id, and lower to zero before setting new asset id. if (asset_id != base_id && asset_id != overlay_id) { if (modifier_alt) { if (blend < 0.5f) { overlay_id = asset_id; } else { base_id = asset_id; } } else { if (blend >= 0.5f) { blend = CLAMP(blend + brush_value, 0.f, 1.f); } else { blend = CLAMP(blend - brush_value, 0.f, 1.f); } if (blend <= 1.0f / 254.f) { overlay_id = asset_id; } else if (blend >= (1.f - 1.0f / 254.f)) { base_id = asset_id; } } } if (base_id == asset_id) { blend = CLAMP(blend - brush_value, 0.f, 1.f); if (brush_alpha > 0.5f && blend < 0.5f) { autoshader = false; } } if (overlay_id == asset_id) { blend = CLAMP(blend + brush_value, 0.f, 1.f); if (brush_alpha > 0.5f && blend >= 0.5f) { autoshader = false; } } } if ((base_id == asset_id && blend < 0.5f) || (overlay_id == asset_id && blend >= 0.5f)) { // Set angle & scale if (enable_angle && !autoshader && brush_alpha > 0.5f) { if (dynamic_angle) { // Angle from mouse movement. angle = Vector2(-_operation_movement.x, _operation_movement.z).angle(); // Avoid negative, align texture "up" with mouse direction. angle = real_t(Math::fmod(Math::rad_to_deg(angle) + 450.f, real_t(360.f))); } // Convert from degrees to 0 - 15 value range uvrotation = uint32_t(CLAMP(Math::round(angle / 22.5f), 0.f, 15.f)); } if (enable_scale && !autoshader && brush_alpha > 0.5f) { // Offset negative and convert from percentage to 0 - 7 bit value range // Maintain 0 = 0, remap negatives to end. uvscale = scale_align[uint8_t(CLAMP(Math::round((scale + 60.f) / 20.f), 0.f, 7.f))]; } } break; } // Lower weight of current asset id case SUBTRACT: { real_t spray_strength = CLAMP(strength * 0.05f, 0.004f, .25f); real_t brush_value = CLAMP(brush_alpha * spray_strength, 0.f, 1.f); if (base_id == asset_id) { blend = CLAMP(blend + brush_value, 0.f, 1.f); } if (overlay_id == asset_id) { blend = CLAMP(blend - brush_value, 0.f, 1.f); } break; } case AVERAGE: { real_t avg = _average(AVG_BLEND, brush_global_position, src.r, 0.f, modifier_alt) / 255.f; blend = Math::lerp(blend, avg, CLAMP(brush_alpha * strength * 2.f, .02f, 1.f)); break; } default: { break; } } break; } case AUTOSHADER: { if (brush_alpha > 0.5f) { autoshader = (_operation == ADD); uvscale = 0.f; uvrotation = 0.f; } break; } case HOLES: { if (brush_alpha > 0.5f) { hole = (_operation == ADD); } break; } case NAVIGATION: { if (brush_alpha > 0.5f) { navigation = (_operation == ADD); } break; } default: { break; } } // Convert back to bitfield uint32_t blend_int = uint32_t(CLAMP(Math::round(blend * 255.f), 0.f, 255.f)); uint32_t bits = enc_base(base_id) | enc_overlay(overlay_id) | enc_blend(blend_int) | enc_uv_rotation(uvrotation) | enc_uv_scale(uvscale) | enc_hole(hole) | enc_nav(navigation) | enc_auto(autoshader); // Write back to pixel in FORMAT_RF. Must be a 32-bit float dest = Color(as_float(bits), 0.f, 0.f, 1.f); } else if (map_type == TYPE_COLOR) { // Filter by visible texture if (texture_filter) { Image *cmap = region->get_map_ptr(TYPE_CONTROL); if (!cmap) { continue; } float src_ctrl = cmap->get_pixelv(map_pixel_position).r; // Must be float int tex_id = (get_blend(src_ctrl) > 110 - margin) ? get_overlay(src_ctrl) : get_base(src_ctrl); if (tex_id != asset_id) { continue; } } if (!data->is_in_slope(brush_global_position, slope_range)) { continue; } switch (_tool) { case COLOR: switch (_operation) { case ADD: { dest = src.lerp(color, CLAMP(brush_alpha * strength, 0.f, 1.f)); dest.a = src.a; break; } case SUBTRACT: { dest = src.lerp(COLOR_WHITE, CLAMP(brush_alpha * strength, 0.f, 1.f)); dest.a = src.a; break; } case AVERAGE: { Color avg_col = _average(brush_global_position, src); dest = src.lerp(avg_col, CLAMP(brush_alpha * strength * 2.f, .02f, 1.f)); dest.a = src.a; break; } default: break; } break; case ROUGHNESS: /* Roughness received from UI is -100 to 100. Changed to 0,1 before storing. * To convert 0,1 back to -100,100 use: 200 * (color.a - 0.5) * However Godot stores values as 8-bit ints. Roundtrip is = int(a*255)/255.0 * Roughness 0 is saved as 0.5, but retreived is 0.498, or -0.4 roughness * We round the final amount in tool_settings.gd:_on_picked(). */ switch (_operation) { case ADD: { real_t target = .5f + .5f * roughness; dest.a = Math::lerp(real_t(src.a), target, CLAMP(brush_alpha * strength, 0.f, 1.f)); dest.a = float(int(dest.a * 255.f)) / 255.f; // Quantize explicitly so picked values match painted values break; } case SUBTRACT: { dest.a = Math::lerp(real_t(src.a), real_t(.5f), CLAMP(brush_alpha * strength, 0.f, 1.f)); dest.a = float(int(dest.a * 255.f)) / 255.f; break; } case AVERAGE: { real_t avg = _average(AVG_ROUGHNESS, brush_global_position, src.a, 0.5f); dest.a = Math::lerp(real_t(dest.a), avg, CLAMP(brush_alpha * strength * 2.f, .0f, 1.f)); dest.a = float(int(dest.a * 255.f)) / 255.f; break; } default: break; } break; default: break; } } backup_region(region); map->set_pixelv(map_pixel_position, dest); } } // Regenerate color mipmaps for edited regions if (map_type == TYPE_COLOR) { for (Ref region : _edited_regions) { if (region.is_valid()) { region->get_map(map_type)->generate_mipmaps(); } } } // If no added or removed regions, update only changed texture array layers from the edited regions in the rendering server if (_added_removed_locations.size() == regions_added_removed) { data->update_maps(map_type, false, false); } else { // If region qty was changed, must fully rebuild the maps data->update_maps(map_type, true, map_type == TYPE_COLOR); } data->add_edited_area(edited_area); if (_tool == HOLES || _tool == HEIGHT || _tool == SCULPT) { _terrain->get_instancer()->update_transforms(edited_area); } // Update Dynamic / Editor collision if (_terrain->get_collision_mode() == Terrain3DCollision::DYNAMIC_EDITOR) { _terrain->get_collision()->update(V2I_MAX, true); } if (_tool == HEIGHT || _tool == SCULPT || _tool == TEXTURE || _tool == AUTOSHADER) { _terrain->snap(); } } void Terrain3DEditor::_store_undo() { IS_INIT_COND_MESG(!_terrain->get_plugin(), "_terrain isn't initialized, returning", VOID); if (_tool < 0 || _tool >= TOOL_MAX) { return; } LOG(DEBUG, "Finalize undo & redo snapshots"); Dictionary redo_data; // Store current locations; Original backed up in start_operation() redo_data["region_locations"] = _terrain->get_data()->get_region_locations().duplicate(); // Store original and current backups of edited regions _undo_data["edited_regions"] = _original_regions; redo_data["edited_regions"] = _edited_regions; if (Terrain3D::debug_level >= DEBUG) { LOG(DEBUG, "Storing Original Regions:"); for (const Ref ®ion : _original_regions) { if (region.is_valid()) { region->dump(); } } LOG(DEBUG, "Storing Edited Regions:"); for (const Ref ®ion : _edited_regions) { if (region.is_valid()) { region->dump(); } } } // Store regions that were removed or added if (_added_removed_locations.size() > 0) { if (_tool == REGION && _operation == SUBTRACT) { _undo_data["removed_regions"] = _added_removed_locations; redo_data["added_regions"] = _added_removed_locations; LOG(DEBUG, "Removed regions: ", _added_removed_locations); } else { _undo_data["added_regions"] = _added_removed_locations; redo_data["removed_regions"] = _added_removed_locations; LOG(DEBUG, "Added regions: ", _added_removed_locations); } } if (_terrain->get_data()->get_edited_area().has_volume()) { _undo_data["edited_area"] = _terrain->get_data()->get_edited_area(); redo_data["edited_area"] = _terrain->get_data()->get_edited_area(); LOG(DEBUG, "Adding edited area to snapshots: ", _undo_data["edited_area"]); } // Request the plugin store the undo/redo data. if (_terrain->get_plugin()->has_method("create_undo_action")) { LOG(INFO, "Storing undo snapshot"); String action_name = String("Terrain3D ") + OPNAME[_operation] + String(" ") + TOOLNAME[_tool]; LOG(DEBUG, "Creating undo action: '", action_name, "'"); _terrain->get_plugin()->call("create_undo_action", action_name); LOG(DEBUG, "Storing undo snapshot: "); Util::print_dict("_undo_data snapshot", _undo_data, DEBUG); _terrain->get_plugin()->call("add_undo_method", Callable(this, "apply_undo").bind(_undo_data.duplicate())); LOG(DEBUG, "Storing redo snapshot: "); Util::print_dict("redo_data snapshot", redo_data, DEBUG); _terrain->get_plugin()->call("add_do_method", Callable(this, "apply_undo").bind(redo_data)); LOG(DEBUG, "Committing undo action"); _terrain->get_plugin()->call("commit_action", false); } } void Terrain3DEditor::_apply_undo(const Dictionary &p_data) { IS_INIT_COND_MESG(!_terrain->get_plugin(), "_terrain isn't initialized, returning", VOID); LOG(INFO, "Applying Undo/Redo data"); Terrain3DData *data = _terrain->get_data(); if (p_data.has("edited_regions")) { Util::print_arr("Edited regions", p_data["edited_regions"]); TypedArray undo_regions = p_data["edited_regions"]; LOG(DEBUG, "Backup has ", undo_regions.size(), " edited regions"); for (Ref region : undo_regions) { if (region.is_null()) { LOG(ERROR, "Null region saved in undo data. Please report this error."); continue; } region->sanitize_maps(); // Live data may not have some maps so must be sanitized Dictionary regions = data->get_regions_all(); regions[region->get_location()] = region; region->set_modified(true); // Tell update_maps() this region has layers that can be individually updated region->set_edited(true); // Flag so update_maps() will include it region->set_deleted(false); // Ensure region not marked for deletion if (Terrain3D::debug_level >= DEBUG) { LOG(DEBUG, "Restoring region:"); region->dump(); } } } if (p_data.has("edited_area")) { LOG(DEBUG, "Edited area: ", p_data["edited_area"]); data->add_edited_area(p_data["edited_area"]); } if (p_data.has("added_regions")) { LOG(DEBUG, "Added regions: ", p_data["added_regions"]); TypedArray region_locs = p_data["added_regions"]; for (const Vector2i region_loc : region_locs) { Ref region = data->get_region(region_loc); if (region.is_valid()) { LOG(DEBUG, "Marking region: ", region_loc, " +deleted, +modified, ", ptr_to_str(*region)); region->set_deleted(true); region->set_modified(true); } } } if (p_data.has("removed_regions")) { LOG(DEBUG, "Removed regions: ", p_data["removed_regions"]); TypedArray region_locs = p_data["removed_regions"]; for (const Vector2i region_loc : region_locs) { Ref region = data->get_region(region_loc); if (region.is_valid()) { LOG(DEBUG, "Marking region: ", region_loc, " -deleted, +modified, ", ptr_to_str(*region)); region->set_deleted(false); region->set_modified(true); _send_region_aabb(region_loc, region->get_height_range()); } } } // After all regions are in place, reset the region map, which also calls update_maps if (p_data.has("region_locations")) { // Load w/ duplicate or it gets a bit wonky undoing removed regions w/ saves TypedArray locations = p_data["region_locations"]; _terrain->get_data()->set_region_locations(locations.duplicate()); LOG(DEBUG, "Locations(", locations.size(), "): ", locations); } // If this undo set modifies the region qty, we must rebuild the arrays. Otherwise we can update individual layers if (p_data.has("added_regions") || p_data.has("removed_regions")) { data->update_maps(TYPE_MAX, true, false); } else { data->update_maps(TYPE_MAX, false, false); } // After TextureArray updates clear edited regions flag. if (p_data.has("edited_regions")) { TypedArray undo_regions = p_data["edited_regions"]; for (Ref region : undo_regions) { if (region.is_valid()) { region->set_edited(false); } } } _terrain->get_instancer()->update_mmis(-1, V2I_MAX, true); } // Returns average of height, blend (as real_t(0-255)), or roughness. Overloaded version handles average color real_t Terrain3DEditor::_average(const AverageMode p_mode, const Vector3 &p_global_position, const real_t p_base, const real_t p_nan_val, bool p_alt) const { IS_DATA_INIT(NAN); Terrain3DData *data = _terrain->get_data(); real_t vertex_spacing = _terrain->get_vertex_spacing(); Vector3 left_position = p_global_position - Vector3(vertex_spacing, 0.f, 0.f); Vector3 right_position = p_global_position + Vector3(vertex_spacing, 0.f, 0.f); Vector3 down_position = p_global_position - Vector3(0.f, 0.f, vertex_spacing); Vector3 up_position = p_global_position + Vector3(0.f, 0.f, vertex_spacing); MapType map_type; int index; switch (p_mode) { case AVG_HEIGHT: map_type = TYPE_HEIGHT; index = 0; // Red break; case AVG_BLEND: map_type = TYPE_CONTROL; index = 0; // Red break; case AVG_ROUGHNESS: map_type = TYPE_COLOR; index = 3; // Alpha break; default: break; } Color pixel; real_t left, right, up, down; pixel = data->get_pixel(map_type, left_position); left = std::isnan(pixel.r) ? p_nan_val : pixel[index]; pixel = data->get_pixel(map_type, right_position); right = std::isnan(pixel.r) ? p_nan_val : pixel[index]; pixel = data->get_pixel(map_type, up_position); up = std::isnan(pixel.r) ? p_nan_val : pixel[index]; pixel = data->get_pixel(map_type, down_position); down = std::isnan(pixel.r) ? p_nan_val : pixel[index]; if (p_mode == AVG_BLEND) { if (p_alt) { return real_t(get_blend(p_base) + get_blend(left) + get_blend(right) + get_blend(up) + get_blend(down)) * 0.2f; } else { return Math::lerp(get_blend(p_base), 128.f, .1f); } } else { return (p_base + left + right + up + down) * 0.2f; } } Color Terrain3DEditor::_average(const Vector3 &p_global_position, const Color &p_base) const { IS_DATA_INIT(COLOR_NAN); Terrain3DData *data = _terrain->get_data(); real_t vertex_spacing = _terrain->get_vertex_spacing(); Vector3 left_position = p_global_position - Vector3(vertex_spacing, 0.f, 0.f); Vector3 right_position = p_global_position + Vector3(vertex_spacing, 0.f, 0.f); Vector3 down_position = p_global_position - Vector3(0.f, 0.f, vertex_spacing); Vector3 up_position = p_global_position + Vector3(0.f, 0.f, vertex_spacing); Color left = data->get_pixel(TYPE_COLOR, left_position).srgb_to_linear(); if (std::isnan(left.r)) { left = COLOR_WHITE; } Color right = data->get_pixel(TYPE_COLOR, right_position).srgb_to_linear(); if (std::isnan(right.r)) { right = COLOR_WHITE; } Color up = data->get_pixel(TYPE_COLOR, up_position).srgb_to_linear(); if (std::isnan(up.r)) { up = COLOR_WHITE; } Color down = data->get_pixel(TYPE_COLOR, down_position).srgb_to_linear(); if (std::isnan(down.r)) { down = COLOR_WHITE; } Color base = p_base.srgb_to_linear(); return Color( (base.r + left.r + right.r + up.r + down.r) * 0.2f, (base.g + left.g + right.g + up.g + down.g) * 0.2f, (base.b + left.b + right.b + up.b + down.b) * 0.2f, 1.f) .linear_to_srgb(); } /////////////////////////// // Public Functions /////////////////////////// // Santize and set incoming brush data w/ defaults and clamps // Only santizes data needed for the editor, other parameters (eg instancer) untouched here void Terrain3DEditor::set_brush_data(const Dictionary &p_data) { _brush_data = p_data; // Same instance. Anything could be inserted after this, eg mouse_pressure // Sanitize image and textures Array brush_images = p_data["brush"]; bool error = false; if (brush_images.size() == 2) { Ref img = brush_images[0]; if (img.is_valid() && !img->is_empty()) { _brush_data["brush_image"] = img; _brush_data["brush_image_size"] = img->get_size(); } else { LOG(ERROR, "Brush data doesn't contain a valid image"); } Ref tex = brush_images[1]; if (tex.is_valid() && tex->get_width() > 0 && tex->get_height() > 0) { _brush_data["brush_texture"] = tex; } else { LOG(ERROR, "Brush data doesn't contain a valid texture"); } } else { LOG(ERROR, "Brush data doesn't contain an image and texture"); } // Santize settings // size is redundantly clamped differently in _operate_map and instancer::add_transforms _brush_data["size"] = CLAMP(real_t(p_data.get("size", 10.f)), 0.1f, 4096.f); // Diameter in meters _brush_data["strength"] = CLAMP(real_t(p_data.get("strength", .1f)) * .01f, .01f, 1000.f); // 1-100k% (max of 1000m per click) // mouse_pressure injected in editor.gd and sanitized in _operate_map() Vector2 slope = p_data.get("slope", Vector2(0.f, 90.f)); slope.x = CLAMP(slope.x, 0.f, 90.f); slope.y = CLAMP(slope.y, 0.f, 90.f); _brush_data["slope"] = slope; // 0-90 (degrees) _brush_data["height"] = CLAMP(real_t(p_data.get("height", 0.f)), -65536.f, 65536.f); // Meters Color col = p_data.get("color", COLOR_ROUGHNESS); col.r = CLAMP(col.r, 0.f, 5.f); col.g = CLAMP(col.g, 0.f, 5.f); col.b = CLAMP(col.b, 0.f, 5.f); col.a = CLAMP(col.a, 0.f, 1.f); _brush_data["color"] = col; _brush_data["roughness"] = CLAMP(real_t(p_data.get("roughness", 0.f)), -100.f, 100.f) * .01f; // Percentage _brush_data["enable_texture"] = p_data.get("enable_texture", true); _brush_data["texture_filter"] = p_data.get("texture_filter", false); _brush_data["asset_id"] = CLAMP(int(p_data.get("asset_id", 0)), 0, ((_tool == INSTANCER) ? Terrain3DAssets::MAX_MESHES : Terrain3DAssets::MAX_TEXTURES) - 1); _brush_data["margin"] = CLAMP(int(p_data.get("margin", 0)), -100, 100); _brush_data["enable_angle"] = p_data.get("enable_angle", true); _brush_data["dynamic_angle"] = p_data.get("dynamic_angle", false); _brush_data["angle"] = CLAMP(real_t(p_data.get("angle", 0.f)), 0.f, 337.5f); _brush_data["enable_scale"] = p_data.get("enable_scale", true); _brush_data["scale"] = CLAMP(real_t(p_data.get("scale", 0.f)), -60.f, 80.f); _brush_data["auto_regions"] = bool(p_data.get("auto_regions", true)); _brush_data["align_to_view"] = bool(p_data.get("align_to_view", true)); _brush_data["gamma"] = CLAMP(real_t(p_data.get("gamma", 1.f)), 0.1f, 2.f); _brush_data["brush_spin_speed"] = CLAMP(real_t(p_data.get("brush_spin_speed", 0.f)), 0.f, 1.f); _brush_data["gradient_points"] = p_data.get("gradient_points", PackedVector3Array()); Util::print_dict("set_brush_data() Santized brush data:", _brush_data, EXTREME); } void Terrain3DEditor::set_tool(const Tool p_tool) { Tool old_tool = _tool; SET_IF_DIFF(_tool, CLAMP(p_tool, Tool(0), TOOL_MAX)); if (_terrain && (_tool == Tool::NAVIGATION || old_tool == Tool::NAVIGATION || _tool == Tool::REGION || old_tool == Tool::REGION)) { _terrain->get_material()->update(Terrain3DMaterial::FULL_REBUILD); } } void Terrain3DEditor::set_operation(const Operation p_operation) { SET_IF_DIFF(_operation, CLAMP(p_operation, Operation(0), OP_MAX)); } // Called on mouse click void Terrain3DEditor::start_operation(const Vector3 &p_global_position) { IS_DATA_INIT_MESG("Terrain isn't initialized", VOID); LOG(INFO, "Setting up undo snapshot"); _undo_data.clear(); _undo_data["region_locations"] = _terrain->get_data()->get_region_locations().duplicate(); _is_operating = true; _original_regions = TypedArray(); // New pointers instead of clear _edited_regions = TypedArray(); _added_removed_locations = TypedArray(); // Reset counter at start to ensure first click places an instance _terrain->get_instancer()->reset_density_counter(); _terrain->get_data()->clear_edited_area(); _operation_position = p_global_position; _operation_movement = V3_ZERO; } // Called on mouse movement with left mouse button down void Terrain3DEditor::operate(const Vector3 &p_global_position, const real_t p_camera_direction) { IS_DATA_INIT_MESG("Terrain isn't initialized", VOID); if (!_is_operating) { LOG(ERROR, "Run start_operation() before operating"); return; } _operation_movement = p_global_position - _operation_position; _operation_position = p_global_position; // Convolve the last 8 movement events, we dont clear on mouse release // so as to make repeated mouse strokes in the same direction consistent _operation_movement_history.push_back(_operation_movement); if (_operation_movement_history.size() > 8) { _operation_movement_history.pop_front(); } // size -1, dont add the last appended entry for (int i = 0; i < _operation_movement_history.size() - 1; i++) { _operation_movement += _operation_movement_history[i]; } _operation_movement *= 0.125f; // 1/8th if (_tool == REGION) { _operate_region(_terrain->get_data()->get_region_location(p_global_position)); } else if (_tool >= 0 && _tool < TOOL_MAX) { _operate_map(p_global_position, p_camera_direction); } } void Terrain3DEditor::backup_region(const Ref &p_region) { // Backup region once at the start of an operation. Once Edited is set, this is skipped if (_is_operating && p_region.is_valid() && !p_region->is_edited()) { LOG(DEBUG, "Storing original copy of region: ", p_region->get_location()); Ref orig_region = p_region->duplicate(true); _original_regions.push_back(orig_region); _edited_regions.push_back(p_region); p_region->set_edited(true); p_region->set_modified(true); if (Terrain3D::debug_level >= DEBUG) { LOG(DEBUG, "Backup original region"); orig_region->dump(); LOG(DEBUG, "Backup edited region"); p_region->dump(); } } } // Called on left mouse button released void Terrain3DEditor::stop_operation() { IS_DATA_INIT_MESG("Terrain isn't initialized", VOID); // If undo was created and terrain actually modified, store it LOG(DEBUG, "Backed up regions: ", _original_regions.size(), ", Edited regions: ", _edited_regions.size(), ", Added/Removed regions: ", _added_removed_locations.size()); if (_is_operating && (!_added_removed_locations.is_empty() || !_edited_regions.is_empty())) { for (int i = 0; i < _edited_regions.size(); i++) { Ref region = _edited_regions[i]; region->set_edited(false); // Make duplicate for redo, necessary or redos won't work Ref redo_region = region->duplicate(true); _edited_regions[i] = redo_region; if (Terrain3D::debug_level >= DEBUG) { LOG(DEBUG, "Edited region:"); region->dump(); LOG(DEBUG, "Redo region:"); redo_region->dump(); } } _store_undo(); } _undo_data.clear(); _original_regions = TypedArray(); //New pointers instead of clear _edited_regions = TypedArray(); _added_removed_locations = TypedArray(); _terrain->get_data()->clear_edited_area(); _is_operating = false; } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DEditor::_bind_methods() { BIND_ENUM_CONSTANT(ADD); BIND_ENUM_CONSTANT(SUBTRACT); BIND_ENUM_CONSTANT(REPLACE); BIND_ENUM_CONSTANT(AVERAGE); BIND_ENUM_CONSTANT(GRADIENT); BIND_ENUM_CONSTANT(OP_MAX); BIND_ENUM_CONSTANT(SCULPT); BIND_ENUM_CONSTANT(HEIGHT); BIND_ENUM_CONSTANT(TEXTURE); BIND_ENUM_CONSTANT(COLOR); BIND_ENUM_CONSTANT(ROUGHNESS); BIND_ENUM_CONSTANT(ANGLE); BIND_ENUM_CONSTANT(SCALE); BIND_ENUM_CONSTANT(AUTOSHADER); BIND_ENUM_CONSTANT(HOLES); BIND_ENUM_CONSTANT(NAVIGATION); BIND_ENUM_CONSTANT(INSTANCER); BIND_ENUM_CONSTANT(REGION); BIND_ENUM_CONSTANT(TOOL_MAX); ClassDB::bind_method(D_METHOD("set_terrain", "terrain"), &Terrain3DEditor::set_terrain); ClassDB::bind_method(D_METHOD("get_terrain"), &Terrain3DEditor::get_terrain); ClassDB::bind_method(D_METHOD("set_brush_data", "data"), &Terrain3DEditor::set_brush_data); ClassDB::bind_method(D_METHOD("set_tool", "tool"), &Terrain3DEditor::set_tool); ClassDB::bind_method(D_METHOD("get_tool"), &Terrain3DEditor::get_tool); ClassDB::bind_method(D_METHOD("set_operation", "operation"), &Terrain3DEditor::set_operation); ClassDB::bind_method(D_METHOD("get_operation"), &Terrain3DEditor::get_operation); ClassDB::bind_method(D_METHOD("start_operation", "position"), &Terrain3DEditor::start_operation); ClassDB::bind_method(D_METHOD("is_operating"), &Terrain3DEditor::is_operating); ClassDB::bind_method(D_METHOD("operate", "position", "camera_direction"), &Terrain3DEditor::operate); ClassDB::bind_method(D_METHOD("backup_region", "region"), &Terrain3DEditor::backup_region); ClassDB::bind_method(D_METHOD("stop_operation"), &Terrain3DEditor::stop_operation); ClassDB::bind_method(D_METHOD("apply_undo", "data"), &Terrain3DEditor::_apply_undo); } ================================================ FILE: src/terrain_3d_editor.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_EDITOR_CLASS_H #define TERRAIN3D_EDITOR_CLASS_H #include #include #include "terrain_3d.h" #include "terrain_3d_region.h" class Terrain3DEditor : public Object { GDCLASS(Terrain3DEditor, Object); CLASS_NAME(); public: // Constants enum Tool { REGION, SCULPT, HEIGHT, TEXTURE, COLOR, ROUGHNESS, AUTOSHADER, HOLES, NAVIGATION, INSTANCER, ANGLE, // used for picking, TODO change to a picking tool SCALE, // used for picking TOOL_MAX, }; static inline const char *TOOLNAME[] = { "Region", "Sculpt", "Height", "Texture", "Color", "Roughness", "Auto Shader", "Holes", "Navigation", "Instancer", "Angle", "Scale", "TOOL_MAX", }; enum Operation { ADD, SUBTRACT, REPLACE, AVERAGE, GRADIENT, OP_MAX, }; static inline const char *OPNAME[] = { "Add", "Subtract", "Replace", "Average", "Gradient", "OP_MAX", }; enum AverageMode { AVG_HEIGHT, AVG_BLEND, AVG_ROUGHNESS, }; private: Terrain3D *_terrain = nullptr; // Painter settings & variables Tool _tool = REGION; Operation _operation = ADD; Dictionary _brush_data; Vector3 _operation_position = V3_ZERO; Vector3 _operation_movement = V3_ZERO; Array _operation_movement_history; bool _is_operating = false; uint64_t _last_region_bounds_error = 0; TypedArray _original_regions; // Queue for undo TypedArray _edited_regions; // Queue for redo TypedArray _added_removed_locations; // Queue for added/removed locations AABB _modified_area; Dictionary _undo_data; // See _get_undo_data for definition uint64_t _last_pen_tick = 0; void _send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range = V2_ZERO); Ref _operate_region(const Vector2i &p_region_loc); void _operate_map(const Vector3 &p_global_position, const real_t p_camera_direction); MapType _get_map_type() const; bool _is_in_bounds(const Point2i &p_pixel, const Point2i &p_size) const; Vector2 _get_uv_position(const Vector3 &p_global_position, const int p_region_size, const real_t p_vertex_spacing) const; Vector2 _get_rotated_uv(const Vector2 &p_uv, const real_t p_angle) const; void _store_undo(); void _apply_undo(const Dictionary &p_data); real_t _average(const AverageMode p_mode, const Vector3 &p_global_position, const real_t p_base, const real_t p_nan_val = 0.f, bool p_alt = false) const; Color _average(const Vector3 &p_global_position, const Color &p_base) const; public: Terrain3DEditor() {} ~Terrain3DEditor() {} void set_terrain(Terrain3D *p_terrain) { _terrain = p_terrain; } Terrain3D *get_terrain() const { return _terrain; } void set_brush_data(const Dictionary &p_data); Dictionary get_brush_data() const { return _brush_data; }; void set_tool(const Tool p_tool); Tool get_tool() const { return _tool; } void set_operation(const Operation p_operation); Operation get_operation() const { return _operation; } void start_operation(const Vector3 &p_global_position); bool is_operating() const { return _is_operating; } void operate(const Vector3 &p_global_position, const real_t p_camera_direction); void backup_region(const Ref &p_region); void stop_operation(); protected: static void _bind_methods(); }; VARIANT_ENUM_CAST(Terrain3DEditor::Operation); VARIANT_ENUM_CAST(Terrain3DEditor::Tool); // Inline functions inline MapType Terrain3DEditor::_get_map_type() const { switch (_tool) { case SCULPT: case HEIGHT: case INSTANCER: return TYPE_HEIGHT; break; case TEXTURE: case AUTOSHADER: case HOLES: case NAVIGATION: case ANGLE: case SCALE: return TYPE_CONTROL; break; case COLOR: case ROUGHNESS: return TYPE_COLOR; break; default: return TYPE_MAX; } } inline bool Terrain3DEditor::_is_in_bounds(const Point2i &p_pixel, const Point2i &p_size) const { bool positive = p_pixel.x >= 0 && p_pixel.y >= 0; bool less_than_max = p_pixel.x < p_size.x && p_pixel.y < p_size.y; return positive && less_than_max; } inline Vector2 Terrain3DEditor::_get_uv_position(const Vector3 &p_global_position, const int p_region_size, const real_t p_vertex_spacing) const { Vector2 descaled_position_2d = Vector2(p_global_position.x, p_global_position.z) / p_vertex_spacing; Vector2 region_position = descaled_position_2d / real_t(p_region_size); region_position = region_position.floor(); Vector2 uv_position = (descaled_position_2d / real_t(p_region_size)) - region_position; return uv_position; } inline Vector2 Terrain3DEditor::_get_rotated_uv(const Vector2 &p_uv, const real_t p_angle) const { Vector2 rotation_offset = V2(0.5f); Vector2 uv = (p_uv - rotation_offset).rotated(p_angle) + rotation_offset; return uv.clamp(V2_ZERO, V2(1.f)); } #endif // TERRAIN3D_EDITOR_CLASS_H ================================================ FILE: src/terrain_3d_instancer.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include "constants.h" #include "logger.h" #include "terrain_3d_instancer.h" #include "terrain_3d_region.h" #include "terrain_3d_util.h" /////////////////////////// // Private Functions /////////////////////////// // Creates MMIs based on Multimesh data stored in Terrain3DRegions void Terrain3DInstancer::_process_updates() { if (_queued_updates.empty()) { if (RS->is_connected("frame_pre_draw", callable_mp(this, &Terrain3DInstancer::_process_updates))) { LOG(DEBUG, "Disconnect from RS::frame_pre_draw signal"); RS->disconnect("frame_pre_draw", callable_mp(this, &Terrain3DInstancer::_process_updates)); } return; } IS_DATA_INIT(VOID); if (!_terrain->is_inside_tree()) { return; } const Terrain3DData *data = _terrain->get_data(); TypedArray region_locations = data->get_region_locations(); int mesh_count = _terrain->get_assets()->get_mesh_count(); // Process all regions/mesh_ids sentinel and exit bool update_all = false; if (_queued_updates.find({ V2I_MAX, -2 }) != _queued_updates.end()) { destroy(); update_all = true; } else if (_queued_updates.find({ V2I_MAX, -1 }) != _queued_updates.end()) { update_all = true; } if (update_all) { LOG(DEBUG, "Updating all regions, all mesh_ids"); for (const Vector2i ®ion_loc : region_locations) { const Terrain3DRegion *region = data->get_region_ptr(region_loc); if (!region) { LOG(WARN, "Errant null region found at: ", region_loc); continue; } for (int mesh_id = 0; mesh_id < mesh_count; mesh_id++) { auto pair = std::make_pair(region_loc, mesh_id); if (region->get_instances().has(mesh_id)) { _update_mmi_by_region(region, mesh_id); } } } _queued_updates.clear(); _terrain->get_assets()->load_pending_meshes(); return; } // Identify pairs to process in a de-duplicating Set V2IIntPair to_process; // Process queued pairs with at least one specific element for (const auto &[queued_loc, queued_mesh] : _queued_updates) { if (queued_loc == V2I_MAX && queued_mesh < 0 || queued_mesh >= mesh_count) { continue; // Skip sentinels handled above } // If all regions for specific mesh_id if (queued_loc == V2I_MAX && queued_mesh >= 0) { for (const Vector2i ®ion_loc : region_locations) { auto pair = std::make_pair(region_loc, queued_mesh); to_process.emplace(pair); } } else { // Else one specific region if (queued_mesh == -1) { // All mesh_ids for this region for (int mesh_id = 0; mesh_id < mesh_count; mesh_id++) { auto pair = std::make_pair(queued_loc, mesh_id); to_process.emplace(pair); } } else { // Specific region + mesh - most common case auto pair = std::make_pair(queued_loc, queued_mesh); to_process.emplace(pair); } } } LOG(DEBUG, "Processing ", (int)to_process.size(), " queued updates"); for (const auto &[region_loc, mesh_id] : to_process) { const Terrain3DRegion *region = data->get_region_ptr(region_loc); if (!region) { LOG(WARN, "Errant null region found at: ", region_loc); continue; } if (!region->get_instances().has(mesh_id)) { continue; } _update_mmi_by_region(region, mesh_id); } _queued_updates.clear(); _terrain->get_assets()->load_pending_meshes(); } void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, const int p_mesh_id) { if (!p_region) { LOG(ERROR, "p_region is null"); return; } if (p_mesh_id < 0 || p_mesh_id >= Terrain3DAssets::MAX_MESHES) { LOG(ERROR, "p_mesh_id is out of bounds"); return; } Vector2i region_loc = p_region->get_location(); Dictionary mesh_inst_dict = p_region->get_instances(); // Verify mesh id is valid, enabled, and has MeshInstance3Ds Ref ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id); if (!ma.is_valid()) { LOG(WARN, "MeshAsset ", p_mesh_id, " is null, destroying MMIs"); _destroy_mmi_by_location(region_loc, p_mesh_id); // Clean up if orphaned return; } if (!ma->is_enabled()) { LOG(DEBUG, "Disabling mesh ", p_mesh_id, " in region ", region_loc, ": destroying MMIs"); _destroy_mmi_by_location(region_loc, p_mesh_id); return; } if (ma->get_lod_count() == 0) { LOG(WARN, "MeshAsset ", p_mesh_id, " valid but has no meshes, destroying MMIs"); _destroy_mmi_by_location(region_loc, p_mesh_id); return; } // Process cells Dictionary cell_inst_dict = mesh_inst_dict[p_mesh_id]; Array cell_locations = cell_inst_dict.keys(); for (const Vector2i &cell : cell_locations) { Array triple = cell_inst_dict[cell]; if (triple.size() < 3) { LOG(WARN, "Triple is empty for region, ", region_loc, ", cell ", cell); continue; } TypedArray xforms = triple[0]; PackedColorArray colors = triple[1]; bool modified = triple[2]; // Clean MMIs if xforms have been removed if (xforms.size() == 0) { LOG(EXTREME, "Empty cell in region ", region_loc, " mesh ", p_mesh_id, " cell ", cell, ": destroying MMIs"); _destroy_mmi_by_cell(region_loc, p_mesh_id, cell); continue; } // Clean MMIs f/ LODs not used for (int lod = 0; lod < Terrain3DMeshAsset::MAX_LOD_COUNT; lod++) { if (lod > ma->get_last_lod() || (ma->get_cast_shadows() == SHADOWS_ONLY && (lod > ma->get_last_shadow_lod() || lod < ma->get_shadow_impostor()))) { _destroy_mmi_by_cell(region_loc, p_mesh_id, cell, lod); LOG(EXTREME, "Destroyed old MMIs mesh ", p_mesh_id, " cell ", cell, ", LOD ", lod); } } // Clean Shadow MMIs bool shadow_lod_disabled = (ma->get_shadow_impostor() == 0 || ma->get_cast_shadows() == SHADOWS_OFF); if (shadow_lod_disabled) { _destroy_mmi_by_cell(region_loc, p_mesh_id, cell, Terrain3DMeshAsset::SHADOW_LOD_ID); LOG(EXTREME, "Destroyed stale shadow MMI mesh ", p_mesh_id, " for disabled impostor in cell ", cell); } // Setup MMIs for each LOD + shadows // Get or create mesh dict (defined here as cleanup above might invalidate it) MeshMMIDict &mesh_mmi_dict = _mmi_rids[region_loc]; RID shadow_impostor_source_mm; for (int lod = ma->get_last_lod(); lod >= Terrain3DMeshAsset::SHADOW_LOD_ID; lod--) { // Don't create shadow MMI if not needed if (lod == Terrain3DMeshAsset::SHADOW_LOD_ID && shadow_lod_disabled) { continue; } // Don't create MMIs for certain lods in Shadows only if (ma->get_cast_shadows() == SHADOWS_ONLY && lod >= 0 && (lod > ma->get_last_shadow_lod() || lod < ma->get_shadow_impostor())) { continue; } // Get or create MMI - [] creates key if missing Vector2i mesh_key(p_mesh_id, lod); CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; RID &mmi = cell_mmi_dict[cell].first; // null if missing if (!mmi.is_valid()) { mmi = RS->instance_create(); RS->instance_set_scenario(mmi, _terrain->get_world_3d()->get_scenario()); modified = true; // New MMI needs full update } // Always update MMI propertiess if (ma->is_highlighted()) { RS->instance_geometry_set_material_override(mmi, ma->get_highlight_material().is_valid() ? ma->get_highlight_material()->get_rid() : RID()); RS->instance_geometry_set_material_overlay(mmi, RID()); } else { RS->instance_geometry_set_material_override(mmi, ma->get_material_override().is_valid() ? ma->get_material_override()->get_rid() : RID()); RS->instance_geometry_set_material_overlay(mmi, ma->get_material_overlay().is_valid() ? ma->get_material_overlay()->get_rid() : RID()); } RS->instance_geometry_set_cast_shadows_setting(mmi, ma->get_lod_cast_shadows(lod)); RS->instance_set_layer_mask(mmi, ma->get_visibility_layers()); _set_mmi_lod_ranges(mmi, ma, lod); // Reposition MMI to region location Transform3D t = Transform3D(); int region_size = p_region->get_region_size(); real_t vertex_spacing = _terrain->get_vertex_spacing(); t.origin.x += region_loc.x * region_size * vertex_spacing; t.origin.z += region_loc.y * region_size * vertex_spacing; RS->instance_set_transform(mmi, t); RID &mm = cell_mmi_dict[cell].second; // Only recreate MultiMesh if modified/no existing (with override for shadow) // Always update shadow MMI (source may have changed) if (modified || !mm.is_valid() || lod == Terrain3DMeshAsset::SHADOW_LOD_ID) { // Subtract previous instance count for this cell int instance_count_mod = 0; if (mm.is_valid() && lod == _get_master_lod(ma)) { instance_count_mod = -RS->multimesh_get_instance_count(mm); } if (lod == Terrain3DMeshAsset::SHADOW_LOD_ID) { // Reuse impostor LOD MM as shadow impostor mm = shadow_impostor_source_mm; if (!mm.is_valid()) { LOG(ERROR, "Shadow MM is null for cell ", cell, " lod ", lod, " xforms: ", xforms.size()); continue; } } else { if (mm.is_valid()) { RS->free_rid(mm); } mm = _create_multimesh(p_mesh_id, lod, xforms, colors); } if (!mm.is_valid()) { LOG(ERROR, "Null MM for cell ", cell, " lod ", lod, " xforms: ", xforms.size()); continue; } RS->instance_set_base(mmi, mm); // Add current instance count for this cell if (lod == _get_master_lod(ma)) { ma->update_instance_count(instance_count_mod + RS->multimesh_get_instance_count(mm)); } // Clear modified only for visible LODs if (lod != Terrain3DMeshAsset::SHADOW_LOD_ID) { triple[2] = false; } } else { // Needed to update generated mesh changes Ref mesh = ma->get_mesh(lod); if (!mesh.is_valid()) { LOG(ERROR, "Mesh is null for LOD ", lod); RS->free_rid(mmi); RS->free_rid(mm); continue; } RS->multimesh_set_mesh(mm, mesh->get_rid()); } // Capture source MM from shadow impostor LOD if (lod == ma->get_shadow_impostor()) { shadow_impostor_source_mm = mm; } } // End for LOD loop // Set all LOD mmi AABB to match LOD0 to ensure no gaps between transitions. AABB mm_custom_aabb; for (int lod = 0; lod <= ma->get_last_lod(); lod++) { Vector2i mesh_key(p_mesh_id, lod); CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; RID &mmi = cell_mmi_dict[cell].first; RID &mm = cell_mmi_dict[cell].second; if (mm.is_valid() && mmi.is_valid()) { if (lod == _get_master_lod(ma)) { mm_custom_aabb = RS->multimesh_get_aabb(mm); } else { RS->multimesh_set_custom_aabb(mm, mm_custom_aabb); } RS->instance_set_custom_aabb(mmi, mm_custom_aabb); } } if (ma->get_shadow_impostor() > 0) { Vector2i mesh_key(p_mesh_id, Terrain3DMeshAsset::SHADOW_LOD_ID); CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; RID &mmi = cell_mmi_dict[cell].first; if (mmi.is_valid()) { RS->instance_set_custom_aabb(mmi, mm_custom_aabb); } } } } void Terrain3DInstancer::_set_mmi_lod_ranges(RID p_mmi, const Ref &p_ma, const int p_lod) { if (!p_mmi || p_ma.is_null()) { return; } int source_lod = p_lod; real_t lod_begin = p_ma->get_lod_range_begin(source_lod); real_t lod_end = p_ma->get_lod_range_end(source_lod); if (source_lod == Terrain3DMeshAsset::SHADOW_LOD_ID) { source_lod = p_ma->get_shadow_impostor(); lod_begin = 0.f; lod_end = p_ma->get_lod_range_begin(source_lod); } real_t margin = p_ma->get_fade_margin(); if (margin > 0.f) { lod_begin = MAX(lod_begin < 0.001f ? 0.f : lod_begin - margin, 0.f); lod_end = MAX(lod_end < 0.001f ? 0.f : lod_end + margin, 0.f); real_t begin_margin = lod_begin < 0.001f ? 0.f : margin; real_t end_margin = lod_end < 0.001f ? 0.f : margin; RS->instance_geometry_set_visibility_range(p_mmi, lod_begin, lod_end, begin_margin, end_margin, RenderingServer::VISIBILITY_RANGE_FADE_SELF); } else { RS->instance_geometry_set_visibility_range(p_mmi, lod_begin, lod_end, 0.f, 0.f, RenderingServer::VISIBILITY_RANGE_FADE_DISABLED); } } void Terrain3DInstancer::_update_vertex_spacing(const real_t p_vertex_spacing) { IS_DATA_INIT(VOID); TypedArray region_locations = _terrain->get_data()->get_region_locations(); for (int r = 0; r < region_locations.size(); r++) { Vector2i region_loc = region_locations[r]; Terrain3DRegion *region = _terrain->get_data()->get_region_ptr(region_loc); if (!region) { LOG(WARN, "Errant null region found at: ", region_loc); continue; } real_t old_spacing = region->get_vertex_spacing(); if (old_spacing == p_vertex_spacing) { LOG(DEBUG, "region vertex spacing == vertex spacing, skipping update transform spacing for region at: ", region_loc); continue; } // For all mesh_ids in region Dictionary mesh_inst_dict = region->get_instances(); LOG(DEBUG, "Updating MMIs from: ", region_loc); Array mesh_types = mesh_inst_dict.keys(); for (int m = 0; m < mesh_types.size(); m++) { int mesh_id = mesh_types[m]; Dictionary cell_inst_dict = mesh_inst_dict[mesh_id]; Array cell_locations = cell_inst_dict.keys(); for (int c = 0; c < cell_locations.size(); c++) { // Get instances Vector2i cell = cell_locations[c]; Array triple = cell_inst_dict[cell]; TypedArray xforms = triple[0]; // Descale, then Scale to the new value for (int i = 0; i < xforms.size(); i++) { Transform3D t = xforms[i]; t.origin.x /= old_spacing; t.origin.x *= p_vertex_spacing; t.origin.z /= old_spacing; t.origin.z *= p_vertex_spacing; xforms[i] = t; } triple[0] = xforms; triple[2] = true; cell_inst_dict[cell] = triple; } } // After all transforms are updated, set the new region vertex spacing value region->set_vertex_spacing(p_vertex_spacing); region->set_modified(true); } update_mmis(-1, V2I_MAX, true); } void Terrain3DInstancer::_destroy_mmi_by_mesh(const int p_mesh_id) { LOG(DEBUG, "Deleting all MMIs in all regions with mesh_id: ", p_mesh_id); TypedArray region_locations = _terrain->get_data()->get_region_locations(); for (const Vector2i ®ion_loc : region_locations) { Ref ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id); ma.is_valid() ? ma->set_instance_count(0) : void(); // Reset count for this mesh _destroy_mmi_by_location(region_loc, p_mesh_id); } } void Terrain3DInstancer::_destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id) { LOG(DEBUG, "Deleting all MMIs in region: ", p_region_loc, " for mesh_id: ", p_mesh_id); // Identify cells with matching mesh_id std::unordered_set cells; if (_mmi_rids.count(p_region_loc) > 0) { MeshMMIDict &mesh_mmi_dict = _mmi_rids[p_region_loc]; for (const auto &mesh_entry : mesh_mmi_dict) { const Vector2i &mesh_key = mesh_entry.first; if (mesh_key.x != p_mesh_id) { continue; } const CellMMIDict &cell_mmi_dict = mesh_entry.second; for (const auto &cell_entry : cell_mmi_dict) { cells.insert(cell_entry.first); } } } // Iterate over unique matching cells; each _destroy_mmi_by_cell will handle all LODs for (const Vector2i &cell : cells) { _destroy_mmi_by_cell(p_region_loc, p_mesh_id, cell); } // After all cells are destroyed, if the region is now empty, erase it if (_mmi_rids.count(p_region_loc) > 0 && _mmi_rids[p_region_loc].empty()) { _mmi_rids.erase(p_region_loc); } } void Terrain3DInstancer::_destroy_mmi_by_cell(const Vector2i &p_region_loc, const int p_mesh_id, const Vector2i p_cell, const int p_lod) { if (_mmi_rids.count(p_region_loc) == 0) { return; } MeshMMIDict &mesh_mmi_dict = _mmi_rids[p_region_loc]; Ref ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id); for (int lod = Terrain3DMeshAsset::SHADOW_LOD_ID; lod < Terrain3DMeshAsset::MAX_LOD_COUNT; lod++) { // Skip if not all lods, or not matching lod if (p_lod != INT32_MAX && lod != p_lod) { continue; } Vector2i mesh_key(p_mesh_id, lod); if (mesh_mmi_dict.count(mesh_key) == 0) { continue; } CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; if (cell_mmi_dict.count(p_cell) == 0) { continue; } RID &mmi = cell_mmi_dict[p_cell].first; RID &mm = cell_mmi_dict[p_cell].second; if (ma.is_valid() && mm.is_valid()) { if (lod == _get_master_lod(ma)) { ma->update_instance_count(-RS->multimesh_get_instance_count(mm)); } } LOG(EXTREME, "Freeing mmi:", mmi, ", mm:", mm, " and erasing mmi cell ", p_cell); if (mmi.is_valid()) { RS->free_rid(mmi); } // Unlike the Shadow MMI, the Shadow MM is a copy of another lod, not a unique RID to be freed if (lod != Terrain3DMeshAsset::SHADOW_LOD_ID) { if (mm.is_valid()) { RS->free_rid(mm); } } cell_mmi_dict.erase(p_cell); // If the cell is empty of all MMIs, remove it if (cell_mmi_dict.empty()) { LOG(EXTREME, "Removing mesh ", mesh_key, " from cell MMI dictionary"); mesh_mmi_dict.erase(mesh_key); // invalidates cell_mmi_dict } } // Clean up region if we've removed the last MMI and cell if (mesh_mmi_dict.empty()) { LOG(EXTREME, "Removing region ", p_region_loc, " from mesh MMI dictionary"); // This invalidates mesh_mmi_dict here and for calling functions _mmi_rids.erase(p_region_loc); } } void Terrain3DInstancer::_backup_region(const Ref &p_region) { if (p_region.is_null()) { return; } if (_terrain && _terrain->get_editor() && _terrain->get_editor()->is_operating()) { _terrain->get_editor()->backup_region(p_region); } else { p_region->set_modified(true); } } RID Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const int p_lod, const TypedArray &p_xforms, const PackedColorArray &p_colors) const { RID mm; IS_INIT(mm); if (p_xforms.size() == 0) { return mm; } Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(p_mesh_id); if (mesh_asset.is_null()) { LOG(ERROR, "No mesh id ", p_mesh_id, " found"); return mm; } Ref mesh = mesh_asset->get_mesh(p_lod); if (mesh.is_null()) { LOG(ERROR, "No LOD ", p_lod, " for mesh id ", p_mesh_id, " found. Max: ", mesh_asset->get_lod_count()); return mm; } mm = RS->multimesh_create(); RS->multimesh_allocate_data(mm, p_xforms.size(), RenderingServer::MULTIMESH_TRANSFORM_3D, true, false, false); RS->multimesh_set_mesh(mm, mesh->get_rid()); for (int i = 0; i < p_xforms.size(); i++) { RS->multimesh_instance_set_transform(mm, i, p_xforms[i]); if (i < p_colors.size()) { RS->multimesh_instance_set_color(mm, i, p_colors[i]); } } return mm; } Vector2i Terrain3DInstancer::_get_cell(const Vector3 &p_global_position, const int p_region_size) const { IS_INIT(V2I_ZERO); real_t vertex_spacing = _terrain->get_vertex_spacing(); Vector2i cell; cell.x = UtilityFunctions::posmod(UtilityFunctions::floori(p_global_position.x / vertex_spacing), p_region_size) / CELL_SIZE; cell.y = UtilityFunctions::posmod(UtilityFunctions::floori(p_global_position.z / vertex_spacing), p_region_size) / CELL_SIZE; return cell; } // Get appropriate terrain height. Could find terrain (excluding slope or holes) or optional collision Array Terrain3DInstancer::_get_usable_height(const Vector3 &p_global_position, const Vector2 &p_slope_range, const bool p_on_collision, const real_t p_raycast_start) const { IS_DATA_INIT(Array()); const Terrain3DData *data = _terrain->get_data(); real_t height = data->get_height(p_global_position); Dictionary raycast_result; bool raycast_hit = false; real_t raycast_height = -FLT_MAX; Vector3 raycast_normal = V3_UP; // Raycast physics if using on_collision if (p_on_collision) { Vector3 start_pos = Vector3(p_global_position.x, height + p_raycast_start, p_global_position.z); raycast_result = _terrain->get_raycast_result(start_pos, Vector3(0.0f, -p_raycast_start - 1.0f, 0.0f), 0xFFFFFFFF, true); if (raycast_result.has("position")) { raycast_hit = true; raycast_height = ((Vector3)raycast_result["position"]).y; raycast_normal = raycast_result["normal"]; } } // Hole, use collision if can or quit if (std::isnan(height)) { if (!raycast_hit) { return Array(); } height = raycast_height; } // No hole, use collision if higher else if (raycast_hit && raycast_height > height) { height = raycast_height; } // No hole or collision, use height if in slope or quit if (!data->is_in_slope(p_global_position, p_slope_range, raycast_hit ? raycast_normal : V3_ZERO)) { return Array(); } Array triple; triple.resize(3); triple[0] = height; triple[1] = raycast_hit; triple[2] = raycast_normal; return triple; } /////////////////////////// // Public Functions /////////////////////////// void Terrain3DInstancer::initialize(Terrain3D *p_terrain) { if (p_terrain) { _terrain = p_terrain; } IS_DATA_INIT_MESG("Terrain3D not initialized yet", VOID); LOG(INFO, "Initializing Instancer"); update_mmis(); } void Terrain3DInstancer::destroy() { IS_DATA_INIT(VOID); _queued_updates.clear(); LOG(INFO, "Destroying all MMIs"); int mesh_count = _terrain->get_assets()->get_mesh_count(); for (int m = 0; m < mesh_count; m++) { _destroy_mmi_by_mesh(m); } } void Terrain3DInstancer::clear_by_mesh(const int p_mesh_id) { LOG(INFO, "Deleting Multimeshes in all regions with mesh_id: ", p_mesh_id); TypedArray region_locations = _terrain->get_data()->get_region_locations(); for (const Vector2i ®ion_loc : region_locations) { clear_by_location(region_loc, p_mesh_id); } Ref ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id); ma.is_valid() ? ma->set_instance_count(0) : void(); // Reset count for this mesh } void Terrain3DInstancer::clear_by_location(const Vector2i &p_region_loc, const int p_mesh_id) { LOG(INFO, "Deleting Multimeshes w/ mesh_id: ", p_mesh_id, " in region: ", p_region_loc); Ref region = _terrain->get_data()->get_region(p_region_loc); clear_by_region(region, p_mesh_id); } void Terrain3DInstancer::clear_by_region(const Ref &p_region, const int p_mesh_id) { if (p_region.is_null()) { LOG(ERROR, "Region is null"); return; } Vector2i region_loc = p_region->get_location(); LOG(INFO, "Deleting Multimeshes w/ mesh_id: ", p_mesh_id, " in region: ", region_loc); Dictionary mesh_inst_dict = p_region->get_instances(); if (mesh_inst_dict.has(p_mesh_id)) { _backup_region(p_region); mesh_inst_dict.erase(p_mesh_id); } _destroy_mmi_by_location(region_loc, p_mesh_id); } void Terrain3DInstancer::set_mode(const InstancerMode p_mode) { LOG(INFO, "Setting instancer mode: ", p_mode); if (p_mode != _mode) { _mode = p_mode; switch (_mode) { case NORMAL: update_mmis(-1, V2I_MAX, true); break; //case PLACEHOLDER: // break; default: destroy(); break; } } } void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const Dictionary &p_params) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); int mesh_id = p_params.get("asset_id", 0); if (mesh_id < 0 || mesh_id >= _terrain->get_assets()->get_mesh_count()) { LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); return; } real_t brush_size = CLAMP(real_t(p_params.get("size", 10.f)), 0.1f, 4096.f); // Meters real_t radius = brush_size * .5f; real_t strength = CLAMP(real_t(p_params.get("strength", .1f)), .01f, 100.f); // (premul) 1-10k% real_t fixed_scale = CLAMP(real_t(p_params.get("fixed_scale", 100.f)) * .01f, .01f, 100.f); // 1-10k% real_t random_scale = CLAMP(real_t(p_params.get("random_scale", 0.f)) * .01f, 0.f, 10.f); // +/- 1000% Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(mesh_id); real_t density = CLAMP(.1f * brush_size * strength * mesh_asset->get_density() / MAX(0.01f, fixed_scale + .5f * random_scale), .001f, 1000.f); // Density based on strength, mesh AABB and input scale determines how many to place, even fractional uint32_t count = _get_density_count(density); if (count <= 0) { return; } LOG(EXTREME, "Adding ", count, " instances at ", p_global_position); real_t fixed_spin = CLAMP(real_t(p_params.get("fixed_spin", 0.f)), .0f, 360.f); // degrees real_t random_spin = CLAMP(real_t(p_params.get("random_spin", 360.f)), 0.f, 360.f); // degrees real_t fixed_tilt = CLAMP(real_t(p_params.get("fixed_tilt", 0.f)), -180.f, 180.f); // degrees real_t random_tilt = CLAMP(real_t(p_params.get("random_tilt", 10.f)), 0.f, 180.f); // degrees bool align_to_normal = bool(p_params.get("align_to_normal", false)); real_t height_offset = CLAMP(real_t(p_params.get("height_offset", 0.f)), -100.0f, 100.f); // meters real_t random_height = CLAMP(real_t(p_params.get("random_height", 0.f)), 0.f, 100.f); // meters Color vertex_color = Color(p_params.get("vertex_color", COLOR_WHITE)); real_t random_hue = CLAMP(real_t(p_params.get("random_hue", 0.f)) / 360.f, 0.f, 1.f); // degrees -> 0-1 real_t random_darken = CLAMP(real_t(p_params.get("random_darken", 0.f)) * .01f, 0.f, 1.f); // 0-100% Vector2 slope_range = p_params.get("slope", Vector2(0.f, 90.f)); // 0-90 degrees slope_range.x = CLAMP(slope_range.x, 0.f, 90.f); slope_range.y = CLAMP(slope_range.y, 0.f, 90.f); bool on_collision = bool(p_params.get("on_collision", false)); real_t raycast_height = p_params.get("raycast_height", 10.f); Terrain3DData *data = _terrain->get_data(); TypedArray xforms; PackedColorArray colors; for (int i = 0; i < count; i++) { Transform3D t; // Get random XZ position and height in a circle real_t r_radius = radius * sqrt(UtilityFunctions::randf()); real_t r_theta = UtilityFunctions::randf() * Math_TAU; Vector3 rand_vec = Vector3(r_radius * cos(r_theta), 0.f, r_radius * sin(r_theta)); Vector3 position = p_global_position + rand_vec; // Get height Array height_data = _get_usable_height(position, slope_range, on_collision, raycast_height); if (height_data.size() != 3) { continue; } position.y = height_data[0]; bool raycast_hit = height_data[1]; // Orientation Vector3 normal = V3_UP; if (align_to_normal) { // Use either collision normal or terrain normal normal = raycast_hit ? (Vector3)height_data[2] : data->get_normal(position); if (!normal.is_finite()) { normal = V3_UP; } else { normal = normal.normalized(); Vector3 z_axis = Vector3(0.f, 0.f, 1.f); Vector3 x_axis = -z_axis.cross(normal); if (x_axis.length_squared() > 0.001f) { t.basis = Basis(x_axis, normal, z_axis).orthonormalized(); } } } real_t spin = (fixed_spin + random_spin * UtilityFunctions::randf()) * Math_PI / 180.f; if (std::abs(spin) > 0.001f) { t.basis = t.basis.rotated(normal, spin); } real_t tilt = (fixed_tilt + random_tilt * (2.f * UtilityFunctions::randf() - 1.f)) * Math_PI / 180.f; if (std::abs(tilt) > 0.001f) { t.basis = t.basis.rotated(t.basis.get_column(0), tilt); // Rotate pitch, X-axis } // Scale real_t t_scale = CLAMP(fixed_scale + random_scale * (2.f * UtilityFunctions::randf() - 1.f), 0.01f, 10.f); t = t.scaled(Vector3(t_scale, t_scale, t_scale)); // Position. mesh_asset height offset added in add_transforms real_t offset = height_offset + random_height * (2.f * UtilityFunctions::randf() - 1.f); position += t.basis.get_column(1) * offset; // Offset along UP axis t = t.translated(position); // Color Color col = vertex_color; col.set_v(CLAMP(col.get_v() - random_darken * UtilityFunctions::randf(), 0.f, 1.f)); col.set_h(fmod(col.get_h() + random_hue * (2.f * UtilityFunctions::randf() - 1.f), 1.f)); xforms.push_back(t); colors.push_back(col); } // Append multimesh if (xforms.size() > 0) { add_transforms(mesh_id, xforms, colors); } } void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, const Dictionary &p_params) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); int mesh_id = p_params.get("asset_id", 0); int mesh_count = _terrain->get_assets()->get_mesh_count(); if (mesh_id < 0 || mesh_id >= mesh_count) { LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); return; } Terrain3DData *data = _terrain->get_data(); int region_size = _terrain->get_region_size(); real_t vertex_spacing = _terrain->get_vertex_spacing(); bool modifier_shift = p_params.get("modifier_shift", false); real_t brush_size = CLAMP(real_t(p_params.get("size", 10.f)), .5f, 4096.f); // Meters real_t half_brush_size = brush_size * 0.5f + 1.f; // 1m margin real_t radius = brush_size * .5f; real_t strength = CLAMP(real_t(p_params.get("strength", .1f)), .01f, 100.f); // (premul) 1-10k% Vector2 slope_range = p_params.get("slope", Vector2(0.f, 90.f)); // 0-90 degrees slope_range.x = CLAMP(slope_range.x, 0.f, 90.f); slope_range.y = CLAMP(slope_range.y, 0.f, 90.f); bool on_collision = bool(p_params.get("on_collision", false)); real_t raycast_height = p_params.get("raycast_height", 10.f); // Build list of potential regions to search, rather than searching the entire terrain, calculate possible regions covered // and check if they are valid; if so add that location to the dictionary keys. Dictionary r_locs; // Calculate step distance to ensure every region is checked inside the bounds of brush size. real_t step = brush_size / ceil(brush_size / real_t(region_size) / vertex_spacing); for (real_t x = p_global_position.x - half_brush_size; x <= p_global_position.x + half_brush_size; x += step) { for (real_t z = p_global_position.z - half_brush_size; z <= p_global_position.z + half_brush_size; z += step) { Vector2i region_loc = data->get_region_location(Vector3(x, 0.f, z)); if (data->has_region(region_loc)) { r_locs[region_loc] = 1; } } } Array region_queue = r_locs.keys(); if (region_queue.size() == 0) { return; } for (const Vector2i ®ion_loc : region_queue) { Ref region = data->get_region(region_loc); if (region.is_null()) { LOG(WARN, "Errant null region found at: ", region_loc); continue; } Dictionary mesh_inst_dict = region->get_instances(); Array mesh_types = mesh_inst_dict.keys(); if (mesh_types.size() == 0) { continue; } Vector3 global_local_offset = Vector3(region_loc.x * region_size * vertex_spacing, 0.f, region_loc.y * region_size * vertex_spacing); Vector2 localised_ring_center = Vector2(p_global_position.x - global_local_offset.x, p_global_position.z - global_local_offset.z); // For this mesh id, or all mesh ids for (int m = (modifier_shift ? 0 : mesh_id); m <= (modifier_shift ? mesh_count - 1 : mesh_id); m++) { // Ensure this region has this mesh if (!mesh_inst_dict.has(m)) { continue; } Dictionary cell_inst_dict = mesh_inst_dict[m]; Array cell_locations = cell_inst_dict.keys(); // This shouldnt be empty if (cell_locations.size() == 0) { LOG(WARN, "Region at: ", region_loc, " has instance dictionary for mesh id: ", m, " but has no cells.") continue; } // Check potential cells rather than searching the entire region, whilst marginally // slower if there are very few cells for the given mesh present. It is significantly // faster when a large number of cells are present. Dictionary c_locs; // Calculate step distance to ensure every cell is checked inside the bounds of brush size. real_t cell_step = brush_size / ceil(brush_size / real_t(CELL_SIZE) / vertex_spacing); for (real_t x = p_global_position.x - half_brush_size; x <= p_global_position.x + half_brush_size; x += cell_step) { for (real_t z = p_global_position.z - half_brush_size; z <= p_global_position.z + half_brush_size; z += cell_step) { Vector3 cell_pos = Vector3(x, 0.f, z) - global_local_offset; // Manually calculate cell pos without modulus, locations not in the current region will not be found. Vector2i cell_loc; cell_loc.x = UtilityFunctions::floori(cell_pos.x / vertex_spacing) / CELL_SIZE; cell_loc.y = UtilityFunctions::floori(cell_pos.z / vertex_spacing) / CELL_SIZE; if (cell_locations.has(cell_loc)) { c_locs[cell_loc] = 1; } } } Array cell_queue = c_locs.keys(); if (cell_queue.size() == 0) { continue; } Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(m); real_t mesh_height_offset = mesh_asset->get_height_offset(); for (int c = 0; c < cell_queue.size(); c++) { Vector2i cell = cell_queue[c]; Array triple = cell_inst_dict[cell]; TypedArray xforms = triple[0]; PackedColorArray colors = triple[1]; TypedArray updated_xforms; PackedColorArray updated_colors; // Remove transforms if inside ring radius for (int i = 0; i < xforms.size(); i++) { Transform3D t = xforms[i]; // Use localised ring center real_t radial_distance = localised_ring_center.distance_to(Vector2(t.origin.x, t.origin.z)); Vector3 height_offset = t.basis.get_column(1) * mesh_height_offset; if (radial_distance >= radius || UtilityFunctions::randf() >= CLAMP(0.175f * strength, 0.005f, 10.f)) { updated_xforms.push_back(t); updated_colors.push_back(colors[i]); continue; } Vector3 global_pos = t.origin + global_local_offset - t.basis.get_column(1) * mesh_height_offset; Array height_data = _get_usable_height(global_pos, slope_range, on_collision, raycast_height); if (height_data.size() != 3) { updated_xforms.push_back(t); updated_colors.push_back(colors[i]); continue; } _backup_region(region); } if (updated_xforms.size() > 0) { triple[0] = updated_xforms; triple[1] = updated_colors; triple[2] = true; cell_inst_dict[cell] = triple; } else { cell_inst_dict.erase(cell); _destroy_mmi_by_cell(region_loc, m, cell); } } if (cell_inst_dict.is_empty()) { mesh_inst_dict.erase(m); } update_mmis(m, region_loc); } } } void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform, const bool p_update) { LOG(INFO, "Extracting ", p_multimesh->get_instance_count(), " transforms from multimesh"); TypedArray xforms; PackedColorArray colors; for (int i = 0; i < p_multimesh->get_instance_count(); i++) { xforms.push_back(p_xform * p_multimesh->get_instance_transform(i)); Color c = COLOR_WHITE; if (p_multimesh->is_using_colors()) { c = p_multimesh->get_instance_color(i); } colors.push_back(c); } add_transforms(p_mesh_id, xforms, colors, p_update); } // Expects transforms in global space void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_update) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); if (p_xforms.size() == 0) { return; } if (p_mesh_id < 0 || p_mesh_id >= _terrain->get_assets()->get_mesh_count()) { LOG(ERROR, "Mesh ID out of range: ", p_mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); return; } Dictionary xforms_dict; Dictionary colors_dict; Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(p_mesh_id); real_t height_offset = mesh_asset->get_height_offset(); // Separate incoming transforms/colors by region Dict{ region_loc => Array() } LOG(INFO, "Separating ", p_xforms.size(), " transforms and ", p_colors.size(), " colors into regions"); for (int i = 0; i < p_xforms.size(); i++) { // Get adjusted xform/color Transform3D trns = p_xforms[i]; trns.origin += trns.basis.get_column(1) * height_offset; // Offset along UP axis Color col = COLOR_WHITE; if (p_colors.size() > i) { col = p_colors[i]; } // Store by region offset Vector2i region_loc = _terrain->get_data()->get_region_location(trns.origin); if (!xforms_dict.has(region_loc)) { xforms_dict[region_loc] = TypedArray(); colors_dict[region_loc] = PackedColorArray(); } TypedArray xforms = xforms_dict[region_loc]; PackedColorArray colors = colors_dict[region_loc]; xforms.push_back(trns); colors.push_back(col); colors_dict[region_loc] = colors; // Note similar bug as godot-cpp#1149 needs this for PCA } // Merge incoming transforms with existing transforms Array region_locations = xforms_dict.keys(); for (const Vector2i ®ion_loc : region_locations) { TypedArray xforms = xforms_dict[region_loc]; PackedColorArray colors = colors_dict[region_loc]; //LOG(MESG, "Appending ", xforms.size(), " xforms, ", colors, " colors to region location: ", region_loc); append_location(region_loc, p_mesh_id, xforms, colors, p_update); } } // Appends new global transforms to existing cells, offsetting transforms to region space, scaled by vertex spacing void Terrain3DInstancer::append_location(const Vector2i &p_region_loc, const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_update) { IS_DATA_INIT(VOID); Ref region = _terrain->get_data()->get_region(p_region_loc); if (region.is_null()) { return; } int region_size = region->get_region_size(); real_t vertex_spacing = _terrain->get_vertex_spacing(); Vector2 global_local_offset = Vector2(p_region_loc.x * region_size * vertex_spacing, p_region_loc.y * region_size * vertex_spacing); TypedArray localised_xforms; for (Transform3D t : p_xforms) { // Localise the transform to "region space" t.origin.x -= global_local_offset.x; t.origin.z -= global_local_offset.y; localised_xforms.push_back(t); } append_region(region, p_mesh_id, localised_xforms, p_colors, p_update); } // append_region requires all transforms are in region space, 0 - region_size * vertex_spacing void Terrain3DInstancer::append_region(const Ref &p_region, const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_update) { if (p_region.is_null()) { LOG(ERROR, "Null region provided. Doing nothing."); return; } if (p_xforms.size() == 0) { LOG(ERROR, "No transforms to add. Doing nothing."); return; } _backup_region(p_region); Dictionary cell_locations = p_region->get_instances()[p_mesh_id]; int region_size = p_region->get_region_size(); for (int i = 0; i < p_xforms.size(); i++) { Transform3D xform = p_xforms[i]; Color col = p_colors[i]; Vector2i cell = _get_cell(xform.origin, region_size); // Get current instance arrays or create if none Array triple = cell_locations[cell]; bool modified = true; if (triple.size() != 3) { LOG(DEBUG, "No data at ", p_region->get_location(), ":", cell, ". Creating triple"); triple.resize(3); triple[0] = TypedArray(); triple[1] = PackedColorArray(); triple[2] = modified; } TypedArray xforms = triple[0]; PackedColorArray colors = triple[1]; xforms.push_back(xform); colors.push_back(col); // Must write back since there are copy constructors somewhere // see godot-cpp#1149 triple[0] = xforms; triple[1] = colors; triple[2] = modified; cell_locations[cell] = triple; } // Write back dictionary. See above comments p_region->get_instances()[p_mesh_id] = cell_locations; if (p_update) { update_mmis(p_mesh_id, p_region->get_location()); } } // Review all transforms in one area and adjust their transforms w/ the current height void Terrain3DInstancer::update_transforms(const AABB &p_aabb) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); Rect2 rect = aabb2rect(p_aabb); LOG(EXTREME, "Updating transforms within ", rect); Vector2 global_position = rect.get_center(); Vector2 size = rect.get_size(); Vector2 half_size = size * 0.5f + V2(1.f); // 1m margin if (size.is_zero_approx()) { return; } Terrain3DData *data = _terrain->get_data(); Dictionary params = _terrain->get_editor() ? _terrain->get_editor()->get_brush_data() : Dictionary(); bool on_collision = params.get("on_collision", false); real_t raycast_height = params.get("raycast_height", 10.f); int region_size = _terrain->get_region_size(); real_t vertex_spacing = _terrain->get_vertex_spacing(); // Build list of valid regions within AABB; add the locations as dictionary keys. Dictionary r_locs; // Calculate step distance to ensure every region is checked inside the bounds of AABB size. Vector2 step = Vector2(size.x / ceil(size.x / real_t(region_size) / vertex_spacing), size.y / ceil(size.y / real_t(region_size) / vertex_spacing)); for (real_t x = global_position.x - half_size.x; x <= global_position.x + half_size.x; x += step.x) { for (real_t z = global_position.y - half_size.y; z <= global_position.y + half_size.y; z += step.y) { Vector2i region_loc = data->get_region_location(Vector3(x, 0.f, z)); if (data->has_region(region_loc)) { r_locs[region_loc] = 0; } } } Array region_queue = r_locs.keys(); if (region_queue.size() == 0) { return; } for (const Vector2i ®ion_loc : region_queue) { Ref region = data->get_region(region_loc); _backup_region(region); Dictionary mesh_inst_dict = region->get_instances(); Array mesh_types = mesh_inst_dict.keys(); if (mesh_types.size() == 0) { continue; } Vector3 global_local_offset = Vector3(region_loc.x * region_size * vertex_spacing, 0.f, region_loc.y * region_size * vertex_spacing); // For each mesh type in this region for (const int ®ion_mesh_id : mesh_types) { // Check potential cells rather than searching the entire region, whilst marginally // slower if there are very few cells for the given mesh present it is significantly // faster when a very large number of cells are present. Dictionary cell_inst_dict = mesh_inst_dict[region_mesh_id]; Array cell_locations = cell_inst_dict.keys(); if (cell_locations.size() == 0) { continue; } Dictionary c_locs; // Calculate step distance to ensure every cell is checked inside the bounds of brush size. Vector2 cell_step = Vector2(size.x / ceil(size.x / real_t(CELL_SIZE) / vertex_spacing), size.y / ceil(size.y / real_t(CELL_SIZE) / vertex_spacing)); for (real_t x = global_position.x - half_size.x; x <= global_position.x + half_size.x; x += cell_step.x) { for (real_t z = global_position.y - half_size.y; z <= global_position.y + half_size.y; z += cell_step.y) { Vector3 cell_pos = Vector3(x, 0.f, z) - global_local_offset; // Manually calculate cell pos without modulus, locations not in the current region will not be found. Vector2i cell_loc; cell_loc.x = UtilityFunctions::floori(cell_pos.x / vertex_spacing) / CELL_SIZE; cell_loc.y = UtilityFunctions::floori(cell_pos.z / vertex_spacing) / CELL_SIZE; if (cell_locations.has(cell_loc)) { c_locs[cell_loc] = 0; } } } Array cell_queue = c_locs.keys(); if (cell_queue.size() == 0) { continue; } Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(region_mesh_id); real_t mesh_height_offset = mesh_asset->get_height_offset(); for (const Vector2i &cell : cell_queue) { Array triple = cell_inst_dict[cell]; TypedArray xforms = triple[0]; PackedColorArray colors = triple[1]; TypedArray updated_xforms; PackedColorArray updated_colors; for (int i = 0; i < xforms.size(); i++) { Transform3D t = xforms[i]; Vector3 global_origin(t.origin + global_local_offset); if (rect.has_point(Vector2(global_origin.x, global_origin.z))) { Vector3 height_offset = t.basis.get_column(1) * mesh_height_offset; t.origin -= height_offset; Array height_data = _get_usable_height(global_origin, Vector2(0.f, 90.f), on_collision, raycast_height); if (height_data.size() != 3) { continue; } t.origin.y = height_data[0]; t.origin += height_offset; } updated_xforms.push_back(t); updated_colors.push_back(colors[i]); } if (updated_xforms.size() > 0) { triple[0] = updated_xforms; triple[1] = updated_colors; triple[2] = true; cell_inst_dict[cell] = triple; } else { // Removed if a hole erased everything cell_inst_dict.erase(cell); _destroy_mmi_by_cell(region_loc, region_mesh_id, cell); } if (cell_inst_dict.is_empty()) { mesh_inst_dict.erase(region_mesh_id); } } update_mmis(region_mesh_id, region_loc); } } } int Terrain3DInstancer::get_closest_mesh_id(const Vector3 &p_global_position) const { LOG(INFO, "Finding mesh asset ID closest to specified position ", p_global_position); Vector2i region_loc = _terrain->get_data()->get_region_location(p_global_position); const Terrain3DRegion *region = _terrain->get_data()->get_region_ptr(region_loc); if (!region) { LOG(WARN, "No region found at position: ", p_global_position); return -1; // No region found } int region_size = region->get_region_size(); Vector3 region_global_pos = v2iv3(region_loc) * real_t(region_size) * _terrain->get_vertex_spacing(); Vector2i cell = _get_cell(p_global_position, region_size); Dictionary mesh_inst_dict = region->get_instances(); if (mesh_inst_dict.is_empty()) { return -1; // No meshes found } if (mesh_inst_dict.size() == 1) { // Only one mesh type in this region, return it return mesh_inst_dict.keys()[0]; } //Iterate through all mesh types looking for meshes in cell to find the closest transform int closest_id = -1; real_t closest_distance = FLT_MAX; Array mesh_instance_keys = mesh_inst_dict.keys(); for (const int &mesh_id : mesh_instance_keys) { Dictionary cell_inst_dict = mesh_inst_dict[mesh_id]; if (!cell_inst_dict.has(cell)) { continue; // No instances in this cell } Array triple = cell_inst_dict[cell]; if (triple.size() != 3) { continue; // Invalid triple, skip } TypedArray xforms = triple[0]; if (xforms.is_empty()) { continue; // No transforms in this cell } for (const Transform3D &instance_transform : xforms) { Vector3 instance_origin = instance_transform.origin + region_global_pos; // Convert to global position real_t distance = instance_origin.distance_squared_to(p_global_position); if (distance < closest_distance) { closest_distance = distance; closest_id = mesh_id; } } } LOG(DEBUG, "Found closest Mesh Asset ID: ", closest_id, " at: ", closest_distance); return closest_id; } // Transfer foliage data from one region to another // p_src_rect is the vertex/pixel offset into the region data, NOT a global position // Need to update_mmis() after void Terrain3DInstancer::copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2i &p_src_rect, const Terrain3DRegion *p_dst_region) { if (!p_src_region || !p_dst_region) { LOG(ERROR, "Source (", p_src_region, ") or destination (", p_dst_region, ") regions are null"); return; } LOG(INFO, "Copying foliage data from src ", p_src_region->get_location(), " to dest ", p_dst_region->get_location()); real_t vertex_spacing = _terrain->get_vertex_spacing(); // Offset to dst from src Vector2i src_region_loc = p_src_region->get_location(); int src_region_size = p_src_region->get_region_size(); Vector2i src_offset = Vector2i(src_region_loc.x * src_region_size, src_region_loc.y * src_region_size); Vector2i dst_region_loc = p_dst_region->get_location(); int dst_region_size = p_dst_region->get_region_size(); Vector2i dst_offset = src_offset - Vector2i(dst_region_loc.x * dst_region_size, dst_region_loc.y * dst_region_size); Vector3 dst_translate = Vector3(dst_offset.x, 0.f, dst_offset.y) * vertex_spacing; // Get all Cell locations in rect, which is already in region space. Vector2i cell_start = p_src_rect.get_position() / CELL_SIZE; Vector2i steps = p_src_rect.get_size() / CELL_SIZE; Dictionary cells_to_copy; for (int x = cell_start.x; x < cell_start.x + steps.x; x++) { for (int y = cell_start.y; y < cell_start.y + steps.y; y++) { cells_to_copy[Vector2i(x, y)] = 0; } } // For each mesh, for each cell, if in rect, convert xforms to target region space, append to target region. Dictionary mesh_inst_dict = p_src_region->get_instances(); Array mesh_types = mesh_inst_dict.keys(); for (const int &mesh_id : mesh_types) { TypedArray xforms; PackedColorArray colors; Dictionary cell_inst_dict = p_src_region->get_instances()[mesh_id]; Array cell_locs = cell_inst_dict.keys(); for (const Vector2i &cell : cell_locs) { if (cells_to_copy.has(cell)) { Array triple = cell_inst_dict[cell]; TypedArray cell_xforms = triple[0]; PackedColorArray cell_colors = triple[1]; for (int i = 0; i < cell_xforms.size(); i++) { Transform3D t = cell_xforms[i]; t.origin += dst_translate; xforms.push_back(t); colors.push_back(cell_colors[i]); } } } if (xforms.size() == 0) { continue; } append_region(Ref(p_dst_region), mesh_id, xforms, colors, false); } } // Changes the ID of a mesh, without changing the mesh on the ground // Called when the mesh asset id has changed. Updates Multimeshes and MMIs dictionary keys void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); Ref assets = _terrain->get_assets(); int mesh_count = assets->get_mesh_count(); LOG(INFO, "Swapping IDs of multimeshes: ", p_src_id, " and ", p_dst_id); if (p_src_id >= 0 && p_src_id < mesh_count && p_dst_id >= 0 && p_dst_id < mesh_count) { TypedArray region_locations = _terrain->get_data()->get_region_locations(); for (const Vector2i ®ion_loc : region_locations) { Ref region = _terrain->get_data()->get_region(region_loc); if (region.is_null()) { LOG(WARN, "No region found at: ", region_loc); continue; } // mesh_inst_dict could have src, src+dst, dst or nothing. All 4 must be considered Dictionary mesh_inst_dict = region->get_instances(); Dictionary cells_inst_dict_src; Dictionary cells_inst_dict_dst; // Extract src dict if (mesh_inst_dict.has(p_src_id)) { _backup_region(region); cells_inst_dict_src = mesh_inst_dict[p_src_id]; mesh_inst_dict.erase(p_src_id); } // Extract dest dict if (mesh_inst_dict.has(p_dst_id)) { _backup_region(region); cells_inst_dict_dst = mesh_inst_dict[p_dst_id]; mesh_inst_dict.erase(p_dst_id); } // If src exists, insert into dst slot if (!cells_inst_dict_src.is_empty()) { _backup_region(region); mesh_inst_dict[p_dst_id] = cells_inst_dict_src; } // If dst exists, insert into src slot if (!cells_inst_dict_dst.is_empty()) { _backup_region(region); mesh_inst_dict[p_src_id] = cells_inst_dict_dst; } LOG(DEBUG, "Swapped mesh_ids for region: ", region_loc); } update_mmis(-1, V2I_MAX, true); } } // Defaults to update all regions, all meshes // If rebuild is true, will destroy all MMIs, then build everything // If region_loc == V2I_MAX, will do all regions for meshes specified // If mesh_id < 0, will do all meshes in the specified region // You safely can call multiple times per frame, and select any combo of options without fillling up the queue. void Terrain3DInstancer::update_mmis(const int p_mesh_id, const Vector2i &p_region_loc, const bool p_rebuild) { if (_mode == DISABLED) { LOG(INFO, "Instancer is disabled"); return; } LOG(INFO, "Queueing MMI update for mesh id: ", p_mesh_id < 0 ? "all" : String::num_int64(p_mesh_id), ", region: ", p_region_loc == V2I_MAX ? "all" : String(p_region_loc), p_rebuild ? ", destroying first" : ""); // Set to destroy and rebuild everything if (p_rebuild) { _queued_updates.clear(); _queued_updates.emplace(V2I_MAX, -2); if (!RS->is_connected("frame_pre_draw", callable_mp(this, &Terrain3DInstancer::_process_updates))) { LOG(DEBUG, "Connecting to RS::frame_pre_draw signal"); RS->connect("frame_pre_draw", callable_mp(this, &Terrain3DInstancer::_process_updates)); } return; } // If already set to destroy, build all, quit if (_queued_updates.find({ V2I_MAX, -2 }) != _queued_updates.end()) { return; } // If already set to build all, quit if (_queued_updates.find({ V2I_MAX, -1 }) != _queued_updates.end()) { return; } // If all meshes for region are queued, quit if (_queued_updates.find({ p_region_loc, -1 }) != _queued_updates.end()) { return; } // If all regions for mesh_id are queued, quit int mesh_id = CLAMP(p_mesh_id, -1, Terrain3DAssets::MAX_MESHES - 1); if (_queued_updates.find({ V2I_MAX, mesh_id }) != _queued_updates.end()) { return; } // Else queue up this region/mesh combo _queued_updates.emplace(p_region_loc, mesh_id); if (!RS->is_connected("frame_pre_draw", callable_mp(this, &Terrain3DInstancer::_process_updates))) { LOG(DEBUG, "Connecting to RS::frame_pre_draw signal"); RS->connect("frame_pre_draw", callable_mp(this, &Terrain3DInstancer::_process_updates)); } } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DInstancer::_bind_methods() { BIND_ENUM_CONSTANT(NORMAL); //BIND_ENUM_CONSTANT(PLACEHOLDER); BIND_ENUM_CONSTANT(DISABLED); ClassDB::bind_method(D_METHOD("clear_by_mesh", "mesh_id"), &Terrain3DInstancer::clear_by_mesh); ClassDB::bind_method(D_METHOD("clear_by_location", "region_location", "mesh_id"), &Terrain3DInstancer::clear_by_location); ClassDB::bind_method(D_METHOD("clear_by_region", "region", "mesh_id"), &Terrain3DInstancer::clear_by_region); ClassDB::bind_method(D_METHOD("set_mode", "mode"), &Terrain3DInstancer::set_mode); ClassDB::bind_method(D_METHOD("get_mode"), &Terrain3DInstancer::get_mode); ClassDB::bind_method(D_METHOD("is_enabled"), &Terrain3DInstancer::is_enabled); ClassDB::bind_method(D_METHOD("add_instances", "global_position", "params"), &Terrain3DInstancer::add_instances); ClassDB::bind_method(D_METHOD("remove_instances", "global_position", "params"), &Terrain3DInstancer::remove_instances); ClassDB::bind_method(D_METHOD("add_multimesh", "mesh_id", "multimesh", "transform", "update"), &Terrain3DInstancer::add_multimesh, DEFVAL(Transform3D()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_transforms", "mesh_id", "transforms", "colors", "update"), &Terrain3DInstancer::add_transforms, DEFVAL(PackedColorArray()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("append_location", "region_location", "mesh_id", "transforms", "colors", "update"), &Terrain3DInstancer::append_location, DEFVAL(true)); ClassDB::bind_method(D_METHOD("append_region", "region", "mesh_id", "transforms", "colors", "update"), &Terrain3DInstancer::append_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("update_transforms", "aabb"), &Terrain3DInstancer::update_transforms); ClassDB::bind_method(D_METHOD("get_closest_mesh_id", "global_position"), &Terrain3DInstancer::get_closest_mesh_id); ClassDB::bind_method(D_METHOD("update_mmis", "mesh_id", "region_location", "rebuild_all"), &Terrain3DInstancer::update_mmis, DEFVAL(-1), DEFVAL(V2I_MAX), DEFVAL(false)); ClassDB::bind_method(D_METHOD("swap_ids", "src_id", "dest_id"), &Terrain3DInstancer::swap_ids); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Disabled,Normal"), "set_mode", "get_mode"); } ================================================ FILE: src/terrain_3d_instancer.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_INSTANCER_CLASS_H #define TERRAIN3D_INSTANCER_CLASS_H #include #include #include #include #include "constants.h" #include "terrain_3d_region.h" class Terrain3D; class Terrain3DAssets; class Terrain3DInstancer : public Object { GDCLASS(Terrain3DInstancer, Object); CLASS_NAME(); friend Terrain3D; public: // Constants static inline const int CELL_SIZE = 32; enum InstancerMode { DISABLED, //PLACEHOLDER, NORMAL, }; private: Terrain3D *_terrain = nullptr; // MM Resources stored in Terrain3DRegion::_instances as // Region::_instances{mesh_id:int} -> cell{v2i} -> [ TypedArray, PackedColorArray, modified:bool ] // A pair of MMI and MM RIDs, freed in destructor, stored as // _mmi_rids{region_loc} -> mesh{v2i(mesh_id,lod)} -> cell{v2i} -> std::pair using CellMMIDict = std::unordered_map, Vector2iHash>; using MeshMMIDict = std::unordered_map; std::unordered_map _mmi_rids; // MMI Updates tracked in a unique Set of // means destroy first, then update everything // means update everything // means update all meshes in that region // means update mesh ID N in all regions using V2IIntPair = std::unordered_set, PairVector2iIntHash>; V2IIntPair _queued_updates; InstancerMode _mode = NORMAL; uint32_t _density_counter = 0; uint32_t _get_density_count(const real_t p_density); int _get_master_lod(const Ref &p_ma); void _process_updates(); void _update_mmi_by_region(const Terrain3DRegion *p_region, const int p_mesh_id); void _set_mmi_lod_ranges(RID p_mmi, const Ref &p_ma, const int p_lod); void _update_vertex_spacing(const real_t p_vertex_spacing); void _destroy_mmi_by_mesh(const int p_mesh_id); void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id); void _destroy_mmi_by_cell(const Vector2i &p_region_loc, const int p_mesh_id, const Vector2i p_cell, const int p_lod = INT32_MAX); void _backup_region(const Ref &p_region); RID _create_multimesh(const int p_mesh_id, const int p_lod, const TypedArray &p_xforms = TypedArray(), const PackedColorArray &p_colors = PackedColorArray()) const; Vector2i _get_cell(const Vector3 &p_global_position, const int p_region_size) const; Array _get_usable_height(const Vector3 &p_global_position, const Vector2 &p_slope_range, const bool p_on_collision, const real_t p_raycast_start) const; public: Terrain3DInstancer() {} ~Terrain3DInstancer() { destroy(); } void initialize(Terrain3D *p_terrain); void destroy(); void clear_by_mesh(const int p_mesh_id); void clear_by_location(const Vector2i &p_region_loc, const int p_mesh_id); void clear_by_region(const Ref &p_region, const int p_mesh_id); void set_mode(const InstancerMode p_mode); InstancerMode get_mode() const { return _mode; } bool is_enabled() const { return _mode != DISABLED; } void add_instances(const Vector3 &p_global_position, const Dictionary &p_params); void remove_instances(const Vector3 &p_global_position, const Dictionary &p_params); void add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform = Transform3D(), const bool p_update = true); void add_transforms(const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors = PackedColorArray(), const bool p_update = true); void append_location(const Vector2i &p_region_loc, const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_update = true); void append_region(const Ref &p_region, const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_update = true); void update_transforms(const AABB &p_aabb); int get_closest_mesh_id(const Vector3 &p_global_position) const; void copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2i &p_src_rect, const Terrain3DRegion *p_dst_region); void swap_ids(const int p_src_id, const int p_dst_id); void update_mmis(const int p_mesh_id = -1, const Vector2i &p_region_loc = V2I_MAX, const bool p_rebuild = false); void reset_density_counter() { _density_counter = 0; } protected: static void _bind_methods(); }; using InstancerMode = Terrain3DInstancer::InstancerMode; VARIANT_ENUM_CAST(Terrain3DInstancer::InstancerMode); // Allows us to instance every X function calls for sparse placement // Modifies _density_counter, not const! inline uint32_t Terrain3DInstancer::_get_density_count(const real_t p_density) { uint32_t count = 0; if (p_density < 1.f && _density_counter++ % uint32_t(1.f / p_density) == 0) { count = 1; } else if (p_density >= 1.f) { count = uint32_t(p_density); } return count; } // Use lod0 to track instance counter and set AABB, but in shadows_only lod0 doesn't exist inline int Terrain3DInstancer::_get_master_lod(const Ref &p_ma) { if (p_ma.is_valid() && p_ma->get_cast_shadows() == SHADOWS_ONLY) { return p_ma->get_shadow_impostor(); } return 0; } #endif // TERRAIN3D_INSTANCER_CLASS_H ================================================ FILE: src/terrain_3d_material.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include #include #include #include #include #include "logger.h" #include "terrain_3d_material.h" #include "terrain_3d_util.h" /////////////////////////// // Private Functions /////////////////////////// void Terrain3DMaterial::_preload_shaders() { // Preprocessor loading of external shader inserts _parse_shader( #include "shaders/samplers.glsl" , "samplers"); _parse_shader( #include "shaders/backgrounds.glsl" , "backgrounds"); _parse_shader( #include "shaders/auto_shader.glsl" , "auto_shader"); _parse_shader( #include "shaders/dual_scaling.glsl" , "dual_scaling"); _parse_shader( #include "shaders/overlays.glsl" , "overlays"); _parse_shader( #include "shaders/displacement.glsl" , "displacement"); _parse_shader( #include "shaders/macro_variation.glsl" , "macro_variation"); _parse_shader( #include "shaders/projection.glsl" , "projection"); _parse_shader( #include "shaders/debug_views.glsl" , "debug_views"); _parse_shader( #include "shaders/pbr_views.glsl" , "pbr_views"); _parse_shader( #include "shaders/editor_functions.glsl" , "editor_functions"); // Load main code _shader_code["main"] = String( #include "shaders/main.glsl" ); _shader_code["displacement_buffer"] = String( #include "shaders/displacement_buffer.glsl" ); if (Terrain3D::debug_level >= DEBUG) { Array keys = _shader_code.keys(); for (const StringName &key : keys) { LOG(DEBUG, "Loaded shader insert: ", key); } } } /** * All `//INSERT: ID` blocks in p_shader are loaded into the DB _shader_code */ void Terrain3DMaterial::_parse_shader(const String &p_shader, const String &p_name) { if (p_name.is_empty()) { LOG(ERROR, "No dictionary key for saving shader snippets specified"); return; } PackedStringArray parsed = p_shader.split("//INSERT:"); for (int i = 0; i < parsed.size(); i++) { // First section of the file before any //INSERT: if (i == 0) { _shader_code[p_name] = parsed[0]; } else { // There is at least one //INSERT: // Get the first ID on the first line PackedStringArray segment = parsed[i].split("\n", true, 1); // If there isn't an ID AND body, skip this insert if (segment.size() < 2) { continue; } String id = segment[0].strip_edges(); // Process the insert if (!id.is_empty() && !segment[1].is_empty()) { _shader_code[id] = segment[1]; } } } return; } /** * `//INSERT: ID` blocks in p_shader are replaced by the entry in the DB * returns a shader string with inserts applied * Skips `EDITOR_*` and `DEBUG_*` inserts */ String Terrain3DMaterial::_apply_inserts(const String &p_shader, const Array &p_excludes) const { PackedStringArray parsed = p_shader.split("//INSERT:"); String shader; for (int i = 0; i < parsed.size(); i++) { // First section of the file before any //INSERT: if (i == 0) { shader = parsed[0]; } else { // There is at least one //INSERT: // Get the first ID on the first line PackedStringArray segment = parsed[i].split("\n", true, 1); // If there isn't an ID AND body, skip this insert if (segment.size() < 2) { continue; } String id = segment[0].strip_edges(); // Process the insert if (!id.is_empty() && !p_excludes.has(id) && _shader_code.has(id)) { if (!id.begins_with("DEBUG_") && !id.begins_with("EDITOR_")) { String str = _shader_code[id]; shader += str; } } shader += segment[1]; } } return shader; } String Terrain3DMaterial::_generate_shader_code() const { LOG(INFO, "Generating default shader code"); Array excludes; if (_world_background != NONE) { excludes.push_back("NONE_FUNCTIONS"); excludes.push_back("NONE_CHECK"); } if (_world_background == NONE) { excludes.push_back("FLAT_UNIFORMS"); excludes.push_back("FLAT_FUNCTIONS"); excludes.push_back("FLAT_VERTEX"); excludes.push_back("FLAT_FRAGMENT"); } if (_world_background != NOISE) { excludes.push_back("WORLD_NOISE_UNIFORMS"); excludes.push_back("WORLD_NOISE_FUNCTIONS"); excludes.push_back("WORLD_NOISE_VERTEX"); excludes.push_back("WORLD_NOISE_FRAGMENT"); } switch (_texture_filtering) { case LINEAR_ANISOTROPIC: excludes.push_back("TEXTURE_SAMPLERS_NEAREST"); excludes.push_back("TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR"); break; case LINEAR: excludes.push_back("TEXTURE_SAMPLERS_NEAREST"); excludes.push_back("TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC"); break; case NEAREST_ANISOTROPIC: excludes.push_back("TEXTURE_SAMPLERS_NEAREST"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC"); break; case NEAREST: excludes.push_back("TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC"); break; } if (!_auto_shader_enabled) { excludes.push_back("AUTO_SHADER_UNIFORMS"); excludes.push_back("AUTO_SHADER"); } if (!_dual_scaling_enabled) { excludes.push_back("DUAL_SCALING_UNIFORMS"); excludes.push_back("DUAL_SCALING"); excludes.push_back("DUAL_SCALING_CONDITION_0"); excludes.push_back("DUAL_SCALING_CONDITION_1"); excludes.push_back("DUAL_SCALING_MIX"); } if (!_macro_variation_enabled) { excludes.push_back("MACRO_VARIATION_UNIFORMS"); excludes.push_back("MACRO_VARIATION"); } if (!_projection_enabled) { excludes.push_back("PROJECTION"); } if (_terrain->get_tessellation_level() == 0) { excludes.push_back("DISPLACEMENT_UNIFORMS"); excludes.push_back("DISPLACEMENT_FUNCTIONS"); excludes.push_back("DISPLACEMENT_VERTEX"); } if (!_output_albedo_enabled) { excludes.push_back("OUTPUT_ALBEDO"); } else { excludes.push_back("OUTPUT_ALBEDO_GREY"); } if (!_output_roughness_enabled) { excludes.push_back("OUTPUT_ROUGHNESS"); } if (!_output_normal_map_enabled) { excludes.push_back("OUTPUT_NORMAL_MAP"); } if (!_output_ambient_occlusion_enabled) { excludes.push_back("OUTPUT_AMBIENT_OCCLUSION"); } String shader = _apply_inserts(_shader_code["main"], excludes); return shader; } // Ripped from ShaderPreprocessor::CommentRemover::strip() String Terrain3DMaterial::_strip_comments(const String &p_shader) const { Vector stripped; String code = p_shader; int index = 0; int line = 0; int comment_line_open = 0; int comments_open = 0; int strings_open = 0; const char32_t CURSOR = 0xFFFF; // Embedded supporting functions auto peek = [&]() { return (index < code.length()) ? code[index] : 0; }; auto advance = [&](char32_t p_what) { while (index < code.length()) { char32_t c = code[index++]; if (c == '\n') { line++; stripped.push_back('\n'); } if (c == p_what) { return true; } } return false; }; auto vector_to_string = [](const Vector &p_v, int p_start = 0, int p_end = -1) { const int stop = (p_end == -1) ? p_v.size() : p_end; const int count = stop - p_start; String result; result.resize(count + 1); for (int i = 0; i < count; i++) { result[i] = p_v[p_start + i]; } result[count] = 0; // Ensure string is null terminated for length() to work. return result; }; // Main function while (index < code.length()) { char32_t c = code[index++]; if (c == CURSOR) { // Cursor. Maintain. stripped.push_back(c); } else if (c == '"') { if (strings_open <= 0) { strings_open++; } else { strings_open--; } stripped.push_back(c); } else if (c == '/' && strings_open == 0) { char32_t p = peek(); if (p == '/') { // Single line comment. advance('\n'); } else if (p == '*') { // Start of a block comment. index++; comment_line_open = line; comments_open++; while (advance('*')) { if (peek() == '/') { // End of a block comment. comments_open--; index++; break; } } } else { stripped.push_back(c); } } else if (c == '*' && strings_open == 0) { if (peek() == '/') { // Unmatched end of a block comment. comment_line_open = line; comments_open--; } else { stripped.push_back(c); } } else if (c == '\n') { line++; stripped.push_back(c); } else { stripped.push_back(c); } } return vector_to_string(stripped); } String Terrain3DMaterial::_generate_buffer_shader_code() const { LOG(INFO, "Generating default displacement buffer shader code"); Array excludes; if (_world_background != NONE) { excludes.push_back("NONE_FUNCTIONS"); excludes.push_back("NONE_CHECK"); } if (_world_background == NONE) { excludes.push_back("FLAT_UNIFORMS"); excludes.push_back("FLAT_FUNCTIONS"); excludes.push_back("FLAT_FRAGMENT"); } switch (_texture_filtering) { case LINEAR_ANISOTROPIC: excludes.push_back("TEXTURE_SAMPLERS_NEAREST"); excludes.push_back("TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR"); break; case LINEAR: excludes.push_back("TEXTURE_SAMPLERS_NEAREST"); excludes.push_back("TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC"); break; case NEAREST_ANISOTROPIC: excludes.push_back("TEXTURE_SAMPLERS_NEAREST"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC"); break; case NEAREST: excludes.push_back("TEXTURE_SAMPLERS_NEAREST_ANISOTROPIC"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR"); excludes.push_back("TEXTURE_SAMPLERS_LINEAR_ANISOTROPIC"); break; } if (!_auto_shader_enabled) { excludes.push_back("AUTO_SHADER_UNIFORMS"); excludes.push_back("AUTO_SHADER"); } if (!_projection_enabled) { excludes.push_back("PROJECTION"); } String shader = _apply_inserts(_shader_code["displacement_buffer"], excludes); return shader; } String Terrain3DMaterial::_inject_editor_code(const String &p_shader) const { String shader = _strip_comments(p_shader); // Insert after render_mode Ref regex; regex.instantiate(); regex->compile("render_mode.*;?"); Ref match = regex->search(shader); int idx = match.is_valid() ? match->get_end() : -1; if (idx < 0) { LOG(DEBUG, "No render mode; cannot inject editor code"); return shader; } Array insert_names; // Whilst this currently does nothing, it can serve as placeholder for future refactor // when useing pre-processor headers to control shader features. //for (int i = 0; i < insert_names.size(); i++) { // String insert = _shader_code[insert_names[i]]; // shader = shader.insert(idx, "\n\n" + insert); // idx += insert.length(); //} //insert_names.clear(); // Insert before vertex() regex->compile("void\\s+vertex\\s*\\("); match = regex->search(shader); idx = match.is_valid() ? match->get_start() - 1 : -1; if (idx < 0) { LOG(DEBUG, "No void vertex(); cannot inject editor code"); return shader; } if (_terrain && _terrain->get_editor()) { insert_names.push_back("EDITOR_DECAL_SETUP"); } if (_debug_view_heightmap) { insert_names.push_back("DEBUG_HEIGHTMAP_SETUP"); } if (_show_contours) { insert_names.push_back("OVERLAY_CONTOURS_SETUP"); } // Apply pending inserts for (const String &name : insert_names) { String insert = _shader_code[name]; shader = shader.insert(idx, "\n" + insert); idx += insert.length(); } insert_names.clear(); // Insert at the end of `fragment(){ }` // Check for each nested {} pair until the closing } is found. regex->compile("void\\s*fragment\\s*\\(\\s*\\)\\s*{"); match = regex->search(shader); idx = -1; if (match.is_valid()) { int start_idx = match->get_end() - 1; int pair = 0; for (int i = start_idx; i < shader.length(); i++) { if (shader[i] == '{') { pair++; } else if (shader[i] == '}') { pair--; } if (pair == 0) { idx = i; break; } } } if (idx < 0) { LOG(DEBUG, "No ending bracket; cannot inject editor code"); return shader; } // Debug Views if (_debug_view_checkered) { insert_names.push_back("DEBUG_CHECKERED"); } if (_debug_view_grey) { insert_names.push_back("DEBUG_GREY"); } if (_debug_view_heightmap) { insert_names.push_back("DEBUG_HEIGHTMAP"); } if (_debug_view_jaggedness) { insert_names.push_back("DEBUG_JAGGEDNESS"); } if (_debug_view_autoshader) { insert_names.push_back("DEBUG_AUTOSHADER"); } if (_debug_view_control_texture) { insert_names.push_back("DEBUG_CONTROL_TEXTURE"); } if (_debug_view_control_blend) { insert_names.push_back("DEBUG_CONTROL_BLEND"); } if (_debug_view_control_angle) { insert_names.push_back("DEBUG_CONTROL_ANGLE"); } if (_debug_view_control_scale) { insert_names.push_back("DEBUG_CONTROL_SCALE"); } if (_debug_view_colormap) { insert_names.push_back("DEBUG_COLORMAP"); } if (_debug_view_roughmap) { insert_names.push_back("DEBUG_ROUGHMAP"); } // PBR views if (_pbr_view_tex_albedo) { insert_names.push_back("PBR_TEXTURE_ALBEDO"); } if (_pbr_view_tex_height) { insert_names.push_back("PBR_TEXTURE_HEIGHT"); } if (_pbr_view_tex_normal) { insert_names.push_back("PBR_TEXTURE_NORMAL"); } if (_pbr_view_tex_ao) { insert_names.push_back("PBR_TEXTURE_AO"); } if (_pbr_view_tex_rough) { insert_names.push_back("PBR_TEXTURE_ROUGHNESS"); } if (_debug_view_displacement_buffer) { insert_names.push_back("DEBUG_DISPLACEMENT_BUFFER"); } // Overlays if (_show_contours) { insert_names.push_back("OVERLAY_CONTOURS_RENDER"); } if (_show_instancer_grid) { insert_names.push_back("OVERLAY_INSTANCER_GRID"); } if (_show_vertex_grid) { insert_names.push_back("OVERLAY_VERTEX_GRID"); } // Editor Functions if (_show_navigation || (_terrain && _terrain->get_editor() && _terrain->get_editor()->get_tool() == Terrain3DEditor::NAVIGATION)) { insert_names.push_back("EDITOR_NAVIGATION"); } if (_show_region_grid || (_terrain && _terrain->get_editor() && _terrain->get_editor()->get_tool() == Terrain3DEditor::REGION)) { insert_names.push_back("EDITOR_REGION_GRID"); } if (_terrain && _terrain->get_editor()) { insert_names.push_back("EDITOR_DECAL_RENDER"); } // Apply pending inserts for (const String &name : insert_names) { String insert = _shader_code[name]; shader = shader.insert(idx, "\n" + insert); idx += insert.length(); } return shader; } void Terrain3DMaterial::_update_shader() { IS_INIT(VOID); LOG(INFO, "Updating shader"); String code; Ref regex; Ref match; regex.instantiate(); // Terrain Material if (_shader_override_enabled && _shader_override.is_valid()) { if (_shader_override->get_code().is_empty()) { _shader_override->set_code(_generate_shader_code()); } code = _shader_override->get_code(); if (!_shader_override->is_connected("changed", callable_mp(this, &Terrain3DMaterial::_update_shader))) { LOG(DEBUG, "Connecting changed signal to _update_shader()"); _shader_override->connect("changed", callable_mp(this, &Terrain3DMaterial::_update_shader)); } } else { code = _generate_shader_code(); } _shader->set_code(_inject_editor_code(code)); RS->material_set_shader(_material, get_shader_rid()); LOG(DEBUG, "Material rid: ", _material, ", shader rid: ", get_shader_rid()); // Displacement Buffer if (_terrain->get_tessellation_level() > 0) { if (_buffer_shader_override_enabled && _buffer_shader_override.is_valid()) { if (_buffer_shader_override->get_code().is_empty()) { _buffer_shader_override->set_code(_generate_buffer_shader_code()); } code = _buffer_shader_override->get_code(); if (!_buffer_shader_override->is_connected("changed", callable_mp(this, &Terrain3DMaterial::_update_shader))) { LOG(DEBUG, "Connecting changed signal to _update_shader()"); _buffer_shader_override->connect("changed", callable_mp(this, &Terrain3DMaterial::_update_shader)); } } else { code = _generate_buffer_shader_code(); } _buffer_shader->set_code(code); RS->material_set_shader(_buffer_material, get_buffer_shader_rid()); LOG(DEBUG, "Buffer Material rid: ", _buffer_material, ", buffer shader rid: ", get_buffer_shader_rid()); } else { _buffer_shader->set_code(String("shader_type spatial;")); RS->material_set_shader(_buffer_material, RID()); } // Update custom shader params in RenderingServer { // Populate _active_params List pi; _get_property_list(&pi); LOG(EXTREME, "_active_params: ", _active_params); Util::print_dict("_shader_params", _shader_params, EXTREME); } // Fetch saved shader parameters, converting textures to RIDs for (const StringName ¶m : _active_params) { Variant value = _shader_params[param]; if (value.get_type() == Variant::OBJECT) { Ref tex = value; if (tex.is_valid()) { RS->material_set_param(_material, param, tex->get_rid()); RS->material_set_param(_buffer_material, param, tex->get_rid()); } else { RS->material_set_param(_material, param, Variant()); RS->material_set_param(_buffer_material, param, Variant()); } } else { RS->material_set_param(_material, param, value); RS->material_set_param(_buffer_material, param, value); } } // Set specific shader parameters RS->material_set_param(_material, "_background_mode", _world_background); // If no noise texture, generate one if (_active_params.has("noise_texture") && RS->material_get_param(_material, "noise_texture").get_type() == Variant::NIL) { LOG(INFO, "Generating default noise_texture for shader"); Ref fnoise; fnoise.instantiate(); fnoise->set_noise_type(FastNoiseLite::TYPE_CELLULAR); fnoise->set_frequency(0.03f); fnoise->set_cellular_jitter(3.0f); fnoise->set_cellular_return_type(FastNoiseLite::RETURN_CELL_VALUE); fnoise->set_domain_warp_enabled(true); fnoise->set_domain_warp_type(FastNoiseLite::DOMAIN_WARP_SIMPLEX_REDUCED); fnoise->set_domain_warp_amplitude(50.f); fnoise->set_domain_warp_fractal_type(FastNoiseLite::DOMAIN_WARP_FRACTAL_INDEPENDENT); fnoise->set_domain_warp_fractal_lacunarity(1.5f); fnoise->set_domain_warp_fractal_gain(1.f); Ref curve; curve.instantiate(); PackedFloat32Array pfa; pfa.push_back(0.2f); pfa.push_back(1.0f); curve->set_offsets(pfa); PackedColorArray pca; pca.push_back(Color(1.f, 1.f, 1.f, 1.f)); pca.push_back(Color(0.f, 0.f, 0.f, 1.f)); curve->set_colors(pca); Ref noise_tex; noise_tex.instantiate(); noise_tex->set_seamless(true); noise_tex->set_generate_mipmaps(true); noise_tex->set_noise(fnoise); noise_tex->set_color_ramp(curve); _set("noise_texture", noise_tex); } notify_property_list_changed(); } void Terrain3DMaterial::_update_uniforms(const RID &p_material, const uint32_t p_flags) { IS_DATA_INIT(VOID); LOG(EXTREME, "Updating uniforms in shader"); Terrain3DData *data = _terrain->get_data(); PackedInt32Array region_map = data->get_region_map(); LOG(EXTREME, "region_map.size(): ", region_map.size()); if (region_map.size() != Terrain3DData::REGION_MAP_SIZE * Terrain3DData::REGION_MAP_SIZE) { LOG(ERROR, "Expected region_map.size() of ", Terrain3DData::REGION_MAP_SIZE * Terrain3DData::REGION_MAP_SIZE); return; } RS->material_set_param(p_material, "_region_map", region_map); RS->material_set_param(p_material, "_region_map_size", Terrain3DData::REGION_MAP_SIZE); if (Terrain3D::debug_level >= EXTREME) { LOG(EXTREME, "Region map"); for (int i = 0; i < region_map.size(); i++) { if (region_map[i]) { LOG(EXTREME, "Region id: ", region_map[i], " array index: ", i); } } } TypedArray region_locations = data->get_region_locations(); LOG(EXTREME, "Region_locations size: ", region_locations.size(), " ", region_locations); RS->material_set_param(p_material, "_region_locations", region_locations); real_t region_size = real_t(_terrain->get_region_size()); LOG(EXTREME, "Setting region size in material: ", region_size); RS->material_set_param(p_material, "_region_size", region_size); RS->material_set_param(p_material, "_region_texel_size", 1.0f / region_size); if (p_flags & REGION_ARRAYS) { RS->material_set_param(p_material, "_height_maps", data->get_height_maps_rid()); RS->material_set_param(p_material, "_control_maps", data->get_control_maps_rid()); RS->material_set_param(p_material, "_color_maps", data->get_color_maps_rid()); LOG(EXTREME, "Height map RID: ", data->get_height_maps_rid()); LOG(EXTREME, "Control map RID: ", data->get_control_maps_rid()); LOG(EXTREME, "Color map RID: ", data->get_color_maps_rid()); } real_t spacing = _terrain->get_vertex_spacing(); LOG(EXTREME, "Setting vertex spacing in material: ", spacing); RS->material_set_param(p_material, "_vertex_spacing", spacing); RS->material_set_param(p_material, "_vertex_density", 1.0f / spacing); real_t mesh_size = real_t(_terrain->get_mesh_size()); RS->material_set_param(p_material, "_mesh_size", mesh_size); real_t tessellation_level = real_t(_terrain->get_tessellation_level()); real_t subdiv = pow(2.f, tessellation_level); RS->material_set_param(p_material, "_subdiv", subdiv); RS->material_set_param(p_material, "_tessellation_level", tessellation_level); RS->material_set_param(p_material, "_displacement_scale", _displacement_scale); RS->material_set_param(p_material, "_displacement_sharpness", _displacement_sharpness); Ref asset_list = _terrain->get_assets(); LOG(INFO, "Updating texture arrays in shader"); if (asset_list.is_null() || !asset_list->is_initialized()) { LOG(INFO, "Asset list is not initialized"); return; } if (p_flags & TEXTURE_ARRAYS) { RS->material_set_param(p_material, "_texture_array_albedo", asset_list->get_albedo_array_rid()); RS->material_set_param(p_material, "_texture_array_normal", asset_list->get_normal_array_rid()); } RS->material_set_param(p_material, "_texture_color_array", asset_list->get_texture_colors()); RS->material_set_param(p_material, "_texture_normal_depth_array", asset_list->get_texture_normal_depths()); RS->material_set_param(p_material, "_texture_ao_strength_array", asset_list->get_texture_ao_strengths()); RS->material_set_param(p_material, "_texture_ao_affect_array", asset_list->get_texture_ao_light_affects()); RS->material_set_param(p_material, "_texture_roughness_mod_array", asset_list->get_texture_roughness_mods()); RS->material_set_param(p_material, "_texture_uv_scale_array", asset_list->get_texture_uv_scales()); RS->material_set_param(p_material, "_texture_detile_array", asset_list->get_texture_detiles()); RS->material_set_param(p_material, "_texture_displacement_array", asset_list->get_texture_displacements()); // Enable checkered view if texture_count is 0, disable if not if (asset_list->get_generated_array_size() == 0) { if (_debug_view_checkered == false) { set_show_checkered(true); LOG(DEBUG, "No textures, enabling checkered view"); } } else { set_show_checkered(false); LOG(DEBUG, "Texture count >0: ", asset_list->get_generated_array_size(), ", disabling checkered view"); } } void Terrain3DMaterial::_set_shader_parameters(const Dictionary &p_dict) { SET_IF_DIFF(_shader_params, p_dict); LOG(INFO, "Setting shader params dictionary: ", p_dict.size()); } /////////////////////////// // Public Functions /////////////////////////// // This function serves as the constructor which is initialized by the class Terrain3D. // Godot likes to create resource objects at startup, so this prevents it from creating // uninitialized materials. void Terrain3DMaterial::initialize(Terrain3D *p_terrain) { if (p_terrain) { _terrain = p_terrain; } else { LOG(ERROR, "Initialization failed, p_terrain is null"); return; } LOG(INFO, "Initializing material"); _preload_shaders(); if (!_material.is_valid()) { _material = RS->material_create(); } if (!_buffer_material.is_valid()) { _buffer_material = RS->material_create(); } _shader.instantiate(); _buffer_shader.instantiate(); update(FULL_REBUILD); } void Terrain3DMaterial::uninitialize() { LOG(INFO, "Uninitializing material"); _terrain = nullptr; } void Terrain3DMaterial::destroy() { LOG(INFO, "Destroying material"); _terrain = nullptr; _shader.unref(); _buffer_shader.unref(); _shader_code.clear(); _active_params.clear(); _shader_params.clear(); if (_material.is_valid()) { RS->free_rid(_material); _material = RID(); } if (_buffer_material.is_valid()) { RS->free_rid(_buffer_material); _buffer_material = RID(); } } void Terrain3DMaterial::update(uint32_t p_flags) { if (p_flags & FULL_REBUILD) { _update_shader(); } _update_uniforms(_material, p_flags); IS_INIT(VOID); if (_terrain->get_tessellation_level() > 0) { _update_uniforms(_buffer_material, p_flags); // Snap to update buffer _terrain->snap(); } } void Terrain3DMaterial::set_displacement_scale(const real_t p_displacement_scale) { SET_IF_DIFF(_displacement_scale, CLAMP(p_displacement_scale, 0.f, 2.f)); LOG(INFO, "Setting displacement scale: ", p_displacement_scale); update(); } void Terrain3DMaterial::set_displacement_sharpness(const real_t p_displacement_sharpness) { SET_IF_DIFF(_displacement_sharpness, CLAMP(p_displacement_sharpness, 0.f, 1.f)); LOG(INFO, "Setting displacement sharpness: ", p_displacement_sharpness); update(); if (_terrain) { _terrain->snap(); } } void Terrain3DMaterial::set_world_background(const WorldBackground p_background) { SET_IF_DIFF(_world_background, p_background); LOG(INFO, "Enable world background: ", p_background); _update_shader(); } void Terrain3DMaterial::set_texture_filtering(const TextureFiltering p_filtering) { SET_IF_DIFF(_texture_filtering, p_filtering); LOG(INFO, "Setting texture filtering: ", p_filtering); _update_shader(); } void Terrain3DMaterial::set_auto_shader_enabled(const bool p_enabled) { SET_IF_DIFF(_auto_shader_enabled, p_enabled); LOG(INFO, "Enable auto shader: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_dual_scaling_enabled(const bool p_enabled) { SET_IF_DIFF(_dual_scaling_enabled, p_enabled); LOG(INFO, "Enable dual scaling: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_macro_variation_enabled(const bool p_enabled) { SET_IF_DIFF(_macro_variation_enabled, p_enabled); LOG(INFO, "Enable macro variation: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_projection_enabled(const bool p_enabled) { SET_IF_DIFF(_projection_enabled, p_enabled); LOG(INFO, "Enable projection: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_shader_override_enabled(const bool p_enabled) { SET_IF_DIFF(_shader_override_enabled, p_enabled); LOG(INFO, "Enable shader override: ", p_enabled); if (_shader_override_enabled && _shader_override.is_null()) { LOG(DEBUG, "Instantiating new _shader_override"); _shader_override.instantiate(); } _update_shader(); } void Terrain3DMaterial::set_shader_override(const Ref &p_shader) { SET_IF_DIFF(_shader_override, p_shader); LOG(INFO, "Setting override shader"); _update_shader(); } void Terrain3DMaterial::set_buffer_shader_override_enabled(const bool p_enabled) { SET_IF_DIFF(_buffer_shader_override_enabled, p_enabled); LOG(INFO, "Enable shader override: ", p_enabled); if (_buffer_shader_override_enabled && _buffer_shader_override.is_null()) { LOG(DEBUG, "Instantiating new _shader_override"); _buffer_shader_override.instantiate(); } _update_shader(); } void Terrain3DMaterial::set_buffer_shader_override(const Ref &p_shader) { SET_IF_DIFF(_buffer_shader_override, p_shader); LOG(INFO, "Setting override shader"); _update_shader(); } void Terrain3DMaterial::set_shader_param(const StringName &p_name, const Variant &p_value) { LOG(INFO, "Setting shader parameter: ", p_name); _set(p_name, p_value); } Variant Terrain3DMaterial::get_shader_param(const StringName &p_name) const { LOG(INFO, "Getting shader parameter: ", p_name); Variant value; _get(p_name, value); return value; } void Terrain3DMaterial::set_output_albedo_enabled(const bool p_enabled) { SET_IF_DIFF(_output_albedo_enabled, p_enabled); LOG(INFO, "Enable PBR output albedo: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_output_roughness_enabled(const bool p_enabled) { SET_IF_DIFF(_output_roughness_enabled, p_enabled); LOG(INFO, "Enable PBR output roughness: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_output_normal_map_enabled(const bool p_enabled) { SET_IF_DIFF(_output_normal_map_enabled, p_enabled); LOG(INFO, "Enable PBR output normal map: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_output_ambient_occlusion_enabled(const bool p_enabled) { SET_IF_DIFF(_output_ambient_occlusion_enabled, p_enabled); LOG(INFO, "Enable PBR output ambient occlusion: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_region_grid(const bool p_enabled) { SET_IF_DIFF(_show_region_grid, p_enabled); LOG(INFO, "Enable show_region_grid: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_instancer_grid(const bool p_enabled) { SET_IF_DIFF(_show_instancer_grid, p_enabled); LOG(INFO, "Enable show_instancer_grid: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_vertex_grid(const bool p_enabled) { SET_IF_DIFF(_show_vertex_grid, p_enabled); LOG(INFO, "Enable show_vertex_grid: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_contours(const bool p_enabled) { SET_IF_DIFF(_show_contours, p_enabled); LOG(INFO, "Enable show_contours: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_navigation(const bool p_enabled) { SET_IF_DIFF(_show_navigation, p_enabled); LOG(INFO, "Enable show_navigation: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_checkered(const bool p_enabled) { SET_IF_DIFF(_debug_view_checkered, p_enabled); LOG(INFO, "Enable set_show_checkered: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_grey(const bool p_enabled) { SET_IF_DIFF(_debug_view_grey, p_enabled); LOG(INFO, "Enable show_grey: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_heightmap(const bool p_enabled) { SET_IF_DIFF(_debug_view_heightmap, p_enabled); LOG(INFO, "Enable show_heightmap: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_jaggedness(const bool p_enabled) { SET_IF_DIFF(_debug_view_jaggedness, p_enabled); LOG(INFO, "Enable show_jaggedness: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_autoshader(const bool p_enabled) { SET_IF_DIFF(_debug_view_autoshader, p_enabled); LOG(INFO, "Enable show_autoshader: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_control_texture(const bool p_enabled) { SET_IF_DIFF(_debug_view_control_texture, p_enabled); LOG(INFO, "Enable show_control_texture: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_control_blend(const bool p_enabled) { SET_IF_DIFF(_debug_view_control_blend, p_enabled); LOG(INFO, "Enable show_control_blend: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_control_angle(const bool p_enabled) { SET_IF_DIFF(_debug_view_control_angle, p_enabled); LOG(INFO, "Enable show_control_angle: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_control_scale(const bool p_enabled) { SET_IF_DIFF(_debug_view_control_scale, p_enabled); LOG(INFO, "Enable show_control_scale: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_colormap(const bool p_enabled) { SET_IF_DIFF(_debug_view_colormap, p_enabled); LOG(INFO, "Enable show_colormap: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_roughmap(const bool p_enabled) { SET_IF_DIFF(_debug_view_roughmap, p_enabled); LOG(INFO, "Enable show_roughmap: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_texture_albedo(const bool p_enabled) { SET_IF_DIFF(_pbr_view_tex_albedo, p_enabled); LOG(INFO, "Enable show_texture_albedo: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_texture_height(const bool p_enabled) { SET_IF_DIFF(_pbr_view_tex_height, p_enabled); LOG(INFO, "Enable show_texture_height: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_texture_normal(const bool p_enabled) { SET_IF_DIFF(_pbr_view_tex_normal, p_enabled); LOG(INFO, "Enable show_texture_normal: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_texture_rough(const bool p_enabled) { SET_IF_DIFF(_pbr_view_tex_rough, p_enabled); LOG(INFO, "Enable show_texture_rough: ", p_enabled); _update_shader(); } void Terrain3DMaterial::set_show_displacement_buffer(const bool p_enabled) { LOG(INFO, "Enable show_texture_rough: ", p_enabled); _debug_view_displacement_buffer = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_texture_ao(const bool p_enabled) { SET_IF_DIFF(_pbr_view_tex_ao, p_enabled); LOG(INFO, "Enable show_texture_ao: ", p_enabled); _update_shader(); } Error Terrain3DMaterial::save(const String &p_path) { if (p_path.is_empty() && get_path().is_empty()) { return ERR_FILE_NOT_FOUND; } if (!p_path.is_empty()) { LOG(DEBUG, "Setting file path to ", p_path); take_over_path(p_path); } LOG(DEBUG, "Generating parameter list from shaders"); // Get shader parameters from default shader (eg world_noise) Array param_list; param_list = RS->get_shader_parameter_list(get_shader_rid()); // Get shader parameters from custom shader if present if (_shader_override.is_valid()) { param_list.append_array(_shader_override->get_shader_uniform_list(true)); } if (_buffer_shader_override.is_valid()) { // Get shader parameters from custom buffer shader param_list.append_array(_buffer_shader_override->get_shader_uniform_list(true)); } else { if (_terrain && _terrain->get_tessellation_level() > 0) { // Get shader parameters from default buffer shader param_list.append_array(RS->get_shader_parameter_list(get_buffer_shader_rid())); } } // Remove saved shader params that don't exist in either shader Array keys = _shader_params.keys(); for (const StringName &name : keys) { bool has = false; for (const Dictionary &dict : param_list) { StringName dname = dict["name"]; if (name == dname) { has = true; break; } } if (!has) { LOG(DEBUG, "'", name, "' not found in shader parameters. Removing from dictionary."); _shader_params.erase(name); } } // Save to external resource file if specified Error err = OK; String path = get_path(); if (path.get_extension() == "tres" || path.get_extension() == "res") { LOG(DEBUG, "Attempting to save external file: " + path); err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); if (err == OK) { LOG(INFO, "File saved successfully: ", path); } else { LOG(ERROR, "Cannot save file: ", path, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); } } return err; } /////////////////////////// // Protected Functions /////////////////////////// // Add shader uniforms to properties. Hides uniforms that begin with _ void Terrain3DMaterial::_get_property_list(List *p_list) const { Resource::_get_property_list(p_list); IS_INIT(VOID); Array param_list; if (_shader_override_enabled && _shader_override.is_valid()) { // Get shader parameters from custom shader param_list = _shader_override->get_shader_uniform_list(true); } else { // Get shader parameters from default shader (eg world_noise) param_list = RS->get_shader_parameter_list(get_shader_rid()); } int buffer_param = param_list.size(); if (_buffer_shader_override_enabled && _buffer_shader_override.is_valid()) { // Get shader parameters from custom shader param_list.append_array(_buffer_shader_override->get_shader_uniform_list(true)); } else { if (_terrain->get_tessellation_level() > 0) { // Get shader parameters from default shader (eg world_noise) param_list.append_array(RS->get_shader_parameter_list(get_buffer_shader_rid())); } } TypedArray new_active_params; Dictionary grouped_params; StringName current_group = StringName("shader_uniforms.general"); grouped_params[current_group] = Array(); for (int i = 0; i < param_list.size(); i++) { Dictionary dict = param_list[i]; StringName name = dict["name"]; // An empty name indicates a group being closed, reset to the "general" group. if (name.is_empty() && i < buffer_param) { current_group = StringName("shader_uniforms.general"); } // Filter out private uniforms that start with _ and nulls if (!name.begins_with("_") && !name.is_empty()) { uint64_t use = dict["usage"]; if (use == PROPERTY_USAGE_GROUP) { PackedStringArray split_name = name.split("::"); dict["name"] = split_name[MAX(split_name.size() - 1, 0)].capitalize(); // Ensure sub groups are batched with their parent group current_group = split_name[0].capitalize(); dict["usage"] = name.contains("::") ? PROPERTY_USAGE_SUBGROUP : PROPERTY_USAGE_GROUP; } else { // Filter out duplicate non-groups entries from displacement buffer shader if (new_active_params.has(name)) { continue; } dict["usage"] = PROPERTY_USAGE_EDITOR; } // Filter out extraneous parameters from the inspector if they are not present in the terrain shader. if (i >= buffer_param && (!new_active_params.has(name) && use != PROPERTY_USAGE_GROUP) && !current_group.contains("Displacement")) { LOG(INFO, "Displacement buffer has active parameter: ", name, " not present in terrain shader."); continue; } // Write dict to grouped arrays to ensure property list groups are populated contigiously. Array group; if (grouped_params.has(current_group)) { group = grouped_params[current_group]; } else { grouped_params[current_group] = Array(); group = grouped_params[current_group]; } group.push_back(dict); grouped_params[current_group] = group; // Populate list of public parameters for current shader new_active_params.push_back(name); // Store this param in a dictionary that is saved in the resource file // Initially set with default value // Also acts as a cache for _get // Property usage above set to EDITOR so it won't be redundantly saved, // which won't get loaded since there is no bound property. if (!_shader_params.has(name)) { _property_get_revert(name, _shader_params[name]); } } } _active_params = new_active_params; // Populate Godot's property list Array keys = grouped_params.keys(); for (int i = 0; i < keys.size(); i++) { StringName key = keys[i]; Array group = grouped_params[key]; for (int k = 0; k < group.size(); k++) { Dictionary dict = group[k]; PropertyInfo pi; pi.class_name = dict["class_name"]; pi.name = dict["name"]; pi.type = Variant::Type(int(dict["type"])); pi.hint = dict["hint"]; pi.hint_string = dict["hint_string"]; pi.usage = dict["usage"]; p_list->push_back(pi); } } return; } // Flag uniforms with non-default values // This is called 10x more than the others, so be efficient bool Terrain3DMaterial::_property_can_revert(const StringName &p_name) const { IS_INIT_COND(!_active_params.has(p_name), Resource::_property_can_revert(p_name)); Variant default_value = RS->shader_get_parameter_default(get_shader_rid(), p_name); Variant current_value = RS->material_get_param(_material, p_name); return default_value != current_value; } // Provide uniform default values in r_property bool Terrain3DMaterial::_property_get_revert(const StringName &p_name, Variant &r_property) const { IS_INIT_COND(!_active_params.has(p_name), Resource::_property_get_revert(p_name, r_property)); Variant prop = RS->shader_get_parameter_default(get_shader_rid(), p_name); // If the default is nil, check the buffer shader for the default value. if (prop.get_type() == Variant::NIL) { prop = RS->shader_get_parameter_default(get_buffer_shader_rid(), p_name); } r_property = prop; return true; } bool Terrain3DMaterial::_set(const StringName &p_name, const Variant &p_property) { IS_INIT_COND(!_active_params.has(p_name), Resource::_set(p_name, p_property)); if (p_property.get_type() == Variant::NIL) { RS->material_set_param(_material, p_name, Variant()); _shader_params.erase(p_name); return true; } // If value is an object, assume a Texture. RS only wants RIDs, but // Inspector wants the object, so set the RID and save the latter for _get if (p_property.get_type() == Variant::OBJECT) { Ref tex = p_property; if (tex.is_valid()) { _shader_params[p_name] = tex; RS->material_set_param(_material, p_name, tex->get_rid()); RS->material_set_param(_buffer_material, p_name, tex->get_rid()); } else { RS->material_set_param(_material, p_name, Variant()); RS->material_set_param(_buffer_material, p_name, Variant()); } } else { _shader_params[p_name] = p_property; RS->material_set_param(_material, p_name, p_property); RS->material_set_param(_buffer_material, p_name, p_property); } return true; } // This is called 200x more than the others, every second the material is open in the // inspector, so be efficient bool Terrain3DMaterial::_get(const StringName &p_name, Variant &r_property) const { IS_INIT_COND(!_active_params.has(p_name), Resource::_get(p_name, r_property)); r_property = RS->material_get_param(_material, p_name); // Material server only has RIDs, but inspector needs objects for things like Textures // So if its an RID, return the object if (r_property.get_type() == Variant::RID && _shader_params.has(p_name)) { r_property = _shader_params[p_name]; } return true; } void Terrain3DMaterial::_bind_methods() { BIND_ENUM_CONSTANT(NONE); BIND_ENUM_CONSTANT(FLAT); BIND_ENUM_CONSTANT(NOISE); BIND_ENUM_CONSTANT(LINEAR_ANISOTROPIC); BIND_ENUM_CONSTANT(LINEAR); BIND_ENUM_CONSTANT(NEAREST_ANISOTROPIC); BIND_ENUM_CONSTANT(NEAREST); BIND_ENUM_CONSTANT(UNIFORMS_ONLY); BIND_ENUM_CONSTANT(TEXTURE_ARRAYS); BIND_ENUM_CONSTANT(REGION_ARRAYS); BIND_ENUM_CONSTANT(UPDATE_ARRAYS); BIND_ENUM_CONSTANT(FULL_REBUILD); // Private ClassDB::bind_method(D_METHOD("_set_shader_parameters", "dict"), &Terrain3DMaterial::_set_shader_parameters); ClassDB::bind_method(D_METHOD("_get_shader_parameters"), &Terrain3DMaterial::_get_shader_parameters); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_shader_parameters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_shader_parameters", "_get_shader_parameters"); // Public ClassDB::bind_method(D_METHOD("update", "flags"), &Terrain3DMaterial::update, DEFVAL(Terrain3DMaterial::UNIFORMS_ONLY)); ClassDB::bind_method(D_METHOD("get_material_rid"), &Terrain3DMaterial::get_material_rid); ClassDB::bind_method(D_METHOD("get_shader_rid"), &Terrain3DMaterial::get_shader_rid); ClassDB::bind_method(D_METHOD("get_buffer_material_rid"), &Terrain3DMaterial::get_buffer_material_rid); ClassDB::bind_method(D_METHOD("get_buffer_shader_rid"), &Terrain3DMaterial::get_buffer_shader_rid); ClassDB::bind_method(D_METHOD("set_world_background", "background"), &Terrain3DMaterial::set_world_background); ClassDB::bind_method(D_METHOD("get_world_background"), &Terrain3DMaterial::get_world_background); ClassDB::bind_method(D_METHOD("set_texture_filtering", "filtering"), &Terrain3DMaterial::set_texture_filtering); ClassDB::bind_method(D_METHOD("get_texture_filtering"), &Terrain3DMaterial::get_texture_filtering); ClassDB::bind_method(D_METHOD("set_auto_shader_enabled", "enabled"), &Terrain3DMaterial::set_auto_shader_enabled); ClassDB::bind_method(D_METHOD("get_auto_shader_enabled"), &Terrain3DMaterial::get_auto_shader_enabled); ClassDB::bind_method(D_METHOD("set_dual_scaling_enabled", "enabled"), &Terrain3DMaterial::set_dual_scaling_enabled); ClassDB::bind_method(D_METHOD("get_dual_scaling_enabled"), &Terrain3DMaterial::get_dual_scaling_enabled); ClassDB::bind_method(D_METHOD("set_macro_variation_enabled", "enabled"), &Terrain3DMaterial::set_macro_variation_enabled); ClassDB::bind_method(D_METHOD("get_macro_variation_enabled"), &Terrain3DMaterial::get_macro_variation_enabled); ClassDB::bind_method(D_METHOD("set_projection_enabled", "enabled"), &Terrain3DMaterial::set_projection_enabled); ClassDB::bind_method(D_METHOD("get_projection_enabled"), &Terrain3DMaterial::get_projection_enabled); ClassDB::bind_method(D_METHOD("set_shader_override_enabled", "enabled"), &Terrain3DMaterial::set_shader_override_enabled); ClassDB::bind_method(D_METHOD("is_shader_override_enabled"), &Terrain3DMaterial::is_shader_override_enabled); ClassDB::bind_method(D_METHOD("set_shader_override", "shader"), &Terrain3DMaterial::set_shader_override); ClassDB::bind_method(D_METHOD("get_shader_override"), &Terrain3DMaterial::get_shader_override); ClassDB::bind_method(D_METHOD("set_buffer_shader_override_enabled", "enabled"), &Terrain3DMaterial::set_buffer_shader_override_enabled); ClassDB::bind_method(D_METHOD("is_buffer_shader_override_enabled"), &Terrain3DMaterial::is_buffer_shader_override_enabled); ClassDB::bind_method(D_METHOD("set_buffer_shader_override", "shader"), &Terrain3DMaterial::set_buffer_shader_override); ClassDB::bind_method(D_METHOD("get_buffer_shader_override"), &Terrain3DMaterial::get_buffer_shader_override); ClassDB::bind_method(D_METHOD("set_displacement_scale", "scale"), &Terrain3DMaterial::set_displacement_scale); ClassDB::bind_method(D_METHOD("get_displacement_scale"), &Terrain3DMaterial::get_displacement_scale); ClassDB::bind_method(D_METHOD("set_displacement_sharpness", "sharpness"), &Terrain3DMaterial::set_displacement_sharpness); ClassDB::bind_method(D_METHOD("get_displacement_sharpness"), &Terrain3DMaterial::get_displacement_sharpness); ClassDB::bind_method(D_METHOD("set_shader_param", "name", "value"), &Terrain3DMaterial::set_shader_param); ClassDB::bind_method(D_METHOD("get_shader_param", "name"), &Terrain3DMaterial::get_shader_param); // PBR output ClassDB::bind_method(D_METHOD("set_output_albedo_enabled", "enabled"), &Terrain3DMaterial::set_output_albedo_enabled); ClassDB::bind_method(D_METHOD("get_output_albedo_enabled"), &Terrain3DMaterial::get_output_albedo_enabled); ClassDB::bind_method(D_METHOD("set_output_roughness_enabled", "enabled"), &Terrain3DMaterial::set_output_roughness_enabled); ClassDB::bind_method(D_METHOD("get_output_roughness_enabled"), &Terrain3DMaterial::get_output_roughness_enabled); ClassDB::bind_method(D_METHOD("set_output_normal_map_enabled", "enabled"), &Terrain3DMaterial::set_output_normal_map_enabled); ClassDB::bind_method(D_METHOD("get_output_normal_map_enabled"), &Terrain3DMaterial::get_output_normal_map_enabled); ClassDB::bind_method(D_METHOD("set_output_ambient_occlusion_enabled", "enabled"), &Terrain3DMaterial::set_output_ambient_occlusion_enabled); ClassDB::bind_method(D_METHOD("get_output_ambient_occlusion_enabled"), &Terrain3DMaterial::get_output_ambient_occlusion_enabled); // Overlays ClassDB::bind_method(D_METHOD("set_show_region_grid", "enabled"), &Terrain3DMaterial::set_show_region_grid); ClassDB::bind_method(D_METHOD("get_show_region_grid"), &Terrain3DMaterial::get_show_region_grid); ClassDB::bind_method(D_METHOD("set_show_instancer_grid", "enabled"), &Terrain3DMaterial::set_show_instancer_grid); ClassDB::bind_method(D_METHOD("get_show_instancer_grid"), &Terrain3DMaterial::get_show_instancer_grid); ClassDB::bind_method(D_METHOD("set_show_vertex_grid", "enabled"), &Terrain3DMaterial::set_show_vertex_grid); ClassDB::bind_method(D_METHOD("get_show_vertex_grid"), &Terrain3DMaterial::get_show_vertex_grid); ClassDB::bind_method(D_METHOD("set_show_contours", "enabled"), &Terrain3DMaterial::set_show_contours); ClassDB::bind_method(D_METHOD("get_show_contours"), &Terrain3DMaterial::get_show_contours); ClassDB::bind_method(D_METHOD("set_show_navigation", "enabled"), &Terrain3DMaterial::set_show_navigation); ClassDB::bind_method(D_METHOD("get_show_navigation"), &Terrain3DMaterial::get_show_navigation); // Debug Views ClassDB::bind_method(D_METHOD("set_show_checkered", "enabled"), &Terrain3DMaterial::set_show_checkered); ClassDB::bind_method(D_METHOD("get_show_checkered"), &Terrain3DMaterial::get_show_checkered); ClassDB::bind_method(D_METHOD("set_show_grey", "enabled"), &Terrain3DMaterial::set_show_grey); ClassDB::bind_method(D_METHOD("get_show_grey"), &Terrain3DMaterial::get_show_grey); ClassDB::bind_method(D_METHOD("set_show_heightmap", "enabled"), &Terrain3DMaterial::set_show_heightmap); ClassDB::bind_method(D_METHOD("get_show_heightmap"), &Terrain3DMaterial::get_show_heightmap); ClassDB::bind_method(D_METHOD("set_show_jaggedness", "enabled"), &Terrain3DMaterial::set_show_jaggedness); ClassDB::bind_method(D_METHOD("get_show_jaggedness"), &Terrain3DMaterial::get_show_jaggedness); ClassDB::bind_method(D_METHOD("set_show_autoshader", "enabled"), &Terrain3DMaterial::set_show_autoshader); ClassDB::bind_method(D_METHOD("get_show_autoshader"), &Terrain3DMaterial::get_show_autoshader); ClassDB::bind_method(D_METHOD("set_show_control_texture", "enabled"), &Terrain3DMaterial::set_show_control_texture); ClassDB::bind_method(D_METHOD("get_show_control_texture"), &Terrain3DMaterial::get_show_control_texture); ClassDB::bind_method(D_METHOD("set_show_control_blend", "enabled"), &Terrain3DMaterial::set_show_control_blend); ClassDB::bind_method(D_METHOD("get_show_control_blend"), &Terrain3DMaterial::get_show_control_blend); ClassDB::bind_method(D_METHOD("set_show_control_angle", "enabled"), &Terrain3DMaterial::set_show_control_angle); ClassDB::bind_method(D_METHOD("get_show_control_angle"), &Terrain3DMaterial::get_show_control_angle); ClassDB::bind_method(D_METHOD("set_show_control_scale", "enabled"), &Terrain3DMaterial::set_show_control_scale); ClassDB::bind_method(D_METHOD("get_show_control_scale"), &Terrain3DMaterial::get_show_control_scale); ClassDB::bind_method(D_METHOD("set_show_colormap", "enabled"), &Terrain3DMaterial::set_show_colormap); ClassDB::bind_method(D_METHOD("get_show_colormap"), &Terrain3DMaterial::get_show_colormap); ClassDB::bind_method(D_METHOD("set_show_roughmap", "enabled"), &Terrain3DMaterial::set_show_roughmap); ClassDB::bind_method(D_METHOD("get_show_roughmap"), &Terrain3DMaterial::get_show_roughmap); // PBR Views ClassDB::bind_method(D_METHOD("set_show_texture_albedo", "enabled"), &Terrain3DMaterial::set_show_texture_albedo); ClassDB::bind_method(D_METHOD("get_show_texture_albedo"), &Terrain3DMaterial::get_show_texture_albedo); ClassDB::bind_method(D_METHOD("set_show_texture_height", "enabled"), &Terrain3DMaterial::set_show_texture_height); ClassDB::bind_method(D_METHOD("get_show_texture_height"), &Terrain3DMaterial::get_show_texture_height); ClassDB::bind_method(D_METHOD("set_show_texture_normal", "enabled"), &Terrain3DMaterial::set_show_texture_normal); ClassDB::bind_method(D_METHOD("get_show_texture_normal"), &Terrain3DMaterial::get_show_texture_normal); ClassDB::bind_method(D_METHOD("set_show_texture_ao", "enabled"), &Terrain3DMaterial::set_show_texture_ao); ClassDB::bind_method(D_METHOD("get_show_texture_ao"), &Terrain3DMaterial::get_show_texture_ao); ClassDB::bind_method(D_METHOD("set_show_texture_rough", "enabled"), &Terrain3DMaterial::set_show_texture_rough); ClassDB::bind_method(D_METHOD("get_show_texture_rough"), &Terrain3DMaterial::get_show_texture_rough); ClassDB::bind_method(D_METHOD("set_show_displacement_buffer", "enabled"), &Terrain3DMaterial::set_show_displacement_buffer); ClassDB::bind_method(D_METHOD("get_show_displacement_buffer"), &Terrain3DMaterial::get_show_displacement_buffer); ClassDB::bind_method(D_METHOD("save", "path"), &Terrain3DMaterial::save, DEFVAL("")); // These must be different from the names of uniform groups ADD_PROPERTY(PropertyInfo(Variant::INT, "world_background", PROPERTY_HINT_ENUM, "None,Flat,Noise"), "set_world_background", "get_world_background"); ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filtering", PROPERTY_HINT_ENUM, "Linear Anisotropic,Linear,Nearest Anisotropic,Nearest"), "set_texture_filtering", "get_texture_filtering"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_shader_enabled"), "set_auto_shader_enabled", "get_auto_shader_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dual_scaling_enabled"), "set_dual_scaling_enabled", "get_dual_scaling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "macro_variation_enabled"), "set_macro_variation_enabled", "get_macro_variation_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "projection_enabled"), "set_projection_enabled", "get_projection_enabled"); ADD_GROUP("PBR Output", "output_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_albedo"), "set_output_albedo_enabled", "get_output_albedo_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_roughness"), "set_output_roughness_enabled", "get_output_roughness_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_normal_map"), "set_output_normal_map_enabled", "get_output_normal_map_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_ambient_occlusion"), "set_output_ambient_occlusion_enabled", "get_output_ambient_occlusion_enabled"); ADD_GROUP("Custom Shader", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shader_override_enabled"), "set_shader_override_enabled", "is_shader_override_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shader_override", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_shader_override", "get_shader_override"); // Hidden in Material, aliased in Terrain3D //ADD_GROUP("Overlays", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_region_grid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_region_grid", "get_show_region_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_instancer_grid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_instancer_grid", "get_show_instancer_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_vertex_grid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_vertex_grid", "get_show_vertex_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_contours", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_contours", "get_show_contours"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_navigation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_navigation", "get_show_navigation"); // Hidden in Material, aliased in Terrain3D // Displacement settings ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_displacement_scale", "get_displacement_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_sharpness", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_displacement_sharpness", "get_displacement_sharpness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "buffer_shader_override_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_buffer_shader_override_enabled", "is_buffer_shader_override_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "buffer_shader_override", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_buffer_shader_override", "get_buffer_shader_override"); //ADD_GROUP("Debug Views", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_checkered", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_checkered", "get_show_checkered"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grey", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_grey", "get_show_grey"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_heightmap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_heightmap", "get_show_heightmap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_jaggedness", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_jaggedness", "get_show_jaggedness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_autoshader", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_autoshader", "get_show_autoshader"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_texture", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_control_texture", "get_show_control_texture"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_blend", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_control_blend", "get_show_control_blend"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_angle", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_control_angle", "get_show_control_angle"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_control_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_control_scale", "get_show_control_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_colormap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_colormap", "get_show_colormap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_roughmap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_roughmap", "get_show_roughmap"); // Hidden in Material, aliased in Terrain3D //ADD_SUBGROUP("PBR Views", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_albedo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_texture_albedo", "get_show_texture_albedo"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_height", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_texture_height", "get_show_texture_height"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_normal", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_texture_normal", "get_show_texture_normal"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_ao", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_texture_ao", "get_show_texture_ao"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_rough", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_texture_rough", "get_show_texture_rough"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_displacement_buffer", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_show_displacement_buffer", "get_show_displacement_buffer"); } ================================================ FILE: src/terrain_3d_material.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_MATERIAL_CLASS_H #define TERRAIN3D_MATERIAL_CLASS_H #include #include "constants.h" #include "generated_texture.h" class Terrain3D; class Terrain3DMaterial : public Resource { GDCLASS(Terrain3DMaterial, Resource); CLASS_NAME(); public: // Constants enum WorldBackground { NONE, FLAT, NOISE, }; enum TextureFiltering { LINEAR_ANISOTROPIC, LINEAR, NEAREST_ANISOTROPIC, NEAREST, }; enum UpdateFlags { UNIFORMS_ONLY = 0, TEXTURE_ARRAYS = 1 << 0, REGION_ARRAYS = 1 << 1, UPDATE_ARRAYS = TEXTURE_ARRAYS | REGION_ARRAYS, FULL_REBUILD = (1 << 2) | UPDATE_ARRAYS, }; private: Terrain3D *_terrain = nullptr; RID _material; Ref _shader; // Active shader Dictionary _shader_code; // All loaded shader and INSERT code bool _shader_override_enabled = false; Ref _shader_override; // User's shader we copy code from mutable TypedArray _active_params; // All shader params in the current shader mutable Dictionary _shader_params; // Public shader params saved to disk RID _buffer_material; Ref _buffer_shader; // Active buffer shader bool _buffer_shader_override_enabled = false; Ref _buffer_shader_override; // User's shader we copy code from real_t _displacement_scale = 1.0f; real_t _displacement_sharpness = 0.5f; // Material Features WorldBackground _world_background = FLAT; TextureFiltering _texture_filtering = LINEAR_ANISOTROPIC; bool _dual_scaling_enabled = false; bool _auto_shader_enabled = false; bool _macro_variation_enabled = false; bool _projection_enabled = false; // PBR Outputs bool _output_albedo_enabled = true; bool _output_roughness_enabled = true; bool _output_normal_map_enabled = true; bool _output_ambient_occlusion_enabled = true; // Overlays bool _show_region_grid = false; bool _show_instancer_grid = false; bool _show_vertex_grid = false; bool _show_contours = false; bool _show_navigation = false; // Debug Views bool _debug_view_checkered = false; bool _debug_view_grey = false; bool _debug_view_heightmap = false; bool _debug_view_jaggedness = false; bool _debug_view_autoshader = false; bool _debug_view_control_texture = false; bool _debug_view_control_blend = false; bool _debug_view_control_angle = false; bool _debug_view_control_scale = false; bool _debug_view_holes = false; bool _debug_view_colormap = false; bool _debug_view_roughmap = false; bool _debug_view_displacement_buffer = false; // PBR Views bool _pbr_view_tex_albedo = false; bool _pbr_view_tex_height = false; bool _pbr_view_tex_normal = false; bool _pbr_view_tex_ao = false; bool _pbr_view_tex_rough = false; // Functions void _preload_shaders(); void _parse_shader(const String &p_shader, const String &p_name); String _apply_inserts(const String &p_shader, const Array &p_excludes = Array()) const; String _generate_shader_code() const; String _generate_buffer_shader_code() const; String _strip_comments(const String &p_shader) const; String _inject_editor_code(const String &p_shader) const; void _update_shader(); void _update_uniforms(const RID &p_material, const uint32_t p_update = UNIFORMS_ONLY); void _set_shader_parameters(const Dictionary &p_dict); Dictionary _get_shader_parameters() const { return _shader_params; } public: Terrain3DMaterial() {} ~Terrain3DMaterial() { destroy(); } void initialize(Terrain3D *p_terrain); bool is_initialized() { return _terrain != nullptr; } void uninitialize(); void destroy(); void update(const uint32_t p_flags = UNIFORMS_ONLY); RID get_material_rid() const { return _material; } RID get_shader_rid() const { return _shader.is_valid() ? _shader->get_rid() : RID(); } RID get_buffer_material_rid() const { return _buffer_material; } RID get_buffer_shader_rid() const { return _buffer_shader.is_valid() ? _buffer_shader->get_rid() : RID(); } // Material settings void set_displacement_scale(const real_t p_displacement_scale); real_t get_displacement_scale() const { return _displacement_scale; } void set_displacement_sharpness(const real_t p_displacement_sharpness); real_t get_displacement_sharpness() const { return _displacement_sharpness; } void set_world_background(const WorldBackground p_background); WorldBackground get_world_background() const { return _world_background; } void set_texture_filtering(const TextureFiltering p_filtering); TextureFiltering get_texture_filtering() const { return _texture_filtering; } void set_auto_shader_enabled(const bool p_enabled); bool get_auto_shader_enabled() const { return _auto_shader_enabled; } void set_dual_scaling_enabled(const bool p_enabled); bool get_dual_scaling_enabled() const { return _dual_scaling_enabled; } void set_macro_variation_enabled(const bool p_enabled); bool get_macro_variation_enabled() const { return _macro_variation_enabled; } void set_projection_enabled(const bool p_enabled); bool get_projection_enabled() const { return _projection_enabled; } void set_shader_override_enabled(const bool p_enabled); bool is_shader_override_enabled() const { return _shader_override_enabled; } void set_shader_override(const Ref &p_shader); Ref get_shader_override() const { return _shader_override; } void set_buffer_shader_override_enabled(const bool p_enabled); bool is_buffer_shader_override_enabled() const { return _buffer_shader_override_enabled; } void set_buffer_shader_override(const Ref &p_shader); Ref get_buffer_shader_override() const { return _buffer_shader_override; } void set_shader_param(const StringName &p_name, const Variant &p_value); Variant get_shader_param(const StringName &p_name) const; // PBR outputs void set_output_albedo_enabled(const bool p_enabled); bool get_output_albedo_enabled() const { return _output_albedo_enabled; } void set_output_roughness_enabled(const bool p_enabled); bool get_output_roughness_enabled() const { return _output_roughness_enabled; } void set_output_normal_map_enabled(const bool p_enabled); bool get_output_normal_map_enabled() const { return _output_normal_map_enabled; } void set_output_ambient_occlusion_enabled(const bool p_enabled); bool get_output_ambient_occlusion_enabled() const { return _output_ambient_occlusion_enabled; } // Overlays void set_show_region_grid(const bool p_enabled); bool get_show_region_grid() const { return _show_region_grid; } void set_show_instancer_grid(const bool p_enabled); bool get_show_instancer_grid() const { return _show_instancer_grid; } void set_show_vertex_grid(const bool p_enabled); bool get_show_vertex_grid() const { return _show_vertex_grid; } void set_show_contours(const bool p_enabled); bool get_show_contours() const { return _show_contours; } void set_show_navigation(const bool p_enabled); bool get_show_navigation() const { return _show_navigation; } // Debug views void set_show_checkered(const bool p_enabled); bool get_show_checkered() const { return _debug_view_checkered; } void set_show_grey(const bool p_enabled); bool get_show_grey() const { return _debug_view_grey; } void set_show_heightmap(const bool p_enabled); bool get_show_heightmap() const { return _debug_view_heightmap; } void set_show_jaggedness(const bool p_enabled); bool get_show_jaggedness() const { return _debug_view_jaggedness; } void set_show_autoshader(const bool p_enabled); bool get_show_autoshader() const { return _debug_view_autoshader; } void set_show_control_texture(const bool p_enabled); bool get_show_control_texture() const { return _debug_view_control_texture; } void set_show_control_blend(const bool p_enabled); bool get_show_control_blend() const { return _debug_view_control_blend; } void set_show_control_angle(const bool p_enabled); bool get_show_control_angle() const { return _debug_view_control_angle; } void set_show_control_scale(const bool p_enabled); bool get_show_control_scale() const { return _debug_view_control_scale; } void set_show_colormap(const bool p_enabled); bool get_show_colormap() const { return _debug_view_colormap; } void set_show_roughmap(const bool p_enabled); bool get_show_roughmap() const { return _debug_view_roughmap; } void set_show_displacement_buffer(const bool p_enabled); bool get_show_displacement_buffer() const { return _debug_view_displacement_buffer; } // PBR Views void set_show_texture_albedo(const bool p_enabled); bool get_show_texture_albedo() const { return _pbr_view_tex_albedo; } void set_show_texture_height(const bool p_enabled); bool get_show_texture_height() const { return _pbr_view_tex_height; } void set_show_texture_normal(const bool p_enabled); bool get_show_texture_normal() const { return _pbr_view_tex_normal; } void set_show_texture_rough(const bool p_enabled); bool get_show_texture_rough() const { return _pbr_view_tex_rough; } void set_show_texture_ao(const bool p_enabled); bool get_show_texture_ao() const { return _pbr_view_tex_ao; } Error save(const String &p_path = ""); protected: void _get_property_list(List *p_list) const; bool _property_can_revert(const StringName &p_name) const; bool _property_get_revert(const StringName &p_name, Variant &r_property) const; bool _set(const StringName &p_name, const Variant &p_property); bool _get(const StringName &p_name, Variant &r_property) const; static void _bind_methods(); }; VARIANT_ENUM_CAST(Terrain3DMaterial::WorldBackground); VARIANT_ENUM_CAST(Terrain3DMaterial::TextureFiltering); VARIANT_ENUM_CAST(Terrain3DMaterial::UpdateFlags); #endif // TERRAIN3D_MATERIAL_CLASS_H ================================================ FILE: src/terrain_3d_mesh_asset.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include #include #include #include #include "logger.h" #include "terrain_3d_mesh_asset.h" /////////////////////////// // Private Functions /////////////////////////// void Terrain3DMeshAsset::_clear_lod_ranges() { LOG(INFO, "ID ", _id, ", ", _name, ": Clearing LOD Ranges, setting last_lod to 128"); _lod_ranges.resize(MAX_LOD_COUNT); for (int i = 0; i < MAX_LOD_COUNT; i++) { _lod_ranges[i] = (i + 1) * Terrain3DInstancer::CELL_SIZE; } _lod_ranges[_last_lod] = MAX(_lod_ranges[_last_lod], 128.f); } bool Terrain3DMeshAsset::_sort_lod_nodes(const Node *a, const Node *b) { ASSERT(a && b, false); return a->get_name().right(1) < b->get_name().right(1); } Ref Terrain3DMeshAsset::_create_generated_mesh(const GenType p_type) const { if (p_type != TYPE_TEXTURE_CARD) { LOG(ERROR, "Only TYPE_TEXTURE_CARD is currently implemented"); return Ref(); } LOG(EXTREME, "Regenerating new mesh"); Ref array_mesh; array_mesh.instantiate(); PackedVector3Array vertices; PackedVector3Array normals; PackedFloat32Array tangents; PackedVector2Array uvs; PackedInt32Array indices; int prevrow, thisrow, point = 0; float x, z; Size2 start_pos = Vector2(_generated_size.x * -0.5f, -0.5f); Vector3 normal = Vector3(0.f, 0.f, 1.f); thisrow = point; prevrow = 0; for (int m = 1; m <= _generated_faces; m++) { z = start_pos.y; real_t angle = 0.f; if (m > 1) { angle = (m - 1) * Math_PI / _generated_faces; } for (int j = 0; j <= 1; j++) { x = start_pos.x; for (int i = 0; i <= 1; i++) { float u = i; float v = j; vertices.push_back(Vector3(-x, z, 0.f).rotated(V3_UP, angle)); normals.push_back(normal); tangents.push_back(1.f); tangents.push_back(0.f); tangents.push_back(0.f); tangents.push_back(1.f); uvs.push_back(Vector2(1.f - u, 1.f - v)); point++; if (i > 0 && j > 0) { indices.push_back(prevrow + i - 1); indices.push_back(prevrow + i); indices.push_back(thisrow + i - 1); indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); } x += _generated_size.x; } z += _generated_size.y; prevrow = thisrow; thisrow = point; } } Array arrays; arrays.resize(Mesh::ARRAY_MAX); arrays[Mesh::ARRAY_VERTEX] = vertices; arrays[Mesh::ARRAY_NORMAL] = normals; arrays[Mesh::ARRAY_TANGENT] = tangents; arrays[Mesh::ARRAY_TEX_UV] = uvs; arrays[Mesh::ARRAY_INDEX] = indices; array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays); return array_mesh; } void Terrain3DMeshAsset::_assign_generated_mesh() { LOG(DEBUG, "Assiging generated mesh & lod settings"); _packed_scene.unref(); _pending_meshes.clear(); _pending_meshes.push_back(_create_generated_mesh()); _last_lod = 0; _last_shadow_lod = 0; _shadow_impostor = 0; if (_material_override.is_null()) { _material_override = _get_material(); } // If no existing meshes, commit immediately. Otherwise, will trigger on an instancer update if (_meshes.is_empty()) { commit_meshes(); } } Ref Terrain3DMeshAsset::_get_material() { if (_material_override.is_null()) { Ref mat; mat.instantiate(); mat->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); mat->set_cull_mode(BaseMaterial3D::CULL_DISABLED); mat->set_feature(BaseMaterial3D::FEATURE_BACKLIGHT, true); mat->set_backlight(Color(.5f, .5f, .5f)); mat->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); mat->set_distance_fade(BaseMaterial3D::DISTANCE_FADE_PIXEL_ALPHA); mat->set_distance_fade_min_distance(128.f); mat->set_distance_fade_max_distance(96.f); mat->set_meta("terrain3d_generated_material", true); return mat; } else { return _material_override; } } /////////////////////////// // Public Functions /////////////////////////// // Called by Terrain3DAssets::_set_asset_list() and _set_asset() void Terrain3DMeshAsset::initialize() { LOG(INFO, _id, ": ", _name, ": initializing asset"); if (_packed_scene.is_null() && _generated_type == TYPE_NONE) { LOG(DEBUG, "Blank mesh, setting up default texture card"); set_generated_type(TYPE_TEXTURE_CARD); _height_offset = 0.5f; _clear_lod_ranges(); } } void Terrain3DMeshAsset::clear() { LOG(INFO, "Clearing MeshAsset"); _name = "New Mesh"; _id = 0; _enabled = true; _highlighted = false; _highlight_mat = Ref(); _packed_scene.unref(); _meshes.clear(); _pending_meshes.clear(); _generated_type = TYPE_NONE; _generated_faces = 2; _generated_size = V2(1.f); _height_offset = 0.f; _density = 0.f; _cast_shadows = SHADOWS_ON; _visibility_layers = 1; _material_override.unref(); _material_overlay.unref(); _last_lod = MAX_LOD_COUNT - 1; _last_shadow_lod = MAX_LOD_COUNT - 1; _shadow_impostor = 0; _clear_lod_ranges(); _fade_margin = 0.f; _thumbnail.unref(); } void Terrain3DMeshAsset::set_name(const String &p_name) { if (p_name.length() > 96) { LOG(WARN, "Name too long, truncating to 96 characters"); } SET_IF_DIFF(_name, p_name.left(96)); LOG(INFO, "ID ", _id, ": Setting name: ", _name); LOG(DEBUG, "Emitting setting_changed, ID: ", _id); emit_signal("setting_changed", _id); } void Terrain3DMeshAsset::set_id(const int p_new_id) { int old_id = _id; SET_IF_DIFF(_id, CLAMP(p_new_id, 0, Terrain3DAssets::MAX_MESHES - 1)); LOG(INFO, _name, ": Setting mesh ID: ", _id); LOG(DEBUG, "Emitting id_changed, TYPE_MESH, ", old_id, ", ", p_new_id); emit_signal("id_changed", Terrain3DAssets::TYPE_MESH, old_id, p_new_id); } void Terrain3DMeshAsset::set_highlighted(const bool p_highlighted) { SET_IF_DIFF(_highlighted, p_highlighted); LOG(INFO, "ID ", _id, ", ", _name, ": Set mesh ID highlight: ", p_highlighted); if (_highlighted && _highlight_mat.is_null()) { Ref mat; mat.instantiate(); mat->set_cull_mode(BaseMaterial3D::CULL_DISABLED); Color color; real_t random_float = real_t(rand()) / real_t(RAND_MAX); color.set_hsv(random_float, 1.f, 1.f, 1.f); mat->set_albedo(color); _highlight_mat = mat; } LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } Color Terrain3DMeshAsset::get_highlight_color() const { StandardMaterial3D *mat = cast_to(_highlight_mat.ptr()); if (_highlighted && mat) { return mat->get_albedo(); } return COLOR_WHITE; } void Terrain3DMeshAsset::set_enabled(const bool p_enabled) { SET_IF_DIFF(_enabled, p_enabled); LOG(INFO, "ID ", _id, ", ", _name, ": Setting enabled: ", _enabled); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::update_instance_count(const int p_amount) { if (p_amount == 0) { return; } int new_count = _instance_count + p_amount; _instance_count = CLAMP(new_count, 0, UINT32_MAX); LOG(EXTREME, "Emitting instance_count_changed, ID: ", _id, ", count: ", _instance_count); emit_signal("instance_count_changed"); } void Terrain3DMeshAsset::set_instance_count(const uint32_t p_amount) { SET_IF_DIFF(_instance_count, CLAMP(p_amount, 0, UINT32_MAX)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting instance_count: ", _instance_count); LOG(DEBUG, "Emitting instance_count_changed"); emit_signal("instance_count_changed"); } void Terrain3DMeshAsset::set_scene_file(const Ref &p_scene_file) { SET_IF_DIFF(_packed_scene, p_scene_file); _pending_meshes.clear(); if (_packed_scene.is_valid()) { Node *node = _packed_scene->instantiate(); if (!node) { LOG(ERROR, "Drag a non-empty glb, fbx, scn, or tscn file into the scene_file slot"); _packed_scene.unref(); return; } LOG(INFO, "ID ", _id, ", ", _name, ": Instantiating scene root node: ", _packed_scene->get_path()); _height_offset = 0.0f; _generated_type = TYPE_NONE; if (_material_override.is_valid() && _material_override->has_meta("terrain3d_generated_material")) { _material_override.unref(); } // Look for MeshInstance3D nodes LOG(DEBUG, "Loaded scene with parent node: ", node); TypedArray mesh_instances; // First look for XXXXLOD# meshes, sorted by last digit mesh_instances = node->find_children("*LOD?", "MeshInstance3D"); if (mesh_instances.size() > 0) { LOG(INFO, "Found ", mesh_instances.size(), " meshes using LOD# naming convention, using the first ", MAX_LOD_COUNT); mesh_instances.sort_custom(callable_mp_static(&Terrain3DMeshAsset::_sort_lod_nodes)); } // Fallback to using all the meshes in provided order if (mesh_instances.size() == 0) { mesh_instances = node->find_children("*", "MeshInstance3D"); if (mesh_instances.size() > 0) { LOG(INFO, "No meshes with LOD# suffixes found, using the first ", MAX_LOD_COUNT, " meshes as LOD0-LOD3"); } } // Fallback to the scene root mesh if (mesh_instances.size() == 0) { if (node->is_class("MeshInstance3D")) { LOG(INFO, "No LOD# meshes found, assuming the root mesh is LOD0"); mesh_instances.push_back(node); } } if (mesh_instances.size() == 0) { LOG(ERROR, "No MeshInstance3D found in scene file"); } // Now process the meshes for (int i = 0, count = MIN(mesh_instances.size(), MAX_LOD_COUNT); i < count; i++) { MeshInstance3D *mi = cast_to(mesh_instances[i]); LOG(DEBUG, "Found mesh: ", mi->get_name()); String filename = _packed_scene->get_path().get_file().get_basename(); if (_name == "New Mesh" && !_packed_scene->get_path().contains("::")) { _name = filename; LOG(INFO, "Setting name based on filename: ", _name); } Ref mesh = mi->get_mesh(); if (mesh.is_null()) { LOG(WARN, "MeshInstance3D ", mi->get_name(), " has no mesh, skipping"); continue; } // Duplicate the mesh to make each Terrain3DMeshAsset unique mesh = mesh->duplicate(); // Apply the active material from the scene to the mesh, including MI or Geom overrides for (int j = 0; j < mi->get_surface_override_material_count(); j++) { Ref mat = mi->get_active_material(j); mesh->surface_set_material(j, mat); } _pending_meshes.push_back(mesh); } node->queue_free(); } if (_pending_meshes.size() > 0) { Ref mesh = _pending_meshes[0]; if (mesh.is_null()) { LOG(ERROR, "First mesh is null after loading scene"); return; } AABB aabb = mesh->get_aabb(); _density = CLAMP(10.f / (aabb.has_volume() ? aabb.get_volume() : 1.f), 0.01f, 10.0f); _last_lod = _pending_meshes.size() - 1; _last_shadow_lod = _last_lod; _shadow_impostor = 0; _clear_lod_ranges(); } else { set_generated_type(TYPE_TEXTURE_CARD); _height_offset = 0.5f; _clear_lod_ranges(); } // If no existing meshes, commit immediately. Otherwise, will trigger on an instancer update if (_meshes.is_empty()) { commit_meshes(); } notify_property_list_changed(); // Call _validate_property to update inspector LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::commit_meshes() { LOG(INFO, _name, ": Committing ", _pending_meshes.size(), " pending meshes"); _meshes.clear(); _meshes = _pending_meshes; _pending_meshes = TypedArray(); } void Terrain3DMeshAsset::set_generated_type(const GenType p_type) { SET_IF_DIFF(_generated_type, CLAMP(p_type, GenType(TYPE_NONE + 1), GenType(TYPE_MAX - 1))); LOG(INFO, "ID ", _id, ", ", _name, ": Setting generated type: ", _generated_type); _assign_generated_mesh(); _density = 10.f; LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); notify_property_list_changed(); } Ref Terrain3DMeshAsset::get_mesh(const int p_lod) const { if (p_lod >= 0 && p_lod < _get_meshes().size()) { return _get_meshes()[p_lod]; } return Ref(); } void Terrain3DMeshAsset::set_height_offset(const real_t p_offset) { SET_IF_DIFF(_height_offset, CLAMP(p_offset, -50.f, 50.f)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting height offset: ", _height_offset); LOG(DEBUG, "Emitting setting_changed, ID: ", _id); emit_signal("setting_changed", _id); } void Terrain3DMeshAsset::set_density(const real_t p_density) { SET_IF_DIFF(_density, CLAMP(p_density, 0.01f, 10.f)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting mesh density: ", p_density); LOG(DEBUG, "Emitting setting_changed, ID: ", _id); emit_signal("setting_changed", _id); } void Terrain3DMeshAsset::set_cast_shadows(const ShadowCasting p_cast_shadows) { SET_IF_DIFF(_cast_shadows, p_cast_shadows); LOG(INFO, "ID ", _id, ", ", _name, ": Setting shadow casting mode: ", _cast_shadows); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } // Returns the approproate cast_shadows setting for the given LOD ID ShadowCasting Terrain3DMeshAsset::get_lod_cast_shadows(const int p_lod_id) const { // If cast shadows is off, disable all shadows if (_cast_shadows == SHADOWS_OFF) { return _cast_shadows; } // Return shadows only if set, ensuring shadow impostor and last lod are processed first if (_cast_shadows == SHADOWS_ONLY) { return _cast_shadows; } // Set to shadows only if this lod is the shadow impostor, which is only ever set to shadows only if (p_lod_id == SHADOW_LOD_ID) { return SHADOWS_ONLY; } // Disable shadows if this lod uses the shadow impostor if (p_lod_id < _shadow_impostor) { return SHADOWS_OFF; } // Disable shadows if this lod is too far if (p_lod_id > _last_shadow_lod) { return SHADOWS_OFF; } return _cast_shadows; } inline void Terrain3DMeshAsset::set_visibility_layers(const uint32_t p_layers) { SET_IF_DIFF(_visibility_layers, p_layers); LOG(INFO, _name, ": Setting visibility layers: ", p_layers); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_material_override(const Ref &p_material) { SET_IF_DIFF(_material_override, p_material); LOG(INFO, "ID ", _id, ", ", _name, ": Setting material override: ", p_material); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_material_overlay(const Ref &p_material) { SET_IF_DIFF(_material_overlay, p_material); LOG(INFO, "ID ", _id, ", ", _name, ": Setting material overlay: ", p_material); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_generated_faces(const int p_count) { SET_IF_DIFF(_generated_faces, CLAMP(p_count, 1, 3)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting generated face count: ", _generated_faces); // If already a texture card, just recreate the mesh w/o other settings if (_generated_type > TYPE_NONE && _generated_type < TYPE_MAX && _get_meshes().size() == 1) { _assign_generated_mesh(); } LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_generated_size(const Vector2 &p_size) { SET_IF_DIFF(_generated_size, p_size); LOG(INFO, "ID ", _id, ", ", _name, ": Setting generated size: ", _generated_size); // If already a texture card, just recreate the mesh w/o other settings if (_generated_type > TYPE_NONE && _generated_type < TYPE_MAX && _get_meshes().size() == 1) { _assign_generated_mesh(); } LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_last_lod(const int p_lod) { int max_lod = _generated_type != TYPE_NONE ? 0 : CLAMP(_get_meshes().size(), 1, MAX_LOD_COUNT) - 1; SET_IF_DIFF(_last_lod, CLAMP(p_lod, 0, max_lod)); if (_last_shadow_lod > _last_lod) { _last_shadow_lod = _last_lod; } if (_shadow_impostor > _last_lod) { _shadow_impostor = _last_lod; } LOG(INFO, "ID ", _id, ", ", _name, ": Setting last LOD: ", _last_lod); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); notify_property_list_changed(); } void Terrain3DMeshAsset::set_last_shadow_lod(const int p_lod) { SET_IF_DIFF(_last_shadow_lod, CLAMP(p_lod, 0, _last_lod)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting last shadow LOD: ", _last_shadow_lod); if (_shadow_impostor > _last_shadow_lod) { _shadow_impostor = _last_shadow_lod; } LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_shadow_impostor(const int p_lod) { SET_IF_DIFF(_shadow_impostor, CLAMP(p_lod, 0, MIN(_last_lod, _last_shadow_lod))); LOG(INFO, "ID ", _id, ", ", _name, ": Setting shadow imposter LOD: ", _shadow_impostor); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_lod_range(const int p_lod, const real_t p_distance) { if (p_lod < 0 || p_lod >= _lod_ranges.size()) { LOG(ERROR, "p_lod out of range. Valid range is 0 - ", _lod_ranges.size() - 1); return; } SET_IF_DIFF(_lod_ranges[p_lod], CLAMP(p_distance, 0.f, 100000.f)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting LOD ", p_lod, " visibility range: ", _lod_ranges[p_lod]); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } real_t Terrain3DMeshAsset::get_lod_range(const int p_lod) const { if (p_lod < 0 || p_lod >= _lod_ranges.size()) { return -1.f; } return _lod_ranges[p_lod]; } real_t Terrain3DMeshAsset::get_lod_range_begin(const int p_lod) const { if (p_lod <= 0) { return 0.f; } ASSERT(p_lod <= _last_lod, 0.f); return _lod_ranges[p_lod - 1]; } real_t Terrain3DMeshAsset::get_lod_range_end(const int p_lod) const { if (p_lod == SHADOW_LOD_ID) { return _lod_ranges[MAX(_shadow_impostor - 1, 0)]; } ASSERT(p_lod >= 0 && p_lod <= _last_lod, 0.f); return _lod_ranges[p_lod]; } void Terrain3DMeshAsset::set_fade_margin(const real_t p_fade_margin) { int max_range = CLAMP(_lod_ranges[1] - _lod_ranges[0], 0.f, 64.f); SET_IF_DIFF(_fade_margin, CLAMP(p_fade_margin, 0.f, max_range)); LOG(INFO, "ID ", _id, ", ", _name, ": Setting visibility margin: ", _fade_margin); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DMeshAsset::_validate_property(PropertyInfo &p_property) const { if (p_property.name != StringName("generated_type") && p_property.name.begins_with("generated_")) { if (_generated_type == TYPE_NONE) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } else { p_property.usage = PROPERTY_USAGE_DEFAULT; } return; } else if (p_property.name.match("lod?_range")) { int lod = p_property.name.substr(3, 1).to_int(); if (_last_lod < lod) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } else { p_property.usage = PROPERTY_USAGE_DEFAULT; } } } bool Terrain3DMeshAsset::_property_can_revert(const StringName &p_name) const { return ( p_name.match("last_lod") || p_name.match("last_shadow_lod") || p_name.match("density") || p_name.match("generated_type") || (_generated_type > TYPE_NONE && (p_name.match("height_offset") || p_name.match("lod0_range")))); } bool Terrain3DMeshAsset::_property_get_revert(const StringName &p_name, Variant &r_property) const { if (p_name.match("last_lod")) { r_property = get_lod_count() - 1; return true; } else if (p_name.match("last_shadow_lod")) { r_property = get_lod_count() - 1; return true; } else if (p_name.match("density")) { r_property = 10.f; return true; } else if (p_name.match("generated_type")) { r_property = TYPE_TEXTURE_CARD; return true; } else if (_generated_type > TYPE_NONE) { if (p_name.match("height_offset")) { r_property = 0.5f; return true; } else if (p_name.match("lod0_range")) { r_property = 128.f; return true; } } return false; } void Terrain3DMeshAsset::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_NONE); BIND_ENUM_CONSTANT(TYPE_TEXTURE_CARD); BIND_ENUM_CONSTANT(TYPE_MAX); ADD_SIGNAL(MethodInfo("id_changed")); ADD_SIGNAL(MethodInfo("setting_changed")); ADD_SIGNAL(MethodInfo("instancer_setting_changed")); ADD_SIGNAL(MethodInfo("instance_count_changed")); ClassDB::bind_method(D_METHOD("clear"), &Terrain3DMeshAsset::clear); ClassDB::bind_method(D_METHOD("set_name", "name"), &Terrain3DMeshAsset::set_name); ClassDB::bind_method(D_METHOD("get_name"), &Terrain3DMeshAsset::get_name); ClassDB::bind_method(D_METHOD("set_id", "id"), &Terrain3DMeshAsset::set_id); ClassDB::bind_method(D_METHOD("get_id"), &Terrain3DMeshAsset::get_id); ClassDB::bind_method(D_METHOD("set_highlighted", "enabled"), &Terrain3DMeshAsset::set_highlighted); ClassDB::bind_method(D_METHOD("is_highlighted"), &Terrain3DMeshAsset::is_highlighted); ClassDB::bind_method(D_METHOD("get_highlight_color"), &Terrain3DMeshAsset::get_highlight_color); ClassDB::bind_method(D_METHOD("get_thumbnail"), &Terrain3DMeshAsset::get_thumbnail); ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &Terrain3DMeshAsset::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &Terrain3DMeshAsset::is_enabled); ClassDB::bind_method(D_METHOD("set_scene_file", "scene_file"), &Terrain3DMeshAsset::set_scene_file); ClassDB::bind_method(D_METHOD("get_scene_file"), &Terrain3DMeshAsset::get_scene_file); ClassDB::bind_method(D_METHOD("set_generated_type", "type"), &Terrain3DMeshAsset::set_generated_type); ClassDB::bind_method(D_METHOD("get_generated_type"), &Terrain3DMeshAsset::get_generated_type); ClassDB::bind_method(D_METHOD("get_mesh", "lod"), &Terrain3DMeshAsset::get_mesh, DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_height_offset", "offset"), &Terrain3DMeshAsset::set_height_offset); ClassDB::bind_method(D_METHOD("get_height_offset"), &Terrain3DMeshAsset::get_height_offset); ClassDB::bind_method(D_METHOD("set_density", "density"), &Terrain3DMeshAsset::set_density); ClassDB::bind_method(D_METHOD("get_density"), &Terrain3DMeshAsset::get_density); ClassDB::bind_method(D_METHOD("set_cast_shadows", "mode"), &Terrain3DMeshAsset::set_cast_shadows); ClassDB::bind_method(D_METHOD("get_cast_shadows"), &Terrain3DMeshAsset::get_cast_shadows); ClassDB::bind_method(D_METHOD("set_visibility_layers", "layers"), &Terrain3DMeshAsset::set_visibility_layers); ClassDB::bind_method(D_METHOD("get_visibility_layers"), &Terrain3DMeshAsset::get_visibility_layers); ClassDB::bind_method(D_METHOD("set_material_override", "material"), &Terrain3DMeshAsset::set_material_override); ClassDB::bind_method(D_METHOD("get_material_override"), &Terrain3DMeshAsset::get_material_override); ClassDB::bind_method(D_METHOD("set_material_overlay", "material"), &Terrain3DMeshAsset::set_material_overlay); ClassDB::bind_method(D_METHOD("get_material_overlay"), &Terrain3DMeshAsset::get_material_overlay); ClassDB::bind_method(D_METHOD("set_generated_faces", "count"), &Terrain3DMeshAsset::set_generated_faces); ClassDB::bind_method(D_METHOD("get_generated_faces"), &Terrain3DMeshAsset::get_generated_faces); ClassDB::bind_method(D_METHOD("set_generated_size", "size"), &Terrain3DMeshAsset::set_generated_size); ClassDB::bind_method(D_METHOD("get_generated_size"), &Terrain3DMeshAsset::get_generated_size); ClassDB::bind_method(D_METHOD("get_lod_count"), &Terrain3DMeshAsset::get_lod_count); ClassDB::bind_method(D_METHOD("set_last_lod", "lod"), &Terrain3DMeshAsset::set_last_lod); ClassDB::bind_method(D_METHOD("get_last_lod"), &Terrain3DMeshAsset::get_last_lod); ClassDB::bind_method(D_METHOD("set_last_shadow_lod", "lod"), &Terrain3DMeshAsset::set_last_shadow_lod); ClassDB::bind_method(D_METHOD("get_last_shadow_lod"), &Terrain3DMeshAsset::get_last_shadow_lod); ClassDB::bind_method(D_METHOD("set_shadow_impostor", "lod"), &Terrain3DMeshAsset::set_shadow_impostor); ClassDB::bind_method(D_METHOD("get_shadow_impostor"), &Terrain3DMeshAsset::get_shadow_impostor); ClassDB::bind_method(D_METHOD("set_lod_range", "lod", "distance"), &Terrain3DMeshAsset::set_lod_range); ClassDB::bind_method(D_METHOD("get_lod_range", "lod"), &Terrain3DMeshAsset::get_lod_range); ClassDB::bind_method(D_METHOD("set_lod0_range", "distance"), &Terrain3DMeshAsset::set_lod0_range); ClassDB::bind_method(D_METHOD("get_lod0_range"), &Terrain3DMeshAsset::get_lod0_range); ClassDB::bind_method(D_METHOD("set_lod1_range", "distance"), &Terrain3DMeshAsset::set_lod1_range); ClassDB::bind_method(D_METHOD("get_lod1_range"), &Terrain3DMeshAsset::get_lod1_range); ClassDB::bind_method(D_METHOD("set_lod2_range", "distance"), &Terrain3DMeshAsset::set_lod2_range); ClassDB::bind_method(D_METHOD("get_lod2_range"), &Terrain3DMeshAsset::get_lod2_range); ClassDB::bind_method(D_METHOD("set_lod3_range", "distance"), &Terrain3DMeshAsset::set_lod3_range); ClassDB::bind_method(D_METHOD("get_lod3_range"), &Terrain3DMeshAsset::get_lod3_range); ClassDB::bind_method(D_METHOD("set_lod4_range", "distance"), &Terrain3DMeshAsset::set_lod4_range); ClassDB::bind_method(D_METHOD("get_lod4_range"), &Terrain3DMeshAsset::get_lod4_range); ClassDB::bind_method(D_METHOD("set_lod5_range", "distance"), &Terrain3DMeshAsset::set_lod5_range); ClassDB::bind_method(D_METHOD("get_lod5_range"), &Terrain3DMeshAsset::get_lod5_range); ClassDB::bind_method(D_METHOD("set_lod6_range", "distance"), &Terrain3DMeshAsset::set_lod6_range); ClassDB::bind_method(D_METHOD("get_lod6_range"), &Terrain3DMeshAsset::get_lod6_range); ClassDB::bind_method(D_METHOD("set_lod7_range", "distance"), &Terrain3DMeshAsset::set_lod7_range); ClassDB::bind_method(D_METHOD("get_lod7_range"), &Terrain3DMeshAsset::get_lod7_range); ClassDB::bind_method(D_METHOD("set_lod8_range", "distance"), &Terrain3DMeshAsset::set_lod8_range); ClassDB::bind_method(D_METHOD("get_lod8_range"), &Terrain3DMeshAsset::get_lod8_range); ClassDB::bind_method(D_METHOD("set_lod9_range", "distance"), &Terrain3DMeshAsset::set_lod9_range); ClassDB::bind_method(D_METHOD("get_lod9_range"), &Terrain3DMeshAsset::get_lod9_range); ClassDB::bind_method(D_METHOD("set_fade_margin", "distance"), &Terrain3DMeshAsset::set_fade_margin); ClassDB::bind_method(D_METHOD("get_fade_margin"), &Terrain3DMeshAsset::get_fade_margin); ClassDB::bind_method(D_METHOD("get_instance_count"), &Terrain3DMeshAsset::get_instance_count); ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE), "set_id", "get_id"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled", PROPERTY_HINT_NONE), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scene_file", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_scene_file", "get_scene_file"); ADD_PROPERTY(PropertyInfo(Variant::INT, "generated_type", PROPERTY_HINT_ENUM, "None,Texture Card", PROPERTY_USAGE_STORAGE), "set_generated_type", "get_generated_type"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_offset", PROPERTY_HINT_RANGE, "-20.0,20.0,.005"), "set_height_offset", "get_height_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, ".01,10.0,.005"), "set_density", "get_density"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadows", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows", "get_cast_shadows"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_visibility_layers", "get_visibility_layers"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material_override", "get_material_override"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material_overlay", "get_material_overlay"); ADD_GROUP("Generated Mesh", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "generated_faces", PROPERTY_HINT_NONE), "set_generated_faces", "get_generated_faces"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "generated_size", PROPERTY_HINT_NONE), "set_generated_size", "get_generated_size"); ADD_GROUP("LODs", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_lod_count"); ADD_PROPERTY(PropertyInfo(Variant::INT, "last_lod", PROPERTY_HINT_NONE), "set_last_lod", "get_last_lod"); ADD_PROPERTY(PropertyInfo(Variant::INT, "last_shadow_lod", PROPERTY_HINT_NONE), "set_last_shadow_lod", "get_last_shadow_lod"); ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_impostor", PROPERTY_HINT_NONE), "set_shadow_impostor", "get_shadow_impostor"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod0_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod0_range", "get_lod0_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod1_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod1_range", "get_lod1_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod2_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod2_range", "get_lod2_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod3_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod3_range", "get_lod3_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod4_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod4_range", "get_lod4_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod5_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod5_range", "get_lod5_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod6_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod6_range", "get_lod6_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod7_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod7_range", "get_lod7_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod8_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod8_range", "get_lod8_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod9_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod9_range", "get_lod9_range"); // Fade disabled until https://github.com/godotengine/godot/issues/102799 is fixed ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fade_margin", PROPERTY_HINT_RANGE, "0.,64.0,.05,or_greater", PROPERTY_USAGE_NO_EDITOR), "set_fade_margin", "get_fade_margin"); } ================================================ FILE: src/terrain_3d_mesh_asset.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_MESH_ASSET_CLASS_H #define TERRAIN3D_MESH_ASSET_CLASS_H #include #include #include #include #include #include "constants.h" #include "terrain_3d_asset_resource.h" using ShadowCasting = RenderingServer::ShadowCastingSetting; constexpr ShadowCasting SHADOWS_ON = RenderingServer::SHADOW_CASTING_SETTING_ON; constexpr ShadowCasting SHADOWS_OFF = RenderingServer::SHADOW_CASTING_SETTING_OFF; constexpr ShadowCasting SHADOWS_ONLY = RenderingServer::SHADOW_CASTING_SETTING_SHADOWS_ONLY; /* This class requires a bit of special care because: * - We want custom defaults depending on if it's a texture card or a scene file * - Any settings saved in the assets resource file need to override the defaults * - generated_type = TEXTURE_CARD is the default and isn't saved in the scene file * * The caveat of this is a texture card MeshAsset needs to determine if it is * new, and should apply defaults, or loaded, and should retain settings. * The specific defaults of concern are height_offset, density, lod0_range. * * The current solution to make the distinction is, * Loaded Assets go through Terrain3DAssets::set_mesh_list(), _set_asset_list() * New Assets go through Terrain3DAssets::set_mesh_asset(), _set_asset() * Both call initialize() with a new/loaded flag. * * New assets might be loaded from the API, AssetDock, or Terrain3DAssets::update_mesh_list() * when the mesh list is empty. */ class Terrain3DMeshAsset : public Terrain3DAssetResource { GDCLASS(Terrain3DMeshAsset, Terrain3DAssetResource); CLASS_NAME(); public: enum GenType { TYPE_NONE, TYPE_TEXTURE_CARD, TYPE_MAX, }; static constexpr int MAX_LOD_COUNT = 10; static constexpr int SHADOW_LOD_ID = -1; // ID used for the shadow lod in instancer private: // Saved data bool _enabled = true; Ref _packed_scene; GenType _generated_type = TYPE_NONE; int _generated_faces = 2; Vector2 _generated_size = V2(1.f); real_t _height_offset = 0.f; real_t _density = 0.f; ShadowCasting _cast_shadows = SHADOWS_ON; uint32_t _visibility_layers = 1; Ref _material_override; Ref _material_overlay; int _last_lod = MAX_LOD_COUNT - 1; int _last_shadow_lod = MAX_LOD_COUNT - 1; int _shadow_impostor = 0; PackedFloat32Array _lod_ranges; real_t _fade_margin = 0.f; // Working data Ref _highlight_mat; TypedArray _meshes; TypedArray _pending_meshes; // Queue to avoid warnings from RS on mesh swap uint32_t _instance_count = 0; void _clear_lod_ranges(); static bool _sort_lod_nodes(const Node *a, const Node *b); Ref _create_generated_mesh(const GenType p_type = TYPE_TEXTURE_CARD) const; void _assign_generated_mesh(); Ref _get_material(); TypedArray _get_meshes() const; public: Terrain3DMeshAsset() { clear(); } ~Terrain3DMeshAsset() {} void initialize() override; void clear() override; void set_name(const String &p_name) override; String get_name() const override { return _name; } void set_id(const int p_new_id) override; int get_id() const override { return _id; } void set_highlighted(const bool p_highlighted) override; bool is_highlighted() const override { return _highlighted; } Ref get_highlight_material() const { return _highlighted ? _highlight_mat : Ref(); } Color get_highlight_color() const override; Ref get_thumbnail() const override { return _thumbnail; } void set_enabled(const bool p_enabled); bool is_enabled() const { return _enabled; } void update_instance_count(const int p_amount); void set_instance_count(const uint32_t p_amount); uint32_t get_instance_count() const { return _instance_count; } void set_scene_file(const Ref &p_scene_file); Ref get_scene_file() const { return _packed_scene; } bool is_scene_file_pending() const { return _pending_meshes.size() > 0; } void commit_meshes(); void set_generated_type(const GenType p_type); GenType get_generated_type() const { return _generated_type; } Ref get_mesh(const int p_lod = 0) const; void set_thumbnail(Ref p_tex) { _thumbnail = p_tex; } void set_height_offset(const real_t p_offset); real_t get_height_offset() const { return _height_offset; } void set_density(const real_t p_density); real_t get_density() const { return _density; } void set_cast_shadows(const ShadowCasting p_cast_shadows); ShadowCasting get_cast_shadows() const { return _cast_shadows; }; ShadowCasting get_lod_cast_shadows(const int p_lod_id) const; void set_visibility_layers(const uint32_t p_layers); uint32_t get_visibility_layers() const { return _visibility_layers; } void set_material_override(const Ref &p_material); Ref get_material_override() const { return _material_override; } void set_material_overlay(const Ref &p_material); Ref get_material_overlay() const { return _material_overlay; } void set_generated_faces(const int p_count); int get_generated_faces() const { return _generated_faces; } void set_generated_size(const Vector2 &p_size); Vector2 get_generated_size() const { return _generated_size; } int get_lod_count() const { return _get_meshes().size(); } void set_last_lod(const int p_lod); int get_last_lod() const { return _last_lod; } void set_last_shadow_lod(const int p_lod); int get_last_shadow_lod() const { return _last_shadow_lod; } void set_shadow_impostor(const int p_lod); int get_shadow_impostor() const { return _shadow_impostor; } void set_lod_range(const int p_lod, const real_t p_distance); real_t get_lod_range(const int p_lod) const; real_t get_lod_range_begin(const int p_lod) const; real_t get_lod_range_end(const int p_lod) const; void set_lod0_range(const real_t p_distance) { set_lod_range(0, p_distance); } real_t get_lod0_range() const { return _lod_ranges[0]; } void set_lod1_range(const real_t p_distance) { set_lod_range(1, p_distance); } real_t get_lod1_range() const { return _lod_ranges[1]; } void set_lod2_range(const real_t p_distance) { set_lod_range(2, p_distance); } real_t get_lod2_range() const { return _lod_ranges[2]; } void set_lod3_range(const real_t p_distance) { set_lod_range(3, p_distance); } real_t get_lod3_range() const { return _lod_ranges[3]; } void set_lod4_range(const real_t p_distance) { set_lod_range(4, p_distance); } real_t get_lod4_range() const { return _lod_ranges[4]; } void set_lod5_range(const real_t p_distance) { set_lod_range(5, p_distance); } real_t get_lod5_range() const { return _lod_ranges[5]; } void set_lod6_range(const real_t p_distance) { set_lod_range(6, p_distance); } real_t get_lod6_range() const { return _lod_ranges[6]; } void set_lod7_range(const real_t p_distance) { set_lod_range(7, p_distance); } real_t get_lod7_range() const { return _lod_ranges[7]; } void set_lod8_range(const real_t p_distance) { set_lod_range(8, p_distance); } real_t get_lod8_range() const { return _lod_ranges[8]; } void set_lod9_range(const real_t p_distance) { set_lod_range(9, p_distance); } real_t get_lod9_range() const { return _lod_ranges[9]; } void set_fade_margin(const real_t p_fade_margin); real_t get_fade_margin() const { return _fade_margin; }; protected: void _validate_property(PropertyInfo &p_property) const; bool _property_can_revert(const StringName &p_name) const; bool _property_get_revert(const StringName &p_name, Variant &r_property) const; static void _bind_methods(); }; VARIANT_ENUM_CAST(Terrain3DMeshAsset::GenType); inline TypedArray Terrain3DMeshAsset::_get_meshes() const { if (!_pending_meshes.is_empty()) { return _pending_meshes; } return _meshes; } #endif // TERRAIN3D_MESH_ASSET_CLASS_H ================================================ FILE: src/terrain_3d_mesher.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include "logger.h" #include "terrain_3d.h" #include "terrain_3d_mesher.h" /////////////////////////// // Private Functions /////////////////////////// void Terrain3DMesher::_generate_mesh_types() { _clear_mesh_types(); LOG(INFO, "Generating all Mesh segments for clipmap of size ", _mesh_size); // Create initial set of Mesh blocks to build the clipmap // # 0 TILE - mesh_size x mesh_size tiles _mesh_rids.push_back(_generate_mesh(V2I(_mesh_size))); // # 1 EDGE_A - 2 by (mesh_size * 4 + 8) strips to bridge LOD transitions along +-Z axis _mesh_rids.push_back(_generate_mesh(Vector2i(2, _mesh_size * 4 + 8))); // # 2 EDGE_B - (mesh_size * 4 + 4) by 2 strips to bridge LOD transitions along +-X axis _mesh_rids.push_back(_generate_mesh(Vector2i(_mesh_size * 4 + 4, 2))); // # 3 FILL_A - 4 by mesh_size _mesh_rids.push_back(_generate_mesh(Vector2i(4, _mesh_size))); // # 4 FILL_B - mesh_size by 4 _mesh_rids.push_back(_generate_mesh(Vector2i(_mesh_size, 4))); // # 5 STANDARD_TRIM_A - 2 by (mesh_size * 4 + 2) strips for LOD0 +-Z axis edge _mesh_rids.push_back(_generate_mesh(Vector2i(2, _mesh_size * 4 + 2), true)); // # 6 STANDARD_TRIM_B - (mesh_size * 4 + 2) by 2 strips for LOD0 +-X axis edge _mesh_rids.push_back(_generate_mesh(Vector2i(_mesh_size * 4 + 2, 2), true)); // # 7 STANDARD_TILE - mesh_size x mesh_size tiles _mesh_rids.push_back(_generate_mesh(Vector2i(_mesh_size, _mesh_size), true)); // # 8 STANDARD_EDGE_A - 2 by (mesh_size * 4 + 8) strips to bridge LOD transitions along +-Z axis _mesh_rids.push_back(_generate_mesh(Vector2i(2, _mesh_size * 4 + 8), true)); // # 9 STANDARD_EDGE_B - (mesh_size * 4 + 4) by 2 strips to bridge LOD transitions along +-X axis _mesh_rids.push_back(_generate_mesh(Vector2i(_mesh_size * 4 + 4, 2), true)); return; } RID Terrain3DMesher::_generate_mesh(const Vector2i &p_size, const bool p_standard_grid) { PackedVector3Array vertices; PackedInt32Array indices; AABB aabb = AABB(V3_ZERO, Vector3(p_size.x, 0.1f, p_size.y)); LOG(DEBUG, "Generating verticies and indices for a", p_standard_grid ? " symmetric " : " standard ", "grid mesh of width: ", p_size.x, " and height: ", p_size.y); // Generate vertices for (int y = 0; y <= p_size.y; ++y) { for (int x = 0; x <= p_size.x; ++x) { // Match GDScript vertex definitions vertices.push_back(Vector3(x, 0.f, y)); // bottom-left } } // Generate indices for quads with alternating diagonals for (int y = 0; y < p_size.y; ++y) { for (int x = 0; x < p_size.x; ++x) { int bottomLeft = y * (p_size.x + 1) + x; int bottomRight = bottomLeft + 1; int topLeft = (y + 1) * (p_size.x + 1) + x; int topRight = topLeft + 1; if ((x + y) % 2 == 0 || p_standard_grid) { indices.push_back(bottomLeft); indices.push_back(topRight); indices.push_back(topLeft); indices.push_back(bottomLeft); indices.push_back(bottomRight); indices.push_back(topRight); } else { indices.push_back(bottomLeft); indices.push_back(bottomRight); indices.push_back(topLeft); indices.push_back(topLeft); indices.push_back(bottomRight); indices.push_back(topRight); } } } return _instantiate_mesh(vertices, indices, aabb); } RID Terrain3DMesher::_instantiate_mesh(const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices, const AABB &p_aabb) { Array arrays; arrays.resize(RenderingServer::ARRAY_MAX); arrays[RenderingServer::ARRAY_VERTEX] = p_vertices; arrays[RenderingServer::ARRAY_INDEX] = p_indices; PackedVector3Array normals; normals.resize(p_vertices.size()); normals.fill(V3_UP); arrays[RenderingServer::ARRAY_NORMAL] = normals; PackedFloat32Array tangents; tangents.resize(p_vertices.size() * 4); tangents.fill(0.0f); arrays[RenderingServer::ARRAY_TANGENT] = tangents; LOG(DEBUG, "Creating mesh via the Rendering server"); RID mesh = RS->mesh_create(); RS->mesh_add_surface_from_arrays(mesh, RenderingServer::PRIMITIVE_TRIANGLES, arrays); LOG(DEBUG, "Setting custom aabb: ", p_aabb.position, ", ", p_aabb.size); RS->mesh_set_custom_aabb(mesh, p_aabb); RS->mesh_surface_set_material(mesh, 0, _material.is_valid() ? _material : RID()); return mesh; } void Terrain3DMesher::_generate_clipmap() { _clear_clipmap(); _generate_mesh_types(); _generate_offset_data(); LOG(DEBUG, "Creating instances for all mesh segments for clipmap of size ", _mesh_size, " for ", _lods, " LODs"); for (int level = 0; level < _lods + _tessellation_level; level++) { Array lod; // 12 Tiles LOD1+, 16 for LOD0 Array tile_rids; int tile_amount = (level == 0) ? 16 : 12; for (int i = 0; i < tile_amount; i++) { RID tile_rid = RS->instance_create2(_mesh_rids[level == 0 ? STANDARD_TILE : TILE], _scenario); tile_rids.append(tile_rid); } lod.append(tile_rids); // index 0 TILE // 4 Edges present on all LODs Array edge_a_rids; for (int i = 0; i < 2; i++) { RID edge_a_rid = RS->instance_create2(_mesh_rids[level == 0 ? STANDARD_EDGE_A : EDGE_A], _scenario); edge_a_rids.append(edge_a_rid); } lod.append(edge_a_rids); // index 1 EDGE_A Array edge_b_rids; for (int i = 0; i < 2; i++) { RID edge_b_rid = RS->instance_create2(_mesh_rids[level == 0 ? STANDARD_EDGE_B : EDGE_B], _scenario); edge_b_rids.append(edge_b_rid); } lod.append(edge_b_rids); // index 2 EDGE_B // Fills only present on LODs 1+ if (level > 0) { Array fill_a_rids; for (int i = 0; i < 2; i++) { RID fill_a_rid = RS->instance_create2(_mesh_rids[FILL_A], _scenario); fill_a_rids.append(fill_a_rid); } lod.append(fill_a_rids); // index 4 FILL_A Array fill_b_rids; for (int i = 0; i < 2; i++) { RID fill_b_rid = RS->instance_create2(_mesh_rids[FILL_B], _scenario); fill_b_rids.append(fill_b_rid); } lod.append(fill_b_rids); // index 5 FILL_B // Trims only on LOD 0 These share the indices of the fills for the offsets. // When snapping LOD 0 Trim a/b positions are looked up instead of Fill a/b } else { Array trim_a_rids; for (int i = 0; i < 2; i++) { RID trim_a_rid = RS->instance_create2(_mesh_rids[STANDARD_TRIM_A], _scenario); trim_a_rids.append(trim_a_rid); } lod.append(trim_a_rids); // index 4 TRIM_A Array trim_b_rids; for (int i = 0; i < 2; i++) { RID trim_b_rid = RS->instance_create2(_mesh_rids[STANDARD_TRIM_B], _scenario); trim_b_rids.append(trim_b_rid); } lod.append(trim_b_rids); // index 5 TRIM_B } // Append LOD to _lod_rids array _clipmap_rids.append(lod); } } // Precomputes all instance offset data into lookup arrays that match created instances. // All meshes are created with 0,0 as their origin and grow along +xz. Offsets account for this. void Terrain3DMesher::_generate_offset_data() { LOG(INFO, "Computing all clipmap instance positioning offsets"); _tile_pos_lod_0.clear(); _trim_a_pos.clear(); _trim_b_pos.clear(); _edge_pos.clear(); _fill_a_pos.clear(); _fill_b_pos.clear(); _tile_pos.clear(); // LOD0 Tiles: Full 4x4 Grid of mesh size tiles _tile_pos_lod_0.push_back(Vector3(0, 0, _mesh_size)); _tile_pos_lod_0.push_back(Vector3(_mesh_size, 0, _mesh_size)); _tile_pos_lod_0.push_back(Vector3(_mesh_size, 0, 0)); _tile_pos_lod_0.push_back(Vector3(_mesh_size, 0, -_mesh_size)); _tile_pos_lod_0.push_back(Vector3(_mesh_size, 0, -_mesh_size * 2)); _tile_pos_lod_0.push_back(Vector3(0, 0, -_mesh_size * 2)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size, 0, -_mesh_size * 2)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size * 2, 0, -_mesh_size * 2)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size * 2, 0, -_mesh_size)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size * 2, 0, 0)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size * 2, 0, _mesh_size)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size, 0, _mesh_size)); // Inner tiles _tile_pos_lod_0.push_back(V3_ZERO); _tile_pos_lod_0.push_back(Vector3(-_mesh_size, 0, 0)); _tile_pos_lod_0.push_back(Vector3(0, 0, -_mesh_size)); _tile_pos_lod_0.push_back(Vector3(-_mesh_size, 0, -_mesh_size)); // LOD0 Trims: Fixed 2 unit wide ring around LOD0 tiles. _trim_a_pos.push_back(Vector3(_mesh_size * 2, 0, -_mesh_size * 2)); _trim_a_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, -_mesh_size * 2 - 2)); _trim_b_pos.push_back(Vector3(-_mesh_size * 2, 0, -_mesh_size * 2 - 2)); _trim_b_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, _mesh_size * 2)); // LOD1+: 4x4 Ring of mesh size tiles, with one 2 unit wide gap on each axis for fill meshes. _tile_pos.push_back(Vector3(2, 0, _mesh_size + 2)); _tile_pos.push_back(Vector3(_mesh_size + 2, 0, _mesh_size + 2)); _tile_pos.push_back(Vector3(_mesh_size + 2, 0, -2)); _tile_pos.push_back(Vector3(_mesh_size + 2, 0, -_mesh_size - 2)); _tile_pos.push_back(Vector3(_mesh_size + 2, 0, -_mesh_size * 2 - 2)); _tile_pos.push_back(Vector3(-2, 0, -_mesh_size * 2 - 2)); _tile_pos.push_back(Vector3(-_mesh_size - 2, 0, -_mesh_size * 2 - 2)); _tile_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, -_mesh_size * 2 - 2)); _tile_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, -_mesh_size + 2)); _tile_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, +2)); _tile_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, _mesh_size + 2)); _tile_pos.push_back(Vector3(-_mesh_size + 2, 0, _mesh_size + 2)); // Edge offsets set edge pair positions to either both before, straddle, or both after // Depending on current LOD position within the next LOD, (via test_x or test_z in snap()) _offset_a = real_t(_mesh_size * 2) + 2.f; _offset_b = real_t(_mesh_size * 2) + 4.f; _offset_c = real_t(_mesh_size * 2) + 6.f; _edge_pos.push_back(Vector3(_offset_a, _offset_a, -_offset_b)); _edge_pos.push_back(Vector3(_offset_b, -_offset_b, -_offset_c)); // Fills: Occupies the gaps between tiles for LOD1+ to complete the ring. _fill_a_pos.push_back(Vector3(_mesh_size - 2, 0, -_mesh_size * 2 - 2)); _fill_a_pos.push_back(Vector3(-_mesh_size - 2, 0, _mesh_size + 2)); _fill_b_pos.push_back(Vector3(_mesh_size + 2, 0, _mesh_size - 2)); _fill_b_pos.push_back(Vector3(-_mesh_size * 2 - 2, 0, -_mesh_size - 2)); return; } // Frees all clipmap instance RIDs. Mesh rids must be freed separately. void Terrain3DMesher::_clear_clipmap() { LOG(INFO, "Freeing all clipmap instances"); for (const Array &lod_array : _clipmap_rids) { for (const Array &mesh_array : lod_array) { for (const RID &rid : mesh_array) { RS->free_rid(rid); } } } _clipmap_rids.clear(); return; } // Frees all Mesh RIDs use for clipmap instances. void Terrain3DMesher::_clear_mesh_types() { LOG(INFO, "Freeing all clipmap meshes"); for (const RID &rid : _mesh_rids) { RS->free_rid(rid); } _mesh_rids.clear(); return; } /////////////////////////// // Public Functions /////////////////////////// void Terrain3DMesher::initialize(Terrain3D *p_terrain, const int p_mesh_size, const int p_lods, const int p_tessellation_level, const real_t p_vertex_spacing, const RID &p_material, const uint32_t p_render_layers) { if (p_terrain) { _terrain = p_terrain; } else { return; } if (!_terrain->is_inside_world()) { LOG(DEBUG, "Terrain3D's world3D is null"); return; } LOG(INFO, "Initializing GeoMesh"); _scenario = _terrain->get_world_3d()->get_scenario(); _material = p_material; _lods = p_lods; _tessellation_level = p_tessellation_level; _mesh_size = p_mesh_size; _vertex_spacing = p_vertex_spacing; _render_layers = p_render_layers; _generate_clipmap(); update(); update_aabbs(); reset_target_position(); snap(); } void Terrain3DMesher::destroy() { LOG(INFO, "Destroying clipmap"); _clear_clipmap(); _clear_mesh_types(); _tile_pos_lod_0.clear(); _trim_a_pos.clear(); _trim_b_pos.clear(); _edge_pos.clear(); _fill_a_pos.clear(); _fill_b_pos.clear(); } void Terrain3DMesher::snap() { IS_INIT(VOID); // Always update target position in shader Vector3 target_pos = _terrain->get_clipmap_target_position(); if (_material.is_valid()) { RS->material_set_param(_material, "_target_pos", target_pos); } // If clipmap target hasn't moved enough, skip Vector2 target_pos_2d = v3v2(target_pos); real_t tessellation_density = 1.f / pow(2.f, _tessellation_level); real_t vertex_spacing = _vertex_spacing * tessellation_density; if (MAX(std::abs(_last_target_position.x - target_pos_2d.x), std::abs(_last_target_position.y - target_pos_2d.y)) < vertex_spacing) { return; } // Recenter terrain on the target _last_target_position = target_pos_2d; Vector3 snapped_pos = (target_pos / vertex_spacing).floor() * vertex_spacing; Vector3 pos = V3_ZERO; for (int lod = 0; lod < _clipmap_rids.size(); ++lod) { real_t snap_step = pow(2.f, lod + 1.f) * vertex_spacing; Vector3 lod_scale = Vector3(pow(2.f, lod) * vertex_spacing, 1.f, pow(2.f, lod) * vertex_spacing); // Snap pos.xz pos.x = round(snapped_pos.x / snap_step) * snap_step; pos.z = round(snapped_pos.z / snap_step) * snap_step; LOG(EXTREME, "Snapping clipmap LOD", lod, " to position: ", pos); // test_x and test_z for edge strip positions real_t next_snap_step = pow(2.f, lod + 2.f) * vertex_spacing; real_t next_x = round(snapped_pos.x / next_snap_step) * next_snap_step; real_t next_z = round(snapped_pos.z / next_snap_step) * next_snap_step; int test_x = CLAMP(int(round((pos.x - next_x) / snap_step)) + 1, 0, 2); int test_z = CLAMP(int(round((pos.z - next_z) / snap_step)) + 1, 0, 2); Array lod_array = _clipmap_rids[lod]; for (int mesh = 0; mesh < lod_array.size(); ++mesh) { Array mesh_array = lod_array[mesh]; for (int instance = 0; instance < mesh_array.size(); ++instance) { Transform3D t = Transform3D(); switch (mesh) { case TILE: { t.origin = (lod == 0) ? _tile_pos_lod_0[instance] : _tile_pos[instance]; break; } case EDGE_A: { Vector3 edge_pos_instance = _edge_pos[instance]; t.origin.z -= _offset_a + (test_z * 2.f); t.origin.x = edge_pos_instance[test_x]; break; } case EDGE_B: { Vector3 edge_pos_instance = _edge_pos[instance]; t.origin.z = edge_pos_instance[test_z]; t.origin.x -= _offset_a; break; } // LOD0 doesnt have fills so the trims share the same index. case FILL_A: { if (lod > 0) { t.origin = _fill_a_pos[instance]; } else { t.origin = _trim_a_pos[instance]; } break; } case FILL_B: { if (lod > 0) { t.origin = _fill_b_pos[instance]; } else { t.origin = _trim_b_pos[instance]; } break; } default: { break; } } t = t.scaled(lod_scale); t.origin += pos; RS->instance_set_transform(mesh_array[instance], t); RS->instance_teleport(mesh_array[instance]); } } } return; } // Iterates over every instance of every mesh and updates all properties. void Terrain3DMesher::update() { IS_INIT(VOID); if (!_terrain->is_inside_world()) { LOG(DEBUG, "Terrain3D's world3D is null"); return; } bool baked_light; bool dynamic_gi; switch (_terrain->get_gi_mode()) { case GeometryInstance3D::GI_MODE_DISABLED: { baked_light = false; dynamic_gi = false; } break; case GeometryInstance3D::GI_MODE_DYNAMIC: { baked_light = false; dynamic_gi = true; } break; case GeometryInstance3D::GI_MODE_STATIC: default: { baked_light = true; dynamic_gi = false; } break; } RenderingServer::ShadowCastingSetting cast_shadows = _terrain->get_cast_shadows(); bool visible = _terrain->is_visible_in_tree(); LOG(INFO, "Updating all mesh instances for ", _clipmap_rids.size(), " LODs"); for (const Array &lod_array : _clipmap_rids) { for (const Array &mesh_array : lod_array) { for (const RID &rid : mesh_array) { RS->instance_set_visible(rid, visible); RS->instance_set_scenario(rid, _scenario); RS->instance_set_layer_mask(rid, _render_layers); RS->instance_geometry_set_cast_shadows_setting(rid, cast_shadows); RS->instance_geometry_set_flag(rid, RenderingServer::INSTANCE_FLAG_USE_BAKED_LIGHT, baked_light); RS->instance_geometry_set_flag(rid, RenderingServer::INSTANCE_FLAG_USE_DYNAMIC_GI, dynamic_gi); } } } return; } // Iterates over all meshes and updates their AABBs // All instances of each mesh inherit the updated AABB // Defaults to using the terrain parameters void Terrain3DMesher::update_aabbs(const real_t p_cull_margin, const Vector2 &p_height_range) { IS_DATA_INIT(VOID); LOG(INFO, "Updating ", _mesh_rids.size(), " meshes AABBs") real_t cull_margin; Vector2 height_range; if (p_cull_margin < 0.f) { cull_margin = _terrain->get_cull_margin(); } else { cull_margin = p_cull_margin; } if (p_height_range.x == FLT_MAX) { height_range = _terrain->get_data()->get_height_range(); } else { height_range = p_height_range; } height_range.y += std::abs(height_range.x); for (const RID &rid : _mesh_rids) { AABB aabb = RS->mesh_get_custom_aabb(rid); aabb.position.y = height_range.x - cull_margin; aabb.size.y = height_range.y + cull_margin * 2.f; RS->mesh_set_custom_aabb(rid, aabb); } return; } ================================================ FILE: src/terrain_3d_mesher.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_MESHER_CLASS_H #define TERRAIN3D_MESHER_CLASS_H #include "constants.h" class Terrain3D; class Terrain3DMesher { CLASS_NAME_STATIC("Terrain3DMesher"); public: // Constants enum MeshType { TILE, EDGE_A, EDGE_B, FILL_A, FILL_B, STANDARD_TRIM_A, STANDARD_TRIM_B, STANDARD_TILE, STANDARD_EDGE_A, STANDARD_EDGE_B, }; private: Terrain3D *_terrain = nullptr; RID _scenario = RID(); Vector2 _last_target_position = V2_MAX; Array _mesh_rids; // LODs -> MeshTypes -> Instances Array _clipmap_rids; // Mesh offset data // LOD0 only PackedVector3Array _trim_a_pos; PackedVector3Array _trim_b_pos; PackedVector3Array _tile_pos_lod_0; // LOD1 + PackedVector3Array _fill_a_pos; PackedVector3Array _fill_b_pos; PackedVector3Array _tile_pos; // All LOD Levels real_t _offset_a = 0.f; real_t _offset_b = 0.f; real_t _offset_c = 0.f; PackedVector3Array _edge_pos; RID _material; int _tessellation_level = 0; int _lods = 0; int _mesh_size = 0; real_t _vertex_spacing = 1.f; uint32_t _render_layers = 1u; // Bit 1 only void _generate_mesh_types(); RID _generate_mesh(const Vector2i &p_size, const bool p_standard_grid = false); RID _instantiate_mesh(const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices, const AABB &p_aabb); void _generate_clipmap(); void _generate_offset_data(); void _clear_clipmap(); void _clear_mesh_types(); public: Terrain3DMesher() {} ~Terrain3DMesher() { destroy(); } void initialize(Terrain3D *p_terrain, const int p_mesh_size, const int p_lods, const int p_tessellation_level, const real_t p_vertex_spacing, const RID &p_material, const uint32_t p_render_layers); void destroy(); void snap(); void reset_target_position() { _last_target_position = V2_MAX; } void update(); void update_aabbs(const real_t p_cull_margin = -1.f, const Vector2 &p_height_range = V2_MAX); void set_material(const RID &p_material) { _material = p_material; } RID get_material() const { return _material; } void set_lods(const int p_lods) { _lods = p_lods; } int get_lods() const { return _lods; } void set_tessellation_level(const int p_level) { _tessellation_level = p_level; } int get_tessellation_level() const { return _tessellation_level; } void set_mesh_size(const int p_size) { _mesh_size = p_size; } int get_mesh_size() const { return _mesh_size; } void set_vertex_spacing(const real_t p_spacing) { _vertex_spacing = p_spacing; } real_t get_vertex_spacing() const { return _vertex_spacing; } void set_render_layers(const uint32_t p_layers) { _render_layers = p_layers; } uint32_t get_render_layers() const { return _render_layers; } }; // Inline Functions #endif // TERRAIN3D_MESHER_CLASS_H ================================================ FILE: src/terrain_3d_region.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include "logger.h" #include "terrain_3d_data.h" #include "terrain_3d_region.h" #include "terrain_3d_util.h" ///////////////////// // Public Functions ///////////////////// void Terrain3DRegion::clear() { _version = 0.8f; _region_size = 0; _height_range = V2_ZERO; _height_map.unref(); _control_map.unref(); _color_map.unref(); _instances.clear(); _vertex_spacing = 1.f; _deleted = false; _edited = false; _modified = false; _location = V2I_MAX; } void Terrain3DRegion::set_version(const real_t p_version) { real_t version = CLAMP(p_version, 0.8f, 100.f); if (_version == version) { return; } // Mark modified if already initialized and we get a new value if (_version > 0.8f) { _modified = true; } _version = version; LOG(INFO, vformat("%.3f", _version)); if (_version < Terrain3DData::CURRENT_DATA_VERSION) { LOG(WARN, "Region Data ", get_path(), " version ", vformat("%.3f", _version), " will be updated to ", vformat("%.3f", Terrain3DData::CURRENT_DATA_VERSION), " upon save"); } } void Terrain3DRegion::set_region_size(const int p_region_size) { if (!is_valid_region_size(p_region_size)) { LOG(ERROR, "Invalid region size: ", p_region_size, ". Must be power of 2, 64-2048"); return; } // Mark modified if already initialized and we get a new value if (_region_size > 0 && _region_size != p_region_size) { _modified = true; } SET_IF_DIFF(_region_size, p_region_size); LOG(INFO, "Setting region ", _location, " size: ", p_region_size); } void Terrain3DRegion::set_map(const MapType p_map_type, const Ref &p_image) { switch (p_map_type) { case TYPE_HEIGHT: set_height_map(p_image); break; case TYPE_CONTROL: set_control_map(p_image); break; case TYPE_COLOR: set_color_map(p_image); break; default: LOG(ERROR, "Requested map type is invalid"); break; } } Ref Terrain3DRegion::get_map(const MapType p_map_type) const { switch (p_map_type) { case TYPE_HEIGHT: return get_height_map(); case TYPE_CONTROL: return get_control_map(); case TYPE_COLOR: return get_color_map(); default: LOG(ERROR, "Requested map type ", p_map_type, ", is invalid"); return Ref(); } } Image *Terrain3DRegion::get_map_ptr(const MapType p_map_type) const { switch (p_map_type) { case TYPE_HEIGHT: return *_height_map; case TYPE_CONTROL: return *_control_map; case TYPE_COLOR: return *_color_map; default: LOG(ERROR, "Requested map type ", p_map_type, ", is invalid"); return nullptr; } } void Terrain3DRegion::set_maps(const TypedArray &p_maps) { if (p_maps.size() != TYPE_MAX) { LOG(ERROR, "Expected ", TYPE_MAX - 1, " maps. Received ", p_maps.size()); return; } _region_size = 0; set_height_map(p_maps[TYPE_HEIGHT]); set_control_map(p_maps[TYPE_CONTROL]); set_color_map(p_maps[TYPE_COLOR]); } TypedArray Terrain3DRegion::get_maps() const { LOG(INFO, "Retrieving maps from region: ", _location); TypedArray maps; maps.push_back(_height_map); maps.push_back(_control_map); maps.push_back(_color_map); return maps; } void Terrain3DRegion::set_height_map(const Ref &p_map) { SET_IF_DIFF(_height_map, p_map); LOG(INFO, "Setting height map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); if (_region_size == 0 && p_map.is_valid()) { set_region_size(p_map->get_width()); } Ref map = sanitize_map(TYPE_HEIGHT, p_map); // If already initialized and receiving a new map, or the map was sanitized if (_height_map.is_valid() && _height_map != p_map || map != p_map) { _modified = true; } _height_map = map; calc_height_range(); } void Terrain3DRegion::set_control_map(const Ref &p_map) { SET_IF_DIFF(_control_map, p_map); LOG(INFO, "Setting control map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); if (_region_size == 0 && p_map.is_valid()) { set_region_size(p_map->get_width()); } Ref map = sanitize_map(TYPE_CONTROL, p_map); // If already initialized and receiving a new map, or the map was sanitized if (_control_map.is_valid() && _control_map != p_map || map != p_map) { _modified = true; } _control_map = map; } void Terrain3DRegion::set_color_map(const Ref &p_map) { SET_IF_DIFF(_color_map, p_map); LOG(INFO, "Setting color map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); if (_region_size == 0 && p_map.is_valid()) { set_region_size(p_map->get_width()); } Ref map = sanitize_map(TYPE_COLOR, p_map); // If already initialized and receiving a new map, or the map was sanitized if (_color_map.is_valid() && _color_map != p_map || map != p_map) { _modified = true; } _color_map = map; } void Terrain3DRegion::sanitize_maps() { if (_region_size == 0) { // blank region, no set_*_map has been called LOG(ERROR, "Set region_size first"); return; } Ref map = sanitize_map(TYPE_HEIGHT, _height_map); if (_height_map != map) { _modified = true; } _height_map = map; map = sanitize_map(TYPE_CONTROL, _control_map); if (_control_map != map) { _modified = true; } _control_map = map; map = sanitize_map(TYPE_COLOR, _color_map); if (_color_map != map) { _modified = true; } _color_map = map; } Ref Terrain3DRegion::sanitize_map(const MapType p_map_type, const Ref &p_map) const { LOG(INFO, "Sanitizing map type: ", p_map_type, ", map: ", p_map); if (!is_valid_region_size(_region_size)) { LOG(ERROR, "Invalid region size: ", _region_size, ". Set it or set a map first. Must be power of 2, 64-2048"); return Ref(); } const char *type_str = TYPESTR[p_map_type]; Image::Format format = FORMAT[p_map_type]; Color color = COLOR[p_map_type]; Ref map; if (p_map.is_valid()) { if (validate_map_size(p_map)) { if (p_map->get_format() == format) { LOG(DEBUG, "Map type ", type_str, " correct format, size. Mipmaps: ", p_map->has_mipmaps()); map = p_map; } else { LOG(DEBUG, "Provided ", type_str, " map wrong format: ", p_map->get_format(), ". Converting copy to: ", format); map.instantiate(); map->copy_from(p_map); map->convert(format); if (map->get_format() != format) { LOG(DEBUG, "Cannot convert image to format: ", format, ". Creating blank "); map.unref(); } } } else { LOG(DEBUG, "Provided ", type_str, " map wrong size: ", p_map->get_size(), ". Creating blank"); } } else { LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); } if (map.is_null()) { LOG(DEBUG, "Making new image of type: ", type_str, " and generating mipmaps: ", p_map_type == TYPE_COLOR); return Util::get_filled_image(V2I(_region_size), color, p_map_type == TYPE_COLOR, format); } else { if (p_map_type == TYPE_COLOR && !map->has_mipmaps()) { LOG(DEBUG, "Color map does not have mipmaps. Generating"); map->generate_mipmaps(); } return map; } } bool Terrain3DRegion::validate_map_size(const Ref &p_map) const { Vector2i region_sizev = p_map->get_size(); if (region_sizev.x != region_sizev.y) { LOG(ERROR, "Image width doesn't match height: ", region_sizev); return false; } if (!is_valid_region_size(region_sizev.x) || !is_valid_region_size(region_sizev.y)) { LOG(ERROR, "Invalid image size: ", region_sizev, ". Must be power of 2, 64-2048 and square"); return false; } if (_region_size != region_sizev.x || _region_size != region_sizev.y) { LOG(ERROR, "Image size doesn't match existing images in this region", region_sizev); return false; } return true; } void Terrain3DRegion::set_height_range(const Vector2 &p_range) { if (differs(_height_range, p_range)) { // Mark modified if setting after initialization if (!_height_range.is_zero_approx()) { _modified = true; } _height_range = p_range; LOG(INFO, vformat("%.2v", p_range)); } else { return; }; } void Terrain3DRegion::calc_height_range() { Vector2 range = Util::get_min_max(_height_map); if (_height_range != range) { _height_range = range; _modified = true; LOG(DEBUG, "Recalculated new height range: ", _height_range, " for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)", ". Marking modified"); } } void Terrain3DRegion::set_instances(const Dictionary &p_instances) { if (!_instances.is_empty() && differs(_instances, p_instances)) { _modified = true; } SET_IF_DIFF(_instances, p_instances); LOG(INFO, "Region ", _location, " setting instances ptr: ", ptr_to_str(p_instances._native_ptr())); } void Terrain3DRegion::set_location(const Vector2i &p_location) { // In the future anywhere they want to put the location might be fine, but because of region_map // We have a limitation of 32x32. if (Terrain3DData::get_region_map_index(p_location) < 0) { LOG(ERROR, "Location ", p_location, " out of bounds. Max: ", -Terrain3DData::REGION_MAP_SIZE / 2, " to ", Terrain3DData::REGION_MAP_SIZE / 2 - 1); return; } // Marked modified if setting after initialized if (_location < V2I_MAX && _location != p_location) { _modified = true; } SET_IF_DIFF(_location, p_location); LOG(INFO, "Set location: ", p_location); } Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { // Initiate save to external file. The scene will save itself. if (_location.x == INT32_MAX) { LOG(ERROR, "Region has not been setup. Location is INT32_MAX. Skipping ", p_path); } if (!_modified) { LOG(DEBUG, "Region ", _location, " not modified. Skipping ", p_path); return ERR_SKIP; } if (p_path.is_empty() && get_path().is_empty()) { LOG(ERROR, "No valid path provided"); return ERR_FILE_NOT_FOUND; } if (!p_path.is_empty()) { LOG(DEBUG, "Setting file path for region ", _location, " to ", p_path); take_over_path(p_path); // Set region path and take over the path from any other cached resources, // incuding those in the undo queue } LOG(MESG, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _location, " to ", get_path()); set_version(Terrain3DData::CURRENT_DATA_VERSION); Error err = OK; if (p_16_bit) { Ref original_map; original_map.instantiate(); original_map->copy_from(_height_map); _height_map->convert(Image::FORMAT_RH); err = ResourceSaver::get_singleton()->save(this, get_path(), ResourceSaver::FLAG_COMPRESS); _height_map = original_map; } else { err = ResourceSaver::get_singleton()->save(this, get_path(), ResourceSaver::FLAG_COMPRESS); } if (err == OK) { _modified = false; LOG(INFO, "File saved successfully"); } else { LOG(ERROR, "Cannot save region file: ", get_path(), ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); } return err; } void Terrain3DRegion::set_data(const Dictionary &p_data) { #define SET_IF_HAS(var, str) \ if (p_data.has(str)) { \ var = p_data[str]; \ } SET_IF_HAS(_location, "location"); SET_IF_HAS(_deleted, "deleted"); SET_IF_HAS(_edited, "edited"); SET_IF_HAS(_modified, "modified"); SET_IF_HAS(_version, "version"); SET_IF_HAS(_region_size, "region_size"); SET_IF_HAS(_vertex_spacing, "vertex_spacing"); SET_IF_HAS(_height_range, "height_range"); SET_IF_HAS(_height_map, "height_map"); SET_IF_HAS(_control_map, "control_map"); SET_IF_HAS(_color_map, "color_map"); SET_IF_HAS(_instances, "instances"); } Dictionary Terrain3DRegion::get_data() const { Dictionary dict; dict["location"] = _location; dict["deleted"] = _deleted; dict["edited"] = _edited; dict["modified"] = _modified; dict["version"] = _version; dict["region_size"] = _region_size; dict["vertex_spacing"] = _vertex_spacing; dict["height_range"] = _height_range; dict["height_map"] = _height_map; dict["control_map"] = _control_map; dict["color_map"] = _color_map; dict["instances"] = _instances; return dict; } Ref Terrain3DRegion::duplicate(const bool p_deep) { Ref region; region.instantiate(); if (!p_deep) { region->set_data(get_data()); } else { Dictionary dict; // Native type copies dict["version"] = _version; dict["region_size"] = _region_size; dict["vertex_spacing"] = _vertex_spacing; dict["height_range"] = _height_range; dict["modified"] = _modified; dict["deleted"] = _deleted; dict["location"] = _location; // Resource duplicates dict["height_map"] = _height_map->duplicate(); dict["control_map"] = _control_map->duplicate(); dict["color_map"] = _color_map->duplicate(); dict["instances"] = _instances.duplicate(true); region->set_data(dict); } return region; } void Terrain3DRegion::dump(const bool verbose) const { LOG(MESG, "Region: ", _location, ", version: ", vformat("%.2f", _version), ", size: ", _region_size, ", spacing: ", vformat("%.1f", _vertex_spacing), ", range: ", vformat("%.2v", _height_range), ", flags (", _edited ? "ed," : "", _modified ? "mod," : "", _deleted ? "del" : "", "), ", ptr_to_str(this)); LOG(MESG, "Height map: ", ptr_to_str(*_height_map), ", Control map: ", ptr_to_str(*_control_map), ", Color map: ", ptr_to_str(*_color_map)); LOG(MESG, "Instances: Mesh IDs: ", _instances.size(), ", ", ptr_to_str(_instances._native_ptr())); Array mesh_ids = _instances.keys(); for (const int &mesh_id : mesh_ids) { int counter = 0; Dictionary cell_inst_dict = _instances[mesh_id]; Array cells = cell_inst_dict.keys(); for (const Vector2i &cell : cells) { Array triple = cell_inst_dict[cell]; if (triple.size() == 3) { counter += Array(triple[0]).size(); } else { LOG(WARN, "Malformed triple at cell ", cell, ": ", triple); continue; } if (verbose) { Array xforms = triple[0]; Array colors = triple[1]; bool modified = triple[2]; LOG(MESG, "Mesh ID: ", mesh_id, " cell: ", cell, " xforms: ", xforms.size(), ", colors: ", colors.size(), modified ? ", modified" : ""); } } LOG(MESG, "Mesh ID: ", mesh_id, ", instance count: ", counter); } } ///////////////////// // Protected Functions ///////////////////// void Terrain3DRegion::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_HEIGHT); BIND_ENUM_CONSTANT(TYPE_CONTROL); BIND_ENUM_CONSTANT(TYPE_COLOR); BIND_ENUM_CONSTANT(TYPE_MAX); ClassDB::bind_method(D_METHOD("clear"), &Terrain3DRegion::clear); ClassDB::bind_method(D_METHOD("set_version", "version"), &Terrain3DRegion::set_version); ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DRegion::get_version); ClassDB::bind_method(D_METHOD("set_region_size", "region_size"), &Terrain3DRegion::set_region_size); ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DRegion::get_region_size); ClassDB::bind_method(D_METHOD("set_vertex_spacing", "vertex_spacing"), &Terrain3DRegion::set_vertex_spacing); ClassDB::bind_method(D_METHOD("get_vertex_spacing"), &Terrain3DRegion::get_vertex_spacing); ClassDB::bind_method(D_METHOD("set_map", "map_type", "map"), &Terrain3DRegion::set_map); ClassDB::bind_method(D_METHOD("get_map", "map_type"), &Terrain3DRegion::get_map); ClassDB::bind_method(D_METHOD("set_maps", "maps"), &Terrain3DRegion::set_maps); ClassDB::bind_method(D_METHOD("get_maps"), &Terrain3DRegion::get_maps); ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DRegion::set_height_map); ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DRegion::get_height_map); ClassDB::bind_method(D_METHOD("set_control_map", "map"), &Terrain3DRegion::set_control_map); ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DRegion::get_control_map); ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DRegion::set_color_map); ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DRegion::get_color_map); ClassDB::bind_method(D_METHOD("sanitize_maps"), &Terrain3DRegion::sanitize_maps); ClassDB::bind_method(D_METHOD("sanitize_map", "map_type", "map"), &Terrain3DRegion::sanitize_map); ClassDB::bind_method(D_METHOD("validate_map_size", "map"), &Terrain3DRegion::validate_map_size); ClassDB::bind_method(D_METHOD("set_height_range", "range"), &Terrain3DRegion::set_height_range); ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DRegion::get_height_range); ClassDB::bind_method(D_METHOD("update_height", "height"), &Terrain3DRegion::update_height); ClassDB::bind_method(D_METHOD("update_heights", "low_high"), &Terrain3DRegion::update_heights); ClassDB::bind_method(D_METHOD("calc_height_range"), &Terrain3DRegion::calc_height_range); ClassDB::bind_method(D_METHOD("set_instances", "instances"), &Terrain3DRegion::set_instances); ClassDB::bind_method(D_METHOD("get_instances"), &Terrain3DRegion::get_instances); ClassDB::bind_method(D_METHOD("save", "path", "save_16_bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_deleted", "deleted"), &Terrain3DRegion::set_deleted); ClassDB::bind_method(D_METHOD("is_deleted"), &Terrain3DRegion::is_deleted); ClassDB::bind_method(D_METHOD("set_edited", "edited"), &Terrain3DRegion::set_edited); ClassDB::bind_method(D_METHOD("is_edited"), &Terrain3DRegion::is_edited); ClassDB::bind_method(D_METHOD("set_modified", "modified"), &Terrain3DRegion::set_modified); ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); ClassDB::bind_method(D_METHOD("set_location", "location"), &Terrain3DRegion::set_location); ClassDB::bind_method(D_METHOD("get_location"), &Terrain3DRegion::get_location); ClassDB::bind_method(D_METHOD("set_data", "data"), &Terrain3DRegion::set_data); ClassDB::bind_method(D_METHOD("get_data"), &Terrain3DRegion::get_data); ClassDB::bind_method(D_METHOD("duplicate", "deep"), &Terrain3DRegion::duplicate, DEFVAL(false)); ClassDB::bind_method(D_METHOD("dump", "verbose"), &Terrain3DRegion::dump, DEFVAL(false)); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_NONE, "", ro_flags), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vertex_spacing", PROPERTY_HINT_NONE, "", ro_flags), "set_vertex_spacing", "get_vertex_spacing"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "height_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "control_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "instances", PROPERTY_HINT_NONE, "", ro_flags), "set_instances", "get_instances"); // Double-clicking a region .res file shows what's on disk, the defaults, not in memory. So these are hidden ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edited", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_edited", "is_edited"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deleted", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_deleted", "is_deleted"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_modified", "is_modified"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_location", "get_location"); } ================================================ FILE: src/terrain_3d_region.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_REGION_CLASS_H #define TERRAIN3D_REGION_CLASS_H #include "constants.h" #include "terrain_3d_util.h" class Terrain3DRegion : public Resource { GDCLASS(Terrain3DRegion, Resource); CLASS_NAME(); public: // Constants enum MapType { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR, TYPE_MAX, }; static inline const Image::Format FORMAT[] = { Image::FORMAT_RF, // TYPE_HEIGHT Image::FORMAT_RF, // TYPE_CONTROL Image::FORMAT_RGBA8, // TYPE_COLOR Image::Format(TYPE_MAX), // Proper size of array instead of FORMAT_MAX }; static inline const char *TYPESTR[] = { "TYPE_HEIGHT", "TYPE_CONTROL", "TYPE_COLOR", "TYPE_MAX", }; static inline const Color COLOR[] = { COLOR_BLACK, // TYPE_HEIGHT COLOR_CONTROL, // TYPE_CONTROL COLOR_ROUGHNESS, // TYPE_COLOR COLOR_NAN, // TYPE_MAX, unused just in case someone indexes the array }; private: // Saved data real_t _version = 0.8f; // Set to first version to ensure we always upgrades this int _region_size = 0; Vector2 _height_range = V2_ZERO; // Maps Ref _height_map; Ref _control_map; Ref _color_map; // Instancer Dictionary _instances; // Meshes{int} -> Cells{v2i} -> [ Transform3D, Color, Modified ] real_t _vertex_spacing = 1.f; // Spacing that instancer transforms are currently scaled by. // Working data not saved to disk bool _deleted = false; // Marked for deletion on save bool _edited = false; // Marked for undo/redo storage bool _modified = false; // Marked for saving Vector2i _location = V2I_MAX; public: Terrain3DRegion() {} ~Terrain3DRegion() {} void clear(); void set_version(const real_t p_version); real_t get_version() const { return _version; } void set_region_size(const int p_region_size); int get_region_size() const { return _region_size; } // Maps void set_map(const MapType p_map_type, const Ref &p_image); Ref get_map(const MapType p_map_type) const; Image *get_map_ptr(const MapType p_map_type) const; void set_maps(const TypedArray &p_maps); TypedArray get_maps() const; void set_height_map(const Ref &p_map); Ref get_height_map() const { return _height_map; } void set_control_map(const Ref &p_map); Ref get_control_map() const { return _control_map; } void set_color_map(const Ref &p_map); Ref get_color_map() const { return _color_map; } void sanitize_maps(); Ref sanitize_map(const MapType p_map_type, const Ref &p_map) const; bool validate_map_size(const Ref &p_map) const; void set_height_range(const Vector2 &p_range); Vector2 get_height_range() const { return _height_range; } void update_height(const real_t p_height); void update_heights(const Vector2 &p_low_high); void calc_height_range(); // Instancer void set_instances(const Dictionary &p_instances); Dictionary get_instances() const { return _instances; } void set_vertex_spacing(const real_t p_vertex_spacing) { _vertex_spacing = CLAMP(p_vertex_spacing, 0.25f, 100.f); } real_t get_vertex_spacing() const { return _vertex_spacing; } // Working Data void set_deleted(const bool p_deleted) { _deleted = p_deleted; } bool is_deleted() const { return _deleted; } void set_edited(const bool p_edited) { _edited = p_edited; } bool is_edited() const { return _edited; } void set_modified(const bool p_modified) { _modified = p_modified; } bool is_modified() const { return _modified; } void set_location(const Vector2i &p_location); Vector2i get_location() const { return _location; } // File I/O Error save(const String &p_path = "", const bool p_16_bit = false); // Utility void set_data(const Dictionary &p_data); Dictionary get_data() const; Ref duplicate(const bool p_deep = false); void dump(const bool verbose = false) const; protected: static void _bind_methods(); }; using MapType = Terrain3DRegion::MapType; VARIANT_ENUM_CAST(Terrain3DRegion::MapType); constexpr Terrain3DRegion::MapType TYPE_HEIGHT = Terrain3DRegion::MapType::TYPE_HEIGHT; constexpr Terrain3DRegion::MapType TYPE_CONTROL = Terrain3DRegion::MapType::TYPE_CONTROL; constexpr Terrain3DRegion::MapType TYPE_COLOR = Terrain3DRegion::MapType::TYPE_COLOR; constexpr Terrain3DRegion::MapType TYPE_MAX = Terrain3DRegion::MapType::TYPE_MAX; constexpr inline const Image::Format *FORMAT = Terrain3DRegion::FORMAT; constexpr inline const char **TYPESTR = Terrain3DRegion::TYPESTR; constexpr inline const Color *COLOR = Terrain3DRegion::COLOR; // Inline functions inline void Terrain3DRegion::update_height(const real_t p_height) { if (p_height < _height_range.x) { _height_range.x = p_height; _modified = true; } else if (p_height > _height_range.y) { _height_range.y = p_height; _modified = true; } } inline void Terrain3DRegion::update_heights(const Vector2 &p_low_high) { if (p_low_high.x < _height_range.x) { _height_range.x = p_low_high.x; _modified = true; } if (p_low_high.y > _height_range.y) { _height_range.y = p_low_high.y; _modified = true; } } #endif // TERRAIN3D_REGION_CLASS_H ================================================ FILE: src/terrain_3d_texture_asset.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include "logger.h" #include "terrain_3d.h" #include "terrain_3d_texture_asset.h" /////////////////////////// // Private Functions /////////////////////////// // Note a null texture is considered a valid format bool Terrain3DTextureAsset::_is_valid_format(const Ref &p_texture) const { if (p_texture.is_null()) { LOG(DEBUG, "Provided texture is null."); return true; } Ref img = p_texture->get_image(); Image::Format format = Image::FORMAT_MAX; if (img.is_valid()) { format = img->get_format(); } if (format < 0 || format >= Image::FORMAT_MAX) { LOG(ERROR, "Invalid texture format. See documentation for format specification."); return false; } return true; } /////////////////////////// // Public Functions /////////////////////////// void Terrain3DTextureAsset::initialize() { LOG(INFO, _id, ": ", _name, ": initializing asset"); } void Terrain3DTextureAsset::clear() { LOG(INFO, "Clearing TextureAsset"); _name = "New Texture"; _id = 0; _highlighted = false; _highlight_color = Color(); _albedo_color = Color(1.0f, 1.0f, 1.0f, 1.0f); _albedo_texture.unref(); _normal_texture.unref(); _thumbnail.unref(); _normal_depth = 1.0f; _ao_strength = 0.5f; _ao_light_affect = 0.0f; _roughness = 0.f; _displacement_scale = 0.f; _displacement_offset = 0.f; _uv_scale = 0.1f; _detiling_rotation = 0.0f; _detiling_shift = 0.0f; } void Terrain3DTextureAsset::set_name(const String &p_name) { if (p_name.length() > 96) { LOG(WARN, "Name too long, truncating to 96 characters"); } SET_IF_DIFF(_name, p_name.left(96)); LOG(INFO, "Setting name: ", _name); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_id(const int p_new_id) { int old_id = _id; SET_IF_DIFF(_id, CLAMP(p_new_id, 0, Terrain3DAssets::MAX_TEXTURES - 1)); LOG(INFO, "Setting texture id: ", _id); LOG(DEBUG, "Emitting id_changed, TYPE_TEXTURE, ", old_id, ", ", _id); emit_signal("id_changed", Terrain3DAssets::TYPE_TEXTURE, old_id, _id); } void Terrain3DTextureAsset::set_highlighted(const bool p_highlighted) { SET_IF_DIFF(_highlighted, p_highlighted); LOG(INFO, "Set mesh ID ", _id, " highlight: ", p_highlighted); real_t random_float = real_t(rand()) / real_t(RAND_MAX); _highlight_color.set_hsv(random_float, 1.f, 1.f, 1.f); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_albedo_color(const Color &p_color) { SET_IF_DIFF(_albedo_color, p_color); LOG(INFO, "Setting color: ", p_color); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_albedo_texture(const Ref &p_texture) { if (_is_valid_format(p_texture)) { SET_IF_DIFF(_albedo_texture, p_texture); LOG(INFO, "Setting albedo texture: ", p_texture); if (p_texture.is_valid()) { String filename = p_texture->get_path().get_file().get_basename(); if (_name == "New Texture" && !p_texture->get_path().contains("::") && !filename.is_empty()) { _name = filename; LOG(INFO, "Setting name based on filename: ", _name); } Ref img = p_texture->get_image(); if (!img->has_mipmaps()) { LOG(WARN, "Albedo texture '", filename, "' has no mipmaps. Change on the Import panel if desired."); } if (img->get_width() != img->get_height()) { LOG(WARN, "Albedo texture '", filename, "' is not square. Mipmaps might have artifacts."); } if (!is_power_of_2(img->get_width()) || !is_power_of_2(img->get_height())) { LOG(WARN, "Albedo texture '", filename, "' size is not power of 2. This is sub-optimal."); } if (IS_EDITOR) { img = img->duplicate(); img->decompress(); img->resize(512, 512, Image::INTERPOLATE_LANCZOS); img->convert(Image::FORMAT_RGB8); _thumbnail = ImageTexture::create_from_image(img); } } else { _thumbnail.unref(); } LOG(DEBUG, "Emitting file_changed"); emit_signal("file_changed"); } } void Terrain3DTextureAsset::set_normal_texture(const Ref &p_texture) { if (_is_valid_format(p_texture)) { SET_IF_DIFF(_normal_texture, p_texture); LOG(INFO, "Setting normal texture: ", p_texture); if (p_texture.is_valid()) { String filename = p_texture->get_path().get_file().get_basename(); Ref img = p_texture->get_image(); if (!img->has_mipmaps()) { LOG(WARN, "Normal texture '", filename, "' has no mipmaps. Change on the Import panel if desired."); } if (img->get_width() != img->get_height()) { LOG(WARN, "Normal texture '", filename, "' is not square. Not recommended. Mipmaps might have artifacts."); } if (!is_power_of_2(img->get_width()) || !is_power_of_2(img->get_height())) { LOG(WARN, "Normal texture '", filename, "' dimensions are not power of 2. This is sub-optimal."); } } LOG(DEBUG, "Emitting file_changed"); emit_signal("file_changed"); } } void Terrain3DTextureAsset::set_normal_depth(const real_t p_normal_depth) { SET_IF_DIFF(_normal_depth, CLAMP(p_normal_depth, 0.0f, 2.0f)); LOG(INFO, "Setting normal_depth: ", _normal_depth); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_roughness(const real_t p_roughness) { SET_IF_DIFF(_roughness, CLAMP(p_roughness, -1.0f, 1.0f)); LOG(INFO, "Setting roughness modifier: ", _roughness); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_ao_strength(const real_t p_ao_strength) { SET_IF_DIFF(_ao_strength, CLAMP(p_ao_strength, 0.0f, 1.0f)); LOG(INFO, "Setting ao_strength: ", _ao_strength); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_ao_light_affect(const real_t p_ao_light_affect) { SET_IF_DIFF(_ao_light_affect, CLAMP(p_ao_light_affect, 0.0f, 1.0f)); LOG(INFO, "Setting ao_light_affect: ", _ao_light_affect); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_displacement_scale(const real_t p_displacement_scale) { SET_IF_DIFF(_displacement_scale, CLAMP(p_displacement_scale, 0.0f, 2.0f)); LOG(INFO, "Setting displacement_scale: ", _displacement_scale); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_displacement_offset(const real_t p_displacement_offset) { SET_IF_DIFF(_displacement_offset, CLAMP(p_displacement_offset, -1.0f, 1.0f)); LOG(INFO, "Setting displacement_offset: ", _displacement_offset); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_uv_scale(const real_t p_scale) { SET_IF_DIFF(_uv_scale, CLAMP(p_scale, 0.001f, 100.0f)); LOG(INFO, "Setting uv_scale: ", _uv_scale); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_detiling_rotation(const real_t p_detiling_rotation) { SET_IF_DIFF(_detiling_rotation, CLAMP(p_detiling_rotation, 0.0f, 1.0f)); LOG(INFO, "Setting detiling_rotation: ", _detiling_rotation); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_detiling_shift(const real_t p_detiling_shift) { SET_IF_DIFF(_detiling_shift, CLAMP(p_detiling_shift, 0.0f, 1.0f)); LOG(INFO, "Setting detiling_shift: ", _detiling_shift); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DTextureAsset::_bind_methods() { ADD_SIGNAL(MethodInfo("id_changed")); ADD_SIGNAL(MethodInfo("file_changed")); ADD_SIGNAL(MethodInfo("setting_changed")); ClassDB::bind_method(D_METHOD("clear"), &Terrain3DTextureAsset::clear); ClassDB::bind_method(D_METHOD("set_name", "name"), &Terrain3DTextureAsset::set_name); ClassDB::bind_method(D_METHOD("get_name"), &Terrain3DTextureAsset::get_name); ClassDB::bind_method(D_METHOD("set_id", "id"), &Terrain3DTextureAsset::set_id); ClassDB::bind_method(D_METHOD("get_id"), &Terrain3DTextureAsset::get_id); ClassDB::bind_method(D_METHOD("set_highlighted", "enabled"), &Terrain3DTextureAsset::set_highlighted); ClassDB::bind_method(D_METHOD("is_highlighted"), &Terrain3DTextureAsset::is_highlighted); ClassDB::bind_method(D_METHOD("get_highlight_color"), &Terrain3DTextureAsset::get_highlight_color); ClassDB::bind_method(D_METHOD("get_thumbnail"), &Terrain3DTextureAsset::get_thumbnail); ClassDB::bind_method(D_METHOD("set_albedo_color", "color"), &Terrain3DTextureAsset::set_albedo_color); ClassDB::bind_method(D_METHOD("get_albedo_color"), &Terrain3DTextureAsset::get_albedo_color); ClassDB::bind_method(D_METHOD("set_albedo_texture", "texture"), &Terrain3DTextureAsset::set_albedo_texture); ClassDB::bind_method(D_METHOD("get_albedo_texture"), &Terrain3DTextureAsset::get_albedo_texture); ClassDB::bind_method(D_METHOD("set_normal_texture", "texture"), &Terrain3DTextureAsset::set_normal_texture); ClassDB::bind_method(D_METHOD("get_normal_texture"), &Terrain3DTextureAsset::get_normal_texture); ClassDB::bind_method(D_METHOD("set_normal_depth", "normal_depth"), &Terrain3DTextureAsset::set_normal_depth); ClassDB::bind_method(D_METHOD("get_normal_depth"), &Terrain3DTextureAsset::get_normal_depth); ClassDB::bind_method(D_METHOD("set_ao_strength", "ao_strength"), &Terrain3DTextureAsset::set_ao_strength); ClassDB::bind_method(D_METHOD("get_ao_strength"), &Terrain3DTextureAsset::get_ao_strength); ClassDB::bind_method(D_METHOD("set_ao_light_affect", "ao_light_affect"), &Terrain3DTextureAsset::set_ao_light_affect); ClassDB::bind_method(D_METHOD("get_ao_light_affect"), &Terrain3DTextureAsset::get_ao_light_affect); ClassDB::bind_method(D_METHOD("set_roughness", "roughness"), &Terrain3DTextureAsset::set_roughness); ClassDB::bind_method(D_METHOD("get_roughness"), &Terrain3DTextureAsset::get_roughness); ClassDB::bind_method(D_METHOD("set_displacement_scale", "displacement_scale"), &Terrain3DTextureAsset::set_displacement_scale); ClassDB::bind_method(D_METHOD("get_displacement_scale"), &Terrain3DTextureAsset::get_displacement_scale); ClassDB::bind_method(D_METHOD("set_displacement_offset", "displacement_offset"), &Terrain3DTextureAsset::set_displacement_offset); ClassDB::bind_method(D_METHOD("get_displacement_offset"), &Terrain3DTextureAsset::get_displacement_offset); ClassDB::bind_method(D_METHOD("set_uv_scale", "scale"), &Terrain3DTextureAsset::set_uv_scale); ClassDB::bind_method(D_METHOD("get_uv_scale"), &Terrain3DTextureAsset::get_uv_scale); ClassDB::bind_method(D_METHOD("set_detiling_rotation", "detiling_rotation"), &Terrain3DTextureAsset::set_detiling_rotation); ClassDB::bind_method(D_METHOD("get_detiling_rotation"), &Terrain3DTextureAsset::get_detiling_rotation); ClassDB::bind_method(D_METHOD("set_detiling_shift", "detiling_shift"), &Terrain3DTextureAsset::set_detiling_shift); ClassDB::bind_method(D_METHOD("get_detiling_shift"), &Terrain3DTextureAsset::get_detiling_shift); ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE), "set_id", "get_id"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_albedo_color", "get_albedo_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "albedo_texture", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture,CompressedTexture2D"), "set_albedo_texture", "get_albedo_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "ImageTexture,CompressedTexture2D"), "set_normal_texture", "get_normal_texture"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "normal_depth", PROPERTY_HINT_RANGE, "0.0, 2.0"), "set_normal_depth", "get_normal_depth"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ao_strength", PROPERTY_HINT_RANGE, "0.0, 1.0"), "set_ao_strength", "get_ao_strength"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ao_light_affect", PROPERTY_HINT_RANGE, "0.0, 1.0"), "set_ao_light_affect", "get_ao_light_affect"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "roughness", PROPERTY_HINT_RANGE, "-1.0, 1.0"), "set_roughness", "get_roughness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_scale", PROPERTY_HINT_RANGE, "0.0, 2.0"), "set_displacement_scale", "get_displacement_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_offset", PROPERTY_HINT_RANGE, "-1.0, 1.0"), "set_displacement_offset", "get_displacement_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv_scale", PROPERTY_HINT_RANGE, "0.001, 2.0, or_greater"), "set_uv_scale", "get_uv_scale"); ADD_GROUP("Detiling", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detiling_rotation", PROPERTY_HINT_RANGE, "0.0, 1.0"), "set_detiling_rotation", "get_detiling_rotation"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detiling_shift", PROPERTY_HINT_RANGE, "0.0, 1.0"), "set_detiling_shift", "get_detiling_shift"); } ================================================ FILE: src/terrain_3d_texture_asset.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_TEXTURE_CLASS_H #define TERRAIN3D_TEXTURE_CLASS_H #include #include "constants.h" #include "terrain_3d_asset_resource.h" class Terrain3DTextureAsset : public Terrain3DAssetResource { GDCLASS(Terrain3DTextureAsset, Terrain3DAssetResource); CLASS_NAME(); friend class Terrain3DAssets; // Saved Data Color _albedo_color = Color(1.f, 1.f, 1.f, 1.f); Ref _albedo_texture; Ref _normal_texture; real_t _normal_depth = 1.0f; real_t _ao_strength = 0.5f; real_t _ao_light_affect = 0.0f; real_t _roughness = 0.f; real_t _displacement_scale = 0.0f; real_t _displacement_offset = 0.0f; real_t _uv_scale = 0.1f; real_t _detiling_rotation = 0.0f; real_t _detiling_shift = 0.0f; // Working Data Color _highlight_color = Color(); bool _is_valid_format(const Ref &p_texture) const; public: Terrain3DTextureAsset() { clear(); } ~Terrain3DTextureAsset() {} void initialize() override; void clear() override; void set_name(const String &p_name) override; String get_name() const override { return _name; } void set_id(const int p_new_id) override; int get_id() const override { return _id; } void set_highlighted(const bool p_highlighted) override; bool is_highlighted() const override { return _highlighted; } Color get_highlight_color() const override { return _highlighted ? _highlight_color : COLOR_WHITE; } Ref get_thumbnail() const override { return _thumbnail; } void set_albedo_color(const Color &p_color); Color get_albedo_color() const { return _albedo_color; } void set_albedo_texture(const Ref &p_texture); Ref get_albedo_texture() const { return _albedo_texture; } void set_normal_texture(const Ref &p_texture); Ref get_normal_texture() const { return _normal_texture; } void set_normal_depth(const real_t p_normal_depth); real_t get_normal_depth() const { return _normal_depth; } void set_ao_strength(const real_t p_ao_strength); real_t get_ao_strength() const { return _ao_strength; } void set_ao_light_affect(const real_t p_ao_light_affect); real_t get_ao_light_affect() const { return _ao_light_affect; } void set_roughness(const real_t p_roughness); real_t get_roughness() const { return _roughness; } void set_displacement_scale(const real_t p_displacement_scale); real_t get_displacement_scale() const { return _displacement_scale; } void set_displacement_offset(const real_t p_displacement_offset); real_t get_displacement_offset() const { return _displacement_offset; } void set_uv_scale(const real_t p_scale); real_t get_uv_scale() const { return _uv_scale; } void set_detiling_rotation(const real_t p_detiling_rotation); real_t get_detiling_rotation() const { return _detiling_rotation; } void set_detiling_shift(const real_t p_detiling_shift); real_t get_detiling_shift() const { return _detiling_shift; } protected: static void _bind_methods(); }; #endif // TERRAIN3D_TEXTURE_CLASS_H ================================================ FILE: src/terrain_3d_util.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include #include #include #include #include "logger.h" #include "terrain_3d_util.h" /////////////////////////// // Public Functions /////////////////////////// void Terrain3DUtil::print_arr(const String &p_name, const Array &p_arr, const int p_level) { LOG(p_level, "Array[", p_arr.size(), "]: ", p_name); for (int i = 0; i < p_arr.size(); i++) { Variant var = p_arr[i]; switch (var.get_type()) { case Variant::ARRAY: { print_arr(p_name + String::num_int64(i), var, p_level); break; } case Variant::DICTIONARY: { print_dict(p_name + String::num_int64(i), var, p_level); break; } case Variant::OBJECT: { Object *obj = cast_to(var); String str = "Object#" + String::num_uint64(obj->get_instance_id()) + ", " + ptr_to_str(obj); LOG(p_level, i, ": ", str); break; } default: { LOG(p_level, i, ": ", p_arr[i]); break; } } } } void Terrain3DUtil::print_dict(const String &p_name, const Dictionary &p_dict, const int p_level) { LOG(p_level, "Dictionary: ", p_name); Array keys = p_dict.keys(); for (const StringName &key : keys) { Variant var = p_dict[key]; switch (var.get_type()) { case Variant::ARRAY: { print_arr(String(key), var, p_level); break; } case Variant::DICTIONARY: { print_dict(String(key), var, p_level); break; } case Variant::OBJECT: { Object *obj = cast_to(var); String str = "Object#" + String::num_uint64(obj->get_instance_id()) + ", " + ptr_to_str(obj); LOG(p_level, "\"", key, "\": ", str); break; } default: { LOG(p_level, "\"", key, "\": Value: ", var); break; } } } } void Terrain3DUtil::dump_gentex(const GeneratedTexture &p_gen, const String &p_name) { LOG(MESG, "Generated ", p_name, " RID: ", p_gen.get_rid(), ", dirty: ", p_gen.is_dirty(), ", image: ", ptr_to_str(*p_gen.get_image())); } void Terrain3DUtil::dump_maps(const TypedArray &p_maps, const String &p_name) { LOG(MESG, "Dumping ", p_name, " array. Size: ", p_maps.size()); for (const Ref &img : p_maps) { LOG(MESG, "Map size: ", img->get_size(), ", format: ", img->get_format(), ", ", ptr_to_str(*img)); } } // Expects a filename in a String like: "terrain3d-01_02.res" which returns (-1, 2) Vector2i Terrain3DUtil::filename_to_location(const String &p_filename) { String location_string = p_filename.trim_prefix("terrain3d").trim_suffix(".res"); return string_to_location(location_string); } // Expects a string formatted as: "±##±##" which returns (##,##) Vector2i Terrain3DUtil::string_to_location(const String &p_string) { String x_str = p_string.left(3).replace("_", ""); String y_str = p_string.right(3).replace("_", ""); if (!x_str.is_valid_int() || !y_str.is_valid_int()) { LOG(ERROR, "Malformed string '", p_string, "'. Result: ", x_str, ", ", y_str); return V2I_MAX; } return Vector2i(x_str.to_int(), y_str.to_int()); } // Expects a v2i(-1,2) and returns terrain3d-01_02.res String Terrain3DUtil::location_to_filename(const Vector2i &p_region_loc) { return "terrain3d" + location_to_string(p_region_loc) + ".res"; } // Expects a v2i(-1,2) and returns -01_02 String Terrain3DUtil::location_to_string(const Vector2i &p_region_loc) { const String POS_REGION_FORMAT = "_%02d"; const String NEG_REGION_FORMAT = "%03d"; String x_str, y_str; x_str = vformat((p_region_loc.x >= 0) ? POS_REGION_FORMAT : NEG_REGION_FORMAT, p_region_loc.x); y_str = vformat((p_region_loc.y >= 0) ? POS_REGION_FORMAT : NEG_REGION_FORMAT, p_region_loc.y); return x_str + y_str; } PackedStringArray Terrain3DUtil::get_files(const String &p_dir, const String &p_glob) { PackedStringArray files; Ref da = DirAccess::open(p_dir); if (da.is_null()) { LOG(ERROR, "Cannot open directory: ", p_dir); return files; } PackedStringArray dir_files = da->get_files(); for (const String &file_name : dir_files) { String fname = file_name.trim_suffix(".remap"); if (!fname.matchn(p_glob)) { continue; } LOG(DEBUG, "Found file: ", p_dir + String("/") + fname); files.push_back(fname); } return files; } Ref Terrain3DUtil::black_to_alpha(const Ref &p_image) { if (p_image.is_null()) { return Ref(); } Ref img = Image::create_empty(p_image->get_width(), p_image->get_height(), false, Image::FORMAT_RGBAF); for (int y = 0; y < img->get_height(); y++) { for (int x = 0; x < img->get_width(); x++) { Color pixel = p_image->get_pixel(x, y); pixel.a = pixel.get_luminance(); img->set_pixel(x, y, pixel); } } if (p_image->has_mipmaps()) { img->generate_mipmaps(); } return img; } /** * Returns the minimum and maximum values for a heightmap (red channel only) */ Vector2 Terrain3DUtil::get_min_max(const Ref &p_image) { if (p_image.is_null()) { LOG(ERROR, "Provided image is not valid. Nothing to analyze"); return V2(INFINITY); } else if (p_image->is_empty()) { LOG(ERROR, "Provided image is empty. Nothing to analyze"); return V2(INFINITY); } Vector2 min_max = Vector2(FLT_MAX, -FLT_MAX); for (int y = 0; y < p_image->get_height(); y++) { for (int x = 0; x < p_image->get_width(); x++) { Color col = p_image->get_pixel(x, y); if (col.r < min_max.x) { min_max.x = col.r; } if (col.r > min_max.y) { min_max.y = col.r; } } } LOG(INFO, "Calculating minimum and maximum values of the image: ", min_max); return min_max; } /** * Returns a Image of a float heightmap normalized to RGB8 greyscale and scaled * Minimum of 8x8 */ Ref Terrain3DUtil::get_thumbnail(const Ref &p_image, const Vector2i &p_size) { if (p_image.is_null()) { LOG(ERROR, "Provided image is not valid. Nothing to process"); return Ref(); } else if (p_image->is_empty()) { LOG(ERROR, "Provided image is empty. Nothing to process"); return Ref(); } Vector2i size = Vector2i(CLAMP(p_size.x, 8, 16384), CLAMP(p_size.y, 8, 16384)); LOG(INFO, "Drawing a thumbnail sized: ", size); // Create a temporary work image scaled to desired width Ref img; img.instantiate(); img->copy_from(p_image); img->resize(size.x, size.y, Image::INTERPOLATE_LANCZOS); // Get minimum and maximum height values on the scaled image Vector2 minmax = get_min_max(img); real_t hmin = minmax.x; real_t hmax = minmax.y; // Define maximum range hmin = std::abs(hmin); hmax = std::abs(hmax) + hmin; // Avoid divide by zero hmax = (hmax == 0) ? 0.001f : hmax; // Create a new image w / normalized values Ref thumb = Image::create_empty(size.x, size.y, false, Image::FORMAT_RGB8); for (int y = 0; y < thumb->get_height(); y++) { for (int x = 0; x < thumb->get_width(); x++) { Color col = img->get_pixel(x, y); col.r = (col.r + hmin) / hmax; col.g = col.r; col.b = col.r; thumb->set_pixel(x, y, col); } } return thumb; } /* Get an Image filled with specified color and format * If p_color.a < 0, fill with checkered pattern multiplied by p_color.rgb * * Behavior changes if a compressed format is requested: * If the editor is running and format is DXT1/5, BPTC_RGBA, it returns a filled image. * Otherwise, it returns a blank image in that format. * * The reason is the Image compression library is available only in the editor. And it is * unreliable, offering little control over the output format, choosing automatically and * often wrong. We have selected a few compressed formats it gets right. */ Ref Terrain3DUtil::get_filled_image(const Vector2i &p_size, const Color &p_color, const bool p_create_mipmaps, const Image::Format p_format) { Image::Format format = p_format; if (format < 0 || format >= Image::FORMAT_MAX) { format = Image::FORMAT_DXT5; } Image::CompressMode compression_format = Image::COMPRESS_MAX; Image::UsedChannels channels = Image::USED_CHANNELS_RGBA; bool compress = false; bool fill_image = true; if (format >= Image::Format::FORMAT_DXT1) { switch (format) { case Image::FORMAT_DXT1: format = Image::FORMAT_RGB8; channels = Image::USED_CHANNELS_RGB; compression_format = Image::COMPRESS_S3TC; compress = true; break; case Image::FORMAT_DXT5: format = Image::FORMAT_RGBA8; channels = Image::USED_CHANNELS_RGBA; compression_format = Image::COMPRESS_S3TC; compress = true; break; case Image::FORMAT_BPTC_RGBA: format = Image::FORMAT_RGBA8; channels = Image::USED_CHANNELS_RGBA; compression_format = Image::COMPRESS_BPTC; compress = true; break; default: compress = false; fill_image = false; break; } } Ref img = Image::create_empty(p_size.x, p_size.y, p_create_mipmaps, format); Color color = p_color; if (fill_image) { if (color.a < 0.0f) { color.a = 1.0f; Color col_a = Color(0.8f, 0.8f, 0.8f, 1.0) * color; Color col_b = Color(0.5f, 0.5f, 0.5f, 1.0) * color; img->fill_rect(Rect2i(V2I_ZERO, p_size / 2), col_a); img->fill_rect(Rect2i(p_size / 2, p_size / 2), col_a); img->fill_rect(Rect2i(Vector2(p_size.x, 0) / 2, p_size / 2), col_b); img->fill_rect(Rect2i(Vector2(0, p_size.y) / 2, p_size / 2), col_b); } else { img->fill(color); } if (p_create_mipmaps) { img->generate_mipmaps(); } } if (compress && IS_EDITOR) { img->compress_from_channels(compression_format, channels); } return img; } /** * Loads a file from disk and returns an Image * Parameters: * p_filename - file on disk to load. EXR, R16/RAW, PNG, or a ResourceLoader format (jpg, res, tres, etc) * p_cache_mode - Send this flag to the resource loader to force caching or not * p_height_range - R16 format: x=Min & y=Max value ranges. Required for R16 import * p_size - R16 format: Image dimensions. Default (0,0) auto detects f/ square images. Required f/ non-square R16 */ Ref Terrain3DUtil::load_image(const String &p_file_name, const int p_cache_mode, const Vector2 &p_r16_height_range, const Vector2i &p_r16_size) { if (p_file_name.is_empty()) { LOG(ERROR, "No file specified. Nothing imported"); return Ref(); } if (!FileAccess::file_exists(p_file_name)) { LOG(ERROR, "File ", p_file_name, " does not exist. Nothing to import"); return Ref(); } // Load file based on extension Ref img; LOG(INFO, "Attempting to load: ", p_file_name); String ext = p_file_name.get_extension().to_lower(); PackedStringArray imgloader_extensions = PackedStringArray(Array::make("bmp", "dds", "exr", "hdr", "jpg", "jpeg", "png", "tga", "svg", "webp")); // If R16 integer format (read/writeable by Krita) if (ext == "r16" || ext == "raw") { LOG(DEBUG, "Loading file as an r16"); Ref file = FileAccess::open(p_file_name, FileAccess::READ); // If p_size is zero, assume square and try to auto detect size Vector2i r16_size = p_r16_size; if (r16_size <= V2I_ZERO) { file->seek_end(); int fsize = file->get_position(); int fwidth = sqrt(fsize / 2); r16_size = V2I(fwidth); LOG(DEBUG, "Total file size is: ", fsize, " calculated width: ", fwidth, " dimensions: ", r16_size); file->seek(0); } img = Image::create_empty(r16_size.x, r16_size.y, false, FORMAT[TYPE_HEIGHT]); for (int y = 0; y < r16_size.y; y++) { for (int x = 0; x < r16_size.x; x++) { real_t h = real_t(file->get_16()) / 65535.0f; h = h * (p_r16_height_range.y - p_r16_height_range.x) + p_r16_height_range.x; img->set_pixel(x, y, Color(h, 0.f, 0.f)); } } // If an Image extension, use Image loader } else if (imgloader_extensions.has(ext)) { LOG(DEBUG, "ImageFormatLoader loading recognized file type: ", ext); img = Image::load_from_file(p_file_name); // Else, see if Godot's resource loader will read it as an image: RES, TRES, etc } else { LOG(DEBUG, "Loading file as a resource"); img = ResourceLoader::get_singleton()->load(p_file_name, "", static_cast(p_cache_mode)); } if (!img.is_valid()) { LOG(ERROR, "File", p_file_name, " cannot be loaded"); return Ref(); } if (img->is_empty()) { LOG(ERROR, "File", p_file_name, " is empty"); return Ref(); } LOG(DEBUG, "Loaded Image size: ", img->get_size(), " format: ", img->get_format()); return img; } /* From source RGB and selected source for Alpha channel, create a new RGBA image. * If p_invert_green is true, the destination green channel will be 1.0 - input green channel. * If p_invert_alpha is true, the destination alpha channel will be 1.0 - input source channel. */ Ref Terrain3DUtil::pack_image(const Ref &p_src_rgb, const Ref &p_src_a, const Ref &p_src_ao, const bool p_invert_green, const bool p_invert_alpha, const bool p_normalize_alpha, const int p_alpha_channel, const int p_ao_channel) { if (!p_src_rgb.is_valid() || !p_src_a.is_valid()) { LOG(ERROR, "Provided images are not valid. Cannot pack"); return Ref(); } if (p_src_rgb->get_size() != p_src_a->get_size()) { LOG(ERROR, "Provided images are not the same size. Cannot pack"); return Ref(); } if (p_src_rgb->is_empty() || p_src_a->is_empty()) { LOG(ERROR, "Provided images are empty. Cannot pack"); return Ref(); } if (p_alpha_channel < 0 || p_alpha_channel > 3) { LOG(ERROR, "Source Channel of Height/Roughness invalid. Cannot Pack"); return Ref(); } bool pack_ao = p_src_ao.is_valid(); if (pack_ao) { if (p_src_rgb->get_size() != p_src_ao->get_size()) { LOG(ERROR, "Provided AO and normal images are not the same size. Cannot pack"); return Ref(); } } real_t a_max = 0.0f; real_t a_min = 0.0f; real_t contrast = 1.0f; if (p_normalize_alpha) { a_min = 1.0f; // Calculate contrast and offset so that we can make full use of the height channel range. for (int y = 0; y < p_src_rgb->get_height(); y++) { for (int x = 0; x < p_src_rgb->get_width(); x++) { real_t h = p_src_a->get_pixel(x, y)[p_alpha_channel]; a_max = MAX(h, a_max); a_min = MIN(h, a_min); } } contrast /= MAX(a_max - a_min, 1e-6f); } Ref dst = Image::create_empty(p_src_rgb->get_width(), p_src_rgb->get_height(), false, Image::FORMAT_RGBA8); LOG(INFO, "Creating image from source RGB + source channel images"); for (int y = 0; y < p_src_rgb->get_height(); y++) { for (int x = 0; x < p_src_rgb->get_width(); x++) { Color col = p_src_rgb->get_pixel(x, y); col.a = p_src_a->get_pixel(x, y)[p_alpha_channel]; if (p_normalize_alpha) { col.a = CLAMP((col.a * contrast - a_min), 0.0f, 1.0f); } if (p_invert_green) { col.g = 1.0f - col.g; } if (p_invert_alpha) { col.a = 1.0f - col.a; } if (pack_ao) { // Compress range to avoid low AO values completely destroying normal vector precision - recovered in shader. real_t ao = sqrt(p_src_ao->get_pixel(x, y)[p_ao_channel]) * 0.5 + 0.5; col.r = col.r * ao + (1.0f - ao) * 0.5f; col.g = col.g * ao + (1.0f - ao) * 0.5f; col.b = col.b * ao + (1.0f - ao) * 0.5f; } dst->set_pixel(x, y, col); } } return dst; } // From source RGB, create a new L image that is scaled to use full 0 - 1 range. Ref Terrain3DUtil::luminance_to_height(const Ref &p_src_rgb) { if (!p_src_rgb.is_valid()) { LOG(ERROR, "Provided images are not valid. Cannot pack"); return Ref(); } if (p_src_rgb->is_empty()) { LOG(ERROR, "Provided images are empty. Cannot pack"); return Ref(); } real_t lum_contrast; real_t l_max = 0.0f; real_t l_min = 1.0f; // Calculate contrast and offset so that we can make the most use of the height channel range. for (int y = 0; y < p_src_rgb->get_height(); y++) { for (int x = 0; x < p_src_rgb->get_width(); x++) { Color col = p_src_rgb->get_pixel(x, y); real_t l = 0.299f * col.r + 0.587f * col.g + 0.114f * col.b; l_max = MAX(l, l_max); l_min = MIN(l, l_min); } } lum_contrast = 1.0f / MAX(l_max - l_min, 1e-6); Ref dst = Image::create_empty(p_src_rgb->get_width(), p_src_rgb->get_height(), false, Image::FORMAT_RGB8); for (int y = 0; y < p_src_rgb->get_height(); y++) { for (int x = 0; x < p_src_rgb->get_width(); x++) { Color col = p_src_rgb->get_pixel(x, y); real_t lum = 0.299f * col.r + 0.587f * col.g + 0.114f * col.b; lum = CLAMP((lum * lum_contrast - l_min), 0.0f, 1.0f); // some shaping col.r = 0.5f - sin(asin(1.0f - 2.0f * lum) / 3.0f); col.g = col.r; col.b = col.r; col.a = col.r; dst->set_pixel(x, y, col); } } return dst; } void Terrain3DUtil::benchmark(Terrain3D *p_terrain) { if (!p_terrain) { return; } const Terrain3DData *data = p_terrain->get_data(); if (!data) { return; } uint64_t start_time; Vector3 vec; Color col; for (int i = 0; i < 3; i++) { start_time = Time::get_singleton()->get_ticks_msec(); for (int j = 0; j < 10000000; j++) { col = data->get_pixel(TYPE_HEIGHT, vec); } LOG(MESG, "get_pixel() 10M: ", Time::get_singleton()->get_ticks_msec() - start_time, "ms"); } vec = Vector3(0.5f, 0.f, 0.5f); for (int i = 0; i < 3; i++) { start_time = Time::get_singleton()->get_ticks_msec(); for (int j = 0; j < 1000000; j++) { data->get_height(vec); } LOG(MESG, "get_height() 1M interpolated: ", Time::get_singleton()->get_ticks_msec() - start_time, "ms"); } for (int i = 0; i < 2; i++) { start_time = Time::get_singleton()->get_ticks_msec(); p_terrain->bake_mesh(0); LOG(MESG, "Bake ArrayMesh: ", Time::get_singleton()->get_ticks_msec() - start_time, "ms"); } } /////////////////////////// // Protected Functions /////////////////////////// void Terrain3DUtil::_bind_methods() { // Control map converters ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("as_float", "value"), &as_float); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("as_uint", "value"), &as_uint); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_base", "pixel"), &gd_get_base); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_base", "base"), &gd_enc_base); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_overlay", "pixel"), &gd_get_overlay); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_overlay", "overlay"), &gd_enc_overlay); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_blend", "pixel"), &gd_get_blend); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_blend", "blend"), &gd_enc_blend); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_uv_rotation", "pixel"), &gd_get_uv_rotation); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_uv_rotation", "rotation"), &gd_enc_uv_rotation); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_uv_scale", "pixel"), &gd_get_uv_scale); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_uv_scale", "scale"), &gd_enc_uv_scale); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("is_hole", "pixel"), &gd_is_hole); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_hole", "pixel"), &enc_hole); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("is_nav", "pixel"), &gd_is_nav); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_nav", "pixel"), &enc_nav); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("is_auto", "pixel"), &gd_is_auto); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_auto", "pixel"), &enc_auto); // String functions ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("filename_to_location", "filename"), &Terrain3DUtil::filename_to_location); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("location_to_filename", "region_location"), &Terrain3DUtil::location_to_filename); // Image handling ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("black_to_alpha", "image"), &Terrain3DUtil::black_to_alpha); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_min_max", "image"), &Terrain3DUtil::get_min_max); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_thumbnail", "image", "size"), &Terrain3DUtil::get_thumbnail, DEFVAL(V2I(256))); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_filled_image", "size", "color", "create_mipmaps", "format"), &Terrain3DUtil::get_filled_image); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("load_image", "file_name", "cache_mode", "r16_height_range", "r16_size"), &Terrain3DUtil::load_image, DEFVAL(ResourceLoader::CACHE_MODE_IGNORE), DEFVAL(Vector2(0.f, 255.f)), DEFVAL(V2I_ZERO)); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("pack_image", "src_rgb", "src_a", "src_ao", "invert_green", "invert_alpha", "normalize_alpha", "alpha_channel", "ao_channel"), &Terrain3DUtil::pack_image, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(0), DEFVAL(0)); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("luminance_to_height", "src_rgb"), &Terrain3DUtil::luminance_to_height); } ================================================ FILE: src/terrain_3d_util.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef TERRAIN3D_UTIL_CLASS_H #define TERRAIN3D_UTIL_CLASS_H #include #include #include #include #include "constants.h" #include "generated_texture.h" class Terrain3D; // This file holds stateless utility functions for both C++ and GDScript // The class exposes static member and inline functions to GDscript // The inline functions below are not part of the class but are in the namespace, eg bilerp // However some of these inline functions are also exposed to GDScript class Terrain3DUtil : public Object { GDCLASS(Terrain3DUtil, Object); CLASS_NAME_STATIC("Terrain3DUtil"); public: // Print info to the console static void print_arr(const String &p_name, const Array &p_arr, const int p_level = 2); // Level 2: DEBUG static void print_dict(const String &p_name, const Dictionary &p_dict, const int p_level = 2); // Level 2: DEBUG static void dump_gentex(const GeneratedTexture &p_gen, const String &p_name = ""); static void dump_maps(const TypedArray &p_maps, const String &p_name = ""); // String functions static Vector2i filename_to_location(const String &p_filename); static Vector2i string_to_location(const String &p_string); static String location_to_filename(const Vector2i &p_region_loc); static String location_to_string(const Vector2i &p_region_loc); static PackedStringArray get_files(const String &p_dir, const String &p_glob = "*"); // Image operations static Ref black_to_alpha(const Ref &p_image); static Vector2 get_min_max(const Ref &p_image); static Ref get_thumbnail(const Ref &p_image, const Vector2i &p_size = V2I(256)); static Ref get_filled_image(const Vector2i &p_size, const Color &p_color = COLOR_BLACK, const bool p_create_mipmaps = true, const Image::Format p_format = Image::FORMAT_MAX); static Ref load_image(const String &p_file_name, const int p_cache_mode = ResourceLoader::CACHE_MODE_IGNORE, const Vector2 &p_r16_height_range = Vector2(0.f, 255.f), const Vector2i &p_r16_size = V2I_ZERO); static Ref pack_image(const Ref &p_src_rgb, const Ref &p_src_a, const Ref &p_src_ao, const bool p_invert_green = false, const bool p_invert_alpha = false, const bool p_normalize_alpha = false, const int p_alpha_channel = 0, const int p_ao_channel = 0); static Ref luminance_to_height(const Ref &p_src_rgb); static void benchmark(Terrain3D *p_terrain); protected: static void _bind_methods(); }; using Util = Terrain3DUtil; // Inline Functions /////////////////////////// // Type Conversion /////////////////////////// // Convert Vector3 to Vector2i, ignoring Y inline Vector2i v3v2i(const Vector3 &p_v3) { return Vector2i(p_v3.x, p_v3.z); } // Convert Vector2i to Vector3, ignoring Y inline Vector3 v2iv3(const Vector2i &p_v2) { return Vector3(p_v2.x, 0., p_v2.y); } // Convert Vector3 to Vector2, ignoring Y inline Vector2 v3v2(const Vector3 &p_v3) { return Vector2(p_v3.x, p_v3.z); } // Convert Vector2 to Vector3, ignoring Y inline Vector3 v2v3(const Vector2 &p_v2) { return Vector3(p_v2.x, 0., p_v2.y); } /////////////////////////// // Math /////////////////////////// inline bool is_valid_region_size(int value) { return value >= 64 && value <= 2048 && is_power_of_2(value); } // Integer round to multiples // https://stackoverflow.com/questions/3407012/rounding-up-to-the-nearest-multiple-of-a-number // Integer round up to a multiple template inline T int_ceil_mult(const T numToRound, const T multiple) { static_assert(std::numeric_limits::is_integer, "Only integer types are allowed"); ASSERT(multiple != 0, 0); T isPositive = (T)(numToRound >= 0); return ((numToRound + isPositive * (multiple - 1)) / multiple) * multiple; } // Integer round up to a power of 2 multiple (3.7x faster) template inline T int_ceil_pow2(T numToRound, T multiple) { static_assert(std::numeric_limits::is_integer, "Only integer types are allowed"); ASSERT(is_power_of_2(multiple), int_ceil_mult(numToRound, multiple)); return (numToRound + multiple - 1) & -multiple; } // Integer round to nearest +/- multiple // https://stackoverflow.com/questions/29557459/round-to-nearest-multiple-of-a-number template inline T int_round_mult(const T numToRound, const T multiple) { static_assert(std::numeric_limits::is_integer, "Only integer types are allowed"); ASSERT(multiple != 0, 0); T abs_num; if constexpr (std::is_signed_v) { abs_num = std::abs(numToRound); } else { abs_num = numToRound; } T result = abs_num + multiple / 2; result -= result % multiple; result *= numToRound > 0 ? 1 : -1; return result; } // Integer division with rounding up, down, nearest // https : //stackoverflow.com/questions/2422712/rounding-integer-division-instead-of-truncating/58568736#58568736 #define V2I_DIVIDE_CEIL(v, f) Vector2i(int_divide_ceil(v.x, int32_t(f)), int_divide_ceil(v.y, int32_t(f))) #define V2I_DIVIDE_FLOOR(v, f) Vector2i(int_divide_floor(v.x, int32_t(f)), int_divide_floor(v.y, int32_t(f))) // Integer division rounding up template inline T int_divide_ceil(const T numer, const T denom) { static_assert(std::numeric_limits::is_integer, "Only integer types are allowed"); T result = ((numer) < 0) != ((denom) < 0) ? (numer) / (denom) : ((numer) + ((denom) < 0 ? (denom) + 1 : (denom)-1)) / (denom); return result; } // Integer division rounding down template inline T int_divide_floor(const T numer, const T denom) { static_assert(std::numeric_limits::is_integer, "Only integer types are allowed"); T result = ((numer) < 0) != ((denom) < 0) ? ((numer) - ((denom) < 0 ? (denom) + 1 : (denom)-1)) / (denom) : (numer) / (denom); return result; } // Integer division rounding to nearest int template inline T int_divide_round(const T numer, const T denom) { static_assert(std::numeric_limits::is_integer, "Only integer types are allowed"); T result = ((numer) < 0) != ((denom) < 0) ? ((numer) - ((denom) / 2)) / (denom) : ((numer) + ((denom) / 2)) / (denom); return result; } // Returns the bilinearly interpolated value derived from parameters: // * 4 values to be interpolated // * Positioned at the 4 corners of the p_pos00 - p_pos11 rectangle // * Interpolated to the position p_pos, which is global, not a 0-1 percentage inline real_t bilerp(const real_t p_v00, const real_t p_v01, const real_t p_v10, const real_t p_v11, const Vector2 &p_pos00, const Vector2 &p_pos11, const Vector2 &p_pos) { real_t x2x1 = p_pos11.x - p_pos00.x; real_t y2y1 = p_pos11.y - p_pos00.y; real_t x2x = p_pos11.x - p_pos.x; real_t y2y = p_pos11.y - p_pos.y; real_t xx1 = p_pos.x - p_pos00.x; real_t yy1 = p_pos.y - p_pos00.y; return (p_v00 * x2x * y2y + p_v01 * x2x * yy1 + p_v10 * xx1 * y2y + p_v11 * xx1 * yy1) / (x2x1 * y2y1); } inline real_t bilerp(const real_t p_v00, const real_t p_v01, const real_t p_v10, const real_t p_v11, const Vector3 &p_pos00, const Vector3 &p_pos11, const Vector3 &p_pos) { Vector2 pos00 = Vector2(p_pos00.x, p_pos00.z); Vector2 pos11 = Vector2(p_pos11.x, p_pos11.z); Vector2 pos = Vector2(p_pos.x, p_pos.z); return bilerp(p_v00, p_v01, p_v10, p_v11, pos00, pos11, pos); } inline Rect2 aabb2rect(const AABB &p_aabb) { Rect2 rect; rect.position = Vector2(p_aabb.position.x, p_aabb.position.z); rect.size = Vector2(p_aabb.size.x, p_aabb.size.z); return rect; } // Functions to match GLSL when needing to duplicate shader code CPU side. inline real_t smoothstep(const real_t p_low, const real_t p_high, const real_t p_value) { real_t t = CLAMP((p_value - p_low) / (p_high - p_low), 0.f, 1.f); return t * t * (3.f - 2.f * t); } inline Vector2 smoothstep(const real_t p_low, const real_t p_high, const Vector2 &p_value) { Vector2 t = (p_value - Vector2(p_low, p_low)) / (p_high - p_low); t.x = CLAMP(t.x, 0.f, 1.f); t.y = CLAMP(t.y, 0.f, 1.f); return t * t * (Vector2(3.f, 3.f) - 2.f * t); } inline Vector3 smoothstep(const real_t p_low, const real_t p_high, const Vector3 &p_value) { Vector3 t = (p_value - Vector3(p_low, p_low, p_low)) / (p_high - p_low); t.x = CLAMP(t.x, 0.f, 1.f); t.y = CLAMP(t.y, 0.f, 1.f); t.z = CLAMP(t.z, 0.f, 1.f); return t * t * (Vector3(3.f, 3.f, 3.f) - 2.f * t); } /////////////////////////// // Controlmap Handling /////////////////////////// // Getters read the 32-bit float as a 32-bit uint, then mask bits to retreive value // Encoders return a full 32-bit uint with bits in the proper place for ORing // Aliases for GDScript prefixed with gd_ since it can't handle overridden functions inline float as_float(const uint32_t p_value) { return *(float *)&p_value; } inline uint32_t as_uint(const float p_value) { return *(uint32_t *)&p_value; } inline uint8_t get_base(const uint32_t p_pixel) { return p_pixel >> 27 & 0x1F; } inline uint8_t get_base(const float p_pixel) { return get_base(as_uint(p_pixel)); } inline uint32_t enc_base(const uint8_t p_base) { return (p_base & 0x1F) << 27; } inline uint32_t gd_get_base(const uint32_t p_pixel) { return get_base(p_pixel); } inline uint32_t gd_enc_base(const uint32_t p_base) { return enc_base(p_base); } inline uint8_t get_overlay(const uint32_t p_pixel) { return p_pixel >> 22 & 0x1F; } inline uint8_t get_overlay(const float p_pixel) { return get_overlay(as_uint(p_pixel)); } inline uint32_t enc_overlay(const uint8_t p_over) { return (p_over & 0x1F) << 22; } inline uint32_t gd_get_overlay(const uint32_t p_pixel) { return get_overlay(p_pixel); } inline uint32_t gd_enc_overlay(const uint32_t p_over) { return enc_overlay(p_over); } inline uint8_t get_blend(const uint32_t p_pixel) { return p_pixel >> 14 & 0xFF; } inline uint8_t get_blend(const float p_pixel) { return get_blend(as_uint(p_pixel)); } inline uint32_t enc_blend(const uint8_t p_blend) { return (p_blend & 0xFF) << 14; } inline uint32_t gd_get_blend(const uint32_t p_pixel) { return get_blend(p_pixel); } inline uint32_t gd_enc_blend(const uint32_t p_blend) { return enc_blend(p_blend); } inline uint8_t get_uv_rotation(const uint32_t p_pixel) { return p_pixel >> 10 & 0xF; } inline uint8_t get_uv_rotation(const float p_pixel) { return get_uv_rotation(as_uint(p_pixel)); } inline uint32_t enc_uv_rotation(const uint8_t p_rotation) { return (p_rotation & 0xF) << 10; } inline uint32_t gd_get_uv_rotation(const uint32_t p_pixel) { return get_uv_rotation(p_pixel); } inline uint32_t gd_enc_uv_rotation(const uint32_t p_rotation) { return enc_uv_rotation(p_rotation); } inline uint8_t get_uv_scale(const uint32_t p_pixel) { return p_pixel >> 7 & 0x7; } inline uint8_t get_uv_scale(const float p_pixel) { return get_uv_scale(as_uint(p_pixel)); } inline uint32_t enc_uv_scale(const uint8_t p_scale) { return (p_scale & 0x7) << 7; } inline uint32_t gd_get_uv_scale(const uint32_t p_pixel) { return get_uv_scale(p_pixel); } inline uint32_t gd_enc_uv_scale(const uint32_t p_scale) { return enc_uv_scale(p_scale); } inline bool is_hole(const uint32_t p_pixel) { return (p_pixel >> 2 & 0x1) == 1; } inline bool is_hole(const float p_pixel) { return is_hole(as_uint(p_pixel)); } inline uint32_t enc_hole(const bool p_hole) { return (p_hole & 0x1) << 2; } inline bool gd_is_hole(const uint32_t p_pixel) { return is_hole(p_pixel); } inline bool is_nav(const uint32_t p_pixel) { return (p_pixel >> 1 & 0x1) == 1; } inline bool is_nav(const float p_pixel) { return is_nav(as_uint(p_pixel)); } inline uint32_t enc_nav(const bool p_nav) { return (p_nav & 0x1) << 1; } inline bool gd_is_nav(const uint32_t p_pixel) { return is_nav(p_pixel); } inline bool is_auto(const uint32_t p_pixel) { return (p_pixel & 0x1) == 1; } inline bool is_auto(const float p_pixel) { return is_auto(as_uint(p_pixel)); } inline uint32_t enc_auto(const bool p_auto) { return p_auto & 0x1; } inline bool gd_is_auto(const uint32_t p_pixel) { return is_auto(p_pixel); } /////////////////////////// // Memory /////////////////////////// template _FORCE_INLINE_ bool memdelete_safely(TType *&p_ptr) { if (p_ptr) { memdelete(p_ptr); p_ptr = nullptr; return true; } return false; } _FORCE_INLINE_ bool remove_from_tree(Node *p_node) { // Note: is_in_tree() doesn't work in Godot-cpp 4.1.3 if (p_node) { Node *parent = p_node->get_parent(); if (parent) { parent->remove_child(p_node); return true; } } return false; } _FORCE_INLINE_ String ptr_to_str(const void *p_ptr) { return "0x" + String::num_uint64(uint64_t(p_ptr), 16, true); } // Returns if A is shallow different from B // O(1) size and pointer compare for Array/Dictionary/String/StringName/NodePath // Operator==() otherwise // Two arrays/dicts with different pointers could have the same value, this will return true // Two strings or ints with different pointers but the same value will return false // Could be extended for PackedArray and other special types template _FORCE_INLINE_ bool differs(const T &a, const T &b) { if constexpr (std::is_base_of_v) { if (a.length() != b.length()) { return true; } if (a.is_empty()) { // Both empty return false; } // Both non-empty, but same length if (a.ptr() == b.ptr()) { // Same COW buffer return false; } return a != b; // Full comparison } else if constexpr (std::is_base_of_v || std::is_base_of_v) { if (a.size() != b.size()) { // Redundant but common path predictable return true; } // These store a real GDExtensionTypePtr (void**) at offset 0 return *(const void **)a._native_ptr() != *(const void **)b._native_ptr(); } else { return a != b; } } // Sets A if different from B, otherwise returns #define SET_IF_DIFF(a, b) \ if (differs(a, b)) { \ a = b; \ } else { \ return; \ } #endif // TERRAIN3D_UTIL_CLASS_H ================================================ FILE: src/unit_testing.cpp ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #include "unit_testing.h" #include "terrain_3d_util.h" void test_differs() { UtilityFunctions::print("=== Testing differs function ==="); // Helper to log differs result with expected and PASS/FAIL auto log_differs = [](const auto &a, const auto &b, const String &desc, bool expected) { bool d = differs(a, b); String result = (d == expected) ? "PASSED" : "FAILED"; UtilityFunctions::print(desc, ": differs(", a, ", ", b, ") = ", d, " - ", result); }; // 1. Scalars: int, float (should use ==, differs if values differ) { int i1 = 42; int i2 = 42; // Same int i3 = 43; // Diff log_differs(i1, i2, "int same", false); log_differs(i1, i3, "int diff", true); double f1 = 3.14; double f2 = 3.14; // Same double f3 = 3.14159; // Diff log_differs(f1, f2, "float same", false); log_differs(f1, f3, "float diff", true); } // 2. Vectors: Vector2, Vector2i, Vector3, Vector3i (should use ==) { Vector2 v2_1(1.0, 2.0); Vector2 v2_2(1.0, 2.0); // Same Vector2 v2_3(1.0, 3.0); // Diff log_differs(v2_1, v2_2, "Vector2 same", false); log_differs(v2_1, v2_3, "Vector2 diff", true); Vector2i v2i_1(1, 2); Vector2i v2i_2(1, 2); // Same Vector2i v2i_3(1, 3); // Diff log_differs(v2i_1, v2i_2, "Vector2i same", false); log_differs(v2i_1, v2i_3, "Vector2i diff", true); Vector3 v3_1(1.0, 2.0, 3.0); Vector3 v3_2(1.0, 2.0, 3.0); // Same Vector3 v3_3(1.0, 2.0, 4.0); // Diff log_differs(v3_1, v3_2, "Vector3 same", false); log_differs(v3_1, v3_3, "Vector3 diff", true); Vector3i v3i_1(1, 2, 3); Vector3i v3i_2(1, 2, 3); // Same Vector3i v3i_3(1, 2, 4); // Diff log_differs(v3i_1, v3i_2, "Vector3i same", false); log_differs(v3i_1, v3i_3, "Vector3i diff", true); } // 3. String: Share (COW), same value diff ptr, diff value { String s1 = "test"; String s2 = s1; // Shared (COW) String s3("test"); // Separate alloc, same value String s4 = "diff"; log_differs(String(), String(), "String() vs String()", false); log_differs(String(), String("a"), "String() vs String('a')", true); log_differs(s1, s2, "String shared", false); log_differs(s1, s3, "String same value diff ptr", false); // Length match + == true log_differs(s1, s4, "String diff value", true); // Length may match, but == false } // 4. StringName: Similar to String (uses ==, but test sharing if applicable) { StringName sn1 = "test"; StringName sn2 = sn1; // Shared StringName sn3("test"); // Separate, same value StringName sn4 = "diff"; log_differs(sn1, sn2, "StringName shared", false); log_differs(sn1, sn3, "StringName same value diff ptr", false); // == handles log_differs(sn1, sn4, "StringName diff value", true); } // 5. Array: Share vs mutate { Array arr1; arr1.push_back(42); Array arr2 = arr1; // Shared Array arr3; // Empty diff arr3.push_back(42); // Same content, but separate (differs=true, conservative) Array empty_arr; // Explicit empty for size diff log_differs(arr1, arr2, "Array shared", false); // Same self ptr log_differs(arr1, arr3, "Array same content diff ptr", true); // Diff self ptr (no fallback for Array) log_differs(arr1, empty_arr, "Array size diff", true); // Size mismatch } // 6. TypedArray: e.g., TypedArray { TypedArray ta1; ta1.push_back(42); TypedArray ta2 = ta1; // Shared TypedArray ta3; // Empty ta3.push_back(42); // Separate TypedArray empty_ta; // Explicit empty log_differs(ta1, ta2, "TypedArray shared", false); // Via Array base log_differs(ta1, ta3, "TypedArray same content diff ptr", true); // Diff self log_differs(ta1, empty_ta, "TypedArray size diff", true); // Size mismatch } // 7. Dictionary: Share vs mutate { Dictionary dict1; dict1["key"] = 42; Dictionary dict2 = dict1; // Shared Dictionary dict3; // Empty dict3["key"] = 42; // Separate Dictionary empty_dict; // Explicit empty log_differs(dict1, dict2, "Dictionary shared", false); log_differs(dict1, dict3, "Dictionary same content diff ptr", true); // Diff self log_differs(dict1, empty_dict, "Dictionary size diff", true); // Size mismatch } // 8. Variant types: Wrap above (falls to ==) { Variant v_int1 = 42; Variant v_int2 = 42; // Same Variant v_int3 = 43; // Diff log_differs(v_int1, v_int2, "Variant int same", false); log_differs(v_int1, v_int3, "Variant int diff", true); Variant v_float1 = 3.14; Variant v_float2 = 3.14; Variant v_float3 = 3.14159; log_differs(v_float1, v_float2, "Variant float same", false); log_differs(v_float1, v_float3, "Variant float diff", true); Variant v_str1 = String("test"); Variant v_str2 = String("test"); Variant v_str3 = String("diff"); log_differs(v_str1, v_str2, "Variant String same", false); log_differs(v_str1, v_str3, "Variant String diff", true); // Variant Object (use RefCounted for ref support) Ref rc1 = memnew(RefCounted); Ref rc2 = rc1; // Same ref Ref rc3 = memnew(RefCounted); // Diff Variant v_rc1 = rc1; Variant v_rc2 = rc2; Variant v_rc3 = rc3; log_differs(v_rc1, v_rc2, "Variant RefCounted same ref", false); // Ref == true log_differs(v_rc1, v_rc3, "Variant RefCounted diff ref", true); // Variant Array Array arr_var1; arr_var1.push_back(42); Variant v_arr1 = arr_var1; Array arr_var2 = arr_var1; Variant v_arr2 = arr_var2; log_differs(v_arr1, v_arr2, "Variant Array shared", false); // Variant == checks inner // Variant Dictionary Dictionary dict_var1; dict_var1["key"] = 42; Variant v_dict1 = dict_var1; Dictionary dict_var2 = dict_var1; Variant v_dict2 = dict_var2; log_differs(v_dict1, v_dict2, "Variant Dictionary shared", false); } UtilityFunctions::print("=== End differs tests ==="); } ================================================ FILE: src/unit_testing.h ================================================ // Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors. #ifndef UNIT_TESTING_H #define UNIT_TESTING_H #define EXPECT_FALSE(cond) \ do { \ if (cond) { \ UtilityFunctions::print("FAILED: ", #cond); \ } else { \ UtilityFunctions::print("PASSED: ", #cond); \ } \ } while (0) #define EXPECT_TRUE(cond) \ do { \ if (cond) { \ UtilityFunctions::print("PASSED: ", #cond); \ } else { \ UtilityFunctions::print("FAILED: ", #cond); \ } \ } while (0) void test_differs(); #endif // UNIT_TESTING_H ================================================ FILE: tools/build_release.sh ================================================ #!/usr/bin/bash ## This file has been superseded by the github automated builds configured under /.github if [ "$3" == "" ]; then echo "Usage: $0 " echo "Platforms: linux|windows|all" echo "e.g. $0 linux 0.8.1-alpha 4.1.1" exit 1 fi NAME=terrain3d PLATFORM=$1 VERSION=$2 GDVER=$3 ZIPFILE="${NAME}_${VERSION}_gd${GDVER}" BIN="addons/terrain_3d/bin" STRIP=`which strip 2>/dev/null` INCLUDE="addons/terrain_3d demo project.godot" ADD="README.md LICENSE CONTRIBUTORS.md" EXCLUDE="$BIN/*.lib $BIN/*.exp" WIN_DBG_FILE="$BIN/libterrain.windows.debug.x86_64.dll" WIN_REL_FILE="$BIN/libterrain.windows.release.x86_64.dll" LIN_DBG_FILE="$BIN/libterrain.linux.debug.x86_64.so" LIN_REL_FILE="$BIN/libterrain.linux.release.x86_64.so" if [ ! -e .git ]; then echo .git dir not found. May not be in git root. exit 1 fi if [ ! -e "project/$BIN" ]; then echo Bin dir not found. May not be in git root. exit 1 fi # Build Windows if [ "$PLATFORM" == "windows" ] || [ "$PLATFORM" == "all" ]; then #if [ ! -e $WIN_DBG_FILE ] || [ ! -e $WIN_REL_FILE ]; then scons platform=windows && scons platform=windows target=template_release debug_symbols=false if [ $? -gt 0 ]; then echo Problem building. Exiting packaging script. exit $? fi if [ "$STRIP" != "" ]; then strip project/$WIN_REL_FILE fi #fi pushd project > /dev/null zip -r ../${ZIPFILE}_win64.zip $INCLUDE -x $EXCLUDE $LIN_DBG_FILE $LIN_REL_FILE popd > /dev/null zip -u ${ZIPFILE}_win64.zip $ADD fi # Build Linux if [ "$PLATFORM" == "linux" ] || [ "$PLATFORM" == "all" ]; then #if [ ! -e $LIN_DBG_FILE ] || [ ! -e $LIN_REL_FILE ]; then scons platform=linux && scons platform=linux target=template_release debug_symbols=false if [ $? -gt 0 ]; then echo Problem building. Exiting packaging script. exit $? fi if [ "$STRIP" != "" ]; then strip project/$LIN_REL_FILE fi #fi pushd project > /dev/null zip -r ../${ZIPFILE}_linux.x86-64.zip $INCLUDE -x $EXCLUDE $WIN_DBG_FILE $WIN_REL_FILE popd > /dev/null zip -u ${ZIPFILE}_linux.x86-64.zip $ADD fi ================================================ FILE: tools/hooks/asmessage.applescript ================================================ on run argv set vButtons to { "OK" } set vButtonCodes to { 0 } set vDbutton to "OK" set vText to "" set vTitle to "" set vTimeout to -1 repeat with i from 1 to length of argv try set vArg to item i of argv if vArg = "-buttons" then set vButtonsAndCodes to my fSplit(item (i + 1) of argv, ",") set vButtons to {} set vButtonCodes to {} repeat with j from 1 to length of vButtonsAndCodes set vBtn to my fSplit(item j of vButtonsAndCodes, ":") copy (item 1 of vBtn) to the end of the vButtons copy (item 2 of vBtn) to the end of the vButtonCodes end repeat else if vArg = "-title" then set vTitle to item (i + 1) of argv else if vArg = "-center" then -- not supported else if vArg = "-default" then set vDbutton to item (i + 1) of argv else if vArg = "-geometry" then -- not supported else if vArg = "-nearmouse" then -- not supported else if vArg = "-timeout" then set vTimeout to item (i + 1) of argv as integer else if vArg = "-file" then set vText to read (item (i + 1) of argv) as string else if vArg = "-text" then set vText to item (i + 1) of argv end if end try end repeat set vDlg to display dialog vText buttons vButtons default button vDbutton with title vTitle giving up after vTimeout with icon stop set vRet to button returned of vDlg repeat with i from 1 to length of vButtons set vBtn to item i of vButtons if vBtn = vRet return item i of vButtonCodes end if end repeat return 0 end run on fSplit(vString, vDelimiter) set oldDelimiters to AppleScript's text item delimiters set AppleScript's text item delimiters to vDelimiter set vArray to every text item of vString set AppleScript's text item delimiters to oldDelimiters return vArray end fSplit ================================================ FILE: tools/hooks/canonicalize_filename.sh ================================================ #!/bin/sh # Provide the canonicalize filename (physical filename with out any symlinks) # like the GNU version readlink with the -f option regardless of the version of # readlink (GNU or BSD). # This file is part of a set of unofficial pre-commit hooks available # at github. # Link: https://github.com/githubbrowser/Pre-commit-hooks # Contact: David Martin, david.martin.mailbox@googlemail.com ########################################################### # There should be no need to change anything below this line. # Canonicalize by recursively following every symlink in every component of the # specified filename. This should reproduce the results of the GNU version of # readlink with the -f option. # # Reference: https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac canonicalize_filename () { local target_file="$1" local physical_directory="" local result="" # Need to restore the working directory after work. local working_dir="`pwd`" cd -- "$(dirname -- "$target_file")" target_file="$(basename -- "$target_file")" # Iterate down a (possible) chain of symlinks while [ -L "$target_file" ] do target_file="$(readlink -- "$target_file")" cd -- "$(dirname -- "$target_file")" target_file="$(basename -- "$target_file")" done # Compute the canonicalized name by finding the physical path # for the directory we're in and appending the target file. physical_directory="`pwd -P`" result="$physical_directory/$target_file" # restore the working directory after work. cd -- "$working_dir" echo "$result" } ================================================ FILE: tools/hooks/pre-commit ================================================ #!/bin/sh # Git pre-commit hook that runs multiple hooks specified in $HOOKS. # Make sure this script is executable. Bypass hooks with git commit --no-verify. # This file is part of a set of unofficial pre-commit hooks available # at github. # Link: https://github.com/githubbrowser/Pre-commit-hooks # Contact: David Martin, david.martin.mailbox@googlemail.com ########################################################### # CONFIGURATION: # pre-commit hooks to be executed. They should be in the same .git/hooks/ folder # as this script. Hooks should return 0 if successful and nonzero to cancel the # commit. They are executed in the order in which they are listed. HOOKS="pre-commit-clang-format" HOOKS="$HOOKS $(find $(dirname -- "$0") -type f -name 'pre-commit-custom-*' -exec basename {} \;)" ########################################################### # There should be no need to change anything below this line. . "$(dirname -- "$0")/canonicalize_filename.sh" # exit on error set -e # Absolute path to this script, e.g. /home/user/bin/foo.sh SCRIPT="$(canonicalize_filename "$0")" # Absolute path this script is in, thus /home/user/bin SCRIPTPATH="$(dirname -- "$SCRIPT")" for hook in $HOOKS do echo "Running hook: $hook" # run hook if it exists # if it returns with nonzero exit with 1 and thus abort the commit if [ -f "$SCRIPTPATH/$hook" ]; then "$SCRIPTPATH/$hook" if [ $? != 0 ]; then exit 1 fi else echo "Error: file $hook not found." echo "Aborting commit. Make sure the hook is in $SCRIPTPATH and executable." echo "You can disable it by removing it from the list in $SCRIPT." echo "You can skip all pre-commit hooks with --no-verify (not recommended)." exit 1 fi done ================================================ FILE: tools/hooks/pre-commit-clang-format ================================================ #!/usr/bin/env bash # git pre-commit hook that runs a clang-format stylecheck. # Features: # - abort commit when commit does not comply with the style guidelines # - create a patch of the proposed style changes # Modifications for clang-format by rene.milk@wwu.de # This file is part of a set of unofficial pre-commit hooks available # at github. # Link: https://github.com/githubbrowser/Pre-commit-hooks # Contact: David Martin, david.martin.mailbox@googlemail.com # Some quality of life modifications made for Godot Engine. ################################################################## # SETTINGS # Set path to clang-format binary. CLANG_FORMAT=`which clang-format 2>/dev/null` # Remove any older patches from previous commits. Set to true or false. DELETE_OLD_PATCHES=false # Only parse files with the extensions in FILE_EXTS. Set to true or false. # If false every changed file in the commit will be parsed with clang-format. # If true only files matching one of the extensions are parsed with clang-format. PARSE_EXTS=true # File types to parse. Only effective when PARSE_EXTS is true. FILE_EXTS=".c .h .cpp .hpp .cc .hh .cxx .m .mm .inc .java .glsl" # Use pygmentize instead of cat to parse diff with highlighting. # Install it with `pip install pygments` (Linux) or `easy_install Pygments` (Mac) PYGMENTIZE=`which pygmentize 2>/dev/null` if [ ! -z "$PYGMENTIZE" ]; then READER="pygmentize -l diff" else READER=cat fi # Path to zenity ZENITY=`which zenity 2>/dev/null` # Path to xmessage XMSG=`which xmessage 2>/dev/null` # Path to powershell (Windows only) PWSH=`which powershell 2>/dev/null` # Path to osascript (macOS only) OSA=`which osascript 2>/dev/null` ################################################################## # There should be no need to change anything below this line. . "$(dirname -- "$0")/canonicalize_filename.sh" # exit on error set -e # check whether the given file matches any of the set extensions matches_extension() { local filename=$(basename "$1") local extension=".${filename##*.}" local ext for ext in $FILE_EXTS; do [[ "$ext" == "$extension" ]] && return 0; done return 1 } # necessary check for initial commit if git rev-parse --verify HEAD >/dev/null 2>&1 ; then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # To get consistent formatting, we recommend contributors to use the same # clang-format version as CI. RECOMMENDED_CLANG_FORMAT_MAJOR_MIN="13" RECOMMENDED_CLANG_FORMAT_MAJOR_MAX="15" if [ ! -x "$CLANG_FORMAT" ] ; then message="Error: clang-format executable not found. Please install clang-format $RECOMMENDED_CLANG_FORMAT_MAJOR_MAX." if [ ! -t 1 ] ; then if [ -x "$ZENITY" ] ; then $ZENITY --error --title="Error" --text="$message" exit 1 elif [ -x "$XMSG" ] ; then $XMSG -center -title "Error" "$message" exit 1 elif [ -x "$OSA" ] ; then asmessage="$(canonicalize_filename "$(dirname -- "$0")/asmessage.applescript")" $OSA "$asmessage" -center -title "Error" --text "$message" exit 1 elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then winmessage="$(canonicalize_filename "$(dirname -- "$0")/winmessage.ps1")" $PWSH -noprofile -executionpolicy bypass -file "$winmessage" -center -title "Error" --text "$message" exit 1 fi fi printf "$message\n" printf "Set the correct path in $(canonicalize_filename "$0").\n" exit 1 fi # The returned string can be inconsistent depending on where clang-format comes from. # Example output strings reported by `clang-format --version`: # - Ubuntu: "Ubuntu clang-format version 11.0.0-2" # - Fedora: "clang-format version 11.0.0 (Fedora 11.0.0-2.fc33)" CLANG_FORMAT_VERSION="$(clang-format --version | sed "s/[^0-9\.]*\([0-9\.]*\).*/\1/")" CLANG_FORMAT_MAJOR="$(echo "$CLANG_FORMAT_VERSION" | cut -d. -f1)" if [[ "$CLANG_FORMAT_MAJOR" -lt "$RECOMMENDED_CLANG_FORMAT_MAJOR_MIN" || "$CLANG_FORMAT_MAJOR" -gt "$RECOMMENDED_CLANG_FORMAT_MAJOR_MAX" ]]; then echo "Warning: Your clang-format binary is the wrong version ($CLANG_FORMAT_VERSION, expected between $RECOMMENDED_CLANG_FORMAT_MAJOR_MIN and $RECOMMENDED_CLANG_FORMAT_MAJOR_MAX)." echo " Consider upgrading or downgrading clang-format as formatting may not be applied correctly." fi # create a random filename to store our generated patch prefix="pre-commit-clang-format" suffix="$(date +%s)" patch="/tmp/$prefix-$suffix.patch" # clean up any older clang-format patches $DELETE_OLD_PATCHES && rm -f /tmp/$prefix*.patch # create one patch containing all changes to the files git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file; do # ignore thirdparty files if grep -q "thirdparty" <<< $file; then continue; fi if grep -q "platform/android/java/lib/src/com" <<< $file; then continue; fi if grep -q "\-so_wrap." <<< $file; then continue; fi # ignore file if we do check for file extensions and the file # does not match any of the extensions specified in $FILE_EXTS if $PARSE_EXTS && ! matches_extension "$file"; then continue; fi # clang-format our sourcefile, create a patch with diff and append it to our $patch # The sed call is necessary to transform the patch from # --- $file timestamp # +++ - timestamp # to both lines working on the same file and having a/ and b/ prefix. # Else it can not be applied with 'git apply'. "$CLANG_FORMAT" -style=file "$file" --Wno-error=unknown | \ diff -u "$file" - | \ sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patch" done # if no patch has been generated all is ok, clean up the file stub and exit if [ ! -s "$patch" ] ; then printf "Files in this commit comply with the clang-format rules.\n" rm -f "$patch" exit 0 fi # a patch has been created, notify the user and exit printf "\nThe following differences were found between the code to commit " printf "and the clang-format rules:\n\n" if [ -t 1 ] ; then $READER "$patch" printf "\n" # Allows us to read user input below, assigns stdin to keyboard exec < /dev/tty terminal="1" else cat "$patch" printf "\n" # Allows non zero zenity/powershell output set +e terminal="0" fi while true; do if [ $terminal = "0" ] ; then if [ -x "$ZENITY" ] ; then choice=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage") if [ "$?" = "0" ] ; then yn="Y" else if [ "$choice" = "Apply and stage" ] ; then yn="S" else yn="N" fi fi elif [ -x "$XMSG" ] ; then $XMSG -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?" choice=$? if [ "$choice" = "100" ] ; then yn="Y" elif [ "$choice" = "200" ] ; then yn="S" else yn="N" fi elif [ -x "$OSA" ] ; then asmessage="$(canonicalize_filename "$(dirname -- "$0")/asmessage.applescript")" choice=`$OSA "$asmessage" -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?"` if [ "$choice" = "100" ] ; then yn="Y" elif [ "$choice" = "200" ] ; then yn="S" else yn="N" fi elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then winmessage="$(canonicalize_filename "$(dirname -- "$0")/winmessage.ps1")" $PWSH -noprofile -executionpolicy bypass -file "$winmessage" -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?" choice=$? if [ "$choice" = "100" ] ; then yn="Y" elif [ "$choice" = "200" ] ; then yn="S" else yn="N" fi else printf "Error: zenity, xmessage, osascript, or powershell executable not found.\n" exit 1 fi else read -p "Do you want to apply that patch (Y - Apply, N - Do not apply, S - Apply and stage files)? [Y/N/S] " yn fi case $yn in [Yy] ) git apply $patch; printf "The patch was applied. You can now stage the changes and commit again.\n\n"; break ;; [Nn] ) printf "\nYou can apply these changes with:\n git apply $patch\n"; printf "(may need to be called from the root directory of your repository)\n"; printf "Aborting commit. Apply changes and commit again or skip checking with"; printf " --no-verify (not recommended).\n\n"; break ;; [Ss] ) git apply $patch; git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file; do git add $file; done printf "The patch was applied and the changed files staged. You can now commit.\n\n"; break ;; * ) echo "Please answer yes or no." ;; esac done exit 1 # we don't commit in any case ================================================ FILE: tools/hooks/winmessage.ps1 ================================================ Param ( [string]$file = "", [string]$text = "", [string]$buttons = "OK:0", [string]$default = "", [switch]$nearmouse = $false, [switch]$center = $false, [string]$geometry = "", [int32]$timeout = 0, [string]$title = "Message" ) Add-Type -assembly System.Windows.Forms $global:Result = 0 $main_form = New-Object System.Windows.Forms.Form $main_form.Text = $title $geometry_data = $geometry.Split("+") if ($geometry_data.Length -ge 1) { $size_data = $geometry_data[0].Split("x") if ($size_data.Length -eq 2) { $main_form.Width = $size_data[0] $main_form.Height = $size_data[1] } } if ($geometry_data.Length -eq 3) { $main_form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual $main_form.Location = New-Object System.Drawing.Point($geometry_data[1], $geometry_data[2]) } if ($nearmouse) { $main_form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual $main_form.Location = System.Windows.Forms.Cursor.Position } if ($center) { $main_form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen } $main_form.SuspendLayout() $button_panel = New-Object System.Windows.Forms.FlowLayoutPanel $button_panel.SuspendLayout() $button_panel.FlowDirection = [System.Windows.Forms.FlowDirection]::RightToLeft $button_panel.Dock = [System.Windows.Forms.DockStyle]::Bottom $button_panel.Autosize = $true if ($file -ne "") { $text = [IO.File]::ReadAllText($file).replace("`n", "`r`n") } if ($text -ne "") { $text_box = New-Object System.Windows.Forms.TextBox $text_box.Multiline = $true $text_box.ReadOnly = $true $text_box.Autosize = $true $text_box.Text = $text $text_box.Select(0,0) $text_box.Dock = [System.Windows.Forms.DockStyle]::Fill $main_form.Controls.Add($text_box) } $buttons_array = $buttons.Split(",") foreach ($button in $buttons_array) { $button_data = $button.Split(":") $button_ctl = New-Object System.Windows.Forms.Button if ($button_data.Length -eq 2) { $button_ctl.Tag = $button_data[1] } else { $button_ctl.Tag = 100 + $buttons_array.IndexOf($button) } if ($default -eq $button_data[0]) { $main_form.AcceptButton = $button_ctl } $button_ctl.Autosize = $true $button_ctl.Text = $button_data[0] $button_ctl.Add_Click( { Param($sender) $global:Result = $sender.Tag $main_form.Close() } ) $button_panel.Controls.Add($button_ctl) } $main_form.Controls.Add($button_panel) $button_panel.ResumeLayout($false) $main_form.ResumeLayout($false) if ($timeout -gt 0) { $timer = New-Object System.Windows.Forms.Timer $timer.Add_Tick( { $global:Result = 0 $main_form.Close() } ) $timer.Interval = $timeout $timer.Start() } $dlg_res = $main_form.ShowDialog() [Environment]::Exit($global:Result) ================================================ FILE: tools/install-hooks.py ================================================ import os import stat import sys import shutil src_dir = 'tools/hooks' dist_dir = '.git/hooks' if not os.path.exists(dist_dir): print("Error: this directory is not a git repository!") sys.exit(1) for file in os.listdir(src_dir): shutil.copy(os.path.join(src_dir, file), dist_dir) # Unix user needs the execute bit set. if os.name == "posix": mode = stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH; os.chmod(os.path.join(dist_dir, "pre-commit"), mode); os.chmod(os.path.join(dist_dir, "pre-commit-clang-format"), mode); print("Copied tools/hooks/* to .git/hooks")