Skip to content

Commit 64a5279

Browse files
author
mfbehrens99
committed
Merge branch 'forces-torque' into main
2 parents 35d2776 + bd6677d commit 64a5279

12 files changed

Lines changed: 284 additions & 107 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Template
2626

2727
### Added
2828

29+
- [[Boat]] Add torques to the boat to make it spin
30+
- [[Simulation]] Run simulation for x steps
31+
- [[Sailor]] Basic rudder steering
32+
- [[GUI]] Display rudder forces in [boatInspector]
2933
- [[Boat]] Add setter methods for rudderAngle
3034
- [[GUI]] Display Sailor Waypoints on [mapView]
3135
- [[GUI]] Add goto start and goto end button
@@ -34,9 +38,18 @@ Template
3438

3539
### Changed
3640

41+
- [[Boat]] Move forces and torques to their own files
42+
- [[Boat]] Rename waterDrag and waterLift to centerboardDrag and centerboardLift
43+
- [[Sailor]] Don't delete Waypoints after executing them
3744
- [[GUI]] Display mainSailAngle and rudderAngle in [boatInspector]
3845
- [[Sailor]] Use index instead of deleting [Commands] after executing them
3946

47+
### Removed
48+
49+
- [[Boat]] Removed Boat::getSpeedSq()
50+
51+
52+
4053
## [0.0.1] - 2021-04-13
4154

4255
### Added

sailsim/boat/Boat.py

Lines changed: 100 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
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
43
from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval
54
from sailsim.utils.coordconversion import cartToRadiusSq, cartToArg
65

@@ -11,42 +10,56 @@
1110
class 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)

sailsim/boat/BoatDataHolder.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,11 @@ def __init__(self):
1212
self.forceX = self.forceY = None
1313
self.sailDragX = self.sailDragY = None
1414
self.sailLiftX = self.sailLiftY = None
15-
self.waterDragX = self.waterDragY = None
16-
self.waterLiftX = self.waterLiftY = None
15+
self.centerboardDragX = self.centerboardDragY = None
16+
self.centerboardLiftX = self.centerboardLiftY = None
17+
self.rudderDragX = self.rudderDragY = None
18+
self.rudderLiftX = self.rudderLiftY = None
19+
20+
self.torque = None
21+
self.waterDragTorque = None
22+
self.centerboardTorque = self.rudderTorque = None

sailsim/boat/boat_forces.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""This module holdes some force calculation for the Boat class."""
2+
3+
from math import pi
4+
5+
from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval
6+
from sailsim.utils.coordconversion import polarToCart
7+
from sailsim.utils.constants import DENSITY_AIR, DENSITY_WATER
8+
9+
10+
def leverSpeedVector(self, lever):
11+
"""Calculate the speed vector at a certain point concidering the rotation."""
12+
(orbSpeedX, orbSpeedY) = polarToCart(self.angSpeed * lever, directionKeepInterval(self.direction + pi))
13+
return (-self.speedX + orbSpeedX, -self.speedY + orbSpeedY)
14+
15+
16+
# Sail forces
17+
def sailDrag(self, apparentWindSpeedSq, apparentWindNormX, apparentWindNormY):
18+
"""Calculate the force that is created when wind blows against the boat."""
19+
scalarSailDrag = 0.5 * DENSITY_AIR * self.sailArea * apparentWindSpeedSq * self.coefficientAirDrag(self.dataHolder.angleOfAttack)
20+
return self.scalarToDragForce(scalarSailDrag, apparentWindNormX, apparentWindNormY)
21+
22+
23+
def sailLift(self, apparentWindSpeedSq, apparentWindNormX, apparentWindNormY):
24+
"""Calculate the lift force that is created when the wind changes its direction in the sail."""
25+
scalarSailLift = 0.5 * DENSITY_AIR * self.sailArea * apparentWindSpeedSq * self.coefficientAirLift(self.dataHolder.angleOfAttack)
26+
return self.scalarToLiftForce(scalarSailLift, self.dataHolder.angleOfAttack, apparentWindNormX, apparentWindNormY)
27+
28+
29+
# Centerboard forces
30+
def centerboardDrag(self, flowSpeedSq, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY):
31+
"""Calculate the drag force of the water that is decelerating the boat."""
32+
scalarCenterboardDrag = 0.5 * DENSITY_WATER * self.centerboardArea * flowSpeedSq * self.coefficientWaterDrag(self.dataHolder.leewayAngle)
33+
return self.scalarToDragForce(scalarCenterboardDrag, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY)
34+
35+
36+
def centerboardLift(self, flowSpeedSq, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY):
37+
"""Calculate force that is caused by lift forces in the water."""
38+
scalarCenterboardLift = 0.5 * DENSITY_WATER * self.centerboardArea * flowSpeedSq * self.coefficientWaterLift(self.dataHolder.leewayAngle)
39+
return self.scalarToLiftForce(scalarCenterboardLift, self.dataHolder.leewayAngle, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY)
40+
41+
42+
# Rudder forces
43+
def rudderDrag(self, flowSpeedSq, flowSpeedRudderNormX, flowSpeedRudderNormY):
44+
"""Calculates Force of the rudder that ist decelerating the boat."""
45+
scalarRudderDrag = 0.5 * DENSITY_WATER * self.rudderArea * flowSpeedSq * self.coefficientWaterDrag(angleKeepInterval(self.dataHolder.leewayAngle + self.rudderAngle))
46+
return self.scalarToDragForce(scalarRudderDrag, flowSpeedRudderNormX, flowSpeedRudderNormY)
47+
48+
49+
def rudderLift(self, flowSpeedSq, flowSpeedRudderNormX, flowSpeedRudderNormY):
50+
"""Calculates Lift caused by rudder."""
51+
scalarRudderLift = 0.5 * DENSITY_WATER * self.rudderArea * flowSpeedSq * self.coefficientWaterLift(angleKeepInterval(self.dataHolder.leewayAngle + self.rudderAngle))
52+
return self.scalarToLiftForce(scalarRudderLift, angleKeepInterval(self.dataHolder.leewayAngle + self.rudderAngle), flowSpeedRudderNormX, flowSpeedRudderNormY)
53+
54+
55+
56+
57+
# Conversions
58+
def scalarToLiftForce(self, scalarForce, angleOfAttack, normX, normY):
59+
"""Convert scalar lift force into cartesian vector"""
60+
if angleOfAttack < 0:
61+
return (-scalarForce * normY, scalarForce * normX) # rotate by 90° counterclockwise
62+
return (scalarForce * normY, -scalarForce * normX) # rotate by 90° clockwise
63+
64+
65+
def scalarToDragForce(self, scalarForce, normX, normY):
66+
"""Convert scalar drag force into cartesian vector"""
67+
return (scalarForce * normX, scalarForce * normY)

sailsim/boat/boat_torques.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""This module calculates torques for the boat class."""
2+
3+
from sailsim.utils.constants import DENSITY_AIR, DENSITY_WATER
4+
5+
6+
def waterDragTorque(self):
7+
c_w = 1.1
8+
# TODO use c_w
9+
# NOTE formula does not contain an area (but only a length)
10+
# print(self.angSpeed)
11+
torque = 1 / 4 * c_w * DENSITY_WATER * pow(self.length, 4) * pow(self.angSpeed, 2)
12+
if self.angSpeed < 0:
13+
return torque
14+
return -torque
15+
16+
17+
def centerboardTorque(self, centerboardX, centerboardY, dirNormX, dirNormY):
18+
return (centerboardY * dirNormX - centerboardX * dirNormY) * self.centerboardLever # negative cross product
19+
20+
def rudderTorque(self, rudderX, rudderY, dirNormX, dirNormY):
21+
return (rudderY * dirNormX - rudderX * dirNormY) * self.rudderLever # negative cross product

0 commit comments

Comments
 (0)