1- from math import sqrt , pi
1+ from math import sqrt , pi , sin , cos
22
3- from sailsim .utils .constants import DENSITY_AIR , DENSITY_WATER
43from sailsim .utils .anglecalculations import angleKeepInterval , directionKeepInterval
54from sailsim .utils .coordconversion import cartToRadiusSq , cartToArg
65
1110class Boat :
1211 """Holds all information about the boat and calculates its speed, forces and torques."""
1312
14- from .boatgetset import getPos , getSpeed , setDirection , setDirectionDeg , setMainSailAngle , setMainSailAngleDeg , setRudderAngle , setRudderAngleDeg , setConstants
13+ from .boatgetset import setBoat , getPos , getSpeed , setDirection , setDirectionDeg , setMainSailAngle , setMainSailAngleDeg , setRudderAngle , setRudderAngleDeg , setConstants
1514
16- def __init__ (self , posX = 0 , posY = 0 , direction = 0 , speedX = 0 , speedY = 0 ):
15+ def __init__ (self , posX = 0 , posY = 0 , direction = 0 , speedX = 0 , speedY = 0 , angSpeed = 0 ):
1716 """
1817 Create a boat.
1918
2019 Args:
2120 posX: x position of the boat (in m)
2221 posY: y position of the boat (in m)
23- direction: direction the boat is pointing
22+ direction: direction the boat is pointing (in rad)
2423 speedX: speed in x direction (in m/s)
2524 speedY: speed in y direction (in m/s)
25+ angSpeed: angular speed in z direction (in rad/s)
2626 """
27+
28+ # Static properties
29+ self .length = 4.2 # m
30+ self .width = 1.63 # m
31+ self .mass = 80 # kg
32+ self .momentumInertia = 1 / 12 * self .mass * (pow (self .length , 2 ) + pow (self .width , 2 )) # kg/m^2
33+ self .sailArea = 7.45 # m^2
34+ self .hullArea = 4 # m^2
35+ self .centerboardArea = 1 # m^2 # self.centerboardDepth * self.centerboardLength
36+ self .centerboardDepth = 0 # m
37+ self .centerboardLength = 0 # m
38+ self .centerboardLever = - 0.3 # m
39+ self .rudderArea = .175 # m^2 # self.rudderDepth * self.rudderLength
40+ self .rudderDepth = 0 # m
41+ self .rudderLength = 0 # m
42+ self .rudderLever = 2.1 # m
43+
2744 # Dynamic properties
2845 self .posX = posX
2946 self .posY = posY
30-
3147 self .speedX = speedX
3248 self .speedY = speedY
33- self .direction = directionKeepInterval (direction )
3449
35- self .rudderAngle = 0
36- self .maxRudderAngle = 80 / 180 * pi
50+ self .direction = directionKeepInterval (direction )
51+ self .angSpeed = angSpeed # rad/s
52+ self .pivot = 0.5 * self .length # m
3753
3854 self .mainSailAngle = 0
3955 self .maxMainSailAngle = 80 / 180 * pi
4056
57+ self .rudderAngle = 0
58+ self .maxRudderAngle = 80 / 180 * pi
59+
4160 self .dataHolder = BoatDataHolder ()
4261 self .sailor = None
4362
44- # Static properties
45- self .mass = 80
46- self .sailArea = 7.45
47- self .hullArea = 4 # arbitrary
48- self .centerboardArea = 1
49-
5063 # Coefficients methods
5164 self .coefficientAirDrag = coefficientAirDrag
5265 self .coefficientAirLift = coefficientAirLift
@@ -57,23 +70,26 @@ def __init__(self, posX=0, posY=0, direction=0, speedX=0, speedY=0):
5770 self .tackingAngleDownwind = 20 / 180 * pi
5871
5972
60- # Simulation
61- def applyForce (self , forceX , forceY , interval ):
62- """Change speed according a force given."""
63- # △v = a * t ; F = m * a
64- # △v = F / m * t
73+ # Simulation methods
74+ def applyCauses (self , forceX , forceY , torque , interval ):
75+ """Change speed according a force & torque given."""
76+ # Translation: △v = a * t; F = m * a -> △v = F / m * t
77+ # Rotation: △ω = α * t; M = I * α -> △ω = M / I * t
6578 self .speedX += forceX / self .mass * interval
6679 self .speedY += forceY / self .mass * interval
80+ self .angSpeed += torque / self .momentumInertia * interval
6781
6882 def moveInterval (self , interval ):
6983 """Change position according to sailsDirection and speed."""
70- # s = v * t
84+ # △ s = v * t; △α = ω * t
7185 self .posX += self .speedX * interval
7286 self .posY += self .speedY * interval
87+ self .direction = directionKeepInterval (self .direction + self .angSpeed * interval )
7388
7489 def runSailor (self ):
7590 """Activate the sailing algorithm to decide what the boat should do."""
7691 if self .sailor is not None :
92+ # Run sailor if sailor exists
7793 self .sailor .run (
7894 self .posX ,
7995 self .posY ,
@@ -82,83 +98,91 @@ def runSailor(self):
8298 self .direction ,
8399 self .dataHolder .apparentWindSpeed ,
84100 self .dataHolder .apparentWindAngle
85- ) # Run sailor
101+ )
86102
87- # Set boat properties
103+ # Retrieve boat properties from Sailor
88104 self .mainSailAngle = self .sailor .mainSailAngle
89- self .direction = self .sailor .boatDirection
90-
105+ # self.direction = self.sailor.boatDirection
106+ self . rudderAngle = self . sailor . rudderAngle
91107
92- # Force calculations
93- def resultingForce (self , trueWindX , trueWindY ):
94- """Add up all reacting forces and return them as a tuple."""
108+ def resultingCauses (self , trueWindX , trueWindY ):
109+ """Add up all acting forces and return them as a tuple."""
95110 h = self .dataHolder
96111
112+ h .boatSpeed = self .boatSpeed ()
113+
97114 # calculate apparent wind angle
98115 (h .apparentWindX , h .apparentWindY ) = self .apparentWind (trueWindX , trueWindY )
99116 h .apparentWindAngle = self .apparentWindAngle (h .apparentWindX , h .apparentWindY )
100-
101117 apparentWindSpeedSq = cartToRadiusSq (h .apparentWindX , h .apparentWindY )
102118 h .apparentWindSpeed = sqrt (apparentWindSpeedSq )
103- boatSpeedSq = self .boatSpeedSq ()
104- h .boatSpeed = sqrt (boatSpeedSq )
119+
120+ # calculate flowSpeed
121+ (flowSpeedRudderX , flowSpeedRudderY ) = self .leverSpeedVector (self .rudderLever )
122+ flowSpeedRudderSq = cartToRadiusSq (flowSpeedRudderX , flowSpeedRudderY )
123+ flowSpeedRudder = sqrt (flowSpeedRudderSq )
124+
125+ (flowSpeedCenterboardX , flowSpeedCenterboardY ) = self .leverSpeedVector (self .centerboardLever )
126+ flowSpeedCenterboardSq = cartToRadiusSq (flowSpeedCenterboardX , flowSpeedCenterboardY )
127+ flowSpeedCenterboard = sqrt (flowSpeedCenterboardSq )
105128
106129 # normalise apparent wind vector and boat speed vetor
107130 # if vector is (0, 0) set normalised vector to (0, 0) aswell
108131 (apparentWindNormX , apparentWindNormY ) = (h .apparentWindX / h .apparentWindSpeed , h .apparentWindY / h .apparentWindSpeed ) if not h .apparentWindSpeed == 0 else (0 , 0 ) # normalised apparent wind vector
109- (speedNormX , speedNormY ) = (self .speedX / h .boatSpeed , self .speedY / h .boatSpeed ) if not h .boatSpeed == 0 else (0 , 0 ) # normalised speed vector
132+ # (speedNormX, speedNormY) = (self.speedX / h.boatSpeed, self.speedY / h.boatSpeed) if not h.boatSpeed == 0 else (0, 0) # normalised speed vector
133+ (flowSpeedRudderNormX , flowSpeedRudderNormY ) = (flowSpeedRudderX / flowSpeedRudder , flowSpeedRudderY / flowSpeedRudder ) if not flowSpeedRudder == 0 else (0 , 0 ) # normalised speed vector
134+ (flowSpeedCenterboardNormX , flowSpeedCenterboardNormY ) = (flowSpeedCenterboardX / flowSpeedCenterboard , flowSpeedCenterboardY / flowSpeedCenterboard ) if not flowSpeedCenterboard == 0 else (0 , 0 ) # normalised speed vector
135+ (dirNormX , dirNormY ) = (sin (self .direction ), cos (self .direction ))
110136
111137 h .leewayAngle = self .calcLeewayAngle ()
112138 h .angleOfAttack = self .angleOfAttack (h .apparentWindAngle )
113139
114- # Sum up all acting forces
115- (forceX , forceY ) = (0 , 0 )
116- (h .sailDragX , h .sailDragY ) = self .sailDrag (apparentWindNormX , apparentWindNormY , apparentWindSpeedSq )
140+ (forceX , forceY , torque ) = (0 , 0 , 0 )
141+
142+ # Sail forces
143+ (h .sailDragX , h .sailDragY ) = self .sailDrag (apparentWindSpeedSq , apparentWindNormX , apparentWindNormY )
117144 forceX += h .sailDragX
118145 forceY += h .sailDragY
119- (h .sailLiftX , h .sailLiftY ) = self .sailLift (apparentWindNormX , apparentWindNormY , apparentWindSpeedSq )
146+ (h .sailLiftX , h .sailLiftY ) = self .sailLift (apparentWindSpeedSq , apparentWindNormX , apparentWindNormY )
120147 forceX += h .sailLiftX
121148 forceY += h .sailLiftY
122149
123- (h .waterDragX , h .waterDragY ) = self .waterDrag (speedNormX , speedNormY , boatSpeedSq )
124- forceX += h .waterDragX
125- forceY += h .waterDragY
126- (h .waterLiftX , h .waterLiftY ) = self .waterLift (speedNormX , speedNormY , boatSpeedSq )
127- forceX += h .waterLiftX
128- forceY += h .waterLiftY
129-
130- (h .forceX , h .forceY ) = (forceX , forceY )
131- return (forceX , forceY )
132-
133- def sailDrag (self , apparentWindNormX , apparentWindNormY , apparentWindSpeedSq ):
134- """Calculate the force that is created when wind blows against the boat."""
135- force = 0.5 * DENSITY_AIR * self .sailArea * apparentWindSpeedSq * self .coefficientAirDrag (self .dataHolder .angleOfAttack )
136- return (force * apparentWindNormX , force * apparentWindNormY )
137-
138- def sailLift (self , apparentWindNormX , apparentWindNormY , apparentWindSpeedSq ):
139- """Calculate the lift force that is created when the wind changes its direction in the sail."""
140- force = 0.5 * DENSITY_AIR * self .sailArea * apparentWindSpeedSq * self .coefficientAirLift (self .dataHolder .angleOfAttack )
141- if self .dataHolder .angleOfAttack < 0 :
142- return (- force * apparentWindNormY , force * apparentWindNormX ) # rotate by 90° counterclockwise
143- return (force * apparentWindNormY , - force * apparentWindNormX ) # rotate by 90° clockwise
144-
145- def waterDrag (self , speedNormX , speedNormY , boatSpeedSq ):
146- """Calculate the drag force of the water that is decelerating the boat."""
147- force = - 0.5 * DENSITY_WATER * (self .hullArea + self .centerboardArea ) * boatSpeedSq * self .coefficientWaterDrag (self .dataHolder .leewayAngle )
148- return (force * speedNormX , force * speedNormY )
149-
150- def waterLift (self , speedNormX , speedNormY , boatSpeedSq ):
151- """Calculate force that is caused by lift forces in the water."""
152- force = - 0.5 * DENSITY_WATER * self .centerboardArea * boatSpeedSq * self .coefficientWaterLift (self .dataHolder .leewayAngle )
153- if self .dataHolder .leewayAngle < 0 :
154- return (- force * speedNormY , force * speedNormX ) # rotate by 90° counterclockwise
155- return (force * speedNormY , - force * speedNormX ) # rotate by 90° clockwise
156-
157-
158- # Speed calculations
159- def boatSpeedSq (self ):
160- """Return speed of the boat but squared."""
161- return pow (self .speedX , 2 ) + pow (self .speedY , 2 )
150+ # Centerboard forces
151+ (h .centerboardDragX , h .centerboardDragY ) = self .centerboardDrag (flowSpeedCenterboardSq , flowSpeedCenterboardNormX , flowSpeedCenterboardNormY )
152+ forceX += h .centerboardDragX
153+ forceY += h .centerboardDragY
154+ (h .centerboardLiftX , h .centerboardLiftY ) = self .centerboardLift (flowSpeedCenterboardSq , flowSpeedCenterboardNormX , flowSpeedCenterboardNormY )
155+ forceX += h .centerboardLiftX
156+ forceY += h .centerboardLiftY
157+
158+ # Rudder forces
159+ (h .rudderDragX , h .rudderDragY ) = self .rudderDrag (flowSpeedRudderSq , flowSpeedRudderNormX , flowSpeedRudderNormY )
160+ forceX += h .rudderDragX
161+ forceY += h .rudderDragY
162+ (h .rudderLiftX , h .rudderLiftY ) = self .rudderLift (flowSpeedRudderSq , flowSpeedRudderNormX , flowSpeedRudderNormY )
163+ forceX += h .rudderLiftX
164+ forceY += h .rudderLiftY
165+
166+ # TODO Hull forces
167+
168+
169+ # Torques
170+ h .waterDragTorque = self .waterDragTorque ()
171+ torque += h .waterDragTorque
172+
173+ h .centerboardTorque = self .centerboardTorque (h .centerboardDragX + h .centerboardLiftX , h .centerboardDragY + h .centerboardLiftY , dirNormX , dirNormY )
174+ torque += h .centerboardTorque
175+
176+ h .rudderTorque = self .rudderTorque (h .rudderDragX + h .rudderLiftX , h .rudderDragY + h .rudderLiftY , dirNormX , dirNormY )
177+ torque += h .rudderTorque
178+
179+ (h .forceX , h .forceY , h .torque ) = (forceX , forceY , torque )
180+ return (forceX , forceY , torque )
181+
182+ # Import force and torque functions
183+ from .boat_forces import leverSpeedVector , sailDrag , sailLift , centerboardDrag , centerboardLift , rudderDrag , rudderLift , scalarToDragForce , scalarToLiftForce
184+ from .boat_torques import waterDragTorque , centerboardTorque , rudderTorque
185+
162186
163187 def boatSpeed (self ):
164188 """Return speed of the boat."""
@@ -185,4 +209,4 @@ def angleOfAttack(self, apparentWindAngle):
185209
186210 def __repr__ (self ):
187211 heading = round (cartToArg (self .speedX , self .speedY ) * 180 / pi , 2 )
188- return "Boat @(%s|%s), v=%sm/s twds %s°" % (round (self .posX , 3 ), round (self .posY , 3 ), round (sqrt ( self .boatSpeedSq () ), 2 ), heading )
212+ return "Boat @(%s|%s), v=%sm/s twds %s°" % (round (self .posX , 3 ), round (self .posY , 3 ), round (self .boatSpeed ( ), 2 ), heading )
0 commit comments