package TrackRider;
import engine.Animator;
import engine.Engine;
import static engine.Engine.appSpeed;
import static engine.Engine.elapsedAppTime;
import engine.Map;
import engine.Passenger;
import engine.Station;
import graphics.emitter.Emitter;
import graphics.material.Material;
import graphics.model.Model;
import java.awt.Point;
import java.util.ArrayList;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;
/**
* Train is the only truly player controllable object.
* It is almost identical to Coach. See TrackRider
*
* @author Jari Saaranen <rasaari@gmail.com>
* @author simokr
*/
public class Train extends TrackRider {
// engine's power in horsepowers
private float power;
private float heat;
private boolean overheat;
private boolean brakesOn;
private Emitter smokeEmitter,smokeEmitter2;
private OverheatSteamEmitters overheatEmitters;
private long coalTimer;
private float passedDistance;
private float damage;
private Animator animator;
public Train(Map map) {
/* Wind effect on steam/smoke */
Vector3f windSpeed = new Vector3f(-0.2f,0,-0.3f);
// model specific initialization
smokeEmitter = new Emitter(30, 4000);
smokeEmitter.setVelocity(new Vector3f(0, 1.6f, 0));
smokeEmitter.setSizes(5, 200);
smokeEmitter.setSizesRandomness(1, 100);
smokeEmitter.setAcceleration(new Vector3f(windSpeed.x, -0.29f, windSpeed.z));
smokeEmitter.setTranslation(new Vector3f(0, 0.08f, 0.30f));
smokeEmitter2 = new Emitter(30, 3000);
smokeEmitter2.setVelocity(new Vector3f(0, 1.4f, 0));
smokeEmitter2.setSizes(5, 75);
smokeEmitter2.setSizesRandomness(1, 75);
smokeEmitter2.setAcceleration(new Vector3f(windSpeed.x, -0.19f, windSpeed.z));
smokeEmitter2.setTranslation(new Vector3f(0, 0.06f, 0.35f));
overheatEmitters = new OverheatSteamEmitters(windSpeed);
animator = new Animator(36, 0.2f);
this.model = new Model();
this.model.setMesh(Engine.MeshHandler.get("res/mesh/penydarren/penydarren.obj"));
this.model.setPosition(0, 1, 1);
// 70 tons
this.setWeight(30*1000);
this.power = 20.0f;
this.heat = 0.0f;
this.damage = 0;
this.coalTimer = elapsedAppTime();
//this.addCash(5000.0f);
this.map = map;
this.passedDistance = 0.0f;
brakesOn = false;
}
/**
* Ask train to turn in next intersection
* to selected direction.
*
* Direction can be -1 or TrackRider.LEFT which means left or
* 1 or TrackRider.RIGHT which means right.
* @param dir
*/
@Override
public void requestTurn(int dir) {
super.requestTurn(dir);
//for(Coach c: coaches) {
// c.requestTurn(dir);
//}
}
public float getHeat() {
return this.heat;
}
public boolean getOverheat() {
return this.overheat;
}
public float getPassedDistance() {
return this.passedDistance;
}
public float getDamage() {
return this.damage;
}
@Override
public void render() {
this.getModel().render();
if(this.coach != null) {
this.coach.render();
}
this.overheatEmitters.render();
this.smokeEmitter.render();
this.smokeEmitter2.render();
}
@Override
public void update() {
this.calculateVelocity();
//this.setVelocity(Math.min(this.getVelocity(), 33.3333f/5f));
for(TrackRider rider = this.coach; rider != null; rider = rider.coach){
rider.setVelocity(this.getVelocity());
}
float distanceThisFrame = this.getVelocity()*(appSpeed()/60.0f);
float oneStep = 0.1f;
this.passedDistance += distanceThisFrame;
/* Iterate over the distance the train moves this frame with steps of 0.1 */
while(distanceThisFrame > 0f){
float movement = Math.min(oneStep, distanceThisFrame);
Point currentTile = this.getGridLocation();
this.move(movement, currentTile, null);
TrackRider prev = this;
for(TrackRider rider = this.coach; rider != null; rider = rider.coach){
rider.move(movement, rider.getGridLocation(), prev);
prev = rider;
}
distanceThisFrame -= oneStep;
}
this.updateModel();
this.smokeEmitter.setVelocity(new Vector3f(0, 0.9f+0.6f*this.getPowerLevel(), 0));
this.smokeEmitter.setUpdateIntervalMultiplier(1.0f+5f*(1.0f-this.getPowerLevel()));
this.smokeEmitter.setPosition(this.getLocation().x, 0.60f, this.getLocation().y);
this.smokeEmitter.setRotation(this.model.getRotation());
this.smokeEmitter.update();
this.smokeEmitter2.setVelocity(new Vector3f(0, 0.9f+0.6f*this.getPowerLevel(), 0));
this.smokeEmitter2.setUpdateIntervalMultiplier(1.0f+5f*(1.0f-this.getPowerLevel()));
this.smokeEmitter2.setPosition(this.getLocation().x, 0.60f, this.getLocation().y);
this.smokeEmitter2.setRotation(this.model.getRotation());
this.smokeEmitter2.update();
if(this.getOverheat()){
this.overheatEmitters.resume();
}
else{
this.overheatEmitters.pause();
}
this.overheatEmitters.update();
if(elapsedAppTime() - coalTimer > 1000) {
coalTimer = elapsedAppTime();
CoalCoach coalCoach = findUsefulCoalCoach();
if(coalCoach != null)
coalCoach.useCoals((long)(this.getPowerLevel()*40));
}
if(this.coach != null) {
this.coach.update();
}
if(this.getCoals() == 0 || this.overheat || this.damage >= 1)
this.setPowerLevel(0.0f);
if(this.getVelocity() == 0.0f)
this.collectCash();
// heat
float heatChange = (float) Math.sin(this.getPowerLevel())*2-1;
this.heat += heatChange*0.001f*appSpeed();
if(heat < 0) {
heat = 0;
this.overheat = false;
}
if(heat > 1) {
heat = 1;
this.overheat = true;
}
// damage
if(damage < 0)
damage = 0;
if(damage > 1) {
damage = 1;
// game over
}
damage += (float) Math.sin(this.getPowerLevel())*0.0001*appSpeed();
animator.setTimeScale(this.getVelocity()/3.1f);
animator.update();
// test animation
this.model.animate(animator.getFrameBend(), animator.getFrame(), animator.getFrame()+1);
}
@Override
protected void onTurn() {
if(this.coach != null) {
this.coach.requestTurn(this.getInterSectionDirection());
}
}
@Override
public void calculateVelocity() {
float totalWeight = getTotalWeight();
float totalWeightN = totalWeight * 9.81f;
float vel = this.getVelocity();
float TE = 0f;
/*
* Super complicated TE calculation for real steam engines
*
* TE = 0.8 * p * D * S * z / 2d
*
* 0.8 = losses to friction etc.
* p = boler pressure (N/m^2)
* D = cylinder area (m^2)
* S = cylinder stroke (m)
* z = number of cylinders
* d = driving wheel diameter (m)
*/
/*
float p = 1621200.384f;
float D = 0.2601f;
float S = 0.66f;
float z = 3;
float d = 1.42f;
// nominal = 235187.311369N = 235kN
TE = 0.8f * p * D * S * z / 2*d;
*/
// maximum TE = train (engine) mass * gravity * multiplier = 240kN
float TEmax = this.getWeight() * 9.81f * 0.3f;
// engine power (watts)
float powerW = this.power*this.getPowerLevel()*745.699872f;
// engine max force = engine power (watts) / speed (ms)
float Fmax = powerW / Math.max(this.getVelocity(),1f);
// Actual TE is the lower of these two
TE = Math.min(Fmax, TEmax);
//Fnet = TEeffective - c0 * W - c1 * W * v - c2 * v2 - Fincl
// c0 = 0.1% axle resistance
float c0 = 0.001f;
if(this.brakesOn)
c0 += 0.1f;
// c1 = 0.15% * ((142.22ms + currentSpeed)/142.22ms) rolling friction
float c1 = 0.0015f * ((142.22f + vel) / 142.22f);
// c2 = 0.5 * air mass density (kg/m3) * speed^2 (ms) * train drag Coeff * area (m2) = ?N air resistance
float c2 = 0.5f * 1.229f * vel * vel * 1.8f * 6 * (this.getCoachCount()+1);
float resistance = c0 * totalWeightN + c1 * totalWeightN + c2;
// effort to move train at all
// 1 ton takes 35 Newtons of force to move on a level track
//float baseN = totalWeight/1000 * 35;
//float maxSpeed = powerkW/(baseN/1000);
float Fnet;
//if(!this.brakesOn){
//this.smokeEmitter.resume();
Fnet = TE-resistance;
vel += Fnet/totalWeight*(appSpeed()/60);
if(vel < 0)
vel = 0f;
/*}
else{
if(vel <= 0){
vel = 0f;
Fnet = 0f;
//this.setPowerLevel(0f);
//this.smokeEmitter.pause();
}
else{
Fnet = -TE-resistance;
vel += Fnet/totalWeight*(appSpeed()/60);
}
}
*/
//System.out.println("Fmax: "+ TE + " Fnet: " + Fnet);
//System.out.println("PL: "+ this.getPowerLevel() + " acc: " + (Fnet*1000/totalWeight) + " maxvel: "+ maxSpeed);
//System.out.format(Locale.UK, "PL: %.2f Acc: %.2fm/s2 Fmax: %.2fkN Fnet: %.2fkN\n", this.getPowerLevel(), (Fnet/totalWeight), TE/1000, Fnet/1000);
setVelocity(vel);
}
public void setBrakes(boolean isOn){
this.brakesOn = isOn;
}
public void addCoach(int type) {
TrackRider that;
that = this;
while(that.coach != null) {
that = that.coach;
}
if(type == Coach.PASSENGER)
that.coach = new PassengerCoach(this.map);
else if(type == Coach.COAL)
that.coach = new CoalCoach(this.map);
// set new position to new coach behind the previous.
// TODO: make it direction-independent
Vector2f newLoc = new Vector2f(that.getLocation());
newLoc.x--;
that.coach.setLocation(newLoc);
}
public int getCoachCount(){
int count = 0;
for(TrackRider rider = this.coach; rider != null; rider = rider.coach){
count++;
}
return count;
}
/**
* Get total weight of the train and its coaches
* @return float total weight (kg)
*/
public float getTotalWeight() {
float totalWeight = 0.0f;
TrackRider that = this;
while(that != null) {
totalWeight += that.getWeight();
that = that.coach;
}
return totalWeight;
}
public long getCoals() {
long coals = 0L;
TrackRider that = this;
while(that != null) {
if(that instanceof CoalCoach) {
CoalCoach coalCoach = (CoalCoach)that;
coals += coalCoach.getCoals();
}
that = that.coach;
}
return coals;
}
public int getPassengerCount() {
int passengers = 0;
TrackRider that = this;
while(that != null) {
if(that instanceof PassengerCoach) {
PassengerCoach passengerCoach = (PassengerCoach)that;
passengers += passengerCoach.getPassengerCount();
}
that = that.coach;
}
return passengers;
}
public int getPassengersToStation(Station station) {
int passengers = 0;
TrackRider that = this;
while(that != null) {
if(that instanceof PassengerCoach) {
PassengerCoach passengerCoach = (PassengerCoach)that;
for(Passenger passenger: passengerCoach.getPassengers()) {
if(passenger.getDestination().equals(station))
passengers++;
}
}
that = that.coach;
}
return passengers;
}
public CoalCoach findUsefulCoalCoach() {
TrackRider that = this;
while(that != null) {
if(that instanceof CoalCoach) {
CoalCoach coalCoach = (CoalCoach)that;
if(coalCoach.getCoals() > 0)
return coalCoach;
}
that = that.coach;
}
return null;
}
public CoalCoach findNotfullCoalCoach() {
TrackRider that = this;
while(that != null) {
if(that instanceof CoalCoach) {
CoalCoach coalCoach = (CoalCoach)that;
if(coalCoach.getCoals() < coalCoach.getCapacity())
return coalCoach;
}
that = that.coach;
}
return null;
}
/**
* Collect ticket income from coaches
*/
public void collectCash() {
TrackRider that = this;
while(that != null) {
if(that instanceof PassengerCoach) {
PassengerCoach passengerCoach = (PassengerCoach)that;
this.addCash(passengerCoach.withdrawCash());
}
that = that.coach;
}
}
@Override
public void free() {
this.smokeEmitter.free();
this.smokeEmitter2.free();
this.overheatEmitters.free();
for(TrackRider rider = this.coach; rider != null; rider = rider.coach){
rider.free();
}
}
public void repair(float cost) {
if(this.damage > 0.01f && this.withdrawCash(cost) > 0)
this.damage -= 0.01f;
}
public ArrayList<Passenger> getPassengerListToStation(Station station) {
ArrayList<Passenger> passengers = new ArrayList();
TrackRider that = this;
while(that != null) {
if(that instanceof PassengerCoach) {
PassengerCoach passengerCoach = (PassengerCoach)that;
for(Passenger passenger: passengerCoach.getPassengers()) {
if(passenger.getDestination().equals(station))
passengers.add(passenger);
}
}
that = that.coach;
}
return passengers;
}
class OverheatSteamEmitters{
private Emitter overheatEmitter[];
public OverheatSteamEmitters(Vector3f windSpeed) {
Material smokeMat = new Material();
smokeMat.setTexture(0, "res/texture/smoke_dark.png");
overheatEmitter = new Emitter[6];
for(int i=0;i<6;i++){
overheatEmitter[i] = new Emitter(40, 1000);
overheatEmitter[i].setMaterial(smokeMat);
overheatEmitter[i].setVelocity(new Vector3f(0, 0.5f, -0.5f));
overheatEmitter[i].setSizes(5, 10);
overheatEmitter[i].setSizesRandomness(1, 40);
overheatEmitter[i].setAcceleration(new Vector3f(windSpeed.x, -0.05f, windSpeed.z));
}
overheatEmitter[0].setTranslation(new Vector3f(-0.227f, -0.06f, 0.30f));
overheatEmitter[1].setTranslation(new Vector3f(-0.227f, -0.06f, 0.20f));
overheatEmitter[2].setTranslation(new Vector3f(-0.227f, -0.06f, 0.10f));
overheatEmitter[3].setTranslation(new Vector3f(0.227f, -0.06f, 0.30f));
overheatEmitter[4].setTranslation(new Vector3f(0.227f, -0.06f, 0.20f));
overheatEmitter[5].setTranslation(new Vector3f(0.227f, -0.06f, 0.10f));
}
public void update(){
double rotYrad = Math.toRadians(model.getRotation().y);
float sin = (float)Math.sin(rotYrad);
float cos = (float)Math.cos(rotYrad);
float heatPow2 = getHeat()*getHeat();
for(int i=0;i<6;i++){
float smokeDir = (i<3)?-1.8f*heatPow2-0.2f:1.8f*heatPow2+0.2f;
this.overheatEmitter[i].setVelocity(new Vector3f((-cos*smokeDir), 1.8f*heatPow2+0.2f, sin*smokeDir));
this.overheatEmitter[i].setPosition(getLocation().x, 0.60f, getLocation().y);
this.overheatEmitter[i].setRotation(model.getRotation());
this.overheatEmitter[i].update();
}
}
public void pause(){
for(int i=0;i<6;i++){
this.overheatEmitter[i].pause();
}
}
public void resume(){
for(int i=0;i<6;i++){
this.overheatEmitter[i].resume();
}
}
public void render(){
for(int i=0;i<6;i++){
this.overheatEmitter[i].render();
}
}
public void free(){
for(int i=0;i<6;i++){
this.overheatEmitter[i].free();
}
}
}
}