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
 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
133
134
135
136
137
138
139
140
// This file is part of Carambolage.

// Carambolage is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Carambolage is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
use super::controller::Controller;
use grphx::Model;
use log::debug;
use ncollide3d::shape::Cuboid;

use nalgebra::{clamp, zero, Matrix4, Vector3};

/// A GameObject controlled by a player.
pub(crate) struct Car {
    pub(crate) position: Vector3<f32>, // position in world space
    pub(crate) rotation: Vector3<f32>, // rotation in radians per axis
    _velocity: Vector3<f32>,
    _force: Vector3<f32>,
    _mass: f32,
    boost: f32,

    pub(crate) model: Model,
    pub(crate) cuboid: Cuboid<f32>,
}

impl Car {
    /// Create a new `Car`.
    ///
    /// For `model` and `color_palette` see `model_from_id()` and `color_from_id()`.
    /// `mass` is the mass of the car in [kg]
    pub fn new(model: &str, color_palette: &str, position: Vector3<f32>, mass: f32) -> Car {
        debug!("New({}, {}, {:?}, {})", model, color_palette, position, mass);

        let model = Model::new(model, color_palette);
        let (min, max) = model.get_min_max();
        let cuboid = Cuboid::new((max - min) * 0.25);

        Car {
            position,
            rotation: zero(),
            _velocity: zero(),
            _force: zero(),
            _mass: mass,
            boost: 100.0,
            model,
            cuboid,
        }
    }

    /// Update the car position and velocity based on the internal car state for
    /// a given time step.
    pub(super) fn update(&mut self, dt: f32, controller: Option<Controller>) {
        if controller.is_some() {
            let ct = controller.unwrap();

            // accel:  0.0 - None
            //         1.0 - Pedal to the metal
            //        -1.0 - Emergency brake
            let accel = ct.get_y_axis();
            // steer:  0.0 - Forward
            //         1.0 - Full right
            //        -1.0 - Full left
            // * accel to prevent steering a non moving car.
            let steer = ct.get_x_axis() * accel;

            // x,y-axis rotation are fixed to 0. No rollovers!
            self.rotation[2] -= steer * dt * 3.5;

            let rot_mat = Matrix4::new_rotation(self.rotation);
            let mut forward = Vector3::new(0f32, 1., 0.).to_homogeneous();
            forward = rot_mat * forward;
            // Set homogeneous coordinate to 0 or unwrap() will panic.
            forward[3] = 0.;

            let booster = if ct.get_boost() {
                self.boost = clamp(self.boost - dt * 30.0, 0.0, 100.0);
                if self.boost > 0.1 {
                    14.0
                } else {
                    10.0
                }
            } else {
                self.boost = clamp(self.boost + dt * 14.0, 0.0, 100.0);
                10.0
            };

            self.position += Vector3::from_homogeneous(forward).unwrap() * accel * booster * dt;
        }
    }

    /// Draw the car to the currently bound framebuffer.
    pub(super) fn draw(&self, view: &Matrix4<f32>, projection: &Matrix4<f32>) {
        // x,y-axis rotation are fixed to 0. No rollovers!
        let rotation = Matrix4::from_euler_angles(0., 0., self.rotation[2]);
        let translation = Matrix4::new_translation(&self.position);
        let model = translation * rotation * Matrix4::new_scaling(0.5f32);
        self.model.draw(&model, view, projection);
    }

    /// Return a `Car` file name from an id.
    ///
    /// 1:kart 2:parsche 3:farara 4:lamba 5:gtc1 6:gtc2 7:formula
    pub fn model_from_id(id: u32) -> String {
        match id {
            1 => String::from("c01.obj"),
            2 => String::from("c02.obj"),
            3 => String::from("c03.obj"),
            4 => String::from("c04.obj"),
            5 => String::from("c05.obj"),
            6 => String::from("c06.obj"),
            7 => String::from("c07.obj"),
            _ => String::from("c01.obj"),
        }
    }

    /// Return a `Car` color palette file name from an id.
    ///
    /// 1:blue 2:green 3:lime 4:orange 5:purple 6:red 7:yellow
    pub fn color_from_id(id: u32) -> String {
        match id {
            1 => String::from("car-blue.png"),
            2 => String::from("car-green.png"),
            3 => String::from("car-lime.png"),
            4 => String::from("car-orange.png"),
            5 => String::from("car-purple.png"),
            6 => String::from("car-red.png"),
            7 => String::from("car-yellow.png"),
            _ => String::from("car-blue.png"),
        }
    }
}