|
|
|
|
|
|
|
class WaterDynamics { |
|
constructor(gravity, drag_mod=0.25, lift_mod=0.25, push_mod=0.05, |
|
max_drag=2000, max_lift=500, max_push=13){ |
|
this.gravity = gravity; |
|
this.drag_mod = drag_mod; |
|
this.lift_mod = lift_mod; |
|
this.max_drag = max_drag; |
|
this.max_lift = max_lift; |
|
this.push_mod = push_mod; |
|
this.max_push = max_push; |
|
} |
|
|
|
compute_centroids(vectors){ |
|
let count = vectors.length; |
|
console.assert(count >= 3); |
|
|
|
let c = new b2.Vec2(0, 0); |
|
let area = 0; |
|
let ref_point = new b2.Vec2(0, 0); |
|
let inv3 = 1/3; |
|
|
|
for(let i = 0; i < count; i++){ |
|
|
|
let p1 = ref_point; |
|
let p2 = vectors[i]; |
|
let p3 = i + 1 < count ? vectors[i + 1] : vectors[0]; |
|
|
|
let e1 = b2.Vec2.Subtract(p2, p1); |
|
let e2 = b2.Vec2.Subtract(p3, p1); |
|
let d = b2.Cross_v2_v2(e1, e2); |
|
let triangle_area = 0.5 * d; |
|
area += triangle_area; |
|
|
|
|
|
c.Add(b2.Vec2.Multiply(triangle_area * inv3, b2.Vec2.Add(p1, b2.Vec2.Add(p2, p3)))); |
|
} |
|
|
|
if(area > b2.epsilon){ |
|
c.Multiply(1/area); |
|
} |
|
else{ |
|
area = 0; |
|
} |
|
|
|
return [c, area]; |
|
} |
|
|
|
inside(cp1, cp2, p){ |
|
return (cp2.x - cp1.x) * (p.y - cp1.y) > (cp2.y - cp1.y) * (p.x - cp1.x); |
|
} |
|
|
|
intersection(cp1, cp2, s, e){ |
|
let dc = new b2.Vec2(cp1.x - cp2.x, cp1.y - cp2.y); |
|
let dp = new b2.Vec2(s.x - e.x, s.y - e.y); |
|
let n1 = cp1.x * cp2.y - cp1.y * cp2.x; |
|
let n2 = s.x * e.y - s.y * e.x; |
|
let n3 = 1.0 / (dc.x * dp.y - dc.y * dp.x); |
|
return new b2.Vec2((n1 * dp.x - n2 * dc.x) * n3, (n1 * dp.y - n2 * dc.y) * n3); |
|
} |
|
|
|
find_intersection(fixture_A, fixture_B){ |
|
|
|
let output_vertices = []; |
|
let polygon_A = fixture_A.GetShape(); |
|
let polygon_B = fixture_B.GetShape(); |
|
|
|
|
|
for(let i = 0; i < polygon_A.m_count; i++){ |
|
output_vertices.push(fixture_A.GetBody().GetWorldPoint(polygon_A.m_vertices[i])); |
|
} |
|
|
|
|
|
let clip_polygon = []; |
|
for(let i = 0; i < polygon_B.m_count; i++){ |
|
clip_polygon.push(fixture_B.GetBody().GetWorldPoint(polygon_B.m_vertices[i])); |
|
} |
|
|
|
let cp1 = clip_polygon[clip_polygon.length - 1]; |
|
for(let j = 0; j < clip_polygon.length; j++){ |
|
let cp2 = clip_polygon[j]; |
|
if(output_vertices.length == 0){ |
|
break; |
|
} |
|
let input_list = output_vertices.slice(); |
|
output_vertices = []; |
|
|
|
let s = input_list[input_list.length - 1]; |
|
for(let i = 0; i < input_list.length; i++){ |
|
let e = input_list[i]; |
|
if(this.inside(cp1, cp2, e)){ |
|
if(!this.inside(cp1, cp2, s)){ |
|
output_vertices.push(this.intersection(cp1, cp2, s, e)); |
|
} |
|
output_vertices.push(e); |
|
} |
|
else if(this.inside(cp1, cp2, s)){ |
|
output_vertices.push(this.intersection(cp1, cp2, s, e)); |
|
} |
|
s = e; |
|
} |
|
cp1 = cp2 |
|
} |
|
return [(output_vertices.length != 0), output_vertices]; |
|
} |
|
|
|
calculate_forces(fixture_pairs){ |
|
for(let pair of fixture_pairs){ |
|
let density = pair[0].GetDensity(); |
|
let [has_intersection, intersection_points] = this.find_intersection(pair[0], pair[1]); |
|
if(has_intersection){ |
|
let [centroid, area] = this.compute_centroids(intersection_points); |
|
|
|
|
|
let displaced_mass = pair[0].GetDensity() * area; |
|
let buoyancy_force = b2.Vec2.Multiply(displaced_mass, b2.Vec2.Negate(this.gravity)); |
|
pair[1].GetBody().ApplyForce(buoyancy_force, centroid, true); |
|
|
|
|
|
for(let i = 0; i < intersection_points.length; i++) { |
|
let v0 = intersection_points[i]; |
|
let v1 = intersection_points[(i + 1) % intersection_points.length]; |
|
let mid_point = b2.Vec2.Multiply(0.5, b2.Vec2.Add(v0, v1)); |
|
|
|
|
|
|
|
let vel_dir = b2.Vec2.Subtract(pair[1].GetBody().GetLinearVelocityFromWorldPoint(mid_point), |
|
pair[0].GetBody().GetLinearVelocityFromWorldPoint(mid_point)); |
|
let vel = vel_dir.Normalize(); |
|
|
|
let edge = b2.Vec2.Subtract(v1, v0); |
|
let edge_length = edge.Normalize(); |
|
let normal = b2.Cross_f_v2(-1, edge); |
|
let drag_dot = b2.Dot_v2_v2(normal, vel_dir); |
|
if(drag_dot >= 0){ |
|
|
|
let drag_mag = drag_dot * this.drag_mod * edge_length * density * vel * vel; |
|
drag_mag = Math.min(drag_mag, this.max_drag); |
|
let drag_force = b2.Vec2.Multiply(drag_mag, b2.Vec2.Negate(vel_dir)); |
|
pair[1].GetBody().ApplyForce(drag_force, mid_point, true); |
|
|
|
|
|
let lift_dot = b2.Dot_v2_v2(edge, vel_dir); |
|
let lift_mag = drag_dot * lift_dot * this.lift_mod * edge_length * density * vel * vel; |
|
lift_mag = Math.min(lift_mag, this.max_lift); |
|
let lift_dir = b2.Cross_f_v2(1, vel_dir); |
|
let lift_force = b2.Vec2.Multiply(lift_mag, lift_dir); |
|
pair[1].GetBody().ApplyForce(lift_force, mid_point, true); |
|
} |
|
|
|
|
|
let body_to_check = pair[1].GetBody(); |
|
|
|
let joints_to_check = []; |
|
let joint_edge = body_to_check.GetJointList(); |
|
while(joint_edge != null){ |
|
if(joint_edge.joint.GetBodyB() == body_to_check){ |
|
joints_to_check.push(joint_edge.joint); |
|
} |
|
joint_edge = joint_edge.next; |
|
} |
|
|
|
for(let joint of joints_to_check){ |
|
if(joint.GetLowerLimit() < joint.GetJointAngle() && joint.GetJointAngle() < joint.GetUpperLimit()){ |
|
let torque = joint.GetMotorTorque(60); |
|
|
|
|
|
let moment_of_inertia = body_to_check.GetInertia(); |
|
let angular_velocity = body_to_check.GetAngularVelocity(); |
|
let angular_inertia = moment_of_inertia * angular_velocity; |
|
|
|
|
|
let world_center = body_to_check.GetWorldCenter(); |
|
let anchor = joint.GetAnchorB(); |
|
let lever_vector = b2.Vec2.Subtract(world_center, anchor); |
|
let force_applied_at_center = b2.Cross_v2_f(lever_vector, -torque); |
|
|
|
let push_dot = b2.Dot_v2_v2(normal, force_applied_at_center); |
|
if(push_dot > 0){ |
|
vel = torque + angular_inertia; |
|
|
|
let push_mag = push_dot * this.push_mod * edge_length * density * vel * vel; |
|
let force = b2.Vec2.Multiply(push_mag, b2.Vec2.Negate(force_applied_at_center)); |
|
let clip_force_x = Math.max(-this.max_push, Math.min(force.x, this.max_push)); |
|
let clip_force_y = Math.max(-this.max_push, Math.min(force.y, this.max_push)) |
|
let push_force = new b2.Vec2(clip_force_x, clip_force_y); |
|
body_to_check.ApplyForce(push_force, joint.GetAnchorB(), true); |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function WaterContactDetector() { |
|
b2.ContactListener.call(this); |
|
this.fixture_pairs = []; |
|
} |
|
|
|
WaterContactDetector.prototype = Object.create(b2.ContactListener.prototype); |
|
WaterContactDetector.prototype.constructor = WaterContactDetector; |
|
WaterContactDetector.prototype.BeginContact = function (contact){ |
|
if(contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER |
|
&& contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){ |
|
this.fixture_pairs.push([contact.GetFixtureA(), contact.GetFixtureB()]); |
|
} |
|
else if(contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER |
|
&& contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){ |
|
this.fixture_pairs.push([contact.GetFixtureB(), contact.GetFixtureA()]); |
|
} |
|
}; |
|
|
|
WaterContactDetector.prototype.EndContact = function (contact) { |
|
if(contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER |
|
&& contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){ |
|
let index = this.fixture_pairs.indexOf([contact.GetFixtureA(), contact.GetFixtureB()]); |
|
if (index !== -1) { |
|
this.fixture_pairs.splice(index, 1); |
|
} |
|
} |
|
else if(contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER |
|
&& contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){ |
|
let index = this.fixture_pairs.indexOf([contact.GetFixtureB(), contact.GetFixtureA()]); |
|
if (index !== -1) { |
|
this.fixture_pairs.splice(index, 1); |
|
} |
|
} |
|
}; |
|
|
|
WaterContactDetector.prototype.Reset = function (){ |
|
this.fixture_pairs = []; |
|
}; |
|
|
|
|