Browse Source

Merge pull request #469 from aaronfranke/voxel

Add a Voxel demo project
Aaron Franke 5 years ago
parent
commit
48eb973127
40 changed files with 1745 additions and 0 deletions
  1. 58 0
      3d/voxel/README.md
  2. 8 0
      3d/voxel/default_bus_layout.tres
  3. 18 0
      3d/voxel/default_env.tres
  4. BIN
      3d/voxel/icon.png
  5. 34 0
      3d/voxel/icon.png.import
  6. BIN
      3d/voxel/menu/button.png
  7. 34 0
      3d/voxel/menu/button.png.import
  8. 45 0
      3d/voxel/menu/debug.gd
  9. 38 0
      3d/voxel/menu/ingame/pause_menu.gd
  10. 133 0
      3d/voxel/menu/ingame/pause_menu.tscn
  11. BIN
      3d/voxel/menu/main/dark_dirt.png
  12. 34 0
      3d/voxel/menu/main/dark_dirt.png.import
  13. 37 0
      3d/voxel/menu/main/main_menu.gd
  14. 236 0
      3d/voxel/menu/main/main_menu.tscn
  15. 8 0
      3d/voxel/menu/main/splash_text.gd
  16. BIN
      3d/voxel/menu/main/title.png
  17. 34 0
      3d/voxel/menu/main/title.png.import
  18. 22 0
      3d/voxel/menu/options/option_buttons.gd
  19. 8 0
      3d/voxel/menu/options/options.gd
  20. 112 0
      3d/voxel/menu/options/options.tscn
  21. 7 0
      3d/voxel/menu/theme/TinyUnicode.tres
  22. BIN
      3d/voxel/menu/theme/TinyUnicode.ttf
  23. 6 0
      3d/voxel/menu/theme/theme.tres
  24. 1 0
      3d/voxel/player/crosshair.svg
  25. 34 0
      3d/voxel/player/crosshair.svg.import
  26. 94 0
      3d/voxel/player/player.gd
  27. 48 0
      3d/voxel/player/player.tscn
  28. 161 0
      3d/voxel/project.godot
  29. 0 0
      3d/voxel/screenshots/.gdignore
  30. BIN
      3d/voxel/screenshots/blocks.png
  31. BIN
      3d/voxel/screenshots/title.png
  32. 40 0
      3d/voxel/settings.gd
  33. 216 0
      3d/voxel/world/chunk.gd
  34. 17 0
      3d/voxel/world/environment.gd
  35. 42 0
      3d/voxel/world/terrain_generator.gd
  36. 8 0
      3d/voxel/world/textures/material.tres
  37. BIN
      3d/voxel/world/textures/texture_sheet.png
  38. 36 0
      3d/voxel/world/textures/texture_sheet.png.import
  39. 138 0
      3d/voxel/world/voxel_world.gd
  40. 38 0
      3d/voxel/world/world.tscn

+ 58 - 0
3d/voxel/README.md

@@ -0,0 +1,58 @@
+# Voxel Game
+
+This demo is a minimal first-person voxel game,
+inspired by others such as Minecraft.
+
+Language: GDScript
+
+Renderer: GLES 2
+
+## How does it work?
+
+Each chunk is a
+[`StaticBody`](https://docs.godotengine.org/en/latest/classes/class_staticbody.html)
+with each block having its own
+[`CollisionShape`](https://docs.godotengine.org/en/latest/classes/class_collisionshape.html)
+for collisions. The meshes are created using
+[`SurfaceTool`](https://docs.godotengine.org/en/latest/classes/class_surfacetool.html)
+which allows specifying vertices, triangles, and UV coordinates
+for constructing a mesh.
+
+The chunks and chunk data are stored in
+[`Dictionary`](https://docs.godotengine.org/en/latest/classes/class_dictionary.html)
+objects. New chunks have their meshes drawn in separate
+[`Thread`](https://docs.godotengine.org/en/latest/classes/class_thread.html)s,
+but generating the collisions is done in the main thread, since Godot does
+not support changing physics objects in a separate thread. There
+are two terrain types, random blocks and flat grass. A more
+complex terrain generator is out-of-scope for this demo project.
+
+The player can place and break blocks using the
+[`RayCast`](https://docs.godotengine.org/en/latest/classes/class_raycast.html)
+node attached to the camera. It uses the collision information to
+figure out the block position and change the block data. You can
+switch the active block using the brackets or with the middle mouse button.
+
+There is a settings menu for render distance and toggling the fog.
+Settings are stored inside of an
+[AutoLoad singleton](https://docs.godotengine.org/en/latest/getting_started/step_by_step/singletons_autoload.html)
+called "Settings". This class will automatically save
+settings, and load them when the game opens, by using the
+[`File`](https://docs.godotengine.org/en/latest/classes/class_file.html) class.
+
+Sticking to GDScript and the built-in Godot tools, as this demo does, is
+quite limiting. If you are making your own voxel game, you should probably
+use Zylann's voxel module instead: https://github.com/Zylann/godot_voxel
+
+## Screenshots
+
+![Screenshot](screenshots/blocks.png)
+
+![Screenshot](screenshots/title.png)
+
+## Licenses
+
+Textures are from [Minetest](https://www.minetest.net/). Copyright © 2010-2018 Minetest contributors, CC BY-SA 3.0 Unported (Attribution-ShareAlike)
+http://creativecommons.org/licenses/by-sa/3.0/
+
+Font is "TinyUnicode" by DuffsDevice. Copyright © DuffsDevice, CC-BY (Attribution) http://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=468

+ 8 - 0
3d/voxel/default_bus_layout.tres

@@ -0,0 +1,8 @@
+[gd_resource type="AudioBusLayout" load_steps=2 format=2]
+
+[sub_resource type="AudioEffectPitchShift" id=1]
+resource_name = "PitchShift"
+
+[resource]
+bus/0/effect/0/effect = SubResource( 1 )
+bus/0/effect/0/enabled = true

+ 18 - 0
3d/voxel/default_env.tres

@@ -0,0 +1,18 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+sun_longitude = 100.0
+sun_angle_min = 2.0
+sun_angle_max = 20.0
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
+fog_enabled = true
+fog_color = Color( 0.501961, 0.6, 0.701961, 1 )
+fog_depth_begin = 32.0
+fog_depth_end = 64.0
+fog_transmit_enabled = true
+dof_blur_far_enabled = true
+dof_blur_far_transition = 32.0
+dof_blur_far_amount = 0.05

BIN
3d/voxel/icon.png


+ 34 - 0
3d/voxel/icon.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

BIN
3d/voxel/menu/button.png


+ 34 - 0
3d/voxel/menu/button.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/button.png-e6ddd405c0968c9fb68dca7b600a69a3.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://menu/button.png"
+dest_files=[ "res://.import/button.png-e6ddd405c0968c9fb68dca7b600a69a3.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 45 - 0
3d/voxel/menu/debug.gd

@@ -0,0 +1,45 @@
+extends Label
+# Displays some useful debug information in a Label.
+
+onready var player = $"../Player"
+onready var voxel_world = $"../VoxelWorld"
+
+
+func _process(_delta):
+	if Input.is_action_just_pressed("debug"):
+		visible = !visible
+	
+	text = "Position: " + _vector_to_string_appropriate_digits(player.transform.origin)
+	text += "\nEffective render distance: " + str(voxel_world.effective_render_distance)
+	text += "\nLooking: " + _cardinal_string_from_radians(player.transform.basis.get_euler().y)
+	text += "\nFPS: " + str(Engine.get_frames_per_second())
+
+
+# Avoids the problem of showing more digits than needed or available.
+func _vector_to_string_appropriate_digits(vector):
+	var factors = [1000, 1000, 1000]
+	for i in range(3):
+		if abs(vector[i]) > 4096:
+			factors[i] = factors[i] / 10
+			if abs(vector[i]) > 65536:
+				factors[i] = factors[i] / 10
+				if abs(vector[i]) > 524288:
+					factors[i] = factors[i] / 10
+	
+	return "(" + \
+			str(round(vector.x * factors[0]) / factors[0]) + ", " + \
+			str(round(vector.y * factors[1]) / factors[1]) + ", " + \
+			str(round(vector.z * factors[2]) / factors[2]) + ")"
+
+
+# Expects a rotation where 0 is North, on the range -PI to PI.
+func _cardinal_string_from_radians(angle):
+	if angle > TAU * 3 / 8:
+		return "South"
+	if angle < -TAU * 3 / 8:
+		return "South"
+	if angle > TAU * 1 / 8:
+		return "West"
+	if angle < -TAU * 1 / 8:
+		return "East"
+	return "North"

+ 38 - 0
3d/voxel/menu/ingame/pause_menu.gd

@@ -0,0 +1,38 @@
+extends Control
+
+onready var tree = get_tree()
+
+onready var crosshair = $Crosshair
+onready var pause = $Pause
+onready var options = $Options
+onready var voxel_world = $"../VoxelWorld"
+
+
+func _process(_delta):
+	if Input.is_action_just_pressed("pause"):
+		pause.visible = crosshair.visible
+		crosshair.visible = !crosshair.visible
+		options.visible = false
+		Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED if crosshair.visible else Input.MOUSE_MODE_VISIBLE)
+
+
+func _on_Resume_pressed():
+	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+	crosshair.visible = true
+	pause.visible = false
+
+
+func _on_Options_pressed():
+	options.prev_menu = pause
+	options.visible = true
+	pause.visible = false
+
+
+func _on_MainMenu_pressed():
+	voxel_world.clean_up()
+	tree.change_scene("res://menu/main/main_menu.tscn")
+
+
+func _on_Exit_pressed():
+	voxel_world.clean_up()
+	tree.quit()

+ 133 - 0
3d/voxel/menu/ingame/pause_menu.tscn

@@ -0,0 +1,133 @@
+[gd_scene load_steps=6 format=2]
+
+[ext_resource path="res://player/crosshair.svg" type="Texture" id=1]
+[ext_resource path="res://menu/theme/theme.tres" type="Theme" id=2]
+[ext_resource path="res://menu/options/options.tscn" type="PackedScene" id=3]
+[ext_resource path="res://menu/ingame/pause_menu.gd" type="Script" id=4]
+[ext_resource path="res://menu/button.png" type="Texture" id=5]
+
+[node name="PauseMenu" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+theme = ExtResource( 2 )
+script = ExtResource( 4 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Crosshair" type="CenterContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TextureRect" type="TextureRect" parent="Crosshair"]
+margin_left = 784.0
+margin_top = 434.0
+margin_right = 816.0
+margin_bottom = 466.0
+texture = ExtResource( 1 )
+
+[node name="Pause" type="VBoxContainer" parent="."]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ButtonHolder" type="HBoxContainer" parent="Pause"]
+margin_right = 1600.0
+margin_bottom = 900.0
+size_flags_vertical = 3
+alignment = 1
+
+[node name="MainButtons" type="VBoxContainer" parent="Pause/ButtonHolder"]
+margin_left = 608.0
+margin_right = 992.0
+margin_bottom = 900.0
+custom_constants/separation = 20
+alignment = 1
+
+[node name="Resume" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
+margin_top = 292.0
+margin_right = 384.0
+margin_bottom = 356.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 5 )
+
+[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/Resume"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Resume Game"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Options" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
+margin_top = 376.0
+margin_right = 384.0
+margin_bottom = 440.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 5 )
+
+[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/Options"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Options"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="MainMenu" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
+margin_top = 460.0
+margin_right = 384.0
+margin_bottom = 524.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 5 )
+
+[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/MainMenu"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Main Menu"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Exit" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
+margin_top = 544.0
+margin_right = 384.0
+margin_bottom = 608.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 5 )
+
+[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/Exit"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Exit Game"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Options" parent="." instance=ExtResource( 3 )]
+[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/Resume" to="." method="_on_Resume_pressed"]
+[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/Options" to="." method="_on_Options_pressed"]
+[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/MainMenu" to="." method="_on_MainMenu_pressed"]
+[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/Exit" to="." method="_on_Exit_pressed"]

BIN
3d/voxel/menu/main/dark_dirt.png


+ 34 - 0
3d/voxel/menu/main/dark_dirt.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/dark_dirt.png-8e8d84e3c30520a8995166be2b7ea97e.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://menu/main/dark_dirt.png"
+dest_files=[ "res://.import/dark_dirt.png-8e8d84e3c30520a8995166be2b7ea97e.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 37 - 0
3d/voxel/menu/main/main_menu.gd

@@ -0,0 +1,37 @@
+extends Control
+
+onready var tree = get_tree()
+
+onready var title = $TitleScreen
+onready var start = $StartGame
+onready var options = $Options
+
+
+func _on_Start_pressed():
+	start.visible = true
+	title.visible = false
+
+
+func _on_Options_pressed():
+	options.prev_menu = title
+	options.visible = true
+	title.visible = false
+
+
+func _on_Exit_pressed():
+	tree.quit()
+
+
+func _on_RandomBlocks_pressed():
+	Settings.world_type = 0
+	tree.change_scene("res://world/world.tscn")
+
+
+func _on_FlatGrass_pressed():
+	Settings.world_type = 1
+	tree.change_scene("res://world/world.tscn")
+
+
+func _on_BackToTitle_pressed():
+	title.visible = true
+	start.visible = false

+ 236 - 0
3d/voxel/menu/main/main_menu.tscn

@@ -0,0 +1,236 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://menu/main/title.png" type="Texture" id=1]
+[ext_resource path="res://menu/main/splash_text.gd" type="Script" id=2]
+[ext_resource path="res://menu/main/main_menu.gd" type="Script" id=3]
+[ext_resource path="res://menu/main/dark_dirt.png" type="Texture" id=4]
+[ext_resource path="res://menu/options/options.tscn" type="PackedScene" id=5]
+[ext_resource path="res://menu/theme/theme.tres" type="Theme" id=6]
+[ext_resource path="res://menu/button.png" type="Texture" id=7]
+
+[node name="MainMenu" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+theme = ExtResource( 6 )
+script = ExtResource( 3 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Background" type="TextureRect" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+texture = ExtResource( 4 )
+stretch_mode = 2
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TitleScreen" type="VBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Logo" type="CenterContainer" parent="TitleScreen"]
+margin_right = 1600.0
+margin_bottom = 300.0
+rect_min_size = Vector2( 0, 300 )
+
+[node name="Logo" type="TextureRect" parent="TitleScreen/Logo"]
+margin_left = 432.0
+margin_top = 110.0
+margin_right = 1168.0
+margin_bottom = 190.0
+texture = ExtResource( 1 )
+
+[node name="SplashHolder" type="Control" parent="TitleScreen/Logo/Logo"]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="SplashText" type="Label" parent="TitleScreen/Logo/Logo/SplashHolder"]
+modulate = Color( 1, 1, 0, 1 )
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = -110.0
+margin_top = 10.0
+margin_right = -110.0
+margin_bottom = 12.0
+rect_rotation = -20.0
+text = "Made in Godot!"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ButtonHolder" type="HBoxContainer" parent="TitleScreen"]
+margin_top = 304.0
+margin_right = 1600.0
+margin_bottom = 680.0
+size_flags_vertical = 3
+alignment = 1
+
+[node name="MainButtons" type="VBoxContainer" parent="TitleScreen/ButtonHolder"]
+margin_left = 608.0
+margin_right = 992.0
+margin_bottom = 376.0
+custom_constants/separation = 20
+alignment = 1
+
+[node name="Start" type="TextureButton" parent="TitleScreen/ButtonHolder/MainButtons"]
+margin_top = 72.0
+margin_right = 384.0
+margin_bottom = 136.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 7 )
+
+[node name="Label" type="Label" parent="TitleScreen/ButtonHolder/MainButtons/Start"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Start Game"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Options" type="TextureButton" parent="TitleScreen/ButtonHolder/MainButtons"]
+margin_top = 156.0
+margin_right = 384.0
+margin_bottom = 220.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 7 )
+
+[node name="Label" type="Label" parent="TitleScreen/ButtonHolder/MainButtons/Options"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Options"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Exit" type="TextureButton" parent="TitleScreen/ButtonHolder/MainButtons"]
+margin_top = 240.0
+margin_right = 384.0
+margin_bottom = 304.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 7 )
+
+[node name="Label" type="Label" parent="TitleScreen/ButtonHolder/MainButtons/Exit"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Exit Game"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Spacer" type="Control" parent="TitleScreen"]
+margin_top = 684.0
+margin_right = 1600.0
+margin_bottom = 900.0
+rect_min_size = Vector2( 0, 216 )
+
+[node name="StartGame" type="HBoxContainer" parent="."]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+alignment = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="StartButtons" type="VBoxContainer" parent="StartGame"]
+margin_left = 608.0
+margin_right = 992.0
+margin_bottom = 900.0
+custom_constants/separation = 20
+alignment = 1
+
+[node name="Spacer" type="Control" parent="StartGame/StartButtons"]
+margin_top = 292.0
+margin_right = 384.0
+margin_bottom = 356.0
+rect_min_size = Vector2( 0, 64 )
+
+[node name="RandomBlocks" type="TextureButton" parent="StartGame/StartButtons"]
+margin_top = 376.0
+margin_right = 384.0
+margin_bottom = 440.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 7 )
+
+[node name="Label" type="Label" parent="StartGame/StartButtons/RandomBlocks"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Random Blocks"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="FlatGrass" type="TextureButton" parent="StartGame/StartButtons"]
+margin_top = 460.0
+margin_right = 384.0
+margin_bottom = 524.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 7 )
+
+[node name="Label" type="Label" parent="StartGame/StartButtons/FlatGrass"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Flat Grass"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="BackToTitle" type="TextureButton" parent="StartGame/StartButtons"]
+margin_top = 544.0
+margin_right = 384.0
+margin_bottom = 608.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 7 )
+
+[node name="Label" type="Label" parent="StartGame/StartButtons/BackToTitle"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Back"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Options" parent="." instance=ExtResource( 5 )]
+[connection signal="pressed" from="TitleScreen/ButtonHolder/MainButtons/Start" to="." method="_on_Start_pressed"]
+[connection signal="pressed" from="TitleScreen/ButtonHolder/MainButtons/Options" to="." method="_on_Options_pressed"]
+[connection signal="pressed" from="TitleScreen/ButtonHolder/MainButtons/Exit" to="." method="_on_Exit_pressed"]
+[connection signal="pressed" from="StartGame/StartButtons/RandomBlocks" to="." method="_on_RandomBlocks_pressed"]
+[connection signal="pressed" from="StartGame/StartButtons/FlatGrass" to="." method="_on_FlatGrass_pressed"]
+[connection signal="pressed" from="StartGame/StartButtons/BackToTitle" to="." method="_on_BackToTitle_pressed"]

+ 8 - 0
3d/voxel/menu/main/splash_text.gd

@@ -0,0 +1,8 @@
+extends Control
+
+var time := 0.0
+
+
+func _process(delta):
+	time += delta
+	rect_scale = Vector2.ONE * (1 - abs(sin(time * 4)) / 4)

BIN
3d/voxel/menu/main/title.png


+ 34 - 0
3d/voxel/menu/main/title.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/title.png-73a3b55f70af530edac2d45668ba262c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://menu/main/title.png"
+dest_files=[ "res://.import/title.png-73a3b55f70af530edac2d45668ba262c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=false
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 22 - 0
3d/voxel/menu/options/option_buttons.gd

@@ -0,0 +1,22 @@
+extends Control
+
+onready var render_distance_label = $RenderDistanceLabel
+onready var render_distance_slider = $RenderDistanceSlider
+onready var fog_checkbox = $FogCheckBox
+
+
+func _ready():
+	render_distance_slider.value = Settings.render_distance
+	render_distance_label.text = "Render distance: " + str(Settings.render_distance)
+	fog_checkbox.pressed = Settings.fog_enabled
+
+
+func _on_RenderDistanceSlider_value_changed(value):
+	Settings.render_distance = value
+	render_distance_label.text = "Render distance: " + str(value)
+	Settings.save_settings()
+
+
+func _on_FogCheckBox_pressed():
+	Settings.fog_enabled = fog_checkbox.pressed
+	Settings.save_settings()

+ 8 - 0
3d/voxel/menu/options/options.gd

@@ -0,0 +1,8 @@
+extends HBoxContainer
+
+var prev_menu
+
+
+func _on_Back_pressed():
+	prev_menu.visible = true
+	visible = false

+ 112 - 0
3d/voxel/menu/options/options.tscn

@@ -0,0 +1,112 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://menu/options/options.gd" type="Script" id=1]
+[ext_resource path="res://menu/options/option_buttons.gd" type="Script" id=2]
+[ext_resource path="res://menu/button.png" type="Texture" id=3]
+
+[node name="Options" type="HBoxContainer"]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+alignment = 1
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+margin_left = 384.0
+margin_right = 1216.0
+margin_bottom = 900.0
+custom_constants/separation = 93
+alignment = 1
+
+[node name="OptionsBackground" type="TextureRect" parent="VBoxContainer"]
+margin_top = 288.0
+margin_right = 832.0
+margin_bottom = 448.0
+rect_min_size = Vector2( 832, 160 )
+texture = ExtResource( 3 )
+stretch_mode = 2
+
+[node name="OptionButtons" type="GridContainer" parent="VBoxContainer/OptionsBackground"]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -400.0
+margin_top = -66.0
+margin_right = 400.0
+margin_bottom = 66.0
+rect_min_size = Vector2( 800, 128 )
+columns = 2
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="RenderDistanceLabel" type="Label" parent="VBoxContainer/OptionsBackground/OptionButtons"]
+margin_right = 384.0
+margin_bottom = 64.0
+rect_min_size = Vector2( 384, 64 )
+size_flags_vertical = 5
+text = "Render distance: 3"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="RenderDistanceSlider" type="HSlider" parent="VBoxContainer/OptionsBackground/OptionButtons"]
+margin_left = 388.0
+margin_right = 800.0
+margin_bottom = 64.0
+rect_min_size = Vector2( 384, 64 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+min_value = 3.0
+max_value = 10.0
+value = 7.0
+
+[node name="FogLabel" type="Label" parent="VBoxContainer/OptionsBackground/OptionButtons"]
+margin_top = 68.0
+margin_right = 384.0
+margin_bottom = 132.0
+rect_min_size = Vector2( 384, 64 )
+size_flags_vertical = 5
+text = "Fog Enabled"
+
+[node name="FogCheckBox" type="CheckBox" parent="VBoxContainer/OptionsBackground/OptionButtons"]
+margin_left = 388.0
+margin_top = 68.0
+margin_right = 800.0
+margin_bottom = 132.0
+rect_min_size = Vector2( 384, 64 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
+margin_top = 548.0
+margin_right = 832.0
+margin_bottom = 612.0
+alignment = 1
+
+[node name="Back" type="TextureButton" parent="VBoxContainer/HBoxContainer"]
+margin_left = 224.0
+margin_right = 608.0
+margin_bottom = 64.0
+rect_min_size = Vector2( 384, 64 )
+texture_normal = ExtResource( 3 )
+
+[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/Back"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_top = -1.0
+margin_bottom = -18.0
+text = "Back"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+[connection signal="value_changed" from="VBoxContainer/OptionsBackground/OptionButtons/RenderDistanceSlider" to="VBoxContainer/OptionsBackground/OptionButtons" method="_on_RenderDistanceSlider_value_changed"]
+[connection signal="pressed" from="VBoxContainer/OptionsBackground/OptionButtons/FogCheckBox" to="VBoxContainer/OptionsBackground/OptionButtons" method="_on_FogCheckBox_pressed"]
+[connection signal="pressed" from="VBoxContainer/HBoxContainer/Back" to="." method="_on_Back_pressed"]

+ 7 - 0
3d/voxel/menu/theme/TinyUnicode.tres

@@ -0,0 +1,7 @@
+[gd_resource type="DynamicFont" load_steps=2 format=2]
+
+[ext_resource path="res://menu/theme/TinyUnicode.ttf" type="DynamicFontData" id=1]
+
+[resource]
+size = 64
+font_data = ExtResource( 1 )

BIN
3d/voxel/menu/theme/TinyUnicode.ttf


+ 6 - 0
3d/voxel/menu/theme/theme.tres

@@ -0,0 +1,6 @@
+[gd_resource type="Theme" load_steps=2 format=2]
+
+[ext_resource path="res://menu/theme/TinyUnicode.tres" type="DynamicFont" id=1]
+
+[resource]
+default_font = ExtResource( 1 )

+ 1 - 0
3d/voxel/player/crosshair.svg

@@ -0,0 +1 @@
+<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2"><path d="m12 2v10h-10v2 6h10v10h8v-10h10v-8h-10v-10z" fill-opacity=".627451"/><path d="m4 14v4l10.000161.000039-.000161 9.999961h4l-.000161-9.999961 10.000161-.000039v-4l-10.000161.000361.000161-10.000361h-4l.000161 10.000361z" fill="#fefefe" fill-opacity=".862745"/></g></svg>

+ 34 - 0
3d/voxel/player/crosshair.svg.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/crosshair.svg-c15896115a8fc4f09948d0fd31ee95e9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://player/crosshair.svg"
+dest_files=[ "res://.import/crosshair.svg-c15896115a8fc4f09948d0fd31ee95e9.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 94 - 0
3d/voxel/player/player.gd

@@ -0,0 +1,94 @@
+extends KinematicBody
+
+var velocity = Vector3()
+
+var _mouse_motion = Vector2()
+var _selected_block = 6
+
+onready var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
+
+onready var head = $Head
+onready var raycast = $Head/RayCast
+onready var selected_block_texture = $SelectedBlock
+onready var voxel_world = $"../VoxelWorld"
+onready var crosshair = $"../PauseMenu/Crosshair"
+
+
+func _ready():
+	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+
+
+func _process(_delta):
+	# Mouse movement.
+	_mouse_motion.y = clamp(_mouse_motion.y, -1550, 1550)
+	transform.basis = Basis(Vector3(0, _mouse_motion.x * -0.001, 0))
+	head.transform.basis = Basis(Vector3(_mouse_motion.y * -0.001, 0, 0))
+	
+	# Block selection.
+	var position = raycast.get_collision_point()
+	var normal = raycast.get_collision_normal()
+	if Input.is_action_just_pressed("pick_block"):
+		# Block picking.
+		var block_global_position = (position - normal / 2).floor()
+		_selected_block = voxel_world.get_block_global_position(block_global_position)
+	else:
+		# Block prev/next keys.
+		if Input.is_action_just_pressed("prev_block"):
+			_selected_block -= 1
+		if Input.is_action_just_pressed("next_block"):
+			_selected_block += 1
+		_selected_block = wrapi(_selected_block, 1, 30)
+	# Set the appropriate texture.
+	var uv = Chunk.calculate_block_uvs(_selected_block)
+	selected_block_texture.texture.region = Rect2(uv[0] * 512, Vector2.ONE * 64)
+	
+	# Block breaking/placing.
+	if crosshair.visible and raycast.is_colliding():
+		var breaking = Input.is_action_just_pressed("break")
+		var placing = Input.is_action_just_pressed("place")
+		# Either both buttons were pressed or neither are, so stop.
+		if breaking == placing:
+			return
+		
+		if breaking:
+			var block_global_position = (position - normal / 2).floor()
+			voxel_world.set_block_global_position(block_global_position, 0)
+		elif placing:
+			var block_global_position = (position + normal / 2).floor()
+			voxel_world.set_block_global_position(block_global_position, _selected_block)
+
+
+func _physics_process(delta):
+	# Crouching.
+	var crouching = Input.is_action_pressed("crouch")
+	if crouching:
+		head.transform.origin = Vector3(0, 1.2, 0)
+	else:
+		head.transform.origin = Vector3(0, 1.6, 0)
+	
+	# Keyboard movement.
+	var movement = transform.basis.xform(Vector3(
+		Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
+		0,
+		Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
+	).normalized() * (1 if crouching else 5))
+	
+	# Gravity.
+	velocity.y -= gravity * delta
+	
+	#warning-ignore:return_value_discarded
+	velocity = move_and_slide(Vector3(movement.x, velocity.y, movement.z), Vector3.UP)
+	
+	# Jumping, applied next frame.
+	if is_on_floor() and Input.is_action_pressed("jump"):
+		velocity.y = 5
+
+
+func _input(event):
+	if event is InputEventMouseMotion:
+		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
+			_mouse_motion += event.relative
+
+
+func chunk_pos():
+	return (transform.origin / Chunk.CHUNK_SIZE).floor()

+ 48 - 0
3d/voxel/player/player.tscn

@@ -0,0 +1,48 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://player/player.gd" type="Script" id=1]
+[ext_resource path="res://world/textures/texture_sheet.png" type="Texture" id=2]
+
+[sub_resource type="CylinderShape" id=1]
+radius = 0.4
+height = 1.8
+
+[sub_resource type="AtlasTexture" id=2]
+flags = 3
+atlas = ExtResource( 2 )
+region = Rect2( 0, 0, 64, 64 )
+
+[node name="Player" type="KinematicBody"]
+script = ExtResource( 1 )
+
+[node name="CollisionShape" type="CollisionShape" parent="."]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0 )
+shape = SubResource( 1 )
+
+[node name="Head" type="Spatial" parent="."]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0 )
+
+[node name="Camera" type="Camera" parent="Head"]
+fov = 75.0
+near = 0.02
+far = 1000.0
+
+[node name="RayCast" type="RayCast" parent="Head"]
+enabled = true
+cast_to = Vector3( 0, 0, -5 )
+
+[node name="SelectedBlock" type="TextureRect" parent="."]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = -128.0
+margin_top = -128.0
+margin_right = -64.0
+margin_bottom = -64.0
+rect_min_size = Vector2( 64, 64 )
+rect_scale = Vector2( 2, 2 )
+texture = SubResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}

+ 161 - 0
3d/voxel/project.godot

@@ -0,0 +1,161 @@
+; 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=4
+
+_global_script_classes=[ {
+"base": "StaticBody",
+"class": "Chunk",
+"language": "GDScript",
+"path": "res://world/chunk.gd"
+}, {
+"base": "Resource",
+"class": "TerrainGenerator",
+"language": "GDScript",
+"path": "res://world/terrain_generator.gd"
+} ]
+_global_script_class_icons={
+"Chunk": "",
+"TerrainGenerator": ""
+}
+
+[application]
+
+config/name="Voxel Game"
+config/description="This demo is a minimal voxel game, inspired by others such as Minecraft."
+run/main_scene="res://menu/main/main_menu.tscn"
+config/icon="res://icon.png"
+
+[autoload]
+
+Settings="*res://settings.gd"
+
+[display]
+
+window/size/width=1600
+window/size/height=900
+
+[input]
+
+move_forward={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
+ ]
+}
+move_back={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
+ ]
+}
+move_left={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
+ ]
+}
+move_right={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
+ ]
+}
+jump={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+crouch={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+pause={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+break={
+"deadzone": 0.5,
+"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":7,"axis_value":1.0,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":7,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+place={
+"deadzone": 0.5,
+"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":2,"pressed":false,"doubleclick":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":6,"axis_value":1.0,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+look_up={
+"deadzone": 0.5,
+"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":-1.0,"script":null)
+ ]
+}
+look_down={
+"deadzone": 0.5,
+"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":1.0,"script":null)
+ ]
+}
+look_left={
+"deadzone": 0.5,
+"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":-1.0,"script":null)
+ ]
+}
+look_right={
+"deadzone": 0.5,
+"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":1.0,"script":null)
+ ]
+}
+debug={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777246,"unicode":0,"echo":false,"script":null)
+ ]
+}
+prev_block={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":123,"unicode":0,"echo":false,"script":null)
+, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":4,"pressed":false,"doubleclick":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":91,"unicode":0,"echo":false,"script":null)
+ ]
+}
+next_block={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":125,"unicode":0,"echo":false,"script":null)
+, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":5,"pressed":false,"doubleclick":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":93,"unicode":0,"echo":false,"script":null)
+ ]
+}
+pick_block={
+"deadzone": 0.5,
+"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":3,"pressed":false,"doubleclick":false,"script":null)
+ ]
+}
+
+[physics]
+
+common/physics_fps=120
+3d/physics_engine="Bullet"
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
+gles2/debug/disable_half_float=true
+environment/default_environment="res://default_env.tres"

+ 0 - 0
3d/voxel/screenshots/.gdignore


BIN
3d/voxel/screenshots/blocks.png


BIN
3d/voxel/screenshots/title.png


+ 40 - 0
3d/voxel/settings.gd

@@ -0,0 +1,40 @@
+extends Node
+
+var render_distance = 7
+var fog_enabled = true
+
+var world_type = 0 # Not saved, only used during runtime.
+
+var _save_path = "user://settings.json"
+var _loaded = false
+
+
+func _enter_tree():
+	if Settings._loaded:
+		printerr("Error: Settings is an AutoLoad singleton and it shouldn't be instanced elsewhere.")
+		printerr("Please delete the instance at: " + get_path())
+	else:
+		Settings._loaded = true
+	
+	var file = File.new()
+	if file.file_exists(_save_path):
+		file.open(_save_path, File.READ)
+		while file.get_position() < file.get_len():
+			# Get the saved dictionary from the next line in the save file
+			var data = parse_json(file.get_line())
+			render_distance = data["render_distance"]
+			fog_enabled = data["fog_enabled"]
+		file.close()
+	else:
+		save_settings()
+
+
+func save_settings():
+	var file = File.new()
+	file.open(_save_path, File.WRITE)
+	var data = {
+		"render_distance": render_distance,
+		"fog_enabled": fog_enabled,
+	}
+	file.store_line(to_json(data))
+	file.close()

+ 216 - 0
3d/voxel/world/chunk.gd

@@ -0,0 +1,216 @@
+class_name Chunk
+extends StaticBody
+# These chunks are instanced and given data by VoxelWorld.
+# After that, chunks finish setting themselves up in the _ready() function.
+# If a chunk is changed, its "regenerate" method is called.
+
+const CHUNK_SIZE = 16 # Keep in sync with TerrainGenerator.
+const TEXTURE_SHEET_WIDTH = 8
+
+const CHUNK_LAST_INDEX = CHUNK_SIZE - 1
+const TEXTURE_TILE_SIZE = 1.0 / TEXTURE_SHEET_WIDTH
+
+var data = {}
+var chunk_position = Vector3() # TODO: Vector3i
+
+var _thread
+
+onready var voxel_world = get_parent()
+
+
+func _ready():
+	transform.origin = chunk_position * CHUNK_SIZE
+	name = str(chunk_position)
+	if Settings.world_type == 0:
+		data = TerrainGenerator.random_blocks()
+	else:
+		data = TerrainGenerator.flat(chunk_position)
+	
+	# We can only add colliders in the main thread due to physics limitations.
+	_generate_chunk_collider()
+	# However, we can use a thread for mesh generation.
+	_thread = Thread.new()
+	_thread.start(self, "_generate_chunk_mesh")
+
+
+func regenerate():
+	# Clear out all old nodes first.
+	for c in get_children():
+		remove_child(c)
+		c.queue_free()
+	
+	# Then generate new ones.
+	_generate_chunk_collider()
+	_generate_chunk_mesh(0)
+
+
+func _generate_chunk_collider():
+	if data.empty():
+		# Avoid errors caused by StaticBody not having colliders.
+		_create_block_collider(Vector3.ZERO)
+		collision_layer = 0
+		collision_mask = 0
+		return
+	
+	# For each block, generate a collider. Ensure collision layers are enabled.
+	collision_layer = 0xFFFFF
+	collision_mask = 0xFFFFF
+	for block_position in data.keys():
+		var block_id = data[block_position]
+		if block_id != 27 and block_id != 28:
+			_create_block_collider(block_position)
+
+
+func _generate_chunk_mesh(_this_argument_exists_due_to_bug_9924):
+	if data.empty():
+		return
+	
+	var surface_tool = SurfaceTool.new()
+	surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
+	
+	# For each block, add data to the SurfaceTool and generate a collider.
+	for block_position in data.keys():
+		var block_id = data[block_position]
+		_draw_block_mesh(surface_tool, block_position, block_id)
+	
+	# Create the chunk's mesh from the SurfaceTool data.
+	surface_tool.generate_normals()
+	surface_tool.generate_tangents()
+	surface_tool.index()
+	var array_mesh = surface_tool.commit()
+	var mi = MeshInstance.new()
+	mi.mesh = array_mesh
+	mi.material_override = preload("res://world/textures/material.tres")
+	add_child(mi)
+
+
+func _draw_block_mesh(surface_tool, block_sub_position, block_id):
+	var verts = calculate_block_verts(block_sub_position)
+	var uvs = calculate_block_uvs(block_id)
+	var top_uvs = uvs
+	var bottom_uvs = uvs
+	
+	# Bush blocks get drawn in their own special way.
+	if block_id == 27 or block_id == 28:
+		_draw_block_face(surface_tool, [verts[2], verts[0], verts[7], verts[5]], uvs)
+		_draw_block_face(surface_tool, [verts[7], verts[5], verts[2], verts[0]], uvs)
+		_draw_block_face(surface_tool, [verts[3], verts[1], verts[6], verts[4]], uvs)
+		_draw_block_face(surface_tool, [verts[6], verts[4], verts[3], verts[1]], uvs)
+		return
+	
+	# Allow some blocks to have different top/bottom textures.
+	if block_id == 3: # Grass.
+		top_uvs = calculate_block_uvs(0)
+		bottom_uvs = calculate_block_uvs(2)
+	elif block_id == 5: # Furnace.
+		top_uvs = calculate_block_uvs(31)
+		bottom_uvs = top_uvs
+	elif block_id == 12: # Log.
+		top_uvs = calculate_block_uvs(30)
+		bottom_uvs = top_uvs
+	elif block_id == 19: # Bookshelf.
+		top_uvs = calculate_block_uvs(4)
+		bottom_uvs = top_uvs
+	
+	# Main rendering code for normal blocks.
+	var other_block_position = block_sub_position + Vector3.LEFT
+	var other_block_id = 0
+	if other_block_position.x == -1:
+		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
+	elif data.has(other_block_position):
+		other_block_id = data[other_block_position]
+	if block_id != other_block_id and is_block_transparent(other_block_id):
+		_draw_block_face(surface_tool, [verts[2], verts[0], verts[3], verts[1]], uvs)
+	
+	other_block_position = block_sub_position + Vector3.RIGHT
+	other_block_id = 0
+	if other_block_position.x == CHUNK_SIZE:
+		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
+	elif data.has(other_block_position):
+		other_block_id = data[other_block_position]
+	if block_id != other_block_id and is_block_transparent(other_block_id):
+		_draw_block_face(surface_tool, [verts[7], verts[5], verts[6], verts[4]], uvs)
+	
+	other_block_position = block_sub_position + Vector3.FORWARD
+	other_block_id = 0
+	if other_block_position.z == -1:
+		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
+	elif data.has(other_block_position):
+		other_block_id = data[other_block_position]
+	if block_id != other_block_id and is_block_transparent(other_block_id):
+		_draw_block_face(surface_tool, [verts[6], verts[4], verts[2], verts[0]], uvs)
+	
+	other_block_position = block_sub_position + Vector3.BACK
+	other_block_id = 0
+	if other_block_position.z == CHUNK_SIZE:
+		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
+	elif data.has(other_block_position):
+		other_block_id = data[other_block_position]
+	if block_id != other_block_id and is_block_transparent(other_block_id):
+		_draw_block_face(surface_tool, [verts[3], verts[1], verts[7], verts[5]], uvs)
+	
+	other_block_position = block_sub_position + Vector3.DOWN
+	other_block_id = 0
+	if other_block_position.y == -1:
+		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
+	elif data.has(other_block_position):
+		other_block_id = data[other_block_position]
+	if block_id != other_block_id and is_block_transparent(other_block_id):
+		_draw_block_face(surface_tool, [verts[4], verts[5], verts[0], verts[1]], bottom_uvs)
+	
+	other_block_position = block_sub_position + Vector3.UP
+	other_block_id = 0
+	if other_block_position.y == CHUNK_SIZE:
+		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
+	elif data.has(other_block_position):
+		other_block_id = data[other_block_position]
+	if block_id != other_block_id and is_block_transparent(other_block_id):
+		_draw_block_face(surface_tool, [verts[2], verts[3], verts[6], verts[7]], top_uvs)
+
+
+func _draw_block_face(surface_tool, verts, uvs):
+	surface_tool.add_uv(uvs[1]); surface_tool.add_vertex(verts[1])
+	surface_tool.add_uv(uvs[2]); surface_tool.add_vertex(verts[2])
+	surface_tool.add_uv(uvs[3]); surface_tool.add_vertex(verts[3])
+	
+	surface_tool.add_uv(uvs[2]); surface_tool.add_vertex(verts[2])
+	surface_tool.add_uv(uvs[1]); surface_tool.add_vertex(verts[1])
+	surface_tool.add_uv(uvs[0]); surface_tool.add_vertex(verts[0])
+
+
+func _create_block_collider(block_sub_position):
+	var collider = CollisionShape.new()
+	collider.shape = BoxShape.new()
+	collider.shape.extents = Vector3.ONE / 2
+	collider.transform.origin = block_sub_position + Vector3.ONE / 2
+	add_child(collider)
+
+
+static func calculate_block_uvs(block_id):
+	# This method only supports square texture sheets.
+	var row = block_id / TEXTURE_SHEET_WIDTH
+	var col = block_id % TEXTURE_SHEET_WIDTH
+	
+	return [
+		TEXTURE_TILE_SIZE * Vector2(col, row),
+		TEXTURE_TILE_SIZE * Vector2(col, row + 1),
+		TEXTURE_TILE_SIZE * Vector2(col + 1, row),
+		TEXTURE_TILE_SIZE * Vector2(col + 1, row + 1),
+	]
+
+
+static func calculate_block_verts(block_position):
+	return [
+		Vector3(block_position.x, block_position.y, block_position.z),
+		Vector3(block_position.x, block_position.y, block_position.z + 1),
+		Vector3(block_position.x, block_position.y + 1, block_position.z),
+		Vector3(block_position.x, block_position.y + 1, block_position.z + 1),
+		Vector3(block_position.x + 1, block_position.y, block_position.z),
+		Vector3(block_position.x + 1, block_position.y, block_position.z + 1),
+		Vector3(block_position.x + 1, block_position.y + 1, block_position.z),
+		Vector3(block_position.x + 1, block_position.y + 1, block_position.z + 1),
+	]
+
+
+static func is_block_transparent(block_id):
+	return block_id == 0 or (block_id > 25 and block_id < 30)

+ 17 - 0
3d/voxel/world/environment.gd

@@ -0,0 +1,17 @@
+extends WorldEnvironment
+# This script controls fog based on the VoxelWorld's effective render distance.
+
+onready var voxel_world = $"../VoxelWorld"
+
+
+func _process(delta):
+	environment.fog_enabled = Settings.fog_enabled
+	environment.dof_blur_far_enabled = Settings.fog_enabled
+	
+	var target_distance = clamp(voxel_world.effective_render_distance, 2, voxel_world.render_distance - 1) * Chunk.CHUNK_SIZE
+	var rate = delta * 4
+	if environment.fog_depth_end > target_distance:
+		rate *= 2
+	environment.fog_depth_begin = move_toward(environment.fog_depth_begin, target_distance - Chunk.CHUNK_SIZE, rate)
+	environment.fog_depth_end = move_toward(environment.fog_depth_end, target_distance, rate)
+	environment.dof_blur_far_distance = environment.fog_depth_end

+ 42 - 0
3d/voxel/world/terrain_generator.gd

@@ -0,0 +1,42 @@
+class_name TerrainGenerator
+extends Resource
+
+# Can't be "Chunk.CHUNK_SIZE" due to cyclic dependency issues.
+# https://github.com/godotengine/godot/issues/21461
+const CHUNK_SIZE = 16
+
+
+static func empty():
+	return {}
+
+
+static func random_blocks():
+	var random_data = {}
+	for x in range(CHUNK_SIZE):
+		for y in range(CHUNK_SIZE):
+			for z in range(CHUNK_SIZE):
+				var vec = Vector3(x, y, z) # TODO: Vector3i
+				if randf() < 0.01:
+					random_data[vec] = randi() % 29 + 1
+	return random_data
+
+
+static func flat(chunk_position):
+	var data = {}
+	
+	if chunk_position.y != -1:
+		return data
+	
+	for x in range(CHUNK_SIZE):
+		for z in range(CHUNK_SIZE):
+			data[Vector3(x, 0, z)] = 3
+	
+	return data
+
+
+# Used to create the project icon.
+static func origin_grass(chunk_position):
+	if chunk_position == Vector3.ZERO:
+		return {Vector3.ZERO: 3}
+	
+	return {}

+ 8 - 0
3d/voxel/world/textures/material.tres

@@ -0,0 +1,8 @@
+[gd_resource type="SpatialMaterial" load_steps=2 format=2]
+
+[ext_resource path="res://world/textures/texture_sheet.png" type="Texture" id=1]
+
+[resource]
+flags_transparent = true
+params_depth_draw_mode = 3
+albedo_texture = ExtResource( 1 )

BIN
3d/voxel/world/textures/texture_sheet.png


+ 36 - 0
3d/voxel/world/textures/texture_sheet.png.import

@@ -0,0 +1,36 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path.s3tc="res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.s3tc.stex"
+path.etc="res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.etc.stex"
+metadata={
+"imported_formats": [ "s3tc", "etc" ],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://world/textures/texture_sheet.png"
+dest_files=[ "res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.s3tc.stex", "res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.etc.stex" ]
+
+[params]
+
+compress/mode=2
+compress/lossy_quality=1.0
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=true
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0

+ 138 - 0
3d/voxel/world/voxel_world.gd

@@ -0,0 +1,138 @@
+extends Node
+# This file manages the creation and deletion of Chunks.
+
+const CHUNK_MIDPOINT = Vector3(0.5, 0.5, 0.5) * Chunk.CHUNK_SIZE
+const CHUNK_END_SIZE = Chunk.CHUNK_SIZE - 1
+
+var render_distance setget _set_render_distance
+var _delete_distance = 0
+var effective_render_distance = 0
+var _old_player_chunk = Vector3() # TODO: Vector3i
+
+var _generating = true
+var _deleting = false
+
+var _chunks = {}
+
+onready var player = $"../Player"
+
+
+func _process(_delta):
+	_set_render_distance(Settings.render_distance)
+	var player_chunk = (player.transform.origin / Chunk.CHUNK_SIZE).round()
+	
+	if _deleting or player_chunk != _old_player_chunk:
+		_delete_far_away_chunks(player_chunk)
+		_generating = true
+	
+	if not _generating:
+		return
+	
+	# Try to generate chunks ahead of time based on where the player is moving.
+	player_chunk.y += round(clamp(player.velocity.y, -render_distance / 4, render_distance / 4))
+	
+	# Check existing chunks within range. If it doesn't exist, create it.
+	for x in range(player_chunk.x - effective_render_distance, player_chunk.x + effective_render_distance):
+		for y in range(player_chunk.y - effective_render_distance, player_chunk.y + effective_render_distance):
+			for z in range(player_chunk.z - effective_render_distance, player_chunk.z + effective_render_distance):
+				var chunk_position = Vector3(x, y, z)
+				if player_chunk.distance_to(chunk_position) > render_distance:
+					continue
+				
+				if _chunks.has(chunk_position):
+					continue
+				
+				var chunk = Chunk.new()
+				chunk.chunk_position = chunk_position
+				_chunks[chunk_position] = chunk
+				add_child(chunk)
+				return
+	
+	# If we didn't generate any chunks (and therefore didn't return), what next?
+	if effective_render_distance < render_distance:
+		# We can move on to the next stage by increasing the effective distance.
+		effective_render_distance += 1
+	else:
+		# Effective render distance is maxed out, done generating.
+		_generating = false
+
+
+func get_block_global_position(block_global_position):
+	var chunk_position = (block_global_position / Chunk.CHUNK_SIZE).floor()
+	if _chunks.has(chunk_position):
+		var chunk = _chunks[chunk_position]
+		var sub_position = block_global_position.posmod(Chunk.CHUNK_SIZE)
+		if chunk.data.has(sub_position):
+			return chunk.data[sub_position]
+	return 0
+
+
+func set_block_global_position(block_global_position, block_id):
+	var chunk_position = (block_global_position / Chunk.CHUNK_SIZE).floor()
+	var chunk = _chunks[chunk_position]
+	var sub_position = block_global_position.posmod(Chunk.CHUNK_SIZE)
+	if block_id == 0:
+		chunk.data.erase(sub_position)
+	else:
+		chunk.data[sub_position] = block_id
+	chunk.regenerate()
+	
+	# We also might need to regenerate some neighboring chunks.
+	if Chunk.is_block_transparent(block_id):
+		if sub_position.x == 0:
+			_chunks[chunk_position + Vector3.LEFT].regenerate()
+		elif sub_position.x == CHUNK_END_SIZE:
+			_chunks[chunk_position + Vector3.RIGHT].regenerate()
+		if sub_position.z == 0:
+			_chunks[chunk_position + Vector3.FORWARD].regenerate()
+		elif sub_position.z == CHUNK_END_SIZE:
+			_chunks[chunk_position + Vector3.BACK].regenerate()
+		if sub_position.y == 0:
+			_chunks[chunk_position + Vector3.DOWN].regenerate()
+		elif sub_position.y == CHUNK_END_SIZE:
+			_chunks[chunk_position + Vector3.UP].regenerate()
+
+
+func clean_up():
+	for chunk_position_key in _chunks.keys():
+		var thread = _chunks[chunk_position_key]._thread
+		if thread:
+			thread.wait_to_finish()
+	_chunks = {}
+	set_process(false)
+	for c in get_children():
+		c.free()
+
+
+func _delete_far_away_chunks(player_chunk):
+	_old_player_chunk = player_chunk
+	# If we need to delete chunks, give the new chunk system a chance to catch up.
+	effective_render_distance = max(1, effective_render_distance - 1)
+	
+	var deleted_this_frame = 0
+	# We should delete old chunks more aggressively if moving fast.
+	# An easy way to calculate this is by using the effective render distance.
+	# The specific values in this formula are arbitrary and from experimentation.
+	var max_deletions = clamp(2 * (render_distance - effective_render_distance), 2, 8)
+	# Also take the opportunity to delete far away chunks.
+	for chunk_position_key in _chunks.keys():
+		if player_chunk.distance_to(chunk_position_key) > _delete_distance:
+			var thread = _chunks[chunk_position_key]._thread
+			if thread:
+				thread.wait_to_finish()
+			_chunks[chunk_position_key].queue_free()
+			_chunks.erase(chunk_position_key)
+			deleted_this_frame += 1
+			# Limit the amount of deletions per frame to avoid lag spikes.
+			if deleted_this_frame > max_deletions:
+				# Continue deleting next frame.
+				_deleting = true
+				return
+	
+	# We're done deleting.
+	_deleting = false
+
+
+func _set_render_distance(value):
+	render_distance = value
+	_delete_distance = value + 2

+ 38 - 0
3d/voxel/world/world.tscn

@@ -0,0 +1,38 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://player/player.tscn" type="PackedScene" id=1]
+[ext_resource path="res://world/voxel_world.gd" type="Script" id=2]
+[ext_resource path="res://default_env.tres" type="Environment" id=3]
+[ext_resource path="res://world/environment.gd" type="Script" id=4]
+[ext_resource path="res://menu/ingame/pause_menu.tscn" type="PackedScene" id=5]
+[ext_resource path="res://menu/debug.gd" type="Script" id=6]
+[ext_resource path="res://menu/theme/theme.tres" type="Theme" id=7]
+
+[node name="World" type="Spatial"]
+
+[node name="Player" parent="." instance=ExtResource( 1 )]
+
+[node name="Debug" type="Label" parent="."]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 20.0
+margin_right = -20.0
+margin_bottom = -20.0
+theme = ExtResource( 7 )
+script = ExtResource( 6 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="PauseMenu" parent="." instance=ExtResource( 5 )]
+
+[node name="VoxelWorld" type="Node" parent="."]
+script = ExtResource( 2 )
+
+[node name="Environment" type="WorldEnvironment" parent="."]
+environment = ExtResource( 3 )
+script = ExtResource( 4 )
+
+[node name="Sun" type="DirectionalLight" parent="Environment"]
+transform = Transform( 0.173648, -0.564863, 0.806707, 0, 0.819152, 0.573576, -0.984808, -0.0996005, 0.142244, 0, 0, 0 )