/* * Simple Mechanics Simulator (SiMS) * copyright (c) 2009 Jakob Odersky * made available under the MIT License */ package sims.dynamics import sims.geometry._ import sims.dynamics.joints._ /**A two dimensional rigid body is made out of shapes. * @param shps shapes that belong to this body.*/ class Body(shps: Shape*){ /**Unique identification number.*/ val uid = Body.nextUid /**Shapes that belong to this body.*/ val shapes: List[Shape] = shps.toList //Shapes are added during initialisation. for (s <- shapes) { s.body = this s.refLocalPos = s.pos - pos s.rotation0 = s.rotation } private var isFixed: Boolean = false /**Returns whether this body is fixed or not.*/ def fixed = isFixed /**Fixes or frees this body. By fixing, linear and angular velocities are set to zero.*/ def fixed_=(value: Boolean) = { if (value) {linearVelocity = Vector2D.Null; angularVelocity = 0.0} isFixed = value } /**Flag for a world to monitor the properties of this body. * @see World#monitors*/ var monitor: Boolean = false /**Returns the position of this body. The position is equivalent to the center of mass. * @return position of this body*/ def pos: Vector2D = // COM = sum(pos*mass)/M (Vector2D.Null /: shapes)((v: Vector2D, s: Shape) => v + s.pos * s.mass) / (0.0 /: shapes)((i: Double, s: Shape) => i + s.mass) /**Sets the position of this body. By doing so all its shapes are translated. * @param newPos new position*/ def pos_=(newPos: Vector2D) = { val stepPos = pos shapes.foreach((s: Shape) => s.pos = s.pos - stepPos + newPos) } /**Contains the current rotation of this body.*/ private var _rotation: Double = 0.0 /**Returns the current rotation of this body.*/ def rotation: Double = _rotation /**Sets the rotation of this body. Position and rotation of shapes are modified accordingly. * @param r new rotation*/ def rotation_=(newRotation: Double) = { _rotation = newRotation val stepPos = pos for (s <- shapes) { s.rotation = newRotation + s.rotation0 s.pos = stepPos + (s.refLocalPos rotate (newRotation)) } } /**Linear velocity of this body.*/ var linearVelocity: Vector2D = Vector2D.Null /**Angular velocity of this body.*/ var angularVelocity: Double = 0 /**Linear velocity of the given point on this body (in world coordinates).*/ def velocityOfPoint(point: Vector2D) = linearVelocity + ((point - pos).leftNormal * angularVelocity) /**Resulting force on the COM of this body.*/ var force: Vector2D = Vector2D.Null /**Resulting torque on this body.*/ var torque: Double = 0 /**Returns the mass of this body. If the body is free, its mass is the sum of the masses of its shapes. * If the body is fixed, its mass is infinite (Double.PositiveInfinity). * @return this body's mass*/ def mass: Double = if (fixed) Double.PositiveInfinity else (0.0 /: shapes)((i: Double, s: Shape) => i + s.mass) /**Returns the moment of inertia for rotations about the COM of this body. * It is calculated using the moments of inertia of this body's shapes and the parallel axis theorem. * If the body is fixed, its moment of inertia is infinite (Double.PositiveInfinity). * @return moment of inertia for rotations about the COM of this body*/ def I: Double = if (fixed) Double.PositiveInfinity else (0.0 /: (for (s <- shapes) yield (s.I + s.mass * ((s.pos - pos) dot (s.pos - pos)))))(_+_) /**Applies a force to the COM of this body. * @param force applied force*/ def applyForce(force: Vector2D) = if (!fixed) this.force += force /**Applies a force to a point on this body. Warning: the point is considered to be contained within this body. * @param force applied force * @param point position vector of the point (in world coordinates)*/ def applyForce(force: Vector2D, point: Vector2D) = if (!fixed) {this.force += force; torque += (point - pos) cross force} /**Applies an impulse to the COM of this body. * @param impulse applied impulse*/ def applyImpulse(impulse: Vector2D) = if (!fixed) linearVelocity += impulse / mass /**Applies an impulse to a point on this body. Warning: the point is considered to be contained within this body. * @param impulse applied impulse * @param point position vector of the point (in world coordinates)*/ def applyImpulse(impulse: Vector2D, point: Vector2D) = if (!fixed) {linearVelocity += impulse / mass; angularVelocity += ((point - pos) cross impulse) / I} /**Checks if the point point is contained in this body.*/ def contains(point: Vector2D) = shapes.exists(_.contains(point)) override def toString: String = { "Body" + uid + " " + shapes + " fixed=" + fixed + " m=" + mass + " I=" + I + " pos=" + pos + " rot=" + rotation + " v=" + linearVelocity + " w=" + angularVelocity + " F=" + force + " tau=" + torque } /**Creates a new body containing this body's shapes and the shape s. * @param s new shape * @return new body*/ def ~(s: Shape) = new Body((s :: shapes): _*) /**Creates a new body containing this body's shapes and the shapes of another body b. * @param b body with extra shapes * @return new body*/ def ~(b: Body) = new Body((this.shapes ::: b.shapes): _*) } object Body { private var uidCounter = -1 private def nextUid = {uidCounter += 1; uidCounter} }