Merge branch 'instancing'

This commit is contained in:
Marc Gilleron 2021-02-16 21:17:04 +00:00
commit 601c1c68f1
123 changed files with 5028 additions and 470 deletions

3
SCsub
View File

@ -33,6 +33,8 @@ voxel_files = [
"util/noise/fast_noise_lite_gradient.cpp",
"terrain/*.cpp",
"terrain/instancing/*.cpp",
"server/*.cpp",
"math/*.cpp",
"edition/*.cpp",
@ -48,6 +50,7 @@ if env["tools"]:
"editor/graph/*.cpp",
"editor/terrain/*.cpp",
"editor/fast_noise_lite/*.cpp",
"editor/instance_library/*.cpp",
]
voxel_files += voxel_editor_files

View File

@ -17,6 +17,9 @@ def get_doc_classes():
"Voxel",
"VoxelLibrary",
"VoxelColorPalette",
"VoxelInstanceLibrary",
"VoxelInstanceLibraryItem",
"VoxelInstanceGenerator",
"VoxelBuffer",
@ -24,11 +27,13 @@ def get_doc_classes():
"VoxelTerrain",
"VoxelLodTerrain",
"VoxelViewer",
"VoxelInstancer",
"VoxelStream",
"VoxelStreamFile",
"VoxelStreamBlockFiles",
"VoxelStreamRegionFiles",
"VoxelStreamSQLite",
"VoxelStreamScript",
"VoxelGenerator",

View File

@ -12,16 +12,16 @@
<return type="void">
</return>
<argument index="0" name="out_buffer" type="VoxelBuffer">
Buffer in which voxel data will be generated. It should not be [code]null[/code] and should be given the requested size.
</argument>
<argument index="1" name="origin_in_voxels" type="Vector3">
Coordinates of the lower corner of the box to generate, relative to LOD0.
</argument>
<argument index="2" name="lod" type="int">
Level of detail index to use for this block. Some generators might not support LOD, in which case it can be left 0.
</argument>
<description>
Generates a block of voxels within the specified world area.
[code]out_buffer[/code]: Buffer in which voxel data will be generated. It should not be [code]null[/code] and should be given the requested size. Do not keep a reference on it after the call.
[code]origin_in_voxels[/code]: Coordinates of the lower corner of the box to generate, relative to LOD0.
[code]lod[/code]: Level of detail index to use for this block. Some generators might not support LOD, in which case it can be left 0.
</description>
</method>
</methods>

View File

@ -13,23 +13,22 @@
<return type="void">
</return>
<argument index="0" name="out_buffer" type="VoxelBuffer">
Buffer in which to populate voxel data. It will never be [code]null[/code] and will have the requested size. It is only valid for this function, do not store it anywhere after the end.
</argument>
<argument index="1" name="origin_in_voxels" type="Vector3">
Coordinates of the lower corner of the box to generate, relative to LOD0. The size of the box is known from [code]out_buffer[/code].
</argument>
<argument index="2" name="lod" type="int">
Level of detail index to use for this block. It can be ignored if you don't use LOD.
This may be used as a power of two, telling how big is one voxel. For example, if you use a loop to fill the buffer using noise, you should sample that noise at steps of 2^lod, starting from [code]origin_in_voxels[/code] (in code you can use [code]1 << lod[/code] for fast computation, instead of [code]pow(2, lod)[/code]). You may want to separate variables that iterate the coordinates in [code]out_buffer[/code] and variables used to generate voxel values in space.
</argument>
<description>
[code]out_buffer[/code]: Buffer in which to populate voxel data. It will never be [code]null[/code] and will have the requested size. It is only valid for this function, do not store it anywhere after the end.
[code]origin_in_voxels[/code]: Coordinates of the lower corner of the box to generate, relative to LOD0. The size of the box is known from [code]out_buffer[/code].
[code]lod[/code]: Level of detail index to use for this block. It can be ignored if you don't use LOD. This may be used as a power of two, telling how big is one voxel. For example, if you use a loop to fill the buffer using noise, you should sample that noise at steps of 2^lod, starting from [code]origin_in_voxels[/code] (in code you can use [code]1 &lt;&lt; lod[/code] for fast computation, instead of [code]pow(2, lod)[/code]). You may want to separate variables that iterate the coordinates in [code]out_buffer[/code] and variables used to generate voxel values in space.
</description>
</method>
<method name="_get_used_channels_mask" qualifiers="virtual">
<return type="int">
</return>
<description>
Use this to indicate which channels your generator will use. It returns a bitmask, so for example you may provide information like this: [code](1 << channel1) | (1 << channel2)[/code]
Use this to indicate which channels your generator will use. It returns a bitmask, so for example you may provide information like this: [code](1 &lt;&lt; channel1) | (1 &lt;&lt; channel2)[/code]
</description>
</method>
</methods>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelInstanceGenerator" inherits="Resource" version="3.2">
<brief_description>
Decides where to spawn instances on top of a voxel surface.
</brief_description>
<description>
Generates the necessry information to spawn instances on top of a voxel surface. This may be used by a [VoxelInstancer].
Note: to generate voxels, see [VoxelGenerator].
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="density" type="float" setter="set_density" getter="get_density" default="0.1">
</member>
<member name="emit_mode" type="int" setter="set_emit_mode" getter="get_emit_mode" enum="VoxelInstanceGenerator.EmitMode" default="0">
</member>
<member name="max_height" type="float" setter="set_max_height" getter="get_max_height" default="3.40282e+38">
</member>
<member name="max_scale" type="float" setter="set_max_scale" getter="get_max_scale" default="1.0">
</member>
<member name="max_slope_degrees" type="float" setter="set_max_slope_degrees" getter="get_max_slope_degrees" default="180.0">
</member>
<member name="min_height" type="float" setter="set_min_height" getter="get_min_height" default="1.17549e-38">
</member>
<member name="min_scale" type="float" setter="set_min_scale" getter="get_min_scale" default="1.0">
</member>
<member name="min_slope_degrees" type="float" setter="set_min_slope_degrees" getter="get_min_slope_degrees" default="0.0">
</member>
<member name="noise" type="FastNoiseLite" setter="set_noise" getter="get_noise">
</member>
<member name="noise_dimension" type="int" setter="set_noise_dimension" getter="get_noise_dimension" enum="VoxelInstanceGenerator.Dimension" default="1">
</member>
<member name="noise_on_scale" type="float" setter="set_noise_on_scale" getter="get_noise_on_scale" default="0.0">
</member>
<member name="offset_along_normal" type="float" setter="set_offset_along_normal" getter="get_offset_along_normal" default="0.0">
</member>
<member name="random_vertical_flip" type="bool" setter="set_random_vertical_flip" getter="get_random_vertical_flip" default="false">
</member>
<member name="scale_distribution" type="int" setter="set_scale_distribution" getter="get_scale_distribution" enum="VoxelInstanceGenerator.Distribution" default="1">
</member>
<member name="vertical_alignment" type="float" setter="set_vertical_alignment" getter="get_vertical_alignment" default="1.0">
</member>
</members>
<constants>
<constant name="EMIT_FROM_VERTICES" value="0" enum="EmitMode">
</constant>
<constant name="EMIT_FROM_FACES" value="1" enum="EmitMode">
</constant>
</constants>
</class>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelInstanceLibrary" inherits="Resource" version="3.2">
<brief_description>
Contains a list of models that can be used by [VoxelInstancer], associated with a unique ID
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_item">
<return type="void">
</return>
<argument index="0" name="id" type="int">
</argument>
<argument index="1" name="arg1" type="VoxelInstanceLibraryItem">
</argument>
<description>
</description>
</method>
<method name="clear">
<return type="void">
</return>
<description>
</description>
</method>
<method name="find_item_by_name" qualifiers="const">
<return type="int">
</return>
<argument index="0" name="name" type="String">
</argument>
<description>
</description>
</method>
<method name="get_item">
<return type="VoxelInstanceLibraryItem">
</return>
<argument index="0" name="id" type="int">
</argument>
<description>
</description>
</method>
<method name="remove_item">
<return type="void">
</return>
<argument index="0" name="id" type="int">
</argument>
<description>
</description>
</method>
</methods>
<constants>
<constant name="MAX_ID" value="65535">
</constant>
</constants>
</class>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelInstanceLibraryItem" inherits="Resource" version="3.2">
<brief_description>
Settings for a model that can be used by [VoxelInstancer]
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_collision_shapes" qualifiers="const">
<return type="Array">
</return>
<description>
</description>
</method>
<method name="get_mesh" qualifiers="const">
<return type="Mesh">
</return>
<argument index="0" name="mesh_lod_index" type="int">
</argument>
<description>
</description>
</method>
<method name="set_collision_shapes">
<return type="void">
</return>
<argument index="0" name="shape_infos" type="Array">
</argument>
<description>
</description>
</method>
<method name="set_mesh">
<return type="void">
</return>
<argument index="0" name="mesh" type="Mesh">
</argument>
<argument index="1" name="mesh_lod_index" type="int">
</argument>
<description>
</description>
</method>
<method name="setup_from_template">
<return type="void">
</return>
<argument index="0" name="node" type="Node">
</argument>
<description>
</description>
</method>
</methods>
<members>
<member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1">
</member>
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
</member>
<member name="generator" type="VoxelInstanceGenerator" setter="set_generator" getter="get_generator">
</member>
<member name="lod_index" type="int" setter="set_lod_index" getter="get_lod_index" default="0">
</member>
<member name="material_override" type="Material" setter="set_material_override" getter="get_material_override">
</member>
<member name="mesh" type="Mesh" setter="_set_mesh_lod0" getter="_get_mesh_lod0">
</member>
<member name="mesh_lod1" type="Mesh" setter="_set_mesh_lod1" getter="_get_mesh_lod1">
</member>
<member name="mesh_lod2" type="Mesh" setter="_set_mesh_lod2" getter="_get_mesh_lod2">
</member>
<member name="mesh_lod3" type="Mesh" setter="_set_mesh_lod3" getter="_get_mesh_lod3">
</member>
<member name="name" type="String" setter="set_item_name" getter="get_item_name" default="&quot;&quot;">
</member>
<member name="persistent" type="bool" setter="set_persistent" getter="is_persistent" default="false">
</member>
</members>
<constants>
<constant name="MAX_MESH_LODS" value="4">
</constant>
</constants>
</class>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelInstancer" inherits="Spatial" version="3.2">
<brief_description>
Spawns items on top of voxel surfaces.
</brief_description>
<description>
Add-on to voxel nodes, allowing to spawn elements on the surface. These elements are rendered with hardware instancing, can have collisions, and also be persistent.
</description>
<tutorials>
</tutorials>
<methods>
<method name="debug_get_block_count" qualifiers="const">
<return type="int">
</return>
<description>
</description>
</method>
</methods>
<members>
<member name="library" type="VoxelInstanceLibrary" setter="set_library" getter="get_library">
</member>
<member name="up_mode" type="int" setter="set_up_mode" getter="get_up_mode" enum="VoxelInstancer.UpMode" default="0">
</member>
</members>
<constants>
<constant name="MAX_LOD" value="8">
</constant>
<constant name="UP_MODE_POSITIVE_Y" value="0" enum="UpMode">
</constant>
<constant name="UP_MODE_SPHERE" value="1" enum="UpMode">
</constant>
</constants>
</class>

View File

@ -36,13 +36,13 @@
<return type="void">
</return>
<argument index="0" name="buffer" type="VoxelBuffer">
Block of voxels to save. It is strongly recommended to not keep a reference to that data afterward, because streams are allowed to cache it, and saved data must represent either snapshots (copies) or last references to the data after the volume they belonged to is destroyed.
</argument>
<argument index="1" name="origin_in_voxels" type="Vector3">
</argument>
<argument index="2" name="lod" type="int">
</argument>
<description>
[code]buffer[/code]: Block of voxels to save. It is strongly recommended to not keep a reference to that data afterward, because streams are allowed to cache it, and saved data must represent either snapshots (copies) or last references to the data after the volume they belonged to is destroyed.
</description>
</method>
</methods>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelStreamBlockFiles" inherits="VoxelStreamFile" version="3.2">
<class name="VoxelStreamBlockFiles" inherits="VoxelStream" version="3.2">
<brief_description>
Loads and saves blocks as individual files under a directory.
</brief_description>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelStreamFile" inherits="VoxelStream" version="3.2">
<brief_description>
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<constants>
</constants>
</class>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelStreamRegionFiles" inherits="VoxelStreamFile" version="3.2">
<class name="VoxelStreamRegionFiles" inherits="VoxelStream" version="3.2">
<brief_description>
Loads and saves blocks to region files indexed by world position, under a directory.
</brief_description>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelStreamSQLite" inherits="VoxelStream" version="3.2">
<brief_description>
Saves voxel data into a single SQLite database file.
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="database_path" type="String" setter="set_database_path" getter="get_database_path" default="&quot;&quot;">
</member>
</members>
<constants>
</constants>
</class>

View File

@ -12,13 +12,13 @@
<return type="void">
</return>
<argument index="0" name="out_buffer" type="VoxelBuffer">
Buffer in which to populate voxel data. It will never be [code]null[/code] and will have the requested size. It is only valid for this function, do not store it anywhere after the end.
</argument>
<argument index="1" name="origin_in_voxels" type="Vector3">
</argument>
<argument index="2" name="lod" type="int">
</argument>
<description>
[code]out_buffer[/code]: Buffer in which to populate voxel data. It will never be [code]null[/code] and will have the requested size. It is only valid for this function, do not store it anywhere after the end.
</description>
</method>
<method name="_get_used_channels_mask" qualifiers="virtual">
@ -31,13 +31,13 @@
<return type="void">
</return>
<argument index="0" name="buffer" type="VoxelBuffer">
Buffer of voxel data to save. It is allowed to keep a reference to it for caching purposes, as saved data will either be snapshots or only references left after removal of a volume.
</argument>
<argument index="1" name="origin_in_voxels" type="Vector3">
</argument>
<argument index="2" name="lod" type="int">
</argument>
<description>
[code]buffer[/code]: Buffer of voxel data to save. It is allowed to keep a reference to it for caching purposes, as saved data will either be snapshots or only references left after removal of a volume.
</description>
</method>
</methods>

View File

@ -138,7 +138,7 @@
<member name="mode" type="int" setter="set_mode" getter="get_mode" enum="VoxelTool.Mode" default="0">
Sets how [code]do_*[/code] functions will behave. This may vary depending on the channel.
</member>
<member name="sdf_scale" type="float" setter="set_sdf_scale" getter="get_sdf_scale" default="0.1">
<member name="sdf_scale" type="float" setter="set_sdf_scale" getter="get_sdf_scale" default="0.002">
When working with smooth voxels, applies a scale to the signed distance field. A high scale (1 or higher) will tend to produce blocky results, and a low scale (below 1, but not too close to zero) will tend to be smoother.
This is related to the [enum VoxelBuffer.Depth] configuration on voxels. For 8-bit and 16-bit, there is a limited range of values the Signed Distance Field can take, and by default it is clamped to -1..1, so the gradient can only range across 2 voxels. But when LOD is used, it is better to stretch that range over a longer distance, and this is achieved by scaling SDF values.

View File

@ -16,6 +16,7 @@ nav:
- 'smooth_terrain.md'
- 'generators.md'
- 'streams.md'
- 'instancing.md'
- 'editor.md'
- 'scripting.md'
- 'access_to_voxels_and_multithreading.md'
@ -40,6 +41,10 @@ nav:
- api/VoxelGeneratorNoise2D.md
- api/VoxelGeneratorScript.md
- api/VoxelGeneratorWaves.md
- api/VoxelInstanceGenerator.md
- api/VoxelInstanceLibrary.md
- api/VoxelInstanceLibraryItem.md
- api/VoxelInstancer.md
- api/VoxelLibrary.md
- api/VoxelLodTerrain.md
- api/VoxelMesher.md
@ -52,8 +57,8 @@ nav:
- api/VoxelServer.md
- api/VoxelStream.md
- api/VoxelStreamBlockFiles.md
- api/VoxelStreamFile.md
- api/VoxelStreamRegionFiles.md
- api/VoxelStreamSQLite.md
- api/VoxelStreamScript.md
- api/VoxelTerrain.md
- api/VoxelTool.md
@ -65,6 +70,7 @@ nav:
- 'Serialization formats':
- 'specs/block_format_v1.md'
- 'specs/block_format_v2.md'
- 'specs/instances_format.md'
- 'specs/region_format_v2.md'
- 'specs/region_format_v3.md'
- 'specs/sqlite_format.md'

View File

@ -3,7 +3,7 @@
Inherits: [Resource](https://docs.godotengine.org/en/stable/classes/class_resource.html)
Generates coherent and fractal noise using the FastNoiseLite library.
Generates coherent and fractal noise using the [url=https://github.com/Auburn/FastNoise](https://docs.godotengine.org/en/stable/classes/class_url=https://github.com/auburn/fastnoise.html)FastNoiseLite[/url](https://docs.godotengine.org/en/stable/classes/class_/url.html) library.
## Properties:
@ -128,17 +128,13 @@ enum **CellularReturnType**:
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_get_noise_2d"></span> **get_noise_2d**( [float](https://docs.godotengine.org/en/stable/classes/class_float.html) x, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) y )
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_get_noise_2dv"></span> **get_noise_2dv**( [Vector2](https://docs.godotengine.org/en/stable/classes/class_vector2.html) position )
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_get_noise_3d"></span> **get_noise_3d**( [float](https://docs.godotengine.org/en/stable/classes/class_float.html) x, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) y, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) z )
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_get_noise_3dv"></span> **get_noise_3dv**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) position )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -3,7 +3,7 @@
Inherits: [Resource](https://docs.godotengine.org/en/stable/classes/class_resource.html)
Generates coherent and fractal noise gradients using the FastNoiseLite library.
Generates coherent and fractal noise gradients using the [url=https://github.com/Auburn/FastNoise](https://docs.godotengine.org/en/stable/classes/class_url=https://github.com/auburn/fastnoise.html)FastNoiseLite[/url](https://docs.godotengine.org/en/stable/classes/class_/url.html) library.
## Properties:
@ -71,4 +71,4 @@ enum **RotationType3D**:
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_seed"></span> **seed** = 0
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -76,9 +76,11 @@ enum **Side**:
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_material_id"></span> **material_id** = 0
ID of the material that will be used. It corresponds to the index of materials found on [VoxelTerrain](VoxelTerrain.md).
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_random_tickable"></span> **random_tickable** = false
If enabled, voxels having this ID in the TYPE channel will be used by method VoxelToolTerrain.run_blocky_random_tick.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_transparency_index"></span> **transparency_index** = 0
@ -88,6 +90,7 @@ If enabled, voxels having this ID in the TYPE channel will be used by method Vox
- [String](https://docs.godotengine.org/en/stable/classes/class_string.html)<span id="i_voxel_name"></span> **voxel_name** = ""
Name that can be used for convenience, when looking up a specific [Voxel](Voxel.md) from [VoxelLibrary](VoxelLibrary.md).
## Method Descriptions
@ -95,13 +98,10 @@ Name that can be used for convenience, when looking up a specific [Voxel](Voxel.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_id"></span> **get_id**( )
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_is_empty"></span> **is_empty**( )
- [void](#)<span id="i_set_id"></span> **set_id**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -50,6 +50,6 @@ Reads the data of a [VoxelBuffer](VoxelBuffer.md) from a [StreamPeer](https://do
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_serialize"></span> **serialize**( [StreamPeer](https://docs.godotengine.org/en/stable/classes/class_streampeer.html) peer, [VoxelBuffer](VoxelBuffer.md) voxel_buffer, [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html) compress )
Stores the data of a [VoxelBuffer](VoxelBuffer.md) into a [StreamPeer](https://docs.godotengine.org/en/stable/classes/class_streampeer.html). To be able to read it back, you should use the
Stores the data of a [VoxelBuffer](VoxelBuffer.md) into a [StreamPeer](https://docs.godotengine.org/en/stable/classes/class_streampeer.html).
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -33,7 +33,6 @@ Return |
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_collision_mask"></span> **get_collision_mask**( )
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_get_motion"></span> **get_motion**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) motion, [AABB](https://docs.godotengine.org/en/stable/classes/class_aabb.html) aabb, [Node](https://docs.godotengine.org/en/stable/classes/class_node.html) terrain )
Given a motion vector, returns a modified vector telling you by how much to move your character. This is similar to method KinematicBody.move_and_slide, except you have to apply the movement.
@ -41,5 +40,4 @@ Given a motion vector, returns a modified vector telling you by how much to move
- [void](#)<span id="i_set_collision_mask"></span> **set_collision_mask**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) mask )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -198,7 +198,6 @@ Checks if every voxel within a channel has the same value.
- [void](#)<span id="i_optimize"></span> **optimize**( )
- [void](#)<span id="i_set_block_metadata"></span> **set_block_metadata**( [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html) meta )
Sets arbitrary data on this buffer. Old data is replaced. Note, this is separate storage from per-voxel metadata.
@ -226,5 +225,4 @@ If this [VoxelBuffer](VoxelBuffer.md) is saved, this metadata will also be saved
- [void](#)<span id="i_set_voxel_v"></span> **set_voxel_v**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) value, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) channel=0 )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -36,9 +36,7 @@ Return | Sign
- [Color](https://docs.godotengine.org/en/stable/classes/class_color.html)<span id="i_get_color"></span> **get_color**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) arg0 )
- [void](#)<span id="i_set_color"></span> **set_color**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) color, [Color](https://docs.godotengine.org/en/stable/classes/class_color.html) arg1 )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -3,9 +3,7 @@
Inherits: [Resource](https://docs.godotengine.org/en/stable/classes/class_resource.html)
Base class to all voxel procedural generators. If you want to define a custom one with a script, this is the class you should extend from.
Important: this engine makes heavy use of threads. Generators will run in one of them, so make sure you don't access the scene tree or other unsafe APIs from within a generator.
Base class to all voxel procedural generators. If you want to define a custom one with a script, this is the class you should extend from. All implementations must be thread safe.
## Methods:
@ -21,4 +19,10 @@ Return | Signature
Generates a block of voxels within the specified world area.
_Generated on Jan 24, 2021_
`out_buffer`: Buffer in which voxel data will be generated. It should not be `null` and should be given the requested size. Do not keep a reference on it after the call.
`origin_in_voxels`: Coordinates of the lower corner of the box to generate, relative to LOD0.
`lod`: Level of detail index to use for this block. Some generators might not support LOD, in which case it can be left 0.
_Generated on Feb 16, 2021_

View File

@ -26,4 +26,4 @@ Type | Name | Default
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_voxel_type"></span> **voxel_type** = 1
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -92,101 +92,76 @@ enum **NodeTypeID**:
- [void](#)<span id="i_add_connection"></span> **add_connection**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_port_index, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) dst_node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) dst_port_index )
- [void](#)<span id="i_bake_sphere_bumpmap"></span> **bake_sphere_bumpmap**( [Image](https://docs.godotengine.org/en/stable/classes/class_image.html) im, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) ref_radius, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) sdf_min, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) sdf_max )
- [void](#)<span id="i_bake_sphere_normalmap"></span> **bake_sphere_normalmap**( [Image](https://docs.godotengine.org/en/stable/classes/class_image.html) im, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) ref_radius, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) strength )
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_can_connect"></span> **can_connect**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_port_index, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) dst_node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) dst_port_index )
- [void](#)<span id="i_clear"></span> **clear**( )
- [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html)<span id="i_compile"></span> **compile**( )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_create_node"></span> **create_node**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) type_id, [Vector2](https://docs.godotengine.org/en/stable/classes/class_vector2.html) position, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id=0 )
- [void](#)<span id="i_debug_load_waves_preset"></span> **debug_load_waves_preset**( )
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_debug_measure_microseconds_per_voxel"></span> **debug_measure_microseconds_per_voxel**( [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html) use_singular_queries )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_find_node_by_name"></span> **find_node_by_name**( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) name )
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_generate_single"></span> **generate_single**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) arg0 )
- [Array](https://docs.godotengine.org/en/stable/classes/class_array.html)<span id="i_get_connections"></span> **get_connections**( )
- [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html)<span id="i_get_node_default_input"></span> **get_node_default_input**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) input_index )
- [Vector2](https://docs.godotengine.org/en/stable/classes/class_vector2.html)<span id="i_get_node_gui_position"></span> **get_node_gui_position**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id )
- [PoolIntArray](https://docs.godotengine.org/en/stable/classes/class_poolintarray.html)<span id="i_get_node_ids"></span> **get_node_ids**( )
- [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html)<span id="i_get_node_param"></span> **get_node_param**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) param_index )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_node_type_count"></span> **get_node_type_count**( )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_node_type_id"></span> **get_node_type_id**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id )
- [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html)<span id="i_get_node_type_info"></span> **get_node_type_info**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) type_id )
- [void](#)<span id="i_remove_connection"></span> **remove_connection**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_port_index, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) dst_node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) dst_port_index )
- [void](#)<span id="i_remove_node"></span> **remove_node**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id )
- [void](#)<span id="i_set_node_default_input"></span> **set_node_default_input**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) input_index, [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html) value )
- [void](#)<span id="i_set_node_gui_position"></span> **set_node_gui_position**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id, [Vector2](https://docs.godotengine.org/en/stable/classes/class_vector2.html) position )
- [void](#)<span id="i_set_node_param"></span> **set_node_param**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) param_index, [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html) value )
- [void](#)<span id="i_set_node_param_null"></span> **set_node_param_null**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) node_id, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) param_index )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -30,4 +30,4 @@ Type | Name | Default
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_iso_scale"></span> **iso_scale** = 0.1
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -22,4 +22,4 @@ Type | Name | Default
- [Image](https://docs.godotengine.org/en/stable/classes/class_image.html)<span id="i_image"></span> **image**
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -30,4 +30,4 @@ Type | Name | Default
- [OpenSimplexNoise](https://docs.godotengine.org/en/stable/classes/class_opensimplexnoise.html)<span id="i_noise"></span> **noise**
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -22,4 +22,4 @@ Type | Name | Default
- [OpenSimplexNoise](https://docs.godotengine.org/en/stable/classes/class_opensimplexnoise.html)<span id="i_noise"></span> **noise**
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -5,6 +5,8 @@ Inherits: [VoxelGenerator](VoxelGenerator.md)
Base class for custom generators defined with a script.
Important: this engine makes heavy use of threads. Generators will run in one of them, so make sure you don't access the scene tree or other unsafe APIs from within a generator.
## Methods:
@ -18,10 +20,14 @@ Return | Signatur
- [void](#)<span id="i__generate_block"></span> **_generate_block**( [VoxelBuffer](VoxelBuffer.md) out_buffer, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin_in_voxels, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) lod )
`out_buffer`: Buffer in which to populate voxel data. It will never be `null` and will have the requested size. It is only valid for this function, do not store it anywhere after the end.
`origin_in_voxels`: Coordinates of the lower corner of the box to generate, relative to LOD0. The size of the box is known from `out_buffer`.
`lod`: Level of detail index to use for this block. It can be ignored if you don't use LOD. This may be used as a power of two, telling how big is one voxel. For example, if you use a loop to fill the buffer using noise, you should sample that noise at steps of 2^lod, starting from `origin_in_voxels` (in code you can use `1 << lod` for fast computation, instead of `pow(2, lod)`). You may want to separate variables that iterate the coordinates in `out_buffer` and variables used to generate voxel values in space.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i__get_used_channels_mask"></span> **_get_used_channels_mask**( )
Use this to indicate which channels your generator will use. It returns a bitmask, so for example you may provide information like this: `(1 << channel1) | (1 << channel2)`
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -26,4 +26,4 @@ Type | Name | Default
- [Vector2](https://docs.godotengine.org/en/stable/classes/class_vector2.html)<span id="i_pattern_size"></span> **pattern_size** = Vector2( 30, 30 )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -0,0 +1,91 @@
# VoxelInstanceGenerator
Inherits: [Resource](https://docs.godotengine.org/en/stable/classes/class_resource.html)
Decides where to spawn instances on top of a voxel surface.
## Description:
Generates the necessry information to spawn instances on top of a voxel surface. This may be used by a [VoxelInstancer](VoxelInstancer.md).
Note: to generate voxels, see [VoxelGenerator](VoxelGenerator.md).
## Properties:
Type | Name | Default
---------------- | ------------------------------------------------ | ------------
`float` | [density](#i_density) | 0.1
`int` | [emit_mode](#i_emit_mode) | 0
`float` | [max_height](#i_max_height) | 3.40282e+38
`float` | [max_scale](#i_max_scale) | 1.0
`float` | [max_slope_degrees](#i_max_slope_degrees) | 180.0
`float` | [min_height](#i_min_height) | 1.17549e-38
`float` | [min_scale](#i_min_scale) | 1.0
`float` | [min_slope_degrees](#i_min_slope_degrees) | 0.0
`FastNoiseLite` | [noise](#i_noise) |
`int` | [noise_dimension](#i_noise_dimension) | 1
`float` | [noise_on_scale](#i_noise_on_scale) | 0.0
`float` | [offset_along_normal](#i_offset_along_normal) | 0.0
`bool` | [random_vertical_flip](#i_random_vertical_flip) | false
`int` | [scale_distribution](#i_scale_distribution) | 1
`float` | [vertical_alignment](#i_vertical_alignment) | 1.0
<p></p>
## Enumerations:
enum **EmitMode**:
- **EMIT_FROM_VERTICES** = **0**
- **EMIT_FROM_FACES** = **1**
## Property Descriptions
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_density"></span> **density** = 0.1
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_emit_mode"></span> **emit_mode** = 0
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_max_height"></span> **max_height** = 3.40282e+38
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_max_scale"></span> **max_scale** = 1.0
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_max_slope_degrees"></span> **max_slope_degrees** = 180.0
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_min_height"></span> **min_height** = 1.17549e-38
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_min_scale"></span> **min_scale** = 1.0
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_min_slope_degrees"></span> **min_slope_degrees** = 0.0
- [FastNoiseLite](FastNoiseLite.md)<span id="i_noise"></span> **noise**
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_noise_dimension"></span> **noise_dimension** = 1
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_noise_on_scale"></span> **noise_on_scale** = 0.0
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_offset_along_normal"></span> **offset_along_normal** = 0.0
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_random_vertical_flip"></span> **random_vertical_flip** = false
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_scale_distribution"></span> **scale_distribution** = 1
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_vertical_alignment"></span> **vertical_alignment** = 1.0
_Generated on Feb 16, 2021_

View File

@ -0,0 +1,41 @@
# VoxelInstanceLibrary
Inherits: [Resource](https://docs.godotengine.org/en/stable/classes/class_resource.html)
Contains a list of models that can be used by [VoxelInstancer](VoxelInstancer.md), associated with a unique ID
## Methods:
Return | Signature
--------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------
[void](#) | [add_item](#i_add_item) ( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id, [VoxelInstanceLibraryItem](VoxelInstanceLibraryItem.md) arg1 )
[void](#) | [clear](#i_clear) ( )
[int](https://docs.godotengine.org/en/stable/classes/class_int.html) | [find_item_by_name](#i_find_item_by_name) ( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) name ) const
[VoxelInstanceLibraryItem](VoxelInstanceLibraryItem.md) | [get_item](#i_get_item) ( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
[void](#) | [remove_item](#i_remove_item) ( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
<p></p>
## Constants:
- **MAX_ID** = **65535**
## Method Descriptions
- [void](#)<span id="i_add_item"></span> **add_item**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id, [VoxelInstanceLibraryItem](VoxelInstanceLibraryItem.md) arg1 )
- [void](#)<span id="i_clear"></span> **clear**( )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_find_item_by_name"></span> **find_item_by_name**( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) name )
- [VoxelInstanceLibraryItem](VoxelInstanceLibraryItem.md)<span id="i_get_item"></span> **get_item**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
- [void](#)<span id="i_remove_item"></span> **remove_item**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
_Generated on Feb 16, 2021_

View File

@ -0,0 +1,94 @@
# VoxelInstanceLibraryItem
Inherits: [Resource](https://docs.godotengine.org/en/stable/classes/class_resource.html)
Settings for a model that can be used by [VoxelInstancer](VoxelInstancer.md)
## Properties:
Type | Name | Default
------------------------- | ------------------------------------------ | --------
`int` | [collision_layer](#i_collision_layer) | 1
`int` | [collision_mask](#i_collision_mask) | 1
`VoxelInstanceGenerator` | [generator](#i_generator) |
`int` | [lod_index](#i_lod_index) | 0
`Material` | [material_override](#i_material_override) |
`Mesh` | [mesh](#i_mesh) |
`Mesh` | [mesh_lod1](#i_mesh_lod1) |
`Mesh` | [mesh_lod2](#i_mesh_lod2) |
`Mesh` | [mesh_lod3](#i_mesh_lod3) |
`String` | [name](#i_name) | ""
`bool` | [persistent](#i_persistent) | false
<p></p>
## Methods:
Return | Signature
------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[Array](https://docs.godotengine.org/en/stable/classes/class_array.html) | [get_collision_shapes](#i_get_collision_shapes) ( ) const
[Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html) | [get_mesh](#i_get_mesh) ( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) mesh_lod_index ) const
[void](#) | [set_collision_shapes](#i_set_collision_shapes) ( [Array](https://docs.godotengine.org/en/stable/classes/class_array.html) shape_infos )
[void](#) | [set_mesh](#i_set_mesh) ( [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html) mesh, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) mesh_lod_index )
[void](#) | [setup_from_template](#i_setup_from_template) ( [Node](https://docs.godotengine.org/en/stable/classes/class_node.html) node )
<p></p>
## Constants:
- **MAX_MESH_LODS** = **4**
## Property Descriptions
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_collision_layer"></span> **collision_layer** = 1
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_collision_mask"></span> **collision_mask** = 1
- [VoxelInstanceGenerator](VoxelInstanceGenerator.md)<span id="i_generator"></span> **generator**
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_lod_index"></span> **lod_index** = 0
- [Material](https://docs.godotengine.org/en/stable/classes/class_material.html)<span id="i_material_override"></span> **material_override**
- [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html)<span id="i_mesh"></span> **mesh**
- [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html)<span id="i_mesh_lod1"></span> **mesh_lod1**
- [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html)<span id="i_mesh_lod2"></span> **mesh_lod2**
- [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html)<span id="i_mesh_lod3"></span> **mesh_lod3**
- [String](https://docs.godotengine.org/en/stable/classes/class_string.html)<span id="i_name"></span> **name** = ""
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_persistent"></span> **persistent** = false
## Method Descriptions
- [Array](https://docs.godotengine.org/en/stable/classes/class_array.html)<span id="i_get_collision_shapes"></span> **get_collision_shapes**( )
- [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html)<span id="i_get_mesh"></span> **get_mesh**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) mesh_lod_index )
- [void](#)<span id="i_set_collision_shapes"></span> **set_collision_shapes**( [Array](https://docs.godotengine.org/en/stable/classes/class_array.html) shape_infos )
- [void](#)<span id="i_set_mesh"></span> **set_mesh**( [Mesh](https://docs.godotengine.org/en/stable/classes/class_mesh.html) mesh, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) mesh_lod_index )
- [void](#)<span id="i_setup_from_template"></span> **setup_from_template**( [Node](https://docs.godotengine.org/en/stable/classes/class_node.html) node )
_Generated on Feb 16, 2021_

View File

@ -0,0 +1,54 @@
# VoxelInstancer
Inherits: [Spatial](https://docs.godotengine.org/en/stable/classes/class_spatial.html)
Spawns items on top of voxel surfaces.
## Description:
Add-on to voxel nodes, allowing to spawn elements on the surface. These elements are rendered with hardware instancing, can have collisions, and also be persistent.
## Properties:
Type | Name | Default
----------------------- | ---------------------- | --------
`VoxelInstanceLibrary` | [library](#i_library) |
`int` | [up_mode](#i_up_mode) | 0
<p></p>
## Methods:
Return | Signature
--------------------------------------------------------------------- | ------------------------------------------------------------
[int](https://docs.godotengine.org/en/stable/classes/class_int.html) | [debug_get_block_count](#i_debug_get_block_count) ( ) const
<p></p>
## Enumerations:
enum **UpMode**:
- **UP_MODE_POSITIVE_Y** = **0**
- **UP_MODE_SPHERE** = **1**
## Constants:
- **MAX_LOD** = **8**
## Property Descriptions
- [VoxelInstanceLibrary](VoxelInstanceLibrary.md)<span id="i_library"></span> **library**
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_up_mode"></span> **up_mode** = 0
## Method Descriptions
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_debug_get_block_count"></span> **debug_get_block_count**( )
_Generated on Feb 16, 2021_

View File

@ -37,6 +37,7 @@ Return | Signatur
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_bake_tangents"></span> **bake_tangents** = true
Enable this option if you need normal mapping on your voxels. If you don't need it, disabling can reduce memory usage and give a small speed boost.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_voxel_count"></span> **voxel_count** = 0
@ -47,21 +48,16 @@ Enable this option if you need normal mapping on your voxels. If you don't need
- [void](#)<span id="i_bake"></span> **bake**( )
- [Voxel](Voxel.md)<span id="i_create_voxel"></span> **create_voxel**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id, [String](https://docs.godotengine.org/en/stable/classes/class_string.html) name )
- [Voxel](Voxel.md)<span id="i_get_voxel"></span> **get_voxel**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
- [Voxel](Voxel.md)<span id="i_get_voxel_by_name"></span> **get_voxel_by_name**( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) name )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_voxel_index_from_name"></span> **get_voxel_index_from_name**( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) name )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -85,39 +85,30 @@ enum **ProcessMode**:
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_debug_dump_as_scene"></span> **debug_dump_as_scene**( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) path )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_debug_get_block_count"></span> **debug_get_block_count**( )
- [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html)<span id="i_debug_get_block_info"></span> **debug_get_block_info**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) block_pos, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) lod )
- [Array](https://docs.godotengine.org/en/stable/classes/class_array.html)<span id="i_debug_get_octrees"></span> **debug_get_octrees**( )
- [Array](https://docs.godotengine.org/en/stable/classes/class_array.html)<span id="i_debug_print_sdf_top_down"></span> **debug_print_sdf_top_down**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) center, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) extents )
- [Array](https://docs.godotengine.org/en/stable/classes/class_array.html)<span id="i_debug_raycast_block"></span> **debug_raycast_block**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) dir )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_block_region_extent"></span> **get_block_region_extent**( )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_block_size"></span> **get_block_size**( )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_process_mode"></span> **get_process_mode**( )
- [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html)<span id="i_get_statistics"></span> **get_statistics**( )
Gets debug information about how much time is spent processing the terrain.
@ -143,17 +134,13 @@ The returned dictionary has the following structure:
- [VoxelTool](VoxelTool.md)<span id="i_get_voxel_tool"></span> **get_voxel_tool**( )
- [void](#)<span id="i_save_modified_blocks"></span> **save_modified_blocks**( )
- [void](#)<span id="i_set_process_mode"></span> **set_process_mode**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) mode )
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_voxel_to_block_position"></span> **voxel_to_block_position**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) lod_index, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) arg1 )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -33,4 +33,4 @@ Gets by how much voxels must be padded before their lower corner in order for th
Gets by how much voxels must be padded after their upper corner in order for the mesher to work.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -30,4 +30,4 @@ Type | Name | Default
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_occlusion_enabled"></span> **occlusion_enabled** = true
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -41,4 +41,4 @@ enum **ColorMode**:
- [VoxelColorPalette](VoxelColorPalette.md)<span id="i_palette"></span> **palette**
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -66,13 +66,10 @@ enum **SeamMode**:
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_get_geometric_error"></span> **get_geometric_error**( )
- [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html)<span id="i_get_statistics"></span> **get_statistics**( )
- [void](#)<span id="i_set_geometric_error"></span> **set_geometric_error**( [float](https://docs.godotengine.org/en/stable/classes/class_float.html) error )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -18,5 +18,4 @@ Return
- [ArrayMesh](https://docs.godotengine.org/en/stable/classes/class_arraymesh.html)<span id="i_build_transition_mesh"></span> **build_transition_mesh**( [VoxelBuffer](VoxelBuffer.md) voxel_buffer, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) direction )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -18,12 +18,15 @@ Type | Name | Default
## Property Descriptions
- [VoxelGenerator](VoxelGenerator.md)<span id="i_generator"></span> **generator**
Procedural generator used to load voxel blocks when not present in the stream.
- [VoxelMesher](VoxelMesher.md)<span id="i_mesher"></span> **mesher**
Defines how voxels are transformed into visible meshes.
- [VoxelStream](VoxelStream.md)<span id="i_stream"></span> **stream**
Primary source of persistent voxel data. If left unassigned, the whole volume will use the generator.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -17,9 +17,11 @@ Type | Name | Default
## Property Descriptions
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_position"></span> **position** = Vector3( 0, 0, 0 )
Integer position of the voxel that was hit.
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_previous_position"></span> **previous_position** = Vector3( 0, 0, 0 )
Integer position of the previous voxel along the ray before the final hit.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -37,4 +37,4 @@ The returned dictionary has the following structure:
```
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -36,24 +36,22 @@ enum **Result**:
## Property Descriptions
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_save_generator_output"></span> **save_generator_output** = false
When this is enabled, if a block cannot be found in the stream and it gets generated, then the generated block will immediately be saved into the stream. This can be used if the generator is too expensive to run on the fly (like Minecraft does), but it will require more disk space and eventual network traffic. If this setting is off, only modified blocks will be saved.
When this is enabled, if a block cannot be found in the stream and it gets generated, then the generated block will immediately be saved into the stream. This can be used if the generator is too expensive to run on the fly (like Minecraft does), but it will require more disk usage (amount of I/Os and space) and eventual network traffic. If this setting is off, only modified blocks will be saved.
## Method Descriptions
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_emerge_block"></span> **emerge_block**( [VoxelBuffer](VoxelBuffer.md) out_buffer, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin_in_voxels, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) lod )
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_get_block_size"></span> **get_block_size**( )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_used_channels_mask"></span> **get_used_channels_mask**( )
- [void](#)<span id="i_immerge_block"></span> **immerge_block**( [VoxelBuffer](VoxelBuffer.md) buffer, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin_in_voxels, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) lod )
`buffer`: Block of voxels to save. It is strongly recommended to not keep a reference to that data afterward, because streams are allowed to cache it, and saved data must represent either snapshots (copies) or last references to the data after the volume they belonged to is destroyed.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -1,6 +1,6 @@
# VoxelStreamBlockFiles
Inherits: [VoxelStreamFile](VoxelStreamFile.md)
Inherits: [VoxelStream](VoxelStream.md)
Loads and saves blocks as individual files under a directory.
@ -20,6 +20,7 @@ Type | Name | Default
## Property Descriptions
- [String](https://docs.godotengine.org/en/stable/classes/class_string.html)<span id="i_directory"></span> **directory** = ""
Directory under which the data is saved.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -1,8 +0,0 @@
# VoxelStreamFile
Inherits: [VoxelStream](VoxelStream.md)
_Generated on Jan 24, 2021_

View File

@ -1,6 +1,6 @@
# VoxelStreamRegionFiles
Inherits: [VoxelStreamFile](VoxelStreamFile.md)
Inherits: [VoxelStream](VoxelStream.md)
Loads and saves blocks to region files indexed by world position, under a directory.
@ -39,6 +39,7 @@ Return |
- [String](https://docs.godotengine.org/en/stable/classes/class_string.html)<span id="i_directory"></span> **directory** = ""
Directory under which the data is saved.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_lod_count"></span> **lod_count** = 1
@ -55,13 +56,10 @@ Directory under which the data is saved.
- [void](#)<span id="i_convert_files"></span> **convert_files**( [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html) new_settings )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_block_size_po2"></span> **get_block_size_po2**( )
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_get_region_size"></span> **get_region_size**( )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -0,0 +1,21 @@
# VoxelStreamSQLite
Inherits: [VoxelStream](VoxelStream.md)
## Properties:
Type | Name | Default
--------- | ---------------------------------- | --------
`String` | [database_path](#i_database_path) | ""
<p></p>
## Property Descriptions
- [String](https://docs.godotengine.org/en/stable/classes/class_string.html)<span id="i_database_path"></span> **database_path** = ""
_Generated on Feb 16, 2021_

View File

@ -19,14 +19,13 @@ Return | Signatur
- [void](#)<span id="i__emerge_block"></span> **_emerge_block**( [VoxelBuffer](VoxelBuffer.md) out_buffer, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin_in_voxels, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) lod )
`out_buffer`: Buffer in which to populate voxel data. It will never be `null` and will have the requested size. It is only valid for this function, do not store it anywhere after the end.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i__get_used_channels_mask"></span> **_get_used_channels_mask**( )
- [void](#)<span id="i__immerge_block"></span> **_immerge_block**( [VoxelBuffer](VoxelBuffer.md) buffer, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin_in_voxels, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) lod )
`buffer`: Buffer of voxel data to save. It is allowed to keep a reference to it for caching purposes, as saved data will either be snapshots or only references left after removal of a volume.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -46,9 +46,11 @@ Emitted when a block unloaded due to being outside view distance.
## Property Descriptions
- [AABB](https://docs.godotengine.org/en/stable/classes/class_aabb.html)<span id="i_bounds"></span> **bounds** = AABB( -5.36871e+08, -5.36871e+08, -5.36871e+08, 1.07374e+09, 1.07374e+09, 1.07374e+09 )
Defines the bounds within which the terrain is allowed to have voxels. If an infinite world generator is used, blocks will only generate within this region. Everything outside will be left empty.
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_generate_collisions"></span> **generate_collisions** = true
Enables the generation of collision shapes using the classic physics engine. Use this feature if you need realistic or non-trivial collisions or physics.
Note 1: you also need [VoxelViewer](VoxelViewer.md) to request collisions, otherwise they won't generate.
@ -56,11 +58,13 @@ Note 1: you also need [VoxelViewer](VoxelViewer.md) to request collisions, other
Note 2: If you need simple Minecraft/AABB physics, you can use [VoxelBoxMover](VoxelBoxMover.md) which may perform better in blocky worlds.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_max_view_distance"></span> **max_view_distance** = 128
Sets the maximum distance this terrain can support. If a [VoxelViewer](VoxelViewer.md) requests more, it will be clamped.
Note: there is an internal limit of 512 for constant LOD terrains, because going further can affect performance and memory very badly at the moment.
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_run_stream_in_editor"></span> **run_stream_in_editor** = true
Makes the terrain appear in the editor.
Important: this option will turn off automatically if you setup a script world generator. Modifying scripts while they are in use by threads causes undefined behaviors. You can still turn on this option if you need a preview, but it is strongly advised to turn it back off and wait until all generation has finished before you edit the script again.
@ -74,7 +78,6 @@ Converts block coordinates into voxel coordinates. Voxel coordinates of a block
- [Material](https://docs.godotengine.org/en/stable/classes/class_material.html)<span id="i_get_material"></span> **get_material**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id )
- [Dictionary](https://docs.godotengine.org/en/stable/classes/class_dictionary.html)<span id="i_get_statistics"></span> **get_statistics**( )
Gets debug information about how much time is spent processing the terrain.
@ -125,9 +128,8 @@ Note 3: saving is asynchronous and won't block the game. the save may complete o
- [void](#)<span id="i_set_material"></span> **set_material**( [int](https://docs.godotengine.org/en/stable/classes/class_int.html) id, [Material](https://docs.godotengine.org/en/stable/classes/class_material.html) material )
- [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html)<span id="i_voxel_to_block"></span> **voxel_to_block**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) voxel_pos )
Converts voxel coordinates into block coordinates.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -19,7 +19,7 @@ Type | Name | Default
`int` | [channel](#i_channel) | 0
`int` | [eraser_value](#i_eraser_value) | 0
`int` | [mode](#i_mode) | 0
`float` | [sdf_scale](#i_sdf_scale) | 0.1
`float` | [sdf_scale](#i_sdf_scale) | 0.002
`int` | [value](#i_value) | 0
<p></p>
@ -54,15 +54,19 @@ enum **Mode**:
## Property Descriptions
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_channel"></span> **channel** = 0
Set which channel will be edited. When used on a terrain node, it will default to the first available channel, based on the stream and generator.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_eraser_value"></span> **eraser_value** = 0
Sets which value will be used to erase voxels when editing the enum VoxelBuffer.CHANNEL_TYPE channel in enum MODE_REMOVE mode.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_mode"></span> **mode** = 0
Sets how `do_*` functions will behave. This may vary depending on the channel.
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_sdf_scale"></span> **sdf_scale** = 0.1
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_sdf_scale"></span> **sdf_scale** = 0.002
When working with smooth voxels, applies a scale to the signed distance field. A high scale (1 or higher) will tend to produce blocky results, and a low scale (below 1, but not too close to zero) will tend to be smoother.
@ -70,6 +74,7 @@ When working with smooth voxels, applies a scale to the signed distance field. A
This is related to the enum VoxelBuffer.Depth configuration on voxels. For 8-bit and 16-bit, there is a limited range of values the Signed Distance Field can take, and by default it is clamped to -1..1, so the gradient can only range across 2 voxels. But when LOD is used, it is better to stretch that range over a longer distance, and this is achieved by scaling SDF values.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_value"></span> **value** = 0
Sets which voxel value will be used. This is not relevant when editing enum VoxelBuffer.CHANNEL_SDF.
## Method Descriptions
@ -81,45 +86,34 @@ Operate on a rectangular cuboid section of the terrain. `begin` and `end` are in
- [void](#)<span id="i_do_point"></span> **do_point**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos )
- [void](#)<span id="i_do_sphere"></span> **do_sphere**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) center, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) radius )
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_get_voxel"></span> **get_voxel**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos )
- [float](https://docs.godotengine.org/en/stable/classes/class_float.html)<span id="i_get_voxel_f"></span> **get_voxel_f**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos )
- [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html)<span id="i_get_voxel_metadata"></span> **get_voxel_metadata**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos )
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_is_area_editable"></span> **is_area_editable**( [AABB](https://docs.godotengine.org/en/stable/classes/class_aabb.html) box )
- [void](#)<span id="i_paste"></span> **paste**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) dst_pos, [Reference](https://docs.godotengine.org/en/stable/classes/class_reference.html) src_buffer, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) src_mask_value )
- [VoxelRaycastResult](VoxelRaycastResult.md)<span id="i_raycast"></span> **raycast**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) origin, [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) direction, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) max_distance=10.0, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) collision_mask=4294967295 )
- [void](#)<span id="i_set_voxel"></span> **set_voxel**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos, [int](https://docs.godotengine.org/en/stable/classes/class_int.html) v )
- [void](#)<span id="i_set_voxel_f"></span> **set_voxel_f**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos, [float](https://docs.godotengine.org/en/stable/classes/class_float.html) v )
- [void](#)<span id="i_set_voxel_metadata"></span> **set_voxel_metadata**( [Vector3](https://docs.godotengine.org/en/stable/classes/class_vector3.html) pos, [Variant](https://docs.godotengine.org/en/stable/classes/class_variant.html) meta )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -5,4 +5,4 @@ Inherits: [VoxelTool](VoxelTool.md)
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -19,4 +19,4 @@ Return | Signature
Picks random voxels within the specified area and executes a function on them. This only works for terrains using [VoxelMesherBlocky](VoxelMesherBlocky.md). Only voxels where member Voxel.random_tickable is `true` will be picked.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -24,12 +24,15 @@ Type | Name | Default
## Property Descriptions
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_requires_collisions"></span> **requires_collisions** = false
If set to `true`, the engine will generate classic collision shapes around this viewer.
- [bool](https://docs.godotengine.org/en/stable/classes/class_bool.html)<span id="i_requires_visuals"></span> **requires_visuals** = true
If set to `true`, the engine will generate meshes around this viewer. This may be enabled for the local player.
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_view_distance"></span> **view_distance** = 128
How far should voxels generate around this viewer.
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -18,5 +18,4 @@ Return | Signatur
- [int](https://docs.godotengine.org/en/stable/classes/class_int.html)<span id="i_load_from_file"></span> **load_from_file**( [String](https://docs.godotengine.org/en/stable/classes/class_string.html) fpath, [VoxelBuffer](VoxelBuffer.md) voxels, [VoxelColorPalette](VoxelColorPalette.md) arg2 )
_Generated on Jan 24, 2021_
_Generated on Feb 16, 2021_

View File

@ -27,7 +27,7 @@ Fast collisions alternative
### Move and slide
Mesh-based collisions are quite accurate and feature-rich in Godot, however it some drawbacks:
Mesh-based collisions are quite accurate and feature-rich in Godot, however it has some drawbacks:
- Trimesh collision shapes have to be built each time the terrain is modified, which is [very slow](https://github.com/Zylann/godot_voxel/issues/54).
- The physics engine has to process arbitrary triangles near the player, which can't take advantage of particular situations, such as everything being cubes
@ -53,7 +53,7 @@ func _physics_process(delta):
```
!!! note
this technique mainly works if you use `VoxelMesherBlocky`. It might have some limited support in other meshers though.
this technique mainly works if you use `VoxelMesherBlocky`, because it gets information about which block is collidable from the `VoxelLibrary` used with it. It might have some limited support in other meshers though.
### Raycast

View File

@ -32,6 +32,7 @@ Ongoing development - `master`
- The SDF channel is now 16-bit by default instead of 8-bit, which reduces terracing in big terrains
- Optimized `VoxelGeneratorGraph` by making it detect empty blocks more accurately and process by buffers
- Added `SdfSphereHeightmap` and `Normalize` nodes to voxel graph, which can help making planets
- Added `VoxelInstancer` to instantiate items on top of `VoxelLodTerrain`, aimed at spawning natural elements such as rocks and foliage
- Blocky voxels
- Introduced a second blocky mesher dedicated to colored cubes, with greedy meshing and palette support
@ -49,6 +50,7 @@ Ongoing development - `master`
- The meshing system no longer "guesses" how voxels will look like. Instead it uses the mesher assigned to the terrain.
- SDF and TYPE channels have different default depth, so if you relied on 8-bit depth, you may have to explicitely set that format in your generator, to avoid mismatch with existing savegames
- The block serialization format has changed, and migration is not implemented, so old saves using it cannot be used. See documentation for more information.
- Terrains no longer auto-save when they are destroyed while having a `stream` assigned. You have to call `save_modified_blocks()` explicitely before doing that.
- Fixes
- C# should be able to properly implement generator/stream functions

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

112
doc/source/instancing.md Normal file
View File

@ -0,0 +1,112 @@
Instancing
=============
The module provides an instancing system with the [VoxelInstancer](api/VoxelInstancer.md) node. It allows to spawn 3D models on top of the terrain's surface. It uses `MultiMeshInstance` internally to allow rendering a lot of them at once, and can support collisions and removal. The node must be added as child of [VoxelLodTerrain](api/VoxelLodTerrain.md).
This system is primarily intented at natural spawning: grass, rocks, trees and other kinds of semi-random foliage. It is not suited for complex man-made structures like houses or villages.
This feature is currently only available under `VoxelLodTerrain`.
VoxelInstanceLibrary
---------------------
### Library setup
In order to spawn items, `VoxelInstancer` needs a [VoxelInstanceLibrary](api/VoxelInstanceLibrary.md) resource. This resource contains a list of all the items that can be spawned, and how they will be placed.
Select a `VoxelInstancer`. In the inspector, assign a library to the `library` property, or create a new embedded one. Then click on the library resource. Now a menu should show up in the top bar of the main viewport:
![Screenshot of the VoxelInstanceLibrary menu](images/instance_library_menu.png)
In this menu, you can add items to the library by clicking `VoxelInstanceLibrary -> Add item`.
Items created this way come with a default setup, so you should be able to see something appear on top of the voxel surface.
!!! note
If you are making a planet, you may want to set the `up_mode` of `VoxelInstancer` to `Sphere` mode. This will tell the instancer where the upward direction is, and will align the items according to the local origin of the terrain.
### Block LOD
The range at which items spawn is based on the LOD system of the voxel terrain itself. This is configured in the `lod_index` property of [VoxelInstanceLibraryItem](api/VoxelInstanceLibraryItem.md). For example, choosing `0` will make the item spawn at the closest range, and fade quickly in the distance. Higher indexes will spawn on a larger range, so will also start to appear earlier as the player gets closer. Instances spawn in the same "blocks" as the ground.
![Screenshot showing the effect of lod_index on the range of instances](images/instances_lod_index.png)
Usually landscapes may be composed of multiple layers so that the closer you get, the more details come in. Bigger items use high lod indexes to be seen from far away, while smaller items may use lower indexes.
![Screenshot of landscape using layers of instances](images/landscape_with_instances.png)
There is a balance to consider when choosing the appropriate `lod_index`: currently, larger indexes are *much more imprecise*, because they work on top of a lower-resolution mesh. When getting closer, it's possible that such instances are seen floating above ground, or sinking into it. This mostly happens in areas with sharp changes such as ridges, crevices or caves:
![Screemshot of misaligned instances](images/misaligned_instances.png)
To combat this, you can adjust the `offset_along_normal` parameter in the `generator` associated to the item. This depends on the asset, so designing them such that they can have part of their bottom sunk into the ground can give some margin of error.
Sometimes it might not be enough, so this problem still has to be worked out in the future. Possible approaches include:
- Querying the world generator to approximate the surface without using the mesh (not suitable if the ground was edited)
- Gradually snap the instances somehow as higher-resolution data becomes available
- Load edited voxels for the entire world at once so they can be queried even from far distance (takes more memory)
!!! note
When making grass or other items, it may be a good idea to fade meshes based on distance from the camera using a custom shader, so they won't disappear abruptly. Using a ground texture of similar colors also helps to make it blend.
### Mesh LOD
A secondary LOD system is included, which applies to meshes themselves, to some limited extent. Godot 3 does not have a LOD system (Godot 4 will), so this allows to reduce vertex count over distance from within a set of visible meshes.
To use this, you have to fill the 3 mesh LOD properties on your `VoxelInstanceLibraryItem`:
![Screenshot of mesh LOD properties](images/mesh_lod_properties.png)
If only the `mesh` property is set, no LOD will be used.
The distance at which a LOD will be chosen is currently hardcoded, because it depends on the `lod_index` the blocks for that item are loaded into, which in turn depends on the `split_scale` property of the parent voxel terrain.
![Screenshot of mesh LODs with colors](images/mesh_lods.png)
If you need fewer LODs, you can assign twice the same mesh. This system is quite rigid because in Godot 4 it might be changed to only have a single slot dedicated to impostor meshes. Indeed, Godot 4 might support LOD on meshes, but it is not planned for the last LODs to become impostors, so this should still be possible to achieve.
!!! note
Impostor meshes are simple quads that can fake the presence of the real model over far distances. For example, this is a really fast way to render forests from afar, while being able to use detailed trees when coming closer.
### Edition
Editing instances manually in the editor is not supported yet. It is only possible to define where instances spawn by using procedural generation.
It is however possible to remove them from within the game, when digging the ground they spawned on.
### Persistence
It is possible for some items to be persistent. This option can be enabled with the `persistent` property of [VoxelInstanceLibraryItem](api/VoxelInstanceLibraryItem.md). If the parent terrain has a `VoxelStream` supporting it, then instances from edited blocks will be saved into the stream, and won't respawn next time the player gets close to the area. Non-persistent instances will always respawn on each surface where procedural conditions are fulfilled.
The ID of persistent items is important, because it will be used in saved data. If you delete an item and try to load instances from a stream that still contains them, warnings will occur.
At time of writing, only [VoxelStreamSQLite](api/VoxelStreamSQLite.md) supports saving instances.
The save format is described in [this document](specs/instances_format.md).
Procedural generation
-----------------------
### Built-in generator
![Screenshot of a layer of instances using noise](images/instances_procgen.png)
Items are added with a default built-in generator, so they will already spawn based on procedural rules rather than being painted manually. You can tweak the generator by inspecting the `generator` property of [VoxelInstanceLibraryItem](api/VoxelInstanceLibraryItem.md).
Persistent instances located in a block that was edited in the game will no longer regenerate.
### Custom instance generator
The feature is recent and the API may still change, so this is not available to scripts at the moment.
Streaming events (advanced)
----------------------------
`VoxelInstancer` knows when to spawn things by registering itself to its parent's block events. This is currently not available to the script API of `VoxelLodTerrain`, but may be added in the future.

View File

@ -1,4 +1,4 @@
Block format
Voxel block format
====================
Version: 2
@ -28,7 +28,7 @@ This is the format provided by the `VoxelBlockSerializer` utility class. If you
Compressed data starts with one byte. Depending on its value, what follows is different.
- 0: no compression. Following bytes can be read as as block format directly. This is rarely used and could be for debugging.
- 1: LZ4 compression. The next 32-bit unsigned integer is the size of the decompressed data, and following bytes are compressed data using LZ4 default parameters. This mode is used by default.
- 1: LZ4 compression. The next big-endian 32-bit unsigned integer is the size of the decompressed data, and following bytes are compressed data using LZ4 default parameters. This mode is used by default.
Knowing the size of the decompressed data may be important when parsing the block later.

View File

@ -0,0 +1,65 @@
Instance block format
=======================
This page describes the binary format used by the module to save instances to files or databases.
Specification
---------------
### Compressed container
A block is usually serialized as compressed data.
Compressed data starts with one byte. Depending on its value, what follows is different.
- 0: no compression. Following bytes can be read as as block format directly. This is rarely used and could be for debugging.
- 1: LZ4 compression. The next big-endian 32-bit unsigned integer is the size of the decompressed data, and following bytes are compressed data using LZ4 default parameters. This mode is used by default.
### Binary data
This data uses big-endian.
In pseudo-code:
```cpp
// Root structure
struct InstanceBlockData {
// Version tag in case more stuff is added in the future
uint8_t version = 0;
// There can be up to 256 different layers in one block
uint8_t layer_count;
// To compress positions we need to know their range.
// It's local to the block so we know it starts from zero.
float position_range;
LayerData layers[layer_count];
// Magic number to signal the end of the data block
uint32_t control_end = 0x900df00d;
};
struct LayerData {
uint16_t id; // Identifies the type of instances (rocks, grass, pebbles, bushes etc)
uint16_t count;
// To be able to compress scale we must know its range
float scale_min;
float scale_max;
// This tells which format instances of this layer use. For now I always use the same format,
// But maybe some types of instances will need more, or less data?
uint8_t format = 0;
InstanceData data[count];
};
struct InstanceData {
// Position is lossy-compressed based on the size of the block
uint16_t x;
uint16_t y;
uint16_t z;
// Scale is uniform and is lossy-compressed to 256 values
uint8_t scale;
// Rotation is a compressed quaternion
uint8_t x;
uint8_t y;
uint8_t z;
uint8_t w;
};
```

View File

@ -127,7 +127,7 @@ The obtained buffer can be read using the block format.
Block format
--------------
See [Block format](block_format.md)
See [Block format](block_format_v1.md)
Current Issues

View File

@ -155,7 +155,7 @@ The obtained buffer can be read using the block format.
Block format
--------------
See [Block format](block_format.md)
See [Block format](block_format_v2.md)
Current Issues

View File

@ -36,7 +36,7 @@ Contains every block of the volume. There can be thousands of them.
- `loc` is a 64-bit integer packing the coordinates and LOD index of the block using little-endian. Coordinates are equal to the origin of the block in voxels, divided by the size of the block + lod index using euclidean division (`coord >> (block_size_po2 + lod_index)`). XYZ are 16-bit signed integers, and LOD is a 8-bit unsigned integer: `0LXXYYZZ`
- `vb` contains compressed voxel data using the [Block format](block_format_v2.md).
- `instances` is currently unused but is planned to contain instancing data.
- `instances` contains compressed instance data using the [Instance format](instances_format.md).
### `channels`

View File

@ -20,5 +20,5 @@ Save formats
--------------
- [Region format](specs/region_format_v3.md)
- [Block format](specs/block_format.md)
- [Block format](specs/block_format_v2.md)

View File

@ -105,6 +105,8 @@ def update_mkdocs_file(mkdocs_config_fpath, md_classes_dir):
processed_lines.append(line)
for class_file in class_files:
processed_lines.append(indent + "- " + class_file + "\n")
else:
processed_lines.append(line)
yml = "".join(processed_lines)
with open(mkdocs_config_fpath, 'w', encoding='utf-8') as f:

View File

@ -328,8 +328,12 @@ def process_xml(f_xml, f_out, module_class_names):
out += "\n\n"
if member.text is not None:
out += make_text(member.text, module_class_names)
out += "\n\n"
text = make_text(member.text, module_class_names)
if text != "":
out += text
out += "\n"
out += "\n"
# Method descriptions
if len(methods) > 0:
@ -350,9 +354,11 @@ def process_xml(f_xml, f_out, module_class_names):
desc = method.find('description')
if desc is not None:
text = make_text(desc.text, module_class_names)
out += text
if text != "":
out += text
out += "\n"
out += "\n\n"
out += "\n"
# Footer
out += "_Generated on " + strftime("%b %d, %Y", gmtime()) + "_\n"

View File

@ -73,11 +73,11 @@ Ref<VoxelRaycastResult> VoxelTool::raycast(Vector3 pos, Vector3 dir, float max_d
// See derived classes for implementations
}
uint64_t VoxelTool::get_voxel(Vector3i pos) {
uint64_t VoxelTool::get_voxel(Vector3i pos) const {
return _get_voxel(pos);
}
float VoxelTool::get_voxel_f(Vector3i pos) {
float VoxelTool::get_voxel_f(Vector3i pos) const {
return _get_voxel_f(pos);
}
@ -122,12 +122,12 @@ void VoxelTool::do_circle(Vector3i pos, int radius, Vector3i direction) {
ERR_PRINT("Not implemented");
}
uint64_t VoxelTool::_get_voxel(Vector3i pos) {
uint64_t VoxelTool::_get_voxel(Vector3i pos) const {
ERR_PRINT("Not implemented");
return 0;
}
float VoxelTool::_get_voxel_f(Vector3i pos) {
float VoxelTool::_get_voxel_f(Vector3i pos) const {
ERR_PRINT("Not implemented");
return 0;
}

View File

@ -48,8 +48,8 @@ public:
void set_eraser_value(uint64_t value);
uint64_t get_eraser_value() const;
uint64_t get_voxel(Vector3i pos);
float get_voxel_f(Vector3i pos);
uint64_t get_voxel(Vector3i pos) const;
float get_voxel_f(Vector3i pos) const;
float get_sdf_scale() const;
void set_sdf_scale(float s);
@ -82,8 +82,8 @@ protected:
// These methods never go alone, but may be used in others.
// They don't represent an edit, they only abstract the lower-level API
virtual uint64_t _get_voxel(Vector3i pos);
virtual float _get_voxel_f(Vector3i pos);
virtual uint64_t _get_voxel(Vector3i pos) const;
virtual float _get_voxel_f(Vector3i pos) const;
virtual void _set_voxel(Vector3i pos, uint64_t v);
virtual void _set_voxel_f(Vector3i pos, float v);
virtual void _post_edit(const Rect3i &box);

View File

@ -11,12 +11,12 @@ bool VoxelToolBuffer::is_area_editable(const Rect3i &box) const {
return Rect3i(Vector3i(), _buffer->get_size()).encloses(box);
}
uint64_t VoxelToolBuffer::_get_voxel(Vector3i pos) {
uint64_t VoxelToolBuffer::_get_voxel(Vector3i pos) const {
ERR_FAIL_COND_V(_buffer.is_null(), 0);
return _buffer->get_voxel(pos, _channel);
}
float VoxelToolBuffer::_get_voxel_f(Vector3i pos) {
float VoxelToolBuffer::_get_voxel_f(Vector3i pos) const {
ERR_FAIL_COND_V(_buffer.is_null(), 0);
return _buffer->get_voxel_f(pos.x, pos.y, pos.z, _channel);
}

View File

@ -18,8 +18,8 @@ public:
Variant get_voxel_metadata(Vector3i pos) override;
protected:
uint64_t _get_voxel(Vector3i pos) override;
float _get_voxel_f(Vector3i pos) override;
uint64_t _get_voxel(Vector3i pos) const override;
float _get_voxel_f(Vector3i pos) const override;
void _set_voxel(Vector3i pos, uint64_t v) override;
void _set_voxel_f(Vector3i pos, float v) override;
void _post_edit(const Rect3i &box) override;

View File

@ -15,12 +15,12 @@ bool VoxelToolLodTerrain::is_area_editable(const Rect3i &box) const {
return _map.is_area_fully_loaded(box.padded(1));
}
uint64_t VoxelToolLodTerrain::_get_voxel(Vector3i pos) {
uint64_t VoxelToolLodTerrain::_get_voxel(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _map.get_voxel(pos, _channel);
}
float VoxelToolLodTerrain::_get_voxel_f(Vector3i pos) {
float VoxelToolLodTerrain::_get_voxel_f(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _map.get_voxel_f(pos, _channel);
}

View File

@ -14,8 +14,8 @@ public:
bool is_area_editable(const Rect3i &box) const override;
protected:
uint64_t _get_voxel(Vector3i pos) override;
float _get_voxel_f(Vector3i pos) override;
uint64_t _get_voxel(Vector3i pos) const override;
float _get_voxel_f(Vector3i pos) const override;
void _set_voxel(Vector3i pos, uint64_t v) override;
void _set_voxel_f(Vector3i pos, float v) override;
void _post_edit(const Rect3i &box) override;

View File

@ -80,12 +80,12 @@ Ref<VoxelRaycastResult> VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa
return res;
}
uint64_t VoxelToolTerrain::_get_voxel(Vector3i pos) {
uint64_t VoxelToolTerrain::_get_voxel(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _terrain->get_storage().get_voxel(pos, _channel);
}
float VoxelToolTerrain::_get_voxel_f(Vector3i pos) {
float VoxelToolTerrain::_get_voxel_f(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _terrain->get_storage().get_voxel_f(pos, _channel);
}

View File

@ -24,8 +24,8 @@ public:
void run_blocky_random_tick(AABB voxel_area, int voxel_count, Ref<FuncRef> callback, int block_batch_count) const;
protected:
uint64_t _get_voxel(Vector3i pos) override;
float _get_voxel_f(Vector3i pos) override;
uint64_t _get_voxel(Vector3i pos) const override;
float _get_voxel_f(Vector3i pos) const override;
void _set_voxel(Vector3i pos, uint64_t v) override;
void _set_voxel_f(Vector3i pos, float v) override;
void _post_edit(const Rect3i &box) override;

View File

@ -0,0 +1,116 @@
#include "voxel_instance_library_editor_plugin.h"
#include <scene/gui/dialogs.h>
#include <scene/gui/menu_button.h>
#include <scene/resources/primitive_meshes.h>
VoxelInstanceLibraryEditorPlugin::VoxelInstanceLibraryEditorPlugin(EditorNode *p_node) {
_menu_button = memnew(MenuButton);
_menu_button->set_text(TTR("VoxelInstanceLibrary"));
// TODO Icon
//_menu_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("MeshLibrary", "EditorIcons"));
_menu_button->get_popup()->add_item(TTR("Add Item"), MENU_ADD_ITEM);
_menu_button->get_popup()->add_item(TTR("Remove Selected Item"), MENU_REMOVE_ITEM);
// TODO Add and update from scene
_menu_button->get_popup()->connect("id_pressed", this, "_on_menu_id_pressed");
_menu_button->hide();
Control *base_control = get_editor_interface()->get_base_control();
_confirmation_dialog = memnew(ConfirmationDialog);
_confirmation_dialog->connect("confirmed", this, "_on_remove_item_confirmed");
base_control->add_child(_confirmation_dialog);
_info_dialog = memnew(AcceptDialog);
base_control->add_child(_info_dialog);
add_control_to_container(EditorPlugin::CONTAINER_SPATIAL_EDITOR_MENU, _menu_button);
}
bool VoxelInstanceLibraryEditorPlugin::handles(Object *p_object) const {
VoxelInstanceLibrary *lib = Object::cast_to<VoxelInstanceLibrary>(p_object);
return lib != nullptr;
}
void VoxelInstanceLibraryEditorPlugin::edit(Object *p_object) {
VoxelInstanceLibrary *lib = Object::cast_to<VoxelInstanceLibrary>(p_object);
_library.reference_ptr(lib);
}
void VoxelInstanceLibraryEditorPlugin::make_visible(bool visible) {
_menu_button->set_visible(visible);
}
void VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed(int id) {
switch (id) {
case MENU_ADD_ITEM: {
ERR_FAIL_COND(_library.is_null());
Ref<VoxelInstanceLibraryItem> item;
item.instance();
// Setup some defaults
Ref<CubeMesh> mesh;
mesh.instance();
item->set_mesh(mesh, 0);
item->set_lod_index(2);
Ref<VoxelInstanceGenerator> generator;
generator.instance();
item->set_generator(generator);
const int item_id = _library->get_next_available_id();
UndoRedo &ur = get_undo_redo();
ur.create_action("Add item");
ur.add_do_method(*_library, "add_item", item_id, item);
ur.add_undo_method(*_library, "remove_item", item_id);
ur.commit_action();
} break;
case MENU_REMOVE_ITEM: {
ERR_FAIL_COND(_library.is_null());
String path = get_editor_interface()->get_inspector()->get_selected_path();
String prefix = "item_";
if (path.begins_with(prefix)) {
_item_id_to_remove = path.substr(prefix.size()).to_int();
_confirmation_dialog->set_text(vformat(TTR("Remove item %d?"), _item_id_to_remove));
_confirmation_dialog->popup_centered();
} else {
// The inspector won't let us know which item the current resource is stored into...
// Gridmap works because it simulates every item as properties of the library,
// but our current resource does not do that,
// and I don't want to modify the API just because the built-in inspector is bad.
_info_dialog->set_text(TTR(
"Could not determine selected item.\n"
"You must select the `item_X` property label of the item you want to remove."));
}
} break;
default:
ERR_PRINT("Unknown menu item");
break;
}
}
void VoxelInstanceLibraryEditorPlugin::_on_remove_item_confirmed() {
ERR_FAIL_COND(_library.is_null());
ERR_FAIL_COND(_item_id_to_remove == -1);
Ref<VoxelInstanceLibraryItem> item = _library->get_item(_item_id_to_remove);
UndoRedo &ur = get_undo_redo();
ur.create_action("Remove item");
ur.add_do_method(*_library, "remove_item", _item_id_to_remove);
ur.add_undo_method(*_library, "add_item", _item_id_to_remove, item);
ur.commit_action();
_item_id_to_remove = -1;
}
void VoxelInstanceLibraryEditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("_on_menu_id_pressed", "id"), &VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed);
ClassDB::bind_method(D_METHOD("_on_remove_item_confirmed"),
&VoxelInstanceLibraryEditorPlugin::_on_remove_item_confirmed);
}

View File

@ -0,0 +1,40 @@
#ifndef VOXEL_INSTANCE_LIBRARY_EDITOR_PLUGIN_H
#define VOXEL_INSTANCE_LIBRARY_EDITOR_PLUGIN_H
#include "../../terrain/instancing/voxel_instance_library.h"
#include <editor/editor_plugin.h>
class MenuButton;
class ConfirmationDialog;
class VoxelInstanceLibraryEditorPlugin : public EditorPlugin {
GDCLASS(VoxelInstanceLibraryEditorPlugin, EditorPlugin)
public:
virtual String get_name() const { return "VoxelInstanceLibrary"; }
VoxelInstanceLibraryEditorPlugin(EditorNode *p_node);
bool handles(Object *p_object) const override;
void edit(Object *p_object) override;
void make_visible(bool visible) override;
private:
void _on_menu_id_pressed(int id);
void _on_remove_item_confirmed();
static void _bind_methods();
enum MenuOption {
MENU_ADD_ITEM,
MENU_REMOVE_ITEM
};
MenuButton *_menu_button = nullptr;
ConfirmationDialog *_confirmation_dialog = nullptr;
AcceptDialog *_info_dialog = nullptr;
int _item_id_to_remove = -1;
Ref<VoxelInstanceLibrary> _library;
};
#endif // VOXEL_INSTANCE_LIBRARY_EDITOR_PLUGIN_H

View File

@ -236,6 +236,7 @@ _FORCE_INLINE_ int Vector3i::distance_sq(const Vector3i &other) const {
return (other - *this).length_sq();
}
// For Godot
struct Vector3iHasher {
static _FORCE_INLINE_ uint32_t hash(const Vector3i &v) {
uint32_t hash = hash_djb2_one_32(v.x);
@ -244,4 +245,14 @@ struct Vector3iHasher {
}
};
// For STL
namespace std {
template <>
struct hash<Vector3i> {
size_t operator()(const Vector3i &v) const {
return Vector3iHasher::hash(v);
}
};
} // namespace std
#endif // VOXEL_VECTOR3I_H

View File

@ -5,6 +5,7 @@
#include "editor/editor_plugin.h"
#include "editor/fast_noise_lite/fast_noise_lite_editor_plugin.h"
#include "editor/graph/voxel_graph_editor_plugin.h"
#include "editor/instance_library/voxel_instance_library_editor_plugin.h"
#include "editor/terrain/voxel_terrain_editor_plugin.h"
#include "generators/graph/voxel_generator_graph.h"
#include "generators/graph/voxel_graph_node_db.h"
@ -27,6 +28,7 @@
#include "streams/vox_loader.h"
#include "streams/voxel_stream_block_files.h"
#include "streams/voxel_stream_script.h"
#include "terrain/instancing/voxel_instancer.h"
#include "terrain/voxel_box_mover.h"
#include "terrain/voxel_lod_terrain.h"
#include "terrain/voxel_map.h"
@ -55,9 +57,12 @@ void register_voxel_types() {
// TODO Can I prevent users from instancing it? is "register_virtual_class" correct for a class that's not abstract?
ClassDB::register_class<VoxelServer>();
// Misc
ClassDB::register_class<Voxel>();
ClassDB::register_class<VoxelLibrary>();
ClassDB::register_class<VoxelColorPalette>();
ClassDB::register_class<VoxelInstanceLibrary>();
ClassDB::register_class<VoxelInstanceLibraryItem>();
// Storage
ClassDB::register_class<VoxelBuffer>();
@ -67,6 +72,8 @@ void register_voxel_types() {
ClassDB::register_class<VoxelTerrain>();
ClassDB::register_class<VoxelLodTerrain>();
ClassDB::register_class<VoxelViewer>();
ClassDB::register_class<VoxelInstanceGenerator>();
ClassDB::register_class<VoxelInstancer>();
// Streams
ClassDB::register_virtual_class<VoxelStream>();
@ -116,6 +123,7 @@ void register_voxel_types() {
#ifdef TOOLS_ENABLED
EditorPlugins::add_by_type<VoxelGraphEditorPlugin>();
EditorPlugins::add_by_type<VoxelTerrainEditorPlugin>();
EditorPlugins::add_by_type<VoxelInstanceLibraryEditorPlugin>();
EditorPlugins::add_by_type<FastNoiseLiteEditorPlugin>();
#endif
}

View File

@ -50,15 +50,18 @@ VoxelServer::VoxelServer() {
// TODO Automatic thread assignment and project settings
// Can't be more than 1 thread. File access with more threads isn't worth it.
_streaming_thread_pool.set_name("Voxel streaming");
_streaming_thread_pool.set_thread_count(1);
_streaming_thread_pool.set_priority_update_period(300);
_streaming_thread_pool.set_batch_count(16);
_generation_thread_pool.set_name("Voxel generation");
_generation_thread_pool.set_thread_count(2);
_generation_thread_pool.set_priority_update_period(300);
_generation_thread_pool.set_batch_count(1);
// This pool works on visuals so it must have lower latency
_meshing_thread_pool.set_name("Voxel meshing");
_meshing_thread_pool.set_thread_count(2);
_meshing_thread_pool.set_priority_update_period(64);
_meshing_thread_pool.set_batch_count(1);
@ -256,23 +259,23 @@ void VoxelServer::request_block_mesh(uint32_t volume_id, BlockMeshInput &input)
_meshing_thread_pool.enqueue(r);
}
void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int lod) {
void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int lod, bool request_instances) {
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream_dependency == nullptr);
if (volume.stream_dependency->stream.is_valid()) {
BlockDataRequest r;
r.volume_id = volume_id;
r.position = block_pos;
r.lod = lod;
r.type = BlockDataRequest::TYPE_LOAD;
r.block_size = volume.block_size;
r.stream_dependency = volume.stream_dependency;
BlockDataRequest *r = memnew(BlockDataRequest);
r->volume_id = volume_id;
r->position = block_pos;
r->lod = lod;
r->type = BlockDataRequest::TYPE_LOAD;
r->block_size = volume.block_size;
r->stream_dependency = volume.stream_dependency;
r->request_instances = request_instances;
init_priority_dependency(r.priority_dependency, block_pos, lod, volume);
init_priority_dependency(r->priority_dependency, block_pos, lod, volume);
BlockDataRequest *rp = memnew(BlockDataRequest(r));
_streaming_thread_pool.enqueue(rp);
_streaming_thread_pool.enqueue(r);
} else {
// Directly generate the block without checking the stream
@ -292,24 +295,48 @@ void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int
}
}
void VoxelServer::request_block_save(uint32_t volume_id, Ref<VoxelBuffer> voxels, Vector3i block_pos, int lod) {
void VoxelServer::request_voxel_block_save(uint32_t volume_id, Ref<VoxelBuffer> voxels, Vector3i block_pos, int lod) {
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream.is_null());
CRASH_COND(volume.stream_dependency == nullptr);
BlockDataRequest r;
r.voxels = voxels;
r.volume_id = volume_id;
r.position = block_pos;
r.lod = lod;
r.type = BlockDataRequest::TYPE_SAVE;
r.block_size = volume.block_size;
r.stream_dependency = volume.stream_dependency;
BlockDataRequest *r = memnew(BlockDataRequest);
r->voxels = voxels;
r->volume_id = volume_id;
r->position = block_pos;
r->lod = lod;
r->type = BlockDataRequest::TYPE_SAVE;
r->block_size = volume.block_size;
r->stream_dependency = volume.stream_dependency;
r->request_instances = false;
r->request_voxels = true;
// No priority data, saving doesnt need sorting
BlockDataRequest *rp = memnew(BlockDataRequest(r));
_streaming_thread_pool.enqueue(rp);
_streaming_thread_pool.enqueue(r);
}
void VoxelServer::request_instance_block_save(uint32_t volume_id, std::unique_ptr<VoxelInstanceBlockData> instances,
Vector3i block_pos, int lod) {
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream.is_null());
CRASH_COND(volume.stream_dependency == nullptr);
BlockDataRequest *r = memnew(BlockDataRequest);
r->instances = std::move(instances);
r->volume_id = volume_id;
r->position = block_pos;
r->lod = lod;
r->type = BlockDataRequest::TYPE_SAVE;
r->block_size = volume.block_size;
r->stream_dependency = volume.stream_dependency;
r->request_instances = true;
r->request_voxels = false;
// No priority data, saving doesnt need sorting
_streaming_thread_pool.enqueue(r);
}
void VoxelServer::request_block_generate_from_data_request(BlockDataRequest *src) {
@ -336,19 +363,19 @@ void VoxelServer::request_block_save_from_generate_request(BlockGenerateRequest
ERR_FAIL_COND(src->voxels.is_null());
BlockDataRequest r;
r.voxels = src->voxels->duplicate(true);
r.volume_id = src->volume_id;
r.position = src->position;
r.lod = src->lod;
r.type = BlockDataRequest::TYPE_SAVE;
r.block_size = src->block_size;
r.stream_dependency = src->stream_dependency;
BlockDataRequest *r = memnew(BlockDataRequest());
r->voxels = src->voxels->duplicate(true);
r->volume_id = src->volume_id;
r->position = src->position;
r->lod = src->lod;
r->type = BlockDataRequest::TYPE_SAVE;
r->block_size = src->block_size;
r->stream_dependency = src->stream_dependency;
// No instances, generators are not designed to produce them at this stage yet.
// No priority data, saving doesnt need sorting
BlockDataRequest *rp = memnew(BlockDataRequest(r));
_streaming_thread_pool.enqueue(rp);
_streaming_thread_pool.enqueue(r);
}
void VoxelServer::remove_volume(uint32_t volume_id) {
@ -438,6 +465,7 @@ void VoxelServer::process() {
r->type != BlockDataRequest::TYPE_FALLBACK_ON_GENERATOR) {
BlockDataOutput o;
o.voxels = r->voxels;
o.instances = std::move(r->instances);
o.position = r->position;
o.lod = r->lod;
o.dropped = !r->has_run;
@ -455,7 +483,7 @@ void VoxelServer::process() {
CRASH_NOW_MSG("Unexpected data request response type");
}
volume->reception_buffers->data_output.push_back(o);
volume->reception_buffers->data_output.push_back(std::move(o));
}
} else {
@ -482,7 +510,7 @@ void VoxelServer::process() {
o.lod = r->lod;
o.dropped = !r->has_run;
o.type = BlockDataOutput::TYPE_LOAD;
volume->reception_buffers->data_output.push_back(o);
volume->reception_buffers->data_output.push_back(std::move(o));
}
} else {
@ -552,29 +580,6 @@ void VoxelServer::process() {
}
}
// void VoxelServer::get_min_max_block_padding(
// bool blocky_enabled, bool smooth_enabled, unsigned int &out_min_padding, unsigned int &out_max_padding) const {
// // const Volume &volume = _world.volumes.get(volume_id);
// // bool smooth_enabled = volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF);
// // bool blocky_enabled = volume.voxel_library.is_valid() &&
// // volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_TYPE);
// out_min_padding = 0;
// out_max_padding = 0;
// if (blocky_enabled) {
// out_min_padding = max(out_min_padding, _blocky_meshers[0]->get_minimum_padding());
// out_max_padding = max(out_max_padding, _blocky_meshers[0]->get_maximum_padding());
// }
// if (smooth_enabled) {
// out_min_padding = max(out_min_padding, _smooth_meshers[0]->get_minimum_padding());
// out_max_padding = max(out_max_padding, _smooth_meshers[0]->get_maximum_padding());
// }
// }
static unsigned int debug_get_active_thread_count(const VoxelThreadPool &pool) {
unsigned int active_count = 0;
for (unsigned int i = 0; i < pool.get_thread_count(); ++i) {
@ -625,12 +630,17 @@ void VoxelServer::BlockDataRequest::run(VoxelTaskContext ctx) {
case TYPE_LOAD: {
voxels.instance();
voxels->create(block_size, block_size, block_size);
// TODO No longer using batches? If that works ok, we should get rid of batch queries in files
const VoxelStream::Result result = stream->emerge_block(voxels, origin_in_voxels, lod);
if (result == VoxelStream::RESULT_ERROR) {
ERR_PRINT("Error loading block");
}
if (result == VoxelStream::RESULT_BLOCK_NOT_FOUND) {
// TODO We should consider batching this again, but it needs to be done carefully.
// Each task is one block, and priority depends on distance to closest viewer.
// If we batch blocks, we have to do it by distance too.
const VoxelStream::Result voxel_result = stream->emerge_block(voxels, origin_in_voxels, lod);
if (voxel_result == VoxelStream::RESULT_ERROR) {
ERR_PRINT("Error loading voxel block");
} else if (voxel_result == VoxelStream::RESULT_BLOCK_NOT_FOUND) {
Ref<VoxelGenerator> generator = stream_dependency->generator;
if (generator.is_valid()) {
VoxelServer::get_singleton()->request_block_generate_from_data_request(this);
@ -642,16 +652,56 @@ void VoxelServer::BlockDataRequest::run(VoxelTaskContext ctx) {
// TODO Define format on volume?
}
}
if (request_instances && stream->supports_instance_blocks()) {
ERR_FAIL_COND(instances != nullptr);
VoxelStreamInstanceDataRequest instance_data_request;
instance_data_request.lod = lod;
instance_data_request.position = position;
VoxelStream::Result instances_result;
stream->load_instance_blocks(
ArraySlice<VoxelStreamInstanceDataRequest>(&instance_data_request, 1),
ArraySlice<VoxelStream::Result>(&instances_result, 1));
if (instances_result == VoxelStream::RESULT_ERROR) {
ERR_PRINT("Error loading instance block");
} else if (voxel_result == VoxelStream::RESULT_BLOCK_FOUND) {
instances = std::move(instance_data_request.data);
}
// If not found, instances will return null,
// which means it can be generated by the instancer after the meshing process
}
} break;
case TYPE_SAVE: {
Ref<VoxelBuffer> voxels_copy;
{
RWLockRead lock(voxels->get_lock());
voxels_copy = voxels->duplicate(true);
if (request_voxels) {
Ref<VoxelBuffer> voxels_copy;
// TODO Is that copy necessary? It's possible it was already done while issuing the request
if (voxels.is_valid()) {
RWLockRead lock(voxels->get_lock());
voxels_copy = voxels->duplicate(true);
}
voxels.unref();
stream->immerge_block(voxels_copy, origin_in_voxels, lod);
}
if (request_instances && stream->supports_instance_blocks()) {
// If the provided data is null, it means this instance block was never modified.
// Since we are in a save request, the saved data will revert to unmodified.
// On the other hand, if we want to represent the fact that "everything was deleted here",
// this should not be null.
PRINT_VERBOSE(String("Saving instance block {0} lod {1} with data {2}")
.format(varray(position.to_vec3(), lod, ptr2s(instances.get()))));
VoxelStreamInstanceDataRequest instance_data_request;
instance_data_request.lod = lod;
instance_data_request.position = position;
instance_data_request.data = std::move(instances);
stream->save_instance_blocks(ArraySlice<VoxelStreamInstanceDataRequest>(&instance_data_request, 1));
}
voxels.unref();
stream->immerge_block(voxels_copy, origin_in_voxels, lod);
} break;
default:

View File

@ -11,6 +11,8 @@
#include <memory>
// TODO Don't inherit Object. Instead have a Godot wrapper, there is very little use for Object stuff
// Access point for asynchronous voxel processing APIs.
// Functions must be used from the main thread.
class VoxelServer : public Object {
@ -36,6 +38,7 @@ public:
Type type;
Ref<VoxelBuffer> voxels;
std::unique_ptr<VoxelInstanceBlockData> instances;
Vector3i position;
uint8_t lod;
bool dropped;
@ -88,8 +91,10 @@ public:
void set_volume_octree_split_scale(uint32_t volume_id, float split_scale);
void invalidate_volume_mesh_requests(uint32_t volume_id);
void request_block_mesh(uint32_t volume_id, BlockMeshInput &input);
void request_block_load(uint32_t volume_id, Vector3i block_pos, int lod);
void request_block_save(uint32_t volume_id, Ref<VoxelBuffer> voxels, Vector3i block_pos, int lod);
void request_block_load(uint32_t volume_id, Vector3i block_pos, int lod, bool request_instances);
void request_voxel_block_save(uint32_t volume_id, Ref<VoxelBuffer> voxels, Vector3i block_pos, int lod);
void request_instance_block_save(uint32_t volume_id, std::unique_ptr<VoxelInstanceBlockData> instances,
Vector3i block_pos, int lod);
void remove_volume(uint32_t volume_id);
// TODO Rename functions to C convention
@ -245,6 +250,7 @@ private:
bool is_cancelled() override;
Ref<VoxelBuffer> voxels;
std::unique_ptr<VoxelInstanceBlockData> instances;
Vector3i position;
uint32_t volume_id;
uint8_t lod;
@ -252,6 +258,8 @@ private:
uint8_t type;
bool has_run = false;
bool too_far = false;
bool request_instances = false;
bool request_voxels = false;
PriorityDependency priority_dependency;
std::shared_ptr<StreamingDependency> stream_dependency;
// TODO Find a way to separate save, it doesnt need sorting

View File

@ -39,6 +39,9 @@ void VoxelThreadPool::create_thread(ThreadData &d, uint32_t i) {
d.stop = false;
d.waiting = false;
d.index = i;
if (!_name.empty()) {
d.name = String("{0} {1}").format(varray(_name, i));
}
d.thread = Thread::create(thread_func_static, &d);
}
@ -63,6 +66,10 @@ void VoxelThreadPool::destroy_all_threads() {
}
}
void VoxelThreadPool::set_name(String name) {
_name = name;
}
void VoxelThreadPool::set_thread_count(uint32_t count) {
if (count > MAX_THREADS) {
count = MAX_THREADS;
@ -116,6 +123,16 @@ void VoxelThreadPool::enqueue(ArraySlice<IVoxelTask *> tasks) {
void VoxelThreadPool::thread_func_static(void *p_data) {
ThreadData &data = *static_cast<ThreadData *>(p_data);
VoxelThreadPool &pool = *data.pool;
if (!data.name.empty()) {
Thread::set_name(data.name);
#ifdef VOXEL_PROFILER_ENABLED
CharString thread_name = data.name.utf8();
VOXEL_PROFILE_SET_THREAD_NAME(thread_name.get_data());
#endif
}
pool.thread_func(data);
}

View File

@ -43,6 +43,10 @@ public:
VoxelThreadPool();
~VoxelThreadPool();
// Set name prefix to recognize threads of this pool in debug tools.
// Must be called before configuring thread count.
void set_name(String name);
// TODO Add ability to change it while running without skipping tasks
// Can't be changed after tasks have been queued
void set_thread_count(uint32_t count);
@ -92,6 +96,7 @@ private:
bool stop = false;
bool waiting = false;
State debug_state = STATE_STOPPED;
String name;
};
static void thread_func_static(void *p_data);
@ -114,6 +119,8 @@ private:
uint32_t _batch_count = 1;
uint32_t _priority_update_period = 32;
String _name;
unsigned int _debug_received_tasks = 0;
unsigned int _debug_completed_tasks = 0;
};

View File

@ -0,0 +1,98 @@
#include "compressed_data.h"
#include "../thirdparty/lz4/lz4.h"
#include "../util/profiling.h"
#include "../util/serialization.h"
#include <core/io/file_access_memory.h>
#include <core/variant.h>
namespace VoxelCompressedData {
bool decompress(ArraySlice<const uint8_t> src, std::vector<uint8_t> &dst) {
VOXEL_PROFILE_SCOPE();
VoxelUtility::MemoryReader f(src, VoxelUtility::ENDIANESS_BIG_ENDIAN);
const Compression comp = static_cast<Compression>(f.get_8());
ERR_FAIL_INDEX_V(comp, COMPRESSION_COUNT, false);
switch (comp) {
case COMPRESSION_NONE: {
// We still have to do a copy. The point of this container is compression,
// so we don't worry too much about the performance impact of not using `src` directly.
dst.resize(src.size() - 1);
memcpy(dst.data(), src.data() + 1, dst.size());
} break;
case COMPRESSION_LZ4: {
const uint32_t decompressed_size = f.get_32();
const uint32_t header_size = sizeof(uint8_t) + sizeof(uint32_t);
dst.resize(decompressed_size);
const uint32_t actually_decompressed_size = LZ4_decompress_safe(
(const char *)src.data() + header_size,
(char *)dst.data(),
src.size() - header_size,
dst.size());
ERR_FAIL_COND_V_MSG(actually_decompressed_size < 0, false,
String("LZ4 decompression error {0}").format(varray(actually_decompressed_size)));
ERR_FAIL_COND_V_MSG(actually_decompressed_size != decompressed_size, false,
String("Expected {0} bytes, obtained {1}")
.format(varray(decompressed_size, actually_decompressed_size)));
} break;
default:
ERR_PRINT("Invalid compression header");
return false;
}
return true;
}
bool compress(ArraySlice<const uint8_t> src, std::vector<uint8_t> &dst, Compression comp) {
VOXEL_PROFILE_SCOPE();
switch (comp) {
case COMPRESSION_NONE: {
dst.resize(src.size() + 1);
dst[0] = comp;
memcpy(dst.data() + 1, src.data(), src.size());
} break;
case COMPRESSION_LZ4: {
ERR_FAIL_COND_V(src.size() > std::numeric_limits<uint32_t>::max(), false);
// Write header
// Must clear first because MemoryWriter writes from the end
dst.clear();
VoxelUtility::MemoryWriter f(dst, VoxelUtility::ENDIANESS_BIG_ENDIAN);
f.store_8(comp);
f.store_32(src.size());
const uint32_t header_size = sizeof(uint8_t) + sizeof(uint32_t);
dst.resize(header_size + LZ4_compressBound(src.size()));
const uint32_t compressed_size = LZ4_compress_default(
(const char *)src.data(),
(char *)dst.data() + header_size,
src.size(),
dst.size() - header_size);
ERR_FAIL_COND_V(compressed_size < 0, false);
ERR_FAIL_COND_V(compressed_size == 0, false);
dst.resize(header_size + compressed_size);
} break;
default:
ERR_PRINT("Invalid compression header");
return false;
}
return true;
}
} // namespace VoxelCompressedData

27
streams/compressed_data.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef VOXEL_COMPRESSED_DATA_H
#define VOXEL_COMPRESSED_DATA_H
#include "../util/array_slice.h"
namespace VoxelCompressedData {
// Compressed data starts with a single byte telling which compression format is used.
// What follows depends on it.
enum Compression {
// No compression. All following bytes can be read as-is.
// Could be used for debugging.
COMPRESSION_NONE = 0,
// The next uint32_t will be the size of decompressed data.
// All following bytes are compressed data using LZ4 defaults.
// This is the fastest compression format.
COMPRESSION_LZ4 = 1,
COMPRESSION_COUNT = 2
};
bool compress(ArraySlice<const uint8_t> src, std::vector<uint8_t> &dst, Compression comp);
bool decompress(ArraySlice<const uint8_t> src, std::vector<uint8_t> &dst);
} // namespace VoxelCompressedData
#endif // VOXEL_COMPRESSED_DATA_H

145
streams/instance_data.cpp Normal file
View File

@ -0,0 +1,145 @@
#include "instance_data.h"
#include "../util/array_slice.h"
#include "../util/serialization.h"
#include "../util/utility.h"
#include "../voxel_constants.h"
namespace {
const uint32_t TRAILING_MAGIC = 0x900df00d;
}
inline uint8_t norm_to_u8(float x) {
return clamp(static_cast<int>(128.f * x + 128.f), 0, 0xff);
}
inline float u8_to_norm(uint8_t v) {
return (static_cast<real_t>(v) - 0x7f) * VoxelConstants::INV_0x7f;
}
struct CompressedQuaternion4b {
uint8_t x;
uint8_t y;
uint8_t z;
uint8_t w;
static CompressedQuaternion4b from_quat(Quat q) {
CompressedQuaternion4b c;
c.x = norm_to_u8(q.x);
c.y = norm_to_u8(q.y);
c.z = norm_to_u8(q.z);
c.w = norm_to_u8(q.w);
return c;
}
Quat to_quat() const {
Quat q;
q.x = u8_to_norm(x);
q.y = u8_to_norm(y);
q.z = u8_to_norm(z);
q.w = u8_to_norm(w);
return q.normalized();
}
};
void serialize_instance_block_data(const VoxelInstanceBlockData &src, std::vector<uint8_t> &dst) {
const uint8_t version = 0;
const uint8_t instance_format = 0;
VoxelUtility::MemoryWriter w(dst, VoxelUtility::ENDIANESS_BIG_ENDIAN);
w.store_8(version);
w.store_8(src.layers.size());
w.store_float(src.position_range);
// TODO Introduce a margin to position coordinates, stuff can spawn offset from the ground.
// Or just compute the ranges
const float pos_norm_scale = 1.f / src.position_range;
for (size_t i = 0; i < src.layers.size(); ++i) {
const VoxelInstanceBlockData::LayerData &layer = src.layers[i];
w.store_16(layer.id);
w.store_16(layer.instances.size());
w.store_float(layer.scale_min);
w.store_float(layer.scale_max);
w.store_8(instance_format);
const float scale_norm_scale = 1.f / (layer.scale_max - layer.scale_min);
for (size_t j = 0; j < layer.instances.size(); ++j) {
const VoxelInstanceBlockData::InstanceData &instance = layer.instances[j];
w.store_16(static_cast<uint16_t>(pos_norm_scale * instance.transform.origin.x * 0xffff));
w.store_16(static_cast<uint16_t>(pos_norm_scale * instance.transform.origin.y * 0xffff));
w.store_16(static_cast<uint16_t>(pos_norm_scale * instance.transform.origin.z * 0xffff));
const float scale = instance.transform.get_basis().get_scale().y;
w.store_8(static_cast<uint8_t>(scale_norm_scale * (scale - layer.scale_min) * 0xff));
const Quat q = instance.transform.get_basis().get_rotation_quat();
const CompressedQuaternion4b cq = CompressedQuaternion4b::from_quat(q);
w.store_8(cq.x);
w.store_8(cq.y);
w.store_8(cq.z);
w.store_8(cq.w);
}
}
w.store_32(TRAILING_MAGIC);
}
bool deserialize_instance_block_data(VoxelInstanceBlockData &dst, ArraySlice<const uint8_t> src) {
const uint8_t expected_version = 0;
const uint8_t expected_instance_format = 0;
VoxelUtility::MemoryReader r(src, VoxelUtility::ENDIANESS_BIG_ENDIAN);
const uint8_t version = r.get_8();
ERR_FAIL_COND_V(version != expected_version, false);
const uint8_t layers_count = r.get_8();
dst.layers.resize(layers_count);
dst.position_range = r.get_float();
for (size_t i = 0; i < dst.layers.size(); ++i) {
VoxelInstanceBlockData::LayerData &layer = dst.layers[i];
layer.id = r.get_16();
const uint16_t instance_count = r.get_16();
layer.instances.resize(instance_count);
layer.scale_min = r.get_float();
layer.scale_max = r.get_float();
ERR_FAIL_COND_V(layer.scale_max < layer.scale_min, false);
const float scale_range = layer.scale_max - layer.scale_min;
const uint8_t instance_format = r.get_8();
ERR_FAIL_COND_V(instance_format != expected_instance_format, false);
for (size_t j = 0; j < layer.instances.size(); ++j) {
const float x = (static_cast<float>(r.get_16()) / 0xffff) * dst.position_range;
const float y = (static_cast<float>(r.get_16()) / 0xffff) * dst.position_range;
const float z = (static_cast<float>(r.get_16()) / 0xffff) * dst.position_range;
const float s = (static_cast<float>(r.get_8()) / 0xff) * scale_range + layer.scale_min;
CompressedQuaternion4b cq;
cq.x = r.get_8();
cq.y = r.get_8();
cq.z = r.get_8();
cq.w = r.get_8();
const Quat q = cq.to_quat();
VoxelInstanceBlockData::InstanceData &instance = layer.instances[j];
instance.transform = Transform(Basis(q).scaled(Vector3(s, s, s)), Vector3(x, y, z));
}
}
const uint32_t control_end = r.get_32();
ERR_FAIL_COND_V_MSG(control_end != TRAILING_MAGIC, false,
String("Expected {0}, found {1}").format(varray(TRAILING_MAGIC, control_end)));
return true;
}

32
streams/instance_data.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef VOXEL_INSTANCE_DATA_H
#define VOXEL_INSTANCE_DATA_H
#include "../util/array_slice.h"
#include <core/math/transform.h>
// Stores data to pass around until it either gets saved or turned into actual instances
struct VoxelInstanceBlockData {
struct InstanceData {
Transform transform;
};
struct LayerData {
uint16_t id;
float scale_min;
float scale_max;
std::vector<InstanceData> instances;
};
float position_range;
std::vector<LayerData> layers;
void copy_to(VoxelInstanceBlockData &dst) const {
// It's all POD so it should work for now
dst = *this;
}
};
void serialize_instance_block_data(const VoxelInstanceBlockData &src, std::vector<uint8_t> &dst);
bool deserialize_instance_block_data(VoxelInstanceBlockData &dst, ArraySlice<const uint8_t> src);
#endif // VOXEL_INSTANCE_DATA_H

View File

@ -2,20 +2,32 @@
#include "../../thirdparty/sqlite/sqlite3.h"
#include "../../util/macros.h"
#include "../../util/profiling.h"
#include "../compressed_data.h"
#include <limits>
#include <string>
// TODO Is the struct really useful?
struct BlockLocation {
int16_t x;
int16_t y;
int16_t z;
uint8_t lod;
static uint64_t encode(BlockLocation loc) {
static bool validate(const Vector3i pos, uint8_t lod) {
ERR_FAIL_COND_V(pos.x < std::numeric_limits<int16_t>::min(), false);
ERR_FAIL_COND_V(pos.y < std::numeric_limits<int16_t>::min(), false);
ERR_FAIL_COND_V(pos.z < std::numeric_limits<int16_t>::min(), false);
ERR_FAIL_COND_V(pos.x > std::numeric_limits<int16_t>::max(), false);
ERR_FAIL_COND_V(pos.y > std::numeric_limits<int16_t>::max(), false);
ERR_FAIL_COND_V(pos.z > std::numeric_limits<int16_t>::max(), false);
return true;
}
uint64_t encode() const {
// 0l xx yy zz
return ((static_cast<uint64_t>(loc.lod) & 0xffff) << 48) |
((static_cast<uint64_t>(loc.x) & 0xffff) << 32) |
((static_cast<uint64_t>(loc.y) & 0xffff) << 16) |
(static_cast<uint64_t>(loc.z) & 0xffff);
return ((static_cast<uint64_t>(lod) & 0xffff) << 48) |
((static_cast<uint64_t>(x) & 0xffff) << 32) |
((static_cast<uint64_t>(y) & 0xffff) << 16) |
(static_cast<uint64_t>(z) & 0xffff);
}
static BlockLocation decode(uint64_t id) {
@ -47,6 +59,11 @@ public:
FixedArray<Channel, VoxelBuffer::MAX_CHANNELS> channels;
};
enum BlockType {
VOXELS,
INSTANCES
};
VoxelStreamSQLiteInternal();
~VoxelStreamSQLiteInternal();
@ -54,13 +71,21 @@ public:
void close();
bool is_open() const { return _db != nullptr; }
// Returns the file path from SQLite
const char *get_file_path() const;
// Return the file path that was used to open the connection.
// You may use this one if you want determinism, as SQLite seems to globalize its path.
const char *get_opened_file_path() const {
return _opened_path.c_str();
}
bool begin_transaction();
bool end_transaction();
bool save_block(BlockLocation loc, const std::vector<uint8_t> &block_data);
VoxelStream::Result load_block(BlockLocation loc, std::vector<uint8_t> &out_block_data);
bool save_block(BlockLocation loc, const std::vector<uint8_t> &block_data, BlockType type);
VoxelStream::Result load_block(BlockLocation loc, std::vector<uint8_t> &out_block_data, BlockType type);
Meta load_meta();
void save_meta(Meta meta);
@ -93,11 +118,14 @@ private:
}
}
std::string _opened_path;
sqlite3 *_db = nullptr;
sqlite3_stmt *_begin_statement = nullptr;
sqlite3_stmt *_end_statement = nullptr;
sqlite3_stmt *_update_block_statement = nullptr;
sqlite3_stmt *_get_block_statement = nullptr;
sqlite3_stmt *_update_voxel_block_statement = nullptr;
sqlite3_stmt *_get_voxel_block_statement = nullptr;
sqlite3_stmt *_update_instance_block_statement = nullptr;
sqlite3_stmt *_get_instance_block_statement = nullptr;
sqlite3_stmt *_load_meta_statement = nullptr;
sqlite3_stmt *_save_meta_statement = nullptr;
sqlite3_stmt *_load_channels_statement = nullptr;
@ -112,6 +140,7 @@ VoxelStreamSQLiteInternal::~VoxelStreamSQLiteInternal() {
}
bool VoxelStreamSQLiteInternal::open(const char *fpath) {
VOXEL_PROFILE_SCOPE();
close();
int rc = sqlite3_open_v2(fpath, &_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
@ -141,11 +170,20 @@ bool VoxelStreamSQLiteInternal::open(const char *fpath) {
}
// Prepare statements
if (!prepare(db, &_update_block_statement,
"INSERT INTO blocks VALUES (:loc, :vb, null) ON CONFLICT(loc) DO UPDATE SET vb=excluded.vb")) {
if (!prepare(db, &_update_voxel_block_statement,
"INSERT INTO blocks VALUES (:loc, :vb, null) "
"ON CONFLICT(loc) DO UPDATE SET vb=excluded.vb")) {
return false;
}
if (!prepare(db, &_get_block_statement, "SELECT vb FROM blocks WHERE loc=:loc")) {
if (!prepare(db, &_get_voxel_block_statement, "SELECT vb FROM blocks WHERE loc=:loc")) {
return false;
}
if (!prepare(db, &_update_instance_block_statement,
"INSERT INTO blocks VALUES (:loc, null, :instances) "
"ON CONFLICT(loc) DO UPDATE SET instances=excluded.instances")) {
return false;
}
if (!prepare(db, &_get_instance_block_statement, "SELECT instances FROM blocks WHERE loc=:loc")) {
return false;
}
if (!prepare(db, &_begin_statement, "BEGIN")) {
@ -164,7 +202,8 @@ bool VoxelStreamSQLiteInternal::open(const char *fpath) {
return false;
}
if (!prepare(db, &_save_channel_statement,
"INSERT INTO channels VALUES (:idx, :depth) ON CONFLICT(idx) DO UPDATE SET depth=excluded.depth")) {
"INSERT INTO channels VALUES (:idx, :depth) "
"ON CONFLICT(idx) DO UPDATE SET depth=excluded.depth")) {
return false;
}
@ -183,6 +222,7 @@ bool VoxelStreamSQLiteInternal::open(const char *fpath) {
save_meta(meta);
}
_opened_path = fpath;
return true;
}
@ -192,14 +232,17 @@ void VoxelStreamSQLiteInternal::close() {
}
finalize(_begin_statement);
finalize(_end_statement);
finalize(_update_block_statement);
finalize(_get_block_statement);
finalize(_update_voxel_block_statement);
finalize(_get_voxel_block_statement);
finalize(_update_instance_block_statement);
finalize(_get_instance_block_statement);
finalize(_load_meta_statement);
finalize(_save_meta_statement);
finalize(_load_channels_statement);
finalize(_save_channel_statement);
sqlite3_close(_db);
_db = nullptr;
_opened_path.clear();
}
const char *VoxelStreamSQLiteInternal::get_file_path() const {
@ -237,11 +280,22 @@ bool VoxelStreamSQLiteInternal::end_transaction() {
return true;
}
bool VoxelStreamSQLiteInternal::save_block(BlockLocation loc, const std::vector<uint8_t> &block_data) {
bool VoxelStreamSQLiteInternal::save_block(BlockLocation loc, const std::vector<uint8_t> &block_data, BlockType type) {
VOXEL_PROFILE_SCOPE();
sqlite3 *db = _db;
sqlite3_stmt *update_block_statement = _update_block_statement;
sqlite3_stmt *update_block_statement;
switch (type) {
case VOXELS:
update_block_statement = _update_voxel_block_statement;
break;
case INSTANCES:
update_block_statement = _update_instance_block_statement;
break;
default:
CRASH_NOW();
}
int rc = sqlite3_reset(update_block_statement);
if (rc != SQLITE_OK) {
@ -249,7 +303,7 @@ bool VoxelStreamSQLiteInternal::save_block(BlockLocation loc, const std::vector<
return false;
}
const uint64_t eloc = BlockLocation::encode(loc);
const uint64_t eloc = loc.encode();
rc = sqlite3_bind_int64(update_block_statement, 1, eloc);
if (rc != SQLITE_OK) {
@ -257,8 +311,12 @@ bool VoxelStreamSQLiteInternal::save_block(BlockLocation loc, const std::vector<
return false;
}
// We use SQLITE_TRANSIENT so SQLite will make its own copy of the data
rc = sqlite3_bind_blob(update_block_statement, 2, block_data.data(), block_data.size(), SQLITE_TRANSIENT);
if (block_data.size() == 0) {
rc = sqlite3_bind_null(update_block_statement, 2);
} else {
// We use SQLITE_TRANSIENT so SQLite will make its own copy of the data
rc = sqlite3_bind_blob(update_block_statement, 2, block_data.data(), block_data.size(), SQLITE_TRANSIENT);
}
if (rc != SQLITE_OK) {
ERR_PRINT(sqlite3_errmsg(db));
return false;
@ -273,9 +331,22 @@ bool VoxelStreamSQLiteInternal::save_block(BlockLocation loc, const std::vector<
return true;
}
VoxelStream::Result VoxelStreamSQLiteInternal::load_block(BlockLocation loc, std::vector<uint8_t> &out_block_data) {
VoxelStream::Result VoxelStreamSQLiteInternal::load_block(
BlockLocation loc, std::vector<uint8_t> &out_block_data, BlockType type) {
sqlite3 *db = _db;
sqlite3_stmt *get_block_statement = _get_block_statement;
sqlite3_stmt *get_block_statement;
switch (type) {
case VOXELS:
get_block_statement = _get_voxel_block_statement;
break;
case INSTANCES:
get_block_statement = _get_instance_block_statement;
break;
default:
CRASH_NOW();
}
int rc;
@ -285,7 +356,7 @@ VoxelStream::Result VoxelStreamSQLiteInternal::load_block(BlockLocation loc, std
return VoxelStream::RESULT_ERROR;
}
const uint64_t eloc = BlockLocation::encode(loc);
const uint64_t eloc = loc.encode();
rc = sqlite3_bind_int64(get_block_statement, 1, eloc);
if (rc != SQLITE_OK) {
@ -450,20 +521,25 @@ void VoxelStreamSQLiteInternal::save_meta(Meta meta) {
thread_local VoxelBlockSerializerInternal VoxelStreamSQLite::_voxel_block_serializer;
thread_local std::vector<uint8_t> VoxelStreamSQLite::_temp_block_data;
thread_local std::vector<uint8_t> VoxelStreamSQLite::_temp_compressed_block_data;
VoxelStreamSQLite::VoxelStreamSQLite() {
_connection_mutex = Mutex::create();
}
VoxelStreamSQLite::~VoxelStreamSQLite() {
PRINT_VERBOSE("~VoxelStreamSQLite");
if (!_connection_path.empty() && _cache.get_indicative_block_count() > 0) {
PRINT_VERBOSE("~VoxelStreamSQLite flushy flushy");
flush_cache();
PRINT_VERBOSE("~VoxelStreamSQLite flushy done");
}
for (auto it = _connection_pool.begin(); it != _connection_pool.end(); ++it) {
delete *it;
}
_connection_pool.clear();
memdelete(_connection_mutex);
PRINT_VERBOSE("~VoxelStreamSQLite done");
}
void VoxelStreamSQLite::set_database_path(String path) {
@ -519,27 +595,21 @@ void VoxelStreamSQLite::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_i
}
void VoxelStreamSQLite::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vector<Result> &out_results) {
VOXEL_PROFILE_SCOPE();
// TODO Get block size from database
const int bs_po2 = 4;
const int bs_po2 = VoxelConstants::DEFAULT_BLOCK_SIZE_PO2;
out_results.resize(p_blocks.size());
// Check the cache first
Vector<int> blocks_to_load;
for (int i = 0; i < p_blocks.size(); ++i) {
const VoxelBlockRequest &r = p_blocks[i];
const Vector3i pos = r.origin_in_voxels >> bs_po2;
VoxelBlockRequest &wr = p_blocks.write[i];
const Vector3i pos = wr.origin_in_voxels >> bs_po2;
Ref<VoxelBuffer> vb;
if (_cache.load_voxel_block(pos, r.lod, vb)) {
VoxelBlockRequest &wr = p_blocks.write[i];
// Copying is required since the cache has ownership on its data,
// and the requests wants us to populate the buffer it provides
wr.voxel_buffer->copy_format(**vb);
wr.voxel_buffer->copy_from(**vb);
wr.voxel_buffer->copy_voxel_metadata(**vb);
if (_cache.load_voxel_block(pos, wr.lod, wr.voxel_buffer)) {
out_results.write[i] = RESULT_BLOCK_FOUND;
} else {
@ -568,7 +638,7 @@ void VoxelStreamSQLite::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vecto
loc.z = r.origin_in_voxels.z >> bs_po2;
loc.lod = r.lod;
const Result res = con->load_block(loc, _temp_block_data);
const Result res = con->load_block(loc, _temp_block_data, VoxelStreamSQLiteInternal::VOXELS);
if (res == RESULT_BLOCK_FOUND) {
VoxelBlockRequest &wr = p_blocks.write[ri];
@ -587,15 +657,109 @@ void VoxelStreamSQLite::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vecto
void VoxelStreamSQLite::immerge_blocks(const Vector<VoxelBlockRequest> &p_blocks) {
// TODO Get block size from database
const int bs_po2 = 4;
const int bs_po2 = VoxelConstants::DEFAULT_BLOCK_SIZE_PO2;
// First put in cache
for (int i = 0; i < p_blocks.size(); ++i) {
const VoxelBlockRequest &r = p_blocks[i];
const Vector3i pos = r.origin_in_voxels >> bs_po2;
if (!BlockLocation::validate(pos, r.lod)) {
ERR_PRINT(String("Block position {0} is outside of supported range").format(varray(pos.to_vec3())));
continue;
}
_cache.save_voxel_block(pos, r.lod, r.voxel_buffer);
}
// TODO We should consider using a serialized cache, and measure the threshold in bytes
if (_cache.get_indicative_block_count() >= CACHE_SIZE) {
flush_cache();
}
}
bool VoxelStreamSQLite::supports_instance_blocks() const {
return true;
}
void VoxelStreamSQLite::load_instance_blocks(
ArraySlice<VoxelStreamInstanceDataRequest> out_blocks, ArraySlice<Result> out_results) {
VOXEL_PROFILE_SCOPE();
// TODO Get block size from database
const int bs_po2 = VoxelConstants::DEFAULT_BLOCK_SIZE_PO2;
// Check the cache first
Vector<int> blocks_to_load;
for (int i = 0; i < out_blocks.size(); ++i) {
VoxelStreamInstanceDataRequest &r = out_blocks[i];
if (_cache.load_instance_block(r.position, r.lod, r.data)) {
out_results[i] = RESULT_BLOCK_FOUND;
} else {
blocks_to_load.push_back(i);
}
}
if (blocks_to_load.size() == 0) {
// Everything was cached, no need to query the database
return;
}
VoxelStreamSQLiteInternal *con = get_connection();
ERR_FAIL_COND(con == nullptr);
// TODO We should handle busy return codes
// TODO recycle on error
ERR_FAIL_COND(con->begin_transaction() == false);
for (int i = 0; i < blocks_to_load.size(); ++i) {
const int ri = blocks_to_load[i];
VoxelStreamInstanceDataRequest &r = out_blocks[ri];
BlockLocation loc;
loc.x = r.position.x;
loc.y = r.position.y;
loc.z = r.position.z;
loc.lod = r.lod;
const Result res = con->load_block(loc, _temp_compressed_block_data, VoxelStreamSQLiteInternal::INSTANCES);
if (res == RESULT_BLOCK_FOUND) {
if (!VoxelCompressedData::decompress(to_slice_const(_temp_compressed_block_data), _temp_block_data)) {
ERR_PRINT("Failed to decompress instance block");
out_results[i] = RESULT_ERROR;
continue;
}
r.data = std::make_unique<VoxelInstanceBlockData>();
if (!deserialize_instance_block_data(*r.data, to_slice_const(_temp_block_data))) {
ERR_PRINT("Failed to deserialize instance block");
out_results[i] = RESULT_ERROR;
continue;
}
}
out_results[i] = res;
}
ERR_FAIL_COND(con->end_transaction() == false);
recycle_connection(con);
}
void VoxelStreamSQLite::save_instance_blocks(ArraySlice<VoxelStreamInstanceDataRequest> p_blocks) {
// TODO Get block size from database
const int bs_po2 = VoxelConstants::DEFAULT_BLOCK_SIZE_PO2;
// First put in cache
for (int i = 0; i < p_blocks.size(); ++i) {
VoxelStreamInstanceDataRequest &r = p_blocks[i];
_cache.save_instance_block(r.position, r.lod, std::move(r.data));
}
// TODO We should consider using a serialized cache, and measure the threshold in bytes
if (_cache.get_indicative_block_count() >= CACHE_SIZE) {
flush_cache();
}
@ -619,17 +783,44 @@ void VoxelStreamSQLite::flush_cache(VoxelStreamSQLiteInternal *con) {
ERR_FAIL_COND(con == nullptr);
ERR_FAIL_COND(con->begin_transaction() == false);
_cache.flush([&serializer, con](VoxelStreamCache::Block &block) {
std::vector<uint8_t> &temp_data = _temp_block_data;
std::vector<uint8_t> &temp_compressed_data = _temp_compressed_block_data;
// TODO Needs better error rollback handling
_cache.flush([&serializer, con, &temp_data, &temp_compressed_data](VoxelStreamCache::Block &block) {
ERR_FAIL_COND(!BlockLocation::validate(block.position, block.lod));
BlockLocation loc;
loc.x = block.position.x;
loc.y = block.position.y;
loc.z = block.position.z;
loc.lod = block.lod;
// TODO We might want to handle null blocks too, in case they intentionally get deleted
ERR_FAIL_COND(block.voxels.is_null());
VoxelBlockSerializerInternal::SerializeResult res = serializer.serialize_and_compress(**block.voxels);
ERR_FAIL_COND(!res.success);
con->save_block(loc, res.data);
// Save voxels
if (block.has_voxels) {
if (block.voxels.is_valid()) {
VoxelBlockSerializerInternal::SerializeResult res = serializer.serialize_and_compress(**block.voxels);
ERR_FAIL_COND(!res.success);
con->save_block(loc, res.data, VoxelStreamSQLiteInternal::VOXELS);
} else {
const std::vector<uint8_t> empty;
con->save_block(loc, empty, VoxelStreamSQLiteInternal::VOXELS);
}
}
// Save instances
temp_compressed_data.clear();
if (block.instances != nullptr) {
temp_data.clear();
serialize_instance_block_data(*block.instances, temp_data);
ERR_FAIL_COND(!VoxelCompressedData::compress(
to_slice_const(temp_data), temp_compressed_data, VoxelCompressedData::COMPRESSION_NONE));
}
con->save_block(loc, temp_compressed_data, VoxelStreamSQLiteInternal::INSTANCES);
// TODO Optimization: add a version of the query that can update both at once
});
ERR_FAIL_COND(con->end_transaction() == false);
@ -663,7 +854,7 @@ VoxelStreamSQLiteInternal *VoxelStreamSQLite::get_connection() {
}
void VoxelStreamSQLite::recycle_connection(VoxelStreamSQLiteInternal *con) {
String con_path = con->get_file_path();
String con_path = con->get_opened_file_path();
_connection_mutex->lock();
// If path differs, delete this connection
if (_connection_path != con_path) {

View File

@ -27,6 +27,11 @@ public:
void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vector<Result> &out_results) override;
void immerge_blocks(const Vector<VoxelBlockRequest> &p_blocks) override;
bool supports_instance_blocks() const override;
void load_instance_blocks(
ArraySlice<VoxelStreamInstanceDataRequest> out_blocks, ArraySlice<Result> out_results) override;
void save_instance_blocks(ArraySlice<VoxelStreamInstanceDataRequest> p_blocks) override;
void flush_cache();
private:
@ -58,6 +63,7 @@ private:
// TODO I should consider specialized memory allocators
static thread_local VoxelBlockSerializerInternal _voxel_block_serializer;
static thread_local std::vector<uint8_t> _temp_block_data;
static thread_local std::vector<uint8_t> _temp_compressed_block_data;
};
#endif // VOXEL_STREAM_SQLITE_H

View File

@ -3,6 +3,8 @@
#include "../math/vector3i.h"
#include "../storage/voxel_buffer.h"
#include "instance_data.h"
#include <memory>
// TODO Rename VoxelStreamBlockRequest
struct VoxelBlockRequest {
@ -11,4 +13,10 @@ struct VoxelBlockRequest {
int lod;
};
struct VoxelStreamInstanceDataRequest {
std::unique_ptr<VoxelInstanceBlockData> data;
Vector3i position;
uint8_t lod;
};
#endif // VOXEL_BLOCK_REQUEST_H

View File

@ -2,9 +2,9 @@
#include "../math/vector3i.h"
#include "../storage/voxel_buffer.h"
#include "../storage/voxel_memory_pool.h"
#include "../thirdparty/lz4/lz4.h"
#include "../util/macros.h"
#include "../util/profiling.h"
#include "compressed_data.h"
#include <core/io/marshalls.h>
#include <core/io/stream_peer.h>
@ -12,112 +12,6 @@
#include <core/os/file_access.h>
#include <limits>
namespace VoxelCompressedData {
// Compressed data starts with a single byte telling which compression format is used.
// What follows depends on it.
enum Compression {
// No compression. All following bytes can be read as-is.
// Could be used for debugging.
COMPRESSION_NONE = 0,
// The next uint32_t will be the size of decompressed data.
// All following bytes are compressed data using LZ4 defaults.
// This is the fastest compression format.
COMPRESSION_LZ4 = 1,
COMPRESSION_COUNT = 2
};
bool decompress(ArraySlice<const uint8_t> src, std::vector<uint8_t> &dst) {
VOXEL_PROFILE_SCOPE();
FileAccessMemory f;
f.open_custom(src.data(), src.size());
const Compression comp = static_cast<Compression>(f.get_8());
ERR_FAIL_INDEX_V(comp, COMPRESSION_COUNT, false);
switch (comp) {
case COMPRESSION_NONE: {
// We still have to do a copy. The point of this container is compression,
// so we don't worry too much about the performance impact of not using `src` directly.
dst.resize(src.size() - 1);
memcpy(dst.data(), src.data() + 1, dst.size());
} break;
case COMPRESSION_LZ4: {
const uint32_t decompressed_size = f.get_32();
const uint32_t header_size = sizeof(uint8_t) + sizeof(uint32_t);
dst.resize(decompressed_size);
const uint32_t actually_decompressed_size = LZ4_decompress_safe(
(const char *)src.data() + header_size,
(char *)dst.data(),
src.size() - header_size,
dst.size());
ERR_FAIL_COND_V_MSG(actually_decompressed_size < 0, false,
String("LZ4 decompression error {0}").format(varray(actually_decompressed_size)));
ERR_FAIL_COND_V_MSG(actually_decompressed_size != decompressed_size, false,
String("Expected {0} bytes, obtained {1}")
.format(varray(decompressed_size, actually_decompressed_size)));
} break;
default:
ERR_PRINT("Invalid compression header");
return false;
}
return true;
}
bool compress(ArraySlice<const uint8_t> src, std::vector<uint8_t> &dst, Compression comp) {
VOXEL_PROFILE_SCOPE();
switch (comp) {
case COMPRESSION_NONE: {
dst.resize(src.size() + 1);
dst[0] = comp;
memcpy(dst.data() + 1, src.data(), src.size());
} break;
case COMPRESSION_LZ4: {
const uint32_t header_size = sizeof(uint8_t) + sizeof(uint32_t);
dst.resize(header_size + LZ4_compressBound(src.size()));
// Write header
FileAccessMemory f;
f.open_custom(dst.data(), dst.size());
f.store_8(comp);
f.store_32(src.size());
f.close();
const uint32_t compressed_size = LZ4_compress_default(
(const char *)src.data(),
(char *)dst.data() + header_size,
src.size(),
dst.size() - header_size);
ERR_FAIL_COND_V(compressed_size < 0, false);
ERR_FAIL_COND_V(compressed_size == 0, false);
dst.resize(header_size + compressed_size);
} break;
default:
ERR_PRINT("Invalid compression header");
return false;
}
return true;
}
} // namespace VoxelCompressedData
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace {
const uint8_t BLOCK_VERSION = 2;
const unsigned int BLOCK_TRAILING_MAGIC = 0x900df00d;

Some files were not shown because too many files have changed in this diff Show More