Compare commits

...

9 Commits

  1. 4
      menu/MainMenu.tscn
  2. 10
      project.godot
  3. 113
      world/Chunk.gd
  4. BIN
      world/Default OBJ.material
  5. 29
      world/MultiMeshInstance.gd
  6. 120
      world/World3d.gd
  7. 25
      world/game.tscn
  8. 12
      world/grass.mtl
  9. 32
      world/grass.obj
  10. 20
      world/grass.obj.import
  11. 29
      world/grass_triangle.obj
  12. 20
      world/grass_triangle.obj.import
  13. BIN
      world/materials/colors.png
  14. 77
      world/materials/wind_grass.shader
  15. 40
      world/materials/wind_grass.tres

@ -47,10 +47,6 @@ margin_right = 360.0
margin_bottom = 377.0 margin_bottom = 377.0
text = "Quitter" text = "Quitter"
[node name="Viewport" type="Viewport" parent="."]
size = Vector2( 2048, 2048 )
own_world = true
[connection signal="pressed" from="VBoxContainer/NewButton" to="." method="_on_NewButton_pressed"] [connection signal="pressed" from="VBoxContainer/NewButton" to="." method="_on_NewButton_pressed"]
[connection signal="pressed" from="VBoxContainer/LoadButton" to="." method="_on_LoadButton_pressed"] [connection signal="pressed" from="VBoxContainer/LoadButton" to="." method="_on_LoadButton_pressed"]
[connection signal="pressed" from="VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"] [connection signal="pressed" from="VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"]

@ -19,6 +19,11 @@ _global_script_classes=[ {
"language": "GDScript", "language": "GDScript",
"path": "res://utils/camera/outline.gd" "path": "res://utils/camera/outline.gd"
}, { }, {
"base": "Spatial",
"class": "Chunk",
"language": "GDScript",
"path": "res://world/Chunk.gd"
}, {
"base": "Reference", "base": "Reference",
"class": "Delaunator", "class": "Delaunator",
"language": "GDScript", "language": "GDScript",
@ -52,6 +57,7 @@ _global_script_classes=[ {
_global_script_class_icons={ _global_script_class_icons={
"CameraController": "", "CameraController": "",
"CameraOutline": "", "CameraOutline": "",
"Chunk": "",
"Delaunator": "", "Delaunator": "",
"LoadingHelper": "", "LoadingHelper": "",
"PoissonDiscSampling": "", "PoissonDiscSampling": "",
@ -70,6 +76,10 @@ config/icon="res://icon.png"
Global="*res://utils/Global.gd" Global="*res://utils/Global.gd"
[display]
window/dpi/allow_hidpi=true
[editor_plugins] [editor_plugins]
enabled=PoolStringArray( ) enabled=PoolStringArray( )

@ -0,0 +1,113 @@
extends Spatial
class_name Chunk
var noise
var should_remove = true
var x
var z
var empty = true
func _init(x, z):
self.x = x
self.z = z
func _ready():
# generate_chunk()
generate_grass()
pass
func generate_grass():
var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new()
var rng := RandomNumberGenerator.new()
rng.randomize()
var coords = []
for center in Global.terrain.get_chunk(Vector2(x, z)):
if (
not center.get_data("mountain")
and not center.get_data("water")
and not center.get_data("coast")
):
var points = poisson_disc_sampling.generate_points(0.1, center.polygon(), 2)
var points3d = []
for point in points:
points3d.append(Vector3(point.x, center.get_elevation() * 120, point.y))
coords += points3d
# var multimesh = MultiMesh.new()
var multimesh = MultiMesh.new()
multimesh.mesh = load("res://world/grass.obj")
multimesh.mesh.surface_set_material(0, load("res://world/materials/wind_grass.tres"))
multimesh.transform_format = MultiMesh.TRANSFORM_3D
multimesh.instance_count = coords.size()
for instance_index in multimesh.instance_count:
var transform := Transform().rotated(Vector3.UP, rng.randf_range(-PI / 2, PI / 2))
transform.origin = Vector3(coords[instance_index].x, coords[instance_index].y, coords[instance_index].z)
# transform.scaled(Vector3())
multimesh.set_instance_transform(instance_index, transform)
var multimesh_instance = MultiMeshInstance.new()
multimesh_instance.multimesh = multimesh
add_child(multimesh_instance)
pass
func generate_chunk():
var file = File.new()
file.open("res://world/materials/materials.json", File.READ)
var materials = JSON.parse(file.get_as_text()).result
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var factor = Vector3(1, 120, 1)
# print(x)
# print(z)
for center in Global.terrain.get_chunk(Vector2(x, z)):
# print(center.get_data("water"))
if not center.get_data("water"):
empty = false
# print(center.get_data("material"))
var material_id = materials[center.get_data("material")]
var top_uv = Vector2(0, float(material_id) / (materials.size()-1))
var border_uv = Vector2(1, float(material_id) / (materials.size()-1))
for edge in center.borders():
if edge.end_center().get_elevation() < edge.start_center().get_elevation():
var top = edge.start_center().get_elevation()
# if edge.start_center().get_data("ocean"):
# top = -1.0
var bottom = edge.end_center().get_elevation()
if edge.end_center().get_data("ocean"):
bottom = 0.0
st.add_uv(border_uv)
st.add_vertex(Vector3(edge.start_corner().point3d().x, bottom, edge.start_corner().point3d().z) * factor)
st.add_vertex(Vector3(edge.end_corner().point3d().x, top, edge.end_corner().point3d().z) * factor)
st.add_vertex(Vector3(edge.start_corner().point3d().x, top, edge.start_corner().point3d().z) * factor)
st.add_vertex(Vector3(edge.start_corner().point3d().x, bottom, edge.start_corner().point3d().z) * factor)
st.add_vertex(Vector3(edge.end_corner().point3d().x, bottom, edge.end_corner().point3d().z) * factor)
st.add_vertex(Vector3(edge.end_corner().point3d().x, top, edge.end_corner().point3d().z) * factor)
for corner_count in center.corners().size():
var current_corner = center.corners()[corner_count]
var next_corner
if corner_count < center.corners().size() - 1:
next_corner = center.corners()[corner_count+1]
else:
next_corner = center.corners()[0]
st.add_uv(Vector2(top_uv))
st.add_vertex(Vector3(current_corner.point2d().x, center.get_elevation(), current_corner.point2d().y) * factor)
st.add_vertex(Vector3(next_corner.point2d().x, center.get_elevation(), next_corner.point2d().y) * factor)
st.add_vertex(Vector3(center.point2d().x, center.get_elevation(), center.point2d().y) * factor)
if not empty:
st.generate_normals()
st.index()
var mi = MeshInstance.new()
mi.mesh = st.commit()
var material = load("res://world/materials/world.material")
mi.set_surface_material(0, material)
mi.create_trimesh_collision()
mi.cast_shadow = GeometryInstance.SHADOW_CASTING_SETTING_ON
add_child(mi)

Binary file not shown.

@ -0,0 +1,29 @@
extends MultiMeshInstance
var extents = Vector2(10, 10)
func _ready():
return
var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new()
var rng := RandomNumberGenerator.new()
rng.randomize()
var coords = []
for center in Global.terrain.get_centers():
if (
not center.get_data("mountain")
and not center.get_data("water")
and not center.get_data("coast")
):
var points = poisson_disc_sampling.generate_points(2, center.polygon(), 2)
var points3d = []
for point in points:
points3d.append(Vector3(point.x, center.get_elevation() * 120, point.y))
coords += points3d
multimesh.instance_count = coords.size()
for instance_index in multimesh.instance_count:
var transform := Transform().rotated(Vector3.UP, rng.randf_range(-PI / 2, PI / 2))
transform.origin = Vector3(coords[instance_index].x, coords[instance_index].y, coords[instance_index].z)
# transform.scaled(Vector3())
multimesh.set_instance_transform(instance_index, transform)

@ -2,13 +2,14 @@ extends Spatial
var rng = RandomNumberGenerator.new() var rng = RandomNumberGenerator.new()
var chunk_size = 32 var chunk_size = 32
var chunk_amount = 16 var chunk_amount = 1
var chunks = {} var chunks = {}
var unready_chunks = {} var unready_chunks = {}
var thread var thread
func _ready(): func _ready():
add_world() add_world()
thread = Thread.new()
add_trees() add_trees()
func add_world(): func add_world():
@ -29,13 +30,110 @@ func add_trees():
var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new() var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new()
for center in Global.terrain.get_centers(): for center in Global.terrain.get_centers():
var num = rng.randi_range(0,100) if not center.get_data("water"):
if center.get_data("forest") or num == 1: var num = rng.randi_range(0,100)
var points2d = poisson_disc_sampling.generate_points(3, center.polygon(), 2) if center.get_data("forest") or num == 1:
for point in points2d: var points2d = poisson_disc_sampling.generate_points(3, center.polygon(), 2)
var tree = treescene.instance() for point in points2d:
var scaling = rng.randi_range(0.8, 1.2) var tree = treescene.instance()
tree.scale = Vector3(scaling, scaling, scaling) var scaling = rng.randi_range(0.8, 1.2)
tree.rotate_y(rng.randi_range(0, 2*PI)) tree.scale = Vector3(scaling, scaling, scaling)
tree.translation = Vector3(point.x, center.get_elevation() * 120, point.y) tree.rotate_y(rng.randi_range(0, 2*PI))
add_child(tree) tree.translation = Vector3(point.x, center.get_elevation() * 120, point.y)
add_child(tree)
func add_chunk(x, z):
var key = str(x) + "," + str(z)
if chunks.has(key) or unready_chunks.has(key):
return
if not thread.is_active():
thread.start(self, "load_chunk", [thread, x, z])
unready_chunks[key] = 1
func load_chunk(array):
var thread = array[0]
var x = array[1]
var z = array[2]
# print(x)
# print(z)
var chunk = Chunk.new(x * chunk_size, z * chunk_size)
# chunk.translation = Vector3(x * chunk_size, 0, z * chunk_size)
call_deferred("load_done", chunk, thread)
func load_done(chunk, thread):
add_child(chunk)
var key = str(chunk.x / chunk_size) + "," + str(chunk.z / chunk_size)
chunks[key] = chunk
unready_chunks.erase(key)
thread.wait_to_finish()
func get_chunk(x, z):
var key = str(x) + "," + str(z)
if chunks.has(key):
return chunks.get(key)
return null
func _process(delta):
update_chunks()
clean_up_chunks()
reset_chunks()
func update_chunks():
var camera_translation = $CamBase/Camera.translation
var c_x = int(camera_translation.x) / chunk_size
var c_z = int(camera_translation.y) / chunk_size * -1
var stack = []
var used = []
stack.append(Vector2(c_x, c_z))
while stack.size():
var current_vector = stack.pop_back()
used.append(current_vector)
add_chunk(current_vector.x, current_vector.y)
var chunk = get_chunk(current_vector.x, current_vector.y)
if chunk != null:
chunk.should_remove = false
var neighbours = [
Vector2(current_vector.x, current_vector.y - 1), # n
Vector2(current_vector.x + 1, current_vector.y - 1), # n_e
Vector2(current_vector.x + 1, current_vector.y), # e
Vector2(current_vector.x + 1, current_vector.y + 1), # s_e
Vector2(current_vector.x, current_vector.y + 1), # s
Vector2(current_vector.x - 1, current_vector.y + 1), # s_w
Vector2(current_vector.x - 1, current_vector.y), # w
Vector2(current_vector.x - 1, current_vector.y - 1) # n_w
]
for neighbour in neighbours:
if(
neighbour.x >= c_x - chunk_amount * 0.5
and neighbour.x <= c_x + chunk_amount * 0.53
and neighbour.y >= c_z - chunk_amount * 0.5
and neighbour.y <= c_z + chunk_amount * 0.53
and not neighbour in used
):
stack.append(neighbour)
func clean_up_chunks():
for key in chunks:
var chunk = chunks[key]
if chunk.should_remove:
chunk.queue_free()
chunks.erase(key)
func reset_chunks():
for key in chunks:
chunks[key].should_remove = true

File diff suppressed because one or more lines are too long

@ -0,0 +1,12 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl Default_OBJ
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2

@ -0,0 +1,32 @@
# Blender v3.2.2 OBJ File: ''
# www.blender.org
mtllib grass.mtl
o grass_triangle
v 0.012754 0.302056 0.000000
v 0.000000 0.402742 0.000000
v -0.012754 0.302056 0.000000
v 0.025508 0.201371 0.000000
v -0.038262 0.100685 0.000000
v 0.038262 0.100685 0.000000
v -0.051016 0.000000 0.000000
v 0.051016 0.000000 0.000000
v -0.025508 0.201371 0.000000
vt 0.621057 0.520384
vt 0.497980 0.979655
vt 0.376628 0.520384
vt 0.744133 0.238847
vt 0.133923 0.098911
vt 0.867209 0.098911
vt 0.012570 0.013663
vt 0.990286 0.013663
vt 0.255275 0.238847
vn 0.0000 -0.0000 1.0000
usemtl Default_OBJ
s 1
f 1/1/1 2/2/1 3/3/1
f 4/4/1 5/5/1 6/6/1
f 6/6/1 7/7/1 8/8/1
f 1/1/1 9/9/1 4/4/1
f 4/4/1 9/9/1 5/5/1
f 6/6/1 5/5/1 7/7/1
f 1/1/1 3/3/1 9/9/1

@ -0,0 +1,20 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/grass.obj-b367f86df935fca9f4b72c7070d3608f.mesh"
[deps]
files=[ "res://.import/grass.obj-b367f86df935fca9f4b72c7070d3608f.mesh" ]
source_file="res://world/grass.obj"
dest_files=[ "res://.import/grass.obj-b367f86df935fca9f4b72c7070d3608f.mesh", "res://.import/grass.obj-b367f86df935fca9f4b72c7070d3608f.mesh" ]
[params]
generate_tangents=true
scale_mesh=Vector3( 1, 1, 1 )
offset_mesh=Vector3( 0, 0, 0 )
octahedral_compression=true
optimize_mesh_flags=4286

@ -0,0 +1,29 @@
# Blender v2.83.0 OBJ File: ''
# www.blender.org
v -0.255078 0.000000 0.000000
v 0.255078 0.000000 0.000000
v 0.000000 2.013708 0.000000
v -0.127539 1.006854 0.000000
v 0.127539 1.006854 0.000000
v -0.191308 0.503427 0.000000
v 0.063769 1.510281 0.000000
v -0.063769 1.510281 0.000000
v 0.191308 0.503427 0.000000
vt 0.621057 0.520384
vt 0.497980 0.979655
vt 0.376628 0.520384
vt 0.744133 0.238847
vt 0.133923 0.098911
vt 0.867209 0.098911
vt 0.012570 0.013663
vt 0.990286 0.013663
vt 0.255275 0.238847
vn 0.0000 0.0000 1.0000
s 1
f 7/1/1 3/2/1 8/3/1
f 5/4/1 6/5/1 9/6/1
f 9/6/1 1/7/1 2/8/1
f 7/1/1 4/9/1 5/4/1
f 5/4/1 4/9/1 6/5/1
f 9/6/1 6/5/1 1/7/1
f 7/1/1 8/3/1 4/9/1

@ -0,0 +1,20 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/grass_triangle.obj-46b3ab3674e3d39ea8727b7c5a77c4a9.mesh"
[deps]
files=[ "res://.import/grass_triangle.obj-46b3ab3674e3d39ea8727b7c5a77c4a9.mesh" ]
source_file="res://world/grass_triangle.obj"
dest_files=[ "res://.import/grass_triangle.obj-46b3ab3674e3d39ea8727b7c5a77c4a9.mesh", "res://.import/grass_triangle.obj-46b3ab3674e3d39ea8727b7c5a77c4a9.mesh" ]
[params]
generate_tangents=true
scale_mesh=Vector3( 1, 1, 1 )
offset_mesh=Vector3( 0, 0, 0 )
octahedral_compression=true
optimize_mesh_flags=4286

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

@ -0,0 +1,77 @@
shader_type spatial;
render_mode cull_disabled, unshaded;
uniform float wind_speed = 0.2;
uniform float wind_strength = 2.0;
// How big, in world space, is the noise texture
// wind will tile every wind_texture_tile_size
uniform float wind_texture_tile_size = 20.0;
uniform float wind_vertical_strength = 0.3;
uniform vec2 wind_horizontal_direction = vec2(1.0, 0.5);
uniform sampler2D color_ramp : hint_black_albedo;
// we need a tiling noise here!
uniform sampler2D wind_noise : hint_black;
uniform vec3 character_position;
uniform float character_radius = 3.0;
uniform sampler2D character_distance_falloff_curve : hint_black_albedo;
uniform float character_push_strength = 1.0;
varying float debug_wind;
void vertex() {
vec3 world_vert = (WORLD_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec2 normalized_wind_direction = normalize(wind_horizontal_direction);
vec2 world_uv = world_vert.xz / wind_texture_tile_size + normalized_wind_direction * TIME * wind_speed;
// we displace only the top part of the mesh
// note that this means that the mesh needs to have UV in a way that the bottom of UV space
// is at the top of the mesh
float displacement_affect = (1.0 - UV.y);
float wind_noise_intensity = (textureLod(wind_noise, world_uv, 0.0).r - 0.5);
// We convert the direction of the wind into vertex space from world space
// if we used it directly in vertex space, rotated blades of grass wouldn't behave properly
vec2 vert_space_horizontal_dir =
(inverse(WORLD_MATRIX) * vec4(wind_horizontal_direction, 0.0, 0.0)).xy;
vert_space_horizontal_dir = normalize(vert_space_horizontal_dir);
vec3 bump_wind = vec3(
wind_noise_intensity * vert_space_horizontal_dir.x,
1.0 - wind_noise_intensity,
wind_noise_intensity * vert_space_horizontal_dir.y);
normalize(bump_wind);
bump_wind *= vec3(wind_strength, wind_vertical_strength, wind_strength);
VERTEX += bump_wind * displacement_affect;
// At the moment the blades are pushed away in a perfectly circular manner.
// We could distort the distance to the character based on a noise, to break a bit the
// circular shape. We could distort the falloff by sampling in a noise based on the xz coordinates.
// The task is left to the reader
vec3 dir_to_character = character_position - WORLD_MATRIX[3].xyz;
// uncomment the following line to have a horizontal only character push
//dir_to_character.y = 0.0;
float distance_to_character = length(dir_to_character);
float falloff = 1.0 - smoothstep(0.0, 1.0, distance_to_character/character_radius);
// Because we operate in vertex space, we need to convert the direction to the character
// in vertex space. Otherwise, it wouldn't work for rotated blades of grass.
// comment the next line to observe how the blades are not all facing away from the character.
dir_to_character = (inverse(WORLD_MATRIX) * vec4(dir_to_character, 0.0)).xyz;
dir_to_character = normalize(dir_to_character);
// sample the curve based on how far we are from the character, in normalized coordinates
float falloff_curve = texture(character_distance_falloff_curve, vec2(falloff)).x;
// direction to character is inverted because we want to point away from it
VERTEX += normalize(-dir_to_character) * falloff_curve * character_push_strength * displacement_affect;
}
void fragment() {
ALBEDO = texture(color_ramp, vec2(1.0 - UV.y, 0)).rgb ;
}

@ -0,0 +1,40 @@
[gd_resource type="ShaderMaterial" load_steps=8 format=2]
[ext_resource path="res://world/materials/wind_grass.shader" type="Shader" id=1]
[sub_resource type="Curve" id=1]
_data = [ Vector2( 0, 0 ), 0.0, 2.71765, 0, 0, Vector2( 1, 1 ), -0.129412, 0.0, 0, 0 ]
[sub_resource type="CurveTexture" id=2]
width = 128
curve = SubResource( 1 )
[sub_resource type="Gradient" id=3]
offsets = PoolRealArray( 0, 0.486339, 0.966102 )
colors = PoolColorArray( 0.054902, 0.556863, 0.439216, 1, 0.321569, 0.886275, 0.341176, 1, 0.498039, 0.921569, 0.356863, 1 )
[sub_resource type="GradientTexture" id=4]
gradient = SubResource( 3 )
[sub_resource type="OpenSimplexNoise" id=5]
period = 109.1
persistence = 0.138
lacunarity = 0.44
[sub_resource type="NoiseTexture" id=6]
seamless = true
noise = SubResource( 5 )
[resource]
shader = ExtResource( 1 )
shader_param/wind_speed = 0.2
shader_param/wind_strength = 2.0
shader_param/wind_texture_tile_size = 20.0
shader_param/wind_vertical_strength = 0.3
shader_param/wind_horizontal_direction = Vector2( 1, 0.5 )
shader_param/character_position = Vector3( 0, 1.22062, 0 )
shader_param/character_radius = 3.0
shader_param/character_push_strength = 1.0
shader_param/color_ramp = SubResource( 4 )
shader_param/wind_noise = SubResource( 6 )
shader_param/character_distance_falloff_curve = SubResource( 2 )
Loading…
Cancel
Save