Compare commits
6 Commits
0f90dd61f2
...
22b6023849
Author | SHA1 | Date | |
---|---|---|---|
22b6023849 | |||
d8c60fbf41 | |||
24ccf0ddbe | |||
7a96f549ff | |||
3bb4ed31e6 | |||
15b8a20493 |
@ -6,21 +6,14 @@ var target := Vector3.ZERO
|
||||
var acceleration := Vector3.ZERO
|
||||
var velocity := Vector3.ZERO
|
||||
|
||||
export var acceleration_factor = 5.0
|
||||
export var accelerating := false
|
||||
|
||||
export var coefficient_of_restitution := 0.5
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
if accelerating:
|
||||
var direction = (target - global_transform.origin).normalized()
|
||||
acceleration += direction * acceleration_factor * delta
|
||||
velocity += acceleration
|
||||
|
||||
var collision = move_and_collide(velocity * delta)
|
||||
|
||||
if collision:
|
||||
# Reflect the bullet and decrease the velocity according to the coefficient of restitution
|
||||
var normal = collision.normal
|
||||
velocity = coefficient_of_restitution * (velocity - 2 * velocity.dot(normal) * normal)
|
||||
|
||||
|
40
DeadzoneInput.gd
Normal file
40
DeadzoneInput.gd
Normal file
@ -0,0 +1,40 @@
|
||||
extends Object
|
||||
class_name DeadzoneInput
|
||||
|
||||
|
||||
static func get_input(type, outer_deadzone, inner_deadzone, min_length = 0.0, normalized = true):
|
||||
var input = Vector2(Input.get_action_strength(type + "_up") -
|
||||
Input.get_action_strength(type + "_down"),
|
||||
Input.get_action_strength(type + "_right") -
|
||||
Input.get_action_strength(type + "_left"))
|
||||
|
||||
# Remove signs to reduce the number of cases
|
||||
var signs = Vector2(sign(input.x), sign(input.y))
|
||||
input = Vector2(abs(input.x), abs(input.y))
|
||||
|
||||
if input.length() < min_length:
|
||||
return Vector2.ZERO
|
||||
|
||||
# Deazones for each axis
|
||||
if input.x > outer_deadzone:
|
||||
input.x = 1.0
|
||||
elif input.x < inner_deadzone:
|
||||
input.x = 0.0
|
||||
else:
|
||||
input.x = inverse_lerp(inner_deadzone, outer_deadzone, input.x)
|
||||
|
||||
if input.y > outer_deadzone:
|
||||
input.y = 1.0
|
||||
elif input.y < inner_deadzone:
|
||||
input.y = 0.0
|
||||
else:
|
||||
input.y = inverse_lerp(inner_deadzone, outer_deadzone, input.y)
|
||||
|
||||
# Re-apply signs
|
||||
input *= signs
|
||||
|
||||
# Limit at length 1
|
||||
if normalized and input.length() > 1.0:
|
||||
input /= input.length()
|
||||
|
||||
return input
|
39
Orbiter.gd
Normal file
39
Orbiter.gd
Normal file
@ -0,0 +1,39 @@
|
||||
extends KinematicBody
|
||||
|
||||
# Orbits the parent planet by velocity.
|
||||
|
||||
|
||||
var time_passed := 0.0
|
||||
var velocity := Vector3.ZERO
|
||||
|
||||
export(float) var move_speed_factor = 1.0
|
||||
export(NodePath) var solar_system
|
||||
|
||||
|
||||
func _ready():
|
||||
var velocity_value = get_node(solar_system).get_orbit_velocity(global_transform.origin, get_parent())
|
||||
|
||||
# We need a vector perpendicular to the gravity acceleration to apply the velocity along.
|
||||
# So we get the cross product of that and some arbitrary other vector, in this case Vector3.RIGHT
|
||||
var gravity_acceleration = get_node(solar_system).get_closest_gravity_acceleration(global_transform.origin)
|
||||
var other = Vector3.RIGHT
|
||||
|
||||
# Apply the velocity
|
||||
velocity = gravity_acceleration.cross(other).normalized() * velocity_value
|
||||
|
||||
|
||||
func _process(delta):
|
||||
# Apply gravity as acceleration to velocity, then velocity to position
|
||||
var gravity_acceleration = get_node(solar_system).get_closest_gravity_acceleration(global_transform.origin)
|
||||
velocity += gravity_acceleration * delta
|
||||
|
||||
move_and_slide(velocity)
|
||||
|
||||
# Rotate down vector to face center of gravity -> tidally locked
|
||||
var down = gravity_acceleration
|
||||
var local_down = transform.basis * Vector3.DOWN
|
||||
var angle = local_down.angle_to(down)
|
||||
var axis = local_down.cross(down).normalized()
|
||||
|
||||
if axis != Vector3.ZERO: # Happens if we're perfectly aligned already (local_down and down are equal)
|
||||
rotate(axis, angle)
|
16
Planets.gd
16
Planets.gd
@ -21,14 +21,17 @@ func get_closest_gravity_acceleration(position: Vector3) -> Vector3:
|
||||
var closest_planet_distance = INF
|
||||
var closest_force = 0.0
|
||||
|
||||
# Iterate through all planets to find the closest one
|
||||
for planet in get_children():
|
||||
var pos_to_center = (planet.transform.origin - position)
|
||||
var distance = pos_to_center.length()
|
||||
|
||||
if distance < closest_planet_distance:
|
||||
# This planet is closer than the previous ones -> calculate and save the values
|
||||
var force = _gravity(planet.mass * mass_multiplier, distance * distance_multiplier)
|
||||
force *= gravity_multiplier
|
||||
|
||||
# Multiply by the normalized vector towards the center to give the force a direction
|
||||
closest_force = (pos_to_center / distance) * force
|
||||
closest_planet_distance = distance
|
||||
|
||||
@ -39,6 +42,7 @@ func get_closest_gravity_acceleration(position: Vector3) -> Vector3:
|
||||
func get_gravity_acceleration(position: Vector3) -> Vector3:
|
||||
var total_force = Vector3.ZERO
|
||||
|
||||
# Add each planet's gravity force to the total force
|
||||
for planet in get_children():
|
||||
var pos_to_center = (planet.transform.origin - position)
|
||||
var distance = pos_to_center.length()
|
||||
@ -51,6 +55,18 @@ func get_gravity_acceleration(position: Vector3) -> Vector3:
|
||||
return total_force
|
||||
|
||||
|
||||
# Return the velocity needed to orbit the given planet
|
||||
func get_orbit_velocity(position: Vector3, planet):
|
||||
var pos_to_center = (planet.global_transform.origin - position)
|
||||
var distance = pos_to_center.length()
|
||||
|
||||
# raw_velocity is the velocity according to the formula, but we also need to account for the gravity_multiplier.
|
||||
var raw_velocity = -sqrt((G * planet.mass * mass_multiplier) / (distance * distance_multiplier))
|
||||
var gravity_scale_factor = sqrt(gravity_multiplier / distance_multiplier)
|
||||
|
||||
return raw_velocity * gravity_scale_factor
|
||||
|
||||
|
||||
# Formula for gravity
|
||||
static func _gravity(mass, distance):
|
||||
return (G * mass) / (distance * distance)
|
||||
|
32
Player.gd
32
Player.gd
@ -44,8 +44,7 @@ func get_center():
|
||||
func apply_acceleration(acceleration):
|
||||
# First drag, then add the new acceleration
|
||||
# For drag: Lerp towards the target velocity
|
||||
# This is usually 0, unless we're on something that's moving, in which case it is that object's
|
||||
# velocity
|
||||
# This is usually 0, unless we're on something that's moving, in which case it is that object's velocity
|
||||
velocity = lerp(velocity, current_target_velocity, drag)
|
||||
velocity += acceleration
|
||||
|
||||
@ -61,8 +60,7 @@ func get_gravity_acceleration():
|
||||
var distance_to_collision = ($GroundCheckRay.get_collision_point()
|
||||
- $GroundCheckRay.global_transform.origin).length()
|
||||
|
||||
# This factor is 0.0 if the player is exactly on the ground, and 1.0 if they're just barely
|
||||
# almost grounded
|
||||
# This factor is 0.0 if the player is exactly on the ground, and 1.0 if they're just barely almost grounded
|
||||
var factor = inverse_lerp(0.0, almost_on_ground_length, distance_to_collision)
|
||||
|
||||
return lerp(planet_gravity, total_gravity, factor)
|
||||
@ -111,24 +109,23 @@ func _physics_process(delta):
|
||||
var move_velocity := Vector3.ZERO
|
||||
var move_acceleration := Vector3.ZERO
|
||||
|
||||
# Movement and rotation
|
||||
if Input.is_action_pressed("move_up"):
|
||||
move_acceleration.z -= move_accel
|
||||
if Input.is_action_pressed("move_down"):
|
||||
move_acceleration.z += move_accel
|
||||
if Input.is_action_pressed("move_left"):
|
||||
rotate(transform.basis.y, delta * rotate_speed)
|
||||
if Input.is_action_pressed("move_right"):
|
||||
rotate(transform.basis.y, -delta * rotate_speed)
|
||||
# Apply input
|
||||
var input = DeadzoneInput.get_input("move", 0.65, 0.2, 0.0, false)
|
||||
|
||||
# Either do velocity-based movement or force-based movement
|
||||
if Input.is_action_pressed("movement_modifier"):
|
||||
move_velocity = transform.basis * Vector3.FORWARD * 15.0 * input.x
|
||||
else:
|
||||
move_acceleration += transform.basis * Vector3.FORWARD * move_accel * input.x
|
||||
|
||||
rotate(transform.basis.y, delta * rotate_speed * -input.y)
|
||||
|
||||
# Make movement local
|
||||
move_acceleration = transform.basis * move_acceleration
|
||||
move_acceleration = move_acceleration
|
||||
|
||||
# Get acceleration caused by gravity
|
||||
var gravity_acceleration = get_gravity_acceleration()
|
||||
|
||||
# FIXME: Consider setting the gravity_acceleration to 0 if on ground
|
||||
|
||||
# Apply both acceleration types
|
||||
apply_acceleration((move_acceleration + gravity_acceleration) * delta)
|
||||
|
||||
@ -150,7 +147,8 @@ func _physics_process(delta):
|
||||
reset_moving_platform_velocity()
|
||||
|
||||
# Apply movement to position
|
||||
velocity = move_and_slide(velocity)
|
||||
# Add and subtract the move_velocity (used for velocity-based movement) because we do want to apply it, but not remember it for next frame
|
||||
velocity = move_and_slide(velocity + move_velocity) - move_velocity
|
||||
|
||||
# Clamp the velocity just to be save
|
||||
velocity.x = clamp(velocity.x, -MAX_VEL, MAX_VEL)
|
||||
|
@ -1,5 +1,8 @@
|
||||
extends KinematicBody
|
||||
|
||||
# An enemy which shoots at the future player position and moves between two targets within a given
|
||||
# amount of time.
|
||||
|
||||
|
||||
export(NodePath) var player_node
|
||||
onready var player = get_node(player_node)
|
||||
@ -7,46 +10,83 @@ onready var player = get_node(player_node)
|
||||
export(NodePath) var solar_system_node
|
||||
onready var solar_system = get_node(solar_system_node)
|
||||
|
||||
export(Array, NodePath) var target_paths
|
||||
|
||||
export var target_fly_time := 5.0
|
||||
var time_passed_since_fly_start := 0.0
|
||||
var current_target_index = 0
|
||||
|
||||
var bullet_scene = preload("res://Bullet.tscn")
|
||||
var bullet_velocity = 40.0
|
||||
|
||||
var target
|
||||
var velocity := Vector3(0.0, 1.0, 1.0)
|
||||
var shoot_target
|
||||
var velocity := Vector3.ZERO
|
||||
|
||||
|
||||
func _ready():
|
||||
# Shoot everytime the timer times out
|
||||
$ShotTimer.connect("timeout", self, "shoot_bullet")
|
||||
|
||||
# Set up pathing: Start at the first target
|
||||
global_transform.origin = get_node(target_paths[0]).global_transform.origin
|
||||
_set_velocity_to_fly_towards_next_target()
|
||||
|
||||
|
||||
# Picks the next target position from the `target_paths` list and sets the `velocity` appropriately
|
||||
# so that the target will be reached after `target_fly_time`.
|
||||
func _set_velocity_to_fly_towards_next_target():
|
||||
current_target_index = (current_target_index + 1) % target_paths.size()
|
||||
|
||||
# Vector from here to the new target
|
||||
var vector_to_next = (get_node(target_paths[current_target_index]).global_transform.origin
|
||||
- global_transform.origin)
|
||||
|
||||
# s = v * t --> v = s / t
|
||||
velocity = vector_to_next / target_fly_time
|
||||
time_passed_since_fly_start = 0.0
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
# Project the player's position into the future
|
||||
target = _get_future_position(
|
||||
shoot_target = _get_future_position(
|
||||
player.get_center(),
|
||||
player.velocity - velocity
|
||||
)
|
||||
|
||||
if target:
|
||||
# Look at the target that will be shot at, with the gravity as the down vector
|
||||
if shoot_target:
|
||||
var gravity = solar_system.get_gravity_acceleration(global_transform.origin)
|
||||
look_at(target, gravity)
|
||||
look_at(shoot_target, gravity)
|
||||
|
||||
# Have we rearched the target?
|
||||
time_passed_since_fly_start += delta
|
||||
if time_passed_since_fly_start >= target_fly_time:
|
||||
# If so, fly towards the next target
|
||||
_set_velocity_to_fly_towards_next_target()
|
||||
|
||||
# Apply velocity
|
||||
global_transform.origin += velocity * delta
|
||||
|
||||
|
||||
func shoot_bullet():
|
||||
if not target:
|
||||
if not shoot_target:
|
||||
# Player can't be hit right now, abort
|
||||
return
|
||||
|
||||
# Add a bullet
|
||||
var instance = bullet_scene.instance()
|
||||
get_tree().get_root().add_child(instance)
|
||||
|
||||
# Make the bullet start at the current position of this object and fly towards the target.
|
||||
# The own velocity is added because of galilean relativity.
|
||||
instance.global_transform.origin = global_transform.origin - global_transform.basis.z
|
||||
instance.velocity = velocity + (target - global_transform.origin).normalized() * bullet_velocity
|
||||
instance.velocity = velocity + (shoot_target - global_transform.origin).normalized() * bullet_velocity
|
||||
|
||||
|
||||
# Return the position at which a bullet fired forwards and with the velocity `bullet_velocity` would
|
||||
# intersect with the object that is at `position` and moving with the given `velocity`
|
||||
func _get_future_position(position, velocity):
|
||||
# Solution to the quadratic formula gives us the time at which the player would be hit
|
||||
# TODO: Take acceleration into account as well!
|
||||
var a = pow(velocity.x, 2) + pow(velocity.y, 2) + pow(velocity.z, 2) - pow(bullet_velocity, 2)
|
||||
var b = 2 * (velocity.x * (position.x - global_transform.origin.x)
|
||||
+ velocity.y * (position.y - global_transform.origin.y)
|
||||
@ -67,4 +107,5 @@ func _get_future_position(position, velocity):
|
||||
var t = min(t1, t2)
|
||||
if t < 0: t = max(t1, t2)
|
||||
|
||||
# Return the position given by the time we calculated
|
||||
return position + t * velocity
|
||||
|
26
World.gd
Normal file
26
World.gd
Normal file
@ -0,0 +1,26 @@
|
||||
extends Spatial
|
||||
|
||||
|
||||
const ray_length = 2000
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
if Input.is_mouse_button_pressed(1):
|
||||
# If the left mouse button was pressed, cast a ray towards the mouse position
|
||||
var mouse_pos = get_viewport().get_mouse_position()
|
||||
|
||||
var camera = $LerpedFollow/LerpedCamera
|
||||
var from = camera.project_ray_origin(mouse_pos)
|
||||
var to = from + camera.project_ray_normal(mouse_pos) * ray_length
|
||||
|
||||
var result = get_world().direct_space_state.intersect_ray(from, to)
|
||||
|
||||
if not result.empty():
|
||||
# If there was a collision, set the depth of the water at that position
|
||||
$Water.set_depth_at_position(result.position, 0.0)
|
||||
|
||||
|
||||
func _ready():
|
||||
# Create some initial waves
|
||||
for i in range(0, 11):
|
||||
$Water.set_depth_at_position(Vector3(-50 + i * 10, -66, -50), 0.0)
|
61
World.tscn
61
World.tscn
File diff suppressed because one or more lines are too long
21
addons/interactive_water/PersistentShaderTexture.tscn
Normal file
21
addons/interactive_water/PersistentShaderTexture.tscn
Normal file
@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/interactive_water/WaterHeights.gd" type="Script" id=1]
|
||||
|
||||
[node name="PersistentShaderTexture" type="Node"]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Viewport" type="Viewport" parent="."]
|
||||
size = Vector2( 1, 1 )
|
||||
transparent_bg = true
|
||||
handle_input_locally = false
|
||||
hdr = false
|
||||
disable_3d = true
|
||||
usage = 0
|
||||
render_target_v_flip = true
|
||||
render_target_update_mode = 3
|
||||
|
||||
[node name="Texture" type="ColorRect" parent="Viewport"]
|
||||
margin_right = 128.0
|
||||
margin_bottom = 128.0
|
||||
rect_min_size = Vector2( 128, 128 )
|
72
addons/interactive_water/Water.gd
Normal file
72
addons/interactive_water/Water.gd
Normal file
@ -0,0 +1,72 @@
|
||||
extends Spatial
|
||||
|
||||
|
||||
# Saves every frame as a PNG in the project directory. Use for debugging (with caution)
|
||||
export var output_debug_textures := false
|
||||
export var first_output_frame := 0
|
||||
|
||||
export var plane_size := 2.0
|
||||
|
||||
var _frame_number := 0
|
||||
var _positions_to_set = []
|
||||
|
||||
|
||||
func set_depth_at_position(pos: Vector3, depth: float):
|
||||
# Transform the global position into 2D points on the water surface with values between 0 and 1
|
||||
pos -= translation
|
||||
|
||||
var position_2d = Vector2(pos.x, pos.z)
|
||||
|
||||
position_2d += Vector2(plane_size, plane_size) / 2.0
|
||||
position_2d /= plane_size
|
||||
|
||||
if position_2d.x < 1.0 and position_2d.y < 1.0 \
|
||||
and position_2d.x >= 0.0 and position_2d.y >= 0.0:
|
||||
_positions_to_set.append([position_2d, depth])
|
||||
|
||||
|
||||
func _ready():
|
||||
$WaterMesh.mesh.size = Vector2(plane_size, plane_size)
|
||||
|
||||
# Apply the scale and positioning to the collider
|
||||
$WaterMesh/StaticBody.scale.x = plane_size / 2.0
|
||||
$WaterMesh/StaticBody.scale.y = plane_size / 2.0
|
||||
$WaterMesh/StaticBody.scale.z = plane_size / 2.0
|
||||
|
||||
$WaterMesh/StaticBody.translation.y = 0.6 * 40.0
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
# Get result of previous frame
|
||||
var result = $WaterHeights.get_texture()
|
||||
|
||||
# Set it as the data of the water mesh
|
||||
$WaterMesh.material_override.set_shader_param("water_heights", result)
|
||||
|
||||
# Calculate a new frame. First, get the data of the last frame and modify it accordingly
|
||||
var image_data = result.get_data()
|
||||
|
||||
# Set outstanding pixels (after some frames, since the texture isn't initialized until then)
|
||||
if _frame_number >= 60:
|
||||
for position_and_depth in _positions_to_set:
|
||||
var pos = position_and_depth[0]
|
||||
var depth = position_and_depth[1]
|
||||
|
||||
image_data.lock()
|
||||
image_data.set_pixel(floor(pos.x * 512), floor(pos.y * 512), Color(depth, 0.0, 0.0, 0.0))
|
||||
image_data.unlock()
|
||||
|
||||
_positions_to_set.clear()
|
||||
|
||||
# Create an ImageTexture for this new frame
|
||||
var previous_frame = ImageTexture.new()
|
||||
previous_frame.create_from_image(image_data)
|
||||
|
||||
# Set the previous texture in the shader so that a new texture will be available next frame
|
||||
$WaterHeights.set_previous_texture(previous_frame)
|
||||
|
||||
# Debug output if needed
|
||||
if output_debug_textures and _frame_number > first_output_frame:
|
||||
image_data.save_png("res://debugframes/frame%s.png" % [_frame_number])
|
||||
|
||||
_frame_number += 1
|
18
addons/interactive_water/Water.shader
Normal file
18
addons/interactive_water/Water.shader
Normal file
@ -0,0 +1,18 @@
|
||||
shader_type spatial;
|
||||
|
||||
uniform sampler2D water_heights;
|
||||
|
||||
|
||||
float read_height(sampler2D tex, vec2 uv) {
|
||||
return texture(tex, uv).r + texture(tex, uv).g / 255.0;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
ALBEDO = vec3(0.5, 0.7, 1.0) * (read_height(water_heights, UV) - 0.6) * 3.0;
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
float vertex_distance = length(VERTEX) * 0.06;
|
||||
|
||||
VERTEX += NORMAL * (read_height(water_heights, UV) * 40.0 - pow(2, vertex_distance));
|
||||
}
|
38
addons/interactive_water/Water.tscn
Normal file
38
addons/interactive_water/Water.tscn
Normal file
@ -0,0 +1,38 @@
|
||||
[gd_scene load_steps=9 format=2]
|
||||
|
||||
[ext_resource path="res://addons/interactive_water/Water.shader" type="Shader" id=1]
|
||||
[ext_resource path="res://addons/interactive_water/Water.gd" type="Script" id=3]
|
||||
[ext_resource path="res://addons/interactive_water/WaterUpdateMaterial.tres" type="Material" id=4]
|
||||
[ext_resource path="res://addons/interactive_water/PersistentShaderTexture.tscn" type="PackedScene" id=5]
|
||||
|
||||
[sub_resource type="ViewportTexture" id=1]
|
||||
viewport_path = NodePath("WaterHeightViewport")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=2]
|
||||
resource_local_to_scene = true
|
||||
shader = ExtResource( 1 )
|
||||
shader_param/water_heights = SubResource( 1 )
|
||||
|
||||
[sub_resource type="PlaneMesh" id=3]
|
||||
subdivide_width = 512
|
||||
subdivide_depth = 512
|
||||
|
||||
[sub_resource type="PlaneShape" id=4]
|
||||
|
||||
[node name="Water" type="Spatial"]
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="WaterMesh" type="MeshInstance" parent="."]
|
||||
material_override = SubResource( 2 )
|
||||
mesh = SubResource( 3 )
|
||||
material/0 = null
|
||||
|
||||
[node name="StaticBody" type="StaticBody" parent="WaterMesh"]
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0 )
|
||||
|
||||
[node name="CollisionShape" type="CollisionShape" parent="WaterMesh/StaticBody"]
|
||||
shape = SubResource( 4 )
|
||||
|
||||
[node name="WaterHeights" parent="." instance=ExtResource( 5 )]
|
||||
size = Vector2( 512, 512 )
|
||||
shader_material = ExtResource( 4 )
|
30
addons/interactive_water/WaterHeights.gd
Normal file
30
addons/interactive_water/WaterHeights.gd
Normal file
@ -0,0 +1,30 @@
|
||||
extends Node
|
||||
|
||||
# A texture which is continuously (statefully) updated by a shader.
|
||||
#
|
||||
# Typical usage: Retrieve the current result with `get_texture` and, at the end of the frame,
|
||||
# re-insert that texture (updated if needed) with `set_previous_texture`.
|
||||
#
|
||||
# Note that the given shader needs to accept a `previous_frame` sampler2D. This represents the
|
||||
# result of the last frame which is used for generating the new result.
|
||||
|
||||
|
||||
export var size := Vector2(64, 64)
|
||||
export var shader_material: ShaderMaterial
|
||||
|
||||
|
||||
func _ready():
|
||||
$Viewport/Texture.rect_min_size = size
|
||||
$Viewport/Texture.rect_size = size
|
||||
$Viewport.size = size
|
||||
|
||||
assert(shader_material)
|
||||
$Viewport/Texture.material = shader_material
|
||||
|
||||
|
||||
func get_texture():
|
||||
return $Viewport.get_texture()
|
||||
|
||||
|
||||
func set_previous_texture(texture):
|
||||
$Viewport/Texture.material.set_shader_param("previous_frame", texture)
|
63
addons/interactive_water/WaterUpdate.shader
Normal file
63
addons/interactive_water/WaterUpdate.shader
Normal file
@ -0,0 +1,63 @@
|
||||
shader_type canvas_item;
|
||||
render_mode unshaded, blend_disabled;
|
||||
|
||||
uniform sampler2D previous_frame;
|
||||
|
||||
uniform float water_height = 0.6;
|
||||
|
||||
uniform float height_damping = 0.13;
|
||||
uniform float velocity_damping = 0.8;
|
||||
uniform float spread = 0.94;
|
||||
|
||||
// Height and Velocity are encoded in two components each, so RG is height and BA is velocity.
|
||||
// This is needed to get a workable accuracy.
|
||||
float read_height(sampler2D tex, vec2 uv) {
|
||||
return texture(tex, uv).r + texture(tex, uv).g / 255.0;
|
||||
}
|
||||
float read_velocity(sampler2D tex, vec2 uv) {
|
||||
return texture(tex, uv).b + texture(tex, uv).a / 255.0;
|
||||
}
|
||||
float get_encoded_remainder(float num) {
|
||||
return fract(num * 255.0);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Read values here
|
||||
float height_here = read_height(previous_frame, UV);
|
||||
float velocity_here = read_velocity(previous_frame, UV);
|
||||
|
||||
// Apply force towards the base height
|
||||
// This follows from the damped harmonic oscillator equation F = -kx-bv
|
||||
float force = -height_damping * (height_here - water_height) - velocity_here * velocity_damping;
|
||||
float acceleration_here = force;
|
||||
|
||||
// In addition to each individual height behaving like a spring, neighbouring heights are
|
||||
// "connected by springs" as well:
|
||||
|
||||
// Read more samples
|
||||
float uv_mod = 1.0 / float(textureSize(previous_frame, 0).x);
|
||||
|
||||
float height_up = read_height(previous_frame, UV + vec2(0.0, uv_mod));
|
||||
float height_down = read_height(previous_frame, UV + vec2(0.0, -uv_mod));
|
||||
float height_left = read_height(previous_frame, UV + vec2(-uv_mod, 0.0));
|
||||
float height_right = read_height(previous_frame, UV + vec2(uv_mod, 0.0));
|
||||
|
||||
// Calculate differences
|
||||
float up_delta = spread * (height_up - height_here);
|
||||
float down_delta = spread * (height_down - height_here);
|
||||
float left_delta = spread * (height_left - height_here);
|
||||
float right_delta = spread * (height_right - height_here);
|
||||
|
||||
// Use the biggest delta to apply to this height
|
||||
float sum_delta = max(max(left_delta, right_delta), max(up_delta, down_delta));
|
||||
|
||||
// Apply velocity and height
|
||||
velocity_here += sum_delta + acceleration_here;
|
||||
height_here += velocity_here;
|
||||
|
||||
// Write to the texture
|
||||
COLOR = vec4(
|
||||
height_here, get_encoded_remainder(height_here),
|
||||
velocity_here, get_encoded_remainder(velocity_here)
|
||||
);
|
||||
}
|
10
addons/interactive_water/WaterUpdateMaterial.tres
Normal file
10
addons/interactive_water/WaterUpdateMaterial.tres
Normal file
@ -0,0 +1,10 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/interactive_water/WaterUpdate.shader" type="Shader" id=1]
|
||||
|
||||
[resource]
|
||||
shader = ExtResource( 1 )
|
||||
shader_param/water_height = 0.6
|
||||
shader_param/height_damping = 0.13
|
||||
shader_param/velocity_damping = 0.8
|
||||
shader_param/spread = 0.94
|
@ -9,6 +9,11 @@
|
||||
config_version=4
|
||||
|
||||
_global_script_classes=[ {
|
||||
"base": "Object",
|
||||
"class": "DeadzoneInput",
|
||||
"language": "GDScript",
|
||||
"path": "res://DeadzoneInput.gd"
|
||||
}, {
|
||||
"base": "KinematicBody",
|
||||
"class": "MovingPlatform",
|
||||
"language": "GDScript",
|
||||
@ -25,6 +30,7 @@ _global_script_classes=[ {
|
||||
"path": "res://Planets.gd"
|
||||
} ]
|
||||
_global_script_class_icons={
|
||||
"DeadzoneInput": "",
|
||||
"MovingPlatform": "",
|
||||
"Planet": "",
|
||||
"SolarSystem": ""
|
||||
@ -63,28 +69,38 @@ ui_down={
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"deadzone": 0.5,
|
||||
"deadzone": 0.0,
|
||||
"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(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.5,
|
||||
"deadzone": 0.0,
|
||||
"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(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.5,
|
||||
"deadzone": 0.0,
|
||||
"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(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.5,
|
||||
"deadzone": 0.0,
|
||||
"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(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)
|
||||
]
|
||||
}
|
||||
movement_modifier={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user