summaryrefslogtreecommitdiffstats
path: root/water/BuoyantBody.gd
diff options
context:
space:
mode:
Diffstat (limited to 'water/BuoyantBody.gd')
-rw-r--r--water/BuoyantBody.gd120
1 files changed, 120 insertions, 0 deletions
diff --git a/water/BuoyantBody.gd b/water/BuoyantBody.gd
new file mode 100644
index 0000000..4a9dc78
--- /dev/null
+++ b/water/BuoyantBody.gd
@@ -0,0 +1,120 @@
+extends RigidBody
+class_name BuoyantBody
+
+export(NodePath) var water = NodePath("/root/Main/Water")
+onready var _water = get_node(water)
+
+var volume_half = 0.0
+var volume = 0.0
+var bounding_box = []
+var center_of_mass = Vector3.ZERO
+var volume_up = Vector3.UP
+
+onready var inv_gravity = -ProjectSettings.get_setting("physics/3d/default_gravity_vector") * ProjectSettings.get_setting("physics/3d/default_gravity")
+const POINT_VOLUME_WEIGHT = 0.125
+
+func _ready():
+ for c in get_children():
+ if c is CollisionShape:
+ if c.shape is SphereShape:
+ _init_buoyancy(c.shape.radius,c.shape.radius,c.shape.radius,c.transform)
+ elif c.shape is CapsuleShape:
+ _init_buoyancy(c.shape.radius,c.shape.radius,c.shape.height/2.0,c.transform)
+ elif c.shape is BoxShape:
+ _init_buoyancy(c.shape.extents.x,c.shape.extents.y,c.shape.extents.z,c.transform)
+ else:
+ assert(false,"this collision shape isn't supported for buoyancy")
+ break
+
+func _init_buoyancy(x_rad,y_rad,z_rad,xform):
+ self.volume_half = x_rad * y_rad * z_rad * 4.0
+ self.volume = volume_half * 2.0
+ var aabb = [
+ Vector3(-x_rad,-y_rad,-z_rad),
+ Vector3(-x_rad,-y_rad, z_rad),
+ Vector3(-x_rad, y_rad,-z_rad),
+ Vector3(-x_rad, y_rad, z_rad),
+ Vector3( x_rad,-y_rad,-z_rad),
+ Vector3( x_rad,-y_rad, z_rad),
+ Vector3( x_rad, y_rad,-z_rad),
+ Vector3( x_rad, y_rad, z_rad)
+ ]
+ for p in aabb:
+ self.bounding_box.push_back(xform * p)
+ self.center_of_mass = xform * center_of_mass
+ self.volume_up = (xform.basis * Vector3.UP).normalized()
+
+func _physics_process(_delta):
+ #check if points are submerged
+ #also weight them based on how deep they are to help get a weighted average center point later
+ var submerged = []
+ var sub_weight = []
+ var sub_total = 0.0
+ for p in bounding_box:
+ var g_p = self.global_transform * p
+ var wave_height = _water.height(g_p)
+ var diff = g_p.y - wave_height
+ if diff < 0.0:
+ submerged.push_back(p)
+ sub_weight.push_back(diff)
+ sub_total += diff
+ #if no points are submerged, the whole thing is likely above water
+ #therefore, no buoyant force is applied
+ if submerged.size() == 0:
+ return
+ #if all points are submerged, the whole thing is likely below water
+ #so the buoyant force would be the weight of the water displaced by the entire volume
+ elif submerged.size() == 8:
+ add_central_force(inv_gravity * volume)
+ #if only some points are submerged, we need to estimate the amount of the volume displacing water
+ #the weight of that water is the buoyant force
+ else:
+ #we want to apply the buoyant force to the center of mass of the submerged part of the volume
+ #we can estimate it with a weighted average of the submerged points
+ var force_pnt = Vector3.ZERO
+ for p in range(submerged.size()):
+ force_pnt += submerged[p] * sub_weight[p]
+ force_pnt /= sub_total
+ #apply_force uses global rotation but local origin...
+ force_pnt = self.global_transform.basis * force_pnt
+ #to estimate the submerged part of the volume,
+ #we can see how deep into the water the bottom of an axis-aligned bounding box is
+ #and do an easing and lerp over the volume
+ var lowest = (self.global_transform * bounding_box[0]).y
+ for p in bounding_box:
+ var p_h = (self.global_transform * p).y
+ if p_h < lowest:
+ lowest = p_h
+ var center = self.global_transform * center_of_mass
+ var depth = _water.height(center) - lowest
+ #the water isn't a flat plane
+ #it's possible that some points are submerged,
+ #but the lowest point of the axis-aligned bounding box isn't actually under water
+ #in that case, for simplicity,
+ #just fall back to the old method of applying a small, constant force
+ #proportional to the number of submerged points
+ if depth <= 0.0:
+ add_force(inv_gravity * volume * POINT_VOLUME_WEIGHT * submerged.size(), force_pnt)
+ #lerp the volume against the depth of the axis-aligned bounding box
+ #but that lerp is only linear when the actual bounding box is axis-aligned
+ #for simplicity, we can lerp an easing curve based on how aligned the bounding box is
+ #on one end, it's linear and on the other it uses a basic exponential easing
+ #we want to do an inverse exponential ease for the upper half of the volume if it is also submerged
+ else:
+ var up = self.global_transform.basis * volume_up
+ var aligned = up.dot(Vector3.UP)
+ aligned = abs(aligned)
+ aligned -= 0.5
+ aligned = abs(aligned)
+ aligned *= 2.0
+ var easing_curve = lerp(4.8,1.0,aligned)
+ var breadth = (center.y - lowest)
+ depth = clamp(depth,0.0,breadth*2.0)
+ depth /= breadth
+ var v = clamp(depth,0.0,1.0)
+ v = ease(v,easing_curve)
+ var v2 = clamp(depth-1.0,0.0,1.0)
+ v2 = 1.0 - ease(1.0 - v2,easing_curve)
+ v += v2
+ v = lerp(0.0,volume_half,v)
+ add_force(inv_gravity * v, force_pnt)