Compare commits

...
Sign in to create a new pull request.

89 commits

Author SHA1 Message Date
82063632b9 Add 'modules/behaviour_nodes/' from commit '2a4e00e6f1'
git-subtree-dir: modules/behaviour_nodes
git-subtree-mainline: 4e641855b4
git-subtree-split: 2a4e00e6f1
2026-03-13 10:49:24 +01:00
4e641855b4 chore: removed behaviour nodes module 2026-03-13 10:40:40 +01:00
2a4e00e6f1 feat: backlog code 2026-03-13 10:38:52 +01:00
f64f126f45 fix: enter/exit get called in hierarchy order 2026-03-12 23:07:51 +01:00
5b9d437600 fix: repeater inherits composite 2026-03-12 22:38:31 +01:00
d9e3f59519 feat: RepeatUntilFail implementation 2026-03-12 22:37:57 +01:00
5556ea9f6e feat: decorator nodes registered with ClassDB 2026-03-12 22:34:38 +01:00
27e3d7c804 fix: BehaviourAction has a Timer 2026-03-12 22:33:22 +01:00
024ace256d fix: Selector inherits Composite 2026-03-12 22:29:56 +01:00
a26fea2802 fix: debug log shows actual new behaviour 2026-03-12 22:28:51 +01:00
1e7f1cb3f6 chore: cleaning up control_nodes 2026-03-12 22:10:13 +01:00
78db4ffa07 feat: added configurationwarnings to AlwaysSuccess 2026-03-12 21:43:52 +01:00
964bcc1217 chore: changed all outlines 2026-03-12 21:38:20 +01:00
c2aa6690c3 feat: implemented behaviour tree 2026-03-12 21:37:29 +01:00
fc11921776 feat: new Behaviour nodes module 2026-03-10 13:07:41 +01:00
01d57480c5 fix: path nodes are only evaluated in-tree 2026-03-10 13:07:01 +01:00
da0419bc13 fix: no gaps in terrain collision time/space
Terrain collision is generated before the first _process

Terrain collision is generated from and to the edges of chunks with no
gaps inbetween
2026-03-09 11:28:17 +01:00
9ea423a2cb chore: removed untaken branches 2026-03-07 11:02:15 +01:00
078200c3f2 chore: adjusting terrain test 2026-03-06 22:04:29 +01:00
91c64e365e chore: updated test terrain, player-scale test 2026-03-04 12:47:47 +01:00
becef85f20 feat: terrain mesh material 2026-03-03 22:57:41 +01:00
957a39af14 chore: added terrain shader & material 2026-03-03 22:56:57 +01:00
0cdfba5707 chore: adjusted terrain test data 2026-03-03 21:37:20 +01:00
9c8f160edd fix: lines cannot be 0 length 2026-03-03 21:37:05 +01:00
0446f302f7 chore: improved smoothness of paths 2026-03-03 18:16:18 +01:00
e2207eec68 fix: check if threads are started before waiting 2026-03-03 16:48:49 +01:00
0f5539ebc0 fix: threads don't idle while inactive 2026-03-03 16:44:58 +01:00
a214291c2f chore: adjusted terrain modifier logic 2026-03-03 12:54:35 +01:00
7a2ed4a785 feat: paths support 1 curve 2026-03-03 10:30:54 +01:00
fac1f4c733 feat: implemented smooth paths 2026-03-02 23:29:32 +01:00
9ea66df220 feat: WIP improved turns on path modifier 2026-03-01 23:01:08 +01:00
f057254396 chore: changed terrain test scene 2026-02-27 23:09:44 +01:00
73ddf09b98 feat: improved path modifier mesh generation 2026-02-27 23:06:06 +01:00
31a9986066 feat: WIP terrain path modifiers 2026-02-27 18:01:14 +01:00
4d4427949d chore: changed terrain files 2026-02-26 20:37:32 +01:00
56c244ee57 feat: replaced sample_baked with sample 2026-02-26 20:37:13 +01:00
0ca27ed61e chore: minor TerrainModifier::blend optimisation 2026-02-26 20:28:09 +01:00
65bf506f0b feat: removed practically unneeded properties 2026-02-26 20:24:25 +01:00
2efd3d2ddf chore: removed some unneeded whitespace 2026-02-25 23:44:41 +01:00
f0e63a1142 fix: chunk gets removed from tree before deleting 2026-02-25 23:11:32 +01:00
0d982a751c fix: rebuilding grid fully stops threads first 2026-02-25 23:05:03 +01:00
1e44fcd09f feat: apply_mesh for chunks is smeared over frames 2026-02-25 22:42:07 +01:00
226c821454 feat: bounds-based mesh reloading 2026-02-25 22:02:02 +01:00
953e4abe5b chore: modifiers are aware of parent terrain 2026-02-25 20:21:13 +01:00
80eb6ef4c0 feat: tore out old generation triggers 2026-02-25 20:12:24 +01:00
51fe41eda0 chore: renamed ReadWrite mtx to Shared mutex 2026-02-25 19:59:59 +01:00
811970a306 chore: massively sped up mesh gen performance 2026-02-25 18:57:06 +01:00
8ff1b1404d feat: multithreading for terrain mesh gen 2026-02-24 23:16:21 +01:00
d3561eb218 feat: terrain tl is 0,0,0 2026-02-24 12:51:05 +01:00
da1512e8cc feat: terrain settings are now editable 2026-02-24 12:50:49 +01:00
508e87f19b chore: TerrainModifier inherits Marker3D ipv Node 2026-02-24 10:48:00 +01:00
c5457ffbc0 feat: real-time distance modifier curve updates 2026-02-23 21:15:30 +01:00
fc6034242e feat: realtime editing 2026-02-23 12:01:35 +01:00
cd4f619a20 chore: testing in terrain test scene 2026-02-22 23:40:41 +01:00
f6442805af feat: simplified distance modifier blend logic 2026-02-22 23:40:29 +01:00
ed1d045ab1 chore: adjusting terrain logic 2026-02-22 23:39:46 +01:00
a10e6d8ad4 chore: adjusted character_fem 2026-02-22 22:50:24 +01:00
c17b513b34 fix: extracted blend logic to evaluate_at 2026-02-22 22:50:07 +01:00
ac139f01b6 feat: started work on terrain editor tools 2026-02-22 22:27:23 +01:00
a61b52806a chore: moved macros.h to modules/ 2026-02-22 22:27:10 +01:00
78433f1514 chore: style test adjustments to various parts 2026-01-27 23:30:46 +01:00
48b8e46448 chore: adjusted vehicle physics 2026-01-26 22:18:34 +01:00
bc7fdda2bc chore: bike fits character proportions 2026-01-26 22:10:01 +01:00
cf71e336fe chore: changed main scene to style test 2026-01-26 22:09:52 +01:00
ae2f24d5ae feat: making a godot vehicle 2026-01-26 22:09:04 +01:00
a2df2e1a47 chore: improved collider of terrain mesh 2026-01-26 22:08:40 +01:00
b422998e25 feat: added "-detailoutline" model import flag 2026-01-26 22:08:18 +01:00
b89eccb62d chore: improvements of character model weights 2026-01-26 22:08:03 +01:00
c0d0451236 feat: expanded style test 2026-01-25 00:02:35 +01:00
b34aae1bda chore: adjusted style test scene and trees 2026-01-22 22:43:36 +01:00
2e271f355c feat: added bike model to style test 2026-01-22 22:43:19 +01:00
9cd0624341 feat: added reservoir to style test 2026-01-21 22:42:13 +01:00
0f8539e803 feat: further work on style test 2026-01-21 20:28:04 +01:00
0642b6b7ba feat: updated engine to 4.6 RC 2 2026-01-21 20:27:30 +01:00
33b088c315 chore: continued style test 2026-01-21 01:02:12 +01:00
cb7138353e feat: started work on style test 2026-01-18 23:27:14 +01:00
1e94542085 feat: npc follow logic 2026-01-18 23:26:41 +01:00
411f1662c1 chore(editor): configure runs project editor 2025-12-31 16:28:52 +01:00
ea3b68aef8 translated movement to state machine 2025-12-31 12:19:30 +01:00
c3ff41e1e6 feat: rethought behaviour separation and states 2025-12-26 00:35:36 +01:00
54a887dbf3 fix(editor): c-mode triggers c++-mode instead 2025-12-24 18:28:45 +01:00
88a1a524f0 chore(project): added .dir-locals.el 2025-12-24 15:24:45 +01:00
79bf5c6ac0 feat: base character class and player character 2025-12-24 15:24:21 +01:00
61c9925b40 feat: GET_SET_FNS in macros.h 2025-12-24 15:23:47 +01:00
9508f2078d chore(git): added __pycache__ to gitignore 2025-12-24 15:23:25 +01:00
3324d25e9b chore: updated engine to latest master 2025-12-21 14:51:54 +01:00
f3c2bf7515 INITIALIZED TEMPLATE 2025-12-21 14:50:06 +01:00
d87b782d6d fix: justfile updates self to rename build 2025-12-21 14:30:58 +01:00
Sara
82f2cae0f6 fix: added .cache and build artifacts to gitignore 2025-06-23 21:13:38 +02:00
122 changed files with 4318 additions and 55 deletions

6
.dir-locals.el Normal file
View file

@ -0,0 +1,6 @@
((c++-mode . ((mode . clang-format-on-save)))
(c-mode . ((mode . c++)))
(nil . ((projectile-project-compilation-cmd . "just build")
(projectile-project-run-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86_64.llvm --path project")
(projectile-project-configure-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86_64.llvm --path project -e")
(projectile-project-test-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86.llvm --path project"))))

14
.gitignore vendored
View file

@ -6,9 +6,17 @@
config.log
.sconf_temp
# build artifacts
*.o
compile_commands.json
engine/.github
project/.godot
build/PROJECT.pck
build/PROJECT.x86_64
build/PROJECT.exe
build/authority.pck
build/authority.x86_64
build/authority.exe
build.zip
# general-purpose cache folder (used by e.g clangd)
.cache
__pycache__

Binary file not shown.

Binary file not shown.

2
engine

@ -1 +1 @@
Subproject commit 215acd52e82f4c575abb715e25e54558deeef998
Subproject commit 6b76a5a8dc011723033cc8ad2ba3da345daab039

View file

@ -1,6 +1,6 @@
set export
BUILD_NAME := "change_me"
BUILD_NAME := "authority"
build: format
# Compiling Editor
@ -37,9 +37,8 @@ release-windows: build
initialize-template projectname:
# Initializing Template {{projectname}}
sed -i -e "s/PROJECT/{{projectname}}/g" ./modules/PROJECT/register_types.h ./modules/PROJECT/register_types.cpp ./project/project.godot ./project/export_presets.cfg .gitignore
sed "s/change_me/{{projectname}}/" ./justfile
sed -i -e "s/authority/{{projectname}}/" ./justfile
mv ./modules/PROJECT ./modules/{{projectname}}
# Done Initializing, you will still have to update BUILD_NAME in your justfile
format:
# Formatting Custom Modules

View file

@ -1,20 +0,0 @@
#ifndef GODOT_EXTRA_MACROS_H
#define GODOT_EXTRA_MACROS_H
#define BIND_GET_SET(m_property) \
ClassDB::bind_method(D_METHOD("set_" #m_property, #m_property), \
&self_type::set_##m_property); \
ClassDB::bind_method(D_METHOD("get_" #m_property), \
&self_type::get_##m_property)
#define BIND_HPROPERTY(m_type, m_property, ...) \
BIND_GET_SET(m_property); \
ADD_PROPERTY(PropertyInfo(m_type, #m_property, __VA_ARGS__), \
"set_" #m_property, "get_" #m_property)
#define BIND_PROPERTY(m_type, m_property) \
BIND_GET_SET(m_property); \
ADD_PROPERTY(PropertyInfo(m_type, #m_property), "set_" #m_property, \
"get_" #m_property)
#endif // !GODOT_EXTRA_MACROS_H

View file

@ -1,15 +0,0 @@
#include "register_types.h"
#include "core/object/class_db.h"
void initialize_PROJECT_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
void uninitialize_PROJECT_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View file

@ -1,9 +0,0 @@
#ifndef PROJECT_REGISTER_TYPES_H
#define PROJECT_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_PROJECT_module(ModuleInitializationLevel p_level);
void uninitialize_PROJECT_module(ModuleInitializationLevel p_level);
#endif // !PROJECT_REGISTER_TYPES_H

View file

@ -0,0 +1,155 @@
#include "character.h"
#include "core/config/engine.h"
#include "macros.h"
void CharacterData::_bind_methods() {
BIND_PROPERTY(Variant::FLOAT, speed);
}
void Character::_bind_methods() {
BIND_HPROPERTY(Variant::OBJECT, data, PROPERTY_HINT_RESOURCE_TYPE, "CharacterData");
}
void Character::physics_process(double delta) {
Vector3 const velocity{ get_velocity() };
Vector3 new_velocity{ velocity };
new_velocity.x = this->world_movement_direction.x;
new_velocity.z = this->world_movement_direction.y;
set_velocity(new_velocity);
if (!velocity.is_zero_approx()) {
move_and_slide();
}
}
void Character::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint() || !this->data.is_valid()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_READY:
set_physics_process(true);
return;
case NOTIFICATION_PHYSICS_PROCESS:
physics_process(get_physics_process_delta_time());
return;
}
}
PackedStringArray Character::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (this->data.is_null()) {
warnings.push_back("Character requires 'data' to be initialised. To avoid crashes consider adding a placeholder if you intend to programmatically initialise it.");
}
return warnings;
}
void Character::set_movement(Vector2 movement) {
this->world_movement_direction = movement;
}
bool Character::is_moving() const {
return !this->world_movement_direction.is_zero_approx();
}
void CharacterState::_bind_methods() {
BIND_PROPERTY(Variant::BOOL, start_active);
}
void CharacterState::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
this->character = cast_to<Character>(get_parent());
ERR_FAIL_COND_EDMSG(this->character == nullptr, "CharacterState requires parent to be of type Character");
return;
case NOTIFICATION_READY:
if (start_active) {
callable_mp(this, &self_type::set_state_active).call_deferred(true);
}
return;
}
}
PackedStringArray CharacterState::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (cast_to<Character>(get_parent()) == nullptr) {
warnings.push_back("CharacterState requires direct Character parent");
}
return warnings;
}
void CharacterState::switch_to_state(String value) {
if (!this->state_active) {
print_error(vformat("Attempt to switch from inactive state %s to new state %s", get_path(), value));
return;
}
set_state_active(false);
stack_state_independent(value);
}
void CharacterState::stack_state_dependent(String value) {
if (!this->state_active) {
print_error(vformat("Attempt to stack dependent state %s from inactive state %s", value, get_path()));
return;
}
Node *node{ get_parent()->get_node(value) };
if (CharacterState * state{ cast_to<CharacterState>(node) }) {
state->depending_state = this;
this->dependent_states.insert(state);
state->set_state_active(true);
}
}
void CharacterState::notify_dependent_inactive(CharacterState *state) {
if (!this->state_active) {
print_error(vformat("Received notification that dependent state %s became inactive, while depending state %s was inactive", state->get_path(), get_path()));
return;
}
this->dependent_states.erase(state);
}
void CharacterState::stack_state_independent(String value) {
if (!this->state_active) {
print_error(vformat("Attempt to stack state %s from inactive state %s", value, get_path()));
return;
}
Node *node{ get_parent()->get_node(value) };
if (CharacterState * state{ cast_to<CharacterState>(node) }) {
state->set_state_active(true);
} else {
print_error(vformat("Attempt to stack nonexistent state %s from %s", value, get_path()));
}
}
void CharacterState::set_state_active(bool active) {
if (this->state_active != active) {
this->state_active = active;
if (active) {
state_entered();
} else {
state_exited();
if (this->depending_state && this->depending_state->state_active) {
this->depending_state->notify_dependent_inactive(this);
}
this->depending_state = nullptr;
for (CharacterState *state : this->dependent_states) {
state->set_state_active(false);
}
this->dependent_states.clear();
}
}
}
bool CharacterState::get_state_active() const {
return this->state_active;
}
Character *CharacterState::get_character() const {
return this->character;
}

View file

@ -0,0 +1,69 @@
#pragma once
#include "core/io/resource.h"
#include "core/templates/hash_set.h"
#include "macros.h"
#include "scene/3d/physics/character_body_3d.h"
class CharacterData : public Resource {
GDCLASS(CharacterData, Resource);
static void _bind_methods();
private:
float speed{};
public:
GET_SET_FNS(float, speed);
};
class Character : public CharacterBody3D {
GDCLASS(Character, CharacterBody3D);
protected:
static void _bind_methods();
void physics_process(double delta);
void _notification(int what);
PackedStringArray get_configuration_warnings() const override;
public:
void set_movement(Vector2 movement);
bool is_moving() const;
private:
Ref<CharacterData> data{};
Vector2 world_movement_direction{};
public:
GET_SET_FNS(Ref<CharacterData>, data);
};
class CharacterState : public Node {
GDCLASS(CharacterState, Node);
static void _bind_methods();
protected:
void _notification(int what);
PackedStringArray get_configuration_warnings() const override;
void switch_to_state(String state);
void stack_state_dependent(String state);
void notify_dependent_inactive(CharacterState *dependent);
void stack_state_independent(String state);
virtual void state_entered() {}
virtual void state_exited() {}
public:
void set_state_active(bool active);
bool get_state_active() const;
Character *get_character() const;
private:
bool start_active{ false };
bool state_active{ false };
Character *character{ nullptr };
HashSet<CharacterState *> dependent_states{};
CharacterState *depending_state{ nullptr };
public:
GET_SET_FNS(bool, start_active);
};

View file

@ -0,0 +1,13 @@
#include "nav_marker.h"
void NavMarker::_bind_methods() {}
void NavMarker::_notification(int what) {
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
this->set_gizmo_extents(3);
return;
}
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "macros.h"
#include "scene/3d/marker_3d.h"
class Character;
class NavMarker : public Marker3D {
GDCLASS(NavMarker, Marker3D);
static void _bind_methods();
protected:
void _notification(int what);
private:
Character *claimed{ nullptr };
public:
GET_SET_FNS(Character *, claimed);
};

View file

@ -0,0 +1,84 @@
#include "party_member_states.h"
#include "authority/nav_marker.h"
#include "authority/player_character.h"
#include "core/config/engine.h"
#include "core/error/error_macros.h"
#include "core/templates/vector.h"
void PartyMemberFollow::_bind_methods() {}
void PartyMemberFollow::process_position_target() {
Vector3 const marker_position{ this->claimed_marker->get_global_position() };
Vector3 const nav_target{ this->nav->get_target_position() };
Vector3 const global_position{ get_character()->get_global_position() };
if (global_position.distance_squared_to(marker_position) < 0.5) {
return;
}
if (nav_target.distance_squared_to(marker_position) > 0.25) {
this->nav->set_target_position(marker_position);
}
if (this->nav->is_navigation_finished()) {
return;
}
Vector3 velocity{ global_position.direction_to(this->nav->get_next_path_position()) };
velocity.y = 0;
if (this->nav->get_avoidance_enabled()) {
this->nav->set_velocity(velocity * get_character()->get_data()->get_speed());
} else {
push_movement_direction(velocity * get_character()->get_data()->get_speed());
}
}
void PartyMemberFollow::push_movement_direction(Vector3 velocity) {
get_character()->set_movement(Vector2{ velocity.x, velocity.z });
}
void PartyMemberFollow::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_READY:
this->nav = cast_to<NavigationAgent3D>(get_parent()->get_node(NodePath("%NavigationAgent3D")));
ERR_FAIL_COND_EDMSG(this->nav == nullptr, "PartyMemberFollow cannot initialise without a navigation agent");
return;
case NOTIFICATION_PROCESS:
process_position_target();
return;
}
}
PackedStringArray PartyMemberFollow::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (!get_parent()->has_node(NodePath("%NavigationAgent3D")) || !cast_to<NavigationAgent3D>(get_parent()->get_node(NodePath("%NavigationAgent3D")))) {
warnings.push_back("PartyMemberFollow expects a scene sibling of type NavigationAgent3D named with unique name '%NavigationAgent3D'");
}
return warnings;
}
void PartyMemberFollow::state_entered() {
Vector<NavMarker *> const &markers{ PlayerCharacter::get_singleton()->get_party_follow_markers() };
for (NavMarker *marker : markers) {
if (marker->get_claimed() == nullptr) {
marker->set_claimed(get_character());
this->claimed_marker = marker;
if (this->nav->get_avoidance_enabled()) {
this->nav->connect("velocity_computed", callable_mp(this, &self_type::push_movement_direction));
}
set_process(true);
return;
}
}
ERR_FAIL_EDMSG("PartyMemberFollow could not find an unclaimed player follow marker");
set_state_active(false);
}
void PartyMemberFollow::state_exited() {
if (this->claimed_marker) {
this->claimed_marker->set_claimed(nullptr);
this->nav->disconnect("velocity_computed", callable_mp(this, &self_type::push_movement_direction));
set_process(false);
}
}

View file

@ -0,0 +1,22 @@
#pragma once
#include "authority/character.h"
#include "authority/nav_marker.h"
#include "scene/3d/navigation/navigation_agent_3d.h"
class PartyMemberFollow : public CharacterState {
GDCLASS(PartyMemberFollow, CharacterState);
static void _bind_methods();
void process_position_target();
void push_movement_direction(Vector3 velocity);
protected:
void _notification(int what);
PackedStringArray get_configuration_warnings() const override;
void state_entered() override;
void state_exited() override;
private:
NavigationAgent3D *nav{ nullptr };
NavMarker *claimed_marker{ nullptr };
};

View file

@ -0,0 +1,8 @@
#pragma once
#include "scene/3d/camera_3d.h"
class PlayerCamera : public Camera3D {
GDCLASS(PlayerCamera, Camera3D);
static void _bind_methods();
};

View file

@ -0,0 +1,36 @@
#include "player_character.h"
#include "authority/nav_marker.h"
void PlayerCharacter::_bind_methods() {}
void PlayerCharacter::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
instance = this;
return;
case NOTIFICATION_READY:
for (Variant var : find_children("*", "NavMarker")) {
if (NavMarker * marker{ cast_to<NavMarker>(var) }) {
this->party_follow_markers.push_back(marker);
}
}
ERR_FAIL_COND_EDMSG(this->party_follow_markers.size() < 4, "PlayerCharacter should have at least 4 follow NavMarkers for party members");
return;
case NOTIFICATION_EXIT_TREE:
if (instance == this) {
instance = nullptr;
}
return;
}
}
PlayerCharacter *PlayerCharacter::instance{ nullptr };
PlayerCharacter *PlayerCharacter::get_singleton() {
return instance;
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "authority/character.h"
#include "authority/nav_marker.h"
#include "macros.h"
class PlayerCharacter : public Character {
GDCLASS(PlayerCharacter, Character);
static void _bind_methods();
protected:
void _notification(int what);
private:
Vector<NavMarker *> party_follow_markers{};
static PlayerCharacter *instance;
public:
static PlayerCharacter *get_singleton();
GET_SET_FNS(Vector<NavMarker *> const &, party_follow_markers);
};

View file

@ -0,0 +1,75 @@
#include "player_states.h"
#include "core/input/input.h"
#include "core/math/basis.h"
#include "scene/3d/camera_3d.h"
#include "scene/main/viewport.h"
void PlayerInputState::_bind_methods() {}
void PlayerInputState::unhandled_input(Ref<InputEvent> const &what) {
bool const is_move_vertical{ what->is_action("move_forward") || what->is_action("move_backward") };
bool const is_move_horizontal{ what->is_action("move_right") || what->is_action("move_left") };
if (is_move_vertical || is_move_horizontal) {
stack_state_dependent("PlayerMovementState");
get_viewport()->set_input_as_handled();
}
}
void PlayerInputState::state_entered() {
set_process_unhandled_input(true);
}
void PlayerInputState::state_exited() {
set_process_unhandled_input(false);
}
void PlayerMovementState::_bind_methods() {}
void PlayerMovementState::process(double delta) {
Basis const basis{ get_viewport()->get_camera_3d()->get_global_basis() };
Vector2 backward{ basis.get_column(0).x, basis.get_column(0).z };
if (backward.is_zero_approx()) {
backward = Vector2{ basis.get_column(1).x, basis.get_column(1).z };
}
Vector2 const right{ basis.get_column(2).x, basis.get_column(2).z };
get_character()->set_movement((backward.normalized() * this->movement.x + right.normalized() * this->movement.y) * get_character()->get_data()->get_speed());
}
void PlayerMovementState::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_READY:
set_process_input(true);
return;
case NOTIFICATION_PROCESS:
process(get_process_delta_time());
return;
}
}
void PlayerMovementState::input(Ref<InputEvent> const &what) {
bool const is_move_vertical{ what->is_action("move_forward") || what->is_action("move_backward") };
bool const is_move_horizontal{ what->is_action("move_right") || what->is_action("move_left") };
if (is_move_vertical || is_move_horizontal) {
this->movement = {
Input::get_singleton()->get_axis("move_left", "move_right"),
Input::get_singleton()->get_axis("move_forward", "move_backward")
};
this->movement.normalize();
}
if (this->get_state_active() && this->movement.is_zero_approx()) {
set_state_active(false);
}
}
void PlayerMovementState::state_entered() {
set_process(true);
}
void PlayerMovementState::state_exited() {
set_process(false);
}

View file

@ -0,0 +1,29 @@
#pragma once
#include "authority/character.h"
class PlayerInputState : public CharacterState {
GDCLASS(PlayerInputState, CharacterState);
static void _bind_methods();
protected:
void unhandled_input(Ref<InputEvent> const &event) override;
void state_entered() override;
void state_exited() override;
};
class PlayerMovementState : public CharacterState {
GDCLASS(PlayerMovementState, CharacterState);
static void _bind_methods();
void ready();
void process(double delta);
protected:
void _notification(int what);
void input(Ref<InputEvent> const &event) override;
void state_entered() override;
void state_exited() override;
private:
Vector2 movement{};
};

View file

@ -0,0 +1,28 @@
#include "register_types.h"
#include "authority/character.h"
#include "authority/nav_marker.h"
#include "authority/party_member_states.h"
#include "authority/player_character.h"
#include "authority/player_states.h"
#include "core/object/class_db.h"
void initialize_authority_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<CharacterData>();
ClassDB::register_class<Character>();
ClassDB::register_class<CharacterState>();
ClassDB::register_class<PlayerCharacter>();
ClassDB::register_class<PlayerInputState>();
ClassDB::register_class<PlayerMovementState>();
ClassDB::register_class<NavMarker>();
ClassDB::register_class<PartyMemberFollow>();
}
void uninitialize_authority_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View file

@ -0,0 +1,9 @@
#ifndef authority_REGISTER_TYPES_H
#define authority_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_authority_module(ModuleInitializationLevel p_level);
void uninitialize_authority_module(ModuleInitializationLevel p_level);
#endif // !authority_REGISTER_TYPES_H

View file

@ -0,0 +1,3 @@
Import('env')
env.add_source_files(env.modules_sources, "*.cpp")

View file

@ -0,0 +1,31 @@
#include "behaviour_action.h"
#include "core/object/object.h"
void BehaviourAction::_bind_methods() {
ClassDB::add_virtual_method(get_class_static(), _gdvirtual__execute_get_method_info());
ClassDB::add_virtual_method(get_class_static(), _gdvirtual__exit_get_method_info());
ClassDB::add_virtual_method(get_class_static(), _gdvirtual__enter_get_method_info());
}
void BehaviourAction::enter() {
GDVIRTUAL_CALL(_enter);
}
void BehaviourAction::execute() {
Status out_status{ Fail };
GDVIRTUAL_CALL(_execute, out_status);
set_status(out_status);
}
void BehaviourAction::exit() {
GDVIRTUAL_CALL(_exit);
}
BehaviourNode *BehaviourAction::get_next() {
switch (get_status()) {
case Running:
return this;
default:
return cast_to<BehaviourNode>(get_parent());
}
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "behaviour_nodes/behaviour_node.h"
class BehaviourAction : public BehaviourNode {
GDCLASS(BehaviourAction, BehaviourNode);
static void _bind_methods();
public:
void enter() override;
void execute() override;
void exit() override;
BehaviourNode *get_next() override;
GDVIRTUAL0R_REQUIRED(Status, _execute);
GDVIRTUAL0(_enter);
GDVIRTUAL0(_exit);
};

View file

@ -0,0 +1,28 @@
#include "behaviour_composite.h"
void BehaviourComposite::_bind_methods() {}
void BehaviourComposite::child_order_changed() {
this->child_behaviours.clear();
for (Variant var : get_children()) {
if (BehaviourNode * node{ cast_to<BehaviourNode>(var) }) {
this->child_behaviours.push_back(node);
}
}
}
void BehaviourComposite::_notification(int what) {
switch (what) {
default:
return;
case NOTIFICATION_READY:
child_order_changed();
set_leaf(get_child_behaviours().is_empty());
return;
case NOTIFICATION_CHILD_ORDER_CHANGED:
if (is_ready()) {
child_order_changed();
}
return;
}
}

View file

@ -0,0 +1,18 @@
#pragma once
#include "behaviour_nodes/behaviour_node.h"
class BehaviourComposite : public BehaviourNode {
GDCLASS(BehaviourComposite, BehaviourNode);
static void _bind_methods();
void child_order_changed();
protected:
void _notification(int what);
private:
Vector<BehaviourNode *> child_behaviours{};
public:
Vector<BehaviourNode *> const &get_child_behaviours() const { return this->child_behaviours; }
GET_SET_REF_FNS(Vector<BehaviourNode *>, child_behaviours);
};

View file

@ -0,0 +1,29 @@
#include "behaviour_node.h"
#include "behaviour_nodes/behaviour_tree.h"
#include "core/config/engine.h"
void BehaviourNode::_bind_methods() {
BIND_ENUM_CONSTANT(Fail);
BIND_ENUM_CONSTANT(Running);
BIND_ENUM_CONSTANT(Success);
}
void BehaviourNode::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
Node *parent{ get_parent() };
this->parent = cast_to<BehaviourNode>(parent);
while (this->behaviour_tree == nullptr && parent != nullptr) {
if ((this->behaviour_tree = cast_to<BehaviourTree>(parent))) {
break;
}
parent = parent->get_parent();
}
return;
}
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "macros.h"
#include "scene/main/node.h"
class BehaviourNode : public Node {
GDCLASS(BehaviourNode, Node);
static void _bind_methods();
public:
GDENUM(Status, Fail, Running, Success);
protected:
void _notification(int what);
public:
virtual void enter() {}
virtual void execute() {}
virtual void exit() {}
virtual BehaviourNode *get_next() { return this; }
private:
class BehaviourTree *behaviour_tree{ nullptr };
BehaviourNode *parent{ nullptr };
Status status{};
bool leaf{ false };
public:
GET_SET_FNS(class BehaviourTree *, behaviour_tree);
GET_SET_FNS(Status, status);
GET_SET_FNS(bool, leaf);
};
MAKE_TYPE_INFO(BehaviourNode::Status, Variant::INT);

View file

@ -0,0 +1,52 @@
#include "behaviour_tree.h"
#include "behaviour_nodes/behaviour_node.h"
#include "behaviour_nodes/control_nodes.h"
#include "core/config/engine.h"
void BehaviourTree::_bind_methods() {}
void BehaviourTree::process() {
this->current->execute();
while (execute_next()) {
this->current->execute();
}
}
void BehaviourTree::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_READY:
for (Variant var : get_children()) {
if ((this->current = cast_to<BehaviourNode>(var))) {
break;
}
}
ERR_FAIL_COND_EDMSG(this->current == nullptr, "No valid BehaviourNode in BehaviourTree");
set_process(true);
return;
case NOTIFICATION_PROCESS:
process();
return;
}
}
bool BehaviourTree::execute_next() {
BehaviourNode *next{ this->current->get_next() };
if (next == this->current) {
return false;
} else {
ERR_FAIL_COND_V_EDMSG(next == nullptr, false, vformat("%s::get_next returned a nullptr, repeating last node", this->current->get_class()));
if (this->current != next->get_parent()) {
this->current->exit();
}
if (this->current->get_parent() != next) {
next->enter();
}
this->current = next;
return cast_to<BehaviourComposite>(this->current);
}
}

View file

@ -0,0 +1,18 @@
#pragma once
#include "scene/main/node.h"
class BehaviourTree : public Node {
GDCLASS(BehaviourTree, Node);
static void _bind_methods();
void process();
protected:
void _notification(int what);
public:
bool execute_next();
private:
class BehaviourNode *current{ nullptr };
};

View file

@ -0,0 +1,5 @@
def can_build(env, platform):
return True;
def configure(env):
pass;

View file

@ -0,0 +1,121 @@
#include "control_nodes.h"
#include "behaviour_nodes/behaviour_node.h"
PackedStringArray BehaviourSequence::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (get_child_behaviours().is_empty()) {
warnings.push_back("Sequence cannot have zero children");
}
return warnings;
}
void BehaviourSequence::execute() {
if (get_child_behaviours().is_empty()) {
set_status(Fail);
ERR_FAIL_EDMSG("BehaviourSequence executed with no children.");
} else if (get_status() == Running && this->current >= 0 && this->current < get_child_behaviours().size()) {
switch (get_child_behaviours().get(this->current)->get_status()) {
case Running:
set_status(Running);
break;
case Fail:
set_status(Fail);
break;
case Success:
++this->current;
set_status(this->current >= get_child_behaviours().size()
? Success
: Running);
break;
}
} else {
set_status(Running);
this->current = 0;
}
}
BehaviourNode *BehaviourSequence::get_next() {
return get_status() == Running
? get_child_behaviours().get(this->current)
: cast_to<BehaviourNode>(get_parent());
}
PackedStringArray BehaviourSelector::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (get_child_behaviours().is_empty()) {
warnings.push_back("Selector cannot have zero children");
}
return warnings;
}
void BehaviourSelector::execute() {
if (get_child_behaviours().is_empty()) {
set_status(Fail);
ERR_FAIL_EDMSG("BehaviourSelector execution with no children.");
} else if (get_status() == Running && this->current >= 0 && this->current < get_child_behaviours().size()) {
switch (get_child_behaviours().get(this->current)->get_status()) {
case Running:
set_status(Running);
break;
case Fail:
++this->current;
set_status(this->current >= get_child_behaviours().size()
? Fail
: Running);
break;
case Success:
set_status(Success);
break;
}
} else {
set_status(Running);
this->current = 0;
}
}
BehaviourNode *BehaviourSelector::get_next() {
return get_status() == Running
? get_child_behaviours().get(this->current)
: cast_to<BehaviourNode>(get_parent());
}
PackedStringArray BehaviourRepeater::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (get_child_behaviours().size() != 1) {
warnings.push_back(vformat("Repeater should have exactly one BehaviourNode child, has %d", get_child_behaviours().size()));
}
return warnings;
}
void BehaviourRepeater::execute() {
set_status(Running);
}
BehaviourNode *BehaviourRepeater::get_next() {
return get_child_behaviours().get(0);
}
PackedStringArray BehaviourRepeatUntilFail::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (get_child_behaviours().size() != 1) {
warnings.push_back("RepeatUntilFailure should have exactly one BehaviourNode child");
}
return warnings;
}
void BehaviourRepeatUntilFail::execute() {
if (get_child_behaviours().is_empty()) {
set_status(Fail);
ERR_FAIL_EDMSG("BehaviourRepeatUntilFail execution with no child");
} else {
set_status(get_child_behaviours().get(0)->get_status() == Fail
? Success
: Running);
}
}
BehaviourNode *BehaviourRepeatUntilFail::get_next() {
return get_status() == Running
? get_child_behaviours().get(0)
: cast_to<BehaviourNode>(get_parent());
}

View file

@ -0,0 +1,50 @@
#pragma once
#include "behaviour_nodes/behaviour_composite.h"
#include "behaviour_nodes/behaviour_node.h"
#include "core/variant/variant.h"
class BehaviourSequence : public BehaviourComposite {
GDCLASS(BehaviourSequence, BehaviourComposite);
static void _bind_methods() {}
public:
PackedStringArray get_configuration_warnings() const override;
void execute() override;
BehaviourNode *get_next() override;
private:
int current{ -1 };
};
class BehaviourSelector : public BehaviourComposite {
GDCLASS(BehaviourSelector, BehaviourComposite);
static void _bind_methods() {}
public:
PackedStringArray get_configuration_warnings() const override;
void execute() override;
BehaviourNode *get_next() override;
private:
int current{ -1 };
};
class BehaviourRepeater : public BehaviourComposite {
GDCLASS(BehaviourRepeater, BehaviourComposite);
static void _bind_methods() {}
public:
PackedStringArray get_configuration_warnings() const override;
void execute() override;
BehaviourNode *get_next() override;
};
class BehaviourRepeatUntilFail : public BehaviourComposite {
GDCLASS(BehaviourRepeatUntilFail, BehaviourComposite);
static void _bind_methods() {}
public:
PackedStringArray get_configuration_warnings() const override;
void execute() override;
BehaviourNode *get_next() override;
};

View file

@ -0,0 +1,30 @@
#include "decorator_nodes.h"
#include "behaviour_nodes/behaviour_node.h"
#include "core/variant/variant.h"
void BehaviourAlwaysSuccess::_bind_methods() {}
PackedStringArray BehaviourAlwaysSuccess::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (get_child_behaviours().size() != 1) {
warnings.push_back("BehaviourAlwaysSuccess should have exactly one child");
}
return warnings;
}
void BehaviourAlwaysSuccess::execute() {
if (get_child_behaviours().is_empty()) {
set_status(Fail);
ERR_FAIL_EDMSG("BehaviourSequence executed with no children.");
} else if (get_status() == Running) {
set_status(get_child_behaviours().get(0)->get_status() == Running ? Running : Success);
} else {
set_status(Running);
}
}
BehaviourNode *BehaviourAlwaysSuccess::get_next() {
return get_status() == Running
? get_child_behaviours().get(0)
: cast_to<BehaviourNode>(get_parent());
}

View file

@ -0,0 +1,15 @@
#pragma once
#include "behaviour_nodes/behaviour_composite.h"
#include "behaviour_nodes/behaviour_node.h"
#include "core/variant/variant.h"
class BehaviourAlwaysSuccess : public BehaviourComposite {
GDCLASS(BehaviourAlwaysSuccess, BehaviourComposite);
static void _bind_methods();
public:
PackedStringArray get_configuration_warnings() const override;
void execute() override;
BehaviourNode *get_next() override;
};

View file

@ -0,0 +1,27 @@
#include "behaviour_nodes/register_types.h"
#include "behaviour_nodes/behaviour_action.h"
#include "behaviour_nodes/behaviour_node.h"
#include "behaviour_nodes/behaviour_tree.h"
#include "behaviour_nodes/control_nodes.h"
#include "behaviour_nodes/decorator_nodes.h"
#include "core/object/class_db.h"
void initialize_behaviour_nodes_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<BehaviourTree>();
ClassDB::register_abstract_class<BehaviourNode>();
ClassDB::register_class<BehaviourSequence>();
ClassDB::register_class<BehaviourRepeater>();
ClassDB::register_class<BehaviourSelector>();
ClassDB::register_class<BehaviourAction>();
ClassDB::register_class<BehaviourRepeatUntilFail>();
ClassDB::register_class<BehaviourAlwaysSuccess>();
}
void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View file

@ -0,0 +1,9 @@
#ifndef BEHAVIOUR_NODES_REGISTER_TYPES_H
#define BEHAVIOUR_NODES_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_behaviour_nodes_module(ModuleInitializationLevel p_level);
void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level);
#endif // !BEHAVIOUR_NODES_REGISTER_TYPES_H

52
modules/macros.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef GODOT_EXTRA_MACROS_H
#define GODOT_EXTRA_MACROS_H
#define BIND_GET_SET(m_property) \
ClassDB::bind_method(D_METHOD("set_" #m_property, #m_property), \
&self_type::set_##m_property); \
ClassDB::bind_method(D_METHOD("get_" #m_property), \
&self_type::get_##m_property)
#define BIND_HPROPERTY(m_type, m_property, ...) \
BIND_GET_SET(m_property); \
ADD_PROPERTY(PropertyInfo(m_type, #m_property, __VA_ARGS__), \
"set_" #m_property, "get_" #m_property)
#define BIND_PROPERTY(m_type, m_property) \
BIND_GET_SET(m_property); \
ADD_PROPERTY(PropertyInfo(m_type, #m_property), "set_" #m_property, \
"get_" #m_property)
#define GET_SET_FNS(m_type, m_property) \
m_type get_##m_property() const { \
return this->m_property; \
} \
void set_##m_property(m_type value) { \
this->m_property = value; \
}
#define GET_SET_REF_FNS(m_type, m_property) \
m_type &get_##m_property() { \
return this->m_property; \
} \
void set_##m_property(m_type &value) { \
this->m_property = value; \
}
#define GET_SET_FNS_EX(m_type, m_property, m_ex) \
m_type get_##m_property() const { \
return this->m_property; \
} \
void set_##m_property(m_type value) { \
m_ex; \
this->m_property = value; \
}
#define __VA_ARGS__STRING(...) String(#__VA_ARGS__)
#define GDENUM(M_Name, ...) \
enum M_Name { __VA_ARGS__ }; \
static String M_Name##_hint() { \
return __VA_ARGS__STRING(__VA_ARGS__); \
}
#endif // !GODOT_EXTRA_MACROS_H

3
modules/terrain/SCsub Normal file
View file

@ -0,0 +1,3 @@
Import('env')
env.add_source_files(env.modules_sources, "*.cpp")

View file

@ -0,0 +1,5 @@
def can_build(env, platform):
return True;
def configure(env):
pass;

View file

@ -0,0 +1,24 @@
#include "register_types.h"
#include "core/object/class_db.h"
#include "terrain/terrain.h"
#include "terrain/terrain_chunk.h"
#include "terrain/terrain_modifier.h"
void initialize_terrain_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
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>();
}
void uninitialize_terrain_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View file

@ -0,0 +1,9 @@
#ifndef TERRAIN_REGISTER_TYPES_H
#define TERRAIN_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_terrain_module(ModuleInitializationLevel p_level);
void uninitialize_terrain_module(ModuleInitializationLevel p_level);
#endif // !TERRAIN_REGISTER_TYPES_H

271
modules/terrain/terrain.cpp Normal file
View file

@ -0,0 +1,271 @@
#include "terrain.h"
#include "terrain/terrain_chunk.h"
#include "terrain/terrain_modifier.h"
void Terrain::_bind_methods() {
BIND_HPROPERTY(Variant::OBJECT, mesh_material, PROPERTY_HINT_RESOURCE_TYPE, "Material");
BIND_PROPERTY(Variant::INT, side_length);
BIND_PROPERTY(Variant::INT, chunk_size);
BIND_PROPERTY(Variant::INT, detail);
BIND_PROPERTY(Variant::INT, thread_count);
}
void Terrain::child_order_changed() {
this->modifiers.clear();
for (Variant var : get_children()) {
if (TerrainModifier * mod{ cast_to<TerrainModifier>(var) }) {
mod->set_terrain(this);
this->modifiers.push_back(mod);
}
}
}
void Terrain::update_meshes() {
size_t num{ 1 };
this->dirty_meshes_lock.lock();
num = num > this->dirty_meshes.size() ? this->dirty_meshes.size() : num;
this->dirty_meshes_lock.unlock();
for (size_t i{ 0 }; i < num; i++) {
this->dirty_meshes_lock.lock();
TerrainChunkMesh *mesh{ this->dirty_meshes[0] };
this->dirty_meshes.remove_at(0);
this->dirty_meshes_lock.unlock();
mesh->apply_new_mesh();
}
}
void Terrain::update_threads() {
this->workload_lock.lock();
if (this->workload.is_empty()) {
this->threads_stop = true;
this->workload_lock.unlock();
for (Thread &thread : this->threads) {
if (thread.is_started()) {
thread.wait_to_finish();
}
}
} else {
this->threads_stop = false;
for (Thread &thread : this->threads) {
if (!thread.is_started()) {
thread.start(Terrain::generate_meshes_thread, this);
}
}
this->workload_lock.unlock();
}
}
void Terrain::synchronous_generate_terrain() {
this->workload_lock.lock();
this->threads_stop = false;
this->workload.clear();
this->workload.append_array(this->meshes);
for (Thread &thread : this->threads) {
if (!thread.is_started()) {
thread.start(&Terrain::generate_meshes_thread, this);
}
}
do {
this->workload_lock.unlock();
Thread::yield();
this->workload_lock.lock();
} while (!this->workload.is_empty());
this->threads_stop = true;
this->workload_lock.unlock();
for (Thread &thread : this->threads) {
thread.wait_to_finish();
}
for (TerrainChunkMesh *mesh : this->dirty_meshes) {
mesh->apply_new_mesh();
}
this->dirty_meshes.clear();
}
void Terrain::_notification(int what) {
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
if (!is_ready()) {
connect("child_order_changed", callable_mp(this, &self_type::child_order_changed));
}
return;
case NOTIFICATION_READY:
construct_chunk_grid();
synchronous_generate_terrain();
return;
case NOTIFICATION_PROCESS:
update_meshes();
update_threads();
return;
case NOTIFICATION_EXIT_TREE:
this->workload_lock.lock();
this->threads_stop = true;
this->workload_lock.unlock();
for (Thread &thread : this->threads) {
if (thread.is_started()) {
thread.wait_to_finish();
}
}
return;
}
}
void Terrain::generate_meshes_thread(void *terrain) {
Terrain *self{ static_cast<Terrain *>(terrain) };
print_line("thread", Thread::get_caller_id(), "start");
for (;;) {
self->workload_lock.lock();
if (self->threads_stop) {
self->workload_lock.unlock();
print_line(Thread::get_caller_id(), "exiting");
break;
}
if (self->workload.is_empty()) {
self->workload_lock.unlock();
Thread::yield();
continue;
}
TerrainChunkMesh *mesh{ self->workload[0] };
self->workload.remove_at(0);
self->workload_lock.unlock();
if (!mesh->is_inside_tree()) {
print_line(Thread::get_caller_id(), "mesh is outside tree, exiting");
break;
}
mesh->update_mesh();
Thread::yield();
}
print_line(Thread::get_caller_id(), "done");
return;
}
void Terrain::construct_chunk_grid() {
this->workload_lock.lock();
this->threads_stop = true;
this->workload_lock.unlock();
for (Thread &thread : this->threads) {
if (thread.is_started()) {
thread.wait_to_finish();
}
}
this->workload_lock.lock();
for (TerrainChunkMesh *mesh : this->meshes) {
remove_child(mesh);
mesh->queue_free();
}
size_t const chunks_per_side{ this->side_length / this->chunk_size };
Vector3 const origin{ (float)this->chunk_size / 2.f, 0.f, (float)this->chunk_size / 2.f };
this->meshes.clear();
this->workload.clear();
for (size_t y{ 0 }; y < chunks_per_side; ++y) {
for (size_t x{ 0 }; x < chunks_per_side; ++x) {
TerrainChunkMesh *chunk{ memnew(TerrainChunkMesh) };
chunk->set_size(this->chunk_size);
chunk->set_detail(this->detail);
chunk->set_terrain(this);
chunk->set_material_override(this->mesh_material);
chunk->set_position(origin + Vector3{ (float)this->chunk_size * (float)x, 0.f, (float)this->chunk_size * (float)y });
add_child(chunk);
chunk->set_owner(this);
this->meshes.push_back(chunk);
this->workload.push_back(chunk);
}
}
this->threads_stop = false;
this->dirty_meshes.clear();
this->workload_lock.unlock();
for (Thread &thread : this->threads) {
thread.start(&self_type::generate_meshes_thread, this);
}
}
float Terrain::height_at(Vector2 world_coordinate) {
float height{ 0 };
for (TerrainModifier *mod : this->modifiers) {
if (!mod->is_inside_tree()) {
return height;
}
height = mod->evaluate_at(world_coordinate, height);
}
return height;
}
void Terrain::push_changed(Rect2 area) {
for (TerrainChunkMesh *mesh : this->meshes) {
this->workload_lock.lock();
if (area.intersects(mesh->get_bounds()) && !this->workload.has(mesh)) {
workload.push_back(mesh);
}
this->workload_lock.unlock();
}
}
void Terrain::mesh_dirty(TerrainChunkMesh *mesh) {
this->dirty_meshes_lock.lock();
this->dirty_meshes.push_back(mesh);
callable_mp(cast_to<Node>(this), &self_type::set_process).call_deferred(true);
this->dirty_meshes_lock.unlock();
}
void Terrain::set_mesh_material(Ref<Material> material) {
this->mesh_material = material;
for (TerrainChunkMesh *mesh : this->meshes) {
mesh->set_material_override(material);
}
}
Ref<Material> Terrain::get_mesh_material() const {
return this->mesh_material;
}
void Terrain::set_side_length(size_t length) {
this->side_length = length;
if (is_inside_tree()) {
construct_chunk_grid();
}
}
size_t Terrain::get_side_length() const {
return this->side_length;
}
void Terrain::set_chunk_size(size_t size) {
this->chunk_size = size;
if (is_inside_tree()) {
construct_chunk_grid();
}
}
size_t Terrain::get_chunk_size() const {
return this->chunk_size;
}
void Terrain::set_detail(size_t detail) {
this->detail = detail;
if (is_inside_tree()) {
construct_chunk_grid();
}
}
size_t Terrain::get_detail() const {
return this->detail;
}
void Terrain::set_thread_count(size_t num) {
this->workload_lock.lock();
this->threads_stop = true;
this->workload_lock.unlock();
for (Thread &thread : this->threads) {
thread.wait_to_finish();
}
this->threads_stop = false;
this->threads.resize_initialized(num);
for (Thread &thread : this->threads) {
thread.start(&self_type::generate_meshes_thread, this);
}
}
size_t Terrain::get_thread_count() const {
return this->threads.size();
}

58
modules/terrain/terrain.h Normal file
View file

@ -0,0 +1,58 @@
#pragma once
#include "core/math/rect2.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
#include "core/templates/vector.h"
#include "scene/main/node.h"
#include "scene/resources/material.h"
class TerrainChunkMesh;
class TerrainModifier;
class Terrain : public Node {
GDCLASS(Terrain, Node);
static void _bind_methods();
void child_order_changed();
void update_meshes();
void update_threads();
void synchronous_generate_terrain();
protected:
void _notification(int what);
static void generate_meshes_thread(void *terrain);
public:
void construct_chunk_grid();
float height_at(Vector2 world_coordinate);
void push_changed(Rect2 area);
void mesh_dirty(TerrainChunkMesh *mesh);
private:
Ref<Material> mesh_material{};
Vector<TerrainChunkMesh *> workload{};
bool threads_stop{ false };
Mutex workload_lock;
Vector<TerrainChunkMesh *> dirty_meshes{};
Mutex dirty_meshes_lock{};
Vector<TerrainChunkMesh *> meshes{};
Vector<TerrainModifier *> modifiers{};
LocalVector<Thread> threads{};
size_t side_length{ 200 };
size_t chunk_size{ 50 };
size_t detail{ 1 };
public:
void set_mesh_material(Ref<Material> material);
Ref<Material> get_mesh_material() const;
void set_side_length(size_t length);
size_t get_side_length() const;
void set_chunk_size(size_t size);
size_t get_chunk_size() const;
void set_detail(size_t detail);
size_t get_detail() const;
void set_thread_count(size_t num);
size_t get_thread_count() const;
};

View file

@ -0,0 +1,127 @@
#include "terrain_chunk.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
#include "core/variant/variant.h"
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/3d/physics/static_body_3d.h"
#include "scene/resources/3d/height_map_shape_3d.h"
#include "scene/resources/surface_tool.h"
#include "terrain/terrain.h"
void TerrainChunkMesh::_bind_methods() {}
void TerrainChunkMesh::ready() {
this->position_buffer = get_global_position();
float const sizef{ (float)get_size() };
this->bounds.position = { this->position_buffer.x - sizef / 2.f, this->position_buffer.z - sizef / 2.f };
this->bounds.size = { sizef, sizef };
add_child(this->body = memnew(StaticBody3D));
ERR_FAIL_COND_EDMSG(this->body == nullptr, "Failed to instantiate StaticBody3D");
this->body->add_child(this->collider = memnew(CollisionShape3D));
this->body->set_owner(this);
ERR_FAIL_COND_EDMSG(this->collider == nullptr, "Failed to instantiate CollisionShape3D");
this->collider->set_owner(this);
this->shape = memnew(HeightMapShape3D);
ERR_FAIL_COND_EDMSG(!this->shape.is_valid(), "Failed to instantiate HeightMapShape3D");
this->collider->set_shape(this->shape);
this->shape->set_map_depth(heightmap_side_length());
this->shape->set_map_width(heightmap_side_length());
}
void TerrainChunkMesh::generate_vertices() {
ERR_FAIL_COND_EDMSG(this->terrain == nullptr, "TerrainChunkMesh::generate_vertices: no terrain assigned");
ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate_vertices: size <= 0");
ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate_vertices: points per side <= 0");
float const half_extent{ (float)this->size / 2.f };
float const point_distance{ (float)this->size / ((float)points_per_side() - 1) };
Vector3 origin{ this->position_buffer - Vector3{ half_extent, 0, half_extent } };
for (size_t x{ 0 }; x < points_per_side(); ++x) {
for (size_t y{ 0 }; y < points_per_side(); ++y) {
Vector2 const coordinate{ origin.x + point_distance * x, origin.z + point_distance * y };
this->surface->set_uv({ (float)x / (float)points_per_side(), (float)y / (float)points_per_side() });
float height{ this->terrain->height_at(coordinate) };
this->surface->add_vertex({ coordinate.x - this->position_buffer.x, height, coordinate.y - this->position_buffer.z });
}
}
this->heightmap.resize_initialized(heightmap_side_length() * heightmap_side_length());
for (size_t x{ 0 }; x < heightmap_side_length(); x++) {
for (size_t y{ 0 }; y < heightmap_side_length(); y++) {
Vector2 coordinate{ origin.x + x, origin.z + y };
this->heightmap.set(x + y * heightmap_side_length(), this->terrain->height_at(coordinate));
}
}
}
void TerrainChunkMesh::generate_faces() {
LocalVector<SurfaceTool::Vertex> &verts{ this->surface->get_vertex_array() };
ERR_FAIL_COND_EDMSG(verts.size() == 0, "TerrainChunkMesh::generate_faces: no vertices in surface, call generate_vertices first");
size_t const faces_per_side{ points_per_side() - 1 };
for (size_t x{ 0 }; x < faces_per_side; ++x) {
for (size_t y{ 0 }; y < faces_per_side; ++y) {
size_t const tl{ x + y * points_per_side() };
float tl_br{ verts[tl].vertex.distance_to(verts[tl + points_per_side() + 1].vertex) };
float tr_bl{ verts[tl + 1].vertex.distance_to(verts[tl + points_per_side()].vertex) };
if (tl_br < tr_bl) {
surface->add_index(tl);
surface->add_index(tl + points_per_side() + 1);
surface->add_index(tl + 1);
surface->add_index(tl);
surface->add_index(tl + points_per_side());
surface->add_index(tl + points_per_side() + 1);
} else {
surface->add_index(tl + points_per_side());
surface->add_index(tl + points_per_side() + 1);
surface->add_index(tl + 1);
surface->add_index(tl + 1);
surface->add_index(tl);
surface->add_index(tl + points_per_side());
}
}
}
}
void TerrainChunkMesh::_notification(int what) {
switch (what) {
default:
return;
case NOTIFICATION_READY:
ready();
return;
}
}
void TerrainChunkMesh::apply_new_mesh() {
this->lock.lock();
set_mesh(this->new_mesh);
if (this->shape->get_map_depth() != heightmap_side_length() || this->shape->get_map_width() != heightmap_side_length()) {
this->shape->set_map_depth(heightmap_side_length());
this->shape->set_map_width(heightmap_side_length());
}
this->shape->set_map_data(heightmap);
this->lock.unlock();
}
void TerrainChunkMesh::update_mesh() {
ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate: size <= 0");
ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate: points per side <= 0");
this->lock.lock();
this->surface = memnew(SurfaceTool);
this->surface->begin(Mesh::PRIMITIVE_TRIANGLES);
generate_vertices();
generate_faces();
this->surface->generate_normals();
this->surface->generate_tangents();
this->new_mesh = memnew(ArrayMesh);
this->surface->commit(this->new_mesh);
this->lock.unlock();
this->terrain->mesh_dirty(this);
}
size_t TerrainChunkMesh::points_per_side() const {
return this->size * this->detail;
}
int TerrainChunkMesh::heightmap_side_length() const {
return get_size() + 1;
}

View file

@ -0,0 +1,49 @@
#pragma once
#include "core/math/rect2.h"
#include "macros.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/3d/physics/static_body_3d.h"
#include "scene/resources/3d/height_map_shape_3d.h"
#include "scene/resources/mesh.h"
#include "scene/resources/surface_tool.h"
class Terrain;
class TerrainChunkMesh : public MeshInstance3D {
GDCLASS(TerrainChunkMesh, MeshInstance3D);
static void _bind_methods();
void ready();
void generate_vertices();
void generate_faces();
protected:
void _notification(int what);
public:
void apply_new_mesh();
void update_mesh();
size_t points_per_side() const;
int heightmap_side_length() const;
private:
Mutex lock{};
Vector3 position_buffer{};
Ref<SurfaceTool> surface{};
Ref<ArrayMesh> new_mesh{};
Terrain *terrain{ nullptr };
size_t detail{ 1 };
size_t size{ 1 };
Rect2 bounds{};
Ref<HeightMapShape3D> shape{};
StaticBody3D *body{ nullptr };
CollisionShape3D *collider{ nullptr };
Vector<real_t> heightmap{};
public:
GET_SET_FNS(Rect2, bounds);
GET_SET_FNS(Terrain *, terrain);
GET_SET_FNS(size_t, detail);
GET_SET_FNS(size_t, size);
};

View file

@ -0,0 +1,417 @@
#include "terrain_modifier.h"
#include "core/config/engine.h"
#include "core/math/math_funcs.h"
#include "core/variant/variant.h"
#include "macros.h"
#include "scene/main/node.h"
#include "terrain/terrain.h"
#include <algorithm>
void TerrainModifier::_bind_methods() {}
void TerrainModifier::_notification(int what) {
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
if (Engine::get_singleton()->is_editor_hint()) {
set_notify_transform(true);
}
this->thread_safe_global_position = get_global_position();
case NOTIFICATION_TRANSFORM_CHANGED:
this->thread_safe_global_position = get_global_position();
return;
}
}
void TerrainModifier::push_changed(Rect2 area) {
if (this->terrain) {
this->terrain->push_changed(area);
}
}
float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) {
Vector3 const global_position{ get_thread_safe_global_position() };
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 SharedMutex::lock_shared() {
this->lock.lock();
this->shared_count++;
this->lock.unlock();
}
void SharedMutex::unlock_shared() {
this->lock.lock();
this->shared_count--;
this->lock.unlock();
}
void SharedMutex::lock_exclusive() {
while (true) {
this->lock.lock();
if (this->shared_count == 0) {
return;
}
this->lock.unlock();
}
}
void SharedMutex::unlock_exclusive() {
this->lock.unlock();
}
void TerrainModifierDistance::_bind_methods() {
BIND_HPROPERTY(Variant::OBJECT, distance_weight_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
}
void TerrainModifierDistance::curves_changed() {
if (!update_bounds()) {
push_changed(get_bounds());
}
this->lock.lock_exclusive();
this->distance_weight_curve_buffer = this->distance_weight_curve.is_valid() ? this->distance_weight_curve->duplicate(true) : nullptr;
this->lock.unlock_exclusive();
}
bool TerrainModifierDistance::update_bounds() {
Rect2 const before{ get_bounds() };
Rect2 bounds{};
Vector3 position{ get_thread_safe_global_position() };
bounds.position = { position.x, position.z };
bounds.size = { 0, 0 };
this->lock.lock_shared();
if (this->distance_weight_curve.is_valid()) {
float const max_radius{ this->distance_weight_curve->get_max_domain() };
float const max_diameter{ 2.f * max_radius };
bounds.size = { max_diameter, max_diameter };
bounds.position -= { max_radius, max_radius };
}
bool const changed{ before != bounds };
this->lock.unlock_shared();
this->lock.lock_exclusive();
set_bounds(bounds);
this->lock.unlock_exclusive();
return changed;
}
void TerrainModifierDistance::_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 TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) {
Vector3 const global_position{ get_thread_safe_global_position() };
return world_coordinate.distance_to({ global_position.x, global_position.z });
}
float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) {
this->lock.lock_shared();
if (this->distance_weight_curve_buffer.is_null()) {
this->lock.unlock_shared();
return before;
}
float const distance{ distance_at(world_coordinate) };
if (distance >= this->distance_weight_curve_buffer->get_max_domain()) {
this->lock.unlock_shared();
return before;
}
float const weight_offset{ std::clamp(distance, this->distance_weight_curve_buffer->get_min_domain(), this->distance_weight_curve_buffer->get_max_domain()) };
float const weight{ this->distance_weight_curve_buffer->sample(weight_offset) };
float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) };
this->lock.unlock_shared();
return out;
}
PackedStringArray TerrainModifierDistance::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (this->distance_weight_curve.is_null()) {
warnings.push_back("distance_weight_curve is invalid, add a valid distance_weight_curve");
}
return warnings;
}
void TerrainModifierDistance::set_distance_weight_curve(Ref<Curve> curve) {
this->lock.lock_exclusive();
if (Engine::get_singleton()->is_editor_hint()) {
if (this->distance_weight_curve.is_valid()) {
this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed));
}
if (curve.is_valid()) {
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
}
}
this->distance_weight_curve = curve;
this->lock.unlock_exclusive();
curves_changed();
update_configuration_warnings();
}
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:
this->path = cast_to<TerrainModifierPath>(get_parent());
return;
case NOTIFICATION_READY:
set_notify_transform(true);
if (this->path) {
this->path->path_changed();
}
return;
case NOTIFICATION_TRANSFORM_CHANGED:
if (this->path && is_inside_tree()) {
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());
}
this->lock.lock_exclusive();
this->curve_left_buffer = this->curve_left.is_valid() ? this->curve_left->duplicate(true) : nullptr;
this->curve_right_buffer = this->curve_right.is_valid() ? this->curve_right->duplicate(true) : nullptr;
this->lock.unlock_exclusive();
}
bool TerrainModifierPath::update_bounds() {
Vector2 min{}, max{};
this->lock.lock_shared();
if (this->points.is_empty() || this->curve_left.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{ max_distance_left };
if (this->curve_right.is_valid()) {
float max_distance_right{ this->curve_right->get_max_domain() };
max_distance = max_distance_right > max_distance ? max_distance_right : max_distance;
}
min -= { max_distance, max_distance };
max += { max_distance * 2.f, max_distance * 2.f };
}
Rect2 bounds{ min, max - min };
bool const changed{ bounds != get_bounds() };
this->lock.unlock_shared();
this->lock.lock_exclusive();
set_bounds(bounds);
this->lock.unlock_exclusive();
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;
case NOTIFICATION_CHILD_ORDER_CHANGED:
path_changed();
return;
}
}
float TerrainModifierPath::evaluate_line(Vector3 a, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance, float &out_percentage) {
Vector2 a2{ a.x, a.z }, b2{ b.x, b.z };
Vector2 const relative_coordinate{ world_coordinate - a2 };
Vector2 const difference2{ b2 - a2 };
float 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 });
out_percentage = w;
return a.y + (b.y - a.y) * w;
}
float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
this->lock.lock_shared();
if (this->curve_left_buffer.is_null() || this->points.size() <= 1) {
this->lock.unlock_shared();
return before;
}
Ref<Curve> right_curve{ this->curve_right_buffer };
if (right_curve.is_null()) {
right_curve = this->curve_left_buffer;
}
float out_height{ before };
long const count{ this->closed ? this->points.size() : this->points.size() - 1 };
for (int i{ 0 }; i < count; i++) {
Vector3 const ipos{ this->points[i] };
Vector3 const next_pos{ this->points[Math::wrapi(i + 1, 0, this->points.size())] };
if (ipos == next_pos) {
continue;
}
float dot, distance, percentage;
bool const is_start{ !this->closed && i == 0 }, is_end{ !this->closed && i == count - 1 };
float height{ evaluate_line(ipos, is_start, next_pos, is_end, world_coordinate, dot, distance, percentage) };
float const left{ this->curve_left_buffer->sample(distance) };
float const right{ right_curve->sample(distance) };
float const ndot{ dot / distance };
float separation{ ndot / 2.f + 0.5f };
if (percentage > 0.f && percentage < 1.f) {
separation = Math::round(separation);
}
float const weight{ left * (1.f - separation) + right * separation };
float const blended_height{ Math::lerp(out_height, height, weight) };
out_height = blended_height;
}
this->lock.unlock_shared();
return out_height;
}
void TerrainModifierPath::path_changed() {
if (!is_inside_tree()) {
return;
}
this->lock.lock_exclusive();
this->points.clear();
this->min_height = INFINITY;
this->max_height = -INFINITY;
Vector3 last{ INFINITY, INFINITY, INFINITY };
for (Variant var : get_children()) {
if (TerrainModifierPathPoint * point{ cast_to<TerrainModifierPathPoint>(var) }) {
Vector3 position{ point->get_global_position() };
if (position != last) {
this->points.push_back(position);
if (position.y > this->max_height) {
this->max_height = position.y;
}
if (position.y < this->min_height) {
this->min_height = position.y;
}
}
}
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("curve_left is invalid, add a valid curve_left");
}
return warnings;
}
void TerrainModifierPath::set_curve_left(Ref<Curve> curve) {
this->lock.lock_exclusive();
if (curve.is_valid() && curve == this->curve_right) {
curve = curve->duplicate();
}
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();
if (!curve.is_valid() && this->curve_right.is_valid()) {
curve = this->curve_right;
set_curve_right(nullptr);
} else {
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 (curve.is_valid() && curve == this->curve_left) {
curve = curve->duplicate();
}
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;
}

View file

@ -0,0 +1,113 @@
#pragma once
#include "core/object/object.h"
#include "core/variant/variant.h"
#include "macros.h"
#include "scene/3d/marker_3d.h"
#include "scene/resources/curve.h"
#include <cmath>
class Terrain;
class TerrainModifier : public Marker3D {
GDCLASS(TerrainModifier, Marker3D);
static void _bind_methods();
protected:
void _notification(int what);
void push_changed(Rect2 bounds);
public:
virtual float evaluate_at(Vector2 world_coordinate, float before);
private:
Vector3 thread_safe_global_position{};
Terrain *terrain{ nullptr };
Rect2 bounds{ { -INFINITY, -INFINITY }, { INFINITY, INFINITY } };
protected:
void set_bounds(Rect2 bounds);
Rect2 get_bounds() const;
public:
Vector3 get_thread_safe_global_position() const;
GET_SET_FNS(Terrain *, terrain);
};
struct SharedMutex {
void lock_shared();
void unlock_shared();
void lock_exclusive();
void unlock_exclusive();
private:
Mutex lock{};
int shared_count{};
};
class TerrainModifierDistance : public TerrainModifier {
GDCLASS(TerrainModifierDistance, TerrainModifier);
static void _bind_methods();
void curves_changed();
bool update_bounds();
protected:
void _notification(int what);
float distance_at(Vector2 const &world_coordinate);
public:
float evaluate_at(Vector2 world_coordinate, float before) override;
PackedStringArray get_configuration_warnings() const override;
private:
SharedMutex lock{};
Ref<Curve> distance_weight_curve{};
Ref<Curve> distance_weight_curve_buffer{};
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, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance, float &out_percentage);
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_buffer{};
Ref<Curve> curve_left{};
Ref<Curve> curve_right_buffer{};
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;
};

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://grb3q5nd2uds"
path="res://.godot/imported/character_fem.blend-e169cb46816e89cf00aa8e7f988a0574.scn"
[deps]
source_file="res://assets/characters/player_fem/character_fem.blend"
dest_files=["res://.godot/imported/character_fem.blend-e169cb46816e89cf00aa8e7f988a0574.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://biqq268lccpng"
path.s3tc="res://.godot/imported/Face.png-08c0111f3b71fa077c35243a4c740f6e.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/characters/player_fem/textures/Face.png"
dest_files=["res://.godot/imported/Face.png-08c0111f3b71fa077c35243a4c740f6e.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cwqc2g4616eun"
path.s3tc="res://.godot/imported/Texture.png-98af1a158e1830cbcd0a13178176c442.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/characters/player_fem/textures/Texture.png"
dest_files=["res://.godot/imported/Texture.png-98af1a158e1830cbcd0a13178176c442.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bsdvnyn6nhiaa"
path="res://.godot/imported/character_masc.blend-0037c94467e2e85d7ec685a838d7e95d.scn"
[deps]
source_file="res://assets/characters/player_masc/character_masc.blend"
dest_files=["res://.godot/imported/character_masc.blend-0037c94467e2e85d7ec685a838d7e95d.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b7n0rgtlub4r7"
path.s3tc="res://.godot/imported/Face.png-f5f833f7c71137a9e4aac5fe268e4dd4.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/characters/player_masc/textures/Face.png"
dest_files=["res://.godot/imported/Face.png-f5f833f7c71137a9e4aac5fe268e4dd4.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://lea3dgv2585y"
path.s3tc="res://.godot/imported/Texture.png-51f4b86d2d244a13f05f399cc30a0d6b.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/characters/player_masc/textures/Texture.png"
dest_files=["res://.godot/imported/Texture.png-51f4b86d2d244a13f05f399cc30a0d6b.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dw4p3s74f1pdg"
path="res://.godot/imported/cliffs_blockout.blend-f86a374f2c48645fd5614df18445a45a.scn"
[deps]
source_file="res://assets/environments/blockouts/cliffs_blockout.blend"
dest_files=["res://.godot/imported/cliffs_blockout.blend-f86a374f2c48645fd5614df18445a45a.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dwmur2qflotv6"
path.bptc="res://.godot/imported/terrain.png-52ea7eaf6b989ed8d9fc2c0c5a398fdf.bptc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/environments/blockouts/terrain.png"
dest_files=["res://.godot/imported/terrain.png-52ea7eaf6b989ed8d9fc2c0c5a398fdf.bptc.ctex"]
[params]
compress/mode=2
compress/high_quality=true
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dyt1mwbep2012"
path="res://.godot/imported/flower.blend-a54f993c4db9e1bce792f272b6b1a837.scn"
[deps]
source_file="res://assets/environments/props/flower.blend"
dest_files=["res://.godot/imported/flower.blend-a54f993c4db9e1bce792f272b6b1a837.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://uujfed6yrp8p"
path.s3tc="res://.godot/imported/grass_a.png-df3280112d606c2f3fb6a8ca84baa85d.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/environments/props/grass_a.png"
dest_files=["res://.godot/imported/grass_a.png-df3280112d606c2f3fb6a8ca84baa85d.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://db6ddpj53gl5w"
path="res://.godot/imported/rock_a.blend-b8d8f34d9e140f1b8adb493b605dd07e.scn"
[deps]
source_file="res://assets/environments/props/rock_a.blend"
dest_files=["res://.godot/imported/rock_a.blend-b8d8f34d9e140f1b8adb493b605dd07e.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b4rklg0l7aqdm"
path.s3tc="res://.godot/imported/rock_a.png-3ff1a5814ef260ae524170318024cca4.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/environments/props/rock_a.png"
dest_files=["res://.godot/imported/rock_a.png-3ff1a5814ef260ae524170318024cca4.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://517yqaw110pf"
path="res://.godot/imported/rock_b.blend-d76791249d4c14d1c951b842f0208090.scn"
[deps]
source_file="res://assets/environments/props/rock_b.blend"
dest_files=["res://.godot/imported/rock_b.blend-d76791249d4c14d1c951b842f0208090.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dpn3sfcjnnool"
path.s3tc="res://.godot/imported/rock_b.png-063dafa9684f12d7486304024d1aaecf.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/environments/props/rock_b.png"
dest_files=["res://.godot/imported/rock_b.png-063dafa9684f12d7486304024d1aaecf.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://8n8fduklxduk"
path="res://.godot/imported/rock_c.blend-26ab78e8b85061bd62ef5331656df04c.scn"
[deps]
source_file="res://assets/environments/props/rock_c.blend"
dest_files=["res://.godot/imported/rock_c.blend-26ab78e8b85061bd62ef5331656df04c.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drf6dro5di2ap"
path.s3tc="res://.godot/imported/rock_c.png-a276885ef75cf615c39e79d768211422.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/environments/props/rock_c.png"
dest_files=["res://.godot/imported/rock_c.png-a276885ef75cf615c39e79d768211422.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

View file

@ -0,0 +1,60 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bnm0go8negvo7"
path="res://.godot/imported/tree.blend-20cb45b772c5003289278d8ec4060a00.scn"
[deps]
source_file="res://assets/environments/props/tree.blend"
dest_files=["res://.godot/imported/tree.blend-20cb45b772c5003289278d8ec4060a00.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path="uid://ba7qlhj5ylm3d"
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/gpu_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true
gltf/naming_version=2

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View file

@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cvfsvh8tdpflf"
path.bptc="res://.godot/imported/tree.png-b3baca9a70a35e7b7075603680fb265f.bptc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/environments/props/tree.png"
dest_files=["res://.godot/imported/tree.png-b3baca9a70a35e7b7075603680fb265f.bptc.ctex"]
[params]
compress/mode=2
compress/high_quality=true
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -0,0 +1,7 @@
[gd_resource type="FastNoiseLite" format=3 uid="uid://xxd1cr8y6ooh"]
[resource]
noise_type = 4
seed = 3
frequency = 0.0086
fractal_gain = 1.0

View file

@ -0,0 +1,16 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://cd4vnmrmj8cj7"]
[resource]
transparency = 2
alpha_scissor_threshold = 0.5
alpha_antialiasing_mode = 0
cull_mode = 1
shading_mode = 0
diffuse_mode = 3
specular_mode = 2
vertex_color_use_as_albedo = true
albedo_color = Color(0.121152334, 0.121152334, 0.121152334, 1)
grow = true
grow_amount = 0.015
proximity_fade_distance = 0.1
stencil_outline_thickness = 0.029

View file

@ -0,0 +1,17 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://02s3lq67141v"]
[resource]
transparency = 2
alpha_scissor_threshold = 0.94
alpha_antialiasing_mode = 0
cull_mode = 1
shading_mode = 0
diffuse_mode = 3
specular_mode = 2
vertex_color_use_as_albedo = true
albedo_color = Color(0.121152334, 0.121152334, 0.121152334, 1)
grow = true
grow_amount = 0.015
proximity_fade_enabled = true
proximity_fade_distance = 0.15
stencil_outline_thickness = 0.029

View file

@ -0,0 +1,36 @@
@tool
extends EditorScenePostImport
var regular_outline_material : StandardMaterial3D
var detail_outline_material : StandardMaterial3D
var thin_outline_material : StandardMaterial3D
func _post_import(root : Node):
regular_outline_material = ResourceLoader.load("res://assets/style/base_outline_material.tres") as StandardMaterial3D
detail_outline_material = ResourceLoader.load("res://assets/style/detail_outline_material.tres") as StandardMaterial3D
thin_outline_material = ResourceLoader.load("res://assets/style/thin_outline_material.tres") as StandardMaterial3D
apply_outline_recursive(root)
return root
func get_flag(node : Node, flag : String) -> bool:
if node.name.contains(flag):
node.name = node.name.erase(node.name.find(flag), flag.length())
return true
else:
return false
func apply_outline_recursive(node : Node):
if node != null:
var outline : bool = not get_flag(node, "-nooutline")
if outline and node is MeshInstance3D:
var detail : bool = get_flag(node, "-detailoutline")
var thin : bool = get_flag(node, "-thinoutline")
var mesh : MeshInstance3D = (node as MeshInstance3D)
if detail and detail_outline_material:
mesh.material_overlay = detail_outline_material
elif thin and thin_outline_material:
mesh.material_overlay = thin_outline_material
elif regular_outline_material:
mesh.material_overlay = regular_outline_material
for child in node.get_children():
apply_outline_recursive(child)

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