First commit

This commit is contained in:
Lucas 2025-06-27 14:07:21 +02:00
commit 4eef953ed6
No known key found for this signature in database
26 changed files with 771 additions and 0 deletions

4
.editorconfig Normal file
View file

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025-present VitSoonYoung - <vitsoonyoung+simpleformatonsave@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,10 @@
class_name Formatter
const RuleSpacing = preload("./rules/spacing.gd")
const RuleBlankLines = preload("./rules/blank_lines.gd")
func format_code(code: String) -> String:
code = RuleSpacing.apply(code)
code = RuleBlankLines.apply(code)
return code

View file

@ -0,0 +1 @@
uid://clqnxlippxlm4

View file

@ -0,0 +1,7 @@
[plugin]
name="Simple Format On Save"
description="Combination of simple gdscript formatter & Format On Save with extra rules for beatiful formatting...maybe"
author="VitSoonYoung"
version="0.1"
script="simple_format_on_save.gd"

View file

@ -0,0 +1,65 @@
class_name RuleBlankLines
var FORMAT_ACTION := "simple_format_on_save/format_code"
var format_key: InputEventKey
static func apply(code: String) -> String:
var trim1_regex = RegEx.create_from_string("\n{2,}")
code = trim1_regex.sub(code, "\n\n", true)
code = _blank_for_func_class(code)
var trim2_regex = RegEx.create_from_string("\n{3,}")
code = trim2_regex.sub(code, "\n\n\n", true)
return code
static func _blank_for_func_class(code: String) -> String:
var assignment_regex = RegEx.create_from_string(r".*=.*")
var statement_regex = RegEx.create_from_string(r"\s+(if|for|while|match)[\s|\(].*")
var misc_statement_regex = RegEx.create_from_string(r"\s+(else|elif|\}|\]).*")
var func_class_regex = RegEx.create_from_string(r".*(func|class) .*")
var comment_line_regex = RegEx.create_from_string(r"^\s*#")
var empty_line_regex = RegEx.create_from_string(r"^\s+$")
var lines := code.split('\n')
var modified_lines: Array[String] = []
for line: String in lines:
# Spaces between functions & classes
if func_class_regex.search(line):
if modified_lines.size() > 0:
var i := modified_lines.size() - 1
while i > 0 and comment_line_regex.search(modified_lines[i]):
i -= 1
if i == 0:
modified_lines.append(line)
continue
modified_lines.insert(i + 1, "")
modified_lines.insert(i + 1, "")
# 1 space between assignment & statement
if statement_regex.search(line):
if modified_lines.size() > 0:
var i := modified_lines.size() - 1
if assignment_regex.search(modified_lines[i]) and not statement_regex.search(modified_lines[i]):
modified_lines.insert(i + 1, "")
else:
pass
# Space after a code block (Doesn't work with spaces for now)
var indent_count := line.count("\t")
if indent_count and not misc_statement_regex.search(line):
if modified_lines.size() > 0:
var i := modified_lines.size() - 1
if modified_lines[i].count("\t") > indent_count:
modified_lines.insert(i + 1, "")
modified_lines.append(line)
return "\n".join(modified_lines)

View file

@ -0,0 +1 @@
uid://dr266abkfhg1o

View file

@ -0,0 +1,155 @@
class_name RuleSpacing
const SYMBOLS = [
r"\*\*=",
r"\*\*",
"<<=",
">>=",
"<<",
">>",
"==",
"!=",
">=",
"<=",
"&&",
r"\|\|",
r"\+=",
"-=",
r"\*=",
"/=",
"%=",
"&=",
r"\^=",
r"\|=",
"~=",
":=",
"->",
r"&",
r"\|",
r"\^",
"-",
r"\+",
"/",
r"\*",
">",
"<",
"-",
"%",
"=",
":",
",",
];
const KEYWORDS = [
"and",
"is",
"or",
"not",
]
static func apply(code: String) -> String:
var string_regex = RegEx.new()
string_regex.compile(r'(?<!#)("""|\'\'\'|"|\')((?:.|\n)*?)\1')
var string_matches = string_regex.search_all(code)
var string_map = {}
for i in range(string_matches.size()):
var match = string_matches[i]
var original = match.get_string()
var placeholder = "__STRING__%d__" % i
string_map[placeholder] = original
code = _replace(code, original, placeholder)
var comment_regex = RegEx.new()
comment_regex.compile("#.*")
var comment_matches = comment_regex.search_all(code)
var comment_map = {}
for i in range(comment_matches.size()):
var match = comment_matches[i]
var original = match.get_string()
var placeholder = "__COMMENT__%d__" % i
comment_map[placeholder] = original
code = _replace(code, original, placeholder)
var ref_regex = RegEx.new()
ref_regex.compile(r"\$[^.]*")
var ref_matches = ref_regex.search_all(code)
var ref_map = {}
for i in range(ref_matches.size()):
var match = ref_matches[i]
var original = match.get_string()
var placeholder = "__REF__%d__" % i
ref_map[placeholder] = original
code = _replace(code, original, placeholder)
code = _format_operators_and_commas(code)
for placeholder in ref_map:
code = code.replace(placeholder, ref_map[placeholder])
for placeholder in comment_map:
code = code.replace(placeholder, comment_map[placeholder])
for placeholder in string_map:
code = code.replace(placeholder, string_map[placeholder])
return code
static func _format_operators_and_commas(code: String) -> String:
var indent_regex = RegEx.create_from_string(r"^\s{4}")
var new_code = indent_regex.sub(code, "\t", true)
while (code != new_code):
code = new_code
new_code = indent_regex.sub(code, "\t", true)
var symbols_regex = "(" + ") | (".join(SYMBOLS) + ")"
symbols_regex = " * ?(" + symbols_regex + ") * "
var symbols_operator_regex = RegEx.create_from_string(symbols_regex)
code = symbols_operator_regex.sub(code, " $1 ", true)
# ": =" => ":="
code = RegEx.create_from_string(r": *=").sub(code, ":=", true)
# "a(" => "a ("
code = RegEx.create_from_string(r"(?<=[\w\)\]]) *([\(:,])(?!=)").sub(code, "$1", true)
# "( a" => "(a"
code = RegEx.create_from_string(r"([\(\{}]) *").sub(code, "$1", true)
# "a )" => "a)"
code = RegEx.create_from_string(r" *([\)\}])").sub(code, "$1", true)
# "if(" => "if ("
code = RegEx.create_from_string(r"\b(if|for|while|switch|match)\(").sub(code, "$1 (", true)
var keywoisrd_regex = r"|".join(KEYWORDS)
var keyword_operator_regex = RegEx.create_from_string(r"(?<=[ \)\]])(" + keywoisrd_regex + r")(?=[ \(\[])")
code = keyword_operator_regex.sub(code, " $1 ", true)
# tab "a\t=" => "a ="
code = RegEx.create_from_string(r"(\t*. * ?)\t * ").sub(code, "$1", true)
#trim
code = RegEx.create_from_string("[ \t]*\n").sub(code, "\n", true)
# " " => " "
code = RegEx.create_from_string(" +").sub(code, " ", true)
# "= -a" => "= -a"
code = RegEx.create_from_string(r"([=,(] ?)- ").sub(code, "$1-", true)
return code
static func _replace(text: String, what: String, forwhat: String) -> String:
var index := text.find(what)
if index != -1:
text = text.substr(0, index) + forwhat + text.substr(index + what.length())
return text

View file

@ -0,0 +1 @@
uid://wfodrj6vpfb6

View file

@ -0,0 +1,82 @@
@tool
extends EditorPlugin
var FORMAT_ACTION := "simple_format_on_save/format_code"
var format_key: InputEventKey
var formatter: Formatter
func _enter_tree():
add_tool_menu_item("Format (Ctrl+Alt+L)", _on_format_code)
if InputMap.has_action(FORMAT_ACTION):
InputMap.erase_action(FORMAT_ACTION)
InputMap.add_action(FORMAT_ACTION)
format_key = InputEventKey.new()
format_key.keycode = KEY_L
format_key.ctrl_pressed = true
format_key.alt_pressed = true
InputMap.action_add_event(FORMAT_ACTION, format_key)
resource_saved.connect(on_resource_saved)
func _exit_tree():
remove_tool_menu_item("Format (Ctrl+Alt+L)")
InputMap.erase_action(FORMAT_ACTION)
resource_saved.disconnect(on_resource_saved)
# Return true if formatted code != original code
func _on_format_code() -> bool:
var current_editor := EditorInterface.get_script_editor().get_current_editor()
if not (current_editor and current_editor.is_class("ScriptTextEditor")):
return false
var text_edit = current_editor.get_base_editor()
var code = text_edit.text
if not formatter:
formatter = Formatter.new()
var formatted_code = formatter.format_code(code)
if not formatted_code:
return false
if code.length() == formatted_code.length() and code == formatted_code:
return false
var scroll_horizontal = text_edit.scroll_horizontal
var scroll_vertical = text_edit.scroll_vertical
var caret_column = text_edit.get_caret_column(0)
var caret_line = text_edit.get_caret_line(0)
text_edit.text = formatted_code
text_edit.set_caret_line(caret_line)
text_edit.set_caret_column(caret_column)
text_edit.scroll_horizontal = scroll_horizontal
text_edit.scroll_vertical = scroll_vertical
return true
func _shortcut_input(event: InputEvent) -> void:
if event is InputEventKey and event.is_pressed():
if Input.is_action_pressed(FORMAT_ACTION):
_on_format_code()
# CALLED WHEN A SCRIPT IS SAVED
func on_resource_saved(resource: Resource):
if resource is Script:
var script: Script = resource
var current_script = get_editor_interface().get_script_editor().get_current_script()
# Prevents other unsaved scripts from overwriting the active one
if current_script == script:
var is_modified: bool = _on_format_code()
#if is_modified:
#print_rich("[color=#636363]Auto formatted code[/color]")

View file

@ -0,0 +1 @@
uid://b6d7va37k5131

1
icon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

43
icon.svg.import Normal file
View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cpufmd5631dpp"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
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=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

28
player/player.gd Normal file
View file

@ -0,0 +1,28 @@
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()

1
player/player.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://b5io1eg4blrp5

24
project.godot Normal file
View file

@ -0,0 +1,24 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="BoneForge"
run/main_scene="uid://bgofilfnbeisl"
config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg"
[editor_plugins]
enabled=PackedStringArray("res://addons/SimpleFormatOnSave/plugin.cfg")
[network]
limits/debugger/max_chars_per_second=99999999

24
scenes/chunk_generator.gd Normal file
View file

@ -0,0 +1,24 @@
extends Node
@export var chunks = 10
const value = 300
var TerrainHtread: Thread
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
TerrainHtread = Thread.new()
TerrainHtread.start(generate_chuks.bind(chunks,value,self))
func generate_chuks(_chunks,_value,parent):
for chunk in _chunks:
var checukn :Node3D= Node3D.new()
checukn.position = Vector3(randf_range(-_value,_value),0,randf_range(-_value,_value))
checukn.set_script(load("res://world_generation/grok_chnk.gd"))
parent.call_deferred("add_child",checukn)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _exit_tree() -> void:
TerrainHtread.wait_to_finish()

View file

@ -0,0 +1 @@
uid://e63lq34qepsg

29
scenes/world.tscn Normal file
View file

@ -0,0 +1,29 @@
[gd_scene load_steps=5 format=3 uid="uid://bgofilfnbeisl"]
[ext_resource type="Script" uid="uid://e63lq34qepsg" path="res://scenes/chunk_generator.gd" id="2_rwgxs"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_nnsk1"]
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
[sub_resource type="Sky" id="Sky_rwgxs"]
sky_material = SubResource("ProceduralSkyMaterial_nnsk1")
[sub_resource type="Environment" id="Environment_4wyf3"]
background_mode = 2
sky = SubResource("Sky_rwgxs")
tonemap_mode = 2
glow_enabled = true
[node name="world" type="Node3D"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_4wyf3")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
shadow_enabled = true
[node name="chunkGenerator" type="Node" parent="."]
script = ExtResource("2_rwgxs")
chunks = 2000

56
world_gen/main.tscn Normal file
View file

@ -0,0 +1,56 @@
[gd_scene load_steps=9 format=3 uid="uid://cioop0k7xmww8"]
[ext_resource type="Script" uid="uid://b4q4faaqbat0x" path="res://world_gen/world_generator.gd" id="1_y7qka"]
[ext_resource type="Script" uid="uid://b5io1eg4blrp5" path="res://player/player.gd" id="2_nwtrj"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_y7qka"]
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
[sub_resource type="Sky" id="Sky_nwtrj"]
sky_material = SubResource("ProceduralSkyMaterial_y7qka")
[sub_resource type="Environment" id="Environment_vrc1b"]
background_mode = 2
sky = SubResource("Sky_nwtrj")
tonemap_mode = 2
volumetric_fog_enabled = true
volumetric_fog_density = 0.01
[sub_resource type="FastNoiseLite" id="FastNoiseLite_nwtrj"]
noise_type = 3
seed = 280
frequency = 0.0035
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_y7qka"]
resource_name = "PlayerCollision"
[sub_resource type="CapsuleMesh" id="CapsuleMesh_nwtrj"]
resource_name = "PlayerMesh"
[node name="main" type="Node"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_vrc1b")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
shadow_enabled = true
[node name="WorldGenerator" type="Node" parent="."]
script = ExtResource("1_y7qka")
noise = SubResource("FastNoiseLite_nwtrj")
height = 24.0
[node name="Player" type="CharacterBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 104.43206, 0)
script = ExtResource("2_nwtrj")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
shape = SubResource("CapsuleShape3D_y7qka")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Player/CollisionShape3D"]
mesh = SubResource("CapsuleMesh_nwtrj")
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 0.9604348, 0.27850503, 0, -0.27850503, 0.9604348, 0, 1.8589027, 2.8465767)

View file

@ -0,0 +1,54 @@
[gd_scene load_steps=9 format=3 uid="uid://cioop0k7xmww8"]
[ext_resource type="Script" uid="uid://b4q4faaqbat0x" path="res://world_gen/world_generator.gd" id="1_y7qka"]
[ext_resource type="Script" uid="uid://b5io1eg4blrp5" path="res://player/player.gd" id="2_nwtrj"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_y7qka"]
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
[sub_resource type="Sky" id="Sky_nwtrj"]
sky_material = SubResource("ProceduralSkyMaterial_y7qka")
[sub_resource type="Environment" id="Environment_vrc1b"]
background_mode = 2
sky = SubResource("Sky_nwtrj")
tonemap_mode = 2
volumetric_fog_enabled = true
volumetric_fog_density = 0.01
[sub_resource type="FastNoiseLite" id="FastNoiseLite_nwtrj"]
noise_type = 3
frequency = 0.0291
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_y7qka"]
resource_name = "PlayerCollision"
[sub_resource type="CapsuleMesh" id="CapsuleMesh_nwtrj"]
resource_name = "PlayerMesh"
[node name="main" type="Node"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_vrc1b")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
shadow_enabled = true
[node name="WorldGenerator" type="Node" parent="."]
script = ExtResource("1_y7qka")
noise = SubResource("FastNoiseLite_nwtrj")
[node name="Player" type="CharacterBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 104.43206, 0)
script = ExtResource("2_nwtrj")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
shape = SubResource("CapsuleShape3D_y7qka")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Player/CollisionShape3D"]
mesh = SubResource("CapsuleMesh_nwtrj")
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 0.9604348, 0.27850503, 0, -0.27850503, 0.9604348, 0, 1.8589027, 2.8465767)

View file

@ -0,0 +1,7 @@
[gd_resource type="PlaneMesh" format=3 uid="uid://rhv7l5ya1qnf"]
[resource]
resource_local_to_scene = true
size = Vector2(256, 256)
subdivide_width = 32
subdivide_depth = 32

View file

@ -0,0 +1,149 @@
@tool
extends Node
var generation_thread: Thread = Thread.new()
#### CHUNK GENERATION ####
@export_category("CHUNK GENERATION")
const CHUNK_SIZE := 256
const CHUNK_BOUNDS := 8
## How detailed the chunk should be. This could be made a const when debug phase is finished
@export_range(4,256,4) var chunk_resolution :=32:
set(new_resolution):
chunk_resolution = new_resolution
update_all_mesh()
#### WORLD GENERATION ####
@export_category("WORLD GENERATION")
@export_tool_button("Generate Chunks") var generate_chunk_callable = generate.bind(true)
@export var noise: FastNoiseLite:
set(new_noise):
noise = new_noise
update_all_mesh()
if noise:
noise.changed.connect(update_all_mesh) #change all thes meshes after eahc changes
@export_range(4.0,128.0,4.0) var height := 64.0:
set(new_height):
height = new_height
update_all_mesh()
var loaded_chunks := {}
# Called when the node enters the scene tree for the first time. ALSO IN EDITOR
func _ready() -> void:
generate(true)
func generate(_ForceRegen:bool=false):
print('Generation starts')
generate_chunks_around(Vector3(0,0,0), _ForceRegen)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
pass
func generate_noise(_seed:float):
noise = FastNoiseLite.new()
# This function is better called in another thread, doing all the heavy process before adding it to the scene tree
func generate_chunk(position: Vector3, _ForceRegen:bool = false):
WorkerThreadPool.add_task(func():
var mesh_instance = MeshInstance3D.new()
update_mesh(mesh_instance)
mesh_instance.position = position
call_deferred("add_child", mesh_instance)
var chunk_coord = get_chunk_coord(position)
loaded_chunks[chunk_coord] = mesh_instance
print("chunk at : ",str(position),", coord ",str(chunk_coord), " loaded")
)
func get_height(x:float,y:float) -> float:
return noise.get_noise_2d(x,y) * height
func get_normal(x:float,y:float) -> Vector3:
var epsilon := CHUNK_SIZE / chunk_resolution
var normal := Vector3(
(get_height(x + epsilon,y) - get_height(x - epsilon, y))/(2.0 *epsilon),
1.0,
(get_height(x, y + epsilon) - get_height(x, y - epsilon))/(2.0 *epsilon)
)
return normal.normalized()
func update_all_mesh():
for chunk in loaded_chunks:
update_mesh(loaded_chunks[chunk])
func update_mesh(chunk_terrain_instance:MeshInstance3D):
var plane: PlaneMesh = PlaneMesh.new() #load("res://world_gen/new_plane_mesh.tres").duplicate()
plane.subdivide_depth = chunk_resolution
plane.subdivide_width = chunk_resolution
plane.size = Vector2(CHUNK_SIZE,CHUNK_SIZE)
var plane_arrays := plane.get_mesh_arrays()
var vertex_array: PackedVector3Array = plane_arrays[ArrayMesh.ARRAY_VERTEX]
var normal_array: PackedVector3Array = plane_arrays[ArrayMesh.ARRAY_NORMAL]
var tangent_array: PackedFloat32Array = plane_arrays[ArrayMesh.ARRAY_TANGENT]
for i:int in vertex_array.size():
var vertex := vertex_array[i]
# Convert local vertex coordinates to world coordinates
var world_x = chunk_terrain_instance.position.x + vertex.x
var world_z = chunk_terrain_instance.position.z + vertex.z
var normal := Vector3.UP
var tangent := Vector3.RIGHT
if noise:
vertex.y = get_height(world_x, world_z)
normal = get_normal(world_x, world_z)
tangent = normal.cross(Vector3.UP)
vertex_array[i] = vertex
normal_array[i] = normal
tangent_array[4*i] = tangent.x
tangent_array[4*i+1] = tangent.y
tangent_array[4*i+2] = tangent.z
tangent_array[4*i+3] = 1.0
var array_mesh := ArrayMesh.new()
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,plane_arrays)
chunk_terrain_instance.mesh = array_mesh
## Convert position to chunk coordinates
func get_chunk_coord(position:Vector3) -> Vector2i:
var coord_chunk_x = int(floor(position.x / CHUNK_SIZE))
var coord_chunk_z = int(floor(position.z / CHUNK_SIZE))
return Vector2i(coord_chunk_x,coord_chunk_z)
func generate_chunks_around(position:Vector3,ForceRegen:bool = false):
# Convert position to chunk coordinates
var position_to_coord := get_chunk_coord(position)
print("closest chunk : "+str(Vector2i(position_to_coord.x,position_to_coord.y)))
for x in range(position_to_coord.x - CHUNK_BOUNDS,position_to_coord.x + CHUNK_BOUNDS+1):
for z in range(position_to_coord.y - CHUNK_BOUNDS, position_to_coord.y + CHUNK_BOUNDS+1):
var chunk_coord := Vector2i(x,z)
var chunck_position := Vector3i(x * CHUNK_SIZE,0,z * CHUNK_SIZE)
print("trying to load chunk at coordinate : "+str(chunk_coord))
if ForceRegen:
loaded_chunks.clear()
for child in get_children():
child.queue_free()
if not loaded_chunks.has(chunk_coord):
print("generate chunk at : "+str(chunck_position))
generate_chunk(chunck_position)

View file

@ -0,0 +1 @@
uid://b4q4faaqbat0x