├── .gitattributes ├── icon.png ├── TestSetup ├── body.material ├── rock.material ├── tread.material ├── cockpit.material ├── Test.tscn ├── Player.gd └── Player.tscn ├── .gitignore ├── default_env.tres ├── icon.png.import ├── floatingOrigin.gd ├── README.md ├── LICENSE └── project.godot /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tobalation/Godot-floating-origin/HEAD/icon.png -------------------------------------------------------------------------------- /TestSetup/body.material: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tobalation/Godot-floating-origin/HEAD/TestSetup/body.material -------------------------------------------------------------------------------- /TestSetup/rock.material: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tobalation/Godot-floating-origin/HEAD/TestSetup/rock.material -------------------------------------------------------------------------------- /TestSetup/tread.material: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tobalation/Godot-floating-origin/HEAD/TestSetup/tread.material -------------------------------------------------------------------------------- /TestSetup/cockpit.material: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tobalation/Godot-floating-origin/HEAD/TestSetup/cockpit.material -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /TestSetup/Test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TestSetup/BigWorld.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TestSetup/Player.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://floatingOrigin.gd" type="Script" id=3] 6 | 7 | [node name="Game" type="Node"] 8 | 9 | [node name="3D world root" type="Spatial" parent="."] 10 | script = ExtResource( 3 ) 11 | threshold = 1000.0 12 | 13 | [node name="BigWorld" parent="3D world root" instance=ExtResource( 1 )] 14 | 15 | [node name="Player" parent="3D world root" instance=ExtResource( 2 )] 16 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 ) 17 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /floatingOrigin.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | # Amount of distance before shifting 4 | export(float) var threshold : float = 2000.0 5 | 6 | # Reference to main camera 7 | onready var camera : Camera = get_viewport().get_camera() 8 | 9 | # Function to contain origin shift logic 10 | func shift_origin() -> void: 11 | # Shift everything by the offset of the camera's position 12 | global_transform.origin -= camera.global_transform.origin 13 | print("World shifted to " + str(global_transform.origin)) 14 | 15 | func _physics_process(delta: float) -> void: 16 | # Set the camera to check to be the current camera 17 | camera = get_viewport().get_camera() 18 | # Check distance of world from camera and shift if greater than threshold 19 | if(camera.global_transform.origin.length() > threshold && camera != null): 20 | shift_origin() 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot-floating-origin 2 | A floating origin system example for godot 3.X and up 3 | 4 | 5 | This project serves as an example for how to create a floating origin system for large worlds in Godot engine. It eliminates floating point precision errors. 6 | 7 | ## Usage in your own project 8 | Make sure all your 3D spatial elements are under a single parent spatial node and attach the ```floatingOrigin.gd``` script and set the threshold value. (about 1000 is fine) 9 | 10 | If you aren't sure about what that means, have a look at the example project first. 11 | 12 | ## How does it work? 13 | The 3D world is all parented under a single root spatial node. When the main viewport camera moves a certain distance away from the world origin the root spatial node is shifted to the camera's position. 14 | 15 | This keeps the visible world centered around the camera, allowing for high floating point precision. 16 | 17 | 18 | ## Known issues 19 | I haven't found any yet so let me know if you try it out and find something. 20 | 21 | This system isn't perfect and is more of a proof of concept at the moment but should work as intended. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /TestSetup/Player.gd: -------------------------------------------------------------------------------- 1 | extends KinematicBody 2 | 3 | # Super simple kinematic body controller 4 | 5 | export var gravity = Vector3.DOWN * 9.8 6 | export var speed = 2000.0 7 | export var rot_speed = 0.85 8 | export var turboFactor = 10.0 9 | 10 | var velocity = Vector3.ZERO 11 | 12 | func get_input(delta): 13 | # Reset velocity 14 | velocity = Vector3.ZERO 15 | 16 | # Obtain throttle and steering 17 | var throttle = Input.get_action_strength("forward") - Input.get_action_strength("back") 18 | var steering = Input.get_action_strength("left") - Input.get_action_strength("right") 19 | 20 | # Check turbo 21 | if Input.is_action_pressed("turbo"): 22 | throttle *= turboFactor 23 | 24 | # Move and rotate the body according to input 25 | velocity += -transform.basis.z * throttle * speed * delta 26 | 27 | rotate_y(rot_speed * steering * delta) 28 | 29 | # Check movement and emit particles based on input 30 | var movement = throttle + steering 31 | if movement > 0: 32 | $Visuals/DustParticlesL.emitting = true 33 | $Visuals/DustParticlesR.emitting = true 34 | else: 35 | $Visuals/DustParticlesL.emitting = false 36 | $Visuals/DustParticlesR.emitting = false 37 | 38 | func _physics_process(delta): 39 | # Apply gravity 40 | velocity += gravity * delta 41 | # Apply inputs and move 42 | get_input(delta) 43 | velocity = move_and_slide(velocity, Vector3.UP) 44 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | } 14 | 15 | [application] 16 | 17 | config/name="Godot-floating-origin" 18 | run/main_scene="res://TestSetup/Test.tscn" 19 | config/icon="res://icon.png" 20 | 21 | [input] 22 | 23 | forward={ 24 | "deadzone": 0.5, 25 | "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":16777232,"unicode":0,"echo":false,"script":null) 26 | , 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) 27 | ] 28 | } 29 | back={ 30 | "deadzone": 0.5, 31 | "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":16777234,"unicode":0,"echo":false,"script":null) 32 | , 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) 33 | ] 34 | } 35 | left={ 36 | "deadzone": 0.5, 37 | "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":16777231,"unicode":0,"echo":false,"script":null) 38 | , 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) 39 | ] 40 | } 41 | right={ 42 | "deadzone": 0.5, 43 | "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":16777233,"unicode":0,"echo":false,"script":null) 44 | , 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) 45 | ] 46 | } 47 | turbo={ 48 | "deadzone": 0.5, 49 | "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) 50 | ] 51 | } 52 | 53 | [physics] 54 | 55 | 3d/active_soft_world=false 56 | 3d/smooth_trimesh_collision=true 57 | 58 | [rendering] 59 | 60 | quality/filters/msaa=2 61 | environment/default_environment="res://default_env.tres" 62 | -------------------------------------------------------------------------------- /TestSetup/Player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=17 format=2] 2 | 3 | [ext_resource path="res://TestSetup/Player.gd" type="Script" id=1] 4 | [ext_resource path="res://TestSetup/rock.material" type="Material" id=2] 5 | [ext_resource path="res://TestSetup/cockpit.material" type="Material" id=3] 6 | [ext_resource path="res://TestSetup/tread.material" type="Material" id=4] 7 | [ext_resource path="res://TestSetup/body.material" type="Material" id=5] 8 | 9 | [sub_resource type="BoxShape" id=1] 10 | extents = Vector3( 1.4, 0.4, 1.4 ) 11 | 12 | [sub_resource type="CubeMesh" id=2] 13 | material = ExtResource( 5 ) 14 | size = Vector3( 1, 0.4, 2 ) 15 | 16 | [sub_resource type="CubeMesh" id=3] 17 | size = Vector3( 1.8, 0.2, 1 ) 18 | 19 | [sub_resource type="CubeMesh" id=4] 20 | material = ExtResource( 5 ) 21 | size = Vector3( 0.6, 0.5, 1 ) 22 | 23 | [sub_resource type="PrismMesh" id=5] 24 | material = ExtResource( 3 ) 25 | size = Vector3( 0.8, 1.8, 0.8 ) 26 | 27 | [sub_resource type="CapsuleMesh" id=6] 28 | material = ExtResource( 4 ) 29 | radius = 0.4 30 | mid_height = 2.0 31 | radial_segments = 32 32 | 33 | [sub_resource type="ParticlesMaterial" id=7] 34 | direction = Vector3( 0, 1, 0.1 ) 35 | spread = 20.0 36 | flatness = 0.2 37 | initial_velocity = 4.0 38 | scale = 1.2 39 | scale_random = 1.0 40 | 41 | [sub_resource type="SpatialMaterial" id=8] 42 | flags_unshaded = true 43 | albedo_color = Color( 0.25, 0.210449, 0.144531, 1 ) 44 | 45 | [sub_resource type="SphereMesh" id=9] 46 | material = SubResource( 8 ) 47 | radius = 0.1 48 | height = 0.2 49 | radial_segments = 8 50 | rings = 4 51 | 52 | [sub_resource type="GDScript" id=10] 53 | script/source = "extends Label 54 | 55 | export(NodePath) var playerPath 56 | 57 | onready var player : Spatial = get_node(playerPath) 58 | 59 | func _process(delta: float) -> void: 60 | text = \"Current global position: \" + str(player.global_transform.origin) 61 | " 62 | 63 | [sub_resource type="GDScript" id=11] 64 | script/source = "extends Label 65 | 66 | export(NodePath) var playerPath 67 | 68 | onready var player : Spatial = get_node(playerPath) 69 | 70 | func _process(delta: float) -> void: 71 | text = \"Current local position: \" + str(player.transform.origin) 72 | " 73 | 74 | [node name="Player" type="KinematicBody"] 75 | script = ExtResource( 1 ) 76 | 77 | [node name="CollisionShape" type="CollisionShape" parent="."] 78 | shape = SubResource( 1 ) 79 | 80 | [node name="Camera" type="Camera" parent="."] 81 | transform = Transform( 1, 0, 0, 0, 0.984808, 0.173648, 0, -0.173648, 0.984808, 0, 5, 12 ) 82 | far = 10000.0 83 | 84 | [node name="Visuals" type="Spatial" parent="."] 85 | 86 | [node name="body" type="MeshInstance" parent="Visuals"] 87 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, 0 ) 88 | mesh = SubResource( 2 ) 89 | material/0 = null 90 | 91 | [node name="body2" type="MeshInstance" parent="Visuals"] 92 | mesh = SubResource( 3 ) 93 | material/0 = ExtResource( 2 ) 94 | 95 | [node name="body3" type="MeshInstance" parent="Visuals"] 96 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.8 ) 97 | mesh = SubResource( 4 ) 98 | material/0 = null 99 | 100 | [node name="cockpit" type="MeshInstance" parent="Visuals"] 101 | transform = Transform( -4.37114e-08, 0, -1, 1, -4.37114e-08, -4.37114e-08, -4.37114e-08, -1, 1.91069e-15, 0, 0.4, -0.5 ) 102 | mesh = SubResource( 5 ) 103 | material/0 = null 104 | 105 | [node name="tread R" type="MeshInstance" parent="Visuals"] 106 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 ) 107 | mesh = SubResource( 6 ) 108 | material/0 = null 109 | 110 | [node name="tread L" type="MeshInstance" parent="Visuals"] 111 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 ) 112 | mesh = SubResource( 6 ) 113 | material/0 = null 114 | 115 | [node name="DustParticlesL" type="Particles" parent="Visuals"] 116 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 1.4 ) 117 | cast_shadow = 0 118 | emitting = false 119 | amount = 16 120 | local_coords = false 121 | process_material = SubResource( 7 ) 122 | draw_pass_1 = SubResource( 9 ) 123 | 124 | [node name="DustParticlesR" type="Particles" parent="Visuals"] 125 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1.4 ) 126 | cast_shadow = 0 127 | emitting = false 128 | amount = 16 129 | local_coords = false 130 | process_material = SubResource( 7 ) 131 | draw_pass_1 = SubResource( 9 ) 132 | 133 | [node name="HUD" type="Control" parent="."] 134 | margin_right = 256.0 135 | margin_bottom = 256.0 136 | __meta__ = { 137 | "_edit_use_anchors_": false 138 | } 139 | 140 | [node name="VBoxContainer" type="VBoxContainer" parent="HUD"] 141 | margin_left = 30.0 142 | margin_right = 300.0 143 | margin_bottom = 200.0 144 | custom_constants/separation = 10 145 | alignment = 1 146 | __meta__ = { 147 | "_edit_use_anchors_": false 148 | } 149 | 150 | [node name="Help" type="Label" parent="HUD/VBoxContainer"] 151 | margin_top = 69.0 152 | margin_right = 270.0 153 | margin_bottom = 83.0 154 | custom_colors/font_color_shadow = Color( 0, 0, 0, 1 ) 155 | text = "Hold shift to go really fast." 156 | __meta__ = { 157 | "_edit_use_anchors_": false 158 | } 159 | 160 | [node name="GlobalPos" type="Label" parent="HUD/VBoxContainer"] 161 | margin_top = 93.0 162 | margin_right = 270.0 163 | margin_bottom = 107.0 164 | custom_colors/font_color_shadow = Color( 0, 0, 0, 1 ) 165 | text = "Current global position: (0,0,0)" 166 | script = SubResource( 10 ) 167 | __meta__ = { 168 | "_edit_use_anchors_": false 169 | } 170 | playerPath = NodePath("../../..") 171 | 172 | [node name="LocalPos" type="Label" parent="HUD/VBoxContainer"] 173 | margin_top = 117.0 174 | margin_right = 270.0 175 | margin_bottom = 131.0 176 | custom_colors/font_color_shadow = Color( 0, 0, 0, 1 ) 177 | text = "Current local position: (0,0,0)" 178 | script = SubResource( 11 ) 179 | __meta__ = { 180 | "_edit_use_anchors_": false 181 | } 182 | playerPath = NodePath("../../..") 183 | --------------------------------------------------------------------------------