chore: init

This commit is contained in:
dusk 2024-08-19 13:56:56 +03:00
commit 822116d607
Signed by: dusk
SSH Key Fingerprint: SHA256:Abmvag+juovVufZTxyWY8KcVgrznxvBjQpJesv071Aw
13 changed files with 470 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# godot
.godot

21
LICENSE.txt Normal file
View File

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

View File

@ -0,0 +1,10 @@
@tool
extends EditorPlugin
func _enter_tree() -> void:
add_custom_type("DynamicWater2D", "Node2D", preload("water.gd"), preload("dynamic_water_2d.svg"))
func _exit_tree() -> void:
remove_custom_type("DynamicWater2D")

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="15.999992"
height="15.99998"
viewBox="0 0 15.999992 15.99998"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
sodipodi:docname="dynamic_water_2d.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="32"
inkscape:cx="8.140625"
inkscape:cy="6.609375"
inkscape:window-width="1858"
inkscape:window-height="1057"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
id="grid1"
units="mm"
originx="-60.000008"
originy="-125"
spacingx="0.99999998"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-60.000007,-125)">
<path
style="fill:#45ffa2;fill-opacity:1;stroke-width:0.98994;stroke-linejoin:bevel"
d="m 61.000009,138.125 3.111109,-1.75 2.333332,1.75 3.111108,-1.75 2.333333,1.75 L 75,136.375 v 1.75 l -3.111109,1.75 -2.333333,-1.75 -3.111108,1.75 -2.333332,-1.75 -3.111109,1.75 v -1.75"
id="path3" />
<path
style="fill:#45ffa2;fill-opacity:1;stroke-width:0.98994;stroke-linejoin:bevel"
d="m 61.000009,132.96875 3.111109,-1.75 2.333332,1.75 3.111108,-1.75 2.333333,1.75 3.111109,-1.75 v 1.75 l -3.111109,1.75 -2.333333,-1.75 -3.111108,1.75 -2.333332,-1.75 -3.111109,1.75 v -1.75"
id="path4" />
<path
style="fill:#45ffa2;fill-opacity:1;stroke-width:0.98994;stroke-linejoin:bevel"
d="m 61.000009,128.125 3.111109,-1.75 2.333332,1.75 3.111108,-1.75 2.333333,1.75 L 75,126.375 v 1.75 l -3.111109,1.75 -2.333333,-1.75 -3.111108,1.75 -2.333332,-1.75 -3.111109,1.75 v -1.75"
id="path5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dj0ovl2e65oy1"
path="res://.godot/imported/dynamic_water_2d.svg-b9cd5300b6b73b0bd8d2e21de8ccddd1.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dynamic_water_2d/dynamic_water_2d.svg"
dest_files=["res://.godot/imported/dynamic_water_2d.svg-b9cd5300b6b73b0bd8d2e21de8ccddd1.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@ -0,0 +1,7 @@
[plugin]
name="DynamicWater2D"
description="Simple sideview water that reacts to forces applied to it."
author="yusdacra"
version="0.1"
script="dynamic_water_2d.gd"

View File

@ -0,0 +1,217 @@
@tool
extends Node2D
class_name DynamicWater2D
## controls which node to use for pulling the top left corner of the water from.
@export var top_left_marker: Node2D
## controls which node to use for pulling the bottom right corner of the water from.
@export var bottom_right_marker: Node2D
@export_group("visuals")
## controls the thickness of the surface water.
@export var surface_thickness: float = 6.0
## controls the color of the water on the surface.
@export var surface_color: Color = Color("2c998e66")
## controls the color of the water below the surface.
@export var water_color: Color = Color("87e0d733")
@export_group("waves")
## enables or disables passive waves.
@export var waves_enabled: bool = true
## controls how high the passive waves are.
@export var wave_height: float = 4.0
## controls how quick the passive waves are.
@export var wave_speed: float = 4.0
## controls how wide the passive waves are.
@export var wave_width: float = 16.0
## controls how many times forces should be calculated between neighbouring points per frame.
## higher values means waves travel faster.
@export var wave_spread_amount: int = 4
@export_group("points")
## controls how many surface points are created per nth unit of distance.
## this is basically the "resolution" of the surface water.
@export_range(2, 32, 2) var point_per_distance: int = 8
## controls the damping that will be applied to all points each frame.
## lower value will cause motion to die out quicker.
@export_range(0.0, 1.0) var point_damping = 0.98
## controls the stiffness between a point and it's resting y pos.
@export var point_independent_stiffness: float = 1.0
## controls the stiffness between neighbouring points.
## higher values mean motion is transferred between points quicker.
@export var point_neighbouring_stiffness: float = 2.0
var top_left_point: Vector2
var top_right_point: Vector2
var bottom_right_point: Vector2
var bottom_left_point: Vector2
var extents_valid: bool = false
var points_positions: PackedVector2Array = PackedVector2Array([])
var points_motions: PackedVector2Array = PackedVector2Array([])
func point_add(pos: Vector2) -> void:
points_positions.append(pos)
points_motions.append(Vector2.ZERO)
func points_size() -> int:
return points_positions.size()
func points_clear() -> void:
points_positions.clear()
points_motions.clear()
func point_global_pos(point_idx: int) -> Vector2:
return position + points_positions[point_idx]
## add some motion (force) to a given point.
func point_add_motion(point_idx: int, d: Vector2) -> Vector2:
var motion := points_motions[point_idx]; motion += d
points_motions[point_idx] = motion
return d
func _point_calc_motion(point_idx: int, target_y: float, stiffness: float) -> void:
var target_point := Vector2(point_global_pos(point_idx).x, target_y)
var motion := (target_point - point_global_pos(point_idx)) * stiffness
points_motions[point_idx] += motion
func _point_calc_physics(point_idx: int, delta: float) -> void:
var motion := points_motions[point_idx]
var pos := points_positions[point_idx]
pos += motion * delta
motion *= point_damping
points_motions[point_idx] = motion
points_positions[point_idx] = pos
func _points_get_circle(origin: Vector2, radius: float) -> Array[int]:
var results: Array[int] = []
# convert global coords to local coords
var local_pos := to_local(origin)
# find the furthest positions that could be affected to the left and right
var left_most := local_pos.x - radius
var right_most := local_pos.x + radius
# convert those local positions to indices in the "points" array
var left_most_index := _get_index_from_local_pos(left_most)
var right_most_index := _get_index_from_local_pos(right_most)
# test which points are in the circle provided
for idx in range(left_most_index, right_most_index + 1):
var point_pos := points_positions[idx]
var dx := absf(point_pos.x - origin.x)
var dy := absf(point_pos.y - origin.y)
if dx + dy <= radius:
results.append(idx); continue
if dx ** 2 + dy ** 2 <= radius ** 2:
results.append(idx); continue
return results
func _ready() -> void:
calc_extents()
calc_surface_points()
## calculates the extents of the water.
func calc_extents() -> void:
top_left_point = top_left_marker.position
bottom_right_point = bottom_right_marker.position
extents_valid = _validate_extents()
if not extents_valid:
push_error("invalid extents: top left corner cannot be bigger or equal on the X or Y axis than the bottom right corner")
return
top_right_point = Vector2(bottom_right_point.x, top_left_point.y)
bottom_left_point = Vector2(top_left_point.x, bottom_right_point.y)
func _validate_extents() -> bool:
var is_x_axis_valid := top_left_point.x < bottom_right_point.x
var is_y_axis_valid := top_left_point.y < bottom_right_point.y
return is_x_axis_valid and is_y_axis_valid
## calculates surface points.
func calc_surface_points() -> void:
points_clear()
if not extents_valid: return
# populate the points arrays
var point_amount := int(floor((top_right_point.x - top_left_point.x) / point_per_distance))
for i in range(point_amount):
var pos := Vector2(top_left_point.x + (point_per_distance * (i + 0.5)), top_left_point.y)
point_add(pos)
func _process(delta: float) -> void:
# update extents and recalculate surface points if any of our size markers change position
if (not top_left_point.is_equal_approx(top_left_marker.position)
or not bottom_right_point.is_equal_approx(bottom_right_marker.position)):
calc_extents()
calc_surface_points()
# only process if extents are valid
if not extents_valid: return
var target_y := global_position.y + top_left_point.y
var points_len := points_size()
for idx in range(points_len):
# calculate motion for point
_point_calc_motion(idx, target_y, point_independent_stiffness)
# add the passive wave if enabled
if waves_enabled:
var time := fmod(float(Time.get_ticks_msec()) / 1000.0, PI * 2.0)
point_add_motion(idx, Vector2.UP * sin(((idx / float(points_len)) * wave_width) + (time * wave_speed)) * wave_height)
# calculate and apply spring forces between neighbouring points
for j in range(wave_spread_amount):
var apply_nforce: Callable = func(nidx: int) -> void:
_point_calc_motion(idx, point_global_pos(nidx).y, point_neighbouring_stiffness)
# to the left
if idx - 1 >= 0: apply_nforce.call(idx - 1)
# to the right
if idx + 1 < points_len: apply_nforce.call(idx + 1)
# run surface point physics
for idx in range(points_len):
_point_calc_physics(idx, delta)
queue_redraw()
## apply some force to provided position.
## will be applied as a circle, all points in the radius will be affected.
func apply_force(pos: Vector2, force: Vector2, radius: float = 16.0) -> void:
# ignore if position outside of area
if (point_global_pos(0).x - radius * 2) > pos.x or (point_global_pos(points_size() - 1).x + radius * 2) < pos.x:
return
var local_pos := to_local(pos)
# get points around the pos
var idxs := _points_get_circle(pos, radius)
for idx in idxs:
# direct force to the point
force *= local_pos.direction_to(points_positions[idx])
point_add_motion(idx, force)
func _get_index_from_local_pos(x: float) -> int:
# returns an index of the "points" array on water's surface to the local pos
var index = floor((abs(top_left_point.x - x) / (top_right_point.x - top_left_point.x)) * points_size())
# ensure the index is a possible index of the array
return int(clamp(index, 0, points_size() - 1))
func _draw() -> void:
if not extents_valid: return
var surface := PackedVector2Array([top_left_point])
var polygon := PackedVector2Array([top_left_point])
var colors := PackedColorArray([water_color])
for idx in range(points_size()):
surface.append(points_positions[idx])
polygon.append(points_positions[idx])
colors.append(water_color)
surface.append(top_right_point)
for p in [top_right_point, bottom_right_point, bottom_left_point]:
polygon.append(p)
colors.append(water_color)
draw_polygon(polygon, colors)
draw_polyline(surface, surface_color, surface_thickness, true)

View File

@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3 uid="uid://cor8igwxetpgn"]
[ext_resource type="Script" path="res://addons/dynamic_water_2d/water.gd" id="1"]
[node name="Water" type="Node2D" node_paths=PackedStringArray("top_left_marker", "bottom_right_marker")]
script = ExtResource("1")
top_left_marker = NodePath("TopLeft")
bottom_right_marker = NodePath("BottomRight")
[node name="TopLeft" type="Marker2D" parent="."]
position = Vector2(0, 64)
gizmo_extents = 32.0
[node name="BottomRight" type="Marker2D" parent="."]
position = Vector2(480, 272)
gizmo_extents = 32.0

View File

@ -0,0 +1,11 @@
@tool
extends Node2D
@onready var water: DynamicWater2D = $Water
func _process(_delta: float) -> void:
var mouse_coords := water.get_global_mouse_position()
# apply 128 units of downwards force on mouse position in a 48 unit radius
water.apply_force(mouse_coords, 128.0 * Vector2.DOWN, 48.0)

View File

@ -0,0 +1,16 @@
[gd_scene load_steps=4 format=3 uid="uid://dua6al8j0x7wx"]
[ext_resource type="Script" path="res://examples/dynamic_water/example.gd" id="1_tfv2h"]
[ext_resource type="Texture2D" uid="uid://c2vwnj65wuypc" path="res://examples/dynamic_water/icon.png" id="1_yawdq"]
[ext_resource type="PackedScene" uid="uid://cor8igwxetpgn" path="res://addons/dynamic_water_2d/water.tscn" id="2_wkqr4"]
[node name="Example" type="Node2D"]
script = ExtResource("1_tfv2h")
[node name="Icon" type="Sprite2D" parent="."]
position = Vector2(241, 167)
texture = ExtResource("1_yawdq")
[node name="Water" parent="." instance=ExtResource("2_wkqr4")]
surface_thickness = 4.0
waves_enabled = false

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c2vwnj65wuypc"
path="res://.godot/imported/icon.png-7881f45d0d39850fc2fce1a9552a2e02.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://examples/dynamic_water/icon.png"
dest_files=["res://.godot/imported/icon.png-7881f45d0d39850fc2fce1a9552a2e02.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

25
project.godot Normal file
View File

@ -0,0 +1,25 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
run/main_scene="res://examples/dynamic_water/example.tscn"
config/features=PackedStringArray("4.3")
[display]
window/size/viewport_width=480
window/size/viewport_height=270
window/size/resizable=false
window/stretch/mode="2d"
[editor_plugins]
enabled=PackedStringArray("res://addons/dynamic_water_2d/plugin.cfg")