aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/sims/dynamics/constraints/Constraint.scala
blob: 367c84d0a0c09c2f850eb5e298434394d128a386 (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
/*    _____ _ __  ________    ___                                      *\
**   / ___/(_)  |/  / ___/   |__ \  Simple Mechanics Simulator 2       **
**   \__ \/ / /|_/ /\__ \    __/ /  copyright (c) 2011 Jakob Odersky   **
**  ___/ / / /  / /___/ /   / __/                                      **
** /____/_/_/  /_//____/   /____/                                      **
\*                                                                     */

package sims.dynamics.constraints

import sims.dynamics._
import sims.math._

/** A base trait for implementing and solving constraints.
  * Constraints are solved using the 'sequential impulse' method presented by Erin Catto
  * at Game Developpers Conference 2008 (see http://www.box2d.org/documentation.html).
  * 
  * One instance of this trait represents one constraint for one degree of freedom between
  * two bodies.
  * @see sims.dynamics.constraints.Constrained */
trait Constraint {
	
	/** First constrained body. */
	def body1: Body
	
	/** Second constrained body. */
	def body2: Body
	
	/** Value of the position constraint function ('C' in the presentation). */
	def value: Double
	
	/** Jacobian for the velocity constraint ('J' in 'Ċ=Jv+b' in the presentation). */
	def jacobian: Jacobian
	
	/** Velocity bias ('b' in 'Ċ=Jv+b' in the presentation).*/
	def bias: Double = 0
	
	/** Lower and upper limits of the total corrective impulse allowed
	  * when solving this constraint.
	  * The first element represents the lower limit,
	  * the second the upper limit.
	  * None represents no limits. */
	def limit: Option[(Double, Double)] = None
	
	/** Defines whether or not this constraint should be treated as an inequality constraint.
	  * An inequality constraint will only be solved if the constraint function is less than
	  * zero (i.e. `value < 0`). */
	def inequality = false
	
	/** Position error used for Baumgarte sabilization. 
	  * Corresponds to `value` by default. */
	def error = value
	
	/** Accumulated impulse. */
	private var accumulated = 0.0
	
	/** Constraint mass cache, constant for all iterations. */
	private var m = 0.0
	
	/** Jacobian cache, constant for all iterations. */
	private var J: Jacobian = null
	
	/** Velocity bias cache, constant for all iterations. */
	private var b = 0.0
	
	/** Method that should be called before solving this constraint.
	  * Computes constraint masses and other invariable data for iterating over constraints. */
	def preSolve() = {
		//compute jacobian and bias for next solving
		J = jacobian
		b = bias
		
		//invMass=(J*invert(M)*transpose(J))
		val invMass: Double = (
				(J.v1.x * J.v1.x + J.v1.y * J.v1.y) / body1.mass + J.w1 * J.w1 / body1.inertia +
				(J.v2.x * J.v2.x + J.v2.y * J.v2.y) / body2.mass + J.w2 * J.w2 / body2.inertia
			)
		
		//if invMass == 0, both bodies have infinite mass (i.e. are fixed)
		m = if (invMass == 0.0) 0.0 else 1.0 / invMass
		
		//apply accumulated impulse from prevoius step
		body1.applyImpulse(J.v1 * accumulated)
		body1.applyAngularImpulse(J.w1 * accumulated)
		body2.applyImpulse(J.v2 * accumulated)
		body2.applyAngularImpulse(J.w2 * accumulated)
		
		//initialize accumulated impulse
		//accumulated = 0.0
	}
	
	/** Solves this constraint by applying corrective impulses to its constrained bodies.
	  * @todo implement error correction properly for collisiions (add slop tolerance, etc)
	  * @param h time interval over which the correction should be applied
	  * @param erp error reduction parameter. */
	def correctVelocity(h: Double, erp: Double): Unit = {
		
		//if m == 0, both bodies are fixed and there is no point in applying corrective impulse
		if (m == 0) return ()
		
		//C > 0 => ignore
		if (inequality && value > 0.0) return ()
		
		//lambda
		var lambda = -m * (
				J.v1.x * body1.linearVelocity.x + J.v1.y * body1.linearVelocity.y + J.w1 * body1.angularVelocity +
				J.v2.x * body2.linearVelocity.x + J.v2.y * body2.linearVelocity.y + J.w2 * body2.angularVelocity +
				b + (erp / h * error)
			)
			
		
		//clamp accumulated impulse
		if (limit != None) {
			val temp = accumulated
			accumulated = math.max(limit.get._1, math.min(accumulated + lambda, limit.get._2))
			lambda = accumulated - temp
		}
		
		
		//apply accumulated impulse
		body1.applyImpulse(J.v1 * lambda)
		body1.applyAngularImpulse(J.w1 * lambda)
		body2.applyImpulse(J.v2 * lambda)
		body2.applyAngularImpulse(J.w2 * lambda)	
	}
	
}