ile-de-test/addons/waterways/water_helper_methods.gd
2024-01-26 21:00:32 +01:00

244 lines
9.9 KiB
GDScript

# Copyright © 2023 Kasper Arnklit Frandsen - MIT License
# See `LICENSE.md` included in the source distribution for details.
#const RiverManager = preload("./river_manager.gd")
static func cart2bary(p : Vector3, a : Vector3, b : Vector3, c: Vector3) -> Vector3:
var v0 := b - a
var v1 := c - a
var v2 := p - a
var d00 := v0.dot(v0)
var d01 := v0.dot(v1)
var d11 := v1.dot(v1)
var d20 := v2.dot(v0)
var d21 := v2.dot(v1)
var denom := d00 * d11 - d01 * d01
var v = (d11 * d20 - d01 * d21) / denom
var w = (d00 * d21 - d01 * d20) / denom
var u = 1.0 - v - w
return Vector3(u, v, w)
static func bary2cart(a : Vector3, b : Vector3, c: Vector3, barycentric: Vector3) -> Vector3:
return barycentric.x * a + barycentric.y * b + barycentric.z * c
static func point_in_bariatric(v : Vector3) -> bool:
return 0 <= v.x and v.x <= 1 and 0 <= v.y and v.y <= 1 and 0 <= v.z and v.z <= 1;
static func sum_array(array : Array[float]) -> float:
var sum := 0.0
for element in array:
sum += element
return sum
static func calculate_side(steps : int) -> int:
var side_float : float = sqrt(steps)
if fmod(side_float, 1.0) != 0.0:
side_float += 1.0
return int(side_float)
static func generate_river_width_values(curve : Curve3D, steps : int, step_length_divs : int, step_width_divs : int, widths : Array[float]) -> Array[float]:
var river_width_values: Array[float]
var length := curve.get_baked_length()
for step in steps * step_length_divs + 1:
var target_pos := curve.sample_baked((float(step) / float(steps * step_length_divs + 1)) * curve.get_baked_length())
var closest_dist := 4096.0
var closest_interpolate : float
var closest_point : int
for c_point in curve.get_point_count() - 1:
for i in 100:
var interpolate := float(i) / 100.0
var pos := curve.sample(c_point, interpolate)
var dist = pos.distance_to(target_pos)
if dist < closest_dist:
closest_dist = dist
closest_interpolate = interpolate
closest_point = c_point
river_width_values.append( lerp(widths[closest_point], widths[closest_point + 1], closest_interpolate) )
return river_width_values
static func generate_river_mesh(curve: Curve3D, steps: int, step_length_divs: int, step_width_divs: int, smoothness: float, river_width_values: Array[float]) -> Mesh:
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var curve_length := curve.get_baked_length()
st.set_smooth_group(0)
# Generating the verts
for step in steps * step_length_divs + 1:
var position := curve.sample_baked(float(step) / float(steps * step_length_divs) * curve_length, false)
var backward_pos := curve.sample_baked((float(step) - smoothness) / float(steps * step_length_divs) * curve_length, false)
var forward_pos := curve.sample_baked((float(step) + smoothness) / float(steps * step_length_divs) * curve_length, false)
var forward_vector := forward_pos - backward_pos
var right_vector := forward_vector.cross(Vector3.UP).normalized()
var width_lerp : float = river_width_values[step]
for w_sub in step_width_divs + 1:
st.set_uv(Vector2(float(w_sub) / (float(step_width_divs)), float(step) / float(step_length_divs) ))
st.add_vertex(position + right_vector * width_lerp - 2.0 * right_vector * width_lerp * float(w_sub) / (float(step_width_divs)))
# Defining the tris
for step in steps * step_length_divs:
for w_sub in step_width_divs:
st.add_index( (step * (step_width_divs + 1)) + w_sub)
st.add_index( (step * (step_width_divs + 1)) + w_sub + 1)
st.add_index( (step * (step_width_divs + 1)) + w_sub + 2 + step_width_divs - 1)
st.add_index( (step * (step_width_divs + 1)) + w_sub + 1)
st.add_index( (step * (step_width_divs + 1)) + w_sub + 3 + step_width_divs - 1)
st.add_index( (step * (step_width_divs + 1)) + w_sub + 2 + step_width_divs - 1)
st.generate_normals()
st.generate_tangents()
st.deindex()
var mesh := ArrayMesh.new()
var mesh2 := ArrayMesh.new()
var mesh3 := ArrayMesh.new()
mesh = st.commit()
var mdt := MeshDataTool.new()
mdt.create_from_surface(mesh, 0)
# Generate UV2
# Decide on grid size
var grid_side := calculate_side(steps)
var grid_side_length := 1.0 / float(grid_side)
var x_grid_sub_length := grid_side_length / float(step_width_divs)
var y_grid_sub_length := grid_side_length / float(step_length_divs)
var grid_size := pow(grid_side, 2)
var index := 0
var UVs := steps * step_width_divs * step_length_divs * 6
var x_offset := 0.0
for x in grid_side:
var y_offset := 0.0
for y in grid_side:
if index < UVs:
var sub_y_offset := 0.0
for sub_y in step_length_divs:
var sub_x_offset := 0.0
for sub_x in step_width_divs:
var x_comb_offset := x_offset + sub_x_offset
var y_comb_offset := y_offset + sub_y_offset
mdt.set_vertex_uv2(index, Vector2(x_comb_offset, y_comb_offset))
mdt.set_vertex_uv2(index + 1, Vector2(x_comb_offset + x_grid_sub_length, y_comb_offset))
mdt.set_vertex_uv2(index + 2, Vector2(x_comb_offset, y_comb_offset + y_grid_sub_length))
mdt.set_vertex_uv2(index + 3, Vector2(x_comb_offset + x_grid_sub_length, y_comb_offset))
mdt.set_vertex_uv2(index + 4, Vector2(x_comb_offset + x_grid_sub_length, y_comb_offset + y_grid_sub_length))
mdt.set_vertex_uv2(index + 5, Vector2(x_comb_offset, y_comb_offset + y_grid_sub_length))
index += 6
sub_x_offset += grid_side_length / float(step_width_divs)
sub_y_offset += grid_side_length / float(step_length_divs)
y_offset += grid_side_length
x_offset += grid_side_length
mdt.commit_to_surface(mesh2)
st.clear()
st.create_from(mesh2, 0)
st.index()
mesh3 = st.commit()
return mesh3
static func generate_collisionmap(image: Image, mesh_instance: MeshInstance3D, raycast_dist: float, raycast_layers: int, steps: int, step_length_divs: int, step_width_divs: int, river) -> Image:
var space_state := mesh_instance.get_world_3d().direct_space_state
var uv2 := mesh_instance.mesh.surface_get_arrays(0)[5] as PackedVector2Array
var verts := mesh_instance.mesh.surface_get_arrays(0)[0] as PackedVector3Array
# We need to move the verts into world space
var world_verts := PackedVector3Array()
for v in verts.size():
world_verts.append( mesh_instance.global_transform * (verts[v]) )
var tris_in_step_quad := step_length_divs * step_width_divs * 2
var side := calculate_side(steps)
var percentage = 0.0
river.emit_signal("progress_notified", percentage, "Calculating Collisions (" + str(image.get_width()) + "x" + str(image.get_width()) + ")")
await river.get_tree().process_frame
#var ray_params := PhysicsRayQueryParameters3D.create(Vector3(0.0, 5.0, 0.0), Vector3(0.0, 0.0, 0.0), raycast_layers)
#ray_params_up.collision_mask = raycast_layers
#var result = space_state.intersect_ray(ray_params)
#print(result)
for x in image.get_width():
var cur_percentage := float(x) / float(image.get_width())
if cur_percentage > percentage + 0.1:
percentage += 0.1
river.emit_signal("progress_notified", percentage, "Calculating Collisions (" + str(image.get_width()) + "x" + str(image.get_width()) + ")")
await river.get_tree().process_frame
for y in image.get_height():
var uv_coordinate := Vector2( ( 0.5 + float(x)) / float(image.get_width()), ( 0.5 + float(y)) / float(image.get_height()) )
var baryatric_coords : Vector3
var correct_triangle := []
var pixel := int(x * image.get_width() + y)
var column := (pixel / image.get_width()) / (image.get_width() / side)
var row := (pixel % image.get_width()) / (image.get_width() / side)
var step_quad := column * side + row
if step_quad >= steps:
break # we are in the empty part of UV2 so we break to the next column
for tris in tris_in_step_quad:
var offset_tris: int = (tris_in_step_quad * step_quad) + tris
var triangle := PackedVector2Array()
triangle.append(uv2[offset_tris * 3])
triangle.append(uv2[offset_tris * 3 + 1])
triangle.append(uv2[offset_tris * 3 + 2])
var p := Vector3(uv_coordinate.x, uv_coordinate.y, 0.0)
var a := Vector3(uv2[offset_tris * 3].x, uv2[offset_tris * 3].y, 0.0)
var b := Vector3(uv2[offset_tris * 3 + 1].x, uv2[offset_tris * 3 + 1].y, 0.0)
var c := Vector3(uv2[offset_tris * 3 + 2].x, uv2[offset_tris * 3 + 2].y, 0.0)
baryatric_coords = cart2bary(p, a, b, c)
if point_in_bariatric(baryatric_coords):
correct_triangle = [offset_tris * 3, offset_tris * 3 + 1, offset_tris * 3 + 2]
break # we have the correct triangle so we break out of loop
if correct_triangle:
var vert0: Vector3 = world_verts[correct_triangle[0]]
var vert1: Vector3 = world_verts[correct_triangle[1]]
var vert2: Vector3 = world_verts[correct_triangle[2]]
var real_pos := bary2cart(vert0, vert1, vert2, baryatric_coords)
var real_pos_up := real_pos + Vector3.UP * raycast_dist
var ray_params_up := PhysicsRayQueryParameters3D.create(real_pos, real_pos_up, raycast_layers)
var result_up = space_state.intersect_ray(ray_params_up)
var ray_params_down := PhysicsRayQueryParameters3D.create(real_pos_up, real_pos, raycast_layers)
var result_down = space_state.intersect_ray(ray_params_down)
var up_hit_frontface := false
if result_up:
if result_up.normal.y < 0:
true
if result_up or result_down:
if not up_hit_frontface and result_down:
image.set_pixel(x, y, Color(1.0, 1.0, 1.0))
return image
# Adds offset margins so filters will correctly extend across UV edges
static func add_margins(image : Image, resolution : int, margin : int) -> Image:
var with_margins_size := resolution + 2 * margin
var image_with_margins := Image.create(with_margins_size, with_margins_size, true, Image.FORMAT_RGB8)
image_with_margins.blend_rect(image, Rect2i(0, resolution - margin, resolution, margin), Vector2i(margin + margin, 0))
image_with_margins.blend_rect(image, Rect2i(0, 0, resolution, resolution), Vector2i(margin, margin))
image_with_margins.blend_rect(image, Rect2i(0, 0, resolution, margin), Vector2i(0, resolution + margin))
return image_with_margins