@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="CompilerConfiguration"> | |||
<bytecodeTargetLevel target="1.8" /> | |||
</component> | |||
</project> |
@@ -1,6 +1,9 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | |||
<component name="JavadocGenerationManager"> | |||
<option name="OUTPUT_DIRECTORY" value="$USER_HOME$/Desktop/TrafficSimDoc" /> | |||
</component> | |||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | |||
<output url="file://$PROJECT_DIR$/build/classes" /> | |||
</component> | |||
<component name="ProjectType"> | |||
@@ -1,26 +0,0 @@ | |||
package de.hems.trafficsim; | |||
import android.content.Context; | |||
import androidx.test.platform.app.InstrumentationRegistry; | |||
import androidx.test.ext.junit.runners.AndroidJUnit4; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import static org.junit.Assert.*; | |||
/** | |||
* Instrumented test, which will execute on an Android device. | |||
* | |||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |||
*/ | |||
@RunWith(AndroidJUnit4.class) | |||
public class ExampleInstrumentedTest { | |||
@Test | |||
public void useAppContext() { | |||
// Context of the app under test. | |||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); | |||
assertEquals("de.hems.trafficsim", appContext.getPackageName()); | |||
} | |||
} |
@@ -1,7 +1,5 @@ | |||
package de.hems.trafficsim; | |||
import androidx.appcompat.app.AppCompatActivity; | |||
import android.os.Bundle; | |||
import android.view.View; | |||
import android.widget.Button; | |||
@@ -9,79 +7,143 @@ import android.widget.LinearLayout; | |||
import android.widget.SeekBar; | |||
import android.widget.TextView; | |||
import java.util.Observable; | |||
import java.util.Observer; | |||
import androidx.appcompat.app.AppCompatActivity; | |||
/** | |||
* Main user interface class, containing all necessary gui elements and their control flow. | |||
*/ | |||
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener { | |||
/** | |||
* default value of the number of vehicles on the track | |||
*/ | |||
public static final int defaultNoOfVehicles = 25; | |||
/** | |||
* default value of the length of the track | |||
*/ | |||
public static final int defaultTrackLength = 100; | |||
/** | |||
* default value of brake probability | |||
*/ | |||
public static final float defaultBrakeProb = 0.3f; | |||
/** | |||
* default value of the maximum velocity of the vehicles | |||
*/ | |||
public static final float defaultMaxVelocity = 5.0f; | |||
/** | |||
* default value of the delay between two simulation steps | |||
*/ | |||
public static final int defaultDelay = 0; | |||
/** | |||
* default value of the number of vehicles on the track | |||
*/ | |||
public static final int defaultHistoryLength = 50; | |||
/** | |||
* default value of the number of vehicles on the track | |||
*/ | |||
public static final int defaultFrameskip = 0; | |||
/** | |||
* the track to show in the activity | |||
*/ | |||
protected Track track; | |||
/** | |||
* the surface view on which the renderer draws the track history | |||
*/ | |||
protected TimeRecordView trackView; | |||
/** | |||
* the thread which runs the simulation and visualization | |||
*/ | |||
protected Worker worker; | |||
/** | |||
* the renderer instance drawing the track history | |||
*/ | |||
protected Renderer renderer; | |||
/** | |||
* the layout which keeps the surface view | |||
*/ | |||
protected LinearLayout viewStack; | |||
/** | |||
* Utility function to round a float to a given amount of digits. | |||
* | |||
* @param number number to round | |||
* @param digits amount of digits | |||
* @return rounded number | |||
*/ | |||
public static float round(float number, int digits) { | |||
float div = (float) Math.pow(10.0f, digits); | |||
return Math.round(number * div) / div; | |||
} | |||
/** | |||
* Constructor for MainActivity | |||
* | |||
* @param savedInstanceState Bundle with previously saved activity state, otherwise null | |||
*/ | |||
@Override | |||
protected void onCreate(Bundle savedInstanceState) { | |||
super.onCreate(savedInstanceState); | |||
setContentView(R.layout.activity_main); | |||
this.track = new Track(defaultNoOfVehicles, defaultTrackLength, defaultBrakeProb, | |||
defaultMaxVelocity, defaultDelay, defaultHistoryLength); | |||
this.viewStack = (LinearLayout) findViewById(R.id.trackViewStack); | |||
this.viewStack = findViewById(R.id.trackViewStack); | |||
this.trackView = new TimeRecordView(this, track); | |||
viewStack.addView(this.trackView); | |||
this.renderer = new Renderer(track, this.trackView.getHolder()); | |||
SeekBar trackLengthSeekBar = (SeekBar) findViewById(R.id.trackLengthSeekBar); | |||
SeekBar trackLengthSeekBar = findViewById(R.id.trackLengthSeekBar); | |||
trackLengthSeekBar.setOnSeekBarChangeListener(this); | |||
trackLengthSeekBar.setProgress(defaultTrackLength); | |||
((TextView)(findViewById(R.id.trackLengthTextView))).setText(String.valueOf(defaultTrackLength)); | |||
SeekBar maxVelocitySeekBar = (SeekBar) findViewById(R.id.maxVelocitySeekBar); | |||
SeekBar maxVelocitySeekBar = findViewById(R.id.maxVelocitySeekBar); | |||
maxVelocitySeekBar.setOnSeekBarChangeListener(this); | |||
maxVelocitySeekBar.setProgress((int)defaultMaxVelocity); | |||
((TextView)(findViewById(R.id.maxVeloTextView))).setText(String.valueOf((int)defaultMaxVelocity)); | |||
SeekBar noOfVehiclesSeekBar = (SeekBar) findViewById(R.id.noOfVehiclesSeekBar); | |||
SeekBar noOfVehiclesSeekBar = findViewById(R.id.noOfVehiclesSeekBar); | |||
noOfVehiclesSeekBar.setOnSeekBarChangeListener(this); | |||
noOfVehiclesSeekBar.setProgress(defaultNoOfVehicles); | |||
((TextView)(findViewById(R.id.noOfVehiclesTextView))).setText(String.valueOf(defaultNoOfVehicles)); | |||
SeekBar brakeProbabilitySeekBar = (SeekBar) findViewById(R.id.brakeProbabilitySeekBar); | |||
SeekBar brakeProbabilitySeekBar = findViewById(R.id.brakeProbabilitySeekBar); | |||
brakeProbabilitySeekBar.setOnSeekBarChangeListener(this); | |||
brakeProbabilitySeekBar.setProgress((int)(defaultBrakeProb*20)); | |||
((TextView)(findViewById(R.id.brakeProbTextView))).setText(String.valueOf(defaultBrakeProb)); | |||
SeekBar delaySeekBar = (SeekBar)(findViewById(R.id.simDelaySeekBar)); | |||
SeekBar delaySeekBar = findViewById(R.id.simDelaySeekBar); | |||
delaySeekBar.setOnSeekBarChangeListener(this); | |||
delaySeekBar.setProgress(defaultDelay); | |||
((TextView)(findViewById(R.id.simDelayTextView))).setText(String.valueOf(defaultDelay)); | |||
((TextView) (findViewById(R.id.simDelayTextView))).setText(String.valueOf(defaultDelay)); | |||
SeekBar frameskipSeekBar = (SeekBar)(findViewById(R.id.frameskipSeekBar)); | |||
SeekBar frameskipSeekBar = findViewById(R.id.frameskipSeekBar); | |||
frameskipSeekBar.setOnSeekBarChangeListener(this); | |||
frameskipSeekBar.setProgress(defaultFrameskip); | |||
((TextView)(findViewById(R.id.frameSkipTextView))).setText(String.valueOf(defaultFrameskip)); | |||
((TextView) (findViewById(R.id.frameSkipTextView))).setText(String.valueOf(defaultFrameskip)); | |||
this.updateStats(); | |||
} | |||
public static float round(float number, int digits) { | |||
float div = (float) Math.pow(10.0f, digits); | |||
return Math.round(number*div)/div; | |||
} | |||
/** | |||
* Updates the statistics view. | |||
*/ | |||
public void updateStats() { | |||
final Track trackRef = this.track; | |||
final TextView lastAvgView = (TextView) findViewById(R.id.avgVeloLastView); | |||
final TextView overallAvgView = (TextView) findViewById(R.id.avgVeloOverallView); | |||
final TextView delayedAvgView = (TextView) findViewById(R.id.delayedAvgTextView); | |||
final TextView stepsView = (TextView) findViewById(R.id.stepsTextView); | |||
final TextView lastAvgView = findViewById(R.id.avgVeloLastView); | |||
final TextView overallAvgView = findViewById(R.id.avgVeloOverallView); | |||
final TextView delayedAvgView = findViewById(R.id.delayedAvgTextView); | |||
final TextView stepsView = findViewById(R.id.stepsTextView); | |||
runOnUiThread(new Runnable() { | |||
@Override | |||
public void run() { | |||
@@ -93,46 +155,59 @@ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBar | |||
}); | |||
} | |||
/** | |||
* Handler function for clicks on the "Step" button. | |||
* | |||
* @param view the view the event is generated from | |||
*/ | |||
public void onStepButtonClick(View view) { | |||
this.track.timeElapse(); | |||
this.updateStats(); | |||
this.renderer.draw(); | |||
} | |||
/** | |||
* Handler function for clicks on the "Play" button. | |||
* | |||
* @param view the view the event is generated from | |||
*/ | |||
public void onPlayButtonClick(View view) { | |||
Button playButton = (Button) findViewById(R.id.playButton); | |||
Button playButton = findViewById(R.id.playButton); | |||
playButton.setEnabled(false); | |||
Button stepButton = (Button) findViewById(R.id.stepButton); | |||
Button stepButton = findViewById(R.id.stepButton); | |||
stepButton.setEnabled(false); | |||
Button stopButton = (Button) findViewById(R.id.stopButton); | |||
Button stopButton = findViewById(R.id.stopButton); | |||
stopButton.setEnabled(true); | |||
Button clearButton = (Button) findViewById(R.id.clearButton); | |||
Button clearButton = findViewById(R.id.clearButton); | |||
clearButton.setEnabled(false); | |||
int frameskip = ((SeekBar)(findViewById(R.id.frameskipSeekBar))).getProgress(); | |||
int frameskip = ((SeekBar) (findViewById(R.id.frameskipSeekBar))).getProgress(); | |||
this.worker = new Worker(track, this, renderer, frameskip); | |||
this.worker.start(); | |||
} | |||
/** | |||
* Handler function for clicks on the "Stop" button. | |||
* | |||
* @param view the view the event is generated from | |||
*/ | |||
public void onStopButtonClick(View view) { | |||
Button playButton = (Button) findViewById(R.id.playButton); | |||
Button playButton = findViewById(R.id.playButton); | |||
playButton.setEnabled(true); | |||
Button stepButton = (Button) findViewById(R.id.stepButton); | |||
Button stepButton = findViewById(R.id.stepButton); | |||
stepButton.setEnabled(true); | |||
Button stopButton = (Button) findViewById(R.id.stopButton); | |||
Button stopButton = findViewById(R.id.stopButton); | |||
stopButton.setEnabled(false); | |||
Button clearButton = (Button) findViewById(R.id.clearButton); | |||
Button clearButton = findViewById(R.id.clearButton); | |||
clearButton.setEnabled(true); | |||
this.worker.setStop(true); | |||
try { | |||
this.worker.join(); | |||
} catch (InterruptedException ex) { | |||
} | |||
this.stopWorker(); | |||
this.worker = null; | |||
} | |||
/** | |||
* Stops the current worker thread. | |||
*/ | |||
protected void stopWorker() { | |||
this.worker.setStop(true); | |||
try { | |||
@@ -142,15 +217,20 @@ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBar | |||
this.worker = null; | |||
} | |||
/** | |||
* Handler function for clicks on the "Stop" button. | |||
* | |||
* @param view the view the event is generated from | |||
*/ | |||
public void onClearButtonClick(View view) { | |||
if (this.worker != null) { // There was a simulation running | |||
this.stopWorker(); | |||
} | |||
Button playButton = (Button) findViewById(R.id.playButton); | |||
Button playButton = findViewById(R.id.playButton); | |||
playButton.setEnabled(true); | |||
Button stepButton = (Button) findViewById(R.id.stepButton); | |||
Button stepButton = findViewById(R.id.stepButton); | |||
stepButton.setEnabled(true); | |||
Button stopButton = (Button) findViewById(R.id.stopButton); | |||
Button stopButton = findViewById(R.id.stopButton); | |||
stopButton.setEnabled(false); | |||
this.updateTrack(); | |||
this.updateStats(); | |||
@@ -158,24 +238,28 @@ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBar | |||
} | |||
/** | |||
* Creates a new track with the current settings. This methods restarts the simulation it | |||
* it was running before. | |||
*/ | |||
protected void updateTrack() { | |||
int newTrackLength = ((SeekBar)(findViewById(R.id.trackLengthSeekBar))).getProgress(); | |||
SeekBar noOfVehiclesSeekBar = (SeekBar)(findViewById(R.id.noOfVehiclesSeekBar)); | |||
int newTrackLength = ((SeekBar) (findViewById(R.id.trackLengthSeekBar))).getProgress(); | |||
SeekBar noOfVehiclesSeekBar = findViewById(R.id.noOfVehiclesSeekBar); | |||
int newNoOfVehicles = noOfVehiclesSeekBar.getProgress(); | |||
if (newTrackLength < newNoOfVehicles) { // Dont allow values greater than track length! | |||
newNoOfVehicles = (int) newTrackLength; | |||
newNoOfVehicles = newTrackLength; | |||
noOfVehiclesSeekBar.setProgress(newNoOfVehicles); | |||
} | |||
TextView noOfVehiclesTextView = (TextView)(findViewById(R.id.noOfVehiclesTextView)); | |||
TextView noOfVehiclesTextView = findViewById(R.id.noOfVehiclesTextView); | |||
noOfVehiclesTextView.setText(String.valueOf(newNoOfVehicles)); | |||
TextView trackLengthTextView = (TextView)(findViewById(R.id.trackLengthTextView)); | |||
TextView trackLengthTextView = findViewById(R.id.trackLengthTextView); | |||
trackLengthTextView.setText(String.valueOf(newTrackLength)); | |||
float newMaxVelocity = ((SeekBar) findViewById(R.id.maxVelocitySeekBar)).getProgress(); | |||
SeekBar brakeProbabilitySeekBar = (SeekBar) findViewById(R.id.brakeProbabilitySeekBar); | |||
float newBrakeProb = (float)brakeProbabilitySeekBar.getProgress() | |||
SeekBar brakeProbabilitySeekBar = findViewById(R.id.brakeProbabilitySeekBar); | |||
float newBrakeProb = (float) brakeProbabilitySeekBar.getProgress() | |||
/ (float)brakeProbabilitySeekBar.getMax(); | |||
int newDelay = ((SeekBar)(findViewById(R.id.simDelaySeekBar))).getProgress(); | |||
@@ -186,7 +270,8 @@ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBar | |||
this.worker.setStop(true); | |||
try { | |||
this.worker.join(); | |||
} catch (InterruptedException ex) { } | |||
} catch (InterruptedException ex) { | |||
} | |||
} | |||
this.track = new Track(newNoOfVehicles, newTrackLength, newBrakeProb, newMaxVelocity, | |||
@@ -199,27 +284,34 @@ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBar | |||
} | |||
} | |||
/** | |||
* Handler function for changes on the seek bars. | |||
* | |||
* @param seekBar the seek bar changed | |||
* @param progress the new progress value of the seek bar | |||
* @param fromUser flag if the event is the result of an user action | |||
*/ | |||
@Override | |||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { | |||
if (seekBar == (SeekBar)(findViewById(R.id.noOfVehiclesSeekBar))) { | |||
if (seekBar == findViewById(R.id.noOfVehiclesSeekBar)) { | |||
this.updateTrack(); | |||
} else if (seekBar == (SeekBar)(findViewById(R.id.brakeProbabilitySeekBar))) { | |||
float newBrakeProb = (float)seekBar.getProgress() / (float)seekBar.getMax(); | |||
} else if (seekBar == findViewById(R.id.brakeProbabilitySeekBar)) { | |||
float newBrakeProb = (float) seekBar.getProgress() / (float) seekBar.getMax(); | |||
this.track.setBrakeProb(newBrakeProb); | |||
TextView newBrakeProbTextView = (TextView)(findViewById(R.id.brakeProbTextView)); | |||
TextView newBrakeProbTextView = findViewById(R.id.brakeProbTextView); | |||
newBrakeProbTextView.setText(String.valueOf((newBrakeProb))); | |||
} else if (seekBar == (SeekBar)(findViewById(R.id.maxVelocitySeekBar))) { | |||
} else if (seekBar == findViewById(R.id.maxVelocitySeekBar)) { | |||
this.track.setMaxVelocity(seekBar.getProgress()); | |||
TextView tv = (TextView)(findViewById(R.id.maxVeloTextView)); | |||
TextView tv = findViewById(R.id.maxVeloTextView); | |||
tv.setText(String.valueOf(progress)); | |||
} else if (seekBar == (SeekBar)(findViewById(R.id.trackLengthSeekBar))) { | |||
} else if (seekBar == findViewById(R.id.trackLengthSeekBar)) { | |||
this.updateTrack(); | |||
} else if (seekBar == (SeekBar)(findViewById(R.id.simDelaySeekBar))) { | |||
TextView tv = (TextView)(findViewById(R.id.simDelayTextView)); | |||
} else if (seekBar == findViewById(R.id.simDelaySeekBar)) { | |||
TextView tv = findViewById(R.id.simDelayTextView); | |||
tv.setText(String.valueOf(seekBar.getProgress())); | |||
this.track.setWaitTime(seekBar.getProgress()); | |||
} else if (seekBar == (SeekBar)(findViewById(R.id.frameskipSeekBar))) { | |||
TextView tv = (TextView)(findViewById(R.id.frameSkipTextView)); | |||
} else if (seekBar == findViewById(R.id.frameskipSeekBar)) { | |||
TextView tv = findViewById(R.id.frameSkipTextView); | |||
tv.setText(String.valueOf(seekBar.getProgress())); | |||
if (this.worker != null) { | |||
this.worker.setFrameskip(seekBar.getProgress()); | |||
@@ -227,11 +319,21 @@ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBar | |||
} | |||
} | |||
/** | |||
* Handler function for beginning touch events on a seek bar. | |||
* | |||
* @param seekBar the seek bar touched | |||
*/ | |||
@Override | |||
public void onStartTrackingTouch(SeekBar seekBar) { | |||
} | |||
/** | |||
* Handler function for ending touch events on a seek bar. | |||
* | |||
* @param seekBar the seek bar touched | |||
*/ | |||
@Override | |||
public void onStopTrackingTouch(SeekBar seekBar) { | |||
@@ -9,25 +9,73 @@ import android.view.SurfaceHolder; | |||
import java.util.ConcurrentModificationException; | |||
import java.util.List; | |||
/** | |||
* User interface class rendering the track history on a SurfaceView. | |||
*/ | |||
public class Renderer { | |||
private List<VehicleTimeRecord> stepRecords; | |||
/** | |||
* the track to render | |||
*/ | |||
protected Track track; | |||
/** | |||
* width of a rectangle representing one vehicle | |||
*/ | |||
protected int pixelPerVehicle; | |||
/** | |||
* height of a rectangle representing one vehicle | |||
*/ | |||
protected int pixelPerLine; | |||
/** | |||
* amount of pixels per track position which are lost by rounding | |||
*/ | |||
protected float tooShortPerTrackLength; | |||
/** | |||
* amount of pixels per height which are lost by rounding | |||
*/ | |||
protected float tooShortPerHeight; | |||
/** | |||
* Paint instance of the renderer | |||
*/ | |||
protected Paint paint; | |||
/** | |||
* holder of the surface the renderer draws to | |||
*/ | |||
protected SurfaceHolder holder; | |||
protected Canvas canvas; | |||
/** | |||
* width of the surface to draw to | |||
*/ | |||
protected int width; | |||
/** | |||
* height of the surface to draw to | |||
*/ | |||
protected int height; | |||
/** | |||
* temporary reference to the time records of one simulation step | |||
*/ | |||
private List<VehicleTimeRecord> stepRecords; | |||
/** | |||
* temporary reference to the canvas to draw to | |||
*/ | |||
private Canvas canvas; | |||
/** | |||
* Constuctor for a Renderer. | |||
* | |||
* @param track the track to render | |||
* @param holder the holder of the surface to draw to | |||
*/ | |||
public Renderer(Track track, SurfaceHolder holder) { | |||
this.track = track; | |||
this.holder = holder; | |||
this.paint = new Paint(); | |||
} | |||
/** | |||
* Updates the dimension information of the renderer. | |||
* | |||
* @param width the width of the SurfaceView | |||
* @param height the height of the SurfaceView | |||
*/ | |||
public void setSize(int width, int height) { | |||
if (width > 0 && height > 0) { | |||
this.pixelPerVehicle = width / (int) this.track.getTrackLength(); | |||
@@ -36,34 +84,50 @@ public class Renderer { | |||
/ this.track.getTrackLength(); | |||
this.pixelPerLine = height / this.track.getHistoryLength(); | |||
this.tooShortPerHeight = (height - this.pixelPerLine * this.track.getHistoryLength()) | |||
/ (float)height; | |||
System.out.println("Viewport: "+width+"x"+height); | |||
/ (float) height; | |||
System.out.println("Viewport: " + width + "x" + height); | |||
this.width = width; | |||
this.height = height; | |||
} | |||
} | |||
/** | |||
* Updates the track of the Surface. | |||
* | |||
* @param track the new track to render | |||
*/ | |||
public void setTrack(Track track) { | |||
this.track = track; | |||
this.setSize(width, height); | |||
} | |||
/** | |||
* Utility function which calculates a color from the relation between the current speed of a | |||
* vehicle and it's maximum speed (from red over yellow to green). | |||
* | |||
* @param curVelocity current velocity of the vehicle | |||
* @param maxVelocity maximum velocity of the vehilce | |||
* @return color encoded as Android color integer | |||
*/ | |||
protected int getColor(float curVelocity, float maxVelocity) { | |||
float perc = curVelocity / maxVelocity; | |||
perc = 1 - perc; | |||
if (perc <= 0.5) { | |||
int red = ((int) (2*perc*0xFF)) << 16; | |||
int red = ((int) (2 * perc * 0xFF)) << 16; | |||
int green = 0xFF << 8; | |||
int blue = 0; | |||
return 0xff000000 | red | green | blue; | |||
} else { | |||
int red = 0xFF << 16; | |||
int green = ((int)(0xFF-0xFF*(perc-0.5)*2)) << 8; | |||
int green = ((int) (0xFF - 0xFF * (perc - 0.5) * 2)) << 8; | |||
int blue = 0; | |||
return 0xff000000 | red | green | blue; | |||
} | |||
} | |||
/** | |||
* Draws the current state of the track history to the Surface. | |||
*/ | |||
protected void draw() { | |||
try { | |||
if (this.pixelPerVehicle == 0) { | |||
@@ -79,7 +143,7 @@ public class Renderer { | |||
int vCompensateStart = 0; | |||
int vCompensateEnd = 0; | |||
canvas.drawColor(Color.BLACK); | |||
for (int curStepIdx = this.track.getVtrList().size()-1;curStepIdx >= 0; | |||
for (int curStepIdx = this.track.getVtrList().size()-1; curStepIdx >= 0; | |||
curStepIdx--) { | |||
try { | |||
try { | |||
@@ -1,24 +1,26 @@ | |||
package de.hems.trafficsim; | |||
import android.app.ActionBar; | |||
import android.content.Context; | |||
import android.graphics.Canvas; | |||
import android.graphics.Color; | |||
import android.graphics.Paint; | |||
import android.text.Layout; | |||
import android.view.SurfaceView; | |||
import android.view.View; | |||
import android.view.ViewGroup; | |||
import java.util.ConcurrentModificationException; | |||
import java.util.List; | |||
/** | |||
* A Surface View which servers as a stage for a renderer. | |||
*/ | |||
public class TimeRecordView extends SurfaceView { | |||
public TimeRecordView(Context context, Track track) { | |||
super(context); | |||
this.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); | |||
} | |||
/** | |||
* Updates the size information of the SurfaceView, used by the Android SDK. | |||
* | |||
* @param w new width | |||
* @param h new height | |||
* @param oldw old width | |||
* @param oldh old height | |||
*/ | |||
@Override | |||
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |||
super.onSizeChanged(w, h, oldw, oldh); | |||
@@ -5,42 +5,92 @@ import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Observable; | |||
import java.util.concurrent.Semaphore; | |||
import java.util.concurrent.locks.LockSupport; | |||
/** | |||
* Main model class of TrafficSim. Represents a round course containing vehicles. | |||
*/ | |||
public class Track extends Observable { | |||
/** | |||
* list a vehicles on the track | |||
*/ | |||
protected List<Vehicle> vehicles; | |||
/** | |||
* list of resulting time records of the simulation | |||
*/ | |||
protected List<List<VehicleTimeRecord>> vtrList; | |||
/** | |||
* length of the track | |||
*/ | |||
protected float trackLength; | |||
/** | |||
* sum of all vehicle speeds during the simulation, used for average calculation | |||
*/ | |||
protected float sumAvgMemory; | |||
/** | |||
* sum of all vehicle speeds during the simulation ignoring the first ten steps, | |||
* used for average calculation | |||
*/ | |||
protected float sumDelAvgMemory; | |||
/** | |||
* length of the history kept | |||
*/ | |||
protected int historyLength; | |||
/** | |||
* average over all velocities in the simulation | |||
*/ | |||
protected float overallAvg; | |||
/** | |||
* average over all velocities in the simulation, ignoring the first ten steps | |||
*/ | |||
protected float delayedAvg; | |||
/** | |||
* average over all velocities in the last step | |||
*/ | |||
protected float lastAvg; | |||
/** | |||
* current configured wait time between two simulation steps | |||
*/ | |||
protected int waitTime; | |||
/** | |||
* currently configured max velocity for all vehicles | |||
*/ | |||
protected float maxVelocity; | |||
/** | |||
* currently configured brake probability for all vehicles | |||
*/ | |||
protected float brakeProb; | |||
/** | |||
* counter for executed simulation steps | |||
*/ | |||
protected long steps; | |||
protected Semaphore listSemaphore; | |||
public List<List<VehicleTimeRecord>> getVtrList() { return vtrList; } | |||
public float getOverallAvg() { | |||
return overallAvg; | |||
} | |||
public float getLastAvg() { | |||
return lastAvg; | |||
} | |||
public float getDelayedAvg() { return delayedAvg; } | |||
public List<Vehicle> getVehicles() { | |||
return vehicles; | |||
} | |||
public float getTrackLength() { | |||
return trackLength; | |||
} | |||
public long getSteps() { return steps; } | |||
public Semaphore getListSemaphore() { return listSemaphore; } | |||
public int getHistoryLength() { return historyLength; } | |||
/** | |||
* semaphore protecting the vtrlist | |||
*/ | |||
protected Semaphore listSemaphore; | |||
/** | |||
* Constructor for a new Track. | |||
* | |||
* @param numberVehicles number of vehicles on the Track | |||
* @param trackLength length of the new Track | |||
* @param brakeProb probability of a vehicle to suddenly brake without reason | |||
* @param maxVelocity maxmimum velocity of the vehicles | |||
* @param waitTime time between two simulation steps to slow down the simulation artificially | |||
* @param historyLength length of the history to keep | |||
*/ | |||
public Track(int numberVehicles, float trackLength, float brakeProb, float maxVelocity, int waitTime, int historyLength) { | |||
this.trackLength = trackLength; | |||
this.brakeProb = brakeProb; | |||
@@ -59,9 +109,96 @@ public class Track extends Observable { | |||
} | |||
protected List<Vehicle> createVehiclesList(int numberVehicles){ | |||
/** | |||
* Getter for vtrList | |||
* | |||
* @return vtrList | |||
*/ | |||
public List<List<VehicleTimeRecord>> getVtrList() { | |||
return vtrList; | |||
} | |||
/** | |||
* Getter for overallAvg | |||
* | |||
* @return overallAvg | |||
*/ | |||
public float getOverallAvg() { | |||
return overallAvg; | |||
} | |||
/** | |||
* Getter for lastAvg | |||
* | |||
* @return lastAvg | |||
*/ | |||
public float getLastAvg() { | |||
return lastAvg; | |||
} | |||
/** | |||
* Getter for delayedAvg | |||
* | |||
* @return delayedAvg | |||
*/ | |||
public float getDelayedAvg() { | |||
return delayedAvg; | |||
} | |||
/** | |||
* Getter for vehicles | |||
* | |||
* @return vehicles | |||
*/ | |||
public List<Vehicle> getVehicles() { | |||
return vehicles; | |||
} | |||
/** | |||
* Getter for trackLength | |||
* | |||
* @return trackLength | |||
*/ | |||
public float getTrackLength() { | |||
return trackLength; | |||
} | |||
/** | |||
* Getter for steps | |||
* | |||
* @return steps | |||
*/ | |||
public long getSteps() { | |||
return steps; | |||
} | |||
/** | |||
* Getter for listSemaphore | |||
* | |||
* @return listSemaphore | |||
*/ | |||
public Semaphore getListSemaphore() { | |||
return listSemaphore; | |||
} | |||
/** | |||
* Getter for historyLength | |||
* | |||
* @return historyLength | |||
*/ | |||
public int getHistoryLength() { | |||
return historyLength; | |||
} | |||
/** | |||
* Utility function to add vehicles to the Track. | |||
* | |||
* @param numberVehicles number of vehicles to add | |||
* @return filled list with vehicles | |||
*/ | |||
protected List<Vehicle> createVehiclesList(int numberVehicles) { | |||
List<Vehicle> result = new ArrayList<>(); | |||
for(int i=0;i<numberVehicles;i++){ | |||
for (int i = 0; i < numberVehicles; i++) { | |||
Vehicle vehicle = new Vehicle(i, i, this.maxVelocity, this.brakeProb, this.trackLength); | |||
result.add(vehicle); | |||
} | |||
@@ -69,24 +206,42 @@ public class Track extends Observable { | |||
return result; | |||
} | |||
/** | |||
* Update the wait time of the simulation. | |||
* | |||
* @param waitTime new wait time in ms | |||
*/ | |||
public void setWaitTime(int waitTime) { | |||
this.waitTime = waitTime; | |||
} | |||
/** | |||
* Update the brake probability of all vehicles. | |||
* | |||
* @param brakeProb new brake probability | |||
*/ | |||
public void setBrakeProb(float brakeProb) { | |||
for (Vehicle v : this.vehicles) { | |||
v.setBrakeProb(brakeProb); | |||
} | |||
} | |||
/** | |||
* Update the maximum velocity of all vehicles. | |||
* | |||
* @param maxVelocity new maximum velocity | |||
*/ | |||
public void setMaxVelocity(float maxVelocity) { | |||
for (Vehicle v : this.vehicles) { | |||
v.setMaxVelocity(maxVelocity); | |||
} | |||
} | |||
/** | |||
* Calculates on simulation step ahead and then waits for the configured wait time. | |||
*/ | |||
public void timeElapse() { | |||
for(int i=0; i<vehicles.size();i++) { | |||
for (int i = 0; i < vehicles.size(); i++) { | |||
Vehicle v = vehicles.get(i); | |||
int forerunnerIndex = i + 1; | |||
if (forerunnerIndex >= vehicles.size()) { | |||
@@ -94,7 +249,7 @@ public class Track extends Observable { | |||
} | |||
Vehicle forerunner = vehicles.get(forerunnerIndex); | |||
float distanceForerunner = forerunner.getPosition() - v.getPosition() - 1; | |||
if(distanceForerunner < 0.0){ | |||
if (distanceForerunner < 0.0) { | |||
distanceForerunner += this.trackLength; | |||
} | |||
v.updateVelocity(distanceForerunner); | |||
@@ -117,25 +272,32 @@ public class Track extends Observable { | |||
this.listSemaphore.release(); | |||
update_avg(); | |||
this.setChanged(); | |||
//this.notifyObservers(); | |||
this.clearChanged(); | |||
//LockSupport.parkNanos(1); | |||
try { | |||
try { | |||
Thread.sleep(waitTime); | |||
} catch (InterruptedException ex) { } | |||
} catch (InterruptedException ex) { | |||
} | |||
} | |||
public float avg_step(int step){ | |||
/** | |||
* Returns the average velocity of the given simulation step. | |||
* | |||
* @param step index of the step in the history list | |||
* @return average velocity | |||
*/ | |||
public float avg_step(int step) { | |||
float sum_step = 0; | |||
for (VehicleTimeRecord r : vtrList.get(step)) { | |||
sum_step += r.velocity; | |||
sum_step += r.velocity; | |||
} | |||
return sum_step / vehicles.size(); | |||
} | |||
protected void update_avg(){ | |||
lastAvg = avg_step(vtrList.size()-1); | |||
/** | |||
* Utility function which updates the averages values with the results from the last simulation | |||
* step. | |||
*/ | |||
protected void update_avg() { | |||
lastAvg = avg_step(vtrList.size() - 1); | |||
if (this.steps > 10) { | |||
sumDelAvgMemory += lastAvg; | |||
delayedAvg = sumDelAvgMemory / (this.steps - 10); | |||
@@ -144,13 +306,4 @@ public class Track extends Observable { | |||
overallAvg = sumAvgMemory / this.steps; | |||
} | |||
public float avg_span(int start, int end){ | |||
float sum_span = 0; | |||
for (int i=start; i <end;i++){ | |||
sum_span += avg_step(i); | |||
} | |||
return sum_span / (end-start+1); | |||
} | |||
} |
@@ -2,15 +2,49 @@ package de.hems.trafficsim; | |||
import java.util.Random; | |||
/** | |||
* Model class which represents a vehicle in the simulation. | |||
*/ | |||
public class Vehicle { | |||
/** | |||
* a number to index the vehicle | |||
*/ | |||
protected int id; | |||
/** | |||
* current position of the vehicle on the track | |||
*/ | |||
protected float position; | |||
/** | |||
* current velocity of the vehicle | |||
*/ | |||
protected float curVelocity; | |||
/** | |||
* maximum velocity of the vehicle | |||
*/ | |||
protected float maxVelocity; | |||
/** | |||
* probability of the vehicle to brake without reason | |||
*/ | |||
protected float brakeProb; | |||
protected float trackLength; | |||
/** | |||
* length of the track the vehicle is placed on | |||
*/ | |||
protected float trackLength; | |||
/** | |||
* Constructs a new vehicle. | |||
* | |||
* @param id a number to index the vehicle | |||
* @param position current position of the vehicle on the track | |||
* @param maxVelocity maximum velocity of the vehicle | |||
* @param brakeProb probability of the vehicle to brake without reason | |||
* @param trackLength length of the track the vehicle is placed on | |||
*/ | |||
public Vehicle(int id, int position, float maxVelocity, float brakeProb, float trackLength) { | |||
this.id = id; | |||
this.position = position; | |||
@@ -21,22 +55,61 @@ public class Vehicle { | |||
} | |||
/** | |||
* Getter for position | |||
* | |||
* @return position | |||
*/ | |||
public float getPosition() { | |||
return position; | |||
} | |||
/** | |||
* Getter for curVelocity | |||
* | |||
* @return curVelocity | |||
*/ | |||
public float getCurVelocity() { | |||
return curVelocity; | |||
} | |||
/** | |||
* Getter for maxVelocity | |||
* | |||
* @return maxVelocity | |||
*/ | |||
public float getMaxVelocity() { | |||
return maxVelocity; | |||
} | |||
public void setBrakeProb(float brakeProb) { this.brakeProb = brakeProb; } | |||
/** | |||
* Setter for maxVelocity | |||
* | |||
* @return maxVelocity | |||
*/ | |||
public void setMaxVelocity(float maxVelocity) { | |||
this.maxVelocity = maxVelocity; | |||
} | |||
public void setMaxVelocity(float maxVelocity) { this.maxVelocity = maxVelocity; } | |||
/** | |||
* Setter for brakeProb | |||
* | |||
* @return brakeProb | |||
*/ | |||
public void setBrakeProb(float brakeProb) { | |||
this.brakeProb = brakeProb; | |||
} | |||
/** | |||
* Calculates the new velocity of the vehicle based on the following rules: | |||
* <ol> | |||
* <li>Increase by one if the vehicle is slower than it's maximum speed</li> | |||
* <li>Random brake by one</li> | |||
* <li>Break if the distance to the forerunner is less than it's speed</li> | |||
* </ol> | |||
* | |||
* @param distanceForerunner distance to the forerunner of the vehicle | |||
*/ | |||
public void updateVelocity(float distanceForerunner) { | |||
Random random = new Random(); | |||
@@ -53,6 +126,9 @@ public class Vehicle { | |||
} | |||
} | |||
/** | |||
* Updates the position of the vehicle according to it's previously calculated speed. | |||
*/ | |||
public void timeElapse() { | |||
position = (position + curVelocity) % this.trackLength; | |||
} | |||
@@ -1,9 +1,27 @@ | |||
package de.hems.trafficsim; | |||
/** | |||
* Model class which keeps the previous simulation data for analysis. | |||
*/ | |||
public class VehicleTimeRecord { | |||
/** | |||
* the id of the vehicle it belongs to | |||
*/ | |||
protected int id; | |||
/** | |||
* the position of the vehicle after the last simulation step | |||
*/ | |||
protected float position; | |||
/** | |||
* the velocity of the vehicle during the last simulation step | |||
*/ | |||
protected float velocity; | |||
/** | |||
* the maximum velocity of the vehicle | |||
*/ | |||
protected float maxVelocity; | |||
public float getMaxVelocity() { | |||
@@ -18,6 +36,14 @@ public class VehicleTimeRecord { | |||
return velocity; | |||
} | |||
/** | |||
* Construct a new VehicleTimeRecord. | |||
* | |||
* @param id the id of the vehicle it belongs to | |||
* @param position the position of the vehicle after the last simulation step | |||
* @param velocity the velocity of the vehicle during the last simulation step | |||
* @param maxVelocity the maximum velocity of the vehicle | |||
*/ | |||
public VehicleTimeRecord(int id, float position, float velocity, float maxVelocity) { | |||
this.id = id; | |||
this.position = position; | |||
@@ -1,17 +1,46 @@ | |||
package de.hems.trafficsim; | |||
import android.graphics.Canvas; | |||
import android.view.SurfaceHolder; | |||
import java.util.ConcurrentModificationException; | |||
/** | |||
* Calculation thread, which runs the simulation itself and also updates the view with a set rate. | |||
*/ | |||
public class Worker extends Thread { | |||
/** | |||
* the track to simulate | |||
*/ | |||
protected Track track; | |||
/** | |||
* Stop flag, indicating that the worker shall stop | |||
*/ | |||
protected boolean stop; | |||
/** | |||
* the MainActivity of the gui | |||
*/ | |||
protected MainActivity gui; | |||
/** | |||
* the Renderer drawing the Track | |||
*/ | |||
protected Renderer renderer; | |||
/** | |||
* Amount of simulation steps between two frames in the view. Zero means | |||
* that every simulation step is drawn. | |||
*/ | |||
protected int frameskip; | |||
/** | |||
* Construct a new worker. | |||
* | |||
* @param track the track to simulate | |||
* @param gui the MainActivity of the gui | |||
* @param renderer the Renderer drawing the Track | |||
* @param frameskip Amount of simulation steps between two frames in the view. Zero means | |||
* that every simulation step is drawn | |||
*/ | |||
public Worker(Track track, MainActivity gui, Renderer renderer, int frameskip) { | |||
super(); | |||
this.track = track; | |||
@@ -21,16 +50,28 @@ public class Worker extends Thread { | |||
this.frameskip = frameskip; | |||
} | |||
/** | |||
* Sets a flag that causes the worker to stop. | |||
* | |||
* @param stop flag whether the thread shall stop | |||
*/ | |||
public void setStop(boolean stop) { | |||
this.stop = stop; | |||
} | |||
/** | |||
* Updates the amount of simulation steps between to view frames. | |||
* | |||
* @param frames amount of steps | |||
*/ | |||
public void setFrameskip(int frames) { | |||
this.frameskip = frames; | |||
} | |||
public int getFrameskip() { return this.frameskip; } | |||
/** | |||
* Method which is automatically executed when the thread is started. It alternates between | |||
* updating the simulation and drawing a new frame in the view. | |||
*/ | |||
@Override | |||
public void run() { | |||
Canvas canvas = null; | |||
@@ -40,7 +81,7 @@ public class Worker extends Thread { | |||
this.gui.updateStats(); | |||
if (i >= this.frameskip) { | |||
this.renderer.draw(); | |||
i=0; | |||
i = 0; | |||
} | |||
i++; | |||
} | |||
@@ -1,17 +0,0 @@ | |||
package de.hems.trafficsim; | |||
import org.junit.Test; | |||
import static org.junit.Assert.*; | |||
/** | |||
* Example local unit test, which will execute on the development machine (host). | |||
* | |||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |||
*/ | |||
public class ExampleUnitTest { | |||
@Test | |||
public void addition_isCorrect() { | |||
assertEquals(4, 2 + 2); | |||
} | |||
} |