Mountain Bike Rear Suspension Design Optimisation

Transcription

Mountain Bike Rear Suspension Design Optimisation
Mountain Bike Rear Suspension Design
Optimisation
BSc. Computer Science (Hons)
University of Bath
David Weldon
8/5/2006
Mountain Bike Rear Suspension Design Optimisation
Submitted by David Weldon
COPYRIGHT
Attention is drawn to the fact that copyright of this thesis rests with its
author. The Intellectual Property Rights of the products produced as part
of the project belong to the University of Bath (see http://www.bath.ac.uk/
ordinances/#intelprop).
This copy of the thesis has been supplied on condition that anyone who
consults it is understood to recognise that its copyright rests with its author
and that no quotation from the thesis and no information derived from it
may be published without the prior written consent of the author.
Declaration
This dissertation is submitted to the University of Bath in accordance with
the requirements of the degree of Batchelor of Science in the Department
of Computer Science. No portion of the work in this dissertation has been
submitted in support of an application for any other degree or qualification of this or any other university or institution of learning. Except where
specifically acknowledged, it is the work of the author.
Signed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
This thesis may be made available for consultation within the University
Library and may be photocopied or lent to other libraries for the purposes
of consultation.
Signed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Acknowledgements
Firstly, I would like to thank my project supervisor, Dr Alwyn Barry, for his
help throughout the project and for providing a surprisingly open door for
meetings, despite his busy schedule. In addition, I would like to thank Dr
Jos Darling, Andrew Pettitt and Robin Long for their help with the engineering difficulties I have had and also for creating the CAD drawings seen
throughout the project. I would also like to thank Neil Pritchard and Catherine Jones for their attempts at finding solutions to some of the mathematical
problems posed throughout the project. Finally, I would like to thank Adrian
Sureshkumar and Chris Wallis for their expert knowledge of all things Java,
as well as anyone else who I may have missed out.
Abstract
The design and implementation of a software application for finding a single
pivot rear suspension mountain bike’s optimal swingarm pivot point. This
pivot point is found as a result of parameters specified by the user which are
entered via a graphical interface and make up a model of the bike. Finalised
models may be exported in a widely recognised CAD format.
Contents
1 Introduction
4
2 Literature Review
2.1 Modeling . . . . . . . . . . . . . . . . . . . . . . . . .
2.1.1 Modeling Techniques . . . . . . . . . . . . . .
2.1.2 Estimating Forces . . . . . . . . . . . . . . . .
2.1.3 Applying Our Knowledge To Create A Model
2.1.4 Rider Preference . . . . . . . . . . . . . . . .
2.1.5 Analysis of Current Bike Geometries . . . . .
2.2 Evaluation of Existing Design Systems . . . . . . . .
2.3 Computational Problem . . . . . . . . . . . . . . . .
2.3.1 Uninformed/Blind Searches . . . . . . . . . .
2.3.2 Heuristic/Informed Searches . . . . . . . . . .
2.3.3 Conclusion . . . . . . . . . . . . . . . . . . . .
2.4 Technology . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Implementation Language . . . . . . . . . . .
2.4.2 Compatibility with other applications . . . . .
2.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
6
6
13
14
16
18
20
21
23
27
28
28
29
29
3 Requirements Analysis
3.1 Analysis . . . . . . . . . . . . . . .
3.2 Requirements Breakdown . . . . . .
3.3 Format of Requirements . . . . . .
3.4 Requirements Gathering . . . . . .
3.5 Requirements of Significant Interest
3.5.1 Functional . . . . . . . . . .
3.5.2 Non-Functional . . . . . . .
3.5.3 User . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
31
31
32
32
33
34
34
35
36
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3.6
3.5.4 System . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4 Design
4.1 Modularisation . . . . . .
4.1.1 Module Overview .
4.1.2 Module Interaction
4.1.3 Module Function .
4.2 User Interface Design . . .
4.3 Random Search . . . . . .
4.4 Conclusion . . . . . . . . .
.
.
.
.
.
.
.
5 Implementation
5.1 Interface . . . . . . . . . . .
5.2 Design Preview . . . . . . .
5.3 Parametric Preview . . . . .
5.4 Input/Output . . . . . . . .
5.5 Search . . . . . . . . . . . .
5.5.1 Simulated Annealing
5.5.2 Consequential Search
5.5.3 Conclusion . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
Techniques
. . . . . . .
6 System Testing
6.1 Software Inspection . . . . . . . . . .
6.1.1 Search . . . . . . . . . . . . .
6.1.2 Model . . . . . . . . . . . . .
6.1.3 Peripheral . . . . . . . . . . .
6.2 Software Testing . . . . . . . . . . .
6.2.1 Simulated Annealing . . . . .
6.2.2 Alternative Search Techniques
6.2.3 Objective Function . . . . . .
6.2.4 Conclusion . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
39
39
39
40
42
44
47
48
.
.
.
.
.
.
.
.
50
50
51
52
52
54
54
61
63
.
.
.
.
.
.
.
.
.
65
65
66
67
68
69
70
71
74
75
7 Conclusion
77
7.1 Future Development . . . . . . . . . . . . . . . . . . . . . . . 79
2
A Requirements
A.1 Search . . . . . . . . .
A.1.1 Functional . . .
A.1.2 Non-Functional
A.1.3 User . . . . . .
A.1.4 System . . . . .
A.2 Model . . . . . . . . .
A.2.1 Functional . . .
A.2.2 Non-Functional
A.2.3 User . . . . . .
A.2.4 System . . . . .
A.3 Peripheral . . . . . . .
A.3.1 Functional . . .
A.3.2 Non-Functional
A.3.3 User . . . . . .
A.3.4 System . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
85
85
85
85
85
86
86
86
86
86
86
87
87
87
88
88
B Design
89
B.1 Prototypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
C Implementation
91
C.1 Search Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
D Testing
93
D.1 Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
D.2 Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
E Code
E.1 Main.java . . . . . . . .
E.2 mainFrame.java . . . . .
E.3 parametricPreview.java .
E.4 designPreview.java . . .
E.5 IOFile.java . . . . . . . .
E.6 DXFFileFilter.java . . .
E.7 randomSearch.java . . .
E.8 SimulatedAnnealing.java
E.9 PivotPoint.java . . . . .
E.10 PivotPointOrdering.java
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
99
99
101
115
119
129
145
147
151
156
158
Chapter 1
Introduction
Much thought has gone into the design of full suspension mountain bikes
in the last decade and many people claim to have found the best solution.
However, many top XC1 riders still choose to dismiss the new technology
in favour of rigid frames. The reason for this is the inefficiency that rear
suspension brings. There are three main disadvantages to full suspension
over a rigid bike, I will explain them below.
Bob: In a badly designed full suspension frame much of a rider’s efforts are
lost to “bob”. Bob is a term used to describe unwanted suspension movement generated from a rider’s pedal strokes. On a rigid bike, the significant
majority of any effort exerted by the rider when pedalling will be translated
into a forward motion. In a badly designed suspension bike, a large amount
of a rider’s efforts will be translated into an up and down movement, a waste
of energy.
Chain Growth: Chain growth is caused by having separate pivot and
bottom bracket2 points. As the swingarm3 moves around its pivot point,
it stretches the chain, this is shown in figure 2.3. As the suspension moves
through its travel (along arc A) the hub gradually moves away from the
1
Cross Country- A discipline of mountain biking where riders are expected to negotiate
both uphill and downhill trails.
2
Bottom Bracket - The point on a frame that the pedal cranks pivot around.
3
Swingarm - The part of a suspension bike connecting the wheel to the frame around
a pivot point.
4
bottom bracket, thus stretching the chain. Some engineers believe that chain
growth can be used in a design’s favour, whereas others disagree.
Brake Jack: The best explanation of brake jack that I have come across
is from Ethos Bicycles[5]:
When the suspension link that the brake is mounted to changes
angle relative to the ground, you have a suspension design that
is going to stiffen up when the brakes are applied. This is due
to the brake link rotating when the brakes are on - the tyre must
rotate with the link. But the tyre is on the ground and cant rotate
because the brakes are on. So either the suspension can’t move
up and down, or the tyre has to slide across the ground. On the
trail a bit of both occurs, the suspension resists moving and the
tyre slips a bit. This causes the rear of the bike to skip and slide
while you brake.
Brake jack only affects multi-link suspension designs. With a single pivot design there is nowhere to mount a brake that won’t be parallel to the ground
and, more importantly, there are no links to stiffen up when the brake is
applied.
At the moment, engineers apply an iterative approach to bike design, using
tools such as pro/Engineer to develop models of their designs, and then moving them into some sort of analysis software, or even skipping the analysis
stage altogether and building a prototype. This process is slow, expensive
and laborious. It also relies on the engineer’s experience to overcome the
problems outlined above. This is not ideal as there are potentially an infinite
number of pivot point positions to be considered.
In this project we will design and implement a software application that will
allow users to specify a bike’s major geometric parameters. These parameters
will then be used to feed an optimisation algorithm that will find the bike’s
optimum pivot point. At first we will focus on the simple single pivot suspension design, but time permitting, we will also explore the more complicated
suspension designs.
5
Chapter 2
Literature Review
2.1
2.1.1
Modeling
Modeling Techniques
For the final software application to return realistic results it must be based
on a realistic data. However, due to time constraints the model produced
will only take into account forces and rider preferences, it will not take into
account material properties. This should not limit the project in any way as
it is imperative in a suspension bike that there is as little flex in materials as
possible.
2.1.2
Estimating Forces
Since mountain bikes are not built to suit any one person’s needs and different people have different levels of fitness and strength that are difficult
to quantify, it is not critical that all forces are measured precisely. What is
more important is working out the correct angles that the forces are working
in. In this instance we will model forces around a rider of average weight and
an optimal pedal stroke.
Since pedal induced bob1 is most noticeable when a bike is being ridden
hard (the rider is putting as much energy as possible into moving the bike
forward), it makes sense for our model to reflect this. Therefore, all forces
1
The unwanted byproduct of the pedal motion that converts rider energy into shock
compression.
6
used in the model will be an estimate of the forces created in a worst case
scenario for a rider, e.g. sprinting or hill climbing.
Chain Tension
Chain tension has a huge part to play in the final model because it is the
only force which can be used to balance out the effects of pedal bob. Chain
tension can be calculated by working out the torque that a rider is exerting
on the pedal crank and then scaling this force in relation to the size of the
chainring2 that the rider is using. In our model we will assume a loss-less
transmission.
To calculate the chain tension it is necessary to work out the torque3 of the
chainring that the rider is using at the time. Torque can be calculated using
equation 2.1, where F is the downward force applied by the rider and D is the
distance from the center of rotation perpendicular to the force F (see figure
2.1).
T =F ∗D
(2.1)
According to R.A.Hebbert[29] a cyclist can impart twice his weight on a pedal
during sprinting or climbing. This is due to a number of factors including
pulling up on the handlebars and pulling up on the opposing pedal with
cleats or toe-straps. Assuming a rider weight of 75kg being applied at 90o
to the ground, 150kg (1470N) will be applied the pedal. Using formula 2.1
with a crank length of 170mm4 parallel to the ground, this creates a torque
figure of 249.9Nm. This is then scaled according to the size of the chainring
that the rider is using.
However, in the real world things are not as simple as they appear. Figure
2.3 shows how the different forces are not always applied in a linear fashion.
When a rider is pedalling, he does not simply push down on the pedal; this
is inefficient. In a perfect world the rider would be applying a force at a
tangent to the end of the crank at all times, but this is unattainable. What
actually happens is that the rider compensates as best they can within the
limitations of their body’s movement.
2
The gears in line with the bottom bracket.
A twisting force that leads to rotation
4
Mountain bike crank lengths tend to be between 165mm and 180mm.
3
7
Figure 2.1: Bicycle chainset diagram for torque calculation.
A study on cycling kinematics[37] shows that the point at which a rider is
exerting most of their effort on the pedal is when the crank arm is about 20o
below horizontal, as shown roughly in Figure 2.1. Because we chose to assume
the worst case scenario for all forces created, it follows that we should take
our torque measurement from here and scale the torque figure with regards
the smallest chainring5 . However, the study on cycling kinematics also shows
us that although the largest force may be being applied to the pedal when
the crank is 20o below horizontal, it is being applied in the direction F’. What
we actually want for our torque calculation is F which can be found using
simple trigonometry (see equation 2.2).
F = cosb ∗ F 0
(2.2)
The same is true of our crank angle. For our torque calculation to be correct
we must calculate the distance D. This can be done via equation 2.3. We are
now in a position to calculate the torque at the circumference of the pedal
movement (the green circle in Figure 2.1). This torque figure can then be
scaled in relation to the size of the chainring. We will discuss this further in
the next section.
5
This chainring is often referred to as the “Granny Ring”.
8
D = cosa ∗ D0
(2.3)
Direction of Chain Force
In order to use the chain tension that we calculated in the previous section
in our model, we must know the direction that the force is acting in. This
relies on a number of factors including the geometry of the bike and the
radius of the gears we are using. With regard to the geometry of the bike
specifically, we must know the height of the rear hub and the height of the
bottom bracket. This will provide us with a basis from which we can work
out the direction that the chain is pulling with respect to the rest of the bike.
Another factor that must be incorporated into the model is suspension sag.
Sag is a term used to describe the suspension compression used up solely by a
rider’s weight. The purpose of sag is to allow negative rear wheel travel. The
result of this is that the rear wheel can stay in contact with the ground more
of the time which leads to more a economical energy transfer between the tyre
and the ground. The recommended level of sag for the majority of mountain
bikes is around one third of the bike’s potential suspension travel[36], but
this is subjective.
Figure 2.2 demonstrates how different gears affect the direction in which the
chain force is working. The red arrow is a simulation of how the chain force
might be acting if it were in a high gear (largest gear at the front and smallest
at the back) and is in the simulation for comparison only. What we will be
focussing on is the blue arrow, the worst case scenario with the highest torque
value on the chainring and as such the greatest chain tension.
It is impossible to set a final value for the angle that the chain tension will
be working in for two reasons; firstly because our application will allow users
to vary the height of the bottom bracket, and secondly because a suspension
bike in use will constantly vary the angle between the bottom bracket and
the rear hub as it moves through its travel.
We can however set the size of the gears that we will use in our model. Again,
assuming a worst case scenario the rider will be in the smallest gear at the
front and the largest at the back. The front chainring (assuming a gear setup
with 3 chainrings at the front) will be about 85mm in diameter and the rear
will be in the region of 120mm.
9
Figure 2.2: Bicycle chainset diagram showing direction of chain force.
Chain Growth
Chain growth is the main limiting factor in suspension design and is caused by
having separate pivot and bottom bracket points. Without it, a bike is solely
reliant on the shock absorber/ damper assembly to attempt to eradicate pedal
induced bob. As the swingarm moves around its pivot point it stretches the
chain, this is shown in figure 2.3. As the suspension moves through its travel
(along arc A) the hub gradually moves away from the bottom bracket, thus
stretching the chain.
There are two schools of thought when it comes to chain growth; people
like Jon Whyte of Whyte Bikes/ Marin Bikes, and formerly the Benetton
Formula 1 team, believe that chain growth can be used to a design’s benefit.
Others believe that any chain growth is bad as it leads to pedal feedback6 .
For the sake of this project we will side with the believers in chain growth.
Regarding our model, a user’s accepted level of pedal feedback is difficult to
quantify. It is for this reason that users should be allowed to specify their
own level of tolerance for it in our application’s user interface. This in turn
6
Where the stretch of the chain it translated into a force that is noticeable to the rider
through the pedals.
10
raises issues regarding parameter boundaries. It is not acceptable to give
users free reign to specify any level of chain growth that they like, they must
be limited to ensure that the final model is physically realisable.
Walter Zorn’s pedal induced feedback calculator[39] offers a slight insight
into how much pedal feedback is acceptable in a design but only simulates
feedback over 20mm of suspension travel. In reality, mountain bikes can
offer more than 300mm of rear wheel travel, and this creates a few issues
over how we govern chain growth. If the user does not specify how much
travel they require from their design, then our application will not know how
to measure whether a design’s maximum chain length has been exceeded. For
example, in figure 2.3 you can see how the wheel’s arc of movement gradually
moves away from its preferred arc. 100mm through its travel the chain may
only have stretched 20mm, but this figure will increase as the suspension
compresses further. This may result in the chain growing beyond what is
considered reasonable.
For the sake of our application we will assume a maximum chaingrowth of
100mm, a measurement far in excess of what would be considered acceptable
from a real bike, but that gives users the scope to explore the boundaries of
suspension optimisation. In addition to this, we will suggest that users keep
chaingrowth below 50mm to preserve the bikes handling characteristics.
Shock Absorber
Suspended bikes are heavily reliant on shock absorbers/dampers to tune
their feel. Some manufacturers even claim to minimise the effects of pedal
induced bob with their damping units. However, shock absorbers play very
little direct part in the optimisation of the frame itself. As stated previously,
the aim of this project is to manipulate the major parameters of a frame in
search of an optimal pivot point. The designs created will be constrained as
little as possible by engineering limitations.
The most important factor for a shock absorber is its placement. Shock
absorbers are generally not designed with one particular bike in mind, they
are generic. Because of this they tend to be designed for applications that
exert a linear force on them. In all but the most extreme cases, with a single
pivot bike it is possible to place a shock absorber in a position where it is
subjected to a linear force.
11
Figure 2.3: Chain Growth Diagram
The other consideration to be made is the size of the force acting on the shock
absorber. Because of the nature of single pivot suspension bike design, shock
absorbers are generally compressed with the aid of leverage from a swingarm
or a series of tuned beams and pivots. It is this leverage ratio that determines
the spring rate7 of the shock absorber that should be used. Again, it is the
job of an engineer to find the best shock absorber mounting point and spring
rate for each frame design. We do however need to specify the height that
the shock absorber will be positioned in relation to our pivot point (see figure
2.5 dimentsion Hf ) for the sake of our optimisation algorithm. If we were
not to set a fixed shock absorber height then we would find that the pivot
point location which our algorithm returned would be distorted. This would
be due to the variation in leverage ratio. Our shock absorber height is purely
for the sake of our algorithm; in no way does it influence the positioning of
our optimal pivot point.
To conclude, shock absorption will play no part in our optimisation problem.
Although it does have a place in the design of mountain bikes, it will in no
way affect the position of an optimal pivot point.
7
The amount of force needed to compress a spring.
12
2.1.3
Applying Our Knowledge To Create A Model
Now that we know how to work out the forces acting on our model, we need
to find out how they translate into suspension movement. To do this we have
to define our model. After discussion with Dr J.Darling (Director of Studies
for Mechanical Engineering at the University of Bath) the model in figure
2.5 was created.
The principle of this model is to balance the turning moments8 imparted
by the tension in the chain (T) on the swingarm, and the opposing force
(F) from the shock absorber. To do this it is necessary for us construct a
formula.
Since our chain tension T is not acting linearly we must find its x and y
components (see figure 2.4). To do this we require some basic trigonometry.
The formula to find Tx is simply cosθT , and Ty can be found in a similar
manner by sinθT , where θ9 is the angle made by T and Tx . Now that we
have our linear forces we are almost ready to balance our turning moments,
but not before we have found our force P.
Figure 2.4: The X and Y components of our chain tension T.
P is the reaction to the force generated at the tyre’s point of contact with
the ground. If we assume that our model is moving at a constant speed on
a smooth surface, it follows that force T Hg will will be in equilibrium with
force P Hw . As we already know the values of T, Hg and Hw we can construct
8
A moment can be found by multiplying the force at a tangent to the pivot point by
its distance from the pivot point.
9
We must be careful to notice that θ is negative.
13
a formula that will tell us the value of P (see formula 2.4).
P = (Tx ∗ Hg )/Hw
(2.4)
We are now in a position to work out F, the moment at the shock absorber (a
view of our complete model can bee seen in figure 2.5). F is simply the sum
of all moments around the pivot point. We must remember not to overlook
Ty in our calculations, the byproduct of our calculations to find Tx . Our
model is now complete, see formula 2.5.
F =−
Tx (Hp − Hc ) + P Hp + Ty a
Hs − Hp
(2.5)
An optimal pivot point will bring our force F as close as possible to 0. Any
results where F is negative signify stiffening of the suspension under acceleration, positive values of F are an indication that the bike will suffer from
pedal induced bob.
Something to notice about a suspension bike is that it becomes increasingly
less likely that a rider will be pedalling as the suspension compresses. This
is due to people’s natural reaction to brace themselves under impact. This
characteristic has an effect on how we search for our optimal pivot point. As
mentioned previously, chain tension is the only force in our model which acts
to limit the effects of pedal induced bob. The direction that this force acts
in will change as the bike moves through its travel; we must therefore reflect
this in our search.
The best way to incorporate suspension movement in our model will be to
take weighted averages from equation 2.5 over a range of different suspension compressions. Maximum pedal efficiency will be achieved when a rider
and bike are in equilibrium, it follows that we should take 100% of our force
calculation’s value between zero and one third of travel and decrease this
weighting linearly for increasing compressions until we reach full travel.
2.1.4
Rider Preference
Because the feel of a mountain bike is as much down to rider preference as it
is the efficiency of a bike’s design, it is important to understand what makes
a good suspension design from a rider’s perspective.
To gauge people’s preferences for suspension design, a thread was started on
14
Figure 2.5: Equilibrium of moments diagram.
a well respected internet forum called BIKEMagic[38]. The purpose of the
thread was to gain an insight into what makes a mountain bike ride well from
a rider’s perspective, regardless of how efficient its design may be.
The general consensus of the forum members (including Mike Davis, ex-editor
of Mountain Biking UK magazine) is that a bike’s characteristics are down to
its geometry alone and that different riders have different preferences regarding their suspension designs. Some prefer a smoother movement (provokes a
bouncy feel), others are prepared to sacrifice a smooth movement in favour
of greater pedal efficiency (tends to lead to skittish handling).
Another point that was raised in the discussion was that multi-link suspension designs are becoming the norm for many bicycle manufacturers. This is
because they allow engineers to tune a bike’s rear wheel path more accurately
than single pivot designs. This in turn means that suspension characteristics can be manipulated in different ways at different stages of travel. For
example, in the early stages of travel it may be more important to minimise
the effects of pedal-induced bob. To achieve this an engineer would make
the wheel move backwards early on its travel. However, as the bike moves
further through its travel the focus may be on plush feeling suspension. This
15
could be achieved keeping chain growth to a minimum.
Overall it seems that the way a bike feels is down to its geometry. This has
very little bearing on the positioning of an optimal pivot point but does have
an effect on the length of the swingarm, a parameter which will be set at the
user’s discretion. Also uncovered in the discussion were peoples preferences
for suspension feel. Some people prefer a supple suspension feel, others prefer
a tighter feel. These characteristics are less a suspension design issue and
more a shock absorber setup one.
2.1.5
Analysis of Current Bike Geometries
There are many different approaches to mountain bike suspension design. To
gain an understanding of what makes a good design it is important to look
at bikes that are considered to be good by the riders themselves. It is also
important that bikes are compared using the same parameters, something
which manufacturers tend to personalise. Figure 2.8 shows the most common perception of bicycle geometry, this will be the basis for all geometrical
references throughout this project.
There appear to be two schools of thought when it comes to single pivot
mountain bike design; one is to place the pivot point as close to the bottom
bracket as possible, see figure 2.6. The other is to position the pivot point
a small distance above and infront of the bottom bracket, as seen in figure
2.7. The benefit of positioning the pivot point close to the bottom bracket is
a suspension design with very little chain growth. This results in a smooth
suspension feel with very little pedal feedback. In comparison, placing the
pivot point a small distance above and in front of the pivot point will cause
some chaingrowth but should limit the effects of pedal induced bob.
Since these pivot point locations are tried and tested, we can use them to
gain an indication of how our finished application is performing. A user who
enters parameters that strictly limit the amount of chain growth should be
presented with a pivot point position that resembles that of figure 2.6. A
user who enters more relaxed parameters with respect to chain growth should
expect to see a pivot point location like the one shown in figure 2.7.
16
Figure 2.6: An Kona single pivot suspension design.
Figure 2.7: A SanAndreas MountainCycle single pivot suspension design.
17
Figure 2.8: Mountain Bike Geometry Diagram [1]
2.2
Evaluation of Existing Design Systems
Since mountain bike design is a fairly specialised area, there are not many
commercially available software applications that are specific to the task.
Research has shown that the majority of bike manufacturers use everyday
CAD and analysis packages for the design of mountain bike frames. These
applications are limiting for designers because they rely on the engineer’s
intuition to get the design right. In this section we will look at a few applications that have been developed/are used in mountain bike development at
the moment.
The first tool that deserves to be touched upon is Pro/Engineer (often referred to as Pro/E or Pro). In reality Pro/E is actually a suite of programs that allow engineers to create solid models at a very high level [35].
It is feature-based; whereas with some CAD packages an engineer will be
required to draw lines, arcs and circles, Pro/E allows users to specify extrusions, sweeps, cuts and holes. The benefit of this is that the engineer is
given the freedom to think about the problem in hand rather than how they
will represent the model in a 3D environment. Although all these features
18
promote a free thinking approach from engineers, Pro/E still requires the
user to have a strong grasp of modeling techniques and bike design. It is for
this reason that enthusiasts/developers have created other, more specialised,
tools to simplify the creation of bicycle frames.
An example of a tool that has been created by an enthusiast is a product
of The Bicycle Forest. The Bicycle Forest is an innovative company that
specialise in bike rentals. In addition to rentals they offer a tool they call
BikeCAD[14] which allows users to design their own frames in a format recognisable to engineers.
BikeCAD is a parametric CAD tool specific to bicycles. It differs from normal
CAD applications in that users are not able to edit the number of parameters
in the design. Users select from a number of different classical frame templates (Road Bike, Rigid Mountain Bike, Single Pivot Full-Suspension Bike,
Tandem, Recumbent) and modify the frames measurements to suit their
needs. The most useful feature (and where it benefits over Pro/E) is in its
ability to assess a design’s suspension characteristics. In Pro/E an engineer
would need to export their CAD file into a separate analysis program to get
an idea of the design’s suspension properties. With BikeCAD the analysis
feature is built in and available to the user throughout the design process.
BikeCAD’s suspension characteristic analysis view allows user’s to plot the
difference in rear wheel vertical travel against chainstay length10 at varying
shock compressions. It also estimates the vertical rear wheel travel and gives
an indication of the amount of sag that is to be expected from the design.
All information presented is in a concise, easy to understand format which
makes the application very usable.
Another tool specific to bicycle design, which shares much of BikeCAD’s
functionality, is Linkage[33]. Linkage gives users the opportunity to design
bikes with very complicated suspension designs and view their characteristics
via a number of graphs and diagrams. Also offered is the option of importing
your own bike design by way of file or by importing a photo and tracing its
outline into the program.
Linkage’s suspension characteristic analysis view is far more comprehensive than that of BikeCAD. Users are given the opportunity to view pedal10
area between the bottom bracket and the rear wheel center
19
kickback11 graphs, material stress graphs (lateral and horizontal), swingarm
leverage ratio12 graphs and axle path graphs. With all this information (assuming they understand it) the user will have a good understanding of how
their design will behave once built.
The three tools outlined are all suited to bike design in different ways. Pro/E
is a tool that may hold a preference for engineers due to its flexibility and
3D modeling features. Programs such as this will always have their place in
mountain bike design, but are often not best suited to creating initial designs.
In the case of the mountain bike, models can only be evaluated for efficiency
once a prototype has been completed. Then follows the iterative process of
exporting the model into an analysis package, reading results, adjusting the
model and then re-exporting the updated model to see if improvements have
been made.
In contrast to Pro/E, BikeCAD is more suited to the enthusiast with a desire
to create a one off mountain bike, but who may not possess the necessary
skills to use a CAD package.
This leaves Linkage, a tool that seems to have gained popularity amongst
the bike buying public as a means of analysing existing suspension designs
prior to purchase rather than being used to create new bike designs. Even so,
Linkage is an extremely competent tool that is capable of very detailed 2D
bike prototyping and deserves more of a presence in the commercial design
of full suspension mountain bikes.
Where all these applications fall down is in their reactive nature. All the tools
mentioned require the user to have a good understanding of the suspension
data’s meaning and to use this understanding accordingly. Unfortunately,
with this approach it is very difficult for a designer to find the optimal balance between their desired design characteristics. It is for this reason that
frame design is typically the domain of experienced engineers.
2.3
Computational Problem
In order to find the optimum pivot point for our model it is necessary to balance our forces optimally. To do this, some sort of search is required. There
11
12
side effect of chain-growth.
The leverage force that is applied to the shock.
20
are many different styles of search algorithms, some which are applicable to
our problem and some which are not. Search algorithms can be judged by
the following four criteria [30]:
Completeness If a solution exists, will it be found every time?
Time Complexity How long does it take to find a solution?
Space Complexity How much memory will be used executing the search?
Optimality If a solution is found, will it be the best?
Our problem will always have an optimal solution, therefore our search algorithm must be complete or a very good approximation. With regards time
complexity, it would be useful if the optimal solution is found quickly but
not essential (as long as it stays within time parameters specified in the requirements section of this document). Again, with space complexity it is not
essential that the program runs using a tiny amount of memory, but the less
memory it uses the better. The goal of this project is to find an ideal pivot
point for a mountain bike; therefore optimality is an important criterion.
It is expected that the search implemented will converge to a single point
or line of points on the model. Therefore an uninformed/blind search13 , although likely to find the optimal solution, will not do it in the most efficient
manner. A better solution would be a heuristic/informed search14 which will
take less time to reach the optimal point, but either will work.
2.3.1
Uninformed/Blind Searches
It is common to represent search data structures as trees; this allows the
concept of nodes and branching to be developed. Four of the six main uninformed search strategies are outlined below (Bidirectional and Uniform cost
searches have been omitted because they are not applicable to our problem).
These searches may prove to be useful in the prototype stage of the software
build.
13
A search where the only distinction between steps is whether a goal state has been
reached of not.
14
A search that gains knowledge as it searches and makes informed decisions about its
next step.
21
Breadth First Search: The method behind breadth first search is an
exhaustive one. It begins by expanding the root node d at depth15 0 and
then goes on to expand all the nodes at d+1, d+2... d+x. Therefore this
process has the complexity O(bd ), where b is the branching factor16 . In
favour of breadth first search, it is guaranteed to find a solution if one exists,
however its time and space complexity are huge. For instance (assuming a
branching factor of 10) at depth 10 there will be 1010 nodes created. This
equates to 128 days of processor time (assuming a processing speed of 1000
nodes/sec and nodes of size 100 bytes each) and even worse, 111 terabytes
of memory. This renders breadth first search an unrealistic problem solving
strategy for searches above depth 3 [30].
Depth First Search: Depth first searching (commonly implemented recursively) is good for problems with many solutions and is often faster than
the breadth first search. It works by always expanding the deepest nodes of
a tree until it reaches a dead end; at this point it finds its way back up the
tree and expands nodes at a shallower level. All values calculated from a
route down the tree are stored in a stack and reproduced when needed. This
results in low memory usage since only one route is stored at any one time.
The storage required by this search is only O(bm), where b is the branching
factor and m is the maximum depth of the search. However, in the worst
case, the time complexity is O(bm ) which leaves it in a similar situation as
the breadth first search with regards processing load. There is also the possibility that this search method will recurse to an infinite depth. As a result
of this, depth first search is classed as incomplete.
Depth Limited Search: This is an evolution of the depth first search. It
is the same in every way but for having a limit on the depth that the search
can descend to. Having this depth-stop eradicates the problem of a search
getting stuck in any anomalous branches. It does however create the question
of where to set the depth-stop. Too low and no gain will be seen over the
original depth-first search, too high and you will never reach the goal state.
As may be expected, the time complexity of the search is O(bl ) and the space
complexity is O(bl) where l is the depth limit.
15
16
The number of levels in a trees branching structure.
The number of new nodes that each root creates.
22
Iterative Deepening Search: This search method builds on what is implemented in both the depth first search and the depth limited search. The
iterative deepening search takes advantage of the aforementioned searches’
exponential nature by varying the depth limiter. Exactly the same method
as the depth limited search is implemented, but the search is run many times
with an incrementing depth parameter. For example, in a problem that has a
solution at depth 5, it may be reasonable (with a depth limited search) to set
a depth of 10. This is a huge waste of resources, because all searching beyond
the depth 5 is needless. Even if the depth limited search were to be lucky
enough to have its depth parameter set at exactly the right level, due to the
exponential growth of the search, the iterative deepening search would only
be (roughly) 11% less efficient [30]. The iterative deepening search’s time
complexity is O(bd ) and its space complexity is O(bd).
2.3.2
Heuristic/Informed Searches
Heuristic searches solve problems by learning about their environment using
trial and error informed by rules. In doing this they are able to cut down
the number of repeated or unnecessary search steps that are made before a
goal state is reached. As a result, heuristic searches have lower time and
space complexity than their uninformed counterparts. Examples of heuristic searches include best-first search, memory bound searches and iterative
improvement algorithms.
Best-first Searches
Best-first searches follow similar principles to their uninformed counterparts,
in particular depth-first search. However, they factor in a heuristic function17
to estimate the next node to expand. This function’s decision is based on how
far away it perceives the goal state to be. Best-first searches are often best
suited to domains where a direct route to a goal is almost always impossible,
such as route finding. It is possible to adapt the behavior of a best-first
search to suit a given problem. For example, the basic greedy search18 can be
adapted to form an A∗ search19 simply by adding another evaluation function
17
often referred to as h.
One of the simplest best-first strategies aimed at minimising the estimated cost to
reach the goal.[30]
19
An adaption of greedy search aimed at minimising the total path cost.[30]
18
23
to the decision process. This new evaluation function gains inspiration from
uniform-cost search.
Memory Bounded Searches
Memory bounded searches (as referred to previously) are a class of search
techniques aimed at keeping space complexity to a minimum. Two well
known searches of this type are IDA∗ (Iterative Deepening A∗ ) and SM A∗
(Simplified Memory-Bounded A∗ ), both are adaptations of the A∗ search intended to minimise memory usage. In the case of IDA∗ , one of the most
memory friendly blind searches has been optimised further by integrating it
with an A∗ search strategy. The result is an optimal and complete search
routine, subject to the same conditions as the A∗ search, but that only requires enough memory to store one route through a tree. A good estimate
of the storage requirements of IDA∗ is bd, where b is the breadth and d the
depth of the tree being searched.
IDA∗ ’s downfall is that it is not good at solving problems similar to that of
the traveling salesman20 . This is because its heuristic value must change for
every state that it is in. It follows that IDA∗ ’s complexity in the worst case
is O(N 2 ) where N is the number of nodes to expand.
SM A∗ search attempts to overcome IDA∗ ’s problems by allowing itself to
remember as much search history as its memory allocation permits. When
there is not enough memory available to store the whole search tree, nodes
must be dropped; these nodes are called forgotten nodes. SM A∗ ’s strategy
for dropping nodes is to drop what it considers to be the least promising.
The only prerequisite of the search is that it is given enough memory to
store the shallowest of solution paths. Without enough memory to store the
shallowest solution path the search loses its complete status. With regards
optimality, the search is only ever optimally efficient when there is enough
memory available to store the entire search tree.
SM A∗ ’s true strengths lie in its ability to solve notably more complicated
problems than A∗ without suffering a large space complexity (assuming that
the memory allocated is limited). SM A∗ ’s downfall however, is when its
20
A common test for search routines; a salesman must visit a number of different locations and wishes to do so in the shortest route possible.
24
memory allocation is too small for the problem in hand. When this is the
case, SM A∗ must continually swap nodes in and out of memory.
Iterative Improvement Algorithms
Finally, iterative improvement algorithms; these are often the most practical
of search routines due to their ability to find a goal state without following
a strict search path. A good way to understand the iterative improvement
approach to problem solving is to think of an undulating landscape where our
objective is to find the highest peak. The job of an iterated search algorithm
is to move around this landscape in search of the goal state. There are two
main types of iterative improvement algorithms, Hill Climbing and Simulated
Annealing, both of which are applicable to our problem.
Hill-Climbing: This algorithm is simply a loop that moves in the direction
of the goal. Because of its iterative nature there is no need for it to store
any of its previous states, the result is a search technique with very low
space complexity. Unfortunately this approach may fall into a series of traps
such as finding a local maxima21 , finding a plateaux22 or finding a ridge23 .
It is clear that this search method will be very limited in its application
areas if it converges to the first peak it comes across. It is for this reason
that random-restart hill-climbing was invented. Random-restart hill-climbing
conducts many hill-climbs starting at random points on the landscape and
stores the result of the evaluation of the highest peak. This method converges
to a solution very quickly given a simple landscape; however, with a more
complicated problem, say for example an NP-complete problem, then the
chances are that it will not be able to find a solution in anything less than
exponential time.
Simulated Annealing: This technique exploits the way in which metal
cools and freezes into a minimal energy crystalline structure [7]. When a
metal cools its atoms gradually pack together. Depending on the speed at
which the metal cools, the density of its atoms varies. If a metal is cooled
21
As opposed to a global maxima; a local maxima is a peak which is high point in an
area, but not for the whole search problem.
22
A flat part of the landscape which provokes the search to conduct a random walk.
23
A characteristic of a landscape that has a peak, but that has sides with a much steeper
gradient than its top. The search algorithm may oscillate from side to side.
25
quickly, then its atoms are not given time to settle into a densely packed
structure, the opposite is true if the metal is cooled slowly. It is similar in
style to the basic hill-climbing algorithm, but benefits from a few important
differences.
The benefit of simulated annealing over some other search techniques is that
there is less chance that the process will get stuck in a local maxima. When
implemented to solve the hill climbing problem this technique avoids local
maxima by varying what is called the temperature (the control parameter
for the algorithm). The major benefit of this is that, unlike other search
techniques, it is able to use its temperature to escape local maxima. The
temperature of the algorithm can be compared to the temperature of metal
when cooling. When the algorithm is told to act at a high temperature,
big jumps are achievable, allowing the search process to get away from local
maxima. However, when the algorithm nears its goal state, it is able to cool
its temperature and focus in on a more accurate solution.
The basic structure for the simulated annealing algorithm is as follows:
1. Input and asses initial solution.
2. Estimate initial solution.
3. Generate new solutions according to temperature.
4. Assess new solutions and select best.
5. Accept new solution? Yes-continue, No-goto 7.
6. Update Stores.
7. Adjust Temperature.
8. Terminate Search? Yes-continue, No-goto 3.
9. Stop.
In order to complete a simulated annealing search it is necessary to have a representation of possible solutions, a generator of random changes in solutions,
a means of evaluating the problem functions and an annealing schedule24 .
24
An initial temperature and a set of rules for lowering it.
26
Regarding speed, simulated annealing is largely dependent on its annealing
schedule. Geman[31] derived an annealing schedule which was adequate for
convergence to a goal state, but in practice, according to Lawrence Davis
and Martha Steenstrup [10], was too conservative. It was considered to be
conservative because many of the problems that simulated annealing is used
to find the solutions for converge naturally to a certain point or plateaux.
As a result, it is not as important to spend time in higher temperatures.
The main reason for simulated annealing’s speed issues are the same as those
that hinder annealing in real life. To get the algorithm to converge on the
optimal point it is necessary for the process to stay at certain temperatures
for long enough, so that a good enough sample of points may be gained (assuming there is no natural convergence to a point as we discussed previously).
If this is not the case then an optimal solution may not be found.
Given an infinite amount of time, simulated annealing will find an optimal
solution, but in the real world this is impossible. What actually happens is
that a rule is defined as to how many steps the algorithm should take. As
long as this number of steps is sufficiently high then a good approximation to
the solution will be found. Setting the limit too low would result in a search
that has not been given time to converge to the optimal point. The same
is true of the algorithm’s temperature; if the temperature of the algorithm
is cooled in steps that are too large, or not enough time is spent at each
temperature, it may converge to the wrong point. Because of these issues it
is essential that the parameters of the annealing algorithm are set correctly.
2.3.3
Conclusion
To begin with, due to our search problem being similar to other problems
which have been solved using iterative improvement algorithms, and their
strong links with the engineering community, it is safe to rule out all but
the iterative improvement algorithms in our hunt for a search algorithm, although an understanding of the problem of search and its various algorithms
is helpful.
It is imperative that our search for the optimal point does not get stuck on
false peaks, as this would render our application useless. Therefore, it is the
case that simulated annealing is probably the most applicable solution in
our problem domain. Although others may find the same result, we cannot
27
be sure that they will not get stuck in local maximas. Whereas, given the
correct control parameters, simulated annealing is assured to find a good
approximation to the solution.
With respect to time complexity, it is important that our search be completed in a reasonable amount of time. Although simulated annealing is not
considered to be the fastest of searches, it is possible (once we have an understanding of its trends in convergence) to manipulate the search parameters
to see performance gains.
2.4
2.4.1
Technology
Implementation Language
The language with which we implement our program is not hugely important.
In the interests of code reuse, it would be nice to use an object oriented
language. This would give us the opportunity to define certain aspects of
our model in separate classes, in turn allowing us to easily modify the code
at a later date to include functionality for multiple pivot suspension designs.
Another consideration is the quality of the language’s graphics library. Java
has a comprehensive graphics library which is simple to use; this is not the
case for C++. A result of a good graphics library will be the ability to create
a graphical representation of our model very quickly and easily.
In regards to speed, C++ is faster than Java because of its compiled form, its
lack of garbage collection and increased potential for optimisation[18]. Both
languages are cross platform, which means that our final application will run
on all of the major operating systems (Java on a virtual machine and C++
on the machine’s native hardware), although Java will run on any platform
without re-compilation.
Overall, the choice between C++ and Java is purely down to user preference.
In this instance we will side with Java because of its ability to create applets,
a feature which will allow us to display our application on the internet.
28
2.4.2
Compatibility with other applications
For our application to be of use in the real world it must integrate with other
applications. The next logical step for an engineer after creating a model in
our program would be to export it into a CAD package. To stop this process
being a matter of redrawing our application’s output into another program,
it would be of great use if we allowed our model to be exported via a file
format recognisable to CAD packages.
Since the model we will be exporting may be used in any number of CAD
packages, it follows that we should find a file format that is recognisable to
as many of them as possible. Research has brought to our attention two file
formats: DXF and CDF.
CDF[9] (CAD Distillation Format) is a comprehensive 3D file format intended for use across a number of domains including medical visualisation,
CAD and visual simulations. It is a product of the web 3D consortium and
as such is an open source format. The web 3D consortium prides itself on its
conformity with XML standards which therefore allow CDF files to be used
in many different situations. However, because of the CDF’s wide application domain it has become rather too complicated for our needs.
In comparison, the DXF[4] (Drawing Exchange Format) is much simpler and
is recognised by all the major CAD packages. There are a number of tutorials
available for the creation of DXF files, which will make life much easier for
us when we come to program our CAD document creation routine. Should
we wish to implement the export function in our program we should without
doubt pursue this format further.
2.5
Conclusion
During this literature review we have constructed a physics model that tells
us the moments being imparted on our simulated shock position. From this
we have learnt much about the transfer and behavior of forces in real life
applications. From our model we have been able to make informed decisions
as to which search algorithms will suit the domain we are working in. The
result of which was our decision to use simulated annealing for its ability to
find near optimal solutions without getting stuck in local maxima.
29
We have learnt that public opinion regarding mountain bike suspension design very much divided. The majority of people buy bikes for their frame
geometry first and worry about its efficiency second. We also picked up on
the fact that the most efficient bikes are more complicated multi-link designs which allow designers to tune the path of the rear axle more accurately.
However, we also know that there is still a demand for single pivot bikes in
the full suspension market due to their simple, robust nature.
From our research into the many areas that our project will cover, we have
gained valuable knowledge of how best to approach it. We have formulated
a physics model and discussed the scale of forces which will act upon it. We
have debated how best to search for our optimum pivot point and what technologies will provide us with the tools to build our application. We are now
in a position to begin the development process.
30
Chapter 3
Requirements Analysis
Our literature review has provided us with an insight into how our project
will progress. We are now in the position to begin thinking about what is
required of our final application. We must be careful at this stage to gather
a complete set of relevant requirements to allow us to progress with the next
stage in our development process. This will in turn aid our development of
applicable test strategies.
We will focus our requirements gathering around functional and non-functional
requirements, user requirements and system requirements[34]. Breaking it
down in this way will promote a more focussed approach to gathering requirements and will provide us with a good framework from which to develop
our application.
3.1
Analysis
Many of our systems requirements are generic to this application area; we
can therefore look at other applications, such as those discussed in our literature review, as a source for requirements. In addition, it is critical that
we take into account user requirements so as not to repeat any mistakes
these applications may have made. In our literature review we analysed a
discussion on the popular mountain biking internet forum, BIKEMagic [38].
This discussion offers some insight into what the end user requires from a
Mountain Bike Rear Suspension Design Optimisation program.
31
3.2
Requirements Breakdown
Our requirements will be broken down into three categories: Search, Model
and Peripheral. In breaking our problem even more, we further increase our
chances of compiling a comprehensive requirements document. Below we
will define the criteria which our requirements must meet to fall within each
category.
Search: For a requirement to fall within this category it must be distinctly
relevant to a search algorithm. Any requirement that becomes apparent
as a result of said search algorithm should fall into its respective group
and not be placed in this category as a matter of course.
Model: Model requirements cover requirements that relate to the bike model
in software.
Peripheral: Requirements applicable to this category shall include standard
user and software requirements along with any other requirements that
do not fall within the other two categories.
3.3
Format of Requirements
Because some requirements are more important than others, we shall implement a ranking system to differentiate them. Breaking down the requirements in this way will allow us to focus in on more important aspects of
the application’s design without becoming compromised by less important
requirements. We will use standard English language to infer rank in our
requirements as follows:
“The system must...”
This is a requirement of the highest importance. The satisfaction of this
requirement is of critical importance to the success of the application.
“The system should...”
Not critical, but should appear in the finished application. If a more important requirement hinders the implementation of this requirement, then the
more important requirement shall take precedence.
32
“It is preferred that...”
These requirements are not vital to the success of the application, but may
improve the application beyond its elementary form.
3.4
Requirements Gathering
Due to the contrasting stages in the development of our application, it will
be necessary to use different methods to gather our requirements. In our
literature review we studied varying sources of information, all of which will
aid our process.
To begin we will consider our model; we have already looked at what mountain bikers require from a single pivot suspension design [38]. From this
discussion we unearthed an article explaining what parameters a mountain
bike designer may look to optimise in their search for an optimal pivot point
[15]. We can draw many requirements from this source, all of which will add
to the realism of the model, and thus the product we create.
Another source from which we can draw requirements for our model are the
designs of existing bikes. There are many single pivot suspension designs on
the market, the majority of which claim to have the best suspension design
for their intended purpose. We must be wary however, not to get drawn
into compiling our list of requirements from bicycle manufacturer marketing
hype, as mentioned in our discussion on the BIKEMagic forums.
Focussing on our search, we immediately uncover a trivial requirement, that
the optimal pivot point position be found within a specified, acceptable tolerance. However, there are many other requirements that need to be looked
into that will make our final application usable, one of these being the time
spent processing the search. To gather requirements of this nature is tricky
as the speed of our search algorithm is dependent on the landscape we are
searching, language of implementation, quality of code and the hardware our
application is run on. However, we must give our application every chance
of being usable. It is therefore necessary that we look into user’ accepted
response times and build our application around this. Jakob Nielsen’s book,
Usability Engineering [26], is a good source from which to gather requirements of this nature.
Another source which may improve our understanding of acceptable search
33
speeds are the journals and books which explain the search algorithms. The
majority of these journals will offer an indication of an algorithms complexity. From this we can grade our search techniques as to how fast they should
be finding points in relation to each other.
Finally we shall look at what we have defined as our peripheral requirements.
The application we are developing is fairly standard regarding its user interface and interactivity. As such, we can learn from the vast array of HCI1
research that has already been carried out.
When discussing matters of HCI design we shall look to the widely accepted
book, Human Computer Interaction by Preece et al [28]. As well as using
this book as a reference during the design stage of our application, we will
use it as a source for requirements. Doing so will ensure our application is
as accessible as possible.
The application we develop will be aimed at a relatively well defined user
base in the form of engineering professionals and enthusiastic amateurs. It
is for this reason that we need not concern ourselves with the intricate details of HCI techniques which, although important, will take up much of our
development time. Our requirements will therefore only include the most
relevant of HCI requirements.
3.5
Requirements of Significant Interest
Now that we have established our requirements gathering process, we are
ready to collate our requirements document (appendix A). In the following
section we shall discuss the more interesting requirements uncovered in that
document, according to the rules outlined earlier on in this chapter. Other
details, whilst important, are of less interest for the general discussion.
3.5.1
Functional
The system must find the optimal pivot point according to the
algorithm we define. The algorithm should be capable of consistently finding the same point over multiple runs: So as not to baffle
our user, we must make sure our search consistently finds the same pivot
1
Human Computer Interaction
34
point. Should there be a situation where our search reaches a plateaux, we
should specify the side of the plateaux on which our search should conclude.
Our decision as to which side of the plateaux our search will stop shall be
influenced by engineering preferences such as swingarm stiffness or suspension travel. If a user specifies that the bike be capable of long travel, then
we should lengthen the swingarm, i.e. choose a pivot point towards the back
of the plateaux (nearer the front of the bike), to accommodate this.
If we still find ourselves searching on a plateaux, then we shall choose the
pivot point that gives us the shortest swingarm for the travel specified by
the user. The reason for this being, a shorter swingarm promotes stiffness
[11] due to the reduced leverage that the wheel can impart on the swingarm.
A spinoff of choosing a shorter swingarm is that manufacturers will need
less material to produce them, resulting in a design that is both lighter and
cheaper to produce.
It is preferred that the method of export for projects is via CAD
a file: For our application to be of any use, we need a method of exporting
our model’s dimensions. We could simply allow users to see the co-ordinates
of the major points of the bike in a text area; however, as most users will
be importing said co-ordinates into some form of CAD package, it would be
of more use to provide them with a facility to create CAD files containing
templates of their designs.
In our literature review we determined that the DXF file format was the
most globally recognisable file format to CAD packages. After discussion
with engineers about this file type we discovered compatibility issues with
Solid Edge2 and DXF files. We eventually discovered that Solid Edge requires
DXF files to be of DXF version 12 or above in order to open them. Therefore,
if we decide to implement the export of DXF files in our application, we must
be sure to produce them in accordance with the DXF version 12 guidelines.
3.5.2
Non-Functional
The system should have a user interface which responds quickly
to user input. All geometry updates should take place in around
0.1 second and searching for an optimal pivot point should take
2
A commonly used 3D modelling package.
35
no longer than 10 seconds. If it appears that searching will take
longer than 1 second then a notification or progress bar should be
displayed: In accordance with research into software usability by Nielsen
[26], we must be sure to make the users interaction with our application as
predictable as possible (this does not always mean fast).
Nielsen explains that a delay of only 0.1 second is permitted if the user is to
feel that the application is reacting instantaneously. In our application, this
limit relates to the changes that users may make to the bike’s geometry. The
limit should be of concern to us as we will be performing multiple calculations to redraw the frame with every change the user makes.
Because searching for a pivot point will be carried out comparatively infrequently, and has the potential to be relatively complicated and slow, it shall
fall into one of Nielsen’s other categories. If our search takes less than a
second to complete we need not worry about implementing any new features
to alert the user as to the progress of the search, as it is likely that their
attention will still be held by our application. However, if a search takes
longer than a second, it will be necessary for us to inform the user of the the
search’s progress. If this is the case, we will need to implement a progress
bar. In doing this we are both informing the user that our application is
busy (and has not crashed), and that they have a certain amount of time to
carry out another task.
3.5.3
User
The system must offer users an accurate view of the model. This
model should update in realtime when a user changes a parameter:
It is important that users have an idea of how the bicycle they are designing
will look in the real world. As a result of this, we will implement a preview of
how the finished design will look once built. Although not strictly necessary,
as most engineers will have preconceived ideas as to a bike’s geometry before
they use our application, it is a nice feature to have at our disposal, especially
when it comes to visualising our pivot point. The preview we implement will
behave in much a similar fashion to that of BikeCAD’s[14] but will have a
somewhat simpler layout.
Due to the fact that no measurements shall be taken from our preview, we
need not worry about rounding errors in our calculations when plotting our
36
model to the screen. However, our preview will be a scaled version of the
real model, as such we need to be careful when handling numbers to avoid
converting types. If a type conversion seems unavoidable, then rather than
simply casting types we shall take care to round numbers beforehand.
3.5.4
System
System requirements for our application are fairly generic to any window
based application. As such, we will not mention them in any detail. Nevertheless, there are one or two points worth touching on.
It is preferred that the search algorithm use minimal memory.
However, speed of search should take precedence over said memory
usage: We should be careful to point out here that we shall not penalise a
search technique if it is memory intensive. This is because computer memory has increased in size dramatically in recent times to a point where we
need not be overly concerned about running out of it. Plainly, if a search
technique appears to be using large amounts of memory then it may not be
right for the job, but we shall balance this against the speed and accuracy
at which it finds optimal pivot points.
It is preferred that the application implement Apple OS X look
and feel patches to further improve the appearance and usability
of the application whilst running in the Apple environment: We
shall be developing our application in the Java programming language. This
language makes a valiant attempt to mimic the native look and feel of the
operating system that its programs are running on. In spite of this it still
faces difficulties in Apple’s OS X environment in the form of menu bars.
In the Windows environment, menu bars are drawn as part of the main
application frame, but in OS X they become part of the operating system’s
task bar at the top of the screen. Currently, Java applications draw menu
bars in the default Windows position on all platforms but, with the inclusion
of a preferences file containing the correct options, this can be rectified.
37
3.6
Conclusion
During this chapter we have studied the best sources from which to gather
requirements for our application and dismissed those which were not applicable. We have learned how best to export our system’s output in a manner
compatible with existing applications through discussion with everyday users
of CAD and 3D modelling programs. We have discussed matters of HCI relating to our interface and determined many non-functional requirements.
Our list of requirements is now complete and, as such, we are ready for the
next stage in our design process.
Some of our more useful resources were applications such as BikeCAD whose
interfaces and functionality provided us with many useful requirements. BikeCAD
was our main source for gathering requirements relating to user input. This
application has been proven as a useful tool in the bicycle design domain, as
such, we can be assured that any requirements taken from it are of use to us.
The requirements we have gathered are complete and take into consideration
a number of sources, all relevant to the final application. Our discussions
on the BIKEMagic forums, along with examination of the DirtWorld topic
“Turner/Horst vs. Turner/Kona, long and geeky” [15] provide us with a
valuable insight into what is required from a design perspective. To further
improve the quality of the design requirements, we could have discussed the
application with an engineer possessing expert knowledge of the problem
domain. However, such engineers are few and far between.
38
Chapter 4
Design
The purpose of this chapter is to explain the process that we shall undertake
in the design of our application whilst paying close attention to our recently
collated requirements document. In forthcoming sections we will break our
program down into smaller, distinct elements, called modules which shall
enable us to focus on the important design goals that they each represent.
We shall begin by identifying our modules and how they will interact with
one another.
4.1
Modularisation
The need for us to break our system into different modules is a consequence
of the very different design problems which they represent. As outlined in
our requirements analysis, we have three distinct areas to focus on: search,
model and peripheral. These groups were sufficient for our requirements
gathering process but may not offer enough detail for our design. In the
following section we shall investigate these groups and determine how best
to modularise our design in an effort to maximise ease of coding and future
development potential.
4.1.1
Module Overview
As we discussed in the previous chapter, the search element of our application
is required to be plugable. As such, it is necessary that we define it as a
distinct module, this affords us the flexibility to interchange different search
39
algorithms without the need for extra coding.
The model element of our application is slightly more complicated. Our
model is the basis for our whole application but has its own distinguishable
elements. In our requirements we explain that our application must have
both a parametric1 and a graphical preview of values entered by the user.
There is a clear distinction between these two elements in both function and
form, and, as such, we shall define them as separate modules.
The final element that we need to look at is what we describe as ‘peripheral’.
This element was originally devised to include interface requirements and
any other requirements relevant to our application. Nevertheless, during our
requirements gathering process we discovered a need for users to open, save
and export their designs in the DXF file format. This functionality shares
no similarities with anything in our interface, therefore, we will define user
interface and input/output as separate modules.
4.1.2
Module Interaction
Now that we have defined our modules we need to determine how they will
interact with each other. An obvious place to start with any system is the
user interface, it is from here that all user interaction will stem. The most
apparent form of interaction in our application will be between the user
interface and the preview screens. Every time a geometric parameter is manipulated by the user, the parameter’s new value shall be sent to these two
modules. In turn, these two modules shall refresh themselves to display the
new geometry.
A similar relationship, which we shall define now, is between the user interface and the file input/output module. This module, when prompted by the
user, shall open or save files containing the current values of the user interface
components. This interaction is much the same as those described previously.
However, in this instance, our user interface module and input/output module shall share a two way relationship. Interface values may be sent to our
input/output module for saving and may also be returned to the user interface when a file is being opened.
The final module for which we need to define a relationship is our search
1
This section will not share any functionality with the graphical preview.
40
module. This is the most difficult relationship to determine because it could
logically be between more than one module, interacting directly with the
graphical interface or through the design preview module. If we were to link
the graphical interface module and search modules we would be required
to pass multiple objects and values to and from the design preview module
through the graphical interface module. This method would be particularly
messy and would require effort to get working properly. It is for this reason
that we shall link the search module to the design preview screen. Although
defining a relationship between these two modules may create problems if
we later require other modules to interact with the search module, its benefits outweigh this. By defining this relationship we simplify our graphical
interface module and preserve its purpose as a module dedicated solely to
the drawing of interface components to the screen. We will also reduce the
number of parameters passed between modules.
Figure 4.1 shows how our modules will interact. Now that we have defined
the relationships between our modules, we can begin to look at the modules
themselves in more detail. In the next section we shall focus more on the
ways in which our modules interact and the parameters they will require in
order to perform their function.
Figure 4.1: Structural Model.
41
4.1.3
Module Function
Graphical Interface Our graphical interface is the starting point for all
user interaction with our system; it is from here that users will specify the
geometry that will represent our model. In the interests of consistency we
shall define a standard data structure for the storage of these design parameters. The data structure shall comprise an array containing each interface
parameter and shall be passed to the design preview and input/output modules. Our array shall maintain its dimensions at all times and be updated
every time a user changes a design parameter.
According to our requirements, our interface shall also allow users to define
the type of search they wish the system to perform. Consequently, we must
pass a value to our design preview (where we will be launching our search
module) to indicate which search method is required.
Design Preview Our design preview is the most complicated module; it
takes our predefined array and constructs our model. We must take special
care when programming it to reuse objects at every opportunity and not
create more objects than necessary, as this will adversely affect the speed at
which our model is rendered [16]. As stated in our requirements, we must
make sure that no rounding errors are made, any such errors could lead to
an unrealistic model being produced and, in turn, an incorrect pivot point
location being found.
Another thing to consider when looking at our design preview module is that
it is directly linked to our search module. As such, it is in this module that
we will create the necessary objects and variables to construct our search.
One of the principal objects that our search module will require is the frame
object2 . The frame object shall be constructed from the geometry passed
from the graphical interface module and shall define the area in which we
may search for an optimal pivot point.
As well as drawing our bike model to the screen, our requirements specify
that we must be able to plot our search path. As such, we require our search
module to pass back an array of all the points that it has considered in its
search process. It is then simply a case of looping through said array and
plotting its co-ordinates to the screen.
2
A digital representation of our bicycle frame.
42
Parametric Preview Our parametric preview is a fairly simple module
due to the fact that, as stated in our requirements, it is only required to
update three fields: effective top tube length, wheelbase length and effective
chainstay length. To calculate the values for these fields we will need the
frame object that we defined earlier. From this object we can extract dimensions and apply the appropriate geometric calculations to give us the values
we need in order to populate our fields. Again, all values in this module shall
be updated for every alteration the user makes to the graphical interface’s
parameters.
Input/Output This is a relatively straightforward module, its main purpose is as a file handler, but it shall also be used to export our frame object
to a DXF file. When functioning as a file handler, having been given an
instruction to save, our module shall take the geometry array and store its
values in a text file with the appropriate extension. If a user then wishes
to open this file, the file handler will retrieve all geometry values and return
a new array mimicking the structure of our standardised geometry array to
the graphical interface module.
As mentioned previously, this module shall also be used as a method for
exporting our frame object to a DXF file, from here it can be opened in the
majority of CAD applications. The export process will follow the same process as the save process. However, instead of defining our own file structure,
we shall adhere to that of DXF version 12. When we come to implement our
DXF file generator, we shall refer to the online manual at autodesk.com[3].
Search As we mentioned earlier in this section, our search module shall be
plugable. This affords us the flexibility to try many different search techniques to find our optimal pivot point. Each of these search methods will
require us to provide a boundary to the search in order for our pivot point to
fall within the bounds of the frame. This boundary shall take the form of our
frame object. All search modules implemented must take this object as an
argument and shall return a two dimensional point representing our optimal
pivot point. Again, as stated in our requirements, we must keep track of
all searches made; we shall do this using Java’s ArrayList object. By using
this we negate the need to continually define new array objects as our array
grows in size, allowing our application to run faster.
43
4.2
User Interface Design
Now that we have defined our application’s behaviour we can begin to look
at the design process at a higher level. The design of an applications user
interface is critical to its success, a good user interface should appear transparent to the user [28]. Much research has gone into interface design in the
last ten to fifteen years, the result of which are countless rules and guidelines
by which software developers are advised to adhere. In the design of our
application’s user interface we shall try to act in accordance with as many of
these as possible in the hope of creating an interface which is as transparent
as possible.
We shall begin by listing the user interface components that our application
must display.
• Open, Save, Save As, Export as DXF.
• All search types.
• Trace whole search, Trace optimal search.
• Search.
• Chain growth and permitted travel.
• Bicycle geometry fields.
• Design preview.
• Parametric preview.
One of Shneiderman’s “Eight Golden Rules of Dialog Design” [32] states that
developers should strive for consistency in their interfaces. Consequently we
shall keep our generic functions, Open, Save, Save As as and Export as DXF,
positioned where users would expect to find them in any other application.
The majority of applications keep such functions in a “File” menu at the top
of the screen, as a result we shall do the same. A further consideration to
arise from placing menus at the top of the application is the use of shortcuts,
it is common for applications to allow advanced users to use keyboard shortcuts to perform the common tasks that often appear in these menus. As it
happens, this is another of Schneiderman’s eight rules and, as such, we shall
44
implement keyboard shortcuts for the most common operations: New, Save,
Open and Export to DXF.
For the same reasons as stated above, we shall define a search menu allowing
users to chose between every search that our application is capable of. Due
to the discrete nature of our searches we do not wish to allow users to select
more than one at a time. The standard Java interface component for such
a circumstance is the radio button which, although frowned upon by HCI
professionals because of its size, is the only component applicable to our situation. We therefore stand no other choice but to use radio buttons in our
menu.
Also included in this menu, for continuity purposes, will be the options to
switch on and off the search trace. We shall use checkboxes as our user interface component here as their intended method of interaction matches the
behaviour of our menu items perfectly.
Another common feature of modern software applications is an accessibility
bar. This bar sits at the top of the screen, below the menu bar, and provides
users with quick access to a number of commonly used operations. We shall
take advantage of this feature to, once again, preserve consistency by adding
New, Open and Save buttons to our interface, and also to locate our Search
button who’s function is to allow users to search for an optimal pivot point.
In the interests of accessibility our buttons will display appropriate tooltips
along with both icons and text describing their function.
The next part of the interface shall contain all the design parameters. Unfortunately we do not have enough space to locate all of these on the screen
at the same time whilst still conserving readability. We must therefore find
some way of switching between them. Java’s standard method for getting
around this problem is to use either a scroll bar or tabbed panes. In this
instance we shall use tabbed panes as we have two clearly defined methods
of interaction. The first of these methods will be the JSpinner, a text box
that allows users to cycle through values incrementally as well as entering
them explicitly. We shall use this component with predefined bounds for all
geometric parameters with no relevance to the bicycle suspension, as these
values must be set accurately. For our permitted chain growth and suspension travel fields we shall use something different. Since these fields are not
required to be as accurate as their geometric counterparts we shall use JSliders. JSliders give the user quick control over parameter values and clearly
45
indicate the limits of input. We shall now look at the next logical stage in
our interface development, our preview modules.
Our preview modules require no human user input which removes some of
the difficulty associated with human computer interaction. However, it is
still important that we make our previews as easy to understand as possible.
Again, we are left in the position where we do not have enough screen space
to accommodate both preview’s comfortably. As such, in the interests of
familiarity [17] we shall once more use tabbed panes to separate our design
and parameter previews.
Our parameter preview is a fairly simple screen as it will only be displaying
three fields. Because of its simplicity there are not many things that can be
done to make it more understandable. We will nonetheless take care to label
each field so that the user knows exactly what they are being shown.
The design view shall pose some slightly different problems for us. We must
ensure that the different elements of the model can be differentiated from
one another, but also that we do not flood our inerface with colour and make
it difficult to understand. Our main aim here is to create contrast where
necessary [2] and to draw the user’s attention to the important parts of our
model. We will begin by setting the colour of the parts of the bike that are
of no direct importance to the search: the wheels and fork. We will colour
these objects in as dull a shade as possible so as not to draw attention to
them- black and grey respectively. Our frame represents the bounds of our
search area and, as such, should stand out. It is for this reason that we will
colour it red; although normally used as a warning, this colour is vibrant and
contrasts well with the white background.
Something else we must consider is how to colour the search trace. In our
requirements we define two different search traces, one that considers every
search leaf and another that considers only those leaves which have been followed. We must take special care when choosing colours to represent these
traces, to pick those with the highest contrast, as it is expected that the optimal search path will have significantly fewer points rendered to the screen
than its counterpart, making viewing particularly difficult. We will choose
light green when plotting all search leaves and black when plotting all followed leaves, but shall withhold our final decision until we can see our chosen
colours in the working interface.
46
Finally we come to the last element in the interface. In our requirements
we state that, if time spent in a search is longer than one second, we should
inform the user as to its progress [19]. However, because we are unsure as to
how long our searches may take to complete, we shall only implement this
feature if it becomes necessary.
4.3
Random Search
To enable us to test the user interface it is necessary that we develop a simple
search module. We could simply create a module which returns any point
within our frame bounds. However, seeing as we have already defined our
objective function (formula 2.5) in the literature review, we shall take a more
informed approach to the search. The method we shall use will be a random
search that will create a set of randomly distributed possible locations for a
pivot point and analyse them in relation to our objective function. Whichever
point is closest to being optimal is the point that shall be returned. The
algorithm implemented will look like so:
sample size = number of locations to try
points = 0
best point = null
while points < sample size;
point = get a random point inside our bicycle frame
score = determine the optimality of point using formula 2.5
if score is closer to 0 than existing best score
best point = current point
end if
increment points
end while
return best point
47
As we can see, this is a fairly straight forward algorithm which shall be
relatively easy to implement. However, the quality of pivot points that it
will find are expected to be fairly poor. The reason for this is that the
algorithm is simply picking the best point from a random distribution. We
have no control over where the distribution of points is in relation to our
bike’s optimal pivot point other than to increase the number of points that
we analyse. Doing this will increase the chances of finding a better pivot
point, but there is still every chance that we will return the best pivot point
from a poor distribution.
Nevertheless, we now have a search module with which to test our interface.
Given a large enough sample of possible pivot point locations, we should
see the point being placed in roughly the area that we expect the more
complicated searches to find.
4.4
Conclusion
This section has provided us with an insight into how we should go about
implementing our application. We broke our application down into modules
which assisted our interface design and should help us further when we begin
implementation. In addition we looked into common HCI design techniques
and applied them to our application wherever possible. This has resulted
in the creation of the prototype interface that can be seen in Appendix B,
figures B.1 and B.2. Finally, we designed a simple search module which will
enable us to test our application.
The modules we have defined will be extremely useful when we come to
implementation. If, when implementation is being carried out, we notice a
need to change the interaction of modules in any way, we will be required
to step back into design to ensure no errors are made that may affect the
rest of the coding process. Nonetheless, in an ideal world this is how our
application shall be structured and it is an important stage of our applications
development.
Our interface follows many predefined HCI guidelines and, as such, should be
relatively usable. However, in our design process it may have been beneficial
for us to test our interface prototype on a test bed of users to establish its
usability. Despite this, the interface we have developed is fairly simple and
48
should not pose any difficulties for the capable user group for which it is
intended.
Overall, our application design has provided us with a solid basis from which
to work from when developing our application. We are now at the point where
we can begin the implementation of our Mountain Bike Rear Suspension
Optimiser.
49
Chapter 5
Implementation
This section will be dedicated to looking at the more interesting parts of
the development of our system. We shall pay special attention to our search
algorithms, as these are the foundation of our application.
5.1
Interface
Our interface is fairly generic, and as such we shall not go into the intricate
details of our implementation. It is worth mentioning however, that we will
try to use lightweight Swing components wherever possible in a bid to speed
up the application at runtime. Swing also avoids the majority of difficulties
associated with cross platform implementation of Java applications and the
AWT1 [6].
Another point worth raising is the lack of suitable Java layout managers
when dealing with forms. In our interface design prototype (appendix B,
figures B.1 and B.2) we have laid out our design parameters in such a way
that we require a layout manger that provides us with a some form of grid
layout. The standard Java API provides us with more than one grid style
layout manager, but each of these suffer the same inadequacy; they all resize
components sitting within them to the size of the grid element that they are
part of.
Our search for a solution to this problem has led us to a package by the
name of RiverLayout [12]. This package allows us to lay our panels out
1
Abstract Window Toolkit.
50
using HTML2 style tags, giving us the freedom to place interface components
wherever we like without the side effects of Java’s native layout managers.
An example of the simple style of coding that this package affords us can be
seen in the following code extract3 (taken from our application’s “Geometry”
tab):
geomPanel.setLayout(new RiverLayout());
geomPanel.add("p center", new JLabel(
"<html>
<font size=’3’>
<b>Set Frame Geometry:</b>
</font>
</html>"));
5.2
Design Preview
From a usability standpoint our design preview is the most important part
of our application. As mentioned in previous sections we must ensure that
changes to the geometry by the user are rendered in real time and that special care should be taken not to create new objects.
The first obstacle that we encounter in our design preview implementation
is how we draw to the screen. The common method for this in Java is to
override a panel’s paint method. Once this has been done, the user is free
to draw wherever they like within said panel. However, drawing straight to
the screen from here is afflicted by hidden inefficiencies in the form of avoidable system calls to methods such as InputStream.read() [20]. It is suggested
therefore, that we use a Java’s BufferedImage class to solve this problem.
A consequence of using the BufferedImage class is that is has built in geometric transformations that we can use when rendering our image to the screen.
Our demand for this functionality comes as a result of the way Java has been
designed. When drawing to the screen in Java we use screen co-ordinates
(our origin is in the top left of the screen). When rendering our bicycles
geometry to the screen we shall be working in a traditional co-ordinate space
2
Hyper Text Markup Language
Extract used contains HTML in the JLabel, this is common to all Java components.
We are focussing on the first argument of the add method; “p center”
3
51
(origin in the bottom left of the screen). Ordinarily we would be required
to write our own transform so that all points be drawn to the screen in the
correct orientation. However, with the BufferedImage class we can simply
define arguments to this object and it will do it for us.
Our basic method for drawing the bicycle to the screen shall take the user
defined geometry and plot the six basic points to the screen. We shall label
the points from A to F, as shown in figure 5.1, and shall store their x and y
co-ordinates in a 2x6 array. This array can then be used as either a reference
for further calculations, or as the basis of our bicycle frame object. It is worth
noting here that we shall use Java’s Polygon class to create the bike frame.
The reason for using this class is that it will save us time when we come
to implement our search methods. The Polygon class includes methods for
getting the bounding rectangle of a polygon, as well as another method for
ascertaining whether a given point falls within a polygon. This will enable
us to easily determine our search area. It should be made clear at this point
that we will not attempt to glamourise the model in any way. Our model is
designed to be functional, and as a result, any attempt to make the model
look more like a bicycle would detract from its usability and eat into our
development time.
5.3
Parametric Preview
Our parametric preview is the simplest of our modules and as such has very
little functionality worth discussing. Our module takes the frame object
discussed in the previous section and extracts information from it regarding
the dimensions of our bike. This information is then displayed in the three
text fields which make up the module’s interface.
5.4
Input/Output
Implementation of this module is surprisingly straight forward. Java supplies
us with all the necessary tools for file handling, and as such we need only
follow common guidelines to implement them.
As mentioned in previous sections, it is necessary that we allow users to save
the contents of the bicycle geometry array. There are many ways that we
52
Figure 5.1: Bike Labelling Schema.
could go about this: for example, CSV4 or XML5 files. However, we shall
choose a simple text file and separate our values by line breaks. There are
no real advantages to this method over the CSV file, it is simply a matter of
personal preference. To prevent confusion when a user is browsing through
their files we shall append an extension to our filenames. This extension will
be influenced by the name that we give our application upon completion.
In much the same way that we wrote our geometry array to a file before, we
shall use the same techniques to export it to a DXF file. Our requirements
state that the method of export should be via DXF file, version 12 or above,
as such we must conform with this in our implementation.
When writing our DXF file we shall abide by the guidelines laid out in the
AutoCAD manual[3]. Although comprehensive, they do highlight the amount
of effort involved in complying with version 12 or above. Fortunately, there
are some applications, such as CADintosh [22], which still allow users to
import old DXF files and export them in the newer format. As such, we shall
4
5
Comma Separated Value.
Extensible Markup Language.
53
take advantage of this fact. We will begin by writing a simple DXF export
feature for our application, this will create a file in a format recognisable to
CADintosh. From here, we shall open one of our exported files and use the
CADintosh export feature to convert this into the DXF version 12+ format
we desire. This allows us to copy the contents of the new version 12+ file into
our application and simply change the locations of the hard coded values so
that they contain the appropriate dimension variables.
5.5
Search
There are numerous methods by which we could find an optimal pivot point
for our bicycle, many of which are outlined in our literature review. In the
previous chapter we specified that a search be given its own dedicated module.
This affords us the flexibility to interchange different search techniques with
minimal disruption to our existing code. In the following section we shall
discuss our implementation of the various methods used to find an optimal
point. We will omit the random search from this section as we have already
discussed its implementation in the previous chapter.
5.5.1
Simulated Annealing
In the literature review we make a strong case for the use of simulated annealing as a method for finding an optimal pivot point due to its ability to
avoid local minima. As such, it is clear that it should become one of our
search modules. Rather than coding our own algorithm it would appear to
make sense that we use an implementation that is already available.
The first implementation we shall look at comes in the form of the Orbital
library [27]. This library, on the surface, appears to contain everything we
need in order to find our optimal pivot point. However, when we attempt to
use it, it becomes apparent that the documentation supplied is not sufficient
and that any attempts to implement a simulated annealing search using it
would be fraught with problems.
The next stage in our search for a simulated annealing package leads us to
the Aima library [25]. Again, on the surface, this library appears to comprise
all we need for our search. However, on closer inspection we notice that it
has been written to solve a specific type of simulated annealing problem, the
54
eight puzzle. The eight puzzle problem is a search for the fewest number of
operations it takes to arrange an eight puzzle. As such, it requires that we
keep track of the algorithm’s depth via a tree data structure. It is this data
structure that makes communication between the various classes a problem.
If we were to use this library then we would end up making countless small
tweaks and many unnecessary calculations in order to find a feasible solution.
It is unfortunate that we cannot use these libraries, as they both capture
our modularisation theme perfectly by offering us multiple search methods
without a considerable coding overhead. However, if we are forced to make
compromises to use them, then they are not the tools for the job. Instead,
we shall code our own simulated annealing algorithm. So that we don’t have
to step back into design, we shall base our simulated annealing algorithm
on the Aima classes discussed earlier. As a result, we shall have 3 classes:
SimulatedAnnealing.java, PivotPoint.java and PivotPointOrdering.java. In
time, we shall discuss the purpose of these classes and the role they play in
our search procedure.
As demonstrated previously, the simulated annealing algorithm is fairly simple. The following code extract, taken from SimulatedAnnealing.java, shows
the basic implementation procedure and how the previously defined classes
relate to it.
public double[] search(){
//start our serch here
PivotPoint pp = new PivotPoint(frame);
PivotPointOrdering o = new PivotPointOrdering(hub, BB, travel, growth);
double[][] aList = new double[n][2];
double[][] oList = new double[n][2];
points = new ArrayList();
chosen = new ArrayList();
double[] curPivot = pp.altPivot();
do{
//Get n new random points
for(int i=0; i<n; i++){
55
aList[i] = pp.altPivot(variance, curPivot);
points.add(aList[i]);
}
//Sort and select our point according to our cooling schedule
oList = o.sort(aList);
curPivot = selectPoint(oList, temp);
chosen.add(curPivot);
//Cool and reduce variance
if(!hillClimb){variance = variance*0.95;}
temp = temp*0.95;
curDepth++;
}while(!isGoalState() && (curDepth != maxDepth));
return oList[0];
}
As can be seen, the search begins by creating new PivotPoint and PivotPointOrdering objects, as well as initialising aList (list of unordered pivot
points) and oList (ordered version of aList). We also declare ‘points’ and
‘chosen’, the purpose of which are to record our pivot points so that we may
trace our search path to the screen.
Before we enter our iterative search process we must determine where our
search shall begin. We will do so with a call to PivotPoint’s altPivot, a polymorphic method which, when supplied with no arguments, returns a random
point within our frame. We are now ready to begin our iterative search process.
The first thing we do on entry to our loop is to collate a set of possible
next moves from the initial position to store in aList. This is achieved via
multiple6 calls to our altPivot method. In this case however, we provide two
arguments: the variance and the current pivot point. With this information
our altPivot method can return a random point within a certain radius, determined by the variance, of the original point.
6
Number of actual calls is decided by the parent.
56
The next stage is to decide which of the points in aList will be the point from
which we continue the search. We begin by ordering aList with respect to
optimality, we shall call this oList. From here we can select a point from the
list to be our new pivot point. It is at this point that we realise simulated
annealing’s strength. Rather than picking the most optimal point from oList,
we are able to pick less optimal points in an effort to avoid pursuing a search
route that may lead us to a local minima. We do so via the algorithm’s
‘temperature’.
When our algorithms temperature is high7 , there is a greater chance that
we will choose a pivot point location of lower optimality. However, as we
progress our search we cool its temperature; this increases our chances of
picking a point of greater optimality and as such, allows us to target the
optimal point. There are various ways of cooling the temperature of an algorithm [23]; our search uses a simple method which focusses in on points very
quickly. If we find that we are finding local minima, then we should adapt
our cooling schedule so that it spends more time at higher temperatures.
This process is repeated until either we reach our optimal point or we arrive
at our maximum search depth. For each loop of the algorithm we lower the
variance, making big changes in pivot point location less likely, and also the
temperature. As stated previously, the result of this is to focus the search
on our goal state. In the case of this search we shall not specify a level of
optimality at which to stop searching, instead we shall always let our search
reach its maximum depth (although we have implemented a code stub to
allow us to implement this feature if necessary). The reason for this decision
is due to the speed at which our search runs; we are finding that we can
run through the algorithm to a reasonable depth in, what HCI guidelines
consider to be, speeds which are hardly noticeable to the user [26]. As such,
there is no point in halting our search prematurely, it makes more sense to
let the search run all the way through in the knowledge that any time spent
in the algorithm will increase the optimality of the point found.
For the sake of readability we skipped over some of the finer details of our
algorithm. We shall now go back and describe our implementation of them
further. We begin by looking at the PivotPointOrdering class. This class uses
a Quick Sort algorithm to order the aList array with respect to its members
levels of optimality. The reason we are using Quick Sort over other algo7
Values range from 1 to 100, 100 being the hottest.
57
rithms of its type is due to the speed advantages it affords us.
We shall determine the level of a point’s optimality using the same method
seen in our random search. We can see how this method works in the following code extract taken from PivotPointOrdering.java.
public double getFCost(double[] p){
double Tx = getTx();
double Ty = getTy();
double Hw = hub[1];
double Hp = p[1];
double Hc = Hw+Hg;
double a = p[0]-hub[0];
double P = (Tx*Hg)/Hw;
//(Tx*(Hp-Hc)+PHp+Ty*a)/Hf (Objective function)
double score = (Tx*(Hp-Hc)+(P*Hp)+(Ty*a))/Hf;
double penalty = getPenalty(p); //Get penalty for chain growth.
/**remove the sign of the number to avoid negative numbers
* being considered more optimal than those closer to 0.
*/
if(score<0){score=score*-1;}
score += penalty;
return score;
}
This example shows the bare-bone structure of the method used to calculate
a given point’s optimality. We begin by calculating the values of all variables
needed to compute the turning force imparted where the shock absorber
meets the swingarm. Notice how we conform with the naming conventions
defined in previous chapters. The next step is to carry out our calculation,
the result of which shall be stored in the variable ‘score’. To see how the
landscape we are searching looks at this point, please refer to figure 5.28 .
As mentioned in previous chapters, we shall penalise any pivot point if the
resultant design breaks a user’s permited chain growth or suspension travel
8
Please note, this figure does not take into account the bounding frame.
58
Figure 5.2: Search space with no penalty for breaking user defined chain
growth and suspension travel.
59
tolerance. Our method for doing this, as discussed by Franco Busetti[7], is
to add a large enough value to the optimality score of the offending point, in
an attempt to encourage the search to disregard it. However, before we can
add said value to the optimality score, we must first make sure that ‘score’
is devoid of any sign; we are searching for a point which is as close to zero as
possible and as such, are not interested in whether our design promotes chain
growth or chain slack. Once we have done this, we may add the penalty to
our signless score and return the result to the sort method. Our landscape
now resembles that of figure 5.3; note, any points falling outside the frame
bounds are shown as scoring 110000.
Figure 5.3: Search space with penalty, frame bounds and an accepted chain
growth of 20mm.
A further point of interest in the search is how we select a pivot point according to the algorithm’s temperature. We studied various ways of implement60
ing this feature, including examining the possibilities of producing a function
with a temperature dependent gradient, but eventually settled on a simpler
method. The method we use creates an array, equal in length to oList, and
divides it so that it contains equally distributed probability boundaries; an
example of such an array is as follows: [20,40,60,80,100]. We then multiply each boundary by the percentage temperature of the algorithm. For
instance, if our temperature were at 75 we would multiply each boundary by
0.75, resulting in an array like so: [15,30,45,60,100]. At this point we select
a random number between 1 and 100, whichever boundary this number falls
within shall determine the oList element for us to return.
We should notice that at temperature 100 there is an equal chance of us
returning any of oList’s elements. However, as we reduce the temperature
we increase the chances of the random point falling within the top boundary, representing the most optimal pivot point location in oList. When our
temperature reaches 0 we have a 100 percent chance of returning the most
optimal point.
This concludes our implementation of the classic simulated annealing algorithm. However, after much manipulation of annealing schedules and variance values we still find the occasional anomalous pivot point. To avoid this
we shall utilise random restarts [24], a technique more often reserved for the
likes of the basic hill climb. By doing this we reduce the speed at which
our search operates. However, this is a small price to pay if we find consistently optimal pivot points. Our results can be seen in figure 5.4, and clearly
demonstrate how the optimal pivot point has been targeted.
5.5.2
Consequential Search Techniques
The simulated annealing algorithm defined in the previous section shares
many similarities with a standard hill climb. Because of this, it is possible
to modify it to create other search methods without much coding overhead.
We have already discussed two techniques, random search and simulated
annealing, and in this section we shall describe two more, both of which shall
be useful when we come to test the quality of our results.
61
Figure 5.4: Sample output from our simulated annealing search.
62
Hill Climb
The first of our variations takes the form of the basic hill climb. This search
technique is similar to simulated annealing, but does not implement temperature or a reduction in variance[24]. Because it has no implementation of
temperature, it suffers from a tendency to find local minima; this may be its
downfall when presented with our search space. Also, since it cannot reduce
its variance, in the unlikely event that it were to find an optimal pivot point,
it may be forced to leave it again before the search concludes. The search
trace for this algorithm can be seen in Appendix C.2.
Variance Based Hill Climb
Our variance based search is much the same as the basic hill climb, but with
the inclusion of variance. This provides the search with the means to home
in on a pivot point far better than the simple hill climb. Also, whereas the
basic hill climb may reach an optimal pivot location and have to leave due
to the search being incomplete, our variance based search will, most likely,
have a small enough variance by this point that it shall still be edging ever
closer to its goal state. The search trace for variance based hill climbing can
be seen in appendix C.1.
5.5.3
Conclusion
In this section we looked at the interesting aspects of our projects implementation. We broke the application down into distinct modules, as outlined
in the requirements and design, allowing us to focus on the individual implementation problems they posed separately. As such, the final program is
logical and easy to maintain.
Our implementation process would have been relatively straight forward were
it not for the fact that we struggled to find an appropriate simulated annealing library. Although being able to use such libraries would have afforded us
the opportunity to test the various other search techniques on offer, we have
now gained a deeper understanding of the simulated annealing algorithm and
are able to refine its parameters to best suit our needs.
In a number of previous chapters we voice concern over the speed at which
the design preview is rendered. Initial indications of how our application
63
responds to user input are promising, we observe that the preview re-renders
in, what is essentially, real time. As such, the careful attention to detail taken
during the implementation of the design preview have not gone to waste.
The application we have produced, which can be seen in figure 5.5, appears
to be everything that we envisaged in our design. At this early stage it seems
to be finding pivot points which compare well to those of existing bicycles.
This is a promising sign, but it is important that we make sure that the
program is behaving in exactly the way it was intended. In the next chapter
we will test our application to determine whether this is the case or not.
Figure 5.5: Sample output from our simulated annealing search.
64
Chapter 6
System Testing
This chapter shall focus on ensuring our application behaves in the way we
expect it to. We shall implement various test cases to assess the accuracy of
the implementation and, in some cases, design. Any faults discovered shall
be rectified1 and discussed. Due to the slightly experimental nature of the
application, we shall center the majority of our resources on making sure the
model returns accurate pivot points.
Somerville [34] dictates that we may use both software inspection and software testing to verify our application. The former tests whether we have
conformed to all requirements laid out in the requirements chapter. The
latter tests whether our application behaves in the manner expected, for
instance with black box testing.
6.1
Software Inspection
In this section, rather than discussing how our application meets every requirement, we shall focus on the requirements that are of most interest. In
sympathy with the requirements document, and in the interests of consistency, we shall break this section down into its requirement types. This will
promote logical thinking which, in turn, may help to focus the inspection
process.
1
Assuming they are not critical faults. In this instance we shall step back into the
design phase.
65
6.1.1
Search
Functional
The majority of our functional requirements require testing to verify whether
they have been met or not. As such, we shall not include many of them in
the inspection process.
Search trace (Requirement A.1.1.3): The application we have produced allows users to turn on both types of search trace. Once on, these
search traces show, very clearly, the progress of the search they are tracking.
The colours used to plot the trace, as discussed in the design, appear to
contrast sufficiently, enabling users to differentiate them without effort.
Non-Functional
Time spent in search (Requirement A.1.2.1): It would appear that
such a requirement would need detailed black box testing to ascertain whether
it has been met. However, the requirement states that our search should take
no longer than ten seconds. Having used the system for a while now, it is
clear that none of our search methods take much longer than 1 second to
complete and that our initial fears of searches taking longer than ten seconds
were unfounded.
The computer we are running the application on is of a medium specification, it is not expected that a slower computer would have any more difficulty
completing the search in the allotted time frame either. As such, we need
not concern ourselves with testing this requirement in any more detail.
User
Multiple search methods (Requirement A.1.3.1): Our application
provides the user with a choice of four search methods: simulated annealing, variance based hill climb, hill climb and random search. Each of these
methods provides a different level of accuracy, as we will show in our testing.
To demonstrate this to the user, we have added a ‘recommended’ tag to the
simulated annealing algorithm.
66
System
Minimal memory usage (Requirement A.1.4.1): Each of the search
techniques used is iterative and requires no knowledge of its previous choices,
bar its immediate predecessor. As such, we have no need to store search
histories (other than in our trace array). Because of this, memory usage
remains low.
A possible consideration at the time of implementation was, as a result of
its recursive nature, whether PivotPointOrdering’s Quick Sort method may
adversely affect memory usage. However, research into the algorithm has
shown that the function is capable of ordering a much larger array than that
of our application [21]. In fact, said research states that for arrays of size 25
or less, Quick Sort is not always the most efficient method for ordering an
array, due to its coding overhead; memory usage is not even a consideration
at this point. Our array is 30 elements long and, as such, only just escapes
this threshold. Nevertheless, with Quick Sort implemented, we are afforded
the freedom to try as many different search parameters as we like, without
consideration for speed.
6.1.2
Model
Functional
Precision of model (Requirement A.2.1.1): Although it is difficult
to tell how precise the model we create is in the application itself, a DXF
export provides us with access to the standard CAD tools for performing
such analysis. An example of a design, showing relevant dimensions, can be
seen in appendix figure D.4. It demonstrates how the model produced by
our application corresponds to the parameters used to create it. It should
be noted that, in this image, the fork length is marked as the effective fork
length and not the actual fork length as specified by the user.
Non-Functional
Model redraw delay (Requirement A.2.2.1): Although this requirement specifically states that our model must redraw itself within 0.1 of a
second, it appears to be the case that we cannot meet this requirement with
the final program. Actual response times are more like 0.2 to 0.3 of a second
67
which, although not in-keeping with HCI guidelines or our requirements, is
perfectly acceptable in practice. This is especially so when coupled with the
fact that users may manipulate parameters directly; removing the need for
unnecessary redrawing of geometry.
User
Removal of pivot point for geometry updates (Requirement A.2.3.2):
A fairly basic requirement that states that whenever a user updates a parameter value in the interface, it must remove any previously found pivot points
so as to avoid confusion when exporting the model. Our application does
this, as well as informing the DXF export facility that it must, once again,
prompt the user to search for a new pivot point before exporting a model.
System
CAD export (Requirement A.2.4.1): As discussed previously, our application allows users to export their designs as DXF files. The DXF file is
a commonly recognised file format which can be opened by the majority of
CAD applications, as well as some illustration programs. We shall discuss
this requirement in more detail in the following section.
6.1.3
Peripheral
Functional
Plugable modules (Requirement A.3.1.1): As discussed in previous
chapters, we require that our application be made up of distinct plugable
objects. This is especially important in the case of the search modules.
Inspection of the code reveals that the designPreview module has only a
small link to its child search modules. As such, we can conclude that the
application we have produced meets this requirement.
Interface behaviour (Requirements A.3.1.2-8): The requirements in
this section are fairly generic interface requirements. Our application implements all input/ output functionality highlighted in the requirements document. In addition, it handles errors accordingly, a contributing factor to
its stability. We also provide all the necessary keyboard shortcuts for expert
68
users. Finally, our parametric preview displays all necessary information, not
displayed in the model, to the user in a clear and easy to understand manner.
Non-Functional
We have already discussed how our application responds to user input in
appropriate time frames, as such, we shall not go into any more detail for
this section.
User
Chain growth and rear wheel travel (Requirements A.3.3.3-4): Our
requirements state that users should be able to set their preferred amount
of rear wheel travel and chain growth. We have implemented JSliders, discussed in the design stage of this project, as the method for setting these
parameters. In practice these interface components provide satisfactory levels of interaction and enable users to set accurate levels of chain growth and
suspension travel.
System
Apple OS X look and feel (Requirement A.3.4.2): We have made
sure that the application created implements all of OS X’s refinements. To
see an example of this, please refer back to figure 5.5. On top of the native
look and feel adjustments, we have also packaged the Jar executable so that
it resembles a native OS X application.
Applet conversion (Requirement A.3.4.3): Unfortunately, we have
not had time to implement this feature. Due to the way that our application uses input/ output, the conversion process is not as simple as we had
first hoped. Nevertheless, this is a useful feature to have in an application of
this sort. As such, we shall consider implementing it at a later date.
6.2
Software Testing
In this section we will analyse how our application performs in various test
cases in an attempt to verify its quality. We shall begin by testing our
main search technique, simulated annealing, by using it to search simple
69
landscapes. From here, we will move on to comparing the quality of the
different search techniques by looking at the standard of pivot point which
they return. We shall also consider how our applications output compares to
that of bicycles being manufactured today.
6.2.1
Simulated Annealing
The purpose of the simulated annealing algorithm is to find global maxima/
minima where other techniques such as hill climbing may fail. Although our
application appears to be returning feasible pivot points, the landscape that
we are searching is unknown to us. It follows therefore, that we may be finding local minima. It is for this reason that we must verify the quality of the
algorithm implemented on simpler landscapes that we are already familiar
with.
To perform such a test we must first adapt our simulated annealing algorithm to operate in a 2D environment. It is important to take special care
when doing this, so as not to detract from the integrity of the main algorithm; doing so would void any test cases. Because of the versatility of the
simulated annealing function, we need simply to set the second dimension
in PivotPoints altPivot class to 0 and to change the random point generator
so that it finds points within the scale of the search. These changes, along
with the obvious change to the objective function, will allow us to carry out
testing on various functions in a 2D environment.
Table 6.1 shows the results of a number of tests that have been carried out
using various test functions, the landscapes of which can be seen in Appendix
D.1. From these results we see a reasonable level of accuracy, considering we
have not set the annealing algorithm up specifically for this task. The first
function we look at, sinx + 1 (figure D.1), should see our algorithm find a y
value of zero2 . Our results reflect this with a mean y value of 0.05, we also
manage to anneal to this point relatively consistently. However, our results
do not reflect this as well as they should, the consequence of a single anomalous search.
The next function we look at is sinx ∗ cos(3 ∗ x) + 1 (figure D.2). The results
we expect to see from this function are slightly different from that of the
previous in that we should be expecting to anneal to a value of around 0.12.
2
The lowest point in a sine graph when considering the y axis.
70
However, this function’s landscape contains local minima and, as such, this
will be a good test of our simulated annealing algorithm. Again, our results
show fairly good accuracy, considering the lack of setup, with a mean of 0.13,
only 0.01 from the solution. However, this time they are not blighted by any
extreme anomalous results, giving us the opportunity to see how consistent
the search can be. Our results show there to be a range of merely 0.09, a
strong test that demonstrates how we can avoid local minima with our algorithm.
The final function we will look at is cosx ∗ sin(3 ∗ x) + 1 (figure D.3). This is
basically the reverse of the previous function. We chose it to check if the annealing process was actually jumping away from local minima, or whether it
was finding global minima simply because of the starting point of the search.
Results, on the whole, mimic that of the previous test with one exception.
Unfortunately, again we find an extreme anomalous result which has distorted the results. The reason for such a result is unknown, as it does not
represent a local minima, it is possible that the search was trying to escape
a local minima and was limited by the little variance left in the algorithm.
Overall, the results look promising. They do however suggest that we should
take care when manipulating our search’s annealing schedules and start
states. We can conclude from this study that the algorithm we have produced is effective.
6.2.2
Alternative Search Techniques
In a similar fashion to the test mentioned previously, we shall run our four
search algorithms over the objective functions landscape (Table 6.2). For the
sake of the test we shall set the models accepted chain growth and suspension
travel to 0, the worst case scenario for many search methods (see figure D.5).
If we were to increase the permitted chain growth, we would see the nonpenalised search space open up (see figure D.6), making it much more likely
that a weak search method may happen upon a solution.
We begin by looking at the results of the simulated annealing search. It is
immediately obvious that the search finds pivot points of the same quality3
extremely consistently. So consistently in fact, that their range is 0. We also
see that the points being found are scoring very close to 0, our optimal pivot
3
Not necessarily the same point.
71
p
1
2
3
4
5
6
7
8
9
10
Mean
Range
sinx + 1
sinx ∗ cos(3 ∗ x) + 1 cosx ∗ sin(3 ∗ x) + 1
0.106003336
0.126919629
1.0566481
0.008221147
0.126919629
0.12861695
0.231745339
0.120053215
0.125406979
0.00431581
0.120053215
0.121198274
9.79E-06
0.130499093
0.129271425
2.45E-04
0.140468696
0.179468126
0.001184775
0.126919629
0.12861695
0.003530827
0.122386606
0.120194278
0.041075725
0.213805446
0.119919001
0.079973962
0.120604927
0.179468126
0.047630554
0.134863009
0.228880821
2.32E-01
0.09375223
0.936729099
Table 6.1: Test results from trials of pivot point optimality. Please note, not
all of these functions contain solutions equal to 0.
location score. This all bodes well as verification of the application’s quality.
The next test was run on the variance based hill climb. Results show similar
scores to simulated annealing, if slightly less optimal (possibly a result of the
method chosen for reducing the variance). The mean score is 1.46, around
0.6 higher than that of simulated annealing and the range is 0.74, again,
perfectly respectable.
We now move on to the hill climb. This search is of great interest to us
because it finds pivot point locations which have been penalised for promoting
chain growth above that permitted by the user. In terms of the landscape,
we can deduce that a local minima has been found, exactly the reason we
chose not to use this as the application’s main search method. Were we to
increase the sample size of this test we would see that the technique finds
occasional non-penalised pivot points, and when it does, they tend to be a
of a quality not dissimilar to the previous two methods.
In spite of the poor quality of results that the hill climb provides us, it is
fairly consistent in its ability to find them, with a range of only 4.11. As
discussed in previous chapters, it is for this reason that random restart hill
climbing was invented[24]. Be that as it may, a random restart hill climb
would still stand very little chance of finding results as good as simulated
72
annealing or its variance based counterpart without tens of restarts. The
result of so many restarts would be a comparatively lengthy search.
Finally, we look at random search. As expected, the output from this search
is hugely inconsistent, displaying a range of 61.57. It does however appear
to find more optimal pivot points than the basic hill climb, with none out
of the ten trials being penalised for excessive chain growth. Nonetheless,
random search’s inconsistency prevents it from being a viable technique for
this domain.
Overall, the results of this test have been very interesting. Beforehand we
had no idea that our search space contained local minima, the tests have
shown this to be the case. We are moderately surprised that the variance
based hill climb does not fall into the same trap as the basic hill climb. We
can only assume that it has a large enough variance, at a given time in its
convergence, to skip over the local minima.
The tests show clearly that simulated annealing finds pivot points of the
highest quality, closely followed by the variance based hill climb. The next
best technique is hard to place, due to the poor quality of results. Be that
as it may, if a user specifies that a pivot point shall not induce chain growth
over a certain level then it is important that our search reflects this, even if
the hill climb may be producing more consistent points.
p
Simulated Annealing
1
0.9120234
2
0.9120234
3
0.9120234
4
0.9120234
5
0.9120234
6
0.9120234
7
0.9120234
8
0.9120234
9
0.9120234
10
0.9120234
Mean
0.9120234
Range
0
VB Hill Climb
1.3077675
2.0513272
1.3077675
1.3077675
1.3077675
1.3077675
1.3077675
2.0513272
1.3077675
1.3077675
1.4564794
0.7435597
Hill Climb
Random Search
9976.4084754
12.2151538
9980.3069710
19.0691527
9976.1958964
26.1672233
9977.1459709
35.6159092
9976.1958964
21.4825519
9977.4516872
13.5221522
9977.4516872
66.9909572
9976.1958964
10.6687477
9976.4084754
12.6073606
9976.1958964
5.4193565
9977.1962850
22.37591651
4.1110750
61.5716007
Table 6.2: Test results from trials of pivot point optimality.
73
6.2.3
Objective Function
In this section we shall look at how our results compare to the bikes on sale
today, in an effort to verify our objective function. Unfortunately there are
no set guidelines for the placement of bicycle pivot points. As such, we are
forced to to test the function in an unconventional manner.
Although it may appear to be the best strategy for analysis, we shall not collate a list of single pivot mountain bikes along with our applications approximation to their pivot points. This is due to the ambiguity surrounding the
geometry published by bicycle manufacturers. Instead, our analytical procedure shall be to study various existing bicycle designs and pick out those
which display pivot points mimicking those of our application. We shall pay
special attention to the relationship between hub and bottom bracket heights,
as these are the parameters most likely to affect pivot point height.
Figure 6.1: The Cannondale Prophet with DXF approximation overlay.
Our analysis has shown that the pivot points our application returns tend
to be, on average, slightly lower than those of existing designs. This is
most likely due to the objective function not taking into account enough sag.
74
However, we observe a strong likeness in the program’s output to that of the
Cannondale Prophet. Figure 6.1 shows a DXF file, created using as many
of the geometric parameters that Cannondale supply on their website [8] as
possible4 , superimposed onto an image of the bike itself.
Cannondale are a well established company who have been designing bikes
for over 35 years. The Prophet in particular receives various accolades commending its cross country prowess, an example of which can be seen at
feedthehabit[13].
We conclude that the pivot points found by our application are realistic,
although it is difficult to quantify whether the locations it returns are any
better than existing manufacturers’ designs. We can however draw parallels
with designs created using our program and those created by well respected
mountain bike manufacturers. As such, we may consider the objective function to be of a satisfactory standard.
6.2.4
Conclusion
In this section we have discussed the software inspection and testing of the
most interesting areas of our application. We have studied whether our final
product meets its requirements and verified the accuracy of the main search
algorithm. On top of this, we have run tests to determine the quality of each
search type and the pivot points they find. We have also attempted to gauge
the accuracy of the objective function.
Software inspection has shown that we have met the vast majority of requirements, with only the occasional, low priority, requirement not being
implemented. As such, the final application displays all the significant functionality expected of it, in the manner prescribed. This section has also
brought to light some work which could be carried out in the future, an example of which being the conversion of our application to run in an Applet.
Moving on to look at system testing, we have been able to draw a significant
amount of valuable information. The tests show our simulated annealing algorithm to be accurate for searching simple functions to a high degree. This,
in turn, demonstrates there to be no issues regarding the search function’s
4
Any parameters not supplied, or that don’t appear to match the image, will be approximated. It should be noted that, in this instance, no parameters affecting the positioning
of the pivot point require estimation.
75
quality. What these tests do highlight however, and as we discussed earlier,
is the importance of taking time to set the annealing process up properly.
In later tests we looked at how simulated annealing compares to alternative search techniques. As expected, simulated annealing was proven to be
the most accurate and consistent, closely followed by the variance based hill
climb. More interesting however, was the basic hill climb’s inability to escape from a local minima, something that we had not picked up from our
landscape plots (Appendix D5 and 6).
Finally, we looked at the objective function. It was difficult to determine
how best to test this because, as mentioned before, there are no set rules or
guidelines for pivot point placement. The only way in which we could test the
function, other than to build and review a bike based on our model, was to
compare the pivot points we discovered with those of bicycles already being
manufactured. The search uncovered the Cannondale Prophet, considered to
be particularly good for riding uphill5 , to be the bike utilising pivot points
most like those found by our application.
Overall, this section has shown our application to fulfil its design goals. We
can see that each of the modules piece together to form a solid, reliable application that provides engineers with a sound basis from which to design a
single pivot full suspension bicycle.
5
An area of mountain biking where pedal bob is at its most pronounced.
76
Chapter 7
Conclusion
The aim of this project was to create an application that, given a set of geometry, would automatically find a single pivot suspension bike’s optimal pivot
point. Preliminary research uncovered the limiting factors that affect pivot
point positioning for such bikes, as well as the methods currently applied by
engineers in their design process.
We then moved on to review the existing work carried out in the various
domains our application will cover. At this point we refined the knowledge
gained in the introduction to get an idea of how the project would progress.
An important part of this process was in determining the objective function,
the basis for the whole project. Our approach to solving this problem was to
study high level sources, such as forums and in some cases academic papers,
to gather a set of requirements with which to discuss with Dr J.Darling.
These discussions led to the creation of equation 2.5, our objective function.
Also in the literature review is a discussion of various search techniques. This
discussion looks at a wide range of techniques, some of which are not applicable for solving the problem in hand, and explains the merits and difficulties
they may face in relation to our application domain. We paid special attention to simulated annealing, as it became apparent that it may be of use to
us further on in the development process.
Another decision made in this chapter is to use the DXF file as our method
of export. This research turned out to be crucial in the usability of our application. Without it, users would have no way to export their drawings into
CAD programs and, as such, no way of continuing the design process.
77
In the next chapter we looked at what is required of the application. The process began with a study of the best places from which to draw requirements
and moved on look at those of most interest to us. We found this section
especially useful as a reference during our design phase, as it encapsulates all
the essential functionality of the application.
The design chapter is where we broke the application down into the distinct
components that we call modules. In doing this we afforded ourselves the
freedom to focus on the separate challenges which they each represent. Following this, we defined how these modules interact and what dependencies
they possess.
Having defined our modules, we moved on to explain their roles in further
detail, paying special attention to HCI guidelines wherever they may be relevant. In addition to this, we explained a simple method for testing the
interface that we call random search. Our explanation of this search utilised
pseudo-code to clearly define how the search executes.
Having designed the application in as much detail as possible, we were able
to move on to implementation. The project’s modularisation allowed us to
break down our discussion of implementation into its component parts. From
here we discussed the more interesting challenges posed to us in the application’s development, one of which being the decision to implement our own
simulated annealing module. This decision required that we look closely at
our literature review and, where necessary, carry out further research to ensure our implementation’s quality.
Having made the decision to write our own simulated annealing function, it
became important that we tested it accordingly. As such, we created a series
of tests on simple landscapes, designed to verify the accuracy at which points
were found. The results showed good consistency when finding points, considering we had not set the algorithm up specifically for the task in hand, with
only the occasional anomalous points skewing the data’s mean and range.
In addition to just testing the simulated annealing algorithm on its own, we
looked at the optimality of points returned from each of the search techniques
on the objective functions landscape. The results backed up our research into
the algorithms, showing simulated annealing to be the best. However, the
tests also uncovered a local minima in the objective function which we were
previously unaware of.
78
Finally, we analysed various existing mountain bike designs in an attempt
to verify our objective function, the results we received were highly satisfactory. We identified parallels between the pivot point locations found by our
application and those set on the Cannondale Prophet. The Prophet receives
very good reviews regarding its climbing efficiency and as such, we can conclude that our objective function provides a landscape on which to search,
containing global minima representing pivot points of a high quality.
Overall, the program we have created provides users with a simple interface
for finding optimal pivot points on single pivot mountain bikes. This gives
engineers the perfect basis for further manipulation of our model in another
application (see Figure 7.1). We have proven this to be the case by testing
the application’s individual components in a suitable manner and taking care
to research the appropriate fields for our design. We have achieved all this
in the allotted time frame with the aid of a Gantt chart and careful project
management.
7.1
Future Development
As we have already discussed, our application serves as a useful tool for
the preliminary stages in bicycle design. However, increasingly complicated
suspension designs are becoming far more popular amongst riders, due to
engineers’ greater understanding of how the rear wheel’s arc of movement
affects efficiency.
Earlier in the project we state that, time permitting, we should implement
support for multi-link suspension designs. However, it became apparent that
this would not be feasible given the time we had to implement the application. Therefore, this would be an obvious place to start when continuing the
development of our program.
Also mentioned earlier in the project was our objective function’s low estimation of sag. It would be nice if a user were able to set their desired level of
sag somewhere on the interface, allowing them to tune the objective function
even further than they can already.
Following on from the previous idea, it is possible that our application be
considered limiting by some advanced users. As such, it may be of use to
provide these users with the tools they need to specify their own objective
79
Figure 7.1: A 3D bicycle design created in SolidEdge from our DXF file
(shown).
80
functions. This would allow them to tailor the search space to their own
needs1 , possibly making the application more suited to a wider variety of
bicycle suspension applications.
1
It is conceivable that an engineer may wish to optimise the location of a pivot point
for something other than pedal bob and chain growth.
81
Bibliography
[1] Alex.
Geometry 101: A rough guide to angles, lengths and
stuff... 2005. http://solitudecycles.blogspot.com/2005/06/geometry101-rough-guide-to-angles.html.
[2] Ambysoft. User interface design tips, techniques, and principles. 2006.
http://www.ambysoft.com/essays/userInterfaceDesign.html.
[3] AutoCAD.
Dxf
reference.
http://www.autodesk.com/techpubs/autocad/acadr14/dxf/.
2006.
[4] Autodesk.com. General dxf file structure. 2005.
[5] Ethos Bicycles. Suspension. 2005.
[6] Borland.
Awt
vs
swing.
http://bdn.borland.com/article/0,1410,26970,00.html.
2006.
[7] Franco Busetti. Simulated annealing overview. 2005. http://www.rosehulman.edu/Class/ma/rader/MA477/F01/Papers/simanneal.pdf.
[8] cannondale.com. Cannondale geometry- prophet geometry chart. 2006.
http://www.cannondale.com/bikes/05/geo-6.html.
[9] Web 3D consortium. Cad distillation format. 2005.
[10] Lawrence Davis. Genetic Algorithms and Simulated Annealing. Pitman,
London, 1987.
[11] dirtworld.com. Cannondale introduces new fs design for 2000. 2000.
http://www.dirtworld.com/productreviews/ReviewStory.asp?id=193.
82
[12] David
Ekholm.
Riverlayout.
http://www.datadosen.se/riverlayout/.
2006.
[13] feedthehabit.com. 2005 cannondale prophet 1000 review.
http://www.feedthehabit.com/.
2005.
[14] The Bicycle Forest.
Full suspension
http://www.bikeforest.com/CAD/fsCAD.html.
2005.
bikecad.
[15] Steve from JH. Turner/horst vs. turner/kona, long and geeky. 2005.
http://forums.mtbr.com/showthread.php?t=132386.
[16] Jonathan Hardwick.
Optimizing java for
http://www.cs.cmu.edu/ jch/java/speed.html.
[17] IBM.
Design
basics.
2006.
3.ibm.com/ibm/easy/eou ext.nsf/publish/6.
speed.
1997.
http://www-
[18] Dejan Jelovic. Why java will always be slower than c++. 2005.
[19] Deborah J.Mayhew. Principles and Guidelines in Software User Interface Design. Prentice-Hall, Inc., 1991.
[20] Kevin
Kluge.
The
experts
talk:
Thirteen
great
ways
to
increase
java
performance.
1997.
http://java.sun.com/developer/technicalArticles/Programming/Performance/.
[21] Michael Lamont. Quick sort.
onml/algor/sort/quick.html.
2006.
http://linux.wku.edu/ lam-
[22] lemkesoft. Cadintosh. 2006. http://www.lemkesoft.de/en/cadintosh.htm.
[23] Brian T. Luke.
Simulated annealing cooling schedules.
http://fconyx.ncifcrf.gov/ lukeb/simanf1.html.
2006.
[24] Sean Luke.
Hill-climbing and simulated annealing.
http://cs.gmu.edu/ sean/cs687/hillclimbing.txt.
2006.
[25] Ravi Mohan. Aima library. 2006. http://aima.cs.berkeley.edu/javaoverview.html.
[26] Jakob Nielsen. Usability Engineering. AP Professional, 1994.
83
[27] Andr
Platzer.
Orbital
http://www.functologic.com/orbital/.
library.
2006.
[28] J. et al. Preece. Human Computer Interaction. Addison-Wesley Publishing Company, Inc., 1994.
[29] R.AHebbert. The humble bicycle. Mech E, 1974.
[30] Russel and Norvig. Artificial Intelligence - A modern Approach, pages
73–81. Prentice Hall International Publishers, 1995.
[31] Geman S. and Geman D. Stochastic relaxation-ieee transactions on
pattern analysis and machine intelligence. 1984. PAMI-6, 721-741.
[32] B Shneiderman. Designing the User Interface, second edition. AddisonWesley Publishing Company, Inc., 1992.
[33] Racooz Software.
Linkage bike suspension simulation.
http://www.bikechecker.com/index.php?home.
2005.
[34] Ian Somerville. Software Engineering - 6th Edition. Addison Wesley,
2001.
[35] Boris Mayer St-Onge.
Pro/engineer tutorial homepage.
1998.
http://wwwrobot.gmc.ulaval.ca/interne/documentation/proeng/.
[36] Mountain Biking UK. Full suspension bikes- how to set them up. 2005.
[37] Unknown.
Cycling
kinematics.
http://www.bsn.com/Cycling/articles/cycling kinematics.html.
2005.
[38] David Weldon.
Suspension design dissertation.
2005.
http://www.bikemagic.com/forum/forummessages/mps/dt/4/UTN/70200/V/6/SP/.
[39] Walter Zorn. Rear wheel suspension induced pedal kickback. 2002.
http://www.kreuzotter.de/english/eschwinge.htm.
84
Appendix A
Requirements
A.1
A.1.1
Search
Functional
1. The system must find the optimal pivot point according to the algorithm we define. The algorithm should be capable of consistently
finding the same point over multiple runs.
2. The system should be accurate to within 5mm when finding a pivot
point. This can be measured by finding a pivot point that falls outside the bounds of the frame and seeing how close the point is to the
boundary.
3. It is preferred that the search algorithms path may be traced to the
screen for users to analyse. Where applicable this trace should show
both considered paths and chosen paths.
A.1.2
Non-Functional
1. The system must find the optimal pivot point in under 10 seconds (in
accordance with accepted HCI guidelines).
A.1.3
User
1. It is preferred that the system allow the user to select from a number
of different search methods
85
A.1.4
System
1. It is preferred that the search algorithm use minimal memory. However,
speed of search should take precedence over said memory usage.
A.2
A.2.1
Model
Functional
1. The system must use a model whose dimensions exactly (be they to
scale or not) mimic that of a real world bicycle.
2. The system must provide bounds on parameters to prevent the creation
of bicycles which may not be feasible in the real world. Said bounds
shall not hinder the user in any way if they wish to create bicycles that
do not adhere to preconceived ideas of what makes good mountain
bike geometry; that is, users should be given creative freedom when
designing a frame but should not be allowed to create a bicycle that is
not physically realisable.
A.2.2
Non-Functional
1. It is preferred that the model redraw itself quickly (within HCI guidelines of around 0.1 seconds) when a user alters a dimension.
A.2.3
User
1. The system must offer users an accurate view of the model. This model
should update in realtime when a user changes a parameter.
2. The system must remove any pivot point that it may have found previously from the model if a user updates any design parameters.
A.2.4
System
1. It is preferred that if the user is to save the model in a CAD format,
that the CAD format be recognisable to as wide a range of applications
as possible.
86
A.3
Peripheral
A.3.1
Functional
1. The system must be designed in such a way that allows for the pluging in of different search classes. By designing in such a way we give
ourselves the freedom to alter/improve our system at a later date. We
also allow ourselves the convenience of being able to test our search
algorithms against those of others.
2. The system must allow users to save new projects.
3. The system must allow users to open existing projects.
4. The system must handle with all exceptions in appropriate manner.
5. The system must not crash.
6. The system should provide support for common shortcuts such as Save
and Open.
7. The system should use the users platforms default look and feel to
render user interface components.
8. The system should provide the user with information that is relevant
to the bikes design. For example; effective top tube length, wheelbase
length and effective chainstay length.
9. It is preferred that the method of export for projects is via CAD a file.
A.3.2
Non-Functional
1. The system should have a user interface which responds quickly to user
input. All geometry updates should take place in around 0.1 second
and searching for an optimal pivot point should take no longer than
10 seconds. If it appears that searching will take longer than 1 second
then a notification or progress bar should be displayed.
87
A.3.3
User
1. The system must provide the user with an easy to use interface that
displays all relevant information in a clear manner.
2. The system must allow users to specify a bike’s head tube angle, seat
tube angle, seat tube length, top tube length, chainstay length, bottom
bracket height, wheel diameter, fork length, fork rake, head tube length,
seat tube extension above top tube, head tube extension above top tube
and head tube extension below down tube.
3. The system must allow users set their accepted level of chain growth.
4. The system must allow users to set their desired amount of rear wheel
travel.
A.3.4
System
1. The system must run on any computer running the Java Runtime Environment.
2. It is preferred that the application implement Apple OS X look and
feel patches to further improve the appearance and usability of the
application whilst running in the Apple environment.
3. It is preferred that the application also be bundled as an Applet to
allow for the use of our application in a web browser.
88
Appendix B
Design
B.1
Prototypes
Figure B.1: View one of our interface.
89
Figure B.2: View two of our interface.
90
Appendix C
Implementation
C.1
Search Traces
Figure C.1: Sample output from a variance based hill climb search.
91
Figure C.2: Sample output from a basic hill climb search.
Figure C.3: Sample output from a random search.
92
Appendix D
Testing
D.1
Graphs
Figure D.1: sinx + 1 test graph.
93
Figure D.2: sinx ∗ cos(x ∗ 3) + 1 test graph.
94
Figure D.3: cosx ∗ sin(x ∗ 3) + 1 test graph.
95
D.2
Output
Figure D.4: DXF analysed in CAD package.
96
Figure D.5: A search landscape with no permitted chain growth or suspension
travel.
97
Figure D.6: A search landscape with 50mm of permitted chain growth.
98
Appendix E
Code
E.1
Main.java
99
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:
/*
* Main.java
*
* Created on February 24, 2006, 9:32 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import javax.swing.*;
/**
*
* @author dave
*/
public class Main {
/** Creates a new instance of Main */
public Main() {
}
/**Creates our Jframe.
*/
public static void createGUI() {
JFrame frame = new mainFrame();
frame.pack();
frame.setVisible(true);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createGUI();
}
});
}
}
100
E.2
mainFrame.java
101
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:
/*
* mainFrame.java
*
* Created on February 24, 2006, 9:34 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import javax.swing.*;
import javax.swing.event.*;
import se.datadosen.component.RiverLayout;
/**
*
* @author dave
*
* This is the JFrame from which we implement our interface.
* Our interface includes calls to methods within the following classes:
* - designPreview.java
* - parametricPreview.java
* - IOFile.java
*
*/
public class mainFrame extends JFrame {
/**Creates a new instance of mainFrame
*It is from here that the majority of our high level interface
*implementation takes place. For instance, we set the title,
*menus and call the methods to setup the JPanels in our interface.
*We also determine the size of the JFrame on the screen
*and iniialise the array containing our interface values.
*/
public mainFrame() {
//Make sure we have nice window decorations.
this.setDefaultLookAndFeelDecorated(true);
initSpinMod();//initialise our geometry on left panel
//Create and set up the window.
this.setTitle("PIVOT > Mountain Bike Rear Suspension Design Optimisation");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().setLayout(new BorderLayout());
screenSize = getEnvironment();
//crate our menu bar
JMenuBar mainMenu = new JMenuBar();
mainMenu.add(createFileMenu());
mainMenu.add(createSearchMenu());
createAccessibilityPanel();
createDesignPanel();
createControlPanel();
createStatusPanel();
/**Add all panels to mainWindow*/
setJMenuBar(mainMenu);
102
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:
this.getContentPane().add(access, BorderLayout.NORTH);
this.getContentPane().add(control, BorderLayout.WEST);
this.getContentPane().add(results, BorderLayout.CENTER);
this.getContentPane().add(status, BorderLayout.SOUTH);
}
/**Get the available screen space and set the Frame position
*to the top left of the screen.
*/
public Dimension getEnvironment(){
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
GraphicsConfiguration config = this.getContentPane().getGraphicsConfiguration();
Insets insets = kit.getScreenInsets(config);
screenSize.width -= (insets.left + insets.right);
screenSize.height -= (insets.top + insets.bottom);
this.setLocation(insets.left, insets.top);
return screenSize;
}
/**Create the file menu for the menu bar
*This menu includes:
*-File menu
*-Save
*-Save As
*-Open
*-Export to dxf
*/
private JMenu createFileMenu(){
JMenu menu = new JMenu("File");
JMenuItem newFile = new JMenuItem("New");
newFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
newFile.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
resetSpinMod();
}
});
JMenuItem saveFile = new JMenuItem("Save");
saveFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
saveFile.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
try {
saveFile();
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
JMenuItem saveAsFile = new JMenuItem("Save As");
saveAsFile.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
try {
saveAsFile();
} catch (NullPointerException ex) {
ex.printStackTrace();
103
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
JMenuItem openFile = new JMenuItem("Open");
openFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
openFile.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
openFile();
}
});
JMenuItem export = new JMenuItem("Export project as \".dxf\"");
export.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E,Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
export.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
try{
if(searched){
exportDXF(desPanel.getPoint(), desPanel.getMajorPoints(), desPanel.getWheelRadius());
}else{
searchForPointError();
}
}catch(NullPointerException n){
searchForPointError();
}
}
});
menu.add(newFile);
menu.add(saveFile);
menu.add(saveAsFile);
menu.add(openFile);
menu.addSeparator();
menu.add(export);
return menu;
}
/**Create our search menu.
*This menu includes:
*-Random Search
*-Variance Based Hill Climb
*-Hill Climb
*-Simulated Annealing
*-Trace Whole Search
*-Trace Optimal Search
*/
private JMenu createSearchMenu() {
JMenu menu = new JMenu("Search");
ButtonGroup group = new ButtonGroup();
JRadioButtonMenuItem randomSearch = new JRadioButtonMenuItem("Random Search");
randomSearch.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
desPanel.setSearchType(1);
}
});
104
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
JRadioButtonMenuItem varianceBased = new JRadioButtonMenuItem("Variance Based Hill Climb");
varianceBased.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
desPanel.setSearchType(2);
}
});
JRadioButtonMenuItem hillClimb = new JRadioButtonMenuItem("Hill Climb");
hillClimb.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
desPanel.setSearchType(3);
}
});
JRadioButtonMenuItem simAnneal = new JRadioButtonMenuItem("Simulated Annealing (Recommended)");
simAnneal.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
desPanel.setSearchType(4);
}
});
simAnneal.setSelected(true);
group.add(randomSearch);
group.add(hillClimb);
group.add(varianceBased);
group.add(simAnneal);
JCheckBoxMenuItem printSearch = new JCheckBoxMenuItem("Trace Whole Search");
printSearch.addItemListener(
new ItemListener(){
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED){
desPanel.setShowSearch(true);
}else{
desPanel.setShowSearch(false);
}
}
});
JCheckBoxMenuItem optimSearch = new JCheckBoxMenuItem("Trace Optimal Search");
optimSearch.addItemListener(
new ItemListener(){
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED){
desPanel.setShowOptimalSearch(true);
}else{
desPanel.setShowOptimalSearch(false);
}
}
});
menu.add(randomSearch);
menu.add(hillClimb);
menu.add(varianceBased);
menu.add(simAnneal);
menu.addSeparator();
menu.add(printSearch);
menu.add(optimSearch);
return menu;
}
105
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
/**Populate our Accessibility panel. This is the panel
*that sits in the north of our frame, below the menu.
*/
private void createAccessibilityPanel() {
//Create Accessibility Panel
FlowLayout layout = new FlowLayout(FlowLayout.LEFT);
access = new JPanel(layout);
access.setPreferredSize(new Dimension(screenSize.width,access_height));
//Add new button
ImageIcon newIcon = new ImageIcon("images/notepad.png");
JButton newDoc = new JButton("New",newIcon);
newDoc.setToolTipText("Click here to create a new design.");
newDoc.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
resetSpinMod();
}
});
//Add save button
ImageIcon saveIcon = new ImageIcon("images/floppy.png");
JButton save = new JButton("Save",saveIcon);
save.setToolTipText("Click here to save your current design.");
save.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
saveFile();
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
//Add file button
ImageIcon fileIcon = new ImageIcon("images/1f.png");
JButton file = new JButton("Open",fileIcon);
file.setToolTipText("Click here to open an existing design.");
file.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
openFile();
}
});
ImageIcon runIcon = new ImageIcon("images/run.png");
JButton run = new JButton("Search ",runIcon);
run.setToolTipText("Click here to search for the optimal pivot point.");
run.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
desPanel.search();
paramPanel.updatePivot(desPanel.getPoint());
searched = true;
}
});
access.add(newDoc);
access.add(save);
access.add(file);
access.add(new Box.Filler(new Dimension(25, 25),
new Dimension(32, 32),
106
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
new Dimension(32, 32)));
access.add(run);
}
/** Method for populating our suspension panel. This panel
*comprises two JSliders.
*/
private void popSusp() {
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new GridLayout(1,2,0,0));
JPanel sliderPanel = new JPanel();
sliderPanel.setLayout(new GridLayout(1,2,0,0));
labelPanel.add(new JLabel("<html><p align='center'><br>" +
"<b>Set Travel</b></p></html>",
SwingConstants.CENTER));
labelPanel.add(new JLabel("<html><p align='center'><br>" +
"<b>Accepted<br>Chain Growth</b><br></p></html>",
SwingConstants.CENTER));
int maxTravel = 350;
int travelIncrement = 50;
int maxGrowth = 50;
int growthIncrement = 5;
travel = new JSlider(JSlider.VERTICAL, 0, 350, 0);
travel.setMajorTickSpacing(50); // sets numbers for biggest tick marks
travel.setMinorTickSpacing(10); // smaller tick marks
travel.setPaintTicks(true); // display the ticks
travel.setPaintLabels(true);
travel.addChangeListener(
new ChangeListener(){ //anonymous inner listener class
public void stateChanged(ChangeEvent e) {
int val = travel.getValue();
desPanel.updateTravel(val); //update our desPanel with new val
spinMod[spinMod.length-2][0] = val;//update our array of parameters
//remind our app that it must reset its pp dues to changes
searched = false;
}
});
//Add the labels to our Slider
Hashtable travelTable = new Hashtable();
for(int i=0; i<=maxTravel; i+=travelIncrement){
travelTable.put(new Integer(i),new JLabel(i+"mm"));
}
travel.setLabelTable(travelTable);
growth = new JSlider(JSlider.VERTICAL, 0, maxGrowth, 0);
growth.setMajorTickSpacing(growthIncrement); // sets numbers for biggest tick marks
growth.setMinorTickSpacing(1); // smaller tick marks
growth.setPaintTicks(true); // display the ticks
growth.setPaintLabels(true);
growth.addChangeListener(
new ChangeListener(){ //anonymous inner listener class
public void stateChanged(ChangeEvent e) {
int val = growth.getValue();
desPanel.updateGrowth(val);//update our desPanel with new val
spinMod[spinMod.length-1][0] = val;//update our array of parameters
//remind our app that it must reset its pp dues to changes
searched = false;
}
107
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
});
//Add the labels to our Slider
Hashtable growthTable = new Hashtable();
for(int i=0; i<=maxGrowth; i+=growthIncrement){
growthTable.put(new Integer(i),new JLabel(i+"mm"));
}
growth.setLabelTable(growthTable);
sliderPanel.add(travel);
sliderPanel.add(growth);
suspPanel.setLayout(new BorderLayout());
suspPanel.add(labelPanel,BorderLayout.NORTH);
suspPanel.add(sliderPanel,BorderLayout.CENTER);
}
/**This panel loops through an array of labels and JSpinners to populate
*our panel. So that we may update our spinners in the future we could
*not simply create new spinners on the fly. Instead, we declare all of
*them in a global array at the bottom of the page.
*/
private void popGeom() {
geomPanel.setLayout(new RiverLayout());
geomPanel.add("p center", new JLabel(
"<html><font size='3'><b>Set Frame Geometry:</b></font></html>"));
/**
* A loop that runs through the "geomFields" array and populates the user interface
* according to the its values. Spinners models are stored in the spinMod array
*/
final double[] tmp = initGeometry;
//Loop that creates our form
for (int i = 0; i < geomFields.length; i++) {
JLabel l = new JLabel(geomFields[i][0]);
geomPanel.add("p right",l);
//Create the spinner itself
model = new SpinnerNumberModel(spinMod[i][0],
spinMod[i][1],
spinMod[i][2],
spinMod[i][3]);
//final JSpinner spinner = new JSpinner(model);
spinner[i].setModel(model); //initialise the spnner elsewhere
final int id = i;
//Add the listener to the spinner
spinner[i].addChangeListener(
new ChangeListener(){ //anonymous inner listener class
public void stateChanged(ChangeEvent e) {
Double val = (Double)(spinner[id].getValue());
tmp[id] = val.doubleValue();
desPanel.reDraw(tmp);
paramPanel.updateFrame(desPanel.popGeometryPlot(new double[6][2]));//takes this for speed reasons
spinMod[id][0] = val.doubleValue();
searched = false;
}
});
//adjust the size of the spinner
ftf = getTextField(spinner[i]);
ftf.setColumns(4);
ftf.setHorizontalAlignment(JTextField.RIGHT);
108
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478:
479:
480:
481:
482:
483:
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
514:
515:
516:
517:
518:
519:
520:
521:
522:
523:
524:
525:
526:
527:
528:
//Add components to the JPanel
l.setLabelFor(spinner[i]);
geomPanel.add("tab hfill",spinner[i]);
geomPanel.add(new JLabel(
"<html><font size='2'>"+geomFields[i][1]+"</font></html>"));
}
}
/** CREATE DESIGN PANEL CONTAINER
* From here: Turn the above into a tabbed pane, create methods/a new
* class for the bike design which can be called multiple times to
* to repaint them
*/
private void createDesignPanel() {
setGeometry();
results = new JTabbedPane();
desPanel = new designPreview(initGeometry);
JScrollPane design = new JScrollPane(desPanel);
results.addTab("Design View",design);
paramPanel = new parametricPreview(
desPanel.popGeometryPlot(new double[initGeometry.length][2]));
JScrollPane param = new JScrollPane(paramPanel);
results.addTab("Parametric View",param);
}
/** CONTROL PANEL
* From here: Turn the above into a Tabbed Pane, create methods
* which detail both pages. From these pages we can implement action
* listeners
*/
private void createControlPanel() {
control = new JTabbedPane();
control.setPreferredSize(new Dimension(control_width,screenSize.height-access_height-status_height));
geomPanel = new JPanel();
popGeom();
JScrollPane geom = new JScrollPane(geomPanel);
control.addTab("Geometry",geom);
suspPanel = new JPanel();
popSusp();
JScrollPane susp = new JScrollPane(suspPanel);
control.addTab("Suspension",susp);
}
/**
*/
private void createStatusPanel() {
//Create Status panel
status = new JPanel();
status.setPreferredSize(new Dimension(screenSize.width,status_height));
status.add(new JLabel("<html><font align=right size=1>© Copyright David Weldon 2006</font><html>"));
}
/**Set our geometry initially for our designPanel
*This only happens once, when we create our designPanel.
109
529:
530:
531:
532:
533:
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546:
547:
548:
549:
550:
551:
552:
553:
554:
555:
556:
557:
558:
559:
560:
561:
562:
563:
564:
565:
566:
567:
568:
569:
570:
571:
572:
573:
574:
575:
576:
577:
578:
579:
580:
581:
582:
583:
584:
585:
586:
587:
588:
589:
590:
591:
592:
593:
594:
*/
private void setGeometry() {
initGeometry = new double[13];
for(int i=0; i<initGeometry.length; i++) {
initGeometry[i] = spinMod[i][0];
}
}
/**2D array containing all information relating to geometric (poss turn to XML?)
* parameters - start val, min val, max val, increments
* CONTAINS FALLBACK GEOMETRY. ACTUAL GEOMETRY IS SET IN setGeometry()
*/
public void initSpinMod(){
spinMod = new double[][]{
{68,45,90,0.1},
{73,45,90,0.1},
{410,200,1000,1},
{560,200,1000,1},
{435,200,1000,1},
{340,100,1000,1},
{650,200,1000,1},
{500,200,1000,1},
{10,0,200,1},
{140,50,250,1},
{10,0,50,1},
{10,0,50,1},
{10,0,50,1},
{0,0,0,0},
{0,0,0,0}};
}
/**Used when user clicks "New" button. It resets all geometry
*to its initial status.
*/
public void resetSpinMod(){
initSpinMod();
updateSpinners(spinMod);
updateSliders(spinMod);
}
/**Used when a user opens a file. Each spinners value is updated
*with that of the new array returned by our openFile method.
*/
private void updateSpinners(double[][] spinMod) {
for(int i=0; i<spinner.length; i++){
spinner[i].setValue(new Double(spinMod[i][0]));
}
}
/**Used when a user opens a file. Each slider value is updated
*with that of the new array returned by our openFile method.
*/
private void updateSliders(double[][] spinMod) {
travel.setValue((int)spinMod[spinMod.length-2][0]);
growth.setValue((int)spinMod[spinMod.length-1][0]);
}
110
595:
596:
597:
598:
599:
600:
601:
602:
603:
604:
605:
606:
607:
608:
609:
610:
611:
612:
613:
614:
615:
616:
617:
618:
619:
620:
621:
622:
623:
624:
625:
626:
627:
628:
629:
630:
631:
632:
633:
634:
635:
636:
637:
638:
639:
640:
641:
642:
643:
644:
645:
646:
647:
648:
649:
650:
651:
652:
653:
654:
655:
656:
657:
658:
659:
660:
/**Calls our IOFile class and updates our spinners and sliders
*with the values contained within the users selected file. Also
*contains file dialogue.
*/
public final void openFile() throws NullPointerException{
JFileChooser fc = new JFileChooser();
pivotFileFilter filter = new pivotFileFilter();
fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fc.setFileFilter(filter);
fc.showOpenDialog(this.getContentPane());
File file = fc.getSelectedFile();
String path = "";
try{
path = file.getAbsolutePath();
}catch(NullPointerException e){}
if(path != ""){
IOFile iof = new IOFile();
spinMod = iof.open(path, spinMod);
updateSpinners(spinMod);
updateSliders(spinMod);
currentSavePath = file.getAbsolutePath();
}
}
/**Our save file method. A simple call to Save in IOFile.
*However, if currentPath is not set (no save has been made
*previously) then we call saveAs.
*/
public final void saveFile() throws IOException {
if(currentSavePath != ""){
IOFile iof = new IOFile();
iof.save(currentSavePath,spinMod); //current values of spinners :s
}else{
saveAsFile();
}
}
/**Saves our geometry (spinMod) to a file of the name specified
*by the user. Also checks to see that the filename has not
*already been taken.
*/
public final void saveAsFile() throws IOException, NullPointerException{
JFileChooser fc = new JFileChooser();
pivotFileFilter filter = new pivotFileFilter();
fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fc.setFileFilter(filter);
fc.showSaveDialog(this.getContentPane());
//Our fun regexp work to strip filename.
String filename = fc.getSelectedFile().getAbsolutePath().replaceAll(".pivot","")+".pivot";
File file = new File(filename);
String path = "";
if(file.exists()){
JOptionPane.showMessageDialog(this,
"File name already exists.",
"Warning",
JOptionPane.WARNING_MESSAGE);
111
661:
662:
663:
664:
665:
666:
667:
668:
669:
670:
671:
672:
673:
674:
675:
676:
677:
678:
679:
680:
681:
682:
683:
684:
685:
686:
687:
688:
689:
690:
691:
692:
693:
694:
695:
696:
697:
698:
699:
700:
701:
702:
703:
704:
705:
706:
707:
708:
709:
710:
711:
712:
713:
714:
715:
716:
717:
718:
719:
720:
721:
722:
723:
724:
725:
726:
}else{
try{
path = file.getAbsolutePath();
}catch(NullPointerException e){
System.out.println("Save Cancelled.");
}
if(path != ""){
IOFile iof = new IOFile();
iof.saveAs(path,spinMod); //current values of spinners :s
currentSavePath = file.getAbsolutePath()+".pivot";
}
}
}
/**Saves our geometry (spinMod) to a file of the name specified
*by the user in the format of a dxf (drawing exchange format)
*file. Also checks to see that the filename has not
*already been taken.
*/
public void exportDXF(double[] p, double[][] c, double wr){
System.out.println("Exporting...");
JFileChooser fc = new JFileChooser();
DXFFileFilter filter = new DXFFileFilter();
fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fc.setFileFilter(filter);
fc.showSaveDialog(this.getContentPane());
String filename = fc.getSelectedFile().getAbsolutePath().replaceAll(".dxf","")+".dxf";
File file = new File(filename);
String path = "";
if(file.exists()){
JOptionPane.showMessageDialog(this,
"File name already exists.",
"Warning",
JOptionPane.WARNING_MESSAGE);
}else{
try{
path = file.getAbsolutePath();
}catch(NullPointerException e){
System.out.println("Export Cancelled.");
}
if(path != ""){
IOFile iof = new IOFile();
iof.export(p,c,wr,path);
}
}
}
/**The reason for this being a separate method is that we
*cannot reference JOptionPane from inside a nested method.
*/
public void searchForPointError(){
JOptionPane.showMessageDialog(this,
"Please search for a pivot point before exporting your \".dxf\" file.
"Warning",
JOptionPane.WARNING_MESSAGE);
}
112
",
727:
728:
729:
730:
731:
732:
733:
734:
735:
736:
737:
738:
739:
740:
741:
742:
743:
744:
745:
746:
747:
748:
749:
750:
751:
752:
753:
754:
755:
756:
757:
758:
759:
760:
761:
762:
763:
764:
765:
766:
767:
768:
769:
770:
771:
772:
773:
774:
775:
776:
777:
778:
779:
780:
781:
782:
783:
784:
785:
786:
787:
788:
789:
790:
791:
792:
/**
* A method taken from java.sun.com which allows the low level editing
* of a spinners formatted textfield.
*/
private JFormattedTextField getTextField(JSpinner spinner) {
JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.DefaultEditor) {
return ((JSpinner.DefaultEditor)editor).getTextField();
} else {
System.err.println("Unexpected editor type: "
+ spinner.getEditor().getClass()
+ " isn't a descendant of DefaultEditor");
return null;
}
}
int access_height = 60; // height of the access panel
int status_height = 20; // height of the access panel
int control_width = 270; // width of the control panel
double[] initGeometry;
double[][] spinMod;
designPreview desPanel;
parametricPreview paramPanel;
Dimension screenSize;
JPanel access;//For the accessibility panel
JTabbedPane results;//For the design panel
JTabbedPane control;//For the control panel
JPanel status;//For the status panel
JPanel geomPanel;
JPanel suspPanel;
String spinnerName;
SpinnerModel model;
JFormattedTextField ftf = null;
String currentSavePath = "";
boolean searched = false;
final JSpinner sp0 = new JSpinner();
final JSpinner sp1 = new JSpinner();
final JSpinner sp2 = new JSpinner();
final JSpinner sp3 = new JSpinner();
final JSpinner sp4 = new JSpinner();
final JSpinner sp5 = new JSpinner();
final JSpinner sp6 = new JSpinner();
final JSpinner sp7 = new JSpinner();
final JSpinner sp8 = new JSpinner();
final JSpinner sp9 = new JSpinner();
final JSpinner sp10 = new JSpinner();
final JSpinner sp11 = new JSpinner();
final JSpinner sp12 = new JSpinner();
JSlider travel;
JSlider growth;
JSpinner[] spinner = new JSpinner[]{sp0,sp1,sp2,sp3,sp4,sp5,sp6,sp7,sp8,sp9,sp10,sp11,sp12};
/**2D array containing all data relevant to form. (poss turn to XML?)
* parameters - Label for spinner, unit measure
*/
String[][] geomFields = {
{"<html><font size='2'>Head angle: </font></html>","deg"},
{"<html><font size='2'>Seat angle: </font></html>","deg"},
{"<html><html><font size='2'>Seat tube length: </font></html>","mm"},
{"<html><font size='2'>Top tube length: </font></html>","mm"},
{"<html><font size='2'>Chainstay length: </font></html>","mm"},
113
793:
794:
795:
796:
797:
798:
799:
800:
801:
802: }
803:
{"<html><font size='2'>BB height: </font></html>","mm"},
{"<html><font size='2'>Wheel diameter: </font></html>","mm"},
{"<html><font size='2'>Fork length: </font></html>","mm"},
{"<html><font size='2'>Fork rake: </font></html>","mm"},
{"<html><font size='2'>Head tube length: </font></html>","mm"},
{"<html><font size='2'>Seat tube extension<p>above top tube: </font></html>","mm"},
{"<html><font size='2'>Head tube extension<p>above top tube: </font></html>","mm"},
{"<html><font size='2'>Head tube extension<p>below downtube: </font></html>","mm"}};
114
E.3
parametricPreview.java
115
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:
/*
* parametricPreview.java
*
* Created on March 6, 2006, 5:54 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import java.awt.*;
import javax.swing.*;
import se.datadosen.component.RiverLayout;
/**
*
* @author dave
*/
public class parametricPreview extends JPanel {
/**Creates a new instance of parametricPreview. Calls
*getStats to initialise the values for the text fields.
*/
public parametricPreview(double[][] geom) {
getStats(geom); //prepare the value array
addParameters();
}
/**Add our parameter interface elements to the panel. Again,
*we use RiverLayoutfor its ease of laying out forms.
*/
private void addParameters(){
this.setLayout(new RiverLayout());
this.setBorder(BorderFactory.createEmptyBorder(10,30,10,10));
ettl = new JTextField(toString(value[0]),6);
ettl.setEditable(false);
wBase = new JTextField(toString(value[1]),6);
wBase.setEditable(false);
eStay = new JTextField(toString(value[2]),6);
eStay.setEditable(false);
add("p left",new JLabel("<html><b>Parametric View:</b><br></html>"));
add("p left",new JLabel("Effective Top Tube Length"));
add("tab",ettl);
add("",new JLabel("mm"));
add("p left",new JLabel("Wheelbase"));
add("tab",wBase);
add("",new JLabel("mm"));
add("p left",new JLabel("Effective Chainstay Length"));
add("tab",eStay);
add("",new JLabel("mm"));
}
/**Get our textField values
*/
public void getStats(double[][] geom){
value[0] = getETTLength(geom);
value[1] = getWheelbase(geom);
value[2] = getChainstay(geom);
}
116
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:
/**Set our textField values.
*/
public void updateFrame(double[][] geom){
getStats(geom);
ettl.setText(toString(getETTLength(geom)));
wBase.setText(toString(getWheelbase(geom)));
eStay.setText(toString(getChainstay(geom)));
}
/**Get the wheelbase of the bike.
*/
private double getWheelbase(double[][] geom) {
return Math.round(geom[5][0]-geom[0][0]);
}
/**Get the effective top tube length.
*/
private double getETTLength(double[][] geom) {
return Math.round(geom[3][0]-geom[2][0]);
}
/**Get the effective chainstay length
*/
private double getChainstay(double[][] geom) {
return Math.round(geom[1][0]-geom[0][0]);
}
/**This method is here incase we ever need to plot the
*coordinates of our pivot point.
*/
public void updatePivot(double[] point) throws NullPointerException{
try{
pivotPoint = point;
revalidate();
}catch(NullPointerException e){
System.out.println("Null pointer in updatePivot");
}
}
/**Turns values into strings for display in textFields.
*/
public String toString(double in){
String val = "";
return val += (int)in;
}
double[] pivotPoint = new double[]{0,0};
double[] value = new double[3]; //Values of text fields
JTextField ettl;
117
133:
JTextField wBase;
134:
JTextField eStay;
135: }
118
E.4
designPreview.java
119
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:
/*
* designPreview.java
*
* Created on March 1, 2006, 9:45 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*
* N.B.: Please refer to drawing to understand method naming conventions.
*
A,B,C,D,E,F all refer to points on the drawing.
*
Might want to include my thought pattern in the writeup
*/
package pivot;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.util.ArrayList;
/**
*
* @author dave
*/
public class designPreview extends JPanel {
/** Creates a new instance of designPreview
*/
public designPreview(double[] geomIn) {
geometry = geomIn;
}
/**Reset the geometry array with new values.
* Possibly listen for which box is being altered and only change that value
*/
public void reDraw(double[] geomIn) {
search = false;
geometry = geomIn;
repaint();
}
/**Takes argument as empty 2d array for speed reasons.
*Populates our geoemtryPlot array with coordinates of
*our points (A,B,C,D,E,F)
*/
public double[][] popGeometryPlot(double[][] geometryPlot) {
geometryPlot = addA(geometryPlot);
geometryPlot = addB(geometryPlot);
geometryPlot = addC(geometryPlot);
geometryPlot = addD(geometryPlot);
geometryPlot = addE(geometryPlot);
geometryPlot = addF(geometryPlot);
return geometryPlot;
}
/**Locate point A
*/
private double[][] addA(double[][] geometryPlot) {
geometryPlot[0][0] = geometry[6]/2; //X
120
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:
geometryPlot[0][1] = geometry[6]/2; //Y
return geometryPlot;
}
/** Locate point B
*/
private double[][] addB(double[][] geometryPlot) {
//Workout shift in Y direction
double bbDrop = (geometry[6]/2)-geometry[5]; //wheel radius - bb height
//Workout shift in X direction
double temp = (geometry[4]*geometry[4])-(bbDrop*bbDrop);
if (temp<0){temp=temp*(-1);} //avoid NaN case
double effectiveCSL = Math.sqrt(temp); //Effective chainstay
//Add new coords
geometryPlot[1][0] = geometryPlot[0][0]+effectiveCSL;
geometryPlot[1][1] = geometryPlot[0][1]-bbDrop;
return geometryPlot;
}
/**NOTE: The size of the frame is the seatpost length,
* the extra length above the toptube is subtracted from the
* seat tube length when drawing because it would affect the
* shape of the bike.
*
* Locate point C
*/
private double[][] addC(double[][] geometryPlot) {
double hypotenuse = geometry[2]-geometry[10]; //bottom bracket to top tube length
double seatAngle = Math.toRadians(geometry[1]);
//Workout shift in X direction
double setBack = Math.cos(seatAngle)*hypotenuse*(-1);
//Workout shift in Y direction
double stHeight = Math.sin(seatAngle)*hypotenuse;
//Add new coords
geometryPlot[2][0] = geometryPlot[1][0]+setBack;
geometryPlot[2][1] = geometryPlot[1][1]+stHeight;
return geometryPlot;
}
/**Locate point D
*/
private double[][] addD(double[][] geometryPlot) {
//Size of head tube taking into account the extensions specified in
//the last 3 options
double headTube = geometry[9]+
geometry[12]geometry[11];
//Find the effective fork length
double d2axle = geometry[7] + headTube;
double eForkLength = Math.sqrt(
121
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
(d2axle*d2axle)+ //fork length
(geometry[8]*geometry[8])); //fork rake (11=head tube extension above tt)
//Find the extra angle created by the fork offset (cah)
double forkAngle = Math.atan(geometry[8]/d2axle);
double angle = (90-geometry[0])+Math.toDegrees(forkAngle);
//Work out the height of the head
double forkHeight = Math.cos(Math.toRadians(angle))*eForkLength;
double headHeight = forkHeight+ (geometry[6]/2);
//What top tube rise does the headHeight equate to?
double rise = headHeight-geometryPlot[2][1]; //use current height of C
//We can now compute the effective length of the top tube
double temp = (geometry[3]*geometry[3])-(rise*rise);
if (temp<0){temp=temp*(-1);} //avoid NaN case
double eTopTubeLength = Math.sqrt(temp);
//Add new coords
geometryPlot[3][0] = geometryPlot[2][0]+eTopTubeLength;
geometryPlot[3][1] = geometryPlot[2][1]+rise;
return geometryPlot;
}
/**Locate point C
*/
private double[][] addE(double[][] geometryPlot) {
double aHeadAngle = 90+geometry[0];
double eHeadLength = geometry[9]- //Hypotenuse
geometry[11]geometry[12];
double headY = Math.cos(Math.toRadians(aHeadAngle))*
eHeadLength;
double headX = Math.sin(Math.toRadians(aHeadAngle))*
eHeadLength;
//Add new coords
geometryPlot[4][0] = geometryPlot[3][0]+headX;
geometryPlot[4][1] = geometryPlot[3][1]+headY;
return geometryPlot;
}
/**Locate point F
*/
private double[][] addF(double[][] geometryPlot) {
double eLength = geometry[7]+geometry[12]; //length of fork + bottom of head tube
double temp = (geometry[7]*geometry[7])+
(geometry[8]*geometry[8]);
double axle2down = Math.sqrt(temp);
//Find the extra angle created by the fork offset (cah)
double forkAngle = Math.atan(geometry[8]/axle2down);
double angle = (90-geometry[0])+
Math.toDegrees(forkAngle);
double Xshift = Math.sin(Math.toRadians(angle))*
axle2down;
122
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
//Add new coords
geometryPlot[5][0] = geometryPlot[4][0]+Xshift;
geometryPlot[5][1] = geometryPlot[0][1];
return geometryPlot;
}
/**Allows parents to retrieve our array of points.
*/
public double[][] getMajorPoints(){return geometryPlotGlobal;}
/**Allows parents to get our optimal pivot point.
*/
public double[] getPoint(){return pivot;}
/**
*/
public double getWheelRadius(){return geometry[6]/2;}
/**Scale our bikes geometry to fit it in the view. Note,
*this method gets called for each change to geometry
*to prevent our image from ourgrowing the screen. We
*have also included a border in our calculations.
*/
private int[][] scaleGeometry(double[][] geometryPlot) {
//set scale
double bikeWidth = geometry[6]+geometryPlot[5][0]-geometryPlot[0][0]+border;
scale = (IMG_WIDTH/bikeWidth);
offset = (border*scale)/2;
//scale geometry
int[][] scaled = new int[geometryPlot.length][2];
for(int i=0; i<scaled.length;i++) {
for(int j=0; j<2; j++){
scaled[i][j] = (int)Math.round(geometryPlot[i][j]*scale+offset);
}
}
wheelWidth = (int)(Math.round(geometry[6])*scale);
return scaled;
}
/**Draw our bikes wheels
*/
private void drawWheels(int[][] geometryScaled) {
//Rear wheel
c.drawOval(geometryScaled[0][0]-(wheelWidth/2),
geometryScaled[0][1]-(wheelWidth/2),
wheelWidth,
wheelWidth);
//Front wheel
c.drawOval(geometryScaled[5][0]-(wheelWidth/2),
geometryScaled[5][1]-(wheelWidth/2),
wheelWidth,
wheelWidth);
}
123
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
/**Draw our frame to the screen.
*/
private void drawFrame(int[][] geometryScaled) {
//x (set them here because creating new objects is slow)
int[] polygonX = new int[4];
polygonX[0] = geometryScaled[1][0];
polygonX[1] = geometryScaled[2][0];
polygonX[2] = geometryScaled[3][0];
polygonX[3] = geometryScaled[4][0];
//y
int[] polygonY = new int[4];
polygonY[0] = geometryScaled[1][1];
polygonY[1] = geometryScaled[2][1];
polygonY[2] = geometryScaled[3][1];
polygonY[3] = geometryScaled[4][1];
frame = new Polygon(polygonX,polygonY,4);
c.drawPolygon(frame);
}
/**A non-scaled version of the bike frame for the search algorithm.
*This is different to drawFrame() because we are not scaling the
*frame. The intended purpose of this method is as the bounds
*of our search.
*/
private Polygon makeFrame(double[][] geometryPlot) {
//x (set them here because creating new classes is slow)
int[] polygonX = new int[4];
polygonX[0] = (int)geometryPlot[1][0];
polygonX[1] = (int)geometryPlot[2][0];
polygonX[2] = (int)geometryPlot[3][0];
polygonX[3] = (int)geometryPlot[4][0];
//y
int[] polygonY = new int[4];
polygonY[0] = (int)geometryPlot[1][1];
polygonY[1] = (int)geometryPlot[2][1];
polygonY[2] = (int)geometryPlot[3][1];
polygonY[3] = (int)geometryPlot[4][1];
frame = new Polygon(polygonX,polygonY,4);
return frame;
}
/** Draws our swingarm. It is in here that we get our search
*to provide us with the pivot point. Once we have our pivot
*point we draw our swingarm. NOTE: we have many searches
*defined, only one will be used at any time.
*/
private void drawSwingarm(double[][] geometryPlot) {
double[] hubHeight = new double[2];
hubHeight[0] = geometryPlot[0][0];
hubHeight[1] = geometryPlot[0][1];
double[] BBheight = new double[2];
BBheight[0] = geometryPlot[1][0];
BBheight[1] = geometryPlot[1][1];
124
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
pivot = new double[2];
switch(searchType){
case(1)://Random Search
randomSearch rs = new randomSearch();
pivot = rs.getOptimalPoint(
makeFrame(geometryPlot),
hubHeight,
BBheight,
travel,
growth);
if(showSearch){printPoints(rs.getPoints(),0);}
break;
case(2)://Variance Based Hill Climb
SimulatedAnnealing vbhc = new SimulatedAnnealing(makeFrame(geometryPlot),
1000, //depth
30, //points picked
0, //start temp
hubHeight,
BBheight,
c,
travel,
growth,
true,
false,
100);
pivot = vbhc.search();
if(showSearch){printPoints(vbhc.getPoints(),0);}
if(showSearch){printPoints(vbhc.getChosenPoints(),1);}
break;
case(3)://Hill Climb
SimulatedAnnealing hc = new SimulatedAnnealing(makeFrame(geometryPlot),
100, //depth
30, //points picked
0, //start temp
hubHeight,
BBheight,
c,
travel,
growth,
false,
true,
makeFrame(geometryPlot).getBounds().width);
pivot = hc.search();
if(showSearch){printPoints(hc.getPoints(),0);}
if(showOptimalSearch){printPoints(hc.getChosenPoints(),1);}
break;
case(4)://Simulated Annealing
int count = 0;
//Random restart simulated annealing
do{
sa = new SimulatedAnnealing(makeFrame(geometryPlot),
100, //depth
30, //points picked
80, //start temp
hubHeight,
BBheight,
c,
travel,
growth,
true,
false,
makeFrame(geometryPlot).getBounds().width);
125
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
pivot = sa.search();
count++;
}while((sa.getFCost(pivot)>1000)&& (count < 10));
System.out.println(sa.getFCost(pivot)+". Searches made: "+count);
if(showSearch){printPoints(sa.getPoints(),0);}
if(showOptimalSearch){printPoints(sa.getChosenPoints(),1);}
break;
}
//create scaled version of our search area
int[] swingarmScaled = new int[4];
swingarmScaled[0] = (int)(geometryPlot[0][0]*scale+offset);
swingarmScaled[1] = (int)(geometryPlot[0][1]*scale+offset);
swingarmScaled[2] = (int)(pivot[0]*scale+offset);
swingarmScaled[3] = (int)(pivot[1]*scale+offset);
//populate the array
c.setColor(Color.BLUE);
c.drawLine(swingarmScaled[0],
swingarmScaled[1],
swingarmScaled[2],
swingarmScaled[3]);
}
/**Turn on our show trace option
*/
public void setShowSearch(boolean a){showSearch = a;}
/**Turn on our show optimal search option.
*/
public void setShowOptimalSearch(boolean a){showOptimalSearch = a;}
/**Sets the global preferred search type selected
*by the user in our user interface.
*/
public void setSearchType(int typeIn){
searchType = typeIn;
}
/**Print our search trace. Colour can be either 0 or 1
*depending on the search you are doing.
*/
public void printPoints(ArrayList a, int colour){
switch(colour){
case(0):c.setColor(Color.green);
break;
case(1):c.setColor(Color.BLACK);
break;
}
double[] p;
126
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478:
479:
480:
481:
482:
483:
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
514:
515:
516:
517:
518:
519:
520:
521:
522:
523:
524:
525:
526:
527:
528:
for(int i=0; i<a.size();i++){
p = (double[])a.get(i);
c.drawLine((int)(p[0]*scale+offset),
(int)(p[1]*scale+offset),
(int)(p[0]*scale+offset),
(int)(p[1]*scale+offset));
}
}
/** Called to initiate search in its parent.
*/
public void search() {
search = true;
repaint();
}
/**Update the amount of travel permitted by the user. This
*is set in out interface
*/
public void updateTravel(int val) {
travel = val;
}
/**Update the amount of chain growth permitted by the user. This
*is set in out interface
*/
public void updateGrowth(int val) {
growth = val;
}
/**Draw our fork to the screen.
*/
private void drawFork(int[][] geometryScaled) {
c.drawLine(geometryScaled[4][0],
geometryScaled[4][1],
geometryScaled[5][0],
geometryScaled[5][1]);
}
/**Our paint component.
*/
public void paintComponent(Graphics g) {
//Initialise JPanel
this.setPreferredSize(new Dimension(this.getWidth(),this.getHeight()));
IMG_WIDTH = this.getWidth()-10;
IMG_HEIGHT = this.getHeight()-10;
//Initialise our canvas
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
bi = new BufferedImage(IMG_WIDTH,
IMG_HEIGHT,
BufferedImage.TYPE_INT_RGB);
c = bi.createGraphics();
127
529:
530:
531:
532:
533:
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546:
547:
548:
549:
550:
551:
552:
553:
554:
555:
556:
557:
558:
559:
560:
561:
562:
563:
564:
565:
566:
567:
568:
569:
570:
571:
572:
573:
574:
575:
576:
577:
578:
579:
580:
581:
582:
583:
584:
585:
586:
587:
588:
589:
590: }
c.setBackground(Color.WHITE);
c.clearRect(0,0,IMG_WIDTH,IMG_HEIGHT);
//set up the geometry arrays
double[][] geometryPlot = new double[6][2];
int[][] geometryScaled = new int[geometryPlot.length][2];
geometryPlotGlobal = geometryPlot;
//Begin Drawing (setStroke)
popGeometryPlot(geometryPlot);
geometryScaled = scaleGeometry(geometryPlot);
c.setColor(Color.BLACK);
drawWheels(geometryScaled);
c.setColor(Color.RED);
drawFrame(geometryScaled);
c.setColor(Color.GRAY);
drawFork(geometryScaled);
c.setColor(Color.BLUE);
if(search == true){
drawSwingarm(geometryPlot);
}
//Determine the size of the border around the canvas before drawing (incase of negative zoom)
int offset_w = (this.getWidth()-bi.getWidth())/2;
int offset_h = (this.getHeight()-bi.getHeight())/2;
//Transform to enable us to not have to worry about Java coordinate space
g2.drawImage(
bi,
offset_w,
bi.getHeight(this)+offset_h,
bi.getWidth(this)+offset_w,
offset_h,
0,
0,
bi.getWidth(this),
bi.getHeight(this),
this);
}
BufferedImage bi;
Graphics2D c;
Polygon frame;
SimulatedAnnealing sa;
double[] geometry;
double[][]geometryPlotGlobal;
double scale;
double offset;
double swingarmRise = 10;
int travel = 0;
int growth = 0;
int border = 200;
int wheelWidth;
int IMG_WIDTH;
int IMG_HEIGHT;
boolean search = false;
int searchType = 4;
double[] pivot;
boolean showSearch = false;
boolean showOptimalSearch = false;
128
E.5
IOFile.java
129
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:
/*
* IOFile.java
*
* Created on April 11, 2006, 3:40 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import java.io.*;
/**
*
* @author dave
*
*Our class for all input and output.
*Includes the following methods:
*-export
*-save
*-saveAs
*-open
*/
public class IOFile {
/** Creates a new instance of IOFile */
public IOFile() {
}
/**Exports a file in the "Drawing Exchange Format"
*/
public void export(double[] p, double[][] c, double wr, String path) {
try{
File outputFile = new File(path+".dxf");
FileOutputStream fout = new FileOutputStream (path);
String temp1 =
" 0\n"+
"SECTION\n"+
" 2\n"+
"HEADER\n"+
" 9\n"+
"$ACADVER\n"+
" 1\n"+
"AC1009\n"+
" 9\n"+
"$INSBASE\n"+
" 10\n"+
"0.0\n"+
" 20\n"+
"0.0\n"+
" 30\n"+
"0.0\n"+
" 9\n"+
"$EXTMIN\n"+
" 10\n"+
"0.0\n"+
" 20\n"+
"0.0\n"+
" 30\n"+
"0.0\n"+
" 9\n"+
130
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:
"$EXTMAX\n"+
" 10\n"+
"1735.32838455\n"+
" 20\n"+
"914.65160099\n"+
" 30\n"+
"0.0\n"+
" 9\n"+
"$LIMMIN\n"+
" 10\n"+
"0.0\n"+
" 20\n"+
"0.0\n"+
" 9\n"+
"$LIMMAX\n"+
" 10\n"+
"1735.32838455\n"+
" 20\n"+
"914.65160099\n"+
" 9\n"+
"$ORTHOMODE\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$REGENMODE\n"+
" 70\n"+
" 1\n"+
" 9\n"+
"$FILLMODE\n"+
" 70\n"+
" 1\n"+
" 9\n"+
"$QTEXTMODE\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$MIRRTEXT\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DRAGMODE\n"+
" 70\n"+
" 2\n"+
" 9\n"+
"$LTSCALE\n"+
" 40\n"+
"25.0\n"+
" 9\n"+
"$ATTMODE\n"+
" 70\n"+
" 1\n"+
" 9\n"+
"$TEXTSIZE\n"+
" 40\n"+
"3.5\n"+
" 9\n"+
"$TEXTSTYLE\n"+
" 7\n"+
"STANDARD\n"+
" 9\n"+
"$CELTYPE\n"+
" 6\n"+
"BYLAYER\n"+
" 9\n"+
"$DIMSCALE\n"+
" 40\n"+
131
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
"1.0\n"+
" 9\n"+
"$DIMASZ\n"+
" 40\n"+
"3.5\n"+
" 9\n"+
"$DIMEXO\n"+
" 40\n"+
"0.1\n"+
" 9\n"+
"$DIMDLI\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMRND\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMDLE\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMEXE\n"+
" 40\n"+
"1.8\n"+
" 9\n"+
"$DIMTP\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMTM\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMTXT\n"+
" 40\n"+
"3.5\n"+
" 9\n"+
"$DIMCEN\n"+
" 40\n"+
"1.5\n"+
" 9\n"+
"$DIMTSZ\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMTOL\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMLIM\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMTIH\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMTOH\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMSE1\n"+
" 70\n"+
" 0\n"+
" 9\n"+
132
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
"$DIMSE2\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMTAD\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMZIN\n"+
" 70\n"+
" 4\n"+
" 9\n"+
"$DIMBLK\n"+
" 1\n"+
"\n"+
" 9\n"+
"$DIMASO\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMSHO\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMPOST\n"+
" 1\n"+
"\n"+
" 9\n"+
"$DIMAPOST\n"+
" 1\n"+
"\n"+
" 9\n"+
"$DIMALT\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMALTD\n"+
" 70\n"+
" 8\n"+
" 9\n"+
"$DIMALTF\n"+
" 40\n"+
"25.4\n"+
" 9\n"+
"$DIMLFAC\n"+
" 40\n"+
"1.0\n"+
" 9\n"+
"$DIMTOFL\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMTVP\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$DIMTIX\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMSOXD\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMSAH\n"+
" 70\n"+
133
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
" 0\n"+
" 9\n"+
"$DIMBLK1\n"+
" 1\n"+
"\n"+
" 9\n"+
"$DIMBLK2\n"+
" 1\n"+
"\n"+
" 9\n"+
"$DIMSTYLE\n"+
" 2\n"+
"*UNNAMED\n"+
" 9\n"+
"$DIMCLRD\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMCLRE\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMCLRT\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DIMTFAC\n"+
" 40\n"+
"1.0\n"+
" 9\n"+
"$DIMGAP\n"+
" 40\n"+
"0.09\n"+
" 9\n"+
"$LUNITS\n"+
" 70\n"+
" 2\n"+
" 9\n"+
"$LUPREC\n"+
" 70\n"+
" 2\n"+
" 9\n"+
"$SKETCHINC\n"+
" 40\n"+
"1.0\n"+
" 9\n"+
"$FILLETRAD\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$AUNITS\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$AUPREC\n"+
" 70\n"+
" 2\n"+
" 9\n"+
"$MENU\n"+
" 1\n"+
"ACAD\n"+
" 9\n"+
"$ANGBASE\n"+
" 50\n"+
"0.0\n"+
" 9\n"+
134
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
"$ANGDIR\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$PDMODE\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$PDSIZE\n"+
" 40\n"+
"0.0\n"+
" 9\n"+
"$SPLFRAME\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$SPLINETYPE\n"+
" 70\n"+
" 6\n"+
" 9\n"+
"$SPLINESEGS\n"+
" 70\n"+
" 8\n"+
" 9\n"+
"$ATTDIA\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$HANDLING\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$HANDSEED\n"+
" 5\n"+
" 0\n"+
" 9\n"+
"$TILEMODE\n"+
" 70\n"+
" 1\n"+
" 9\n"+
"$MAXACTVP\n"+
" 70\n"+
" 16\n"+
" 9\n"+
"$PLIMMIN\n"+
" 10\n"+
"0.0\n"+
" 20\n"+
"0.0\n"+
" 9\n"+
"$PLIMMAX\n"+
" 10\n"+
"12.0\n"+
" 20\n"+
"9.0\n"+
" 9\n"+
"$UNITMODE\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$VISRETAIN\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$PLINEGEN\n"+
" 70\n"+
135
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
" 0\n"+
" 9\n"+
"$TREEDEPTH\n"+
" 70\n"+
" 0\n"+
" 9\n"+
"$DWGCODEPAGE\n"+
" 3\n"+
"dos861\n"+
" 0\n"+
"ENDSEC\n";
String temp2 =
" 0\n"+
"SECTION\n"+
" 2\n"+
"TABLES\n"+
" 0\n"+
"TABLE\n"+
" 2\n"+
"LTYPE\n"+
" 70\n"+
" 6\n"+
" 0\n"+
"LTYPE\n"+
" 2\n"+
"CONTINUOUS\n"+
" 70\n"+
" 64\n"+
" 3\n"+
"Solid Line\n"+
" 72\n"+
" 65\n"+
" 73\n"+
" 0\n"+
" 40\n"+
"0.0\n"+
" 0\n"+
"LTYPE\n"+
" 2\n"+
"DASHED\n"+
" 70\n"+
" 64\n"+
" 3\n"+
"__ __ __ __ __\n"+
" 72\n"+
" 65\n"+
" 73\n"+
" 2\n"+
" 40\n"+
"1.5\n"+
" 49\n"+
"1.2\n"+
" 49\n"+
"-0.3\n"+
" 0\n"+
"LTYPE\n"+
" 2\n"+
"HIDDEN\n"+
" 70\n"+
" 64\n"+
" 3\n"+
"_ _ _ _ _\n"+
" 72\n"+
" 65\n"+
136
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478:
479:
480:
481:
482:
483:
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
514:
515:
516:
517:
518:
519:
520:
521:
522:
523:
524:
525:
526:
527:
528:
" 73\n"+
" 2\n"+
" 40\n"+
"1.5\n"+
" 49\n"+
"1.2\n"+
" 49\n"+
"-0.3\n"+
" 0\n"+
"LTYPE\n"+
" 2\n"+
"CENTER\n"+
" 70\n"+
" 64\n"+
" 3\n"+
"__ . __ . __\n"+
" 72\n"+
" 65\n"+
" 73\n"+
" 4\n"+
" 40\n"+
"3.6\n"+
" 49\n"+
"2.4\n"+
" 49\n"+
"-0.6\n"+
" 49\n"+
"0.0\n"+
" 49\n"+
"-0.6\n"+
" 0\n"+
"LTYPE\n"+
" 2\n"+
"PHANTOM\n"+
" 70\n"+
" 64\n"+
" 3\n"+
"_____ . . _____ . . _____ . . ____ .. ____\n"+
" 72\n"+
" 65\n"+
" 73\n"+
" 6\n"+
" 40\n"+
"17.5\n"+
" 49\n"+
"10.0\n"+
" 49\n"+
"-2.5\n"+
" 49\n"+
"0.0\n"+
" 49\n"+
"-2.5\n"+
" 49\n"+
"0.0\n"+
" 49\n"+
"-2.5\n"+
" 0\n"+
"LTYPE\n"+
" 2\n"+
"DOT\n"+
" 70\n"+
" 64\n"+
" 3\n"+
". . . . . . . . . . . . . . . . . . . . . . . .\n"+
" 72\n"+
" 65\n"+
137
529:
530:
531:
532:
533:
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546:
547:
548:
549:
550:
551:
552:
553:
554:
555:
556:
557:
558:
559:
560:
561:
562:
563:
564:
565:
566:
567:
568:
569:
570:
571:
572:
573:
574:
575:
576:
577:
578:
579:
580:
581:
582:
583:
584:
585:
586:
587:
588:
589:
590:
591:
592:
593:
594:
" 73\n"+
" 2\n"+
" 40\n"+
"0.25\n"+
" 49\n"+
"0.0\n"+
" 49\n"+
"-0.25\n"+
" 0\n"+
"ENDTAB\n"+
" 0\n"+
"TABLE\n"+
" 2\n"+
"LAYER\n"+
" 70\n"+
" 3\n"+
" 0\n"+
"LAYER\n"+
" 2\n"+
"0\n"+
" 70\n"+
" 64\n"+
" 62\n"+
" 7\n"+
" 6\n"+
"CONTINUOUS\n"+
" 0\n"+
"LAYER\n"+
" 2\n"+
"DEFPOINTS\n"+
" 70\n"+
" 64\n"+
" 62\n"+
" -7\n"+
" 6\n"+
"CONTINUOUS\n"+
" 0\n"+
"LAYER\n"+
" 2\n"+
"LAYER_0000\n"+
" 70\n"+
" 64\n"+
" 62\n"+
" 18\n"+
" 6\n"+
"CONTINUOUS\n"+
" 0\n"+
"ENDTAB\n"+
" 0\n"+
"TABLE\n"+
" 2\n"+
"STYLE\n"+
" 70\n"+
" 1\n"+
" 0\n"+
"STYLE\n"+
" 2\n"+
"STANDARD\n"+
" 70\n"+
" 0\n"+
" 40\n"+
"0.0\n"+
" 41\n"+
"1.0\n"+
" 50\n"+
"0.0\n"+
138
595:
596:
597:
598:
599:
600:
601:
602:
603:
604:
605:
606:
607:
608:
609:
610:
611:
612:
613:
614:
615:
616:
617:
618:
619:
620:
621:
622:
623:
624:
625:
626:
627:
628:
629:
630:
631:
632:
633:
634:
635:
636:
637:
638:
639:
640:
641:
642:
643:
644:
645:
646:
647:
648:
649:
650:
651:
652:
653:
654:
655:
656:
657:
658:
659:
660:
" 71\n"+
" 0\n"+
" 42\n"+
"3.5\n"+
" 3\n"+
"txt\n"+
" 4\n"+
"\n"+
" 0\n"+
"ENDTAB\n"+
" 0\n"+
"TABLE\n"+
" 2\n"+
"DIMSTYLE\n"+
" 70\n"+
" 1\n"+
" 0\n"+
"DIMSTYLE\n"+
" 2\n"+
"Standard\n"+
" 70\n"+
" 0\n"+
" 3\n"+
"\n"+
" 4\n"+
"\n"+
" 5\n"+
"\n"+
" 6\n"+
"\n"+
" 7\n"+
"\n"+
" 40\n"+
"1.0\n"+
" 41\n"+
"3.5\n"+
" 42\n"+
"0.1\n"+
" 43\n"+
"0.0\n"+
" 44\n"+
"0.0\n"+
" 45\n"+
"0.0\n"+
" 46\n"+
"0.0\n"+
" 47\n"+
"0.0\n"+
" 48\n"+
"0.0\n"+
"140\n"+
"3.5\n"+
"141\n"+
"1.5\n"+
"142\n"+
"0.0\n"+
"143\n"+
"25.4\n"+
"144\n"+
"1.0\n"+
"145\n"+
"0.0\n"+
"146\n"+
"1.0\n"+
"147\n"+
"0.09\n"+
139
661:
662:
663:
664:
665:
666:
667:
668:
669:
670:
671:
672:
673:
674:
675:
676:
677:
678:
679:
680:
681:
682:
683:
684:
685:
686:
687:
688:
689:
690:
691:
692:
693:
694:
695:
696:
697:
698:
699:
700:
701:
702:
703:
704:
705:
706:
707:
708:
709:
710:
711:
712:
713:
714:
715:
716:
717:
718:
719:
720:
721:
722:
723:
724:
725:
726:
" 71\n"+
" 0\n"+
" 72\n"+
" 0\n"+
" 73\n"+
" 0\n"+
" 74\n"+
" 0\n"+
" 75\n"+
" 0\n"+
" 76\n"+
" 0\n"+
" 77\n"+
" 0\n"+
" 78\n"+
" 4\n"+
"170\n"+
" 0\n"+
"171\n"+
" 8\n"+
"172\n"+
" 0\n"+
"173\n"+
" 0\n"+
"174\n"+
" 0\n"+
"175\n"+
" 0\n"+
"176\n"+
" 0\n"+
"177\n"+
" 0\n"+
"178\n"+
" 0\n"+
" 0\n"+
"ENDTAB\n"+
" 0\n"+
"TABLE\n"+
" 2\n"+
"VIEW\n"+
" 70\n"+
" 0\n"+
" 0\n"+
"ENDTAB\n"+
" 0\n"+
"ENDSEC\n"+
" 0\n"+
"SECTION\n"+
" 2\n"+
"BLOCKS\n"+
" 0\n"+
"ENDSEC\n"+
" 0\n"+
"SECTION\n"+
" 2\n"+
"ENTITIES\n"+
" 0\n"+
"LINE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
140
727:
728:
729:
730:
731:
732:
733:
734:
735:
736:
737:
738:
739:
740:
741:
742:
743:
744:
745:
746:
747:
748:
749:
750:
751:
752:
753:
754:
755:
756:
757:
758:
759:
760:
761:
762:
763:
764:
765:
766:
767:
768:
769:
770:
771:
772:
773:
774:
775:
776:
777:
778:
779:
780:
781:
782:
783:
784:
785:
786:
787:
788:
789:
790:
791:
792:
" 10\n"+
c[2][0]+"\n"+
" 20\n"+
c[2][1]+"\n"+
" 30\n"+
"0.0\n"+
" 11\n"+
c[1][0]+"\n"+
" 21\n"+
c[1][1]+"\n"+
" 31\n"+
"0.0\n"+
" 0\n"+
"LINE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
" 10\n"+
c[2][0]+"\n"+
" 20\n"+
c[2][1]+"\n"+
" 30\n"+
"0.0\n"+
" 11\n"+
c[3][0]+"\n"+
" 21\n"+
c[3][1]+"\n"+
" 31\n"+
"0.0\n"+
" 0\n"+
"LINE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
" 10\n"+
c[3][0]+"\n"+
" 20\n"+
c[3][1]+"\n"+
" 30\n"+
"0.0\n"+
" 11\n"+
c[4][0]+"\n"+
" 21\n"+
c[4][1]+"\n"+
" 31\n"+
"0.0\n"+
" 0\n"+
"LINE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
141
793:
794:
795:
796:
797:
798:
799:
800:
801:
802:
803:
804:
805:
806:
807:
808:
809:
810:
811:
812:
813:
814:
815:
816:
817:
818:
819:
820:
821:
822:
823:
824:
825:
826:
827:
828:
829:
830:
831:
832:
833:
834:
835:
836:
837:
838:
839:
840:
841:
842:
843:
844:
845:
846:
847:
848:
849:
850:
851:
852:
853:
854:
855:
856:
857:
858:
" 10\n"+
c[1][0]+"\n"+
" 20\n"+
c[1][1]+"\n"+
" 30\n"+
"0.0\n"+
" 11\n"+
c[4][0]+"\n"+
" 21\n"+
c[4][1]+"\n"+
" 31\n"+
"0.0\n"+
" 0\n"+
"LINE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
" 10\n"+
c[4][0]+"\n"+
" 20\n"+
c[4][1]+"\n"+
" 30\n"+
"0.0\n"+
" 11\n"+
c[5][0]+"\n"+
" 21\n"+
c[5][1]+"\n"+
" 31\n"+
"0.0\n"+
" 0\n"+
"LINE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
" 10\n"+
c[0][0]+"\n"+
" 20\n"+
c[0][1]+"\n"+
" 30\n"+
"0.0\n"+
" 11\n"+
p[0]+"\n"+
" 21\n"+
p[1]+"\n"+
" 31\n"+
"0.0\n"+
" 0\n"+
"CIRCLE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
142
859:
860:
861:
862:
863:
864:
865:
866:
867:
868:
869:
870:
871:
872:
873:
874:
875:
876:
877:
878:
879:
880:
881:
882:
883:
884:
885:
886:
887:
888:
889:
890:
891:
892:
893:
894:
895:
896:
897:
898:
899:
900:
901:
902:
903:
904:
905:
906:
907:
908:
909:
910:
911:
912:
913:
914:
915:
916:
917:
918:
919:
920:
921:
922:
923:
924:
" 10\n"+
c[0][0]+"\n"+
" 20\n"+
c[0][1]+"\n"+
" 30\n"+
"0.0\n"+
" 40\n"+
wr+"\n"+
" 0\n"+
"CIRCLE\n"+
" 8\n"+
"LAYER_0000\n"+
" 6\n"+
"CONTINUOUS\n"+
" 62\n"+
" 1\n"+
" 39\n"+
"5.0\n"+
" 10\n"+
c[5][0]+"\n"+
" 20\n"+
c[5][1]+"\n"+
" 30\n"+
"0.0\n"+
" 40\n"+
wr+"\n"+
" 0\n"+
"ENDSEC\n"+
" 0\n"+
"EOF";
/**Hard coded DXF file with values replaced
*(now AutoCAD 12+ compatible)
*File structure courtesy of CADintosh
*/
String toWrite = temp1+temp2;
new PrintStream(fout).println (toWrite);
fout.close();
}catch (IOException e) {
System.err.println ("Unable to write to file");
System.exit(-1);
}
}
/**Saves spinMod array to specified file
*/
public void save(String path, double[][] values) throws IOException {
try{
String toWrite = ""+values[0][0];
FileOutputStream fout = new FileOutputStream (path);
for(int i=1; i<values.length; i++){
toWrite += "\n"+values[i][0];
}
new PrintStream(fout).println (toWrite);
fout.close();
}catch (IOException e) {
System.err.println ("Unable to write to file");
143
925:
926:
927:
928:
929:
930:
931:
932:
933:
934:
935:
936:
937:
938:
939:
940:
941:
942:
943:
944:
945:
946:
947:
948:
949:
950:
951:
952:
953:
954:
955:
956:
957:
958:
959:
960:
961:
962:
963:
964:
965:
966:
967:
968:
969:
970:
971:
972:
973:
974:
975: }
System.exit(-1);
}
}
/**
*/
public void saveAs(String path, double[][] values) throws IOException {
try{
String toWrite = ""+values[0][0];
FileOutputStream fout = new FileOutputStream (path);
for(int i=1; i<values.length; i++){
toWrite += "\n"+values[i][0];
}
new PrintStream(fout).println(toWrite);
fout.close();
}catch (IOException e) {
System.err.println ("Unable to write to file");
System.exit(-1);
}
}
/** Opens a file and returns a new spinMod array
*/
public double[][] open(String path, double[][] values){
String thisLine = "";
int i=0;
try{
BufferedReader br = new BufferedReader(new FileReader(path));
while ((thisLine = br.readLine()) != null) { // while loop begins here
values[i][0] = Double.parseDouble(thisLine);
//System.out.println(values[i][0]);
i++;
}
}
catch (IOException e){
System.err.println ("Unable to read from file");
System.exit(-1);
}
return values;
}
144
E.6
DXFFileFilter.java
145
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:
/*
* DXFFileFilter.java
*
* Created on April 13, 2006, 1:17 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import java.io.File;
import javax.swing.filechooser.FileFilter;
/**
*
* @author dave
*
*A filter for our FileChooser object. Allows only pivot files
*to be highlighted.
*/
public class DXFFileFilter extends FileFilter {
public boolean accept(File f) {
return f.isDirectory() || f.getName().toLowerCase().endsWith(".dxf");
}
public String getDescription() {
return ".dxf files";
}
}
146
E.7
randomSearch.java
147
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:
/*
* randomSearch.java
*
* Created on March 14, 2006, 4:27 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*
*
*/
package pivot;
import java.awt.*;
import java.util.ArrayList;
/**
*
* @author dave
*
*Refer to literature for definitions of variable names.
*This class takes various arguments and returns the
*most optimal pivot point possible.
*/
public class randomSearch {
/** Creates a new instance of randomSearch */
public randomSearch() {
points = new ArrayList();
points.clear();
}
/**Get our optimal pivot point by using the following methods
*/
public double[] getOptimalPoint(Polygon frame,
double[] hub,
double[] BB,
int t,
int g){
travel = t;
allowedGrowth = g;
double[] point = new double[2];
double[] bestPoint = new double[2];
double score;
double bestScore = 1000000;
for(int i=0; i < 1000; i++) {
point = getPoint(frame.getBounds(), frame);
points.add(point);
score = getOptimalityScore(point, hub, BB);
if(score < bestScore){
bestScore = score;
bestPoint = point;
}
}
return bestPoint;
}
/** Get a random point inside the bike frame.
*/
148
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:
private double[] getPoint(Rectangle bounds,Polygon frame) {
double myPoint[] = new double[2];
do {
myPoint[0] = (int)(Math.random()*bounds.width+bounds.x);
myPoint[1] = (int)(Math.random()*bounds.height+bounds.y);
}while(!frame.contains(myPoint[0],myPoint[1]));
return myPoint;
}
/**Return all the points considered
*/
public ArrayList getPoints(){return points;}
/**Gets the optimality score of any given point.
*/
private double getOptimalityScore(double[] p, double[] hub, double[] BB){
double Tx = getTx(hub,BB);
double Ty = getTy(hub,BB);
double Hw = hub[1];
double Hp = p[1];
double Hc = Hw+Hg;
double a = p[0]-hub[0];
double P = (Tx*Hg)/Hw;
//(Tx*(Hp-Hc)+PHp+Ty*a)/Hf;
double score = (Tx*(Hp-Hc)+(P*Hp)+(Ty*a))/Hf;
double penalty = getPenalty(p,hub,BB); //Get penalty for chain growth.
//remove the sign of the number as we are looking for number close to 0
if(score<0){score=score*-1;}
//Add chaingrowth penalty to score
score += penalty;
return score;
}
/** Should be positive.
*/
private double getTx(double[] hub, double[] BB){
double opposite = hub[1]+Hg-BB[1];
double adjacent = BB[0]-hub[0];
double theta = Math.tan(opposite/adjacent);
return Math.cos(theta)*T;
}
/** Should be more, often than not, negative.
*/
private double getTy(double[] hub, double[] BB){
double opposite = hub[1]+Hg-BB[1];
double adjacent = BB[0]-hub[0];
double theta = Math.tan(opposite/adjacent);
return Math.sin(theta)*T*-1;
}
149
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195: }
/**Get the penalty for our algorithm if chosen point results
*in excessive chain growth.
*/
private double getPenalty(double[] p, double[] hub, double[] BB) {
double sl = getSwingarmLength(hub,p);
double growth = getFinalDistanceFromBB(sl,hub,BB)getInitialDistanceFromBB(hub,BB);
double score = growth*-1; //This is wrong, need to play with it!
if(growth>allowedGrowth){
score = score+100000;
}
return score;
}
/**Gets the swingarm length (for penalty calculation reasons).
*/
private double getSwingarmLength(double[] hub, double[] p) {
//difference in height
double opposite = hub[1]-p[1];
//difference in width
double adjacent = p[0]-hub[0];
return Math.sqrt((opposite*opposite)+(adjacent*adjacent));
}
/**Gets the hub's initial distance from BB (for penalty calculation reasons).
*/
private double getInitialDistanceFromBB(double[] hub, double[] BB) {
//difference in height
double opposite = hub[1]-BB[1];
//difference in width
double adjacent = BB[0]-hub[0];
return Math.sqrt((opposite*opposite)+(adjacent*adjacent));
}
/**Gets the BB distance from hub at full travel (for working out chain growth).
*/
private double getFinalDistanceFromBB(double sl,double[] hub,double[] BB) {
//difference in height
double opposite = hub[1]-BB[1]-travel;
return Math.sqrt((sl*sl)-(opposite*opposite));
}
double Hg = 60; //Cassette Radius
double Hf = 100; //Y distance from shock mount to pivot
double T = 250; //Chain tension
double travel;
double allowedGrowth;
ArrayList points;
150
E.8
SimulatedAnnealing.java
151
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:
/*
* SimulatedAnnealing.java
*
* Created on April 3, 2006, 12:54 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import java.awt.*;
import java.util.ArrayList;
/**
*
* @author dave
*/
/**Pick "n" random points according to "variance"
* Order the points with our score class.
* Pick one of the points according to our temperature.
* Is it our goal state?
* NO? - Plug it back in to the class
* YES? - Stop and return our pivot point.
*/
public class SimulatedAnnealing {
/** Creates a new instance of SimulatedAnnealing */
public SimulatedAnnealing(Polygon frameIn,
int depthIn,
int nIn,
int tempIn,
double[] hubIn,
double[] BBIn,
Graphics2D cIn,
double travelIn,
double growthIn,
boolean varianceOnIn,
boolean hillClimbIn,
double varianceIn) {
frame = frameIn;
maxDepth = depthIn;
n = nIn;
temp = tempIn; //in range 0-100
hub = hubIn;
BB = BBIn;
variance = varianceIn;
c = cIn;
travel = travelIn;
growth = growthIn;
o = new PivotPointOrdering(hub, BB, travel, growth);
hillClimb = hillClimbIn;
varianceOn = varianceOnIn;
}
/**
*/
public double[] search(){
//start our serch here
PivotPoint pp = new PivotPoint(frame);
152
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:
PivotPointOrdering o = new PivotPointOrdering(hub, BB, travel, growth);
double[][] aList = new double[n][2];
double[][] oList = new double[n][2];
points = new ArrayList();
chosen = new ArrayList();
double[] curPivot = pp.altPivot();
do{
//Get n new random points
for(int i=0; i<n; i++){
aList[i] = pp.altPivot(variance, curPivot);
points.add(aList[i]);
}
//Sort and select our point according to our cooling schedule
oList = o.sort(aList);
if(hillClimb){
curPivot = oList[0];
}else{
curPivot = selectPoint(oList, temp);
}
chosen.add(curPivot);
//Cool and reduce variance
if(varianceOn){variance = variance*0.95;}
temp = temp*0.95;
curDepth++;
}while(!isGoalState() && (curDepth != maxDepth));
return oList[0];
}
/**Get the chosen pivot point with which to continue our
*search from our ordering function.
*/
public double getFCost(double[] pointIn){
return o.getFCost(pointIn);
}
/**Return list of considered (for parent class)
*/
public ArrayList getPoints(){return points;}
/**Returns list of points chosen as best (for parent class)
*/
public ArrayList getChosenPoints(){return chosen;}
/**We are not using this as our search is running very quickly
*as it is. Also, any futher optimisation over and above what we
*may define as acceptable can only be a good thing.
*/
private boolean isGoalState() {
return false;
}
153
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
/**Uses temperature to select where to move next*/
private double[] selectPoint(double[][] list, double temperature){
//Our array containing probabilities
double[] probList = new double[list.length];
double[] result = new double[]{0,0};
probList[0] = 100;
//Initialise our array with even probabilities.
for(int i=1; i<list.length;i++){
probList[i] = probList[i-1] - 100/list.length;
}
//Alter these probabilites according to
for(int i=1; i<probList.length; i++){
probList[i] = probList[i]*(temperature/100);
}
//Get our random selection
double rand = Math.random()*100;
//Select our list element to return
for(int i=0; i<probList.length-1; i++){
if((rand<probList[i])&&(rand>probList[i+1])){
result = list[i];
}
}
//Catch the case where we find the worst point
if((result[0] == 0)&&(result[1] == 0)){
result = list[list.length-1];
}
return result;
}
/**Debugging function for printing points considered
*/
private void printList(double[][] pList) {
String temp = "";
for(int i=0; i<pList.length; i++){
temp = temp+",("+pList[i][0]+","+pList[i][1]+")";
}
System.out.println("point:"+temp);
}
int n; //Number of points to choose
double temp;
double variance;
int maxDepth; //Depth to recurse to.
int curDepth = 0; //Current depth of search
ArrayList points;
ArrayList chosen;
PivotPointOrdering o;
double best = 1000000000; //Initial best pivot point score (start it high)
Graphics2D c;
double[] hub;
double[] BB;
Polygon frame;
double travel;
154
199:
double growth;
200:
boolean hillClimb = false;
201:
boolean varianceOn = false;
202:
203: }
155
E.9
PivotPoint.java
156
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:
/*
* PivotPoint.java
*
* Created on April 3, 2006, 7:16 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
import java.awt.*;
/**
*
* @author dave
*
*Returns a new possible pivot point taking into account its
*variance.
*/
public class PivotPoint {
/**
* Creates a new instance of PivotPoint
*/
public PivotPoint(Polygon frameIn) {
frame = frameIn;
}
/**Returns a new random point inside frame (1st time)*/
public double[] altPivot() {
double myPoint[] = new double[2];
do {
myPoint[0] = (int)(Math.random()*frame.getBounds().width+frame.getBounds().x);
myPoint[1] = (int)(Math.random()*frame.getBounds().height+frame.getBounds().y);
}while(!frame.contains(myPoint[0],myPoint[1]));
return myPoint;
}
/**Returns a new random point inside frame (after 1st time)*/
public double[] altPivot(double variance, double[] curPoint) {
double myPoint[] = new double[2];
do {
myPoint[0] = (int)((Math.random()-0.5)*(double)variance+curPoint[0]);
myPoint[1] = (int)((Math.random()-0.5)*(double)variance+curPoint[1]);
}while(!frame.contains(myPoint[0],myPoint[1]));
return myPoint;
}
Polygon frame;
}
157
E.10
PivotPointOrdering.java
158
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:
/*
* PivotPointOrdering.java
*
* Created on April 3, 2006, 9:09 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package pivot;
/**
*
* @author dave
*
*Sorts the possible pivot point array into an order according
*to its values optimality. Note, we are using quick sort for
*speed purposes.
*/
public class PivotPointOrdering {
/** Creates a new instance of PivotPointOrdering
*/
public PivotPointOrdering(double[] hubIn, double[] BBIn, double travelIn, double growthIn) {
hub = hubIn;
BB = BBIn;
travel = travelIn;
allowedGrowth = growthIn;
}
/**A recursive array quicksort sorting mechanism
*/
public double[][] sort(double[][] a) {
sort(a, 0, a.length - 1);
return a;
}
/**A recursive array quicksort sorting mechanism
*/
public double[][] sort(double[][] a, int left, int right) {
if (right <= left) return a; //was just return; before
int i = partition(a, left, right);
sort(a, left, i-1);
sort(a, i+1, right);
return a;
}
/**
*/
private int partition(double[][] a, int left, int right) {
//System.out.println("In partition");
int i = left - 1;
int j = right;
while(true) {
while (less(a[++i], a[right]));
// find item on left to swap
while (less(a[right], a[--j]))
// find item on right to swap
if (j == left) break;
// don't go out-of-bounds
if (i >= j) break;
// check if pointers cross
159
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:
exch(a, i, j);
}
exch(a, i, right);
return i;
// swap two elements into place
// swap with partition element
}
/** is x < y ?
*/
private boolean less(double[] x, double[] y) {
//System.out.println("In less");
return (getFCost(x) < getFCost(y));
}
/** exchange a[i] and a[j]
*/
private void exch(double[][] a, int i, int j) {
//System.out.println("In exch");
double[] swap = a[i];
a[i] = a[j];
a[j] = swap;
}
/**Get the cost of
*/
public double getFCost(double[] p){
double Tx = getTx();
double Ty = getTy();
double Hw = hub[1];
double Hp = p[1];
double Hc = Hw+Hg;
double a = p[0]-hub[0];
double P = (Tx*Hg)/Hw;
//(Tx*(Hp-Hc)+PHp+Ty*a)/Hf;
double score = (Tx*(Hp-Hc)+(P*Hp)+(Ty*a))/Hf;
double penalty = getPenalty(p); //Get penalty for chain growth.
//remove the sign of the number
if(score<0){score=score*-1;}
score += penalty;
return score;
}
/** X component of chain tension (Should be positive)
*/
private double getTx(){
double opposite = hub[1]+Hg-BB[1];
double adjacent = BB[0]-hub[0];
double theta = Math.tan(opposite/adjacent);
return Math.cos(theta)*T;
}
/** Y component of chain tension (Should be more,
* often than not, negative).
160
133:
*/
134:
private double getTy(){
135:
double opposite = hub[1]+Hg-BB[1];
136:
double adjacent = BB[0]-hub[0];
137:
double theta = Math.tan(opposite/adjacent);
138:
139:
return Math.sin(theta)*T*-1;
140:
}
141:
142:
143:
144:
/**Get penalty incurred if we go over recommended chain growth.
145:
*/
146:
private double getPenalty(double[] p) {
147:
double sl = getSwingarmLength(p);
148:
double growth = getFinalDistanceFromBB(sl)-getInitialDistanceFromBB();
149:
double score = growth*-1; //Promotes chain slack
150:
151:
if(growth>allowedGrowth){
152:
score = score+10000;
153:
}
154:
155:
return score;
156:
}
157:
158:
159:
160:
/**Get the length of the swingarm
161:
*/
162:
private double getSwingarmLength(double[] p) {
163:
//difference in height
164:
double opposite = hub[1]-p[1];
165:
//difference in width
166:
double adjacent = p[0]-hub[0];
167:
168:
return Math.sqrt((opposite*opposite)+(adjacent*adjacent));
169:
}
170:
171:
172:
173:
/**Get hub's initial distance from BB
174:
*/
175:
private double getInitialDistanceFromBB() {
176:
//difference in height
177:
double opposite = hub[1]-BB[1];
178:
//difference in width
179:
double adjacent = BB[0]-hub[0];
180:
181:
return Math.sqrt((opposite*opposite)+(adjacent*adjacent));
182:
}
183:
184:
185:
186:
/**Gwt hubs final distance from BB
187:
*/
188:
private double getFinalDistanceFromBB(double sl) {
189:
//difference in height
190:
double opposite = hub[1]-BB[1]-travel;
191:
192:
return Math.sqrt((sl*sl)-(opposite*opposite));
193:
}
194:
195:
196:
197: double Hg = 60; //Cassette Radius
198: double Hf = 100; //Y distance from shock mount to pivot
161
199:
200:
201:
202:
203:
204:
205:
double T = 250; //Chain tension
double travel;
double allowedGrowth;
double[] hub;
double[] BB;
}
162