feat: WIP terrain path modifiers
This commit is contained in:
parent
4d4427949d
commit
31a9986066
4 changed files with 288 additions and 74 deletions
|
|
@ -12,6 +12,8 @@ void initialize_terrain_module(ModuleInitializationLevel p_level) {
|
|||
ClassDB::register_class<Terrain>();
|
||||
ClassDB::register_abstract_class<TerrainModifier>();
|
||||
ClassDB::register_class<TerrainModifierDistance>();
|
||||
ClassDB::register_class<TerrainModifierPath>();
|
||||
ClassDB::register_class<TerrainModifierPathPoint>();
|
||||
ClassDB::register_class<TerrainChunkMesh>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
#include "terrain_modifier.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "macros.h"
|
||||
#include "terrain/terrain.h"
|
||||
#include <algorithm>
|
||||
|
||||
void TerrainModifier::_bind_methods() {
|
||||
BIND_PROPERTY(Variant::FLOAT, blend_distance);
|
||||
}
|
||||
void TerrainModifier::_bind_methods() {}
|
||||
|
||||
void TerrainModifier::_notification(int what) {
|
||||
switch (what) {
|
||||
|
|
@ -24,18 +23,6 @@ void TerrainModifier::_notification(int what) {
|
|||
}
|
||||
}
|
||||
|
||||
float TerrainModifier::blend(float under, float over) {
|
||||
float const difference{ under - over };
|
||||
float const distance{ Math::abs(difference) };
|
||||
// .25 because we need half of each half of the blend range to be used
|
||||
float const center_distance{ this->blend_distance == 0.f ? 0.f : this->blend_distance * 0.25f - distance / this->blend_distance };
|
||||
if (center_distance <= 0.f) {
|
||||
return over;
|
||||
}
|
||||
float const smooth_center_distance{ center_distance * center_distance };
|
||||
return over + smooth_center_distance;
|
||||
}
|
||||
|
||||
void TerrainModifier::push_changed(Rect2 area) {
|
||||
if (this->terrain) {
|
||||
this->terrain->push_changed(area);
|
||||
|
|
@ -44,22 +31,25 @@ void TerrainModifier::push_changed(Rect2 area) {
|
|||
|
||||
float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
Vector3 const global_position{ get_thread_safe_global_position() };
|
||||
world_coordinate -= { global_position.x, global_position.z };
|
||||
return blend(before, 0.0);
|
||||
return global_position.y;
|
||||
}
|
||||
|
||||
void TerrainModifier::set_bounds(Rect2 bounds) {
|
||||
if (this->bounds != bounds) {
|
||||
push_changed(bounds);
|
||||
push_changed(this->bounds);
|
||||
this->bounds = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
Rect2 TerrainModifier::get_bounds() const {
|
||||
return this->bounds;
|
||||
}
|
||||
|
||||
Vector3 TerrainModifier::get_thread_safe_global_position() const {
|
||||
return this->thread_safe_global_position;
|
||||
}
|
||||
|
||||
void TerrainModifier::set_blend_distance(float value) {
|
||||
this->blend_distance = value;
|
||||
}
|
||||
|
||||
float TerrainModifier::get_blend_distance() const {
|
||||
return this->blend_distance;
|
||||
}
|
||||
|
||||
void SharedMutex::lock_shared() {
|
||||
this->lock.lock();
|
||||
this->shared_count++;
|
||||
|
|
@ -91,11 +81,6 @@ void TerrainModifierDistance::_bind_methods() {
|
|||
}
|
||||
|
||||
void TerrainModifierDistance::curves_changed() {
|
||||
this->lock.lock_exclusive();
|
||||
if (this->distance_weight_curve.is_valid()) {
|
||||
this->distance_weight_curve->bake();
|
||||
}
|
||||
this->lock.unlock_exclusive();
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
|
|
@ -117,11 +102,7 @@ bool TerrainModifierDistance::update_bounds() {
|
|||
this->lock.unlock_shared();
|
||||
this->lock.lock_exclusive();
|
||||
bool const changed{ before != bounds };
|
||||
if (changed) {
|
||||
set_bounds(bounds);
|
||||
push_changed(before);
|
||||
push_changed(bounds);
|
||||
}
|
||||
set_bounds(bounds);
|
||||
this->lock.unlock_exclusive();
|
||||
return changed;
|
||||
}
|
||||
|
|
@ -164,7 +145,7 @@ float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float befor
|
|||
std::clamp(distance, this->distance_weight_curve->get_min_domain(), this->distance_weight_curve->get_max_domain())
|
||||
};
|
||||
float const weight{ this->distance_weight_curve->sample(weight_offset) };
|
||||
float out{ weight <= 0.f ? before : Math::lerp(before, blend(before, get_thread_safe_global_position().y), weight) };
|
||||
float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) };
|
||||
this->lock.unlock_shared();
|
||||
|
||||
return out;
|
||||
|
|
@ -197,3 +178,201 @@ void TerrainModifierDistance::set_distance_weight_curve(Ref<Curve> curve) {
|
|||
Ref<Curve> TerrainModifierDistance::get_distance_weight_curve() const {
|
||||
return this->distance_weight_curve;
|
||||
}
|
||||
|
||||
void TerrainModifierPathPoint::_bind_methods() {}
|
||||
|
||||
void TerrainModifierPathPoint::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
if ((this->path = cast_to<TerrainModifierPath>(get_parent()))) {
|
||||
this->path->path_changed();
|
||||
}
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
set_notify_transform(true);
|
||||
return;
|
||||
case NOTIFICATION_TRANSFORM_CHANGED:
|
||||
if (this->path) {
|
||||
this->path->path_changed();
|
||||
}
|
||||
return;
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
this->path = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainModifierPath::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, curve_left, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
BIND_HPROPERTY(Variant::OBJECT, curve_right, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
}
|
||||
|
||||
void TerrainModifierPath::curves_changed() {
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
}
|
||||
|
||||
bool TerrainModifierPath::update_bounds() {
|
||||
Vector2 min{}, max{};
|
||||
this->lock.lock_shared();
|
||||
if (this->points.is_empty() || this->curve_left.is_null() || this->curve_right.is_null()) {
|
||||
Vector3 point{ this->get_thread_safe_global_position() };
|
||||
min.x = point.x;
|
||||
min.y = point.z;
|
||||
max = min;
|
||||
} else {
|
||||
max = min = { this->points[0].x, this->points[0].y };
|
||||
for (Vector3 const &point : this->points) {
|
||||
max.x = max.x > point.x ? max.x : point.x;
|
||||
max.y = max.y > point.z ? max.y : point.z;
|
||||
min.x = min.x < point.x ? min.x : point.x;
|
||||
min.y = min.y < point.z ? min.y : point.z;
|
||||
}
|
||||
float max_distance_left{ this->curve_left->get_max_domain() };
|
||||
float max_distance_right{ this->curve_right->get_max_domain() };
|
||||
float max_distance{ max_distance_left > max_distance_right ? max_distance_left : max_distance_right };
|
||||
min -= { max_distance, max_distance };
|
||||
max += { max_distance, max_distance };
|
||||
}
|
||||
Rect2 bounds{ min, max - min };
|
||||
bool const changed{ bounds != get_bounds() };
|
||||
this->lock.unlock_shared();
|
||||
set_bounds(bounds);
|
||||
return changed;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
update_bounds();
|
||||
set_notify_transform(true);
|
||||
return;
|
||||
case NOTIFICATION_TRANSFORM_CHANGED:
|
||||
if (is_inside_tree()) {
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifierPath::evaluate_line(Vector3 a, Vector3 b, Vector2 world_coordinate, float &out_dot, float &out_distance) {
|
||||
Vector2 a2{ a.x, a.z }, b2{ b.x, b.z };
|
||||
Vector2 const relative_coordinate{ world_coordinate - a2 };
|
||||
Vector2 const difference2{ b2 - a2 };
|
||||
float const w{ difference2.normalized().dot(relative_coordinate) / difference2.length() };
|
||||
Vector3 const difference{ b - a };
|
||||
Vector3 const closest_on_line{ a + difference * (w > 0 ? (w < 1 ? w : 1) : 0) };
|
||||
Vector2 const right{ -difference.z, difference.x };
|
||||
out_dot = right.normalized().dot(relative_coordinate);
|
||||
out_distance = world_coordinate.distance_to({ closest_on_line.x, closest_on_line.z });
|
||||
if (a.y > b.y) {
|
||||
return a.y + (b.y - a.y) * (w > 0 ? w : 0);
|
||||
} else {
|
||||
return a.y + (b.y - a.y) * (w < 1 ? w : 1);
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
this->lock.lock_shared();
|
||||
if (this->curve_left.is_null() || this->curve_right.is_null() || this->points.size() <= 1) {
|
||||
this->lock.unlock_shared();
|
||||
return before;
|
||||
}
|
||||
float out_score{ -INFINITY };
|
||||
float out_height{ 0 };
|
||||
for (int i{ 0 }; i < this->points.size(); i++) {
|
||||
if (this->closed || i < this->points.size() - 1) {
|
||||
float dot, distance;
|
||||
float height{ evaluate_line(this->points[i], this->points[Math::wrapi(i + 1, 0, this->points.size())], world_coordinate, dot, distance) };
|
||||
float left{ this->curve_left->sample(distance) };
|
||||
float right{ this->curve_right->sample(distance) };
|
||||
float ndot{ dot / distance };
|
||||
float separation{ ndot / 2.f + 0.5f };
|
||||
float weight{ left * (1 - separation) + right * separation };
|
||||
float blended_height{ Math::lerp(before, height, weight) };
|
||||
float score{ weight - (Math::abs(ndot) == 1) * 100000.f };
|
||||
if (score > out_score) {
|
||||
out_score = score;
|
||||
out_height = blended_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->lock.unlock_shared();
|
||||
return out_height;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::path_changed() {
|
||||
this->lock.lock_exclusive();
|
||||
this->points.clear();
|
||||
Vector3 last{ INFINITY, INFINITY, INFINITY };
|
||||
for (Variant var : get_children()) {
|
||||
if (TerrainModifierPathPoint * point{ cast_to<TerrainModifierPathPoint>(var) }) {
|
||||
if (var != last) {
|
||||
this->points.push_back(point->get_global_position());
|
||||
}
|
||||
}
|
||||
last = var;
|
||||
}
|
||||
this->lock.unlock_exclusive();
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
}
|
||||
|
||||
PackedStringArray TerrainModifierPath::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (this->curve_left.is_null()) {
|
||||
warnings.push_back("distance_weight_curve is invalid, add a valid curve_left");
|
||||
}
|
||||
if (this->curve_right.is_null()) {
|
||||
warnings.push_back("distance_weight_curve is invalid, add a valid curve_right");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::set_curve_left(Ref<Curve> curve) {
|
||||
this->lock.lock_exclusive();
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (this->curve_left.is_valid()) {
|
||||
this->curve_left->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
if (curve.is_valid()) {
|
||||
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
}
|
||||
this->curve_left = curve;
|
||||
this->lock.unlock_exclusive();
|
||||
curves_changed();
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
Ref<Curve> TerrainModifierPath::get_curve_left() const {
|
||||
return this->curve_left;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::set_curve_right(Ref<Curve> curve) {
|
||||
this->lock.lock_exclusive();
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (this->curve_right.is_valid()) {
|
||||
this->curve_right->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
if (curve.is_valid()) {
|
||||
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
}
|
||||
this->curve_right = curve;
|
||||
this->lock.unlock_exclusive();
|
||||
curves_changed();
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
Ref<Curve> TerrainModifierPath::get_curve_right() const {
|
||||
return this->curve_right;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,25 +14,22 @@ class TerrainModifier : public Marker3D {
|
|||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
float blend(float under, float over);
|
||||
void push_changed(Rect2 bounds);
|
||||
|
||||
public:
|
||||
virtual float evaluate_at(Vector2 world_coordinate, float before);
|
||||
|
||||
private:
|
||||
float blend_distance{ 10.0 };
|
||||
Vector3 thread_safe_global_position{};
|
||||
Terrain *terrain{ nullptr };
|
||||
Rect2 bounds{ { -INFINITY, -INFINITY }, { INFINITY, INFINITY } };
|
||||
|
||||
protected:
|
||||
GET_SET_FNS(Rect2, bounds);
|
||||
void set_bounds(Rect2 bounds);
|
||||
Rect2 get_bounds() const;
|
||||
|
||||
public:
|
||||
Vector3 get_thread_safe_global_position() const;
|
||||
void set_blend_distance(float value);
|
||||
float get_blend_distance() const;
|
||||
GET_SET_FNS(Terrain *, terrain);
|
||||
};
|
||||
|
||||
|
|
@ -55,7 +52,7 @@ class TerrainModifierDistance : public TerrainModifier {
|
|||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
virtual float distance_at(Vector2 const &world_coordinate);
|
||||
float distance_at(Vector2 const &world_coordinate);
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinate, float before) override;
|
||||
|
|
@ -69,3 +66,45 @@ public:
|
|||
void set_distance_weight_curve(Ref<Curve> curve);
|
||||
Ref<Curve> get_distance_weight_curve() const;
|
||||
};
|
||||
|
||||
class TerrainModifierPathPoint : public Marker3D {
|
||||
GDCLASS(TerrainModifierPathPoint, Marker3D);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
class TerrainModifierPath *path{ nullptr };
|
||||
};
|
||||
|
||||
class TerrainModifierPath : public TerrainModifier {
|
||||
GDCLASS(TerrainModifierPath, TerrainModifier);
|
||||
static void _bind_methods();
|
||||
void curves_changed();
|
||||
bool update_bounds();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
float evaluate_line(Vector3 a, Vector3 b, Vector2 world_coordinate, float &out_dot, float &out_distance);
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinate, float before) override;
|
||||
void path_changed();
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
private:
|
||||
SharedMutex lock{};
|
||||
float min_height{};
|
||||
float max_height{};
|
||||
bool closed{ false };
|
||||
Vector<Vector3> points{};
|
||||
Ref<Curve> curve_left{};
|
||||
Ref<Curve> curve_right{};
|
||||
|
||||
public:
|
||||
void set_curve_left(Ref<Curve> curve);
|
||||
Ref<Curve> get_curve_left() const;
|
||||
void set_curve_right(Ref<Curve> curve);
|
||||
Ref<Curve> get_curve_right() const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,25 +12,20 @@ sky_material = SubResource("ProceduralSkyMaterial_kbmr5")
|
|||
background_mode = 2
|
||||
sky = SubResource("Sky_w3uoq")
|
||||
|
||||
[sub_resource type="Curve" id="Curve_nonsf"]
|
||||
_limits = [0.0, 1.0, 0.0, 500.0]
|
||||
_data = [Vector2(47.722435, 1), 0.0, -9.816581e-05, 0, 0, Vector2(353.09595, 0.40317744), -0.0059316778, -0.0059316778, 0, 0, Vector2(500, 0), 0.0, 0.0, 0, 0]
|
||||
[sub_resource type="Curve" id="Curve_kbmr5"]
|
||||
_limits = [0.0, 1.0, 0.0, 100.0]
|
||||
_data = [Vector2(0, 1), 0.0, -0.0015643721, 0, 0, Vector2(60.370926, 0.60930693), -0.018071167, -0.018071167, 0, 0, Vector2(100, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Curve" id="Curve_w3uoq"]
|
||||
_limits = [0.0, 1.0, 0.0, 500.0]
|
||||
_data = [Vector2(0, 1), 0.0, -9.816581e-05, 0, 0, Vector2(269.62408, 0.60529476), -0.00438691, -0.00438691, 0, 0, Vector2(500, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Curve" id="Curve_kbmr5"]
|
||||
_limits = [0.0, 1.0, 0.0, 300.0]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(126.071, 0.5549462), -0.009107173, -0.009107173, 0, 0, Vector2(186.00266, 0.15958571), -0.004071936, -0.004071936, 0, 0, Vector2(300, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 4
|
||||
_limits = [0.0, 1.0, 0.0, 200.0]
|
||||
_data = [Vector2(0, 1), 0.0, -0.005, 0, 1, Vector2(200, 0), -0.005, 0.0, 1, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="Curve" id="Curve_chm2y"]
|
||||
_limits = [0.0, 1.0, 0.0, 300.0]
|
||||
_data = [Vector2(0, 1), -0.005856493, -0.0037222188, 0, 0, Vector2(300, 0), 7.00571e-05, -0.05797184, 0, 0]
|
||||
point_count = 2
|
||||
_data = [Vector2(0, 1), -0.005856493, -0.0020244052, 0, 0, Vector2(195.24551, 0.17229712), -0.0043557375, -0.0043557375, 0, 0, Vector2(300, 0), 0.00031304389, -0.05797184, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_kbmr5"]
|
||||
|
||||
|
|
@ -44,29 +39,28 @@ side_length = 1000
|
|||
chunk_size = 100
|
||||
thread_count = 5
|
||||
|
||||
[node name="TerrainModifierDistance2" type="TerrainModifierDistance" parent="Terrain" unique_id=2110821264]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 856.484, -141.91985, 376.6767)
|
||||
blend_distance = 0.0
|
||||
distance_weight_curve = SubResource("Curve_nonsf")
|
||||
[node name="TerrainModifierPath" type="TerrainModifierPath" parent="Terrain" unique_id=462259542]
|
||||
transform = Transform3D(2.7896824, 0, 0, 0, 1, 0, 0, 0, 3.9856973, 154.71588, 151.32993, 365.46173)
|
||||
curve_left = SubResource("Curve_kbmr5")
|
||||
curve_right = SubResource("Curve_w3uoq")
|
||||
|
||||
[node name="TerrainModifierDistance5" type="TerrainModifierDistance" parent="Terrain" unique_id=54251754]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 587.8701, -100.01076, 96.289734)
|
||||
blend_distance = 0.0
|
||||
distance_weight_curve = SubResource("Curve_w3uoq")
|
||||
[node name="TerrainModifierPathPoint" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=1975236067]
|
||||
transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.9999999, -6.678116, -33.99875, -74.15768)
|
||||
|
||||
[node name="TerrainModifierDistance4" type="TerrainModifierDistance" parent="Terrain" unique_id=961725906]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 945.23486, -114.01094, 837.39813)
|
||||
blend_distance = 0.0
|
||||
distance_weight_curve = SubResource("Curve_nonsf")
|
||||
[node name="TerrainModifierPathPoint5" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=2007122252]
|
||||
transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, 41.63815, 4.6532288, -38.86402)
|
||||
|
||||
[node name="TerrainModifierDistance" type="TerrainModifierDistance" parent="Terrain" unique_id=1885116624]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 111.654144, 74.591, 740.1567)
|
||||
blend_distance = 0.0
|
||||
distance_weight_curve = SubResource("Curve_kbmr5")
|
||||
[node name="TerrainModifierPathPoint2" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=88875414]
|
||||
transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, 4.2666435, -27.249336, 5.506424)
|
||||
|
||||
[node name="TerrainModifierDistance3" type="TerrainModifierDistance" parent="Terrain" unique_id=1846439541]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 260.8238, 202.32297, 899.09283)
|
||||
blend_distance = 0.0
|
||||
[node name="TerrainModifierPathPoint3" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=910243114]
|
||||
transform = Transform3D(-0.08673841, 0, 0.9962309, 0, 1, 0, -0.9962308, 0, -0.08673839, 77.83667, 84.083954, 12.383522)
|
||||
|
||||
[node name="TerrainModifierPathPoint4" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=738726374]
|
||||
transform = Transform3D(-0.08673839, 0, 0.9962309, 0, 1, 0, -0.9962308, 0, -0.08673841, 124.05687, -50.373947, -21.531578)
|
||||
|
||||
[node name="TerrainModifierDistance8" type="TerrainModifierDistance" parent="Terrain" unique_id=1993490768]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 784.91595, 251.11382, 135.92102)
|
||||
distance_weight_curve = SubResource("Curve_chm2y")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1089775425]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue