Skip to content

Commit

Permalink
Add HeightMapShape3D update with Image data
Browse files Browse the repository at this point in the history
Adds HeightMapShape3D update with Image data.
  • Loading branch information
smix8 committed Feb 5, 2024
1 parent d335281 commit 04858d4
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 0 deletions.
23 changes: 23 additions & 0 deletions doc/classes/HeightMapShape3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
<description>
A 3D heightmap shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. This is useful for terrain, but it is limited as overhangs (such as caves) cannot be stored. Holes in a [HeightMapShape3D] are created by assigning very low values to points in the desired area.
[b]Performance:[/b] [HeightMapShape3D] is faster to check collisions against than [ConcavePolygonShape3D], but it is significantly slower than primitive shapes like [BoxShape3D].
A heightmap collision shape can also be build by using an [Image] reference:
[codeblocks]
[gdscript]
var heightmap_texture: Texture = ResourceLoader.load("res://heightmap_image.png")
var heightmap_image: Image = heightmap_texture.get_image()
heightmap_image.convert(Image.FORMAT_RF)

var height_min: float = 0.0
var height_max: float = 10.0

update_map_data_from_image(heightmap_image, height_min, height_max)
[/gdscript]
[/codeblocks]
</description>
<tutorials>
</tutorials>
Expand All @@ -20,6 +33,16 @@
<return type="float" />
<description>
Returns the smallest height value found in [member map_data]. Recalculates only when [member map_data] changes.
</method>
<method name="update_map_data_from_image">
<return type="void" />
<param index="0" name="image" type="Image" />
<param index="1" name="height_min" type="float" />
<param index="2" name="height_max" type="float" />
<description>
Updates [member map_data] with data read from an [Image] reference in [constant Image.FORMAT_RF] format. Automatically resize heightmap [member map_width] and [member map_depth] to fit the full image width and height.
Each image pixel is read in as a float on the range from [code]0.0[/code] (black pixel) to [code]1.0[/code] (white pixel). This range value gets remapped to [param height_min] and [param height_max] to form the final height value.
[b]Performance:[/b] The image data needs to be received from the GPU, stalling the [RenderingServer] in the process. This can have a noticeable frame rate impact at runtime when the image update is used frequently or with high resolution images.
</description>
</method>
</methods>
Expand Down
45 changes: 45 additions & 0 deletions scene/resources/height_map_shape_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "height_map_shape_3d.h"

#include "core/io/image.h"
#include "servers/physics_server_3d.h"

Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const {
Expand Down Expand Up @@ -187,6 +188,48 @@ real_t HeightMapShape3D::get_max_height() const {
return max_height;
}

void HeightMapShape3D::update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max) {
ERR_FAIL_COND(p_image.is_null());
ERR_FAIL_COND(p_image->get_format() != Image::FORMAT_RF);
ERR_FAIL_COND(p_image->get_width() < 2);
ERR_FAIL_COND(p_image->get_height() < 2);
ERR_FAIL_COND(p_height_min > p_height_max);

map_width = p_image->get_width();
map_depth = p_image->get_height();
map_data.resize(map_width * map_depth);

real_t new_min_height = p_height_max;
real_t new_max_height = p_height_min;

float remap_height_min = float(p_height_min);
float remap_height_max = float(p_height_max);

const float *image_data_ptr = (float *)p_image->get_data().ptr();
real_t *map_data_ptrw = map_data.ptrw();

for (int i = 0; i < map_data.size(); i++) {
DEV_ASSERT(image_data_ptr[i] >= 0.0 && image_data_ptr[i] <= 1.0);
real_t height_value = Math::remap(image_data_ptr[i], 0.0f, 1.0f, remap_height_min, remap_height_max);

if (height_value < new_min_height) {
new_min_height = height_value;
}

if (height_value > new_max_height) {
new_max_height = height_value;
}

map_data_ptrw[i] = height_value;
}

min_height = new_min_height;
max_height = new_max_height;

_update_shape();
emit_changed();
}

void HeightMapShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape3D::set_map_width);
ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape3D::get_map_width);
Expand All @@ -197,6 +240,8 @@ void HeightMapShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_min_height"), &HeightMapShape3D::get_min_height);
ClassDB::bind_method(D_METHOD("get_max_height"), &HeightMapShape3D::get_max_height);

ClassDB::bind_method(D_METHOD("update_map_data_from_image", "image", "height_min", "height_max"), &HeightMapShape3D::update_map_data_from_image);

ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_width", "get_map_width");
ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_depth", "get_map_depth");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "map_data"), "set_map_data", "get_map_data");
Expand Down
4 changes: 4 additions & 0 deletions scene/resources/height_map_shape_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

#include "scene/resources/shape_3d.h"

class Image;

class HeightMapShape3D : public Shape3D {
GDCLASS(HeightMapShape3D, Shape3D);

Expand All @@ -57,6 +59,8 @@ class HeightMapShape3D : public Shape3D {
real_t get_min_height() const;
real_t get_max_height() const;

void update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max);

virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual real_t get_enclosing_radius() const override;

Expand Down

0 comments on commit 04858d4

Please sign in to comment.