summaryrefslogtreecommitdiff
path: root/src/sims/dynamics/Body.scala
blob: 8c0e2eea87e4e919a6baf85b8dc0860e22055ade (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
 * 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 (<code>Double.PositiveInfinity</code>).
   * @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 (<code>Double.PositiveInfinity</code>).
   * @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 <code>point</code> 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 <code>s</code>.
   * @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 <code>b</code>.
   * @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}
}