├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── MarkingGuide.pdf └── VehiclePhysicsSimulation.pdf ├── include ├── AllCameras.h ├── Axle.hpp ├── Brake.hpp ├── CameraSystem.h ├── Car.h ├── ControlSystem.h ├── DebugCarModel.h ├── DebugVector.hpp ├── DebugVectorGroup.h ├── Environment.h ├── EnvironmentModel.h ├── GameCarModel.h ├── ICarModel.h ├── PacejkaMagicFormula.h ├── Suspension.hpp ├── Terrain.h ├── TerrainGenLayers.hpp ├── TerrainModel.h ├── TorqueGenerator.hpp ├── Track.h ├── Tyre.h ├── UILayer.h ├── VehicleSimulation.h ├── VisualShell.h ├── Wheel.h ├── WheelInterface.h ├── WheelSystem.h └── add_function.h ├── linux_build.sh ├── res ├── images │ └── windowIcon.png ├── models │ ├── CarBody.obj │ ├── Spring.obj │ ├── SteeringWheel.obj │ └── Wheel.obj └── shaders │ ├── body.frag │ ├── body.vert │ ├── debug.frag │ ├── debug.vert │ ├── skyBox.frag │ ├── skyBox.vert │ ├── terrain.frag │ └── terrain.vert ├── screenshots └── screenshot.png ├── src ├── AllCameras.cpp ├── CameraSystem.cpp ├── Car.cpp ├── ControlSystem.cpp ├── DebugCarModel.cpp ├── DebugVectorGroup.cpp ├── Environment.cpp ├── EnvironmentModel.cpp ├── GameCarModel.cpp ├── ICarModel.cpp ├── PacejkaMagicFormula.cpp ├── Terrain.cpp ├── TerrainModel.cpp ├── Track.cpp ├── Tyre.cpp ├── UILayer.cpp ├── VehicleSimulation.cpp ├── VisualShell.cpp ├── Wheel.cpp ├── WheelInterface.cpp ├── WheelSystem.cpp ├── add_function.cpp ├── dummy_main.cpp └── main.cpp └── tests ├── CMakeLists.txt └── test_dummy.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | 13 | build/* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | project(vehicle-dynamics-sim) 3 | 4 | set(LIB VehicleDynamicsSimLibrary) 5 | 6 | add_library(${LIB} 7 | src/add_function.cpp 8 | ) 9 | 10 | #add_executable(${APP_EXE} 11 | # src/AllCameras.cpp 12 | # src/CameraSystem.cpp 13 | # src/Car.cpp 14 | # src/ControlSystem.cpp 15 | # src/DebugCarModel.cpp 16 | # src/DebugVectorGroup.cpp 17 | # src/Environment.cpp 18 | # src/EnvironmentModel.cpp 19 | # src/GameCarModel.cpp 20 | # src/ICarModel.cpp 21 | # src/main.cpp 22 | # src/PacejkaMagicFormula.cpp 23 | # src/Terrain.cpp 24 | # src/TerrainModel.cpp 25 | # src/test_main.cpp 26 | # src/Track.cpp 27 | # src/Tyre.cpp 28 | # src/UILayer.cpp 29 | # src/VehicleSimulation.cpp 30 | # src/VisualShell.cpp 31 | # src/Wheel.cpp 32 | # src/WheelInterface.cpp 33 | # src/WheelSystem.cpp) 34 | 35 | target_include_directories(${LIB} PRIVATE include/) 36 | 37 | # todo: link to game framework 38 | #TARGET_LINK_LIBRARIES(${APP_EXE} game-framework-graphics) 39 | 40 | set(APP VehicleDynamicsSim) 41 | 42 | add_executable(${APP} 43 | src/dummy_main.cpp 44 | ) 45 | 46 | target_include_directories(${APP} PRIVATE include/) 47 | target_link_libraries(${APP} PRIVATE ${LIB}) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lewis Bowes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vehicle-dynamics-simulation 2 | 3 | ![](screenshots/screenshot.png) 4 | 5 | Non-exam assessment project for AQA A level computer science, developed between September 2017 and April 2018. 6 | 7 | ### Aims 8 | 1. The main focus was getting the maximum number of marks possible for the A level. 9 | 2. After this, the project was used to test and make improvements to an application framework library being developed. 10 | 11 | ### The Non-Exam Assessment 12 | This project counts for 20% of the final A level result. Details on the requirements for the NEA can be found [here](http://filestore.aqa.org.uk/resources/computing/AQA-7517-NEA-GUIDE.PDF). 13 | 14 | ### License 15 | [MIT License](https://github.com/lbowes/A-Level-Computer-Science-NEA/blob/master/LICENSE) 16 | -------------------------------------------------------------------------------- /docs/MarkingGuide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbowes/vehicle-dynamics-simulation/773fcc527fa3858f186e0165613d9bbaf8b1759c/docs/MarkingGuide.pdf -------------------------------------------------------------------------------- /docs/VehiclePhysicsSimulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbowes/vehicle-dynamics-simulation/773fcc527fa3858f186e0165613d9bbaf8b1759c/docs/VehiclePhysicsSimulation.pdf -------------------------------------------------------------------------------- /include/AllCameras.h: -------------------------------------------------------------------------------- 1 | /* CLASS(ES) OVERVIEW 2 | * - SimulationCamera is just a wrapper around a Framework::PerspectiveCamera 3 | * - FPVCamera, FrontWheelCamera, DriverCamera all inherit from SimulationCamera 4 | * - Each have unique ways of updating themselves/controllable features (like movement, gaze direction etc) 5 | */ 6 | 7 | #ifndef ALLCAMERAS_H 8 | #define ALLCAMERAS_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Visual { 16 | class SimulationCamera { 17 | protected: 18 | Framework::PerspectiveCamera mPerspectiveCamera; 19 | 20 | public: 21 | SimulationCamera(glm::vec3 position_OGL, glm::vec3 direction_OGL, float near, float far, float aspect, float FOV); 22 | ~SimulationCamera() = default; 23 | 24 | inline Framework::Camera& getInternalCamera() { return mPerspectiveCamera; } 25 | 26 | }; 27 | 28 | class FPVCamera : public SimulationCamera { 29 | private: 30 | const float 31 | mMovementSpeed = 275.0f, //400.0f 32 | mMovementFriction = 7.0f, //7.0f 33 | mZoomSensitivity = 0.1f, //0.1f 34 | mLookAroundSensitivity = 0.05f; //0.05f 35 | 36 | float 37 | mPitch = 0.0f, 38 | mYaw = 0.0f; 39 | 40 | glm::vec3 41 | mPosition_OGL, 42 | mVelocity_OGL, 43 | mDirection_OGL; 44 | 45 | public: 46 | FPVCamera(glm::vec3 position_OGL, glm::vec3 direction_OGL, float near, float far, float aspect, float FOV); 47 | ~FPVCamera() = default; 48 | 49 | void update(float windowAspect, float dt); 50 | void handleInput(float dt); 51 | 52 | private: 53 | void handleMovementInput(float dt); 54 | glm::dvec3 afterPositionConstraints(glm::dvec3 input); 55 | 56 | }; 57 | 58 | class FrontWheelCamera : public SimulationCamera { 59 | private: 60 | const glm::dvec3 61 | mPositionOnStage_stage = glm::vec3(-0.401277f, 44.73737f, -1.850528f), 62 | mFront_stage = glm::vec3(0.0f, -1.0f, 0.0f), 63 | mUp_stage = glm::vec3(-0.214532f, 0.0f, -0.976717f); 64 | 65 | public: 66 | FrontWheelCamera(glm::vec3 position_car, glm::vec3 direction_car, float near, float far, float aspect, float FOV); 67 | ~FrontWheelCamera() = default; 68 | 69 | void update(float windowAspect, glm::mat4 localToWorld_car, glm::quat localToWorldRotation_car); 70 | 71 | }; 72 | 73 | class DriverCamera : public SimulationCamera { 74 | private: 75 | const glm::dvec3 mPosition_car = glm::vec3(-0.401277f, 44.73737f, -1.850528f); 76 | 77 | glm::vec3 78 | mPositionInCar_OGL, 79 | mVelocityInCar_OGL, 80 | mDirection_OGL; 81 | 82 | const float 83 | mZoomSensitivity = 0.1f, //0.1f 84 | mLookAroundSensitivity = 0.05f; //0.05f 85 | 86 | float 87 | mPitch = 0.0f, 88 | mYaw = 0.0f; 89 | 90 | public: 91 | DriverCamera(glm::vec3 position_car, glm::vec3 direction_car, float near, float far, float aspect, float FOV); 92 | ~DriverCamera() = default; 93 | 94 | void update(float windowAspect, glm::mat4 localToWorld_car, glm::quat localToWorldRotation_car); 95 | void handleInput(float dt); 96 | 97 | }; 98 | 99 | void handleDirectionInput(Framework::PerspectiveCamera& camHandle, float& yawHandle, float& pitchHandle, glm::vec3& directionHandle, float sensitivity); 100 | void handleZoomInput(Framework::PerspectiveCamera& camHandle, float sensitivity); 101 | 102 | } 103 | 104 | #endif -------------------------------------------------------------------------------- /include/Axle.hpp: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Responsible for calculating, storing and providing access to a total torque value 3 | */ 4 | 5 | #ifndef AXLE_H 6 | #define AXLE_H 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "TorqueGenerator.hpp" 12 | 13 | namespace Internal { 14 | class Axle { 15 | private: 16 | double 17 | mLength = 0.0, //m, equivalent to the (front or rear) 'track' of the car 18 | mLongDisplacement_car = 0.0, //m, longitudinal displacement of axle from car's origin (positive for front, negative for rear) 19 | mTransferredTorque = 0.0, //Nm, sign represents direction 20 | mRPM = 0.0; //Rotations per minute 21 | 22 | TorqueGenerator* mTorqueGenerator = nullptr; //Should remain a null pointer for lazy axles 23 | 24 | public: 25 | Axle() = default; 26 | ~Axle() = default; 27 | 28 | void update(double RPM, double totalCounterTorque) 29 | /* Called by WheelSystem::updateAxles 30 | * Calculates a total torque value based on counter torque passed in and drive torque 31 | */ 32 | { 33 | //Reset the torque before recalculating it again 34 | mTransferredTorque = 0.0; 35 | 36 | //Add drive torque from the torque generator (if this isn't a lazy axle) 37 | if (mTorqueGenerator) { 38 | mTorqueGenerator->updateRPM(RPM); 39 | mTransferredTorque -= mTorqueGenerator->getOutputTorque(); 40 | } 41 | 42 | //Add resiting torque from wheel traction and brakes 43 | mTransferredTorque += totalCounterTorque; 44 | } 45 | 46 | inline double getTransferredTorque() const { return mTransferredTorque; } 47 | inline double getLongDisplacement_car() const { return mLongDisplacement_car; } 48 | inline double getLength() const { return mLength; } 49 | inline void attachTorqueGenerator(TorqueGenerator* newTorqueGenerator) { mTorqueGenerator = newTorqueGenerator; } 50 | inline void setLongDisplacement_car(double longDisplacement_car) { mLongDisplacement_car = longDisplacement_car; } 51 | inline void setLength(double length) { mLength = length; } 52 | 53 | }; 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /include/Brake.hpp: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Produces a torque that can be updated and accessed 3 | */ 4 | 5 | #ifndef BRAKE_H 6 | #define BRAKE_H 7 | #pragma once 8 | 9 | namespace Internal { 10 | class Brake { 11 | private: 12 | const double mDistToWheelCentre = 0.16; //m 13 | 14 | double 15 | mCompressionForce = 0.0, //Nm 16 | mTorqueMagnitude = 0.0, //Nm 17 | mTravelPercentage = 0.0; //0.0 = released, 1.0 = full contact, *directionless* 18 | 19 | public: 20 | Brake() = default; 21 | ~Brake() = default; 22 | 23 | void update() 24 | /* Called by WheelInterface::update 25 | */ 26 | { 27 | //This function uses the travel percentage to generate a compression force. 28 | //This is just the *magnitude*, independent of direction: the WheelInterface uses this value as it knows about direction. 29 | 30 | mCompressionForce = mTravelPercentage * 9000.0; 31 | mTorqueMagnitude = mCompressionForce * mDistToWheelCentre; 32 | } 33 | 34 | inline void setTravelPercentage(double newTravelPercent) { mTravelPercentage = newTravelPercent < 0.0 ? 0.0 : newTravelPercent > 1.0 ? 1.0 : newTravelPercent; } 35 | inline double getTorqueMagnitude() const { return mTorqueMagnitude; } 36 | 37 | }; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/CameraSystem.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Manages instances of all 3 SimulationCameras, FPVCamera, DriverCamera and FrontWheelCamera 3 | * - Controls which of the 3 is currently active and updates all 3 accordingly 4 | */ 5 | 6 | #ifndef CAMERASYSTEM_H 7 | #define CAMERASYSTEM_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "AllCameras.h" 14 | 15 | #define FPV_CAM static_cast(mCameras[0].get()) 16 | #define FRONT_WHEEL_CAM static_cast(mCameras[1].get()) 17 | #define DRIVER_CAM static_cast(mCameras[2].get()) 18 | 19 | namespace Internal { 20 | class Car; 21 | } 22 | 23 | namespace Visual { 24 | class CameraSystem { 25 | public: 26 | enum CameraName { FPV, FR_WHEEL, DRIVER }; 27 | 28 | private: 29 | std::vector> mCameras; 30 | 31 | int mCurrentCameraName = DRIVER; 32 | 33 | bool mHasFocus = false; 34 | 35 | public: 36 | CameraSystem(float windowAspect); 37 | ~CameraSystem() = default; 38 | 39 | void update(float windowAspect, float dt, Internal::Car& cameraTarget); 40 | void checkInput(float dt); 41 | void cycleCameras(bool left); 42 | 43 | inline bool hasFocus() const { return mHasFocus; } 44 | inline SimulationCamera& getCurrentSimCamera() const { return *mCameras[mCurrentCameraName].get(); } 45 | inline int getCurrentCameraName() const { return mCurrentCameraName; } 46 | 47 | inline void shouldHaveFocus(bool shouldHaveFocus) { mHasFocus = shouldHaveFocus; } 48 | 49 | private: 50 | void addAllCameras(float windowAspect); 51 | 52 | }; 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /include/Car.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - The main simulated object in the application and the root of multiple object hierarchies 3 | * - Referenced by the visual side of the application 4 | * - Responsible for updating all member objects, and then updating its own state using calculated forces and torques 5 | */ 6 | 7 | #ifndef CAR_H 8 | #define CAR_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "Environment.h" 16 | #include "WheelSystem.h" 17 | #include "ControlSystem.h" 18 | 19 | namespace Internal { 20 | class Car : public Framework::Physics::RigidBody { 21 | protected: 22 | ControlSystem mControlSystem; 23 | WheelSystem mWheelSystem; 24 | std::unique_ptr mTorqueGenerator; 25 | 26 | glm::dvec3 27 | mAerodynamicDrag_world, //N 28 | mTotalForce_world, //N 29 | mTotalTorque_world; //Nm 30 | 31 | double 32 | mFrontalArea = 2.63, //m^2 33 | mDragCoefficient = 1.3, //(dimensionless) 34 | mWidth = 0.0, //m 35 | mLength = 0.0; //m 36 | 37 | public: 38 | Car(); 39 | ~Car() = default; 40 | 41 | void update(double t, double dt); 42 | void checkInput(double dt); 43 | void resetToTrackPosition(); 44 | 45 | inline Framework::Physics::State& getState() { return mState; } 46 | inline WheelSystem& getWheelSystem() { return mWheelSystem; } 47 | inline ControlSystem& getControlSystem() { return mControlSystem; } 48 | inline TorqueGenerator& getTorqueGenerator() { return *mTorqueGenerator.get(); } 49 | inline glm::dvec3 getAeroDrag_world() { return mAerodynamicDrag_world; } 50 | 51 | private: 52 | void updateTotalForce_world(); 53 | glm::dvec3 getForce_world(Framework::Physics::State& state, double t); 54 | void updateTotalTorque_world(); 55 | glm::dvec3 getTorque_world(Framework::Physics::State& state, double t); 56 | void positionConstraints(); 57 | void assemble(); 58 | 59 | }; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /include/ControlSystem.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - References different sub components of the Car whose state updates depend on user input 3 | * - A layer between user input and the Car 4 | * - Where a relatively large amount of input handling takes place 5 | */ 6 | 7 | #ifndef CONTROLSYSTEM_H 8 | #define CONTROLSYSTEM_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #include "TorqueGenerator.hpp" 15 | 16 | namespace Internal { 17 | class Brake; 18 | class Wheel; 19 | 20 | class ControlSystem { 21 | private: 22 | Wheel 23 | *mLeftWheel = nullptr, 24 | *mRightWheel = nullptr; 25 | 26 | std::vector mBrakes; 27 | TorqueGenerator* mTorqueGenerator = nullptr; 28 | 29 | const double mSteeringRate = 5000.0; 30 | 31 | double 32 | mSteeringRatio = 0.0, 33 | mMaxAbsWheelAngle = 0.0, //degs 34 | mMaxAbsSteeringWheelAngle = 0.0, //degs 35 | mSteeringWheelAngle = 0.0; //Inner tyre deflection angle (degs) 36 | 37 | bool mBrakesOn = false; 38 | 39 | public: 40 | ControlSystem() = default; 41 | ~ControlSystem() = default; 42 | 43 | void update(double wheelBase, double frontAxleTrack); 44 | void handleInput(double dt); 45 | void attachWheels(Wheel* left, Wheel* right); 46 | void setMaxAbsWheelAngle(double maxAbsAngle); 47 | 48 | inline double getSteeringWheelAngle() const { return mSteeringWheelAngle; } 49 | inline void setSteeringRatio(double newRatio) { mSteeringRatio = newRatio; } 50 | inline void attachTorqueGenerator(TorqueGenerator* torqueGenerator) { mTorqueGenerator = torqueGenerator; } 51 | inline void attachBrakes(std::vector brakes) { mBrakes = brakes; } 52 | inline bool brakesOn() const { return mBrakesOn; } 53 | 54 | private: 55 | void updateSteeringAngle(double wheelBase, double frontAxleTrack); 56 | void handleSteeringInput(double dt); 57 | void handleSpeedInput(); 58 | 59 | }; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /include/DebugCarModel.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Provides a debugging *overlay* model for the Car object 3 | * Used to visualise physical quantities 4 | */ 5 | 6 | #ifndef DEBUGCARMODEL_H 7 | #define DEBUGCARMODEL_H 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "ICarModel.h" 13 | #include "DebugVectorGroup.h" 14 | 15 | namespace Visual { 16 | class DebugCarModel : public ICarModel { 17 | public: 18 | DebugCarModel(Internal::Car& carData, Framework::ResourceSet& resourceBucket); 19 | ~DebugCarModel() = default; 20 | 21 | virtual void render(Framework::Graphics::Renderer& renderer) override; 22 | 23 | private: 24 | glm::vec4 25 | mWheelNeutralColour = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), 26 | mWheelCollidingColour = glm::vec4(0.0f, 1.0f, 0.0f, 1.0f); 27 | 28 | const unsigned int 29 | mWheelNumSides = 16, 30 | mNumVectorsPerWheelInterface = 4; 31 | 32 | std::vector 33 | mWheelNeutralColourData, 34 | mWheelCollidingColourData; 35 | 36 | DebugVectorGroup mVectorGroup; 37 | 38 | private: 39 | void addVectorLines(); 40 | void loadResources(); 41 | void populateWheelColourBufferData(); 42 | void carBaseResources(); 43 | void carWheelsResources(); 44 | void loadVectorLines(); 45 | void update(); 46 | void updateVectorLines(); 47 | 48 | }; 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /include/DebugVector.hpp: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - A visual representation of a vector quantity in the simulation (a line with a position, direction, length and colour) 3 | */ 4 | 5 | #ifndef DEBUGVECTOR_H 6 | #define DEBUGVECTOR_H 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace Visual { 13 | class DebugVector { 14 | private: 15 | static const unsigned char 16 | mNumPositionComponents = 6, 17 | mNumColourComponents = 8, 18 | mNumIndices = 2; 19 | 20 | glm::dvec3 21 | mPosition_world, 22 | mDirection_world; 23 | 24 | glm::dvec4 mColour; 25 | 26 | bool mIsVisible = true; 27 | 28 | public: 29 | DebugVector(glm::dvec3 position_world, glm::dvec3 direction_world, glm::dvec4 colour) : 30 | /* Called by DebugCarModel::addVectorLines 31 | */ 32 | mPosition_world(position_world), 33 | mDirection_world(direction_world), 34 | mColour(colour) 35 | { } 36 | 37 | ~DebugVector() = default; 38 | 39 | static inline unsigned char getNumPositionComponents() { return mNumPositionComponents; } 40 | static inline unsigned char getNumColourComponents() { return mNumColourComponents; } 41 | static inline unsigned char getNumIndices() { return mNumIndices; } 42 | inline glm::dvec3 getPosition_world() const { return mPosition_world; } 43 | inline glm::dvec3 getDirection_world() const { return mDirection_world; } 44 | inline glm::dvec4 getColour() const { return mColour; } 45 | inline bool isVisible() const { return mIsVisible; } 46 | 47 | inline void setPosition_world(glm::dvec3 newPosition_world) { mPosition_world = newPosition_world; } 48 | inline void setDirection_world(glm::dvec3 newDirection_world) { mDirection_world = newDirection_world; } 49 | inline void setColour(glm::dvec4 newColour) { mColour = newColour; } 50 | inline void setVisible(bool newVisibility) { mIsVisible = newVisibility; } 51 | 52 | }; 53 | 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /include/DebugVectorGroup.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Responsible for creating and maintaining renderable buffers of data, that represent the DebugVectors in mVectors 3 | */ 4 | 5 | #ifndef DEBUGVECTORGROUP_H 6 | #define DEBUGVECTORGROUP_H 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "DebugVector.hpp" 12 | 13 | namespace Visual { 14 | class DebugVectorGroup { 15 | private: 16 | std::vector mVectors; 17 | 18 | std::vector 19 | mPositionBuffer, 20 | mColourBuffer; 21 | 22 | std::vector mIndexBuffer; 23 | 24 | public: 25 | DebugVectorGroup() = default; 26 | ~DebugVectorGroup() = default; 27 | 28 | void addVector(DebugVector newVector); 29 | void update(); 30 | 31 | inline DebugVector* getVector(unsigned int index) { return (index >= 0 && index < mVectors.size()) ? &mVectors[index] : (DebugVector*)nullptr; } 32 | inline DebugVector* operator[](unsigned int index) { return getVector(index); } 33 | inline unsigned int getNumVectors() const { return mVectors.size(); } 34 | inline std::vector& getPositionBuffer() { return mPositionBuffer; } 35 | inline std::vector& getColourBuffer() { return mColourBuffer; } 36 | inline std::vector& getIndexBuffer() { return mIndexBuffer; } 37 | 38 | private: 39 | unsigned int recalcNumVisbleVectors(); 40 | 41 | }; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/Environment.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Encapsulates a static Terrain instance 3 | * - Contains purely static data 4 | * - Can be easily accessed by #including this file anywhere 5 | */ 6 | 7 | #ifndef ENVIRONMENT_H 8 | #define ENVIRONMENT_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "Terrain.h" 16 | 17 | namespace External { 18 | class Environment { 19 | public: 20 | static Terrain mTerrain; 21 | 22 | static const double 23 | mGravityAccel, 24 | mAirDensity; 25 | 26 | }; 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/EnvironmentModel.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Visual model to represent the internal shape of the terrain 3 | * Contains a skybox, colours and fog/sun parameters to enhance the appearance of the environment 4 | */ 5 | 6 | #ifndef ENVIRONMENTMODEL_H 7 | #define ENVIRONMENTMODEL_H 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "TerrainModel.h" 20 | 21 | #define FOG 1 22 | 23 | namespace Visual { 24 | class EnvironmentModel { 25 | private: 26 | Framework::ResourceSet& mResourceBucket; 27 | Framework::Model3D mModel; 28 | std::unique_ptr mSkyBox; 29 | std::unique_ptr mTerrainModel; 30 | 31 | Framework::Graphics::Shader 32 | *mTerrainShader = nullptr, 33 | *mSkyboxShader = nullptr; 34 | 35 | const float 36 | mFogDensity = FOG ? 0.005f : 0.0f, //0.005f 37 | mFogGradient = 2.0f; //2.0f 38 | 39 | const glm::vec3 40 | mSkyColour = glm::vec3(0.4823529411764706f, 0.6549019607843137f, 0.8588235294117647f), 41 | mSunColour = glm::vec3(1.0f), 42 | mSunDirection = glm::normalize(glm::vec3(10.0f, 5.0f, 0.0f)); 43 | 44 | public: 45 | EnvironmentModel(Framework::ResourceSet& resourceBucket); 46 | ~EnvironmentModel() = default; 47 | 48 | void render(Framework::Graphics::Renderer& renderer); 49 | inline float getFogDensity() const { return mFogDensity; } 50 | inline float getFogGradient() const { return mFogGradient; } 51 | inline glm::vec3 getSkyColour() const { return mSkyColour; } 52 | inline glm::vec3 getSunColour() const { return mSunColour; } 53 | inline glm::vec3 getSunDirection() const { return mSunDirection; } 54 | 55 | private: 56 | void loadResources(); 57 | 58 | }; 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /include/GameCarModel.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - A visual representation of the physical state of a Car object 3 | * - One of two classes that inherits from ICarModel 4 | */ 5 | 6 | #ifndef GAMECARMODEL_H 7 | #define GAMECARMODEL_H 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "ICarModel.h" 13 | 14 | namespace Visual { 15 | class GameCarModel : public ICarModel { 16 | public: 17 | GameCarModel(Internal::Car& carData, Framework::ResourceSet& resourceBucket); 18 | ~GameCarModel() = default; 19 | 20 | void loadResources(); 21 | void setShaderUniforms(float fogDensity, float fogGradient, glm::vec3 skyColour, glm::vec3 sunDirection); 22 | void update(); 23 | 24 | }; 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/ICarModel.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * A base class for GameCarModel and DebugCarModel 3 | * Stores a reference to a Car object, that is used as the source of all model transformation data 4 | */ 5 | 6 | #ifndef CARMODEL_H 7 | #define CARMODEL_H 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Internal { 16 | class Car; 17 | } 18 | 19 | namespace Visual { 20 | class ICarModel { 21 | protected: 22 | Framework::ResourceSet& mResourceBucket; 23 | Framework::Model3D mModel; 24 | Internal::Car& mCarData; 25 | 26 | public: 27 | ICarModel(Internal::Car& carData, Framework::ResourceSet& resourceBucket); 28 | ~ICarModel() = default; 29 | 30 | virtual void render(Framework::Graphics::Renderer& renderer); 31 | 32 | private: 33 | virtual void loadResources() = 0; 34 | virtual void update() = 0; 35 | 36 | 37 | }; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/PacejkaMagicFormula.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Implementation of Pacejka's Magic Formula for calculating tyre force (lon and lat) from slip (ratio and angle), 3 | * a detailed description of which can be found at http://www.edy.es/dev/docs/pacejka-94-parameters-explained-a-comprehensive-guide/ 4 | */ 5 | 6 | #ifndef PACEJKAMAGICFORMULA_H 7 | #define PACEJKAMAGICFORMULA_H 8 | #pragma once 9 | 10 | namespace Internal { 11 | struct Slip; 12 | 13 | class PacejkaMagicFormula { 14 | public: 15 | //Longitudinal parameters 16 | static float b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13; 17 | 18 | //Lateral parameters 19 | static float a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17; 20 | 21 | private: 22 | float 23 | C = 0.0, 24 | D = 0.0, 25 | BCD = 0.0, 26 | B = 0.0, 27 | E = 0.0, 28 | H = 0.0, 29 | V = 0.0, 30 | Bx1 = 0.0; 31 | 32 | double 33 | mLongitudinalForce = 0.0, 34 | mLateralForce = 0.0; 35 | 36 | public: 37 | PacejkaMagicFormula(); 38 | ~PacejkaMagicFormula() = default; 39 | 40 | void updateForces(double verticalLoad_N, double slipPercent0_to_100, double slipAngle_degs, double camberAngle_degs); 41 | static void setToRoadTyreParams(); 42 | static void setToDriftingTyreParams(); 43 | 44 | inline double getLongitudinalForce() const { return mLongitudinalForce; } 45 | inline double getLateralForce() const { return mLateralForce; } 46 | 47 | private: 48 | void updateLongitudinalForce(double load_kN, double slipAsPercent); 49 | void updateLateralForce(double load_kN, double slipAngle_degs, double camberAngle_degs); 50 | 51 | //Implementation of the mathematical sign() function 52 | inline int sign(double val) { return (0.0 < val) - (val < 0.0); } 53 | 54 | }; 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /include/Suspension.hpp: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Produces a force using a Spring object, that can be updated and accessed 3 | */ 4 | 5 | #ifndef SUSPENSION_H 6 | #define SUSPENSION_H 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace Internal { 12 | class Suspension { 13 | private: 14 | const double 15 | mSpringConstant = 49000.0, 16 | mDamping = 3000.0; 17 | 18 | Framework::Physics::Spring mSpring; 19 | 20 | glm::dvec3 mForce_world; 21 | 22 | public: 23 | Suspension() : 24 | /* Called during WheelInterface::WheelInterface 25 | */ 26 | mSpring(mSpringConstant, 0.0, mDamping) 27 | { } 28 | 29 | ~Suspension() = default; 30 | 31 | void update(double verticalRoadVelocity_car, double terrainOverlap, glm::dvec3 lineOfAction_world) 32 | /* Called by WheelInterface::update 33 | */ 34 | { 35 | mSpring.update(-terrainOverlap, terrainOverlap ? verticalRoadVelocity_car : 0.0); 36 | mForce_world = terrainOverlap ? normalize(lineOfAction_world) * mSpring.getForce() : glm::dvec3(0.0); 37 | } 38 | 39 | inline void neutralise() { mSpring.update(mSpring.getRestLength(), 0.0); } 40 | inline glm::dvec3 getForce_world() const { return mForce_world; } 41 | inline double getLength() const { return mSpring.getCurrentLength(); } 42 | inline Framework::Physics::Spring& getSpring() { return mSpring; } 43 | 44 | }; 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /include/Terrain.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Responsible for generating, storing, and providing acces to 3 buffers of data (mHeights, mNormals, mSurfaceTypes) 3 | * - Generation of these buffers uses multiple TerrainGenLayer objects 4 | * - The majority of the code in this class is executed at load-time 5 | */ 6 | 7 | #ifndef TERRAIN_H 8 | #define TERRAIN_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "TerrainGenLayers.hpp" 20 | #include "Track.h" 21 | 22 | namespace Visual { 23 | class TerrainModel; 24 | } 25 | 26 | namespace External { 27 | class Terrain { 28 | friend class Visual::TerrainModel; 29 | private: 30 | //Width of the terrain square in height sample points e.g. 7 for 6m x 6m terrain. 31 | const unsigned short mSize = 291; 32 | 33 | std::vector mHeights; 34 | std::vector mNormals; 35 | std::vector mSurfaceTypes; 36 | std::vector> mGenerationLayers; 37 | 38 | public: 39 | Terrain(); 40 | ~Terrain() = default; 41 | 42 | void generate(); 43 | double getHeight(glm::dvec2 horizontalSamplePoint); 44 | glm::dvec3 getNormal_world(glm::dvec2 horizontalSamplePoint); 45 | 46 | inline const unsigned short getSize() const { return mSize; } 47 | 48 | private: 49 | void generateHeightData(); 50 | void generateNormalData(); 51 | void generateSurfaceTypeData(); 52 | unsigned int calc_PerTriAttribute_Index(glm::dvec2 horizontalSamplePoint); 53 | 54 | }; 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /include/TerrainGenLayers.hpp: -------------------------------------------------------------------------------- 1 | /* CLASS(ES) OVERVIEW 2 | * - A TerrainGenLayer adds one layer of modification to data buffers passed to it (heights and surface types). 3 | * - RoughSand derives from TerrainGenLayer and modifies the heights buffer using Perlin noise 4 | * - All code in this class is executed at load-time 5 | */ 6 | 7 | #ifndef TERRAINGENLAYERS_H 8 | #define TERRAINGENLAYERS_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace External { 19 | enum TerrainType { GRASS, TARMAC, ERROR_TYPE }; 20 | 21 | class TerrainGenLayer { 22 | public: 23 | virtual void runHeights(std::vector& previousLayerHeights) = 0; 24 | virtual void runSurfaceTypes(std::vector& previousLayerSurfaceTypes) = 0; 25 | 26 | }; 27 | 28 | class RoughGround : public TerrainGenLayer { 29 | public: 30 | virtual void runHeights(std::vector& previousLayerHeights) 31 | /* Called by Terrain::generateHeightData 32 | * Modifies the data in previousLayerHeights by adding to it 33 | */ 34 | { 35 | //This ensures that the terrain is random for each run 36 | srand(time(NULL)); 37 | 38 | const int 39 | terrainSize = sqrt(previousLayerHeights.size()), 40 | halfTerrainSize = 0.5 * terrainSize, 41 | randomXOffset = rand(), 42 | randomZOffset = rand(); 43 | 44 | int currentHeightIndex = 0; 45 | 46 | double 47 | perlinCorrectX = 0.0, //Corrects for the fact that 1 Perlin noise 'unit' will be 16 meters 48 | perlinCorrectZ = 0.0, //^ 49 | total = 0, //A total value, used each iteration, to summate layers of Perlin noise 50 | amplitude = 0, //Used in the Perlin noise calculation 51 | frequency = 0; //^ 52 | 53 | //Iterate over all heights in the buffer passed in 54 | for (int x = -halfTerrainSize; x <= halfTerrainSize; x++) { 55 | for (int z = -halfTerrainSize; z <= halfTerrainSize; z++) { 56 | currentHeightIndex = (x + halfTerrainSize) * terrainSize + (z + halfTerrainSize); 57 | 58 | perlinCorrectX = randomXOffset + x / 16.0; 59 | perlinCorrectZ = randomZOffset + z / 16.0; 60 | 61 | //Reset the total 62 | total = 0.0; 63 | 64 | //Add low frequency hills 65 | amplitude = 8.0; 66 | frequency = 0.1; 67 | total += -abs(Framework::Maths::Noise::octavePerlin(perlinCorrectX * frequency, perlinCorrectZ * frequency, 3.0, 1.1) * amplitude); 68 | 69 | //Add higher frequency mounds of earth 70 | amplitude = 0.5; 71 | frequency = 1.0; 72 | total += -Framework::Maths::Noise::multiFractalRidged(perlinCorrectX * frequency, perlinCorrectZ * frequency, 2.0, 1.0, 1.0) * amplitude; 73 | 74 | //Set the current value in the buffer to this new total 75 | previousLayerHeights[(x + halfTerrainSize) * terrainSize + (z + halfTerrainSize)] = total; 76 | } 77 | } 78 | } 79 | 80 | virtual void runSurfaceTypes(std::vector& previousLayerSurfaceTypes) 81 | /*Called by Terrain::generateSurfaceTypeData 82 | * The terrain is grass by default, so the RoughGround does not need to modify it 83 | */ 84 | { } 85 | 86 | }; 87 | 88 | } 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /include/TerrainModel.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Generates a graphical model of the Terrain, to be rendered within the Environment 3 | */ 4 | 5 | #ifndef TERRAINMODEL_H 6 | #define TERRAINMODEL_H 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "Environment.h" 15 | 16 | namespace Visual { 17 | class TerrainModel { 18 | private: 19 | Framework::ResourceSet& mResourceBucket; 20 | Framework::Model3D mModel; 21 | Framework::Graphics::Shader* mShader = nullptr; 22 | 23 | public: 24 | TerrainModel(Framework::ResourceSet& resourceBucket, Framework::Graphics::Shader* terrainShader); 25 | ~TerrainModel() = default; 26 | 27 | void render(Framework::Graphics::Renderer& renderer); 28 | 29 | private: 30 | void loadModel(); 31 | void fillWithPositionData(std::vector& toFill); 32 | void fillWithNormalData(std::vector& toFill); 33 | void fillWithColourData(std::vector& toFill); 34 | void fillWithIndexData(std::vector& toFill); 35 | 36 | }; 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /include/TorqueGenerator.hpp: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Calculates, updates and provides access to a torque 3 | */ 4 | 5 | #ifndef TORQUEGENERATOR_H 6 | #define TORQUEGENERATOR_H 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace Internal { 13 | class TorqueGenerator { 14 | private: 15 | const double 16 | mMaxOutputForwardTorque = 0.0, 17 | mMaxOutputReverseTorque = 0.0, 18 | mGeneratorTorque = 100.0; 19 | 20 | double 21 | mOutputTorque = 0.0, 22 | mRPM = 0.0, 23 | mInertia = 0.0, 24 | mThrottle = 0.0; 25 | 26 | bool mReverseMode = false; 27 | 28 | public: 29 | TorqueGenerator(double maxForwardTorque, double maxReverseTorque) : 30 | /* Called by Car::assemble 31 | */ 32 | mMaxOutputForwardTorque(maxForwardTorque), 33 | mMaxOutputReverseTorque(maxReverseTorque) 34 | { } 35 | 36 | ~TorqueGenerator() = default; 37 | 38 | void update() 39 | /* Called by Car::update 40 | */ 41 | { 42 | //Prevents the torque generator from rotating so quickly that brakes become ineffective 43 | if (mReverseMode) 44 | mOutputTorque = -(std::max)((1.0 - pow(mRPM / 1000.0, 2.0)), 0.0) * mMaxOutputReverseTorque * mThrottle; 45 | else 46 | mOutputTorque = (std::max)((1.0 - pow(mRPM / 4000.0, 2.0)), 0.0) * mMaxOutputForwardTorque * mThrottle; 47 | } 48 | 49 | inline void updateRPM(double newRPM) { mRPM = newRPM; } 50 | inline void setThrottle(double newThrottle) { mThrottle = newThrottle < 0.0 ? 0.0 : newThrottle > 1.0 ? 1.0 : newThrottle; } 51 | inline void toggleReverse() { mReverseMode = !mReverseMode; } 52 | inline double getOutputTorque() const { return mOutputTorque; } 53 | inline bool reverseModeOn() const { return mReverseMode; } 54 | 55 | }; 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /include/Track.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Imprints the recessed shape and surface of a race track into the terrain 3 | * - All code in this class is executed at load-time 4 | */ 5 | 6 | #ifndef TRACK_H 7 | #define TRACK_H 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "TerrainGenLayers.hpp" 18 | 19 | namespace External { 20 | class Track : public TerrainGenLayer { 21 | private: 22 | struct PilotRunResults { 23 | glm::dvec2 24 | mShapeDimensions, 25 | mLowerBoundDisplacement, 26 | mUpperBoundDisplacement; 27 | 28 | inline bool shapeIsLandscape() { return mShapeDimensions.x > mShapeDimensions.y; } 29 | }; 30 | 31 | PilotRunResults mPilotResults; 32 | 33 | std::vector mPoints_graph; 34 | 35 | std::vector mTempSurfaceTypes; 36 | 37 | const unsigned int 38 | mTerrainSize_heightSamples = 0, 39 | mNumSamplesOverTotal = 2000, //10000 40 | mWidth = 20, 41 | mTerrainBorderPadding = 10; 42 | 43 | unsigned int mSizeLimit = 0; 44 | 45 | const double mMaxDepth = 2.0; 46 | 47 | double 48 | mPilotToMainScaleFactor = 0.0, 49 | mHeightCounter = 0.0; 50 | 51 | const glm::dvec2 mStartDirection = glm::dvec2(1.0, 0.0); 52 | 53 | glm::dvec2 mStartPosition = glm::dvec2(0.0); 54 | 55 | public: 56 | Track(const unsigned int terrainSize_heightSamples); 57 | ~Track() = default; 58 | 59 | virtual void runHeights(std::vector& previousLayerHeights); 60 | virtual void runSurfaceTypes(std::vector& previousLayerSurfaceTypes); 61 | 62 | private: 63 | void addAllPoints(); 64 | void addPoint_graph(double percent, double angle); 65 | double lookUpAngleAtPercent_graph(double percent); 66 | void imprintCircle(std::vector& toImprintHeights, int centreX, int centreZ); 67 | void runPilotVersion(); 68 | void updateStartingPosition(); 69 | 70 | }; 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /include/Tyre.h: -------------------------------------------------------------------------------- 1 | /* CLASS(ES) OVERVIEW 2 | * - Slip calculates, updates and provides access to longitudinal and lateral slip values 3 | * - Slip can be passed around as a single object 4 | * - Tyre calculates, updates and provides access to two force components 5 | * - Tyre encapsulates a PacejkaMagicFormula and Slip 6 | */ 7 | 8 | #ifndef TYRE_H 9 | #define TYRE_H 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "PacejkaMagicFormula.h" 18 | 19 | namespace Internal { 20 | class Wheel; 21 | 22 | struct Slip { 23 | private: 24 | double 25 | mLongSlipSpeed = 0.0, //m/s 26 | mLateralSlipSpeed = 0.0, //m/s 27 | mLongitudinal = 0.0, //0.0 -> 1.0, for longitudinal calculations 28 | mAngle_degs = 0.0; //Degrees, for lateral calculations 29 | 30 | public: 31 | void update(glm::dvec2 tyreVel_lat_long, double tyreRotSpeed_radPerSec, double effectiveRollingRadius) 32 | /* Called by Tyre::update 33 | * Recalculates longitudinal and lateral slip 34 | */ 35 | { 36 | //page 5/6 of http://mech.unibg.it/~lorenzi/VD&S/Matlab/Tire/tire_models_pac2002.pdf 37 | 38 | //Longitudinal 39 | mLongSlipSpeed = tyreVel_lat_long.y - tyreRotSpeed_radPerSec * effectiveRollingRadius; 40 | mLongitudinal = tyreVel_lat_long.y == 0.0 ? 0.0 : mLongSlipSpeed; 41 | 42 | //Lateral 43 | mLateralSlipSpeed = tyreVel_lat_long.x; 44 | mAngle_degs = tyreVel_lat_long.y == 0.0 ? (tyreVel_lat_long.x > 0.0 ? -90.0 : 90.0) : glm::degrees(atan(mLateralSlipSpeed / abs(tyreVel_lat_long.y))); 45 | } 46 | 47 | inline void reset() { mLongitudinal = mAngle_degs = 0.0; } 48 | inline bool isGenerated() const { return mAngle_degs != 0.0 || mLongitudinal != 0.0; } 49 | 50 | inline double getLongitudinal() const { return mLongitudinal; } 51 | inline double getAngle_degs() const { return mAngle_degs; } 52 | 53 | }; 54 | 55 | class Tyre { 56 | friend class Wheel; 57 | private: 58 | Slip mSlip; 59 | 60 | glm::dvec2 mTotalForce_wheel; //Wheel-space, x = lateral, y = longitudinal 61 | 62 | PacejkaMagicFormula mForceCalculator; 63 | 64 | const double 65 | mRubberDensity = 650.0, //kg/m^3 66 | mDepth = 0.06544, //m 67 | mTreadWidth = 0.2, //m 68 | mRollResistCoefficient = 0.0125, //dimensionless 69 | mAxialInertia = 0.0; //kg/m^2 70 | 71 | double 72 | mRollResistForce_long = 0.0, //N 73 | mRollingSpeed = 0.0; //m/s 74 | 75 | public: 76 | Tyre(double wheelRimRadius); 77 | ~Tyre() = default; 78 | 79 | void update(glm::dvec2 wheelVelocity_wheel, double verticalLoad, double camberAngle, double wheelRimRadius, double wheelRotSpeed_radPerSec); 80 | 81 | inline glm::dvec2 getTotalForce_wheel() const { return mTotalForce_wheel; } 82 | inline double getDepth() const { return mDepth; } 83 | inline double getAxialInertia() const { return mAxialInertia; } 84 | inline Slip getSlip() const { return mSlip; } 85 | 86 | }; 87 | } 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /include/UILayer.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Encapsulates all user interface code 3 | * Rendered on top of the simulation 4 | */ 5 | 6 | #ifndef UILAYER_H 7 | #define UILAYER_H 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "CameraSystem.h" 15 | 16 | namespace Internal { 17 | class Car; 18 | } 19 | 20 | namespace Visual { 21 | class UILayer { 22 | private: 23 | Internal::Car& mDataSource; 24 | 25 | Framework::Graphics::Shader& mCarModelShader; 26 | 27 | CameraSystem& mCameraSystem; 28 | 29 | float &mSimulationSpeedHandle; 30 | 31 | bool &mShowDebugMode_handle; 32 | 33 | bool 34 | mShowDriverInfo = true, 35 | mShowCarCustomise = false, 36 | mShowTyreParams = false, 37 | mShowHelpInfo = true; 38 | 39 | public: 40 | UILayer(Internal::Car& simDataSource, Framework::Graphics::Shader& carModelShader, CameraSystem& cameraSystem, float& simulationSpeedHandle, bool& debugModeHandle); 41 | ~UILayer() = default; 42 | 43 | void render(); 44 | 45 | private: 46 | void load() const; 47 | 48 | void mainControlPanel(); 49 | void carCustomisation() const; 50 | void driverInfo() const; 51 | void helpInfo() const; 52 | void carDebugInfo() const; 53 | void debug_physicsTelemetry() const; 54 | void debug_wheelTelemetry(unsigned char axlePos, unsigned char side, ImVec4 colour) const; 55 | void debug_allWheelTelemetry() const; 56 | void tyreParameters() const; 57 | void upsideDownWarning() const; 58 | 59 | }; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /include/VehicleSimulation.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * The root class for the application 3 | * Owns the physics simulation (in the form of the mCar object) and its visual representation (mVisuals) 4 | */ 5 | 6 | #ifndef VEHICLESIMULATION_H 7 | #define VEHICLESIMULATION_H 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "Car.h" 13 | #include "VisualShell.h" 14 | 15 | class VehicleSimulation : public Framework::Application { 16 | private: 17 | std::unique_ptr mVisuals; 18 | 19 | Internal::Car mCar; 20 | 21 | float mSimulationSpeed = 1.0; 22 | 23 | public: 24 | VehicleSimulation(); 25 | ~VehicleSimulation() = default; 26 | 27 | private: 28 | void onLoad(); 29 | void onInputCheck(); 30 | void onUpdate(); 31 | void onRender(); 32 | 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /include/VisualShell.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * Root class of the visual side of the application 3 | * Manages all graphical models, the UI layer and the camera system, as well as the objects required to render the models (a ResourceSet, Renderers) 4 | */ 5 | 6 | #ifndef VISUALSHELL_H 7 | #define VISUALSHELL_H 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "GameCarModel.h" 16 | #include "DebugCarModel.h" 17 | #include "EnvironmentModel.h" 18 | #include "CameraSystem.h" 19 | #include "UILayer.h" 20 | 21 | //temp 22 | #include 23 | // 24 | 25 | namespace Visual { 26 | class VisualShell { 27 | private: 28 | Framework::Window& mWindow; 29 | Framework::ResourceSet mResourceHolder; 30 | 31 | Framework::Graphics::Renderer 32 | mBaseRenderer, 33 | mDebugLayerRenderer; 34 | 35 | Internal::Car& mDataSource; 36 | 37 | //temp 38 | std::unique_ptr orthoCam; 39 | // 40 | 41 | CameraSystem mCameraSystem; 42 | std::unique_ptr mUILayer; 43 | 44 | std::unique_ptr mGameCarModel; 45 | std::unique_ptr mDebugCarModel; 46 | std::unique_ptr mEnvironmentModel; 47 | 48 | bool mDebugMode = false; 49 | 50 | float& mSimulationSpeedHandle; 51 | 52 | public: 53 | VisualShell(Internal::Car& dataSource, Framework::Window& window, float& simSpeedHandle); 54 | ~VisualShell() = default; 55 | 56 | void update(float dt); 57 | void renderAll(); 58 | void checkInput(float dt); 59 | 60 | inline bool getDebugMode() const { return mDebugMode; } 61 | inline void setDebugMode(bool debugOn) { mDebugMode = debugOn; } 62 | 63 | private: 64 | void load(); 65 | 66 | }; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /include/Wheel.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Provides further abstraction around the force generated by the Tyre 3 | * - Transforms force data coming from the Tyre 4 | * - Can move vertically in car space, and can rotate with steering and rolling 5 | */ 6 | 7 | #ifndef WHEEL_H 8 | #define WHEEL_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "Tyre.h" 16 | 17 | namespace Internal { 18 | class Wheel { 19 | private: 20 | glm::dvec3 21 | mBasePosition_car, //Car-space, from the wheel's geometric (cylindrical) centre 22 | mPosition_car, //Car-space, from the wheel's geometric (cylindrical) centre 23 | mTyreForce_world; //World-space, total force generated by the tyre 24 | 25 | const double 26 | mRimRadius = 0.29491; //m 27 | 28 | double 29 | mInertiaAboutAxle = 0.0, 30 | mAngularAcceleration = 0.0, //Radians per second 31 | mAngularVelocity = 0.0, //Radians per second 32 | mAngularPosition = 0.0, //Radians from start 33 | mSteeringAngle = 0.0; //Degrees, angle from default forward position, left < 0, right > 0 34 | 35 | char mRotationDirection = 0; //1 = forward, -1 = backwards, 0 = not rotating 36 | 37 | Tyre mTyre; 38 | 39 | public: 40 | Wheel(); 41 | ~Wheel() = default; 42 | 43 | //Note: roadVel_car and load should be glm::dvec2(0.0) if vehicle is airborne 44 | void update(glm::dmat4 carToWorldRotation_car, glm::dvec3 terrainNormalUnderWheel, glm::dvec2 wheelVel_car, double load, double totalInputTorque, double dt); 45 | void reset(); 46 | 47 | inline glm::dvec3 getBasePosition_car() const { return mBasePosition_car; } 48 | inline glm::dvec3 getPosition_car() const { return mPosition_car; } 49 | inline glm::dvec3 getContactPatchPosition_car() const { return mPosition_car - glm::dvec3(0.0, mRimRadius + mTyre.mDepth, 0.0); } 50 | inline glm::dvec3 getTyreForce_world() const { return mTyreForce_world; } 51 | inline double getAngularVelocity() const { return mAngularVelocity; } 52 | inline double getRPM() const { return mAngularVelocity / glm::two_pi(); } 53 | inline double getAngularPosition() const { return mAngularPosition; } 54 | inline double getRimRadius() const { return mRimRadius; } 55 | inline double getTotalRadius() const { return mRimRadius + mTyre.getDepth(); } 56 | inline double getSteeringAngle() const { return mSteeringAngle; } 57 | inline char getRotationDirection() const { return mRotationDirection; } 58 | inline Tyre& getTyre() { return mTyre; } 59 | 60 | inline void resetToBasePosition() { mPosition_car = mBasePosition_car; } 61 | inline void setPosition_car(glm::dvec3 pos_car) { mPosition_car = pos_car; } 62 | inline void setBasePosition_car(glm::dvec3 basePos_car) { mBasePosition_car = basePos_car; } 63 | inline void setSteeringAngle(double newSteeringAngle) { mSteeringAngle = newSteeringAngle; } 64 | 65 | private: 66 | void updateAngularMotion(double totalTorque, double dt); 67 | void updateTyreForce_world(glm::dmat4 carToWorldRotation_car, glm::dvec3 terrainNormalUnderWheel); 68 | 69 | }; 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /include/WheelInterface.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Encapsulates and updates all per-wheel components 3 | * - Provides a level of abstraction around the Wheel 4 | * - Manages the vertical motion of the Wheel in car space (with access to Suspension) 5 | */ 6 | 7 | #ifndef WHEELINTERFACE_H 8 | #define WHEELINTERFACE_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "Wheel.h" 16 | #include "Axle.hpp" 17 | #include "Brake.hpp" 18 | #include "Suspension.hpp" 19 | 20 | namespace Internal { 21 | class WheelInterface { 22 | private: 23 | Axle& mConnectedAxle; 24 | Wheel mWheel; 25 | Brake mBrake; 26 | Suspension mSuspension; 27 | 28 | glm::dvec3 29 | mPosition_car, 30 | mPosition_world, 31 | mVelocity_world; 32 | 33 | double mLoad = 0.0; //N 34 | 35 | bool mCollisionRegistered = false; 36 | 37 | public: 38 | WheelInterface(Axle& connectedAxle); 39 | ~WheelInterface() = default; 40 | 41 | void update(Framework::Physics::State& carState, double load, double dt); 42 | void setPosition_car(glm::dvec3 newPosition_car); 43 | void reset(); 44 | 45 | inline Wheel& getWheel() { return mWheel; } 46 | inline Brake& getBrake() { return mBrake; } 47 | inline Suspension& getSuspension() { return mSuspension; } 48 | inline glm::dvec3 getPosition_car() const { return mPosition_car; } 49 | inline glm::dvec3 getPosition_world() const { return mPosition_world; } 50 | inline glm::dvec3 getVelocity_world() const { return mVelocity_world; } 51 | inline double getLoad() const { return mLoad; } 52 | inline bool collisionRegistered() const { return mCollisionRegistered; } 53 | 54 | private: 55 | void updateWheel(glm::dmat4 carToWorldRotation_car, glm::dvec3 terrainNormalUnderWheel, double terrainOverlap, double dt); 56 | 57 | }; 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /include/WheelSystem.h: -------------------------------------------------------------------------------- 1 | /* CLASS OVERVIEW 2 | * - Encapsulates everything relating to the Wheels 3 | * - Provides the total force and torque produced by the Tyres and Suspension 4 | * - Owns Axles and connects them to WheelInterfaces 5 | */ 6 | 7 | #ifndef WHEELSYSTEM_H 8 | #define WHEELSYSTEM_H 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "WheelInterface.h" 16 | 17 | namespace Internal { 18 | class ControlSystem; 19 | 20 | class WheelSystem { 21 | friend class Axle; 22 | public: 23 | enum AxlePos : unsigned char { FRONT, REAR }; 24 | enum Side : unsigned char { LEFT, RIGHT }; 25 | 26 | private: 27 | const double 28 | mWheelBase = 2.96, //m 29 | mTrack_front = 1.66116, //m 30 | mTrack_rear = 1.69926, //m 31 | mWheelHeight = 0.13; //m 32 | 33 | double mMinTurningRadius = 0.0; //m 34 | 35 | glm::dvec3 36 | mTotalForce_world, 37 | mTotalTorque_world; 38 | 39 | std::vector mWheelInterfaces; 40 | 41 | Axle 42 | mFrontAxle, 43 | mRearAxle; 44 | 45 | public: 46 | WheelSystem(); 47 | ~WheelSystem() = default; 48 | 49 | void update(Framework::Physics::State& carState, glm::dvec3 carAcceleration_car, double dt); 50 | void bindControlSystem(ControlSystem& controlSystem); 51 | void reset(); 52 | 53 | inline double getWheelBase() const { return mWheelBase; } 54 | inline WheelInterface& getWheelInterface(AxlePos pos, Side side) { return mWheelInterfaces[pos * 2 + side]; } 55 | inline WheelInterface* getWheelInterface(unsigned char index) { return (index >= 0 && index < mWheelInterfaces.size()) ? &mWheelInterfaces[index] : (WheelInterface*)nullptr; } 56 | inline WheelInterface* operator[](unsigned char index) { return (index >= 0 && index < mWheelInterfaces.size()) ? &mWheelInterfaces[index] : (WheelInterface*)nullptr; } 57 | inline std::vector& getAllWheelInterfaces() { return mWheelInterfaces; } 58 | inline Axle& getAxle(AxlePos pos) { return pos == AxlePos::FRONT ? mFrontAxle : mRearAxle; } 59 | inline glm::dvec3 getTotalForce_world() const { return mTotalForce_world; } 60 | inline glm::dvec3 getTotalTorque_world() const { return mTotalTorque_world; } 61 | 62 | inline void setMinimumTurnRadius(double minTurnRadius) { mMinTurningRadius = minTurnRadius; } 63 | 64 | private: 65 | void positionWheelInterfaces(); 66 | void updateAxles(); 67 | void updateAllWheelInterfaces(Framework::Physics::State& carState, glm::dvec3 carAcceleration_car, double dt); 68 | double recalcLoad(AxlePos position, Side side, Framework::Physics::Mass& carMass_car, glm::dvec3 carAcceleration_car, double carCMHeightAboveGround); 69 | double recalcCarCmHeightAboveGround(Framework::Physics::State& carState); 70 | void updateTotalForce_world(); 71 | void updateTotalTorque_world(glm::dvec3 carPosition_world, glm::dmat4 carToWorldTransform_car); 72 | 73 | inline AxlePos calcAxleFromIndex(unsigned char index) const { return index < 2 ? FRONT : REAR; } 74 | inline Side calcSideFromIndex(unsigned char index) const { return index % 2 == 0 ? LEFT : RIGHT; } 75 | 76 | }; 77 | } 78 | 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /include/add_function.h: -------------------------------------------------------------------------------- 1 | #ifndef ADD_FUNCTION_H_ 2 | #define ADD_FUNCTION_H_ 3 | 4 | int add(int a, int b); 5 | 6 | #endif // ADD_FUNCTION_H_ 7 | -------------------------------------------------------------------------------- /linux_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p build 3 | cmake -S . -B build/ 4 | cd build 5 | make -------------------------------------------------------------------------------- /res/images/windowIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbowes/vehicle-dynamics-simulation/773fcc527fa3858f186e0165613d9bbaf8b1759c/res/images/windowIcon.png -------------------------------------------------------------------------------- /res/models/Spring.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: 'Spring.blend' 2 | # www.blender.org 3 | v 0.073434 0.019131 0.000000 4 | v 0.055123 -0.009565 -0.000000 5 | v 0.091745 -0.009565 -0.000000 6 | v 0.022692 0.032140 -0.069840 7 | v 0.017034 0.003444 -0.052425 8 | v 0.028351 0.003444 -0.087255 9 | v -0.059409 0.045149 -0.043163 10 | v -0.044595 0.016452 -0.032400 11 | v -0.074223 0.016452 -0.053926 12 | v -0.059409 0.058157 0.043163 13 | v -0.044595 0.029461 0.032400 14 | v -0.074223 0.029461 0.053926 15 | v 0.022692 0.071166 0.069840 16 | v 0.017034 0.042470 0.052425 17 | v 0.028351 0.042470 0.087255 18 | v 0.073434 0.084175 -0.000000 19 | v 0.055123 0.055479 -0.000000 20 | v 0.091745 0.055479 -0.000000 21 | v 0.022692 0.097184 -0.069840 22 | v 0.017034 0.068488 -0.052425 23 | v 0.028351 0.068488 -0.087255 24 | v -0.059409 0.110193 -0.043163 25 | v -0.044595 0.081497 -0.032400 26 | v -0.074223 0.081497 -0.053926 27 | v -0.059409 0.123202 0.043163 28 | v -0.044595 0.094506 0.032400 29 | v -0.074223 0.094506 0.053926 30 | v 0.022692 0.136211 0.069840 31 | v 0.017034 0.107515 0.052425 32 | v 0.028351 0.107515 0.087255 33 | v 0.073434 0.149220 -0.000000 34 | v 0.055123 0.120524 -0.000000 35 | v 0.091745 0.120524 -0.000000 36 | v 0.022692 0.162229 -0.069840 37 | v 0.017034 0.133533 -0.052425 38 | v 0.028351 0.133533 -0.087255 39 | v -0.059409 0.175238 -0.043163 40 | v -0.044595 0.146541 -0.032400 41 | v -0.074223 0.146541 -0.053926 42 | v -0.059409 0.188246 0.043163 43 | v -0.044595 0.159550 0.032400 44 | v -0.074223 0.159550 0.053926 45 | v 0.022692 0.201255 0.069840 46 | v 0.017034 0.172559 0.052425 47 | v 0.028351 0.172559 0.087255 48 | v 0.073434 0.214264 0.000000 49 | v 0.055123 0.185568 0.000000 50 | v 0.091745 0.185568 0.000000 51 | v 0.022692 0.227273 -0.069840 52 | v 0.017034 0.198577 -0.052425 53 | v 0.028351 0.198577 -0.087255 54 | v -0.059409 0.240282 -0.043163 55 | v -0.044595 0.211586 -0.032400 56 | v -0.074223 0.211586 -0.053926 57 | v -0.059409 0.253291 0.043163 58 | v -0.044595 0.224595 0.032400 59 | v -0.074223 0.224595 0.053926 60 | v 0.022692 0.266300 0.069840 61 | v 0.017034 0.237604 0.052425 62 | v 0.028351 0.237604 0.087255 63 | v 0.073434 0.279309 -0.000000 64 | v 0.055123 0.250613 -0.000000 65 | v 0.091745 0.250613 -0.000000 66 | v 0.022692 0.292318 -0.069840 67 | v 0.017034 0.263622 -0.052425 68 | v 0.028351 0.263622 -0.087255 69 | v -0.059409 0.305327 -0.043163 70 | v -0.044595 0.276630 -0.032400 71 | v -0.074223 0.276630 -0.053926 72 | v -0.059409 0.318336 0.043163 73 | v -0.044595 0.289639 0.032400 74 | v -0.074223 0.289639 0.053926 75 | v 0.022692 0.331344 0.069840 76 | v 0.017034 0.302648 0.052425 77 | v 0.028351 0.302648 0.087255 78 | v 0.073434 0.344353 -0.000000 79 | v 0.055123 0.315657 -0.000000 80 | v 0.091745 0.315657 -0.000000 81 | v 0.022692 0.357362 -0.069840 82 | v 0.017034 0.328666 -0.052425 83 | v 0.028351 0.328666 -0.087255 84 | v -0.059409 0.370371 -0.043163 85 | v -0.044595 0.341675 -0.032400 86 | v -0.074223 0.341675 -0.053926 87 | v -0.059409 0.383380 0.043163 88 | v -0.044595 0.354684 0.032400 89 | v -0.074223 0.354684 0.053926 90 | v 0.022692 0.396389 0.069840 91 | v 0.017034 0.367693 0.052425 92 | v 0.028351 0.367693 0.087255 93 | v 0.073434 0.409398 0.000000 94 | v 0.055123 0.380702 0.000000 95 | v 0.091745 0.380702 0.000000 96 | vn -0.6757 0.4312 0.5979 97 | vn 0.3599 0.4312 0.8274 98 | vn 0.8981 0.4312 -0.0866 99 | vn 0.1952 0.4312 -0.8809 100 | vn -0.7775 0.4312 -0.4579 101 | vn -0.1402 -0.9891 -0.0456 102 | vn -0.0867 -0.9891 0.1193 103 | vn 0.0867 -0.9891 0.1193 104 | vn -0.0000 -0.9891 -0.1475 105 | vn 0.1402 -0.9891 -0.0456 106 | vn 0.7641 0.4381 -0.4736 107 | vn -0.2143 0.4381 -0.8730 108 | vn -0.8965 0.4381 -0.0660 109 | vn -0.3398 0.4381 0.8322 110 | vn 0.6865 0.4381 0.5804 111 | vn -0.6655 0.4788 0.5727 112 | vn 0.3390 0.4788 0.8099 113 | vn 0.8750 0.4788 -0.0721 114 | vn 0.2018 0.4788 -0.8544 115 | vn -0.7503 0.4788 -0.4559 116 | vn 0.0000 -0.9706 -0.2408 117 | vn -0.1416 -0.9706 0.1948 118 | vn 0.1416 -0.9706 0.1948 119 | vn 0.2291 -0.9706 -0.0744 120 | vn -0.2291 -0.9706 -0.0744 121 | vn 0.7441 0.4748 -0.4699 122 | vn -0.2169 0.4748 -0.8529 123 | vn -0.8782 0.4748 -0.0573 124 | vn -0.3259 0.4748 0.8175 125 | vn 0.6768 0.4748 0.5625 126 | s off 127 | f 1//1 5//1 2//1 128 | f 4//2 8//2 5//2 129 | f 7//3 11//3 8//3 130 | f 10//4 14//4 11//4 131 | f 13//5 17//5 14//5 132 | f 16//1 20//1 17//1 133 | f 19//2 23//2 20//2 134 | f 22//3 26//3 23//3 135 | f 25//4 29//4 26//4 136 | f 28//5 32//5 29//5 137 | f 31//1 35//1 32//1 138 | f 34//2 38//2 35//2 139 | f 37//3 41//3 38//3 140 | f 40//4 44//4 41//4 141 | f 43//5 47//5 44//5 142 | f 46//1 50//1 47//1 143 | f 49//2 53//2 50//2 144 | f 52//3 56//3 53//3 145 | f 55//4 59//4 56//4 146 | f 58//5 62//5 59//5 147 | f 61//1 65//1 62//1 148 | f 64//2 68//2 65//2 149 | f 67//3 71//3 68//3 150 | f 70//4 74//4 71//4 151 | f 73//5 77//5 74//5 152 | f 76//1 80//1 77//1 153 | f 79//2 83//2 80//2 154 | f 82//3 86//3 83//3 155 | f 85//4 89//4 86//4 156 | f 88//5 92//5 89//5 157 | f 3//6 5//6 6//6 158 | f 5//6 9//6 6//6 159 | f 8//7 12//7 9//7 160 | f 11//8 15//8 12//8 161 | f 15//9 17//9 18//9 162 | f 17//9 21//9 18//9 163 | f 21//7 23//7 24//7 164 | f 24//8 26//8 27//8 165 | f 26//8 30//8 27//8 166 | f 29//10 33//10 30//10 167 | f 33//6 35//6 36//6 168 | f 36//7 38//7 39//7 169 | f 39//8 41//8 42//8 170 | f 41//8 45//8 42//8 171 | f 44//10 48//10 45//10 172 | f 47//9 51//9 48//9 173 | f 50//6 54//6 51//6 174 | f 53//7 57//7 54//7 175 | f 56//8 60//8 57//8 176 | f 59//10 63//10 60//10 177 | f 63//6 65//6 66//6 178 | f 65//6 69//6 66//6 179 | f 68//7 72//7 69//7 180 | f 71//8 75//8 72//8 181 | f 75//9 77//9 78//9 182 | f 77//9 81//9 78//9 183 | f 80//6 84//6 81//6 184 | f 83//7 87//7 84//7 185 | f 86//8 90//8 87//8 186 | f 89//10 93//10 90//10 187 | f 1//11 6//11 4//11 188 | f 4//12 9//12 7//12 189 | f 7//13 12//13 10//13 190 | f 10//14 15//14 13//14 191 | f 13//15 18//15 16//15 192 | f 16//11 21//11 19//11 193 | f 19//12 24//12 22//12 194 | f 22//13 27//13 25//13 195 | f 25//14 30//14 28//14 196 | f 28//15 33//15 31//15 197 | f 31//11 36//11 34//11 198 | f 34//12 39//12 37//12 199 | f 37//13 42//13 40//13 200 | f 40//14 45//14 43//14 201 | f 43//15 48//15 46//15 202 | f 46//11 51//11 49//11 203 | f 49//12 54//12 52//12 204 | f 52//13 57//13 55//13 205 | f 55//14 60//14 58//14 206 | f 58//15 63//15 61//15 207 | f 61//11 66//11 64//11 208 | f 64//12 69//12 67//12 209 | f 67//13 72//13 70//13 210 | f 70//14 75//14 73//14 211 | f 73//15 78//15 76//15 212 | f 76//11 81//11 79//11 213 | f 79//12 84//12 82//12 214 | f 82//13 87//13 85//13 215 | f 85//14 90//14 88//14 216 | f 88//15 93//15 91//15 217 | f 1//16 4//16 5//16 218 | f 4//17 7//17 8//17 219 | f 7//18 10//18 11//18 220 | f 10//19 13//19 14//19 221 | f 13//20 16//20 17//20 222 | f 16//16 19//16 20//16 223 | f 19//17 22//17 23//17 224 | f 22//18 25//18 26//18 225 | f 25//19 28//19 29//19 226 | f 28//20 31//20 32//20 227 | f 31//16 34//16 35//16 228 | f 34//17 37//17 38//17 229 | f 37//18 40//18 41//18 230 | f 40//19 43//19 44//19 231 | f 43//20 46//20 47//20 232 | f 46//16 49//16 50//16 233 | f 49//17 52//17 53//17 234 | f 52//18 55//18 56//18 235 | f 55//19 58//19 59//19 236 | f 58//20 61//20 62//20 237 | f 61//16 64//16 65//16 238 | f 64//17 67//17 68//17 239 | f 67//18 70//18 71//18 240 | f 70//19 73//19 74//19 241 | f 73//20 76//20 77//20 242 | f 76//16 79//16 80//16 243 | f 79//17 82//17 83//17 244 | f 82//18 85//18 86//18 245 | f 85//19 88//19 89//19 246 | f 88//20 91//20 92//20 247 | f 3//21 2//21 5//21 248 | f 5//22 8//22 9//22 249 | f 8//23 11//23 12//23 250 | f 11//24 14//24 15//24 251 | f 15//24 14//24 17//24 252 | f 17//25 20//25 21//25 253 | f 21//25 20//25 23//25 254 | f 24//22 23//22 26//22 255 | f 26//24 29//24 30//24 256 | f 29//21 32//21 33//21 257 | f 33//21 32//21 35//21 258 | f 36//25 35//25 38//25 259 | f 39//22 38//22 41//22 260 | f 41//24 44//24 45//24 261 | f 44//21 47//21 48//21 262 | f 47//25 50//25 51//25 263 | f 50//22 53//22 54//22 264 | f 53//23 56//23 57//23 265 | f 56//24 59//24 60//24 266 | f 59//21 62//21 63//21 267 | f 63//21 62//21 65//21 268 | f 65//22 68//22 69//22 269 | f 68//23 71//23 72//23 270 | f 71//24 74//24 75//24 271 | f 75//24 74//24 77//24 272 | f 77//25 80//25 81//25 273 | f 80//22 83//22 84//22 274 | f 83//23 86//23 87//23 275 | f 86//24 89//24 90//24 276 | f 89//21 92//21 93//21 277 | f 1//26 3//26 6//26 278 | f 4//27 6//27 9//27 279 | f 7//28 9//28 12//28 280 | f 10//29 12//29 15//29 281 | f 13//30 15//30 18//30 282 | f 16//26 18//26 21//26 283 | f 19//27 21//27 24//27 284 | f 22//28 24//28 27//28 285 | f 25//29 27//29 30//29 286 | f 28//30 30//30 33//30 287 | f 31//26 33//26 36//26 288 | f 34//27 36//27 39//27 289 | f 37//28 39//28 42//28 290 | f 40//29 42//29 45//29 291 | f 43//30 45//30 48//30 292 | f 46//26 48//26 51//26 293 | f 49//27 51//27 54//27 294 | f 52//28 54//28 57//28 295 | f 55//29 57//29 60//29 296 | f 58//30 60//30 63//30 297 | f 61//26 63//26 66//26 298 | f 64//27 66//27 69//27 299 | f 67//28 69//28 72//28 300 | f 70//29 72//29 75//29 301 | f 73//30 75//30 78//30 302 | f 76//26 78//26 81//26 303 | f 79//27 81//27 84//27 304 | f 82//28 84//28 87//28 305 | f 85//29 87//29 90//29 306 | f 88//30 90//30 93//30 307 | -------------------------------------------------------------------------------- /res/models/SteeringWheel.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: 'SteeringWheel.blend' 2 | # www.blender.org 3 | v 0.020370 0.000001 -0.020370 4 | v -0.020370 0.000001 -0.020370 5 | v 0.020370 0.000001 0.020370 6 | v -0.020370 0.000001 0.020370 7 | v 0.020370 0.539132 -0.020370 8 | v -0.020370 0.539132 -0.020370 9 | v 0.020370 0.539132 0.020370 10 | v -0.020370 0.539131 0.020370 11 | v 0.000000 0.540585 0.130040 12 | v 0.025369 0.540585 0.127541 13 | v 0.049764 0.540585 0.120141 14 | v 0.072246 0.540585 0.108124 15 | v 0.091952 0.540585 0.091952 16 | v 0.108124 0.540585 0.072246 17 | v 0.120141 0.540585 0.049764 18 | v 0.127541 0.540585 0.025370 19 | v 0.130039 0.540585 0.000000 20 | v 0.127541 0.540585 -0.025369 21 | v 0.120141 0.540585 -0.049764 22 | v 0.108124 0.540585 -0.072246 23 | v 0.091952 0.540585 -0.091951 24 | v 0.072246 0.540585 -0.108123 25 | v 0.049764 0.540585 -0.120140 26 | v 0.025369 0.540585 -0.127540 27 | v -0.000000 0.540585 -0.130039 28 | v -0.025370 0.540585 -0.127540 29 | v -0.049764 0.540585 -0.120140 30 | v -0.072246 0.540585 -0.108123 31 | v -0.091952 0.540585 -0.091951 32 | v -0.108124 0.540585 -0.072246 33 | v -0.120141 0.540585 -0.049763 34 | v -0.127541 0.540585 -0.025369 35 | v -0.130039 0.540585 0.000000 36 | v -0.127541 0.540585 0.025370 37 | v -0.120141 0.540585 0.049764 38 | v -0.108124 0.540585 0.072246 39 | v -0.091951 0.540585 0.091952 40 | v -0.072246 0.540585 0.108124 41 | v -0.049764 0.540585 0.120141 42 | v -0.025369 0.540585 0.127541 43 | v 0.020427 0.539131 0.102691 44 | v 0.000000 0.539132 0.104703 45 | v 0.040068 0.539131 0.096733 46 | v 0.058170 0.539131 0.087058 47 | v 0.074036 0.539131 0.074036 48 | v 0.087057 0.539131 0.058170 49 | v 0.096733 0.539131 0.040068 50 | v 0.102691 0.539131 0.020427 51 | v 0.104703 0.539131 0.000000 52 | v 0.102691 0.539131 -0.020426 53 | v 0.096733 0.539131 -0.040068 54 | v 0.087057 0.539131 -0.058170 55 | v 0.074036 0.539131 -0.074036 56 | v 0.058170 0.539131 -0.087057 57 | v 0.040068 0.539131 -0.096733 58 | v 0.020426 0.539131 -0.102691 59 | v -0.000000 0.539131 -0.104703 60 | v -0.020427 0.539131 -0.102691 61 | v -0.040068 0.539131 -0.096733 62 | v -0.058170 0.539131 -0.087057 63 | v -0.074036 0.539131 -0.074036 64 | v -0.087057 0.539131 -0.058170 65 | v -0.096733 0.539131 -0.040068 66 | v -0.102691 0.539131 -0.020426 67 | v -0.104703 0.539131 0.000000 68 | v -0.102691 0.539131 0.020427 69 | v -0.096733 0.539131 0.040069 70 | v -0.087057 0.539131 0.058170 71 | v -0.074036 0.539131 0.074037 72 | v -0.058170 0.539131 0.087058 73 | v -0.040068 0.539131 0.096733 74 | v -0.020426 0.539132 0.102691 75 | v 0.020427 0.570249 0.102691 76 | v 0.000000 0.570249 0.104703 77 | v 0.040068 0.570249 0.096733 78 | v 0.058170 0.570249 0.087058 79 | v 0.074036 0.570249 0.074036 80 | v 0.087057 0.570249 0.058170 81 | v 0.096733 0.570248 0.040068 82 | v 0.102691 0.570248 0.020427 83 | v 0.104703 0.570249 0.000000 84 | v 0.102691 0.570249 -0.020426 85 | v 0.096733 0.570249 -0.040068 86 | v 0.087057 0.570249 -0.058170 87 | v 0.074036 0.570249 -0.074036 88 | v 0.058170 0.570249 -0.087057 89 | v 0.040068 0.570249 -0.096733 90 | v 0.020426 0.570249 -0.102691 91 | v -0.000000 0.570249 -0.104703 92 | v -0.020427 0.570249 -0.102691 93 | v -0.040068 0.570249 -0.096733 94 | v -0.058170 0.570249 -0.087057 95 | v -0.074036 0.570249 -0.074036 96 | v -0.087057 0.570249 -0.058170 97 | v -0.096733 0.570249 -0.040068 98 | v -0.102691 0.570249 -0.020426 99 | v -0.104703 0.570249 0.000000 100 | v -0.102691 0.570249 0.020427 101 | v -0.096733 0.570249 0.040069 102 | v -0.087057 0.570249 0.058170 103 | v -0.074036 0.570249 0.074037 104 | v -0.058170 0.570249 0.087058 105 | v -0.040068 0.570249 0.096733 106 | v -0.020426 0.570249 0.102691 107 | v 0.025369 0.568795 0.127541 108 | v 0.000000 0.568795 0.130040 109 | v 0.049764 0.568795 0.120141 110 | v 0.072246 0.568795 0.108124 111 | v 0.091952 0.568795 0.091952 112 | v 0.108124 0.568795 0.072246 113 | v 0.120141 0.568795 0.049764 114 | v 0.127541 0.568795 0.025370 115 | v 0.130039 0.568795 0.000000 116 | v 0.127541 0.568795 -0.025369 117 | v 0.120141 0.568795 -0.049764 118 | v 0.108124 0.568795 -0.072246 119 | v 0.091952 0.568795 -0.091951 120 | v 0.072246 0.568795 -0.108123 121 | v 0.049764 0.568795 -0.120140 122 | v 0.025369 0.568795 -0.127540 123 | v -0.000000 0.568795 -0.130039 124 | v -0.025370 0.568795 -0.127540 125 | v -0.049764 0.568795 -0.120140 126 | v -0.072246 0.568795 -0.108123 127 | v -0.091952 0.568795 -0.091951 128 | v -0.108124 0.568795 -0.072246 129 | v -0.120141 0.568795 -0.049763 130 | v -0.127541 0.568795 -0.025369 131 | v -0.130039 0.568795 0.000000 132 | v -0.127541 0.568795 0.025370 133 | v -0.120141 0.568795 0.049764 134 | v -0.108124 0.568795 0.072246 135 | v -0.091951 0.568795 0.091952 136 | v -0.072246 0.568795 0.108124 137 | v -0.049764 0.568795 0.120141 138 | v -0.025369 0.568795 0.127541 139 | v 0.020370 0.570249 0.020370 140 | v 0.020370 0.570249 -0.020370 141 | v -0.020370 0.570249 -0.020370 142 | v -0.020370 0.570249 0.020370 143 | v 0.000000 0.570249 0.020370 144 | v 0.000000 0.539131 0.020370 145 | v 0.020370 0.539132 -0.000000 146 | v -0.020370 0.539131 0.000000 147 | v 0.020370 0.570249 -0.000000 148 | v -0.020370 0.570249 0.000000 149 | v 0.020398 0.570249 0.061531 150 | v -0.020398 0.570249 0.061531 151 | v 0.020398 0.539131 0.061531 152 | v -0.020398 0.539131 0.061531 153 | v 0.060005 0.539131 0.020399 154 | v 0.060005 0.570249 0.020399 155 | v -0.060005 0.539131 0.020399 156 | v -0.060005 0.570249 0.020399 157 | vn 0.0000 -1.0000 0.0000 158 | vn 1.0000 0.0000 -0.0000 159 | vn -1.0000 0.0000 0.0000 160 | vn 0.0000 0.0000 1.0000 161 | vn -0.0000 0.0000 -1.0000 162 | vn 0.0167 -0.9983 0.0551 163 | vn 0.9569 0.0000 0.2903 164 | vn -0.0980 0.0000 -0.9952 165 | vn 0.8819 0.0000 0.4714 166 | vn 0.0980 0.0000 -0.9952 167 | vn 0.7730 0.0000 0.6344 168 | vn 0.2903 0.0000 -0.9569 169 | vn 0.6344 0.0000 0.7730 170 | vn 0.4714 0.0000 -0.8819 171 | vn 0.4714 0.0000 0.8819 172 | vn 0.6344 0.0000 -0.7730 173 | vn 0.2903 0.0000 0.9569 174 | vn 0.7730 0.0000 -0.6344 175 | vn 0.0980 0.0000 0.9952 176 | vn 0.8819 0.0000 -0.4714 177 | vn -0.0980 0.0000 0.9952 178 | vn 0.9569 0.0000 -0.2903 179 | vn -0.2903 0.0000 0.9569 180 | vn 0.9952 0.0000 -0.0980 181 | vn -0.4714 0.0000 0.8819 182 | vn 0.9952 0.0000 0.0980 183 | vn -0.6344 0.0000 0.7730 184 | vn -0.7730 0.0000 0.6344 185 | vn -0.8819 0.0000 0.4714 186 | vn -0.9569 0.0000 0.2903 187 | vn 0.0056 0.9983 0.0573 188 | vn 0.0167 0.9983 0.0551 189 | vn 0.0271 0.9983 0.0508 190 | vn 0.0365 0.9983 0.0445 191 | vn 0.0445 0.9983 0.0365 192 | vn 0.0508 0.9983 0.0271 193 | vn 0.0551 0.9983 0.0167 194 | vn 0.0573 0.9983 0.0057 195 | vn 0.0573 0.9983 -0.0056 196 | vn 0.0551 0.9983 -0.0167 197 | vn 0.0508 0.9983 -0.0271 198 | vn 0.0445 0.9983 -0.0365 199 | vn 0.0365 0.9983 -0.0445 200 | vn 0.0271 0.9983 -0.0508 201 | vn 0.0167 0.9983 -0.0551 202 | vn 0.0056 0.9983 -0.0573 203 | vn -0.0056 0.9983 -0.0573 204 | vn -0.0167 0.9983 -0.0551 205 | vn -0.0271 0.9983 -0.0508 206 | vn -0.0365 0.9983 -0.0445 207 | vn -0.0445 0.9983 -0.0365 208 | vn -0.0508 0.9983 -0.0271 209 | vn -0.0551 0.9983 -0.0167 210 | vn -0.0573 0.9983 -0.0056 211 | vn -0.0573 0.9983 0.0056 212 | vn -0.0551 0.9983 0.0167 213 | vn -0.0508 0.9983 0.0271 214 | vn -0.0445 0.9983 0.0365 215 | vn -0.0365 0.9983 0.0445 216 | vn -0.0271 0.9983 0.0508 217 | vn -0.0167 0.9983 0.0551 218 | vn -0.0056 0.9983 0.0573 219 | vn -0.2903 0.0000 -0.9569 220 | vn -0.9952 0.0000 -0.0980 221 | vn -0.4714 0.0000 -0.8819 222 | vn -0.9952 0.0000 0.0980 223 | vn -0.6344 0.0000 -0.7730 224 | vn -0.7730 0.0000 -0.6344 225 | vn -0.8819 0.0000 -0.4714 226 | vn -0.9569 0.0000 -0.2903 227 | vn 0.0271 -0.9983 0.0508 228 | vn 0.0365 -0.9983 0.0445 229 | vn 0.0445 -0.9983 0.0365 230 | vn 0.0508 -0.9983 0.0271 231 | vn 0.0551 -0.9983 0.0167 232 | vn 0.0573 -0.9983 0.0056 233 | vn 0.0573 -0.9983 -0.0056 234 | vn 0.0551 -0.9983 -0.0167 235 | vn 0.0508 -0.9983 -0.0271 236 | vn 0.0445 -0.9983 -0.0365 237 | vn 0.0365 -0.9983 -0.0445 238 | vn 0.0271 -0.9983 -0.0508 239 | vn 0.0167 -0.9983 -0.0551 240 | vn 0.0056 -0.9983 -0.0573 241 | vn -0.0056 -0.9983 -0.0573 242 | vn -0.0167 -0.9983 -0.0551 243 | vn -0.0271 -0.9983 -0.0508 244 | vn -0.0365 -0.9983 -0.0445 245 | vn -0.0445 -0.9983 -0.0365 246 | vn -0.0508 -0.9983 -0.0271 247 | vn -0.0551 -0.9983 -0.0167 248 | vn -0.0573 -0.9983 -0.0056 249 | vn -0.0573 -0.9983 0.0056 250 | vn -0.0551 -0.9983 0.0167 251 | vn -0.0508 -0.9983 0.0271 252 | vn -0.0445 -0.9983 0.0365 253 | vn -0.0365 -0.9983 0.0445 254 | vn -0.0271 -0.9983 0.0508 255 | vn -0.0167 -0.9983 0.0551 256 | vn -0.0056 -0.9983 0.0573 257 | vn 0.0056 -0.9983 0.0573 258 | vn -0.0000 1.0000 0.0000 259 | vn 1.0000 0.0000 -0.0007 260 | vn -1.0000 0.0000 -0.0007 261 | vn 0.0007 0.0000 -1.0000 262 | vn -0.0007 0.0000 -1.0000 263 | vn 0.0007 0.0000 1.0000 264 | vn -0.0007 0.0000 1.0000 265 | vn 1.0000 0.0000 0.0007 266 | vn -0.7203 0.0000 0.6936 267 | vn 0.7203 0.0000 0.6936 268 | vn 0.0573 0.9983 0.0056 269 | s off 270 | f 3//1 2//1 1//1 271 | f 5//2 145//2 143//2 272 | f 4//3 144//3 2//3 273 | f 1//2 143//2 3//2 274 | f 3//4 142//4 4//4 275 | f 2//5 5//5 1//5 276 | f 43//6 10//6 41//6 277 | f 95//7 64//7 63//7 278 | f 122//8 25//8 26//8 279 | f 94//9 63//9 62//9 280 | f 121//10 24//10 25//10 281 | f 93//11 62//11 61//11 282 | f 120//12 23//12 24//12 283 | f 92//13 61//13 60//13 284 | f 119//14 22//14 23//14 285 | f 91//15 60//15 59//15 286 | f 118//16 21//16 22//16 287 | f 90//17 59//17 58//17 288 | f 117//18 20//18 21//18 289 | f 89//19 58//19 57//19 290 | f 116//20 19//20 20//20 291 | f 88//21 57//21 56//21 292 | f 115//22 18//22 19//22 293 | f 87//23 56//23 55//23 294 | f 114//24 17//24 18//24 295 | f 86//25 55//25 54//25 296 | f 113//26 16//26 17//26 297 | f 85//27 54//27 53//27 298 | f 112//7 15//7 16//7 299 | f 84//28 53//28 52//28 300 | f 111//9 14//9 15//9 301 | f 106//21 40//21 9//21 302 | f 83//29 52//29 51//29 303 | f 110//11 13//11 14//11 304 | f 136//23 39//23 40//23 305 | f 82//30 51//30 50//30 306 | f 109//13 12//13 13//13 307 | f 135//25 38//25 39//25 308 | f 50//24 81//24 49//24 309 | f 106//31 73//31 74//31 310 | f 105//32 75//32 73//32 311 | f 75//33 108//33 76//33 312 | f 108//34 77//34 76//34 313 | f 109//35 78//35 77//35 314 | f 78//36 111//36 79//36 315 | f 111//37 80//37 79//37 316 | f 80//38 113//38 81//38 317 | f 113//39 82//39 81//39 318 | f 82//40 115//40 83//40 319 | f 115//41 84//41 83//41 320 | f 84//42 117//42 85//42 321 | f 117//43 86//43 85//43 322 | f 86//44 119//44 87//44 323 | f 119//45 88//45 87//45 324 | f 88//46 121//46 89//46 325 | f 121//47 90//47 89//47 326 | f 90//48 123//48 91//48 327 | f 123//49 92//49 91//49 328 | f 124//50 93//50 92//50 329 | f 125//51 94//51 93//51 330 | f 94//52 127//52 95//52 331 | f 127//53 96//53 95//53 332 | f 96//54 129//54 97//54 333 | f 129//55 98//55 97//55 334 | f 98//56 131//56 99//56 335 | f 131//57 100//57 99//57 336 | f 100//58 133//58 101//58 337 | f 133//59 102//59 101//59 338 | f 102//60 135//60 103//60 339 | f 135//61 104//61 103//61 340 | f 136//62 74//62 104//62 341 | f 123//63 26//63 27//63 342 | f 65//64 96//64 64//64 343 | f 124//65 27//65 28//65 344 | f 66//66 97//66 65//66 345 | f 125//67 28//67 29//67 346 | f 98//22 67//22 66//22 347 | f 126//68 29//68 30//68 348 | f 99//20 68//20 67//20 349 | f 42//19 73//19 74//19 350 | f 127//69 30//69 31//69 351 | f 100//18 69//18 68//18 352 | f 73//63 43//63 41//63 353 | f 128//70 31//70 32//70 354 | f 101//16 70//16 69//16 355 | f 75//65 44//65 43//65 356 | f 129//64 32//64 33//64 357 | f 102//14 71//14 70//14 358 | f 76//67 45//67 44//67 359 | f 130//66 33//66 34//66 360 | f 103//12 72//12 71//12 361 | f 77//68 46//68 45//68 362 | f 131//30 34//30 35//30 363 | f 42//21 104//21 72//21 364 | f 46//69 79//69 47//69 365 | f 132//29 35//29 36//29 366 | f 105//19 9//19 10//19 367 | f 79//70 48//70 47//70 368 | f 133//28 36//28 37//28 369 | f 107//17 10//17 11//17 370 | f 49//26 80//26 48//26 371 | f 134//27 37//27 38//27 372 | f 108//15 11//15 12//15 373 | f 44//71 11//71 43//71 374 | f 44//72 13//72 12//72 375 | f 46//73 13//73 45//73 376 | f 46//74 15//74 14//74 377 | f 48//75 15//75 47//75 378 | f 48//76 17//76 16//76 379 | f 50//77 17//77 49//77 380 | f 50//78 19//78 18//78 381 | f 52//79 19//79 51//79 382 | f 52//80 21//80 20//80 383 | f 54//81 21//81 53//81 384 | f 54//82 23//82 22//82 385 | f 56//83 23//83 55//83 386 | f 56//84 25//84 24//84 387 | f 58//85 25//85 57//85 388 | f 58//86 27//86 26//86 389 | f 60//87 27//87 59//87 390 | f 61//88 28//88 60//88 391 | f 62//89 29//89 61//89 392 | f 62//90 31//90 30//90 393 | f 64//91 31//91 63//91 394 | f 64//92 33//92 32//92 395 | f 66//93 33//93 65//93 396 | f 66//94 35//94 34//94 397 | f 68//95 35//95 67//95 398 | f 68//96 37//96 36//96 399 | f 70//97 37//97 69//97 400 | f 70//98 39//98 38//98 401 | f 72//99 39//99 71//99 402 | f 42//100 40//100 72//100 403 | f 42//101 10//101 9//101 404 | f 146//102 141//102 145//102 405 | f 142//4 141//4 140//4 406 | f 6//5 138//5 5//5 407 | f 144//3 140//3 146//3 408 | f 73//102 147//102 74//102 409 | f 141//102 148//102 74//102 410 | f 149//103 73//103 41//103 411 | f 72//104 148//104 150//104 412 | f 7//1 149//1 142//1 413 | f 42//1 150//1 142//1 414 | f 6//3 146//3 139//3 415 | f 138//102 146//102 145//102 416 | f 143//2 137//2 7//2 417 | f 143//1 50//1 49//1 418 | f 151//1 7//1 143//1 419 | f 7//105 152//105 151//105 420 | f 5//106 82//106 50//106 421 | f 145//102 82//102 138//102 422 | f 152//102 80//102 81//102 423 | f 144//1 8//1 153//1 424 | f 144//1 64//1 6//1 425 | f 153//107 140//107 154//107 426 | f 6//105 96//105 139//105 427 | f 146//102 96//102 97//102 428 | f 154//102 146//102 97//102 429 | f 66//107 154//107 98//107 430 | f 80//108 151//108 48//108 431 | f 140//109 150//109 8//109 432 | f 7//103 147//103 149//103 433 | f 8//1 150//1 153//1 434 | f 150//110 154//110 153//110 435 | f 154//102 148//102 140//102 436 | f 7//1 151//1 149//1 437 | f 152//111 149//111 151//111 438 | f 147//102 152//102 137//102 439 | f 3//1 4//1 2//1 440 | f 5//2 138//2 145//2 441 | f 6//3 2//3 144//3 442 | f 4//3 8//3 144//3 443 | f 7//2 3//2 143//2 444 | f 1//2 5//2 143//2 445 | f 8//4 4//4 142//4 446 | f 3//4 7//4 142//4 447 | f 2//5 6//5 5//5 448 | f 43//6 11//6 10//6 449 | f 95//7 96//7 64//7 450 | f 122//8 121//8 25//8 451 | f 94//9 95//9 63//9 452 | f 121//10 120//10 24//10 453 | f 93//11 94//11 62//11 454 | f 120//12 119//12 23//12 455 | f 92//13 93//13 61//13 456 | f 119//14 118//14 22//14 457 | f 91//15 92//15 60//15 458 | f 118//16 117//16 21//16 459 | f 90//17 91//17 59//17 460 | f 117//18 116//18 20//18 461 | f 89//19 90//19 58//19 462 | f 116//20 115//20 19//20 463 | f 88//21 89//21 57//21 464 | f 115//22 114//22 18//22 465 | f 87//23 88//23 56//23 466 | f 114//24 113//24 17//24 467 | f 86//25 87//25 55//25 468 | f 113//26 112//26 16//26 469 | f 85//27 86//27 54//27 470 | f 112//7 111//7 15//7 471 | f 84//28 85//28 53//28 472 | f 111//9 110//9 14//9 473 | f 106//21 136//21 40//21 474 | f 83//29 84//29 52//29 475 | f 110//11 109//11 13//11 476 | f 136//23 135//23 39//23 477 | f 82//30 83//30 51//30 478 | f 109//13 108//13 12//13 479 | f 135//25 134//25 38//25 480 | f 50//24 82//24 81//24 481 | f 106//31 105//31 73//31 482 | f 105//32 107//32 75//32 483 | f 75//33 107//33 108//33 484 | f 108//34 109//34 77//34 485 | f 109//35 110//35 78//35 486 | f 78//36 110//36 111//36 487 | f 111//37 112//37 80//37 488 | f 80//112 112//112 113//112 489 | f 113//39 114//39 82//39 490 | f 82//40 114//40 115//40 491 | f 115//41 116//41 84//41 492 | f 84//42 116//42 117//42 493 | f 117//43 118//43 86//43 494 | f 86//44 118//44 119//44 495 | f 119//45 120//45 88//45 496 | f 88//46 120//46 121//46 497 | f 121//47 122//47 90//47 498 | f 90//48 122//48 123//48 499 | f 123//49 124//49 92//49 500 | f 124//50 125//50 93//50 501 | f 125//51 126//51 94//51 502 | f 94//52 126//52 127//52 503 | f 127//53 128//53 96//53 504 | f 96//54 128//54 129//54 505 | f 129//55 130//55 98//55 506 | f 98//56 130//56 131//56 507 | f 131//57 132//57 100//57 508 | f 100//58 132//58 133//58 509 | f 133//59 134//59 102//59 510 | f 102//60 134//60 135//60 511 | f 135//61 136//61 104//61 512 | f 136//62 106//62 74//62 513 | f 123//63 122//63 26//63 514 | f 65//64 97//64 96//64 515 | f 124//65 123//65 27//65 516 | f 66//66 98//66 97//66 517 | f 125//67 124//67 28//67 518 | f 98//22 99//22 67//22 519 | f 126//68 125//68 29//68 520 | f 99//20 100//20 68//20 521 | f 42//19 41//19 73//19 522 | f 127//69 126//69 30//69 523 | f 100//18 101//18 69//18 524 | f 73//63 75//63 43//63 525 | f 128//70 127//70 31//70 526 | f 101//16 102//16 70//16 527 | f 75//65 76//65 44//65 528 | f 129//64 128//64 32//64 529 | f 102//14 103//14 71//14 530 | f 76//67 77//67 45//67 531 | f 130//66 129//66 33//66 532 | f 103//12 104//12 72//12 533 | f 77//68 78//68 46//68 534 | f 131//30 130//30 34//30 535 | f 42//21 74//21 104//21 536 | f 46//69 78//69 79//69 537 | f 132//29 131//29 35//29 538 | f 105//19 106//19 9//19 539 | f 79//70 80//70 48//70 540 | f 133//28 132//28 36//28 541 | f 107//17 105//17 10//17 542 | f 49//26 81//26 80//26 543 | f 134//27 133//27 37//27 544 | f 108//15 107//15 11//15 545 | f 44//71 12//71 11//71 546 | f 44//72 45//72 13//72 547 | f 46//73 14//73 13//73 548 | f 46//74 47//74 15//74 549 | f 48//75 16//75 15//75 550 | f 48//76 49//76 17//76 551 | f 50//77 18//77 17//77 552 | f 50//78 51//78 19//78 553 | f 52//79 20//79 19//79 554 | f 52//80 53//80 21//80 555 | f 54//81 22//81 21//81 556 | f 54//82 55//82 23//82 557 | f 56//83 24//83 23//83 558 | f 56//84 57//84 25//84 559 | f 58//85 26//85 25//85 560 | f 58//86 59//86 27//86 561 | f 60//87 28//87 27//87 562 | f 61//88 29//88 28//88 563 | f 62//89 30//89 29//89 564 | f 62//90 63//90 31//90 565 | f 64//91 32//91 31//91 566 | f 64//92 65//92 33//92 567 | f 66//93 34//93 33//93 568 | f 66//94 67//94 35//94 569 | f 68//95 36//95 35//95 570 | f 68//96 69//96 37//96 571 | f 70//97 38//97 37//97 572 | f 70//98 71//98 39//98 573 | f 72//99 40//99 39//99 574 | f 42//100 9//100 40//100 575 | f 42//101 41//101 10//101 576 | f 137//102 145//102 141//102 577 | f 146//102 140//102 141//102 578 | f 140//4 8//4 142//4 579 | f 142//4 7//4 137//4 580 | f 137//4 141//4 142//4 581 | f 6//5 139//5 138//5 582 | f 144//3 8//3 140//3 583 | f 141//102 74//102 147//102 584 | f 147//102 137//102 141//102 585 | f 104//102 74//102 148//102 586 | f 141//102 140//102 148//102 587 | f 149//103 147//103 73//103 588 | f 72//104 104//104 148//104 589 | f 42//1 142//1 149//1 590 | f 149//1 41//1 42//1 591 | f 8//1 142//1 150//1 592 | f 42//1 72//1 150//1 593 | f 6//3 144//3 146//3 594 | f 138//102 139//102 146//102 595 | f 143//2 145//2 137//2 596 | f 143//1 5//1 50//1 597 | f 143//1 49//1 151//1 598 | f 48//1 151//1 49//1 599 | f 7//105 137//105 152//105 600 | f 5//106 138//106 82//106 601 | f 145//102 81//102 82//102 602 | f 81//102 145//102 152//102 603 | f 137//102 152//102 145//102 604 | f 153//1 66//1 65//1 605 | f 65//1 144//1 153//1 606 | f 144//1 65//1 64//1 607 | f 153//107 8//107 140//107 608 | f 6//105 64//105 96//105 609 | f 146//102 139//102 96//102 610 | f 97//102 98//102 154//102 611 | f 154//102 140//102 146//102 612 | f 66//107 153//107 154//107 613 | f 80//108 152//108 151//108 614 | f 140//109 148//109 150//109 615 | f 7//103 137//103 147//103 616 | f 150//110 148//110 154//110 617 | f 152//111 147//111 149//111 618 | -------------------------------------------------------------------------------- /res/shaders/body.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 colour; 4 | in vec4 viewSpace; 5 | in vec3 skyColour_v; 6 | 7 | out vec4 outputColour; 8 | 9 | uniform float fogDensity; 10 | uniform float fogGradient; 11 | 12 | float getFogModifier() { 13 | float 14 | distance = length(viewSpace), 15 | fogModifier = exp(-pow((distance * fogDensity), fogGradient)); 16 | 17 | return clamp(fogModifier, 0.0f, 1.0f); 18 | } 19 | 20 | void main(){ 21 | outputColour = vec4(mix(skyColour_v, colour, getFogModifier()), 1.0f); 22 | //outputColour = vec4(vec3(1.0f, 0.0f, 0.0f) * getFogModifier(), 1.0f); 23 | } -------------------------------------------------------------------------------- /res/shaders/body.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec3 a_Position; 4 | layout (location = 2) in vec3 a_Normal; 5 | 6 | out vec3 colour; 7 | out vec4 viewSpace; 8 | out vec3 skyColour_v; 9 | 10 | uniform mat4 modelMatrix; 11 | uniform mat4 viewMatrix; 12 | uniform mat4 projectionMatrix; 13 | 14 | uniform vec3 skyColour; 15 | uniform vec3 sunDirection; 16 | uniform vec3 bodyColour; 17 | 18 | void main(){ 19 | float lighting = max(dot(a_Normal, inverse(mat3(modelMatrix)) * sunDirection), 0.0f); 20 | colour = mix(skyColour * 0.1, bodyColour, lighting); 21 | skyColour_v = skyColour; 22 | 23 | viewSpace = viewMatrix * modelMatrix * vec4(a_Position, 1.0f); 24 | gl_Position = projectionMatrix * viewSpace; 25 | } -------------------------------------------------------------------------------- /res/shaders/debug.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec4 colour; 4 | 5 | out vec4 outputColour; 6 | 7 | void main(){ 8 | outputColour = colour; 9 | } -------------------------------------------------------------------------------- /res/shaders/debug.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec3 a_Position; 4 | layout (location = 1) in vec4 a_Colour; 5 | 6 | out vec4 colour; 7 | 8 | uniform mat4 modelMatrix; 9 | uniform mat4 viewMatrix; 10 | uniform mat4 projectionMatrix; 11 | 12 | void main(){ 13 | colour = a_Colour; 14 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(a_Position, 1.0f); 15 | } -------------------------------------------------------------------------------- /res/shaders/skyBox.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 vertexPosition; 4 | 5 | out vec4 colour; 6 | 7 | uniform vec3 skyColour; 8 | uniform vec3 sunColour; 9 | uniform vec3 sunDir; 10 | 11 | void main() { 12 | const float 13 | b1 = 0.0005f, //0.0005f 14 | b2 = 0.0005f, //0.0005f 15 | b3 = 0.8f, //0.8f 16 | b2Intensity = 0.9f; //0.7f 17 | 18 | float 19 | intensity = 0.0f, 20 | betweenSun = 1.0f - max(dot(normalize(vertexPosition), normalize(sunDir)), 0.0f); 21 | 22 | if(betweenSun < b1) 23 | intensity = 1.0f; 24 | else if(betweenSun > b1 && betweenSun < b1 + b2) 25 | intensity = b2Intensity + 1.0f - (betweenSun - b1) / b2; 26 | else if(betweenSun > b1 + b2 && betweenSun < b1 + b2 + b3) 27 | intensity = (1.0f - (betweenSun - b2 - b2) / b3) * b2Intensity; 28 | 29 | intensity = clamp(intensity, 0.0f, 1.0f); 30 | 31 | colour = vec4(mix(skyColour, sunColour, intensity), 1.0f); 32 | } -------------------------------------------------------------------------------- /res/shaders/skyBox.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 a_Position; 3 | 4 | out vec3 vertexPosition; 5 | 6 | uniform mat4 projectionMatrix; 7 | uniform mat4 viewMatrix; 8 | 9 | void main() { 10 | vertexPosition = a_Position; 11 | gl_Position = projectionMatrix * viewMatrix * vec4(a_Position, 1.0f); 12 | } -------------------------------------------------------------------------------- /res/shaders/terrain.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 terrainColour; 4 | in vec4 viewSpace; 5 | in vec3 skyColour_v; 6 | 7 | out vec4 outputColour; 8 | 9 | uniform float fogDensity; 10 | uniform float fogGradient; 11 | 12 | float getFogModifier() { 13 | float 14 | distance = length(viewSpace), 15 | fogModifier = exp(-pow((distance * fogDensity), fogGradient)); 16 | 17 | return clamp(fogModifier, 0.0f, 1.0f); 18 | } 19 | 20 | void main(){ 21 | outputColour = vec4(mix(skyColour_v, terrainColour, getFogModifier()), 1.0f); 22 | } -------------------------------------------------------------------------------- /res/shaders/terrain.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec3 a_Position; 4 | layout (location = 1) in vec3 a_Normal; 5 | layout (location = 2) in vec3 a_Colour; 6 | 7 | out vec3 terrainColour; 8 | out vec4 viewSpace; 9 | out vec3 skyColour_v; 10 | 11 | uniform mat4 modelMatrix; 12 | uniform mat4 viewMatrix; 13 | uniform mat4 projectionMatrix; 14 | 15 | uniform vec3 sunDirection; 16 | uniform vec3 skyColour; 17 | 18 | void main(){ 19 | float lighting = max(dot(a_Normal, sunDirection), 0.0f); 20 | terrainColour = mix(skyColour * 0.1f, a_Colour, lighting); 21 | skyColour_v = skyColour; 22 | 23 | viewSpace = viewMatrix * modelMatrix * vec4(a_Position, 1.0f); 24 | gl_Position = projectionMatrix * viewSpace; 25 | } -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbowes/vehicle-dynamics-simulation/773fcc527fa3858f186e0165613d9bbaf8b1759c/screenshots/screenshot.png -------------------------------------------------------------------------------- /src/AllCameras.cpp: -------------------------------------------------------------------------------- 1 | #include "AllCameras.h" 2 | #include "Environment.h" 3 | 4 | namespace Visual { 5 | 6 | SimulationCamera::SimulationCamera(glm::vec3 position_OGL, glm::vec3 direction_OGL, float near, float far, float aspect, float FOV) : 7 | /* Called by 8 | * - FPVCamera::FPVCamera 9 | * - FrontWheelCamera::FrontWheelCamera 10 | * - DriverCamera::DriverCamera 11 | */ 12 | mPerspectiveCamera(position_OGL, direction_OGL, glm::vec3(0.0f, 1.0f, 0.0f), near, far, aspect, FOV) 13 | { } 14 | 15 | FPVCamera::FPVCamera(glm::vec3 position_OGL, glm::vec3 direction_OGL, float near, float far, float aspect, float FOV) : 16 | /* Called by CameraSystem::addAllCameras 17 | * Initialises camera's state ready to view the simulation from the correct position/direction 18 | */ 19 | SimulationCamera(position_OGL, direction_OGL, near, far, aspect, FOV), 20 | mPosition_OGL(position_OGL), 21 | mDirection_OGL(direction_OGL) 22 | { 23 | using namespace glm; 24 | 25 | //Calculating intial yaw and pitch values from direction vector 26 | vec3 horizontalDirection = normalize(vec3(direction_OGL.x, 0.0f, direction_OGL.z)); 27 | mYaw = degrees(orientedAngle(horizontalDirection, vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f))); 28 | mPitch = degrees(asin(normalize(direction_OGL).y)); 29 | } 30 | 31 | void FPVCamera::update(float windowAspect, float dt) 32 | /* Called by CameraSystem::update 33 | */ 34 | { 35 | //If the window is resized between updates, then the camera's aspect ratio must be changed 36 | mPerspectiveCamera.setAspect(windowAspect); 37 | 38 | //Recalculates position 39 | mPosition_OGL += mVelocity_OGL * dt; 40 | 41 | //Ensures that the new position is not below the terrain, or outside its borders 42 | mPosition_OGL = afterPositionConstraints(mPosition_OGL); 43 | 44 | //Updates the internal position of the camera with this new position 45 | mPerspectiveCamera.setPosition(mPosition_OGL); 46 | 47 | //Applies friction to the camera movement 48 | mVelocity_OGL *= 1.0f / (1.0f + (dt * mMovementFriction)); 49 | } 50 | 51 | void FPVCamera::handleInput(float dt) 52 | /* Called by CameraSystem::checkInput 53 | */ 54 | { 55 | handleDirectionInput(mPerspectiveCamera, mYaw, mPitch, mDirection_OGL, mLookAroundSensitivity); 56 | handleMovementInput(dt); 57 | handleZoomInput(mPerspectiveCamera, mZoomSensitivity); 58 | } 59 | 60 | void FPVCamera::handleMovementInput(float dt) 61 | /* Called by FPVCamera::handleInput 62 | * Deals with all user input that affects camera translation 63 | */ 64 | { 65 | using namespace Framework; 66 | using namespace glm; 67 | 68 | if (Input::isKeyPressed(GLFW_KEY_W)) mVelocity_OGL += normalize(vec3(mDirection_OGL.x, 0.0f, mDirection_OGL.z)) * mMovementSpeed * dt; 69 | if (Input::isKeyPressed(GLFW_KEY_S)) mVelocity_OGL -= normalize(vec3(mDirection_OGL.x, 0.0f, mDirection_OGL.z)) * mMovementSpeed * dt; 70 | if (Input::isKeyPressed(GLFW_KEY_A)) mVelocity_OGL -= normalize(cross(mDirection_OGL, vec3(0.0f, 1.0f, 0.0f))) * mMovementSpeed * dt; 71 | if (Input::isKeyPressed(GLFW_KEY_D)) mVelocity_OGL += normalize(cross(mDirection_OGL, vec3(0.0f, 1.0f, 0.0f))) * mMovementSpeed * dt; 72 | if (Input::isKeyPressed(GLFW_KEY_SPACE)) mVelocity_OGL.y += mMovementSpeed * dt; 73 | if (Input::isKeyPressed(GLFW_KEY_LEFT_SHIFT)) mVelocity_OGL.y -= mMovementSpeed * dt; 74 | } 75 | 76 | glm::dvec3 FPVCamera::afterPositionConstraints(glm::dvec3 input) 77 | /* Called by FPVCamera::update 78 | */ 79 | { 80 | glm::dvec3 output = input; 81 | 82 | //Terrain surface constraint 83 | double terrainHeight = External::Environment::mTerrain.getHeight(glm::dvec2(input.x, input.z)); 84 | if (input.y < terrainHeight + 0.1f) 85 | output.y = terrainHeight + 0.1f; 86 | 87 | //Terrain border constraints 88 | double halfTerrainSize = floor(External::Environment::mTerrain.getSize() * 0.5); 89 | if (input.x < -halfTerrainSize) output.x = -halfTerrainSize; 90 | else if (input.x > halfTerrainSize) output.x = halfTerrainSize; 91 | if (input.z < -halfTerrainSize) output.z = -halfTerrainSize; 92 | else if (input.z > halfTerrainSize) output.z = halfTerrainSize; 93 | 94 | return output; 95 | } 96 | 97 | FrontWheelCamera::FrontWheelCamera(glm::vec3 position_car, glm::vec3 direction_car, float near, float far, float aspect, float FOV) : 98 | /* Called by CameraSystem::addAllCameras 99 | * Initialises camera's state ready to view the simulation from the correct position/direction 100 | */ 101 | SimulationCamera(position_car, direction_car, near, far, aspect, FOV) 102 | { } 103 | 104 | void FrontWheelCamera::update(float windowAspect, glm::mat4 localToWorld_car, glm::quat localToWorldRotation_car) 105 | /* Called by CameraSystem::update 106 | */ 107 | { 108 | mPerspectiveCamera.setAspect(windowAspect); 109 | 110 | //The position and orientation of this camera are tied to the state of the Car, rather than user input 111 | mPerspectiveCamera.transformPosition(localToWorld_car); 112 | mPerspectiveCamera.rotate(localToWorldRotation_car); 113 | } 114 | 115 | DriverCamera::DriverCamera(glm::vec3 position_car, glm::vec3 direction_car, float near, float far, float aspect, float FOV) : 116 | /* Called by CameraSystem::addAllCameras 117 | * Initialises camera's state ready to view the simulation from the correct position/direction 118 | */ 119 | SimulationCamera(position_car, direction_car, near, far, aspect, FOV), 120 | mDirection_OGL(direction_car) 121 | { 122 | using namespace glm; 123 | 124 | //Calculating intial yaw and pitch values from direction vector 125 | vec3 horizontalDirection = normalize(vec3(direction_car.x, 0.0f, direction_car.z)); 126 | mYaw = degrees(orientedAngle(horizontalDirection, vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f))); 127 | mPitch = degrees(asin(normalize(direction_car).y)); 128 | } 129 | 130 | void DriverCamera::update(float windowAspect, glm::mat4 localToWorld_car, glm::quat localToWorldRotation_car) 131 | /* Called by CameraSystem::update 132 | */ 133 | { 134 | mPerspectiveCamera.setAspect(windowAspect); 135 | 136 | //The position and up vectors of this camera are tied to the state of the Car... 137 | mPerspectiveCamera.transformPosition(localToWorld_car); 138 | mPerspectiveCamera.rotateUp(localToWorldRotation_car); 139 | 140 | //...but the user can still look around inside the vehicle 141 | mPerspectiveCamera.setFront(glm::vec3(localToWorldRotation_car * glm::vec4(mDirection_OGL, 1.0f))); 142 | } 143 | 144 | void DriverCamera::handleInput(float dt) 145 | /* Called by CameraSystem::checkInput 146 | */ 147 | { 148 | handleDirectionInput(mPerspectiveCamera, mYaw, mPitch, mDirection_OGL, mLookAroundSensitivity); 149 | handleZoomInput(mPerspectiveCamera, mZoomSensitivity); 150 | } 151 | 152 | void handleDirectionInput(Framework::PerspectiveCamera& camHandle, float& yawHandle, float& pitchHandle, glm::vec3& directionHandle, float sensitivity) 153 | /* Called by 154 | * - FPVCamera::handleInput 155 | * - DriverCamera::handleInput 156 | * Used by multiple camera classes 157 | */ 158 | { 159 | glm::vec2 mouseDelta = Framework::Input::getMouseDelta(); 160 | 161 | yawHandle += mouseDelta.x * sensitivity; 162 | pitchHandle -= mouseDelta.y * sensitivity; 163 | 164 | if (pitchHandle > 89.0f) pitchHandle = 89.0f; 165 | if (pitchHandle < -89.0f) pitchHandle = -89.0f; 166 | 167 | directionHandle.x = cos(glm::radians(pitchHandle)) * cos(glm::radians(yawHandle)); 168 | directionHandle.y = sin(glm::radians(pitchHandle)); 169 | directionHandle.z = cos(glm::radians(pitchHandle)) * sin(glm::radians(yawHandle)); 170 | directionHandle = normalize(directionHandle); 171 | 172 | camHandle.setFront(directionHandle); 173 | } 174 | 175 | void handleZoomInput(Framework::PerspectiveCamera& camHandle, float sensitivity) 176 | /* Called by 177 | * - FPVCamera::handleInput 178 | * - DriverCamera::handleInput 179 | * - Used by multiple camera classes 180 | */ 181 | { 182 | float 183 | mouseScroll = Framework::Input::getMouseScroll(), 184 | currentFOV = camHandle.getFOVY(), 185 | newTargetFOV = currentFOV -= mouseScroll * sensitivity; 186 | 187 | if (newTargetFOV >= 44.0f && newTargetFOV <= 46.0f) 188 | camHandle.setFOVY(currentFOV -= mouseScroll * sensitivity); 189 | 190 | if (Framework::Input::isMouseButtonReleased(GLFW_MOUSE_BUTTON_5) || Framework::Input::isMouseButtonReleased(GLFW_MOUSE_BUTTON_MIDDLE)) 191 | camHandle.setFOVY(45.0f); 192 | } 193 | 194 | } -------------------------------------------------------------------------------- /src/CameraSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "CameraSystem.h" 2 | #include "Car.h" 3 | 4 | namespace Visual { 5 | 6 | CameraSystem::CameraSystem(float windowAspect) 7 | /* Called by VisualShell::VisualShell 8 | */ 9 | { 10 | addAllCameras(windowAspect); 11 | } 12 | 13 | void CameraSystem::update(float windowAspect, float dt, Internal::Car& cameraTarget) 14 | /* Called by VisualShell::update 15 | */ 16 | { 17 | FPV_CAM->update(windowAspect, dt); 18 | 19 | //The front wheel camera and the driver camera are each bound to the car in some way 20 | glm::mat4 carToWorldTransform = cameraTarget.getState().getLocalToWorld_position(); 21 | glm::quat carToWorldRotation = cameraTarget.getState().getOrientation_world(); 22 | 23 | FRONT_WHEEL_CAM->update(windowAspect, carToWorldTransform, carToWorldRotation); 24 | DRIVER_CAM->update(windowAspect, carToWorldTransform, carToWorldRotation); 25 | } 26 | 27 | void CameraSystem::checkInput(float dt) 28 | /* Called by VisualShell::checkInput 29 | */ 30 | { 31 | if (Framework::Input::isKeyReleased(GLFW_KEY_C)) 32 | cycleCameras(true); 33 | 34 | if (!mHasFocus) return; 35 | 36 | if (mCurrentCameraName == FPV) 37 | FPV_CAM->handleInput(dt); 38 | else if (mCurrentCameraName == DRIVER) 39 | DRIVER_CAM->handleInput(dt); 40 | } 41 | 42 | void CameraSystem::cycleCameras(bool left) 43 | /* Called by CameraSystem::checkInput 44 | * - CameraSystem::checkInput 45 | * - UILayer::mainControlPanel 46 | */ 47 | { 48 | if (left) 49 | mCurrentCameraName = mCurrentCameraName + 1 > DRIVER ? FPV : mCurrentCameraName + 1; 50 | else 51 | mCurrentCameraName = mCurrentCameraName - 1 < FPV ? DRIVER : mCurrentCameraName - 1; 52 | } 53 | 54 | void CameraSystem::addAllCameras(float windowAspect) 55 | /* Called by CameraSystem::CameraSystem 56 | * Defines the initial states of the SimulationCameras 57 | */ 58 | { 59 | mCameras.push_back(std::make_unique( 60 | glm::vec3(13.513960f, -1.0f, -3.806919f), 61 | glm::vec3(-0.711257f, 0.125334f, 0.691669f), 62 | 0.01f, 63 | 400.0f, 64 | windowAspect, 65 | 45.0f 66 | )); 67 | 68 | mCameras.push_back(std::make_unique( 69 | glm::vec3(1.417804f, 0.665811f, -0.026093f), 70 | glm::vec3(-0.314092f, -0.224951f, -0.922357f), 71 | 0.01f, 72 | 1000.0f, 73 | windowAspect, 74 | 45.0f 75 | )); 76 | 77 | mCameras.push_back(std::make_unique( 78 | glm::vec3(0.5f, 1.0f, 0.0f), 79 | glm::vec3(0.0f, 0.0f, -1.0f), 80 | 0.01f, 81 | 1000.0f, 82 | windowAspect, 83 | 45.3f 84 | )); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/Car.cpp: -------------------------------------------------------------------------------- 1 | #include "Car.h" 2 | 3 | namespace Internal { 4 | 5 | Car::Car() : 6 | /* Called during VehicleSimulation::VehicleSimulation 7 | * Fully sets up this object ready for the start of the simulation 8 | */ 9 | RigidBody(Framework::Physics::RigidBody::IntegrationMethod::EULER) 10 | { 11 | assemble(); 12 | resetToTrackPosition(); 13 | } 14 | 15 | void Car::update(double t, double dt) 16 | /* Called by VehicleSimulation::onUpdate 17 | * The core update function for the Car's simulation 18 | * Updates member objects and then updates own internal physical state 19 | */ 20 | { 21 | //Member objects updated 22 | mControlSystem.update(mWheelSystem.getWheelBase(), mWheelSystem.getAxle(WheelSystem::AxlePos::FRONT).getLength()); 23 | mTorqueGenerator->update(); 24 | mWheelSystem.update(mState, mAcceleration, dt); 25 | 26 | //State advanced by dt seconds 27 | integrate(t, dt); 28 | 29 | positionConstraints(); 30 | } 31 | 32 | void Car::checkInput(double dt) 33 | /* Called by VehicleSimulation::onInputCheck 34 | * Passes main input handling responsibility to mControlSystem 35 | */ 36 | { 37 | using namespace Framework; 38 | 39 | mControlSystem.handleInput(dt); 40 | 41 | if (Input::isKeyPressed(GLFW_KEY_R)) { 42 | resetToTrackPosition(); 43 | mWheelSystem.reset(); 44 | } 45 | } 46 | 47 | void Car::resetToTrackPosition() 48 | /* Called by 49 | * - Car::Car 50 | * - Car::checkInput 51 | * - UILayer::mainControlPanel 52 | */ 53 | { 54 | mState.reset(); 55 | mState.setPosition_world(glm::dvec3(10.0, -1.0, 0.0)); 56 | } 57 | 58 | void Car::updateTotalForce_world() 59 | /* Called by Car::getForce_world 60 | * Responsible for summating all forces affecting the Car 61 | */ 62 | { 63 | mTotalForce_world = glm::dvec3(0.0); 64 | mAerodynamicDrag_world = glm::dvec3(0.0); 65 | 66 | glm::dvec3 velocity_world = mState.getVelocity_world(); 67 | 68 | if(glm::length(velocity_world)) 69 | mAerodynamicDrag_world = -0.5 * pow(glm::length(velocity_world), 2.0) * mDragCoefficient * mFrontalArea * External::Environment::mAirDensity * glm::normalize(velocity_world); 70 | 71 | mTotalForce_world += glm::dvec3(0.0, mState.getMass().getValue() * -External::Environment::mGravityAccel, 0.0); //Gravity 72 | mTotalForce_world += mWheelSystem.getTotalForce_world(); //Wheel interfaces 73 | mTotalForce_world += mAerodynamicDrag_world; //Aerodynamic drag 74 | } 75 | 76 | glm::dvec3 Car::getForce_world(Framework::Physics::State& state, double t) 77 | /* Called by RigidBody::integrate 78 | * Pure virtual function, inherited from RigidBody 79 | */ 80 | { 81 | updateTotalForce_world(); 82 | return mTotalForce_world; 83 | } 84 | 85 | void Car::updateTotalTorque_world() 86 | /* Called by Car::getTorque_world 87 | * Responsible for summating all torques affecting the Car 88 | */ 89 | { 90 | //Since all other forces on the Car are modelling as acting through the centre of mass, the wheels are the only 91 | //objects that generate a torque 92 | mTotalTorque_world = mWheelSystem.getTotalTorque_world(); //Wheels 93 | } 94 | 95 | glm::dvec3 Car::getTorque_world(Framework::Physics::State& state, double t) 96 | /* Called by RigidBody::integrate 97 | * Pure virtual function, inherited from RigidBody 98 | */ 99 | { 100 | updateTotalTorque_world(); 101 | return mTotalTorque_world; 102 | } 103 | 104 | void Car::positionConstraints() 105 | /* Called by Car::update 106 | * Point collision with the terrain's surface, and a boundary constraint at edges of the terrain 107 | * Corrective action after physics state update 108 | */ 109 | { 110 | using namespace External; 111 | using namespace glm; 112 | 113 | dvec3 114 | positionInitial = mState.getPosition_world(), 115 | velocityInitial = mState.getVelocity_world(), 116 | newPosition = positionInitial, 117 | newVelocity = velocityInitial; 118 | 119 | const double planeHeight = Environment::mTerrain.getHeight(dvec2(positionInitial.x, positionInitial.z)); 120 | 121 | //Terrain collision 122 | if (positionInitial.y < planeHeight) { 123 | double beneathTerrain = planeHeight - positionInitial.y; 124 | newPosition.y += beneathTerrain; 125 | newVelocity.y *= -0.3; 126 | } 127 | 128 | //Playable region limits 129 | double halfTerrainSize = floor(Environment::mTerrain.getSize() * 0.5); 130 | if (positionInitial.x < -halfTerrainSize) { 131 | newPosition.x = -halfTerrainSize; 132 | 133 | //This causes the Car to bounce of the invisible walls at terrain edges 134 | newVelocity.x *= -0.5; 135 | } 136 | else if (positionInitial.x > halfTerrainSize) { 137 | newPosition.x = halfTerrainSize; 138 | newVelocity.x *= -0.5; 139 | } 140 | 141 | if (positionInitial.z < -halfTerrainSize) { 142 | newPosition.z = -halfTerrainSize; 143 | newVelocity.z *= -0.5; 144 | } 145 | else if (positionInitial.z > halfTerrainSize) { 146 | newPosition.z = halfTerrainSize; 147 | newVelocity.z *= 0.5; 148 | } 149 | 150 | mState.setPosition_world(newPosition); 151 | mState.setVelocity_world(newVelocity); 152 | } 153 | 154 | void Car::assemble() 155 | /* Called by Car::Car 156 | * Crucial that this is called before the simulation begins 157 | * 'Wires together' sub components of the Car 158 | */ 159 | { 160 | using namespace Internal; 161 | 162 | const double 163 | minTurningRadius = 3.0, 164 | steeringRatio = 13.0, 165 | wheelBase = mWheelSystem.getWheelBase(), 166 | maxAbsoluteAngle = glm::degrees(asin(wheelBase / sqrt(pow(minTurningRadius, 2) + pow(wheelBase, 2)))); 167 | 168 | //Add rear engine 169 | mTorqueGenerator = std::make_unique(5500.0, 4000.0); 170 | 171 | //...and attach it to the rear axle 172 | Axle* tempAxle = &mWheelSystem.getAxle(WheelSystem::REAR); 173 | tempAxle->attachTorqueGenerator(mTorqueGenerator.get()); 174 | 175 | //Set geometrical properties of car 176 | mWheelSystem.setMinimumTurnRadius(minTurningRadius); 177 | 178 | mControlSystem.setSteeringRatio(steeringRatio); 179 | mControlSystem.setMaxAbsWheelAngle(maxAbsoluteAngle); 180 | mControlSystem.attachTorqueGenerator(mTorqueGenerator.get()); 181 | 182 | mWheelSystem.bindControlSystem(mControlSystem); 183 | 184 | mState.setMassValue_local(1961.0); 185 | mState.setInertiaTensor_local(glm::dmat3(mState.getMass().getValue())); 186 | } 187 | 188 | } -------------------------------------------------------------------------------- /src/ControlSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "ControlSystem.h" 2 | #include "Wheel.h" 3 | #include "Brake.hpp" 4 | 5 | namespace Internal { 6 | 7 | void ControlSystem::update(double wheelBase, double frontAxleTrack) 8 | /* Called by Car::update 9 | */ 10 | { 11 | updateSteeringAngle(wheelBase, frontAxleTrack); 12 | } 13 | 14 | void ControlSystem::handleInput(double dt) 15 | /* Called by Car::checkInput 16 | * Called once per Car update 17 | */ 18 | { 19 | handleSteeringInput(dt); 20 | handleSpeedInput(); 21 | } 22 | 23 | void ControlSystem::attachWheels(Wheel* left, Wheel* right) 24 | /* Called by WheelSystem::bindControlSystem 25 | * Called once at load time 26 | */ 27 | { 28 | mLeftWheel = left; 29 | mRightWheel = right; 30 | } 31 | 32 | void ControlSystem::setMaxAbsWheelAngle(double maxAbsAngle) 33 | /* Called by Car::assemble 34 | * Called once at load time 35 | */ 36 | { 37 | mMaxAbsWheelAngle = maxAbsAngle; 38 | mMaxAbsSteeringWheelAngle = maxAbsAngle * mSteeringRatio; 39 | } 40 | 41 | void ControlSystem::updateSteeringAngle(double wheelBase, double frontAxleTrack) 42 | /* Called by ControlSystem::update 43 | * Responsible for updating the steering angles of both attached wheels 44 | */ 45 | { 46 | using namespace glm; 47 | 48 | //Using the current steering wheel's angle, this can set the deflection angles of the two front wheels 49 | //accounting for Ackermann geometry and the steering ratio. 50 | double 51 | leftWheelAngle = 0.0, 52 | rightWheelAngle = 0.0; 53 | 54 | //Turning right 55 | if (mSteeringWheelAngle > 0.0) { 56 | rightWheelAngle = -mSteeringWheelAngle / mSteeringRatio; 57 | leftWheelAngle = -degrees(asin(wheelBase / sqrt(pow((((wheelBase * sin(radians(90.0 - abs(rightWheelAngle)))) / (sin(radians(abs(rightWheelAngle))))) + frontAxleTrack), 2) + pow(wheelBase, 2)))); 58 | } 59 | //Turning left 60 | else if (mSteeringWheelAngle < 0.0) { 61 | leftWheelAngle = -mSteeringWheelAngle / mSteeringRatio; 62 | rightWheelAngle = degrees(asin(wheelBase / sqrt(pow((((wheelBase * sin(radians(90.0 - leftWheelAngle))) / (sin(radians(leftWheelAngle)))) + frontAxleTrack), 2) + pow(wheelBase, 2)))); 63 | } 64 | 65 | //Car's wheels are toe-out during steering, hence why two different angles are required 66 | mLeftWheel->setSteeringAngle(leftWheelAngle); 67 | mRightWheel->setSteeringAngle(rightWheelAngle); 68 | } 69 | 70 | void ControlSystem::handleSteeringInput(double dt) 71 | /* Called by ControlSystem::handleInput 72 | * Responsible for handling the input for just the steering wheel 73 | */ 74 | { 75 | bool 76 | steerLeft = Framework::Input::isKeyPressed(GLFW_KEY_RIGHT), 77 | steerRight = Framework::Input::isKeyPressed(GLFW_KEY_LEFT); 78 | 79 | if (steerLeft || steerRight) { 80 | double targetAngle = 0.0; 81 | 82 | if (steerLeft) { 83 | targetAngle = mSteeringWheelAngle + mSteeringRate * dt; 84 | mSteeringWheelAngle = targetAngle > mMaxAbsSteeringWheelAngle ? mMaxAbsSteeringWheelAngle : targetAngle; 85 | } 86 | if (steerRight) { 87 | targetAngle = mSteeringWheelAngle - mSteeringRate * dt; 88 | mSteeringWheelAngle = targetAngle < -mMaxAbsSteeringWheelAngle ? -mMaxAbsSteeringWheelAngle : targetAngle; 89 | } 90 | } 91 | else { 92 | double steeringFraction = abs(mSteeringWheelAngle) / mMaxAbsSteeringWheelAngle; 93 | 94 | if (mSteeringWheelAngle < 0.0) 95 | mSteeringWheelAngle += mSteeringRate * steeringFraction * dt; 96 | else 97 | mSteeringWheelAngle -= mSteeringRate * steeringFraction * dt; 98 | } 99 | } 100 | 101 | void ControlSystem::handleSpeedInput() 102 | /* Called by ControlSystem::handleInput 103 | * Responsible for handling the input for changing the speed of the Car 104 | */ 105 | { 106 | //Neutralise everything before input is checked 107 | mTorqueGenerator->setThrottle(0.0); 108 | 109 | for (Brake* b : mBrakes) 110 | b->setTravelPercentage(0.0); 111 | 112 | mBrakesOn = false; 113 | 114 | //Checking switch to reverse mode 115 | if (Framework::Input::isKeyReleased(GLFW_KEY_END)) 116 | mTorqueGenerator->toggleReverse(); 117 | 118 | //Accelerator pedal input 119 | if (Framework::Input::isKeyPressed(GLFW_KEY_UP)) 120 | mTorqueGenerator->setThrottle(1.0); 121 | 122 | //Brake input (All brakes are activated) 123 | if (Framework::Input::isKeyPressed(GLFW_KEY_DOWN)) { 124 | mBrakesOn = true; 125 | for (Brake* b : mBrakes) 126 | b->setTravelPercentage(1.0); 127 | } 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/DebugCarModel.cpp: -------------------------------------------------------------------------------- 1 | #include "DebugCarModel.h" 2 | #include "Car.h" 3 | 4 | namespace Visual { 5 | 6 | DebugCarModel::DebugCarModel(Internal::Car& carData, Framework::ResourceSet& resourceBucket) : 7 | /* Called by VisualShell::load 8 | */ 9 | ICarModel(carData, resourceBucket) 10 | { 11 | addVectorLines(); 12 | loadResources(); 13 | } 14 | 15 | void DebugCarModel::render(Framework::Graphics::Renderer& renderer) 16 | /* Called by VisualShell::renderAll 17 | */ 18 | { 19 | update(); 20 | mModel.sendRenderCommands(renderer); 21 | } 22 | 23 | void DebugCarModel::addVectorLines() 24 | /* Called by DebugCarModel::DebugCarModel 25 | * Called once at load-time 26 | * Adds vector line objects to the mVectorGroup object 27 | */ 28 | { 29 | using namespace glm; 30 | 31 | //3 vectors for each wheel 32 | for (unsigned char i = 0; i < mCarData.getWheelSystem().getAllWheelInterfaces().size(); i++) { 33 | 34 | //Wheel-to-ground displacement 35 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(1.0f, 1.0f, 0.0f, 1.0f))); 36 | 37 | //Tyre force 38 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(0.0f, 1.0f, 1.0f, 1.0f))); 39 | 40 | //Wheel interface velocity (world space) 41 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(1.0f, 0.0f, 1.0f, 1.0f))); 42 | 43 | //Terrain normal 44 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(0.5f, 0.5f, 1.0f, 1.0f))); 45 | } 46 | 47 | //Car's angular velocity 48 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(0.0f, 0.0f, 0.0f, 1.0f))); 49 | 50 | //Car's linear velocity 51 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(0.0f, 0.0f, 0.0f, 1.0f))); 52 | 53 | //Aerodynamic drag force 54 | mVectorGroup.addVector(DebugVector(vec3(), vec3(), vec4(1.0f, 0.5f, 0.0f, 1.0f))); 55 | } 56 | 57 | void DebugCarModel::loadResources() 58 | /* Called by DebugCarModel::DebugCarModel 59 | * Called once at load-time 60 | */ 61 | { 62 | using namespace Framework::Graphics; 63 | 64 | Shader* debugShader = mResourceBucket.addShader("res/shaders/debug.vert", "res/shaders/debug.frag", "debugShader"); 65 | debugShader->addUniform("modelMatrix"); 66 | debugShader->addUniform("viewMatrix"); 67 | debugShader->addUniform("projectionMatrix"); 68 | 69 | populateWheelColourBufferData(); 70 | carBaseResources(); 71 | carWheelsResources(); 72 | loadVectorLines(); 73 | } 74 | 75 | void DebugCarModel::populateWheelColourBufferData() 76 | /* Called by DebugCarModel::loadResources 77 | * Called once at load-time 78 | */ 79 | { 80 | //Fill one buffer with a red colour... 81 | for (unsigned int i = 0; i < 2 * mWheelNumSides; i++) { 82 | mWheelCollidingColourData.push_back(mWheelCollidingColour.x); 83 | mWheelCollidingColourData.push_back(mWheelCollidingColour.y); 84 | mWheelCollidingColourData.push_back(mWheelCollidingColour.z); 85 | mWheelCollidingColourData.push_back(mWheelCollidingColour.a); 86 | } 87 | 88 | //...and buffer with a green colour 89 | for (unsigned int i = 0; i < 2 * mWheelNumSides; i++) { 90 | mWheelNeutralColourData.push_back(mWheelNeutralColour.x); 91 | mWheelNeutralColourData.push_back(mWheelNeutralColour.y); 92 | mWheelNeutralColourData.push_back(mWheelNeutralColour.z); 93 | mWheelNeutralColourData.push_back(mWheelNeutralColour.a); 94 | } 95 | } 96 | 97 | void DebugCarModel::carBaseResources() 98 | /* Called by DebugCarModel::loadResources 99 | * Instantiates and initialises a mesh for the rectangular base overlay of the Car 100 | */ 101 | { 102 | using namespace Framework::Graphics; 103 | 104 | //Add an (empty) mesh for the debug model 105 | Mesh* debugBaseMesh = mResourceBucket.addMesh("debugBaseMesh", GL_LINES, nullptr, mResourceBucket.getResource("debugShader")); 106 | 107 | //Create a vertex position buffer and fill with data 108 | { 109 | std::vector debugBasePositions; 110 | 111 | glm::dvec3 position; 112 | for (Internal::WheelInterface& w : mCarData.getWheelSystem().getAllWheelInterfaces()) { 113 | position = w.getPosition_car(); 114 | 115 | debugBasePositions.push_back(position.x); 116 | debugBasePositions.push_back(position.y); 117 | debugBasePositions.push_back(position.z); 118 | } 119 | 120 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(0, 3, GL_FLOAT, false), debugBasePositions, "debugBasePositions"); 121 | } 122 | 123 | //Create a vertex colour buffer and fill with data 124 | { 125 | std::vector debugBaseColours = { 126 | 1.0f, 0.0f, 0.0f, 1.0f, 127 | 1.0f, 0.0f, 0.0f, 1.0f, 128 | 1.0f, 0.0f, 0.0f, 1.0f, 129 | 1.0f, 0.0f, 0.0f, 1.0f 130 | }; 131 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(1, 4, GL_FLOAT, false), debugBaseColours, "debugBaseColours"); 132 | } 133 | 134 | //Create an index buffer and fill with data 135 | { 136 | std::vector debugBaseIndices = { 137 | 0, 1, 138 | 1, 3, 139 | 3, 2, 140 | 2, 0 141 | }; 142 | mResourceBucket.addIndexBuffer(GL_STATIC_DRAW, debugBaseIndices, "debugBaseIndices"); 143 | } 144 | 145 | //Add buffers to mesh 146 | debugBaseMesh->addBuffer(mResourceBucket.getResource("debugBasePositions")); 147 | debugBaseMesh->addBuffer(mResourceBucket.getResource("debugBaseColours")); 148 | debugBaseMesh->addIndexBuffer(mResourceBucket.getResource("debugBaseIndices")); 149 | 150 | //Add the mesh to the model 151 | mModel.addMesh(debugBaseMesh); 152 | } 153 | 154 | void DebugCarModel::carWheelsResources() 155 | /* Called by DebugCarModel::loadResources 156 | * Instantiates and initialises meshes for the car's wheels 157 | */ 158 | { 159 | using namespace Framework::Graphics; 160 | 161 | //Used to give the wheel mesh resources unique ID's 162 | std::string wheelId = ""; 163 | Internal::Wheel* targetWheel = nullptr; 164 | 165 | //For each wheel on the vehicle... 166 | for (unsigned char i = 0; i < mCarData.getWheelSystem().getAllWheelInterfaces().size(); i++) { 167 | targetWheel = &mCarData.getWheelSystem()[i]->getWheel(); 168 | wheelId = "debugWheel" + std::to_string(i); 169 | 170 | //Add an (empty) mesh for the debug model 171 | Mesh* debugWheelMesh = mResourceBucket.addMesh(wheelId + "mesh", GL_LINES, nullptr, mResourceBucket.getResource("debugShader")); 172 | 173 | //Create a vertex position buffer and fill with data 174 | std::vector debugWheelPositions; 175 | { 176 | const float sphereRadius = targetWheel->getTotalRadius(); 177 | 178 | glm::vec2 179 | startPointer = glm::vec2(0.0f, 1.0f), 180 | currentPointer = startPointer; 181 | 182 | //Procedurally generates the vertex positions of circles by rotating a vector around the circumference, 183 | //like one of the hands of a clock, starting at 12 o'clock and sampling the position of the end of the hand 184 | //before adding it to the buffer. 185 | 186 | //z-aligned circle first 187 | for (unsigned char side = 0; side < mWheelNumSides; side++) { 188 | currentPointer = glm::rotate(startPointer, glm::radians(side / (float)mWheelNumSides * 360.0f)) * sphereRadius; 189 | 190 | debugWheelPositions.push_back(0.0f); //x 191 | debugWheelPositions.push_back(currentPointer.y); //y 192 | debugWheelPositions.push_back(currentPointer.x); //z 193 | } 194 | 195 | //reset the pointer back up to 12 o'clock 196 | currentPointer = startPointer; 197 | 198 | //then x-aligned circle 199 | for (unsigned char side = 0; side < mWheelNumSides; side++) { 200 | currentPointer = glm::rotate(startPointer, glm::radians(side / (float)mWheelNumSides * 360.0f)) * sphereRadius; 201 | 202 | debugWheelPositions.push_back(currentPointer.x); //x 203 | debugWheelPositions.push_back(currentPointer.y); //y 204 | debugWheelPositions.push_back(0.0); //z 205 | } 206 | 207 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(0, 3, GL_FLOAT, false), debugWheelPositions, wheelId + "positions"); 208 | } 209 | 210 | //Create a vertex colour buffer and fill with data 211 | { 212 | std::vector debugWheelColours; 213 | 214 | //Run through each vertex on each the circles 215 | for (unsigned char i = 0; i < debugWheelPositions.size() / 3; i++) { 216 | //And give it a blue colour 217 | debugWheelColours.push_back(0.0f); //r 218 | debugWheelColours.push_back(1.0f); //g 219 | debugWheelColours.push_back(0.0f); //b 220 | debugWheelColours.push_back(1.0f); //a 221 | } 222 | 223 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(1, 4, GL_FLOAT, false), debugWheelColours, wheelId + "colours"); 224 | } 225 | 226 | //Create an index buffer and fill with data 227 | { 228 | std::vector debugWheelIndices; 229 | 230 | const unsigned int numVertices = debugWheelPositions.size() / 3; 231 | for (unsigned int i = 0; i < numVertices - 1; i++) { 232 | debugWheelIndices.push_back(i); 233 | debugWheelIndices.push_back(i + 1); 234 | } 235 | 236 | debugWheelIndices.push_back(numVertices - 1); 237 | debugWheelIndices.push_back(0); 238 | 239 | mResourceBucket.addIndexBuffer(GL_STATIC_DRAW, debugWheelIndices, wheelId + "indices"); 240 | } 241 | 242 | //Add buffers to mesh 243 | debugWheelMesh->addBuffer(mResourceBucket.getResource(wheelId + "positions")); 244 | debugWheelMesh->addBuffer(mResourceBucket.getResource(wheelId + "colours")); 245 | debugWheelMesh->addIndexBuffer(mResourceBucket.getResource(wheelId + "indices")); 246 | 247 | //Add the Wheel mesh to the model 248 | mModel.addMesh(debugWheelMesh); 249 | } 250 | } 251 | 252 | void DebugCarModel::loadVectorLines() 253 | /* Called by DebugCarModel::loadResources 254 | * Instantiates and populates the position, colour and index buffer of the vectors 255 | */ 256 | { 257 | using namespace Framework::Graphics; 258 | 259 | Mesh* debugVectorLinesMesh = mResourceBucket.addMesh("debugVectors", GL_LINES, nullptr, mResourceBucket.getResource("debugShader")); 260 | 261 | //Create the buffers 262 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(0, 3, GL_FLOAT, false), mVectorGroup.getPositionBuffer(), "debugVectorPositions"); 263 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(1, 4, GL_FLOAT, false), mVectorGroup.getColourBuffer(), "debugVectorColours"); 264 | mResourceBucket.addIndexBuffer(GL_STATIC_DRAW, mVectorGroup.getIndexBuffer(), "debugVectorIndices"); 265 | 266 | //Add buffers to mesh 267 | debugVectorLinesMesh->addBuffer(mResourceBucket.getResource("debugVectorPositions")); 268 | debugVectorLinesMesh->addBuffer(mResourceBucket.getResource("debugVectorColours")); 269 | debugVectorLinesMesh->addIndexBuffer(mResourceBucket.getResource("debugVectorIndices")); 270 | 271 | //Add the finished mesh to the model 272 | mModel.addMesh(debugVectorLinesMesh); 273 | 274 | //Give the buffers data 275 | mResourceBucket.getResource("debugVectorPositions")->updateData(mVectorGroup.getPositionBuffer()); 276 | mResourceBucket.getResource("debugVectorColours")->updateData(mVectorGroup.getColourBuffer()); 277 | mResourceBucket.getResource("debugVectorIndices")->updateData(mVectorGroup.getIndexBuffer()); 278 | } 279 | 280 | void DebugCarModel::update() 281 | /* Called by DebugCarModel::render 282 | * Each frame, the colours of specific meshes must be updated to represent a collision 283 | * The transforms of these meshes are also updated 284 | */ 285 | { 286 | using namespace glm; 287 | 288 | unsigned char meshCount = 0; 289 | 290 | glm::mat4 carToWorld_car = mCarData.getState().getLocalToWorld_position(); 291 | 292 | //The base panel of the car 293 | { 294 | //Mesh transforms 295 | { 296 | mModel.getMesh(meshCount)->setWorldTransform(carToWorld_car); 297 | meshCount++; 298 | } 299 | 300 | //Mesh colours 301 | std::vector newBaseColours = { 302 | 1.0f, 0.0f, 0.0f, 1.0f, 303 | 1.0f, 0.0f, 0.0f, 1.0f, 304 | 1.0f, 0.0f, 0.0f, 1.0f, 305 | 1.0f, 0.0f, 0.0f, 1.0f 306 | }; 307 | 308 | { 309 | if (mCarData.getState().getPosition_world().y <= 0.01) 310 | newBaseColours = { 311 | 0.0f, 1.0f, 0.0f, 1.0f, 312 | 0.0f, 1.0f, 0.0f, 1.0f, 313 | 0.0f, 1.0f, 0.0f, 1.0f, 314 | 0.0f, 1.0f, 0.0f, 1.0f 315 | }; 316 | } 317 | 318 | mResourceBucket.getResource("debugBaseColours")->updateData(newBaseColours); 319 | } 320 | 321 | //The wheels on the car 322 | { 323 | mat4 324 | totalWheelTransform, 325 | translation, 326 | rotation, 327 | steeringAngleRot, 328 | axleRot; 329 | 330 | Internal::Wheel* currentWheel = nullptr; 331 | 332 | vec3 displacement; 333 | 334 | Internal::WheelSystem& wheelSystem = mCarData.getWheelSystem(); 335 | for (unsigned char i = 0; i < wheelSystem.getAllWheelInterfaces().size(); i++) { 336 | //Mesh transforms 337 | { 338 | currentWheel = &wheelSystem[i]->getWheel(); 339 | displacement = currentWheel->getPosition_car(); 340 | 341 | translation = translate(mat4(), displacement), 342 | steeringAngleRot = rotate(mat4(), (float)radians(currentWheel->getSteeringAngle()), vec3(0.0f, 1.0f, 0.0f)), 343 | axleRot = rotate(mat4(), (float)currentWheel->getAngularPosition(), vec3(1.0f, 0.0f, 0.0f)), 344 | 345 | rotation = translate(steeringAngleRot * axleRot, displacement) * translate(mat4(), -displacement); 346 | 347 | totalWheelTransform = carToWorld_car * translation * rotation; 348 | mModel.getMesh(i + meshCount)->setWorldTransform(totalWheelTransform); 349 | } 350 | 351 | //Mesh colours 352 | { 353 | mResourceBucket.getResource("debugWheel" + std::to_string(i) + "colours")->updateData(mWheelNeutralColourData); 354 | 355 | if (wheelSystem[i]->collisionRegistered()) 356 | mResourceBucket.getResource("debugWheel" + std::to_string(i) + "colours")->updateData(mWheelCollidingColourData); 357 | } 358 | } 359 | } 360 | 361 | updateVectorLines(); 362 | } 363 | 364 | void DebugCarModel::updateVectorLines() 365 | /* Called by DebugCarModel::update 366 | * The vector models' positions, directions and lengths are updated to represent the vector quantities in the simulation 367 | */ 368 | { 369 | using namespace Framework::Graphics; 370 | using namespace glm; 371 | using namespace External; 372 | 373 | Internal::WheelInterface* currentWheelInterface = nullptr; 374 | 375 | dvec3 temp; 376 | 377 | //Used to track the index of the start of the current vector line's data 378 | unsigned int indexTracker = 0; 379 | 380 | double terrainHeight = 0.0; 381 | 382 | for (unsigned int i = 0; i < mCarData.getWheelSystem().getAllWheelInterfaces().size(); i++) { 383 | currentWheelInterface = mCarData.getWheelSystem()[i]; 384 | temp = currentWheelInterface->getPosition_world(); 385 | terrainHeight = Environment::mTerrain.getHeight(dvec2(temp.x, temp.z)); 386 | 387 | //Wheel-to-ground displacement 388 | { 389 | mVectorGroup[i * mNumVectorsPerWheelInterface]->setPosition_world(temp); 390 | mVectorGroup[i * mNumVectorsPerWheelInterface]->setDirection_world(dvec3(0.0, terrainHeight - temp.y, 0.0)); 391 | } 392 | 393 | //Tyre force 394 | { 395 | mVectorGroup[i * mNumVectorsPerWheelInterface + 1]->setPosition_world(dvec3(mCarData.getState().getLocalToWorld_position() * dvec4(mCarData.getWheelSystem()[i]->getWheel().getContactPatchPosition_car(), 1.0))); 396 | mVectorGroup[i * mNumVectorsPerWheelInterface + 1]->setDirection_world(normalize(mCarData.getWheelSystem()[i]->getWheel().getTyreForce_world())); 397 | } 398 | 399 | //Wheel interface velocity 400 | { 401 | mVectorGroup[i * mNumVectorsPerWheelInterface + 2]->setPosition_world(currentWheelInterface->getPosition_world()); 402 | mVectorGroup[i * mNumVectorsPerWheelInterface + 2]->setDirection_world(currentWheelInterface->getVelocity_world()); 403 | } 404 | 405 | //Terrain normal 406 | { 407 | mVectorGroup[i * mNumVectorsPerWheelInterface + 3]->setPosition_world(dvec3(temp.x, terrainHeight, temp.z)); 408 | mVectorGroup[i * mNumVectorsPerWheelInterface + 3]->setDirection_world(Environment::mTerrain.getNormal_world(dvec2(temp.x, temp.z))); 409 | } 410 | 411 | indexTracker += mNumVectorsPerWheelInterface; 412 | } 413 | 414 | dvec3 carPosition_world = mCarData.getState().getPosition_world(); 415 | 416 | //Car angular velocity 417 | { 418 | mVectorGroup[indexTracker]->setPosition_world(carPosition_world); 419 | mVectorGroup[indexTracker]->setDirection_world(mCarData.getState().getAngularVelocity_world()); 420 | indexTracker++; 421 | } 422 | 423 | //Car velocity 424 | { 425 | mVectorGroup[indexTracker]->setPosition_world(carPosition_world); 426 | mVectorGroup[indexTracker]->setDirection_world(mCarData.getState().getVelocity_world()); 427 | indexTracker++; 428 | } 429 | 430 | //Aerodynamic drag 431 | { 432 | mVectorGroup[indexTracker]->setPosition_world(carPosition_world); 433 | 434 | dvec3 drag = mCarData.getAeroDrag_world(); 435 | if(length(drag) > 1.0) 436 | mVectorGroup[indexTracker]->setDirection_world(normalize(drag)); 437 | else 438 | mVectorGroup[indexTracker]->setDirection_world(drag); 439 | 440 | indexTracker++; 441 | } 442 | 443 | mVectorGroup.update(); 444 | 445 | mResourceBucket.getResource("debugVectorPositions")->updateData(mVectorGroup.getPositionBuffer()); 446 | } 447 | 448 | } -------------------------------------------------------------------------------- /src/DebugVectorGroup.cpp: -------------------------------------------------------------------------------- 1 | #include "DebugVectorGroup.h" 2 | 3 | namespace Visual { 4 | 5 | void DebugVectorGroup::addVector(DebugVector newVector) 6 | /* Called by DebugCarModel::addVectorLines 7 | * Adds a new DebugVector object instance to mVectors 8 | * Automatically updates the buffers to account for this change 9 | */ 10 | { 11 | mVectors.push_back(newVector); 12 | 13 | static unsigned int 14 | positionBufferSize = 0, 15 | colourBufferSize = 0, 16 | indexBufferSize = 0; 17 | 18 | positionBufferSize += DebugVector::getNumPositionComponents(); 19 | mPositionBuffer.resize(positionBufferSize, 0.0f); 20 | 21 | colourBufferSize += DebugVector::getNumColourComponents(); 22 | mColourBuffer.resize(colourBufferSize, 0.0f); 23 | 24 | indexBufferSize += DebugVector::getNumIndices(); 25 | mIndexBuffer.resize(indexBufferSize, 0); 26 | 27 | update(); 28 | } 29 | 30 | void DebugVectorGroup::update() 31 | /* Called by 32 | * - DebugVectorGroup::addVector 33 | * - DebugCarModel::updateVectorLines 34 | * This function is responsible for rebuilding the position and colour buffers based on the data contained within the VectorLine objects in mVectors. 35 | */ 36 | { 37 | const unsigned int 38 | numVisibleVectors = recalcNumVisbleVectors(), 39 | posComponentsPerVec = DebugVector::getNumPositionComponents(), 40 | colourComponentsPerVec = DebugVector::getNumColourComponents(), 41 | indicesPerVec = DebugVector::getNumIndices(); 42 | 43 | DebugVector* currentVector = nullptr; 44 | 45 | glm::dvec3 46 | vecStartPos, 47 | vecEndPos; 48 | 49 | glm::dvec4 vecColour; 50 | 51 | //Remove all data currently in the 3 buffers before repopulating them 52 | mPositionBuffer.resize(0); 53 | mColourBuffer.resize(0); 54 | mIndexBuffer.resize(0); 55 | 56 | //Run through each vector 57 | for (unsigned int i = 0; i < numVisibleVectors; i++) { 58 | currentVector = &mVectors[i]; 59 | 60 | //Retrieve the properties of the current vector 61 | vecStartPos = currentVector->isVisible() ? currentVector->getPosition_world() : glm::dvec3(0.0); 62 | vecEndPos = currentVector->isVisible() ? vecStartPos + currentVector->getDirection_world() : glm::dvec3(0.0); 63 | vecColour = currentVector->getColour(); 64 | 65 | //Position data 66 | { 67 | //Start point 68 | mPositionBuffer.push_back(vecStartPos.x); 69 | mPositionBuffer.push_back(vecStartPos.y); 70 | mPositionBuffer.push_back(vecStartPos.z); 71 | 72 | //End point 73 | mPositionBuffer.push_back(vecEndPos.x); 74 | mPositionBuffer.push_back(vecEndPos.y); 75 | mPositionBuffer.push_back(vecEndPos.z); 76 | } 77 | 78 | //Colour data 79 | { 80 | //Start point 81 | mColourBuffer.push_back(vecColour.r); 82 | mColourBuffer.push_back(vecColour.g); 83 | mColourBuffer.push_back(vecColour.b); 84 | mColourBuffer.push_back(vecColour.a); 85 | 86 | //End point 87 | mColourBuffer.push_back(vecColour.r); 88 | mColourBuffer.push_back(vecColour.g); 89 | mColourBuffer.push_back(vecColour.b); 90 | mColourBuffer.push_back(vecColour.a); 91 | } 92 | 93 | //Index data 94 | { 95 | mIndexBuffer.push_back(i * indicesPerVec); 96 | mIndexBuffer.push_back(i * indicesPerVec + 1); 97 | } 98 | } 99 | } 100 | 101 | unsigned int DebugVectorGroup::recalcNumVisbleVectors() 102 | /* Called by DebugVectorGroup::update 103 | */ 104 | { 105 | unsigned int count = 0; 106 | 107 | for (DebugVector& v : mVectors) { 108 | if (v.isVisible()) 109 | count++; 110 | } 111 | 112 | return count; 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /src/Environment.cpp: -------------------------------------------------------------------------------- 1 | #include "Environment.h" 2 | 3 | namespace External { 4 | 5 | Terrain Environment::mTerrain; 6 | 7 | const double 8 | Environment::mGravityAccel = 9.80665, //m/s^2 - gravitational acceleration at sea level 9 | Environment::mAirDensity = 1.225; //kg/m^3 - air density at sea level 10 | } -------------------------------------------------------------------------------- /src/EnvironmentModel.cpp: -------------------------------------------------------------------------------- 1 | #include "EnvironmentModel.h" 2 | 3 | namespace Visual { 4 | 5 | EnvironmentModel::EnvironmentModel(Framework::ResourceSet& resourceBucket) : 6 | /* Called by VisualShell::load 7 | */ 8 | mResourceBucket(resourceBucket) 9 | { 10 | loadResources(); 11 | } 12 | 13 | void EnvironmentModel::render(Framework::Graphics::Renderer& renderer) 14 | /* Called by VisualShell::renderAll 15 | * Renders everything in the Environment 16 | */ 17 | { 18 | mTerrainModel->render(renderer); 19 | mSkyBox->render(renderer); 20 | } 21 | 22 | void EnvironmentModel::loadResources() 23 | /* Called by EnvironmentModel::EnvironmentModel 24 | * Instantiation and initialisation of all resources needed to represent the environment graphically 25 | */ 26 | { 27 | //Setting uniforms in the terrain shader 28 | mTerrainShader = mResourceBucket.addShader("res/shaders/terrain.vert", "res/shaders/terrain.frag", "terrainShader"); 29 | mTerrainShader->addUniform("modelMatrix"); 30 | mTerrainShader->addUniform("viewMatrix"); 31 | mTerrainShader->addUniform("projectionMatrix"); 32 | mTerrainShader->addUniformWithDefault("sunDirection", mSunDirection); 33 | mTerrainShader->addUniformWithDefault("fogDensity", mFogDensity); 34 | mTerrainShader->addUniformWithDefault("fogGradient", mFogGradient); 35 | mTerrainShader->addUniformWithDefault("skyColour", mSkyColour); 36 | 37 | //TerrainModel object 38 | mTerrainModel = std::make_unique(mResourceBucket, mTerrainShader); 39 | 40 | //SkyBox object 41 | mSkyBox = std::make_unique("res/shaders/skyBox.vert", "res/shaders/skyBox.frag"); 42 | 43 | //Settings uniforms in the SkyBox shader 44 | mSkyboxShader = mSkyBox->getShader(); 45 | mSkyboxShader->addUniformWithDefault("skyColour", mSkyColour); 46 | mSkyboxShader->addUniformWithDefault("sunColour", mSunColour); 47 | mSkyboxShader->addUniformWithDefault("sunDir", mSunDirection); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/GameCarModel.cpp: -------------------------------------------------------------------------------- 1 | #include "GameCarModel.h" 2 | #include "Car.h" 3 | 4 | namespace Visual { 5 | 6 | GameCarModel::GameCarModel(Internal::Car& carData, Framework::ResourceSet& resourceBucket) : 7 | /* Called by VisualShell::load 8 | */ 9 | ICarModel(carData, resourceBucket) 10 | { 11 | loadResources(); 12 | } 13 | 14 | void GameCarModel::loadResources() 15 | /* Called by GameCarModel::GameCarModel 16 | * All graphical resources such as meshes, shaders etc are instantiated here 17 | */ 18 | { 19 | //Add all meshes, textures, shaders belonging to the game model of the car should be added in here 20 | using namespace Framework::Graphics; 21 | using namespace glm; 22 | 23 | Shader* bodyShader = mResourceBucket.addShader("res/shaders/body.vert", "res/shaders/body.frag", "bodyShader"); 24 | bodyShader->addUniform("modelMatrix"); 25 | bodyShader->addUniform("viewMatrix"); 26 | bodyShader->addUniform("projectionMatrix"); 27 | bodyShader->addUniform("skyColour"); 28 | bodyShader->addUniform("sunDirection"); 29 | bodyShader->addUniform("fogDensity"); 30 | bodyShader->addUniform("fogGradient"); 31 | bodyShader->addUniformWithDefault("bodyColour", vec3(0.1f, 0.2f, 0.5f)); 32 | 33 | Mesh* bodyMesh = mResourceBucket.addOBJMesh("carBody", "res/models/CarBody.obj", GL_TRIANGLES, nullptr, bodyShader); 34 | 35 | //Chassis 36 | mModel.addMesh(bodyMesh); 37 | 38 | //Wheels interfaces 39 | { 40 | Mesh* tempMesh = nullptr; 41 | for (unsigned char i = 0; i < mCarData.getWheelSystem().getAllWheelInterfaces().size(); i++) { 42 | //Wheels 43 | tempMesh = mResourceBucket.addOBJMesh("wheel" + std::to_string(i), "res/models/Wheel.obj", GL_TRIANGLES, nullptr, bodyShader); 44 | mModel.addMesh(tempMesh); 45 | 46 | //Suspension springs 47 | tempMesh = mResourceBucket.addOBJMesh("spring" + std::to_string(i), "res/models/Spring.obj", GL_TRIANGLES, nullptr, bodyShader); 48 | mModel.addMesh(tempMesh); 49 | } 50 | } 51 | 52 | //Steering wheel 53 | mModel.addMesh(mResourceBucket.addOBJMesh("steeringWheel", "res/models/SteeringWheel.obj", GL_TRIANGLES, nullptr, bodyShader)); 54 | } 55 | 56 | void GameCarModel::setShaderUniforms(float fogDensity, float fogGradient, glm::vec3 skyColour, glm::vec3 sunDirection) 57 | /* Called by VisualShell::load 58 | * Called once at load-time to give shader uniforms initial values 59 | */ 60 | { 61 | Framework::Graphics::Shader* bodyShader = mResourceBucket.getResource("bodyShader"); 62 | 63 | bodyShader->bind(); 64 | bodyShader->setUniform(3, skyColour); 65 | bodyShader->setUniform(4, sunDirection); 66 | bodyShader->setUniform(5, fogDensity); 67 | bodyShader->setUniform(6, fogGradient); 68 | } 69 | 70 | void GameCarModel::update() 71 | /* Called by 72 | * Updates the transformation matrices of all meshes belonging to the Model 73 | */ 74 | { 75 | using namespace glm; 76 | 77 | unsigned char meshCount = 0; 78 | 79 | //Chassis 80 | mat4 carToWorld_car = mCarData.getState().getLocalToWorld_position(); 81 | mModel.getMesh(meshCount)->setWorldTransform(carToWorld_car); 82 | meshCount++; 83 | 84 | //Wheels 85 | { 86 | mat4 87 | totalTransform, 88 | translation, 89 | rotation, 90 | steeringAngleRot, 91 | axleRot; 92 | 93 | Internal::Wheel* currentWheel = nullptr; 94 | 95 | vec3 displacement; 96 | 97 | Internal::WheelSystem& wheelSystem = mCarData.getWheelSystem(); 98 | for (unsigned char i = 0; i < wheelSystem.getAllWheelInterfaces().size(); i++) { 99 | currentWheel = &wheelSystem[i]->getWheel(); 100 | 101 | //Wheels 102 | { 103 | displacement = currentWheel->getPosition_car(); 104 | 105 | translation = translate(displacement), 106 | steeringAngleRot = rotate((float)radians(currentWheel->getSteeringAngle()), vec3(0.0f, 1.0f, 0.0f)), 107 | axleRot = rotate((float)currentWheel->getAngularPosition(), vec3(1.0f, 0.0f, 0.0f)), 108 | rotation = translate(steeringAngleRot * axleRot, displacement) * translate(mat4(), -displacement); 109 | 110 | totalTransform = carToWorld_car * translation * rotation; 111 | mModel.getMesh(meshCount)->setWorldTransform(totalTransform); 112 | meshCount++; 113 | } 114 | 115 | //Suspension springs 116 | { 117 | const double 118 | modelHeight = 0.39983, 119 | springLength = wheelSystem[i]->getSuspension().getLength(); 120 | 121 | translation = translate(vec3(displacement.x * 0.8f, displacement.y, displacement.z)); 122 | totalTransform = carToWorld_car * translation * scale(vec3(1.0f, (springLength + 0.55f) / modelHeight, 1.0f)); 123 | mModel.getMesh(meshCount)->setWorldTransform(totalTransform); 124 | meshCount++; 125 | } 126 | } 127 | } 128 | 129 | //Steering wheel 130 | { 131 | mat4 132 | translation_car = translate(vec3(0.5f, 0.523f, -0.817f)), 133 | steeringRotation_car = rotate((float)radians(-mCarData.getControlSystem().getSteeringWheelAngle()), vec3(0.0f, 1.0f, 0.0f)), 134 | pitchRotation_car = rotate((float)radians(60.0f), vec3(1.0f, 0.0f, 0.0f)); 135 | 136 | mModel.getMesh(meshCount)->setWorldTransform(carToWorld_car * translation_car * pitchRotation_car * steeringRotation_car); 137 | } 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /src/ICarModel.cpp: -------------------------------------------------------------------------------- 1 | #include "ICarModel.h" 2 | #include "Car.h" 3 | 4 | namespace Visual { 5 | 6 | ICarModel::ICarModel(Internal::Car& carData, Framework::ResourceSet& resourceBucket) : 7 | /* Called by 8 | * - GameCarModel::GameCarModel 9 | * - DebugCarModel::DebugCarModel 10 | */ 11 | mResourceBucket(resourceBucket), 12 | mCarData(carData) 13 | { } 14 | 15 | void ICarModel::render(Framework::Graphics::Renderer& renderer) 16 | /* Called by VisualShell::renderAll 17 | */ 18 | { 19 | update(); 20 | mModel.sendRenderCommands(renderer); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/PacejkaMagicFormula.cpp: -------------------------------------------------------------------------------- 1 | #include "PacejkaMagicFormula.h" 2 | #include "Tyre.h" 3 | 4 | namespace Internal { 5 | 6 | float //default values 7 | PacejkaMagicFormula::b0 = 1.4, //1.65 8 | PacejkaMagicFormula::b1 = 80.0, //0 9 | PacejkaMagicFormula::b2 = 1700.0, //1100 10 | PacejkaMagicFormula::b3 = 0.0, //0 11 | PacejkaMagicFormula::b4 = 300.0, //300 12 | PacejkaMagicFormula::b5 = 0.0, //0 13 | PacejkaMagicFormula::b6 = 0.0, //0 14 | PacejkaMagicFormula::b7 = 0.0, //0 15 | PacejkaMagicFormula::b8 = -2.0, //-2 16 | PacejkaMagicFormula::b9 = 0.0, //0 17 | PacejkaMagicFormula::b10 = 0.0, //0 18 | PacejkaMagicFormula::b11 = 0.0, //0 19 | PacejkaMagicFormula::b12 = 0.0, //0 20 | PacejkaMagicFormula::b13 = 0.0; //0 21 | 22 | float //default values 23 | PacejkaMagicFormula::a0 = 1.4, //1.4 24 | PacejkaMagicFormula::a1 = 80.0, //0 25 | PacejkaMagicFormula::a2 = 1700, //1100 26 | PacejkaMagicFormula::a3 = 1100, //1100 27 | PacejkaMagicFormula::a4 = 50.0, //10 28 | PacejkaMagicFormula::a5 = 0.0, //0 29 | PacejkaMagicFormula::a6 = 0.0, //0 30 | PacejkaMagicFormula::a7 = -2.0, //-2 31 | PacejkaMagicFormula::a8 = 0.0, //0 32 | PacejkaMagicFormula::a9 = 0.0, //0 33 | PacejkaMagicFormula::a10 = 0.0, //0 34 | PacejkaMagicFormula::a11 = 0.0, //0 35 | PacejkaMagicFormula::a12 = 0.0, //0 36 | PacejkaMagicFormula::a13 = 0.0, //0 37 | PacejkaMagicFormula::a14 = 0.0, //0 38 | PacejkaMagicFormula::a15 = 0.0, //0 39 | PacejkaMagicFormula::a16 = 0.0, //0 40 | PacejkaMagicFormula::a17 = 0.0; //0 41 | 42 | PacejkaMagicFormula::PacejkaMagicFormula() 43 | /* Called during Tyre::Tyre 44 | */ 45 | { 46 | setToDriftingTyreParams(); 47 | } 48 | 49 | void PacejkaMagicFormula::updateForces(double verticalLoad_N, double slipPercent_0_to_100, double slipAngle_degs, double camberAngle_degs) 50 | /* Called by Tyre::update 51 | */ 52 | { 53 | //http://www.edy.es/dev/docs/pacejka-94-parameters-explained-a-comprehensive-guide/ 54 | 55 | updateLongitudinalForce(verticalLoad_N / 1000.0, slipPercent_0_to_100); 56 | updateLateralForce(verticalLoad_N / 1000.0, slipAngle_degs, camberAngle_degs); 57 | } 58 | 59 | void PacejkaMagicFormula::setToRoadTyreParams() 60 | /* Called by UILayer::tyreParameters 61 | */ 62 | { 63 | //Longitudinal 64 | b0 = 1.4, 65 | b1 = 80.0, 66 | b2 = 1700.0, 67 | b3 = 0.0, 68 | b4 = 140.0, 69 | b5 = 0.67, 70 | b6 = 0.0, 71 | b7 = 0.273, 72 | b8 = -2.0, 73 | b9 = 0.698, 74 | b10 = 0.0, 75 | b11 = 0.0, 76 | b12 = 0.0, 77 | b13 = 0.0; 78 | 79 | //Lateral 80 | a0 = 1.4, 81 | a1 = 0.0, 82 | a2 = 1700.0, 83 | a3 = 2000.0, 84 | a4 = 18.5, 85 | a5 = 0.0, 86 | a6 = 0.0, 87 | a7 = -2.0, 88 | a8 = 1.0, 89 | a9 = 0.0, 90 | a10 = 0.0, 91 | a11 = 0.0, 92 | a12 = 0.0, 93 | a13 = 0.0, 94 | a14 = 0.0, 95 | a15 = 0.0, 96 | a16 = 0.0, 97 | a17 = 0.0; 98 | } 99 | 100 | void PacejkaMagicFormula::setToDriftingTyreParams() 101 | /* Called by 102 | * - PacejkaMagicFormula::PacejkaMagicFormula 103 | * - UILayer::tyreParameters 104 | */ 105 | { 106 | //Longitudinal 107 | b0 = 1.4, 108 | b1 = 80.0, 109 | b2 = 1700.0, 110 | b3 = 0.0, 111 | b4 = 300.0, 112 | b5 = 1.0, 113 | b6 = 0.0, 114 | b7 = 0.0, 115 | b8 = -2.0, 116 | b9 = 0.0, 117 | b10 = 0.0, 118 | b11 = 0.0, 119 | b12 = 0.0, 120 | b13 = 0.0; 121 | 122 | //Lateral 123 | a0 = 1.4, 124 | a1 = 80.0, 125 | a2 = 1700, 126 | a3 = 1100, 127 | a4 = 50.0, 128 | a5 = 0.0, 129 | a6 = 0.0, 130 | a7 = -2.0, 131 | a8 = 0.0, 132 | a9 = 0.0, 133 | a10 = 0.0, 134 | a11 = 0.0, 135 | a12 = 0.0, 136 | a13 = 0.0, 137 | a14 = 0.0, 138 | a15 = 0.0, 139 | a16 = 0.0, 140 | a17 = 0.0; 141 | } 142 | 143 | void PacejkaMagicFormula::updateLongitudinalForce(double load_kN, double slipAsPercent) 144 | /* Called by PacejkaMagicFormula::updateForces 145 | */ 146 | { 147 | if (load_kN == 0.0 || abs(slipAsPercent == 0.0)) { 148 | mLongitudinalForce = 0.0; 149 | return; 150 | } 151 | 152 | double loadSquared_kN = pow(load_kN, 2.0); 153 | 154 | C = b0; 155 | D = load_kN * (b1 * load_kN + b2); 156 | BCD = (b0 * loadSquared_kN + b4 * load_kN) * exp(-b5 * load_kN); 157 | B = BCD / (C * D); 158 | E = (b6 * loadSquared_kN + b7 * load_kN + b8) * (1.0 - b13 * sign(slipAsPercent + H)); 159 | H = b9 * load_kN + b10; 160 | V = b11 * load_kN + b12; 161 | Bx1 = B * (slipAsPercent + H); 162 | 163 | mLongitudinalForce = D * sin(C * atan(Bx1 - E * (Bx1 - atan(Bx1)))) + V; 164 | } 165 | 166 | void PacejkaMagicFormula::updateLateralForce(double load_kN, double slipAngle_degs, double camberAngle_degs) 167 | /* Called by PacejkaMagicFormula::updateForces 168 | */ 169 | { 170 | if (load_kN == 0.0 || abs(slipAngle_degs == 0.0)) { 171 | mLateralForce = 0.0; 172 | return; 173 | } 174 | 175 | C = a0; 176 | D = load_kN * (a1 * load_kN + a2) * (1.0 - a15 * pow(camberAngle_degs, 2.0)); 177 | BCD = a3 * sin(atan(load_kN / a4) * 2.0) * (1.0 - a5 * abs(camberAngle_degs)); 178 | B = BCD / (C * D); 179 | E = (a6 * load_kN + a7) * (1.0 - (a16 * camberAngle_degs + a17) * sign(slipAngle_degs + H)); 180 | H = a8 * load_kN + a9 + a10 * camberAngle_degs; 181 | V = a11 * load_kN + a12 + (a13 * load_kN + a14) * camberAngle_degs * load_kN; 182 | Bx1 = B * (slipAngle_degs + H); 183 | 184 | mLateralForce = D * sin(C * atan(Bx1 - E * (Bx1 - atan(Bx1)))) + V; 185 | } 186 | 187 | } -------------------------------------------------------------------------------- /src/Terrain.cpp: -------------------------------------------------------------------------------- 1 | #include "Terrain.h" 2 | 3 | namespace External { 4 | 5 | Terrain::Terrain() 6 | /* Called in External::Environment 7 | */ 8 | { 9 | mGenerationLayers.push_back(std::make_unique()); 10 | mGenerationLayers.push_back(std::make_unique(mSize)); 11 | 12 | generate(); 13 | } 14 | 15 | void Terrain::generate() 16 | /* Called by Terrain::Terrain 17 | */ 18 | { 19 | //Must be called in the following order due to normals needing height data for their calculation 20 | generateHeightData(); 21 | generateNormalData(); 22 | generateSurfaceTypeData(); 23 | } 24 | 25 | double Terrain::getHeight(glm::dvec2 horizontalSamplePoint) 26 | /* Called by 27 | * - FPVCamera::afterPositionConstraints 28 | * - Car::basicCollision 29 | * - DebugCarModel::updateVectorLines 30 | * - TerrainModel::fillWithPositionData 31 | * - UILayer::upsideDownWarning 32 | * - WheelInterface::update 33 | * Calculates the height of the terrain at an arbitrary 2D position on it 34 | */ 35 | { 36 | /* -z 37 | * | 38 | * -x --- + --- +x 39 | * | 40 | * +z 41 | */ 42 | 43 | using namespace glm; 44 | 45 | //The horizontalSamplePoint will be a non-integer coordinate within the bounds of a terrain square. 46 | //The location of the target within this square is required 47 | const dvec2 withinSquare = horizontalSamplePoint - floor(horizontalSamplePoint); 48 | 49 | //The 3 vertices of the triangle that the target horizontalSamplePoint lies within 50 | dvec3 51 | vertex1, 52 | vertex2, 53 | vertex3; 54 | 55 | const int halfTerrainSize = floor(0.5 * mSize); 56 | 57 | //Indices into the heights array 58 | const unsigned int 59 | heightsIndexBL_X = horizontalSamplePoint.x < -halfTerrainSize ? 0 : horizontalSamplePoint.x > halfTerrainSize ? mSize - 2 : floor(horizontalSamplePoint.x) + halfTerrainSize, 60 | heightsIndexBL_Z = horizontalSamplePoint.y < -halfTerrainSize ? 0 : horizontalSamplePoint.y > halfTerrainSize ? mSize - 2 : floor(horizontalSamplePoint.y) + halfTerrainSize, 61 | actualHeightsIndexBL = heightsIndexBL_X * mSize + heightsIndexBL_Z; 62 | 63 | vertex1 = dvec3(0.0, mHeights[actualHeightsIndexBL], 0.0); 64 | 65 | //If point is on lower right triangle 66 | if(withinSquare.x >= withinSquare.y) 67 | vertex2 = dvec3(1.0, mHeights[actualHeightsIndexBL + mSize], 0.0); 68 | //If point is on upper left triangle 69 | else 70 | vertex2 = dvec3(0.0, mHeights[actualHeightsIndexBL + 1], 1.0); 71 | 72 | vertex3 = dvec3(1.0, mHeights[actualHeightsIndexBL + mSize + 1], 1.0); 73 | 74 | //Use barycentric interpolation to calculate the final height given the 3 vertices and a between them 75 | return Framework::Maths::barycentric(vertex1, vertex2, vertex3, withinSquare); 76 | } 77 | 78 | glm::dvec3 Terrain::getNormal_world(glm::dvec2 horizontalSamplePoint) 79 | /* Called by 80 | - DebugCarModel::updateVectorLines 81 | - WheelInterface::update 82 | Returns the surface normal vector at any arbitrary 2D position on the terrain 83 | */ 84 | { 85 | return mNormals[calc_PerTriAttribute_Index(horizontalSamplePoint)]; 86 | } 87 | 88 | void Terrain::generateHeightData() 89 | /* Called by Terrain::generate 90 | * Calculates and stores height data in mHeights 91 | */ 92 | { 93 | mHeights.resize(mSize * mSize, 0.0); 94 | 95 | for (const auto& layer : mGenerationLayers) 96 | layer->runHeights(mHeights); 97 | } 98 | 99 | void Terrain::generateNormalData() 100 | /* Called by Terrain::generate 101 | * Calculates and stores normal vectors in mNormals 102 | */ 103 | { 104 | /* 105 | * -z 106 | * | 107 | * -x --- + --- +x 108 | * | 109 | * +z 110 | * 111 | * Left triangle 'L' / right triangle 'R' 112 | * 113 | * TL ___ TR 114 | * | / /| 115 | * |L/ /R| 116 | * |/ /__| 117 | * BL BR 118 | */ 119 | 120 | using namespace glm; 121 | 122 | mNormals.resize(pow(mSize - 1, 2) * 2); 123 | 124 | dvec3 125 | ThisBLpoint, //The currently inspected terrain point is considered as the bottom left of a square 126 | TLpoint, //The top left terrain point in this square 127 | TRpoint, //The top right terrain point 128 | BRpoint, //The bottom right terrain point 129 | leftTriangleNormal, //Temporary variables used each iteration to store the normal calculation results for both triangles 130 | rightTriangleNormal; //^ 131 | 132 | const int halfTerrainSize = floor(0.5 * mSize); 133 | 134 | int 135 | XIndex = 0, //If mHeights was treated as a 2D array, this would be the X index into this array ie. mHeights[XIndex][...] 136 | currentIndex = 0; //This is the actual index into the (1D) mHeights array (from the bottom left position) 137 | 138 | //Iterate over the bottom left points of all squares in the terrain 139 | for (int x = -halfTerrainSize; x < halfTerrainSize; x++) { 140 | for (int z = -halfTerrainSize; z < halfTerrainSize; z++) { 141 | 142 | //Calculate the index into mHeights that gets the height at the *current* position 143 | currentIndex = (x + halfTerrainSize) * mSize + (z + halfTerrainSize); 144 | 145 | //Use this index to calculate the X index of mHeights if it was a 2D array (with the bottom left item being [0][0]) 146 | XIndex = floor(currentIndex / mSize); 147 | 148 | //Retrieve the height values that will be needed for the normal calculation 149 | ThisBLpoint = dvec3(0.0, mHeights[currentIndex], 0.0); 150 | TLpoint = dvec3(0.0, mHeights[currentIndex + 1], 1.0); 151 | TRpoint = dvec3(1.0, mHeights[currentIndex + mSize + 1], 1.0); 152 | BRpoint = dvec3(1.0, mHeights[currentIndex + mSize], 0.0); 153 | 154 | //Use the height values to calculate the normal vectors for both halves (triangles) of the currently inspected terrain square 155 | leftTriangleNormal = normalize(cross(TRpoint - TLpoint, ThisBLpoint - TLpoint)); 156 | rightTriangleNormal = normalize(cross(ThisBLpoint - BRpoint, TRpoint - BRpoint)); 157 | 158 | //Insert these normal vectors into the correct positions in mNormals 159 | mNormals[2 * (currentIndex - XIndex)] = leftTriangleNormal; 160 | mNormals[2 * (currentIndex - XIndex) + 1] = rightTriangleNormal; 161 | } 162 | } 163 | } 164 | 165 | void Terrain::generateSurfaceTypeData() 166 | /* Called by Terrain::generate 167 | * Calculates and stores terrain surface types in mSurfaceTypes 168 | */ 169 | { 170 | mSurfaceTypes.resize(pow(mSize - 1, 2) * 2); 171 | 172 | for (const auto& layer : mGenerationLayers) 173 | layer->runSurfaceTypes(mSurfaceTypes); 174 | } 175 | 176 | unsigned int Terrain::calc_PerTriAttribute_Index(glm::dvec2 horizontalSamplePoint) 177 | /* Called by Terrain::getNormal_world 178 | * Maps any arbitrary 2D position in space, onto (the index of) the triangle it is contained within. 179 | * Works for mSurfaceTypes and mNormals (both are one-per-triangle) 180 | */ 181 | { 182 | /* -y 183 | * | 184 | * -x --- + --- +x 185 | * | 186 | * +y 187 | */ 188 | 189 | //Used to determine which triangle (half of a terrain square) the horizontalSamplePoint lies within 190 | const glm::dvec2 withinSquare = horizontalSamplePoint - floor(horizontalSamplePoint); 191 | 192 | const int halfTerrainSize = floor(0.5 * mSize); 193 | 194 | //Indices into the per-triangle-attribute array 195 | const unsigned int 196 | heightsIndexBL_X = horizontalSamplePoint.x < -halfTerrainSize ? 0 : horizontalSamplePoint.x > halfTerrainSize ? mSize - 2 : floor(horizontalSamplePoint.x) + halfTerrainSize, 197 | heightsIndexBL_Z = horizontalSamplePoint.y < -halfTerrainSize ? 0 : horizontalSamplePoint.y > halfTerrainSize ? mSize - 2 : floor(horizontalSamplePoint.y) + halfTerrainSize, 198 | actualHeightsIndexBL = heightsIndexBL_X * mSize + heightsIndexBL_Z; 199 | 200 | return 2 * (actualHeightsIndexBL - heightsIndexBL_X) + (withinSquare.x > withinSquare.y); 201 | } 202 | 203 | } -------------------------------------------------------------------------------- /src/TerrainModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainModel.h" 2 | 3 | namespace Visual { 4 | 5 | TerrainModel::TerrainModel(Framework::ResourceSet& resourceBucket, Framework::Graphics::Shader* terrainShader) : 6 | /* Called by EnvironmentModel::loadResources 7 | */ 8 | mResourceBucket(resourceBucket), 9 | mShader(terrainShader) 10 | { 11 | loadModel(); 12 | } 13 | 14 | void TerrainModel::render(Framework::Graphics::Renderer& renderer) { 15 | mModel.sendRenderCommands(renderer); 16 | } 17 | 18 | void TerrainModel::loadModel() 19 | /* Called by TerrainModel::TerrainModel 20 | */ 21 | { 22 | using namespace Framework::Graphics; 23 | 24 | //This function needs to load the model with procedurally generated terrain 25 | 26 | std::vector positionData; 27 | fillWithPositionData(positionData); 28 | 29 | std::vector normalData; 30 | fillWithNormalData(normalData); 31 | 32 | std::vector colourData; 33 | fillWithColourData(colourData); 34 | 35 | std::vector indexData; 36 | fillWithIndexData(indexData); 37 | 38 | //Add an(empty) mesh for the terrain 39 | Mesh* terrainMesh = mResourceBucket.addMesh("terrainMesh", GL_TRIANGLES, nullptr, mShader); 40 | 41 | //Add a vertex buffer for terrain positions 42 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(0, 3, GL_FLOAT, false), positionData, "terrainPositions"); 43 | 44 | //Add a vertex buffer for terrain positions 45 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(1, 3, GL_FLOAT, false), normalData, "terrainNormals"); 46 | 47 | //Add a vertex buffer for terrain positions 48 | mResourceBucket.addVertexBuffer(GL_STATIC_DRAW, VertexFormat(2, 3, GL_FLOAT, false), colourData, "terrainColours"); 49 | 50 | //Add an index buffer for terrain indices 51 | mResourceBucket.addIndexBuffer(GL_STATIC_DRAW, indexData, "terrainIndices"); 52 | 53 | //Add buffers to mesh 54 | terrainMesh->addBuffer(mResourceBucket.getResource("terrainPositions")); 55 | terrainMesh->addBuffer(mResourceBucket.getResource("terrainNormals")); 56 | terrainMesh->addBuffer(mResourceBucket.getResource("terrainColours")); 57 | terrainMesh->addIndexBuffer(mResourceBucket.getResource("terrainIndices")); 58 | 59 | mModel.addMesh(terrainMesh); 60 | } 61 | 62 | void TerrainModel::fillWithPositionData(std::vector& toFill) 63 | /* Called by TerrainModel::loadModel 64 | */ 65 | { 66 | /* 67 | * Vector comes in empty, and leaves full of position data 68 | * 69 | * Uses the following coordinate system for x and z 70 | * 71 | * -z 72 | * | 73 | * -x --- + --- +x 74 | * | 75 | * +z 76 | */ 77 | 78 | using namespace External; 79 | 80 | const int halfTerrainSize = floor(0.5 * Environment::mTerrain.getSize()); 81 | 82 | //These represent the terrain heights at each of the four corners of the current terrain square being inspected 83 | double 84 | BL = 0.0, 85 | TL = 0.0, 86 | TR = 0.0, 87 | BR = 0.0; 88 | 89 | for (int x = -halfTerrainSize; x < halfTerrainSize; x++) { 90 | for (int z = -halfTerrainSize; z < halfTerrainSize; z++) { 91 | //An (x,z) coordinate in here sits on the bottom left (origin) of one of the terrain squares. 92 | //This can be used to add the position data. 93 | 94 | //First get the heights 95 | BL = Environment::mTerrain.getHeight(glm::dvec2(x, z)); 96 | TL = Environment::mTerrain.getHeight(glm::dvec2(x, z + 1.0)); 97 | TR = Environment::mTerrain.getHeight(glm::dvec2(x + 1.0, z + 1.0)); 98 | BR = Environment::mTerrain.getHeight(glm::dvec2(x + 1.0, z)); 99 | 100 | //Now add each position to the buffer 101 | 102 | //v0 103 | toFill.push_back(x); 104 | toFill.push_back(BL); 105 | toFill.push_back(z); 106 | 107 | //v1 108 | toFill.push_back(x); 109 | toFill.push_back(TL); 110 | toFill.push_back(z + 1.0); 111 | 112 | //v2 113 | toFill.push_back(x + 1.0); 114 | toFill.push_back(TR); 115 | toFill.push_back(z + 1.0); 116 | 117 | //v2 118 | toFill.push_back(x + 1.0); 119 | toFill.push_back(TR); 120 | toFill.push_back(z + 1.0); 121 | 122 | //v3 123 | toFill.push_back(x + 1.0); 124 | toFill.push_back(BR); 125 | toFill.push_back(z); 126 | 127 | //v0 128 | toFill.push_back(x); 129 | toFill.push_back(BL); 130 | toFill.push_back(z); 131 | } 132 | } 133 | } 134 | 135 | void TerrainModel::fillWithNormalData(std::vector& toFill) 136 | /* Called by TerrainModel::loadModel 137 | * Iterates over the terrain, and retrieves the surface normal vector for each triangle before adding it to the buffer passed in 138 | */ 139 | { 140 | /* 141 | * -z 142 | * | 143 | * -x --- + --- +x 144 | * | 145 | * +z 146 | * 147 | * Left triangle 'L' / right triangle 'R' 148 | * 149 | * ___ 150 | * | / /| 151 | * |L/ /R| 152 | * |/ /__| 153 | * 154 | */ 155 | 156 | using namespace glm; 157 | using namespace External; 158 | 159 | const int 160 | terrainSize = Environment::mTerrain.getSize(), 161 | halfTerrainSize = floor(0.5 * terrainSize); 162 | 163 | vec3 164 | leftTriangleNormal, 165 | rightTriangleNormal; 166 | 167 | int 168 | normalsArrayIndexX = 0, 169 | heightsIndex = 0; 170 | 171 | for (int x = -halfTerrainSize; x < halfTerrainSize; x++) { 172 | for (int z = -halfTerrainSize; z < halfTerrainSize; z++) { 173 | heightsIndex = (x + halfTerrainSize) * terrainSize + (z + halfTerrainSize); 174 | normalsArrayIndexX = floor(heightsIndex / terrainSize); 175 | 176 | leftTriangleNormal = Environment::mTerrain.mNormals[2 * (heightsIndex - normalsArrayIndexX)]; 177 | rightTriangleNormal = Environment::mTerrain.mNormals[2 * (heightsIndex - normalsArrayIndexX) + 1]; 178 | 179 | //v0 180 | toFill.push_back(leftTriangleNormal.x); 181 | toFill.push_back(leftTriangleNormal.y); 182 | toFill.push_back(leftTriangleNormal.z); 183 | 184 | //v1 185 | toFill.push_back(leftTriangleNormal.x); 186 | toFill.push_back(leftTriangleNormal.y); 187 | toFill.push_back(leftTriangleNormal.z); 188 | 189 | //v2 190 | toFill.push_back(leftTriangleNormal.x); 191 | toFill.push_back(leftTriangleNormal.y); 192 | toFill.push_back(leftTriangleNormal.z); 193 | 194 | //v3 195 | toFill.push_back(rightTriangleNormal.x); 196 | toFill.push_back(rightTriangleNormal.y); 197 | toFill.push_back(rightTriangleNormal.z); 198 | 199 | //v4 200 | toFill.push_back(rightTriangleNormal.x); 201 | toFill.push_back(rightTriangleNormal.y); 202 | toFill.push_back(rightTriangleNormal.z); 203 | 204 | //v5 205 | toFill.push_back(rightTriangleNormal.x); 206 | toFill.push_back(rightTriangleNormal.y); 207 | toFill.push_back(rightTriangleNormal.z); 208 | } 209 | } 210 | } 211 | 212 | void TerrainModel::fillWithColourData(std::vector& toFill) 213 | /* Called by TerrainModel::loadModel 214 | */ 215 | { 216 | /* 217 | * -z 218 | * | 219 | * -x --- + --- +x 220 | * | 221 | * +z 222 | * 223 | * Left triangle 'L' / right triangle 'R' 224 | * 225 | * ___ 226 | * | / /| 227 | * |L/ /R| 228 | * |/ /__| 229 | * 230 | */ 231 | 232 | using namespace glm; 233 | using namespace External; 234 | 235 | const int 236 | terrainSize = Environment::mTerrain.getSize(), 237 | halfTerrainSize = floor(0.5 * terrainSize); 238 | 239 | vec3 240 | leftTriangleColour, 241 | rightTriangleColour; 242 | 243 | int 244 | surfaceTypesArrayIndexX = 0, 245 | surfaceTypesArrayIndexZ = 0, 246 | heightsIndex = 0; 247 | 248 | vec3 terrainSurfaceColours[3]; 249 | terrainSurfaceColours[External::TerrainType::GRASS] = glm::vec3(0.2509803921568627f, 0.3529411764705882f, 0.1215686274509804f); 250 | terrainSurfaceColours[External::TerrainType::TARMAC] = glm::vec3(0.3725490196078431f, 0.2509803921568627f, 0.1411764705882353f); 251 | terrainSurfaceColours[External::TerrainType::ERROR_TYPE] = glm::vec3(1.0f, 0.0f, 0.0f); 252 | 253 | for (int x = -halfTerrainSize; x < halfTerrainSize; x++) { 254 | for (int z = -halfTerrainSize; z < halfTerrainSize; z++) { 255 | heightsIndex = (x + halfTerrainSize) * terrainSize + (z + halfTerrainSize); 256 | surfaceTypesArrayIndexX = floor(heightsIndex / terrainSize); 257 | surfaceTypesArrayIndexZ = heightsIndex - terrainSize * surfaceTypesArrayIndexX; 258 | 259 | leftTriangleColour = terrainSurfaceColours[Environment::mTerrain.mSurfaceTypes[2 * (heightsIndex - surfaceTypesArrayIndexX)]]; 260 | rightTriangleColour = terrainSurfaceColours[Environment::mTerrain.mSurfaceTypes[2 * (heightsIndex - surfaceTypesArrayIndexX) + 1]]; 261 | 262 | //Add some distortion 263 | double distortionLevel = 0.08; 264 | leftTriangleColour += vec3(distortionLevel) * ((((float)rand() / RAND_MAX) * 2.0f - 1.0f) / 2.0f); 265 | rightTriangleColour += vec3(distortionLevel) * ((((float)rand() / RAND_MAX) * 2.0f - 1.0f) / 2.0f); 266 | 267 | //v0 268 | toFill.push_back(leftTriangleColour.x); 269 | toFill.push_back(leftTriangleColour.y); 270 | toFill.push_back(leftTriangleColour.z); 271 | 272 | //v1 273 | toFill.push_back(leftTriangleColour.x); 274 | toFill.push_back(leftTriangleColour.y); 275 | toFill.push_back(leftTriangleColour.z); 276 | 277 | //v2 278 | toFill.push_back(leftTriangleColour.x); 279 | toFill.push_back(leftTriangleColour.y); 280 | toFill.push_back(leftTriangleColour.z); 281 | 282 | //v3 283 | toFill.push_back(rightTriangleColour.x); 284 | toFill.push_back(rightTriangleColour.y); 285 | toFill.push_back(rightTriangleColour.z); 286 | 287 | //v4 288 | toFill.push_back(rightTriangleColour.x); 289 | toFill.push_back(rightTriangleColour.y); 290 | toFill.push_back(rightTriangleColour.z); 291 | 292 | //v5 293 | toFill.push_back(rightTriangleColour.x); 294 | toFill.push_back(rightTriangleColour.y); 295 | toFill.push_back(rightTriangleColour.z); 296 | } 297 | } 298 | } 299 | 300 | void TerrainModel::fillWithIndexData(std::vector& toFill) 301 | /*Called by TerrainModel::loadModel 302 | */ 303 | { 304 | for (unsigned int i = 0; i < pow(External::Environment::mTerrain.getSize() - 1, 2) * 6; i++) 305 | toFill.push_back(i); 306 | } 307 | 308 | } -------------------------------------------------------------------------------- /src/Track.cpp: -------------------------------------------------------------------------------- 1 | #include "Track.h" 2 | 3 | #define CIRCULAR_TRACK 0 4 | #define COMPLEX_TRACK 1 5 | #define SQUARE_TRACK 0 6 | #define ALMOST_SQUARE_TRACK 0 7 | #define P_SHAPED_TRACK 0 8 | 9 | namespace External { 10 | 11 | Track::Track(const unsigned int terrainSize_heightSamples) : 12 | /* Called by Terrain::Terrain 13 | * Prepares the class for runHeights and runSurfaceTypes to be called, by running a pilot version of the track generation algorithm 14 | */ 15 | mTerrainSize_heightSamples(terrainSize_heightSamples) 16 | { 17 | mTempSurfaceTypes.resize(pow(terrainSize_heightSamples - 1, 2) * 2); 18 | 19 | addAllPoints(); 20 | runPilotVersion(); 21 | 22 | mSizeLimit = (mTerrainSize_heightSamples - 1) - (2 * mTerrainBorderPadding) - mWidth; 23 | mPilotToMainScaleFactor = mSizeLimit / std::max(mPilotResults.mShapeDimensions.x, mPilotResults.mShapeDimensions.y); 24 | 25 | updateStartingPosition(); 26 | } 27 | 28 | void Track::runHeights(std::vector& previousLayerHeights) 29 | /* Called by Terrain::generateHeightData 30 | * Adds the track shape to the terrain height buffer passed in 31 | */ 32 | { 33 | const int 34 | terrainSize = sqrt(previousLayerHeights.size()), 35 | halfTerrainSize = 0.5 * terrainSize; 36 | 37 | int 38 | currentX = 0, 39 | currentZ = 0, 40 | lastX = 0, 41 | lastZ = 0; 42 | 43 | double currentAngle = 0.0; 44 | 45 | glm::dvec2 46 | positionTracker = mStartPosition, 47 | currentDirection = mStartDirection; 48 | 49 | //Iterate over the shape of the track by taking small steps along a changing direction vector 50 | for (unsigned int i = 0; i < mNumSamplesOverTotal; i++) { 51 | 52 | //Look up the angle of the 2D direction vector, clockwise from the -Z axis, at the current position on the track 53 | currentAngle = lookUpAngleAtPercent_graph((double)i / (double)mNumSamplesOverTotal); 54 | 55 | //Create a direction (unit) vector using this angle 56 | currentDirection = normalize(glm::rotate(mStartDirection, glm::radians(currentAngle))); 57 | 58 | //Take a step along the vector 59 | positionTracker += currentDirection * mPilotToMainScaleFactor; 60 | 61 | //If the track could sit inside the terrain at our position after taking this step... 62 | if ( 63 | positionTracker.x > (-halfTerrainSize - 0.5 * mWidth) && 64 | positionTracker.x < (halfTerrainSize + 0.5 * mWidth) && 65 | positionTracker.y >(-halfTerrainSize - 0.5 * mWidth) && 66 | positionTracker.y < (halfTerrainSize + 0.5 * mWidth)) 67 | { 68 | //...then find the terrain square that our current position lies within 69 | currentX = floor(positionTracker.x); 70 | currentZ = floor(positionTracker.y); 71 | 72 | //If this is different to the previous square... 73 | if (currentX != lastX || currentZ != lastZ) { 74 | 75 | //...then imprint/'stamp' a circle with the track width as its diameter around this point and continue 76 | imprintCircle(previousLayerHeights, currentX, currentZ); 77 | lastX = currentX; 78 | lastZ = currentZ; 79 | } 80 | } 81 | else 82 | continue; 83 | } 84 | } 85 | 86 | void Track::runSurfaceTypes(std::vector& previousLayerSurfaceTypes) 87 | /* Called by Terrain::generateSurfaceTypeData 88 | * Sets the terrain surface types for the track 89 | */ 90 | { 91 | //To avoid unnecessarily iterating over the track twice, mTempSurfaceTypes is filled DURING the height iteration and then 92 | //just copied into previousLayerSurfaceTypes here. 93 | previousLayerSurfaceTypes = mTempSurfaceTypes; 94 | } 95 | 96 | void Track::addAllPoints() 97 | /* Called by Track::Track 98 | * Defines the shape of a continuous loop (independent of position, orientation or scale) 99 | */ 100 | { 101 | //The angle of the tangent to the track (clockwise from the starting direction) is a function of the percentage along the track. 102 | //Points are added to a graph of this function, and linear interpolation between them is used to make it continuous. 103 | 104 | mPoints_graph.push_back(glm::dvec2(0.0, 0.0)); 105 | 106 | //Multiple track shapes were used during testing, but 'COMPLEX_TRACK' is the final version used 107 | //in the application 108 | 109 | #if COMPLEX_TRACK 110 | double 111 | pi = glm::pi(), 112 | total = 0.0, 113 | L = 6.0 + (21.0 / 2.0) * pi; 114 | 115 | total += 0.5 * pi; 116 | addPoint_graph(total / L, 90.0); //b 117 | 118 | total += 3.0; 119 | addPoint_graph(total / L, 90.0); //c 120 | 121 | total += (3.0 / 2.0) * pi; 122 | addPoint_graph(total / L, 180.0); //d 123 | 124 | total += pi; 125 | addPoint_graph(total / L, 270.0); //e 126 | 127 | total += 0.5 * pi; 128 | addPoint_graph(total / L, 360.0); //f 129 | 130 | total += pi; 131 | addPoint_graph(total / L, 270.0); //g 132 | 133 | total += 0.5 * pi; 134 | addPoint_graph(total / L, 180.0); //h 135 | 136 | total += 0.5 * pi; 137 | addPoint_graph(total / L, 90.0); //i 138 | 139 | total += 0.5 * pi; 140 | addPoint_graph(total / L, 180.0); //j 141 | 142 | total += 3; 143 | addPoint_graph(total / L, 180.0); //k 144 | 145 | total += 0.5 * pi; 146 | addPoint_graph(total / L, 270.0); //l 147 | 148 | total += 0.5 * pi; 149 | addPoint_graph(total / L, 360.0); //m 150 | 151 | total += (3.0 / 2.0) * pi; 152 | addPoint_graph(total / L, 270.0); //n 153 | 154 | total += 0.5 * pi; 155 | addPoint_graph(total / L, 360.0); //o 156 | 157 | total += pi; 158 | addPoint_graph(total / L, 90.0); //p 159 | #endif 160 | 161 | #if P_SHAPED_TRACK 162 | addPoint_graph(0.242597, 180); 163 | addPoint_graph(0.485194, 0); 164 | addPoint_graph(0.588155, 0); 165 | addPoint_graph(0.588155, 270); 166 | addPoint_graph(0.897039, 270); 167 | addPoint_graph(0.897039, 180); 168 | addPoint_graph(1, 180); 169 | #endif 170 | 171 | #if ALMOST_SQUARE_TRACK 172 | addPoint_graph(0.25, 0); 173 | addPoint_graph(0.25, 90); 174 | addPoint_graph(0.375, 90); 175 | addPoint_graph(0.375, 180); 176 | addPoint_graph(0.5, 180); 177 | addPoint_graph(0.5, 90); 178 | addPoint_graph(0.625, 90); 179 | addPoint_graph(0.625, 180); 180 | addPoint_graph(0.75, 180); 181 | addPoint_graph(0.75, 270); 182 | addPoint_graph(1, 270); 183 | #endif 184 | 185 | #if SQUARE_TRACK 186 | addPoint_graph(0.25, 0.0); 187 | addPoint_graph(0.25, 90.0); 188 | addPoint_graph(0.5, 90.0); 189 | addPoint_graph(0.5, 180.0); 190 | addPoint_graph(0.75, 180.0); 191 | addPoint_graph(0.75, 270.0); 192 | addPoint_graph(1.0, 270.0); 193 | #endif 194 | 195 | mPoints_graph.push_back(glm::dvec2(1.0, 360.0)); 196 | } 197 | 198 | void Track::addPoint_graph(double percent, double angle) 199 | /* Called by Track::addAllPoints 200 | */ 201 | { 202 | if (percent >= 0.0 && percent <= 1.0 && angle >= 0.0 && angle <= 360.0) 203 | mPoints_graph.push_back(glm::dvec2(percent, angle)); 204 | else 205 | printf("ERROR: Point out of bounds: (%g, %g)\n", percent, angle); 206 | } 207 | 208 | double Track::lookUpAngleAtPercent_graph(double percent) 209 | /* Called by 210 | * - Track::runPilotVersion 211 | * - Track::runHeights 212 | * Input: a percentage along the track, given between 0.0 and 1.0 213 | * Output: the angle of the tangent to the track at this percentage 214 | */ 215 | { 216 | //Lower and upper bounds for interpolation are both points on the graph 217 | glm::dvec2 218 | behind, 219 | ahead; 220 | 221 | //Make sure that the track wraps round 222 | if (percent < 0.0) percent += 1.0; 223 | if (percent > 1.0) percent -= 1.0; 224 | 225 | //Iterate over all points on the percentage-angle graph 226 | for (unsigned int i = 0; i < mPoints_graph.size(); i++) { 227 | 228 | //If there's a point on the graph with the exact target percentage... 229 | if (mPoints_graph[i].x == percent) { 230 | 231 | //... then no interpolation is required, and this can be returned directly. 232 | 233 | unsigned int latest = 0; 234 | 235 | //Just check that there aren't any other more recent points with the same coordinate 236 | for (unsigned int j = i; j < mPoints_graph.size(); j++) { 237 | if (mPoints_graph[j].x == percent) 238 | latest = j; 239 | } 240 | 241 | return mPoints_graph[latest].y; 242 | } 243 | //Just overshot the target point 244 | else if (mPoints_graph[i].x > percent) { 245 | 246 | //The point behind the current position 247 | behind = mPoints_graph[i - 1]; 248 | 249 | //The point ahead of the current position 250 | ahead = mPoints_graph[i]; 251 | 252 | //Handle the fact that 0 degrees is the same as 360 degrees 253 | if (behind.y == 360.0 && (360.0 - ahead.y >= 180.0)) 254 | behind.y = 0.0; 255 | else if (ahead.y == 360.0 && (360.0 - behind.y >= 180.0) && (behind.y != 0.0)) 256 | ahead.y = 0.0; 257 | 258 | //Interpolate between points to return the answer 259 | return behind.y + (ahead.y - behind.y) * ((percent - behind.x) / (ahead.x - behind.x)); 260 | } 261 | } 262 | } 263 | 264 | void Track::imprintCircle(std::vector& toImprintHeights, int centreX, int centreZ) 265 | /* Called by Track::runHeights 266 | * Imprints a circle with a diameter of mWidth, and centre (centreX, centreZ) in the terrain height data 267 | */ 268 | { 269 | const int 270 | halfTerrainSize = floor(mTerrainSize_heightSamples * 0.5), 271 | circleRadius = floor(0.5 * mWidth); 272 | 273 | //X and Z need to be integer coordinates within the bounds of the heights array 274 | int 275 | targetX = 0, 276 | targetZ = 0, 277 | currentHeightIndex = 0; 278 | 279 | double 280 | distToCircleCentre = 0.0, //The distance from the current point to the centre of the circle 281 | circleWeighting = 0.0, //A value between 0.0 and 1.0 describing the depth of the 'bowl' shape at a point 282 | newHeight = 0.0; //Used to store the new height being added to the terrain 283 | 284 | //Iterate over a square of points around (centreX, centreZ) 285 | for (int x = -circleRadius; x <= circleRadius; x++) { 286 | for (int z = -circleRadius; z <= circleRadius; z++) { 287 | 288 | //Calculate the distance of the current point to the centre of the circle 289 | distToCircleCentre = sqrt(pow(x, 2.0) + pow(z, 2.0)); 290 | 291 | //If this distance is greater than the radius, then the point is outside the circle 292 | if (distToCircleCentre > circleRadius) continue; 293 | 294 | //Otherwise, establish where the target point is on the terrain (given the position of the centre, 295 | //and the position of the point relative to the centre). 296 | targetX = centreX + x; 297 | targetZ = centreZ + z; 298 | 299 | //Use these values to get the index into the height array of this point 300 | currentHeightIndex = (targetX + halfTerrainSize) * mTerrainSize_heightSamples + (targetZ + halfTerrainSize); 301 | 302 | //Check that this point is actually within the terrain 303 | if (targetX >= -halfTerrainSize && targetX <= halfTerrainSize && targetZ >= -halfTerrainSize && targetZ <= halfTerrainSize) { 304 | 305 | //This line is used to give the track a 'bowl' like shape, by calculating a depth based on the distance to the centre of the circle 306 | circleWeighting = (1.0 - pow(distToCircleCentre / circleRadius, 3.0)); 307 | newHeight = circleWeighting * -mMaxDepth; 308 | 309 | //Avoids a shape issue with overlapping circles 310 | if (newHeight < toImprintHeights[currentHeightIndex]) 311 | toImprintHeights[currentHeightIndex] = newHeight; 312 | 313 | //Lastly, modify the surface types to represent the track 314 | mTempSurfaceTypes[2 * (currentHeightIndex - halfTerrainSize - targetX)] = TerrainType::TARMAC; 315 | mTempSurfaceTypes[2 * (currentHeightIndex - halfTerrainSize - targetX) + 1] = TerrainType::TARMAC; 316 | } 317 | } 318 | } 319 | } 320 | 321 | void Track::runPilotVersion() 322 | /* Called by Track::Track 323 | * Iterates over the track shape once to determine various parameters required before the full iteration is completed, 324 | * like the bounds of shape, its dimensions and a 325 | */ 326 | { 327 | //Used to store values during the iteration 328 | double 329 | percent = 0.0, 330 | currentAngle = 0.0; 331 | 332 | glm::dvec2 333 | direction = mStartDirection, 334 | displacementTracker = glm::dvec2(0.0); 335 | 336 | //Iterate round the shape with a given number of samples 337 | for (unsigned int i = 0; i < mNumSamplesOverTotal; i++) { 338 | 339 | //The the current percentage round the track, between 0.0 and 1.0 340 | percent = (double)i / (double)mNumSamplesOverTotal; 341 | currentAngle = lookUpAngleAtPercent_graph(percent); 342 | 343 | direction = normalize(glm::rotate(mStartDirection, glm::radians(currentAngle))); 344 | displacementTracker += direction; 345 | 346 | //The track shape's bounding rectangle grows as the track covers more area 347 | if (displacementTracker.x < mPilotResults.mLowerBoundDisplacement.x) 348 | mPilotResults.mLowerBoundDisplacement.x = displacementTracker.x; 349 | else if (displacementTracker.x > mPilotResults.mUpperBoundDisplacement.x) 350 | mPilotResults.mUpperBoundDisplacement.x = displacementTracker.x; 351 | 352 | if (displacementTracker.y < mPilotResults.mLowerBoundDisplacement.y) 353 | mPilotResults.mLowerBoundDisplacement.y = displacementTracker.y; 354 | else if (displacementTracker.y > mPilotResults.mUpperBoundDisplacement.y) 355 | mPilotResults.mUpperBoundDisplacement.y = displacementTracker.y; 356 | } 357 | 358 | mPilotResults.mShapeDimensions = mPilotResults.mUpperBoundDisplacement - mPilotResults.mLowerBoundDisplacement; 359 | } 360 | 361 | void Track::updateStartingPosition() 362 | /* Called by Track::Track 363 | * Calculates the starting position of the track, for the full iteration, given the results obtained by the pilot run. 364 | * This accounts for the track dimensions, width, and mTerrainBorderPadding (the space that should always be left 365 | * clear of the track at the edges of the terrain). 366 | */ 367 | { 368 | //Occurs on both axes 369 | double 370 | terrainSize_units = mTerrainSize_heightSamples - 1, 371 | completePadding = mTerrainBorderPadding + 0.5 * mWidth; 372 | 373 | //Scale the results of the pilot run to match the size that the full track should cover 374 | glm::dvec2 375 | lowerBound = mPilotResults.mLowerBoundDisplacement * mPilotToMainScaleFactor, 376 | upperBound = mPilotResults.mUpperBoundDisplacement * mPilotToMainScaleFactor, 377 | dimensions = mPilotResults.mShapeDimensions * mPilotToMainScaleFactor; 378 | 379 | //Calculate a starting position for the track that allows its bounding rectangle to fit within that of the terrain (square) 380 | if (mPilotResults.shapeIsLandscape()) { 381 | mStartPosition.x = completePadding + abs(lowerBound.x); 382 | mStartPosition.y = completePadding + abs(lowerBound.y) + 0.5 * (terrainSize_units - (completePadding * 2.0) - dimensions.y); 383 | } 384 | else { 385 | mStartPosition.x = completePadding + abs(lowerBound.x) + 0.5 * (terrainSize_units - (completePadding * 2.0) - dimensions.x); 386 | mStartPosition.y = completePadding + abs(lowerBound.y); 387 | } 388 | 389 | mStartPosition -= glm::dvec2(0.5 * terrainSize_units); 390 | } 391 | 392 | } -------------------------------------------------------------------------------- /src/Tyre.cpp: -------------------------------------------------------------------------------- 1 | #include "Tyre.h" 2 | 3 | namespace Internal { 4 | 5 | Tyre::Tyre(double wheelRimRadius) : 6 | /* Called by Wheel::Wheel 7 | */ 8 | mAxialInertia((glm::pi() * mRubberDensity * mTreadWidth) / 2.0 * (pow(wheelRimRadius + mDepth, 4) - pow(wheelRimRadius, 4))) 9 | { } 10 | 11 | void Tyre::update(glm::dvec2 wheelVelocity_wheel, double verticalLoad, double camberAngle, double wheelRimRadius, double wheelRotSpeed_radPerSec) 12 | /* Called by Wheel::update 13 | */ 14 | { 15 | double effectiveRollingRadius = wheelRimRadius + mDepth; 16 | mSlip.update(wheelVelocity_wheel, wheelRotSpeed_radPerSec, effectiveRollingRadius); 17 | mRollingSpeed = wheelRotSpeed_radPerSec * effectiveRollingRadius; 18 | 19 | mRollResistForce_long = wheelVelocity_wheel.y > 0.0 ? -mRollResistCoefficient * verticalLoad : wheelVelocity_wheel.y < 0.0 ? mRollResistCoefficient * verticalLoad : 0.0; 20 | 21 | mForceCalculator.updateForces(verticalLoad, mSlip.getLongitudinal() * 100.0, mSlip.getAngle_degs(), camberAngle); 22 | mTotalForce_wheel.x = mForceCalculator.getLateralForce(); 23 | mTotalForce_wheel.y = mForceCalculator.getLongitudinalForce() + mRollResistForce_long; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/UILayer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "UILayer.h" 4 | #include "Car.h" 5 | #include "Environment.h" 6 | 7 | namespace Visual { 8 | 9 | UILayer::UILayer(Internal::Car& simDataSource, Framework::Graphics::Shader& carModelShader, CameraSystem& cameraSystem, float& simulationSpeedHandle, bool& debugModeHandle) : 10 | /* Called by VisualShell::load 11 | */ 12 | mDataSource(simDataSource), 13 | mCarModelShader(carModelShader), 14 | mCameraSystem(cameraSystem), 15 | mSimulationSpeedHandle(simulationSpeedHandle), 16 | mShowDebugMode_handle(debugModeHandle) 17 | { 18 | load(); 19 | } 20 | 21 | void UILayer::render() 22 | /* Called by VisualShell::renderAll 23 | * Renders all active ImGui windows 24 | */ 25 | { 26 | mainControlPanel(); 27 | 28 | if (mShowCarCustomise) carCustomisation(); 29 | if (mShowDriverInfo) driverInfo(); 30 | if (mShowDebugMode_handle) carDebugInfo(); 31 | if (mShowHelpInfo) helpInfo(); 32 | if (mShowTyreParams) tyreParameters(); 33 | 34 | upsideDownWarning(); 35 | } 36 | 37 | void UILayer::load() const 38 | /* Called by UILayer::UILayer 39 | */ 40 | { 41 | //Load the ImGui style 42 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 43 | ImGui::PushStyleVar(ImGuiStyleVar_ChildWindowRounding, 4.0f); 44 | ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.17647058823f, 0.17647058823f, 0.18823529411f, 0.4f)); 45 | ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImVec4(0.11764705882f, 0.11764705882f, 0.11764705882f, 0.4f)); 46 | ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.64705882352, 0.64705882352, 0.64705882352, 0.4f)); 47 | } 48 | 49 | void UILayer::mainControlPanel() 50 | /* Called by UILayer::render 51 | * Defines the structure of the main control panel window 52 | * The main control panel is always available (unlike other windows that can be hidden) 53 | */ 54 | { 55 | using namespace ImGui; 56 | 57 | SetNextWindowPos(ImVec2(0.0f, 0.0f)); 58 | SetNextWindowSize(ImVec2(235.0f, 425.0f)); 59 | Begin("Control panel", NULL, ImGuiWindowFlags_NoResize); 60 | { 61 | float childWidth = GetContentRegionAvailWidth(); 62 | 63 | Text("Simulation settings"); 64 | BeginChild("Simulation settings", ImVec2(childWidth, 80.0f), true); 65 | { 66 | Text("Time"); 67 | SliderFloat("Speed", &mSimulationSpeedHandle, 0.0f, 10.0f); 68 | if (Button("Resume")) mSimulationSpeedHandle = 1.0f; SameLine(); 69 | if (Button("Pause")) mSimulationSpeedHandle = 0.0f; SameLine(); 70 | if (Button("0.5%")) mSimulationSpeedHandle = 0.005f; SameLine(); 71 | if (Button("50%")) mSimulationSpeedHandle = 0.5f; 72 | EndChild(); 73 | } 74 | 75 | Text("Vehicle state"); 76 | BeginChild("Vehicle state", ImVec2(childWidth, 65.0f), true); 77 | { 78 | Framework::Physics::State& carState = mDataSource.getState(); 79 | static glm::vec3 position = carState.getPosition_world(); 80 | 81 | if (Button("Vehicle to track")) 82 | mDataSource.resetToTrackPosition(); 83 | 84 | if (Button("Suspension demo")) { 85 | glm::dvec3 currentPosition = carState.getPosition_world(); 86 | carState.reset(); 87 | carState.setPosition_world(currentPosition + glm::dvec3(0.0, 5.0, 0.0)); 88 | 89 | carState.setOrientation_world( 90 | rotate( 91 | carState.getOrientation_world(), 92 | glm::radians(30.0), 93 | glm::dvec3((double)rand() / RAND_MAX * 2.0 - 0.5, 0.0, (double)rand() / RAND_MAX * 2.0 - 0.5) 94 | ) 95 | ); 96 | } 97 | EndChild(); 98 | } 99 | 100 | Text("View"); 101 | BeginChild("View", ImVec2(childWidth, 185.0f), true); 102 | { 103 | Text("Windows"); 104 | Checkbox("Driver info panel", &mShowDriverInfo); 105 | Checkbox("Debug panel", &mShowDebugMode_handle); 106 | Checkbox("Car customisation panel", &mShowCarCustomise); 107 | Checkbox("Help", &mShowHelpInfo); 108 | Checkbox("Tyre parameters", &mShowTyreParams); 109 | 110 | Separator(); 111 | 112 | Text("Cameras"); 113 | if (Button(" <- ")) mCameraSystem.cycleCameras(true); 114 | SameLine(); 115 | switch (mCameraSystem.getCurrentCameraName()) { 116 | case CameraSystem::DRIVER: Text("Driver "); break; 117 | case CameraSystem::FPV: Text("Free camera "); break; 118 | case CameraSystem::FR_WHEEL: Text("Front-right wheel"); break; 119 | default: Text("ERROR"); break; 120 | } 121 | SameLine(); 122 | if (Button(" -> ")) mCameraSystem.cycleCameras(false); 123 | 124 | EndChild(); 125 | } 126 | } 127 | 128 | End(); 129 | } 130 | 131 | void UILayer::carCustomisation() const 132 | /* Called by UILayer::render 133 | * Defines the structure of the car customisation window 134 | */ 135 | { 136 | using namespace ImGui; 137 | using namespace Internal; 138 | 139 | Begin("Vehicle customisation"); 140 | { 141 | float childWidth = GetContentRegionAvailWidth(); 142 | 143 | //Car's mass 144 | static float mass = 1961.0f; 145 | Text("Physics state"); 146 | SliderFloat("mass", &mass, 100.0f, 2000.0f); 147 | if (Button("Reset")) mass = 1961.0f; 148 | mDataSource.getState().setMassValue_local(mass); 149 | 150 | //Suspension parameters 151 | Text("Suspension"); 152 | BeginChild("Suspension", ImVec2(childWidth, 100.0f), true); 153 | { 154 | static float 155 | springConstant = 49000.0f, 156 | dampingCoefficient = 3000.0f; 157 | 158 | SliderFloat("Spring constant", &springConstant, 10000.0f, 50000.0f); 159 | if (Button("Reset")) springConstant = 49000.0f; 160 | SliderFloat("Damping coeffient", &dampingCoefficient, 0.0f, 8000.0f); 161 | if (Button("Reset damping")) dampingCoefficient = 3000.0f; 162 | 163 | Framework::Physics::Spring* currentSpring = nullptr; 164 | for (Internal::WheelInterface& s : mDataSource.getWheelSystem().getAllWheelInterfaces()) { 165 | currentSpring = &s.getSuspension().getSpring(); 166 | currentSpring->setSpringConstant(springConstant); 167 | currentSpring->setDamping(dampingCoefficient); 168 | } 169 | 170 | EndChild(); 171 | } 172 | 173 | Text("Appearance"); 174 | static float colours[3] = { 0.1f, 0.2f, 0.5f }; 175 | ColorPicker3("body colour", colours); 176 | mCarModelShader.bind(); 177 | mCarModelShader.setUniform(7, glm::vec3(colours[0], colours[1], colours[2])); 178 | } 179 | 180 | End(); 181 | } 182 | 183 | void UILayer::driverInfo() const 184 | /* Called by UILayer::render 185 | * Displays at-a-glance information about the car 186 | * Defines the structure of the driver information window 187 | */ 188 | { 189 | using namespace ImGui; 190 | 191 | ImVec4 192 | red = ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 193 | green = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); 194 | 195 | Begin("Vehicle overview"); 196 | 197 | //Speed info 198 | { 199 | double speedMetPerSec = glm::length(mDataSource.getState().getVelocity_world()); 200 | Text("Speed (mph): %.1f", speedMetPerSec * 2.23694); 201 | Text("Speed (kph): %.1f", speedMetPerSec * 3.6); 202 | } 203 | 204 | //Drive mode info 205 | { 206 | Text("Engine mode: "); SameLine(); 207 | bool reverseMode = mDataSource.getTorqueGenerator().reverseModeOn(); 208 | PushStyleColor(ImGuiCol_Text, reverseMode ? red : green); 209 | Text("%s", reverseMode ? "REVERSE" : "FORWARD"); 210 | PopStyleColor(); 211 | } 212 | 213 | Internal::ControlSystem& controls = mDataSource.getControlSystem(); 214 | 215 | //Brake info 216 | { 217 | Text("Brakes: "); SameLine(); 218 | bool brakesOn = controls.brakesOn(); 219 | PushStyleColor(ImGuiCol_Text, brakesOn ? green : red); 220 | Text("%s", brakesOn ? "ON" : "OFF"); 221 | PopStyleColor(); 222 | } 223 | 224 | //Steering info 225 | Text("Steering wheel angle: %.3f", controls.getSteeringWheelAngle()); 226 | 227 | End(); 228 | } 229 | 230 | void UILayer::helpInfo() const 231 | /* Called by UILayer::render 232 | * Available by default when the application starts 233 | */ 234 | { 235 | using namespace ImGui; 236 | 237 | Begin("Help"); 238 | { 239 | float childWidth = GetContentRegionAvailWidth(); 240 | 241 | Text("Keyboard controls"); 242 | BeginChild("Keyboard controls", ImVec2(childWidth, 180.0f), true); 243 | { 244 | std::string info = 245 | "ESC = quit\n" 246 | "UP ARROW = accelerate\n" 247 | "DOWN ARROW = brake\n" 248 | "LEFT ARROW = steering wheel left\n" 249 | "RIGHT ARROW = steering wheel right\n" 250 | "C = cycle cameras\n" 251 | "R = reset vehicle\n" 252 | "END = toggle engine mode\n" 253 | "W = free camera forwards\n" 254 | "A = free camera left\n" 255 | "S = free camera backwards\n" 256 | "D = free camera right\n" 257 | "SPACE = free camera up\n" 258 | "SHIFT = free camera down\n" 259 | "0 = toggle wireframe\n"; 260 | 261 | Text("%s", info.c_str()); 262 | EndChild(); 263 | } 264 | 265 | Text("Application controls"); 266 | BeginChild("Application controls", ImVec2(childWidth, 270.0f), true); 267 | { 268 | TextWrapped("Windows can be shown/hidden by double clicking their title bars"); 269 | ImGui::Spacing(); 270 | TextWrapped("All windows apart from the control panel can be moved with click + drag."); 271 | ImGui::Spacing(); 272 | TextWrapped("All windows apart from control panel can be resized by dragging the bottom right corner."); 273 | ImGui::Spacing(); 274 | TextWrapped("Left click anywhere outside the menu windows to give mouse focus to the simulation cameras and hide the cursor."); 275 | ImGui::Spacing(); 276 | TextWrapped("Right click anywhere to show the cursor and end camera mouse focus."); 277 | ImGui::Spacing(); 278 | TextWrapped("The scroll wheel changes the field of view on the free camera and driver camera, if they have mouse focus"); 279 | ImGui::Spacing(); 280 | TextWrapped("The middle mouse button is used to reset the zoom on the free camera and driver camera."); 281 | 282 | EndChild(); 283 | } 284 | } 285 | End(); 286 | } 287 | 288 | void UILayer::carDebugInfo() const 289 | /* Called by UILayer::render 290 | * Available when the DebugCarModel's is rendered 291 | */ 292 | { 293 | ImGui::Begin("Debug info"); 294 | { 295 | debug_physicsTelemetry(); 296 | debug_allWheelTelemetry(); 297 | } 298 | ImGui::End(); 299 | } 300 | 301 | void UILayer::debug_physicsTelemetry() const 302 | /* Called by UILayer::carDebugInfo 303 | * Displays a child region within the debug window, that shows the core physical state of the Car 304 | */ 305 | { 306 | using namespace ImGui; 307 | 308 | Framework::Physics::State carState = mDataSource.getState(); 309 | glm::dvec3 temp; 310 | 311 | Text("Physics state"); 312 | BeginChild("State", ImVec2(0.0f, 250.0f), true); 313 | { 314 | temp = carState.getPosition_world(); 315 | Text("Position\nx: %.3f y: %.3f z: %.3f", temp.x, temp.y, temp.z); Separator(); 316 | 317 | temp = carState.getVelocity_world(); 318 | Text("Linear Velocity\nx: %.3f y: %.3f z: %.3f", temp.x, temp.y, temp.z); Separator(); 319 | 320 | float speed = glm::length(carState.getVelocity_world()); 321 | Text("Speed\nm/h: %.3f k/h: %.3f m/s: %.3f", speed * 2.23694, speed * 3.6, speed); Separator(); 322 | 323 | temp = mDataSource.getAcceleration_world(); 324 | Text("Linear Acceleration\nx: %.3f y: %.3f z: %.3f", temp.x, temp.y, temp.z); Separator(); 325 | 326 | temp = carState.getMomentum_world(); 327 | Text("Momentum\nx: %.3f y: %.3f z: %.3f", temp.x, temp.y, temp.z); Separator(); 328 | 329 | temp = carState.getAngularVelocity_world(); 330 | Text("Angular Velocity\nx: %.3f y: %.3f z: %.3f", temp.x, temp.y, temp.z); Separator(); 331 | 332 | temp = carState.getAngularMomentum_world(); 333 | Text("Angular Momentum\nx: %.3f y: %.3f z: %.3f", temp.x, temp.y, temp.z); 334 | } 335 | EndChild(); 336 | } 337 | 338 | void UILayer::debug_wheelTelemetry(unsigned char axlePos, unsigned char side, ImVec4 colour) const 339 | /* Called by UILayer::debug_allWheelTelemetry 340 | * Adds ImGui::Text to a window detailing some properties of one Wheel 341 | */ 342 | { 343 | Internal::WheelInterface* temp = &mDataSource.getWheelSystem().getWheelInterface(Internal::WheelSystem::AxlePos(axlePos), Internal::WheelSystem::Side(side)); 344 | 345 | ImGui::PushStyleColor(ImGuiCol_Text, colour); 346 | glm::dvec3 wheelPos_car = temp->getWheel().getPosition_car(); 347 | ImGui::Text("Position_car: (%.3f, %.3f, %.3f)", wheelPos_car.x, wheelPos_car.y, wheelPos_car.z); 348 | ImGui::Text("Long force: %.3f", temp->getWheel().getTyre().getTotalForce_wheel().y); 349 | ImGui::Text("Lateral force: %.3f", temp->getWheel().getTyre().getTotalForce_wheel().x); 350 | ImGui::Text("Load: %.3f", temp->getLoad()); 351 | ImGui::Text("Longitudinal slip: %.3f", temp->getWheel().getTyre().getSlip().getLongitudinal()); 352 | ImGui::Text("Lateral slip angle: %.3f", temp->getWheel().getTyre().getSlip().getAngle_degs()); 353 | ImGui::PopStyleColor(); 354 | } 355 | 356 | void UILayer::debug_allWheelTelemetry() const 357 | /* Called by UILayer::carDebugInfo 358 | * Combines the text provided by 4 calls to debug_wheelTelemetry, into a 2 x 2 grid and displays this in a child region 359 | */ 360 | { 361 | using namespace Internal; 362 | using namespace ImGui; 363 | 364 | Text("Wheel info"); 365 | BeginChild("Wheel info", ImVec2(0.0f, 265.0f), true); 366 | { 367 | Columns(2); 368 | 369 | Text("Front left"); 370 | debug_wheelTelemetry(WheelSystem::AxlePos::FRONT, WheelSystem::Side::LEFT, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); 371 | 372 | NextColumn(); 373 | Text("Front right"); 374 | Separator(); 375 | debug_wheelTelemetry(WheelSystem::AxlePos::FRONT, WheelSystem::Side::RIGHT, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); 376 | 377 | Separator(); 378 | 379 | NextColumn(); 380 | Text("Rear left"); 381 | debug_wheelTelemetry(WheelSystem::AxlePos::REAR, WheelSystem::Side::LEFT, ImVec4(0.0f, 1.0f, 1.0f, 1.0f)); 382 | 383 | NextColumn(); 384 | Text("Rear right"); 385 | Separator(); 386 | debug_wheelTelemetry(WheelSystem::AxlePos::REAR, WheelSystem::Side::RIGHT, ImVec4(1.0f, 0.0f, 1.0f, 1.0f)); 387 | } 388 | EndChild(); 389 | } 390 | 391 | void UILayer::tyreParameters() const 392 | /* Called by UILayer::render 393 | * Enables individual tyre parameter alteration 394 | */ 395 | { 396 | using namespace ImGui; 397 | using namespace Internal; 398 | 399 | Begin("Pacejka magic formula parameters"); 400 | 401 | if (Button("Drifting tyres")) 402 | PacejkaMagicFormula::setToDriftingTyreParams(); 403 | if (Button("Standard tyres")) 404 | PacejkaMagicFormula::setToRoadTyreParams(); 405 | 406 | //Longitudinal 407 | Text("Longitidinal parameters"); 408 | BeginChild("longitudinal params", ImVec2(0, 335.0f), true); 409 | { 410 | SliderFloat("b0 Shape factor", &PacejkaMagicFormula::b0, 1.4f, 1.8f); 411 | SliderFloat("b1 Load influence on longitudinal friction coefficient", &PacejkaMagicFormula::b1, -80.0f, 80.0f); 412 | SliderFloat("b2 Longitudinal friction coefficient", &PacejkaMagicFormula::b2, 900.0f, 1700.0f); 413 | SliderFloat("b3 Curvature factor of stiffness/load", &PacejkaMagicFormula::b3, -20.0f, 20.0f); 414 | SliderFloat("b4 Change of stiffness with slip", &PacejkaMagicFormula::b4, 100.0f, 500.0f); 415 | SliderFloat("b5 Change of progressivity of stiffness/load", &PacejkaMagicFormula::b5, -1.0f, 1.0f); 416 | SliderFloat("b6 Curvature change with load^2", &PacejkaMagicFormula::b6, -0.1f, 0.1f); 417 | SliderFloat("b7 Curvature change with load", &PacejkaMagicFormula::b7, -1.0f, 1.0f); 418 | SliderFloat("b8 Curvature factor", &PacejkaMagicFormula::b8, -20.0f, 1.0f); 419 | SliderFloat("b9 Load influence on horizontal shift", &PacejkaMagicFormula::b9, -1.0f, 1.0f); 420 | SliderFloat("b10 Horizontal shift", &PacejkaMagicFormula::b10, -5.0f, 5.0f); 421 | SliderFloat("b11 Vertical shift", &PacejkaMagicFormula::b11, -100.0f, 100.0f); 422 | SliderFloat("b12 Vertical shift at load = 0", &PacejkaMagicFormula::b12, -10.0f, 10.0f); 423 | SliderFloat("b13 Curvature shift", &PacejkaMagicFormula::b13, -1.0f, 1.0f); 424 | } 425 | EndChild(); 426 | 427 | //Lateral 428 | Text("Lateral parameters"); 429 | BeginChild("lateral params", ImVec2(0, 425.0f), true); 430 | { 431 | SliderFloat("a0 Shape factor", &PacejkaMagicFormula::a0, 1.2f, 1.8f); 432 | SliderFloat("a1 Load influence on lateral friction coefficient", &PacejkaMagicFormula::a1, -80.0f, 80.0f); 433 | SliderFloat("a2 Lateral friction coefficient", &PacejkaMagicFormula::a2, 900.0f, 1700.0f); 434 | SliderFloat("a3 Change of stiffness with slip", &PacejkaMagicFormula::a3, 500.0f, 2000.0f); 435 | SliderFloat("a4 Change of progressivity of stiffness / load", &PacejkaMagicFormula::a4, 0.0f, 50.0f); 436 | SliderFloat("a5 Camber influence on stiffness", &PacejkaMagicFormula::a5, -0.1f, 1.0f); 437 | SliderFloat("a6 Curvature change with load", &PacejkaMagicFormula::a6, -2.0f, 2.0f); 438 | SliderFloat("a7 Curvature factor", &PacejkaMagicFormula::a7, -20.0f, 1.0f); 439 | SliderFloat("a8 Load influence on horizontal shift", &PacejkaMagicFormula::a8, -1.0f, 1.0f); 440 | SliderFloat("a9 Horizontal shift at load = 0 and camber = 0", &PacejkaMagicFormula::a9, -1.0f, 1.0f); 441 | SliderFloat("a10 Camber influence on horizontal shift", &PacejkaMagicFormula::a10, -0.1f, 0.1f); 442 | SliderFloat("a11 Vertical shift", &PacejkaMagicFormula::a11, -200.0f, 200.0f); 443 | SliderFloat("a12 Vertical shift at load = 0", &PacejkaMagicFormula::a12, -10.0f, 10.0f); 444 | SliderFloat("a13 Camber influence on vertical shift, load dependent ", &PacejkaMagicFormula::a13, -10.0f, 10.0f); 445 | SliderFloat("a14 Camber influence on vertical shift", &PacejkaMagicFormula::a14, -15.0f, 15.0f); 446 | SliderFloat("a15 Camber influence on lateral friction coefficient", &PacejkaMagicFormula::a15, -0.01f, 0.01f); 447 | SliderFloat("a16 Curvature change with camber", &PacejkaMagicFormula::a16, -0.1f, 0.1f); 448 | SliderFloat("a17 Curvature shift", &PacejkaMagicFormula::a17, -1.0f, 1.0f); 449 | } 450 | EndChild(); 451 | End(); 452 | } 453 | 454 | void UILayer::upsideDownWarning() const 455 | /* Called by UILayer::render 456 | * If the user flips the Car over, this warning prompts them to reset it 457 | */ 458 | { 459 | glm::dvec3 carPosition_world = mDataSource.getState().getPosition_world(); 460 | 461 | double terrainHeight = External::Environment::mTerrain.getHeight(glm::dvec2(carPosition_world.x, carPosition_world.y)); 462 | 463 | bool displayWarning = 464 | glm::dvec3(mDataSource.getState().getLocalToWorld_direction() * glm::dvec4(0.0, 1.0, 0.0, 1.0)).y < 0.0 465 | && (abs(carPosition_world.y - terrainHeight)) < 2.0; 466 | 467 | if (displayWarning) 468 | ImGui::OpenPopup("Reset vehicle"); 469 | 470 | if (ImGui::BeginPopupModal("Reset vehicle", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) 471 | { 472 | ImGui::Text("Your vehicle is upside down.\nPress R to reset.\n"); 473 | if (!displayWarning) 474 | ImGui::CloseCurrentPopup(); 475 | ImGui::EndPopup(); 476 | } 477 | } 478 | 479 | } -------------------------------------------------------------------------------- /src/VehicleSimulation.cpp: -------------------------------------------------------------------------------- 1 | #include "VehicleSimulation.h" 2 | 3 | VehicleSimulation::VehicleSimulation() : 4 | /* Called by main 5 | */ 6 | Application("NEA - Vehicle Simulation", "res/images/windowIcon.png", false) 7 | { 8 | onLoad(); 9 | } 10 | 11 | void VehicleSimulation::onLoad() 12 | /* Called by VehicleSimulation::VehicleSimulation 13 | * Called once 14 | */ 15 | { 16 | mVisuals = std::make_unique(mCar, mWindow, mSimulationSpeed); 17 | } 18 | 19 | void VehicleSimulation::onInputCheck() 20 | /* Called by VehicleSimulation::run 21 | * Called once per frame 22 | */ 23 | { 24 | using namespace Framework; 25 | 26 | if (Input::isKeyReleased(GLFW_KEY_ESCAPE)) mRunning = false; 27 | 28 | mCar.checkInput(mFrameTime * mSimulationSpeed); 29 | mVisuals->checkInput(mFrameTime); 30 | } 31 | 32 | void VehicleSimulation::onUpdate() 33 | /* Called by VehicleSimulation::run 34 | * Called multiple times per frame 35 | */ 36 | { 37 | mCar.update(mCurrentTime, mUpdateDelta * mSimulationSpeed); 38 | } 39 | 40 | void VehicleSimulation::onRender() 41 | /* Called by Application::render 42 | * Called once per frame 43 | */ 44 | { 45 | mVisuals->update(mFrameTime); 46 | mVisuals->renderAll(); 47 | } -------------------------------------------------------------------------------- /src/VisualShell.cpp: -------------------------------------------------------------------------------- 1 | #include "VisualShell.h" 2 | #include "Car.h" 3 | 4 | namespace Visual { 5 | 6 | VisualShell::VisualShell(Internal::Car& dataSource, Framework::Window& window, float& simSpeedHandle) : 7 | /* Called by VehicleSimulation::onLoad 8 | */ 9 | mWindow(window), 10 | mDataSource(dataSource), 11 | mCameraSystem(window.getAspect()), 12 | mSimulationSpeedHandle(simSpeedHandle) 13 | { 14 | load(); 15 | } 16 | 17 | void VisualShell::update(float dt) 18 | /* Called by VehicleSimulation::onRender 19 | */ 20 | { 21 | mCameraSystem.update(mWindow.getAspect(), dt, mDataSource); 22 | } 23 | 24 | void VisualShell::renderAll() 25 | /* Called by VehicleSimulation::onRender 26 | */ 27 | { 28 | //temp 29 | //glm::dvec3 carPos = mDataSource.getWheelSystem().getWheelInterface(Internal::WheelSystem::AxlePos::REAR, Internal::WheelSystem::Side::LEFT).getPosition_world(); 30 | //glm::dvec3 31 | // wheelPos_world = mDataSource.getWheelSystem().getWheelInterface(Internal::WheelSystem::AxlePos::REAR, Internal::WheelSystem::Side::LEFT).getPosition_world(), 32 | // targetPos_world = glm::dvec3(0.0, External::Environment::mTerrain.getHeight({ wheelPos_world.x, wheelPos_world.z }), wheelPos_world.z); 33 | //glm::dvec3 carPos = mDataSource.getState().getPosition_world(); 34 | 35 | //orthoCam->setPosition({0.0, carPos.y, carPos.z}); 36 | //mBaseRenderer.setCamera(*orthoCam); 37 | //mDebugLayerRenderer.setCamera(*orthoCam); 38 | // 39 | 40 | //Always rendered 41 | mUILayer->render(); 42 | mGameCarModel->render(mBaseRenderer); 43 | mEnvironmentModel->render(mBaseRenderer); 44 | mBaseRenderer.flush(); 45 | 46 | //Only rendered in debug mode 47 | if (mDebugMode) { 48 | mDebugCarModel->render(mDebugLayerRenderer); 49 | glLineWidth(3.0f); 50 | glClear(GL_DEPTH_BUFFER_BIT); 51 | mDebugLayerRenderer.flush(); 52 | glLineWidth(1.0f); 53 | } 54 | } 55 | 56 | void VisualShell::checkInput(float dt) 57 | /* Called by VehicleSimulation::onInputCheck 58 | * Any user input that the visual side of the application requires is processed here 59 | */ 60 | { 61 | using namespace Framework; 62 | 63 | //Toggle wireframe mode 64 | static int mode = GL_FILL; 65 | if (Input::isKeyReleased(GLFW_KEY_0)) { 66 | mode = mode == GL_LINE ? GL_FILL : GL_LINE; 67 | glPolygonMode(GL_FRONT_AND_BACK, mode); 68 | } 69 | 70 | //Hide/show cursor for menu interaction/camera focus respectively 71 | if (Input::isMouseButtonReleased(GLFW_MOUSE_BUTTON_RIGHT) && mCameraSystem.hasFocus()) { 72 | Input::showCursor(); 73 | Input::setMousePosition(glm::vec2(mWindow.getWidth() / 2.0f, mWindow.getHeight() / 2.0f)); 74 | mCameraSystem.shouldHaveFocus(false); 75 | } 76 | if (Input::isMouseButtonReleased(GLFW_MOUSE_BUTTON_LEFT) && !ImGui::GetIO().WantCaptureMouse) { 77 | Input::hideCursor(); 78 | mCameraSystem.shouldHaveFocus(true); 79 | } 80 | 81 | SimulationCamera& currentCamera = mCameraSystem.getCurrentSimCamera(); 82 | mBaseRenderer.setCamera(currentCamera.getInternalCamera()); 83 | mDebugLayerRenderer.setCamera(currentCamera.getInternalCamera()); 84 | 85 | mCameraSystem.checkInput(dt); 86 | } 87 | 88 | void VisualShell::load() 89 | /* Called by VisualShell::VisualShell 90 | * Called once at load time 91 | */ 92 | { 93 | using namespace glm; 94 | 95 | //temp 96 | //For viewing car 97 | orthoCam = std::make_unique(vec3(0.0f, 0.0f, 0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f), 0.1f, 100.0f, mWindow.getAspect(), 1.5f/*2.5f*/); 98 | //For viewing terrain from above 99 | //orthoCam = std::make_unique(vec3(0.0f, 200.0f, 0.0f), vec3(0.0, -1.0f, 0.0f), vec3(1.0f, 0.0f, 0.0f), 100.0f, 300.0f, mWindow.getAspect(), 150.0f); 100 | // 101 | 102 | mWindow.setClearColour(vec4(0.5, 0.5, 0.5, 1.0)); 103 | 104 | glEnable(GL_BLEND); 105 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 106 | 107 | mEnvironmentModel = std::make_unique(mResourceHolder); 108 | mGameCarModel = std::make_unique(mDataSource, mResourceHolder); 109 | mGameCarModel->setShaderUniforms( 110 | mEnvironmentModel->getFogDensity(), 111 | mEnvironmentModel->getFogGradient(), 112 | mEnvironmentModel->getSkyColour(), 113 | mEnvironmentModel->getSunDirection() 114 | ); 115 | 116 | mDebugCarModel = std::make_unique(mDataSource, mResourceHolder); 117 | 118 | mUILayer = std::make_unique(mDataSource, *mResourceHolder.getResource("bodyShader"), mCameraSystem, mSimulationSpeedHandle, mDebugMode); 119 | 120 | Framework::Camera& currentCamera = mCameraSystem.getCurrentSimCamera().getInternalCamera(); 121 | mBaseRenderer.setCamera(currentCamera); 122 | mDebugLayerRenderer.setCamera(currentCamera); 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /src/Wheel.cpp: -------------------------------------------------------------------------------- 1 | #include "Wheel.h" 2 | 3 | namespace Internal { 4 | 5 | Wheel::Wheel() : 6 | /* Called during WheelInterface::WheelInterface 7 | */ 8 | mTyre(mRimRadius) 9 | { } 10 | 11 | void Wheel::update(glm::dmat4 carToWorldRotation_car, glm::dvec3 terrainNormalUnderWheel, glm::dvec2 wheelVel_car, double load, double totalInputTorque, double dt) 12 | /* Called by WheelInterface::updateWheel 13 | */ 14 | { 15 | mInertiaAboutAxle = mTyre.getAxialInertia(); 16 | 17 | updateAngularMotion(totalInputTorque, dt); 18 | 19 | mRotationDirection = mAngularVelocity < 0.0 ? -1 : mAngularVelocity > 0.0 ? 1 : 0; 20 | 21 | glm::dvec2 wheelVel_wheel = glm::rotate(wheelVel_car, glm::radians(mSteeringAngle)); 22 | mTyre.update(wheelVel_wheel, load, 0.0, mRimRadius, mAngularVelocity); 23 | 24 | updateTyreForce_world(carToWorldRotation_car, terrainNormalUnderWheel); 25 | } 26 | 27 | void Wheel::reset() 28 | /* Called by WheelInterface::reset 29 | */ 30 | { 31 | mAngularAcceleration = 0.0; 32 | mAngularVelocity = 0.0; 33 | mAngularPosition = 0.0; 34 | mRotationDirection = 0; 35 | mSteeringAngle = 0.0; 36 | 37 | resetToBasePosition(); 38 | } 39 | 40 | void Wheel::updateAngularMotion(double totalTorque, double dt) 41 | /* Called by Wheel::update 42 | * Handles the updating of state that is only linked to angular motion 43 | */ 44 | { 45 | if (mInertiaAboutAxle) 46 | mAngularAcceleration = totalTorque / mInertiaAboutAxle; 47 | 48 | mAngularVelocity += mAngularAcceleration * dt; 49 | mAngularPosition += mAngularVelocity * dt; 50 | 51 | if (mAngularPosition >= glm::two_pi()) 52 | mAngularPosition -= glm::two_pi(); 53 | 54 | if (mAngularPosition <= -glm::two_pi()) 55 | mAngularPosition += glm::two_pi(); 56 | } 57 | 58 | void Wheel::updateTyreForce_world(glm::dmat4 carToWorldRotation_car, glm::dvec3 terrainNormalUnderWheel) 59 | /* Called by Wheel::update 60 | * Transforms individual tyre force components, into a 3D force vector in world space that lies tangent to the terrain surface 61 | */ 62 | { 63 | /* 1. Create normalised vector pointing along forward axis in wheel space (0, 0, -1). 64 | * 2. Rotate this vector about Y axis, by the steering angle, taking it into car space. 65 | * 3. Then rotate the result into world space using carToWorldRotation_car. 66 | * 4. Flatten this vector down into the XZ plane and re-normalize it, to remove the Y component. 67 | * 5. Rotate it 90 degrees around the Y axis (0, 1, 0) 68 | * 6. Take the cross product of the result and the terrain normal to get a vector at a tangent to the terrain's surface. 69 | * 70 | * This resulting normalised vector will be the direction in which the longitudinal tyre force will act, 71 | * in world space. It must then be scaled according to the value produced by the force generator in the Tyre. 72 | */ 73 | 74 | using namespace glm; 75 | 76 | //This is a check performed to make sure that, if the car is upside down, the tyres do not generate any force 77 | if (dvec3(carToWorldRotation_car * dvec4(0.0, 1.0, 0.0, 1.0)).y < 0.0) { 78 | mTyreForce_world = dvec3(); 79 | return; 80 | } 81 | 82 | dvec3 83 | normalisedLong_world = normalize(dvec3(carToWorldRotation_car * dvec4(rotate(dvec3(0.0, 0.0, -1.0), radians(mSteeringAngle), dvec3(0.0, 1.0, 0.0)), 1.0))), 84 | flattenedNormLong_world = normalize(dvec3(normalisedLong_world.x, 0.0, normalisedLong_world.z)), 85 | rotatedFlatNormLong_world = rotate(flattenedNormLong_world, radians(90.0), dvec3(0.0, 1.0, 0.0)), 86 | normTangentResult_world = normalize(cross(rotatedFlatNormLong_world, terrainNormalUnderWheel)), 87 | normalisedLat_world = normalize(cross(terrainNormalUnderWheel, normTangentResult_world)); 88 | 89 | mTyreForce_world = normTangentResult_world * mTyre.getTotalForce_wheel().y + normalisedLat_world * mTyre.getTotalForce_wheel().x; 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/WheelInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "WheelInterface.h" 2 | #include "Environment.h" 3 | 4 | namespace Internal { 5 | 6 | WheelInterface::WheelInterface(Axle& connectedAxle) : 7 | /* Called by WheelSystem::WheelSystem 8 | */ 9 | mConnectedAxle(connectedAxle) 10 | { } 11 | 12 | void WheelInterface::update(Framework::Physics::State& carState, double load, double dt) 13 | /* Called by WheelSystem::updateAllWheelInterfaces 14 | * Updates all per-wheel components 15 | * Transforms car physical-state data before passing it to components 16 | */ 17 | { 18 | using namespace glm; 19 | using namespace External; 20 | 21 | mPosition_world = dvec3(carState.getLocalToWorld_position() * dvec4(mPosition_car, 1.0)); 22 | mVelocity_world = carState.getVelocity_world() + cross(carState.getAngularVelocity_world(), mPosition_world - dvec3(carState.getLocalToWorld_position() * dvec4(carState.getMass().getCentre(), 1.0))); 23 | 24 | dvec3 terrainNormal = Environment::mTerrain.getNormal_world(dvec2(mPosition_world.x, mPosition_world.z)); 25 | 26 | double 27 | terrainHeight = Environment::mTerrain.getHeight(dvec2(mPosition_world.x, mPosition_world.z)), 28 | terrainOverlap = std::max(0.0, mWheel.getTotalRadius() - (mPosition_world.y - terrainHeight)); 29 | 30 | mCollisionRegistered = terrainOverlap ? true : false; 31 | mLoad = mCollisionRegistered ? load : 0.0; 32 | 33 | //Member object updates 34 | mBrake.update(); 35 | mSuspension.update(mVelocity_world.y, terrainOverlap, terrainNormal); 36 | updateWheel(carState.getLocalToWorld_direction(), terrainNormal, terrainOverlap, dt); 37 | } 38 | 39 | void WheelInterface::setPosition_car(glm::dvec3 newPosition_car) 40 | /* Called by WheelSystem::positionWheelInterfaces 41 | * Changing Wheel interface position, must cause the Wheel to change position 42 | */ 43 | { 44 | //Reposition the wheels 45 | mPosition_car = newPosition_car; 46 | mWheel.setBasePosition_car(newPosition_car); 47 | mWheel.resetToBasePosition(); 48 | 49 | //Neutralise the suspension 50 | mSuspension.neutralise(); 51 | } 52 | 53 | void WheelInterface::reset() 54 | /* Called by WheelSystem::reset 55 | */ 56 | { 57 | mWheel.reset(); 58 | mSuspension.neutralise(); 59 | mLoad = 0.0; 60 | } 61 | 62 | void WheelInterface::updateWheel(glm::dmat4 carToWorldRotation_car, glm::dvec3 terrainNormalUnderWheel, double terrainOverlap, double dt) 63 | /* Called by WheelInterface::update 64 | */ 65 | { 66 | using namespace glm; 67 | 68 | //Use carToWorldRotation_car to calculate the velocity of the Wheel, relative to the Car 69 | dvec3 wheelVelocity_car = dvec3(inverse(carToWorldRotation_car) * dvec4(mVelocity_world, 1.0)); 70 | mWheel.update(carToWorldRotation_car, terrainNormalUnderWheel, dvec2(wheelVelocity_car.x, wheelVelocity_car.z), mLoad, mConnectedAxle.getTransferredTorque(), dt); 71 | 72 | mWheel.resetToBasePosition(); 73 | mWheel.setPosition_car(mWheel.getPosition_car() + dvec3(0.0, terrainOverlap, 0.0)); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/WheelSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "WheelSystem.h" 2 | #include "Environment.h" 3 | #include "ControlSystem.h" 4 | 5 | namespace Internal { 6 | 7 | WheelSystem::WheelSystem() 8 | /* Called during Car::Car 9 | */ 10 | { 11 | for (unsigned char i = 0; i < 4; i++) 12 | mWheelInterfaces.push_back(WheelInterface(getAxle(calcAxleFromIndex(i)))); 13 | 14 | mFrontAxle.setLongDisplacement_car(-mWheelBase * 0.5); 15 | mFrontAxle.setLength(mTrack_front); 16 | mRearAxle.setLongDisplacement_car(mWheelBase * 0.5); 17 | mRearAxle.setLength(mTrack_rear); 18 | 19 | positionWheelInterfaces(); 20 | } 21 | 22 | void WheelSystem::update(Framework::Physics::State& carState, glm::dvec3 carAcceleration_world, double dt) 23 | /* Called by Car::update 24 | * Passes Car physical-state information down to the WheelInterfaces and updates them 25 | * Calculates the final force and torque vectors 26 | */ 27 | { 28 | glm::dvec3 carAcceleration_car = glm::dvec3(carState.getWorldToLocal_direction() * glm::dvec4(carAcceleration_world, 1.0)); 29 | 30 | updateAxles(); 31 | updateAllWheelInterfaces(carState, carAcceleration_car, dt); 32 | updateTotalForce_world(); 33 | updateTotalTorque_world(carState.getPosition_world(), carState.getLocalToWorld_position()); 34 | } 35 | 36 | void WheelSystem::bindControlSystem(ControlSystem& controlSystem) 37 | /* Called by Car::assemble 38 | */ 39 | { 40 | //Gives the control system access to the front wheels so they can be steered with this class 41 | controlSystem.attachWheels(&getWheelInterface(FRONT, LEFT).getWheel(), &getWheelInterface(FRONT, RIGHT).getWheel()); 42 | 43 | //Gives the control system access to all the brakes 44 | std::vector brakes = { 45 | &mWheelInterfaces[0].getBrake(), 46 | &mWheelInterfaces[1].getBrake(), 47 | &mWheelInterfaces[2].getBrake(), 48 | &mWheelInterfaces[3].getBrake(), 49 | }; 50 | controlSystem.attachBrakes(brakes); 51 | } 52 | 53 | void WheelSystem::reset() 54 | /* Called by Car::checkInput 55 | */ 56 | { 57 | for (WheelInterface& w : mWheelInterfaces) 58 | w.reset(); 59 | } 60 | 61 | void WheelSystem::positionWheelInterfaces() 62 | /* Called by 63 | * Responsible for calculating the position of the wheel interfaces based on parameters 64 | * Must be called whenever these parameters change (or are set for the first time) 65 | */ 66 | { 67 | glm::dvec3 newPosition; 68 | 69 | Axle* currentAxle = nullptr; 70 | 71 | for (unsigned char i = 0; i < 4; i++) { 72 | currentAxle = &getAxle(calcAxleFromIndex(i)); 73 | 74 | newPosition.x = (calcSideFromIndex(i) == LEFT ? -1.0 : 1.0) * currentAxle->getLength() * 0.5; 75 | newPosition.y = mWheelInterfaces[i].getPosition_car().y + mWheelHeight; 76 | newPosition.z = currentAxle->getLongDisplacement_car(); 77 | 78 | mWheelInterfaces[i].setPosition_car(newPosition); 79 | } 80 | } 81 | 82 | void WheelSystem::updateAxles() 83 | /* Called by WheelSystem::update 84 | * Summates counter torques for both the front and rear axles, and updates them 85 | */ 86 | { 87 | WheelInterface 88 | *frontLeft = &getWheelInterface(FRONT, LEFT), 89 | *frontRight = &getWheelInterface(FRONT, RIGHT), 90 | *rearLeft = &getWheelInterface(REAR, LEFT), 91 | *rearRight = &getWheelInterface(REAR, RIGHT); 92 | 93 | double 94 | frontAxleCounterTorque = 0.0, 95 | rearAxleCounterTorque = 0.0; 96 | 97 | //Front axle 98 | { 99 | //Brake torque 100 | frontAxleCounterTorque += frontLeft->getBrake().getTorqueMagnitude() * -frontLeft->getWheel().getRotationDirection(); //Left wheel 101 | frontAxleCounterTorque += frontRight->getBrake().getTorqueMagnitude() * -frontRight->getWheel().getRotationDirection(); //Right 102 | 103 | //Traction torque 104 | frontAxleCounterTorque += frontRight->getWheel().getTyre().getTotalForce_wheel().y * frontRight->getWheel().getTotalRadius(); 105 | frontAxleCounterTorque += frontLeft->getWheel().getTyre().getTotalForce_wheel().y * frontRight->getWheel().getTotalRadius(); 106 | 107 | //Send this total resistive torque to the front axle itself 108 | mFrontAxle.update(std::max(frontLeft->getWheel().getRPM(), frontRight->getWheel().getRPM()) * 60.0, frontAxleCounterTorque); 109 | } 110 | 111 | //Rear axle 112 | { 113 | //Add the brake torques from both wheels on the rear axle 114 | rearAxleCounterTorque += rearLeft->getBrake().getTorqueMagnitude() * -rearLeft->getWheel().getRotationDirection(); //Left wheel 115 | rearAxleCounterTorque += rearRight->getBrake().getTorqueMagnitude() * -rearRight->getWheel().getRotationDirection(); //Right 116 | 117 | //Add the traction torques from both wheels on the rear axle 118 | rearAxleCounterTorque += rearRight->getWheel().getTyre().getTotalForce_wheel().y * rearRight->getWheel().getTotalRadius(); 119 | rearAxleCounterTorque += rearLeft->getWheel().getTyre().getTotalForce_wheel().y * rearLeft->getWheel().getTotalRadius(); 120 | 121 | //Send this total resistive torque to the front axle itself 122 | mRearAxle.update(std::max(rearLeft->getWheel().getRPM(), rearRight->getWheel().getRPM()) * 60.0, rearAxleCounterTorque); 123 | } 124 | } 125 | 126 | void WheelSystem::updateAllWheelInterfaces(Framework::Physics::State& carState, glm::dvec3 carAcceleration_car, double dt) 127 | /* Called by WheelSystem::update 128 | */ 129 | { 130 | double carCMHeightAboveGround = recalcCarCmHeightAboveGround(carState); 131 | 132 | for (unsigned char i = 0; i < mWheelInterfaces.size(); i++) 133 | mWheelInterfaces[i].update(carState, recalcLoad(calcAxleFromIndex(i), calcSideFromIndex(i), carState.getMass(), carAcceleration_car, carCMHeightAboveGround), dt); 134 | } 135 | 136 | double WheelSystem::recalcLoad(AxlePos position, Side side, Framework::Physics::Mass& carMass_car, glm::dvec3 carAcceleration_car, double carCMHeightAboveGround) 137 | /* Called by WheelSystem::updateAllWheelInterfaces 138 | */ 139 | { 140 | Axle& currentAxle = position == FRONT ? mFrontAxle : mRearAxle; 141 | 142 | double 143 | carMassValue = carMass_car.getValue(), 144 | 145 | //The load on the axle involved, when the car is at rest. 146 | restAxleLoad = abs(currentAxle.getLongDisplacement_car() - carMass_car.getCentre().z) / mWheelBase * (carMassValue * External::Environment::mGravityAccel), 147 | 148 | //The current load on the axle involved, considering load transfer due to longitudinal acceleration. 149 | currentAxleLoad = restAxleLoad + (carCMHeightAboveGround / mWheelBase) * carMassValue * carAcceleration_car.z * (signbit(currentAxle.getLongDisplacement_car()) ? -1.0 : 1.0), 150 | 151 | //The load on the wheel involved, when the car is at rest. 152 | restIndividualWheelLoad = abs(getWheelInterface(position, side).getWheel().getPosition_car().x - carMass_car.getCentre().x) / currentAxle.getLength() * currentAxleLoad, 153 | 154 | //The current load on the wheel involved, considering load transfer due to lateral acceleration. 155 | currentWheelLoad = restIndividualWheelLoad + (carCMHeightAboveGround / currentAxle.getLength()) * carMassValue * carAcceleration_car.x * (signbit(getWheelInterface(position, side).getWheel().getPosition_car().x) ? 1.0 : -1.0); 156 | 157 | return currentWheelLoad; 158 | } 159 | 160 | double WheelSystem::recalcCarCmHeightAboveGround(Framework::Physics::State& carState) 161 | /* Called by WheelSystem::updateAllWheelInterfaces 162 | */ 163 | { 164 | /* This function needs to return a single value representing the height of the car's centre of mass above the ground. 165 | * The 'ground' in this case is not strictly speaking the actual height of the terrain below the car's centre of mass 166 | * (what might be presumed). Instead, the ground height is taken to be the average height of the contact patches of 167 | * the tyres. 168 | * 169 | * Using the terrain height sampling method, if the car drove *over* a huge dip in the terrain (not *into* the dip, as 170 | * the wheels would pass around it), then the centre of mass height above the ground would suddenly increase and this 171 | * would have a sudden effect on the load. But this is completely wrong, as the wheels have continued moving along the 172 | * flat terrain in front of them. There should not be any increase in load in this situation at all. 173 | * 174 | * It can thus be said that the height used must be connected to the height of the wheels, specifically, the average 175 | * height of the tyre contact patches. 176 | */ 177 | 178 | glm::dvec3 avgWheelOriginPos_world; 179 | 180 | double 181 | avgWheelRadius = 0.0, 182 | avgTyreContactPatchHeight_world = 0.0; 183 | 184 | Wheel* currentWheel = nullptr; 185 | 186 | for (WheelInterface& w : mWheelInterfaces) { 187 | currentWheel = &w.getWheel(); 188 | avgWheelOriginPos_world += glm::dvec3(carState.getLocalToWorld_position() * glm::dvec4(currentWheel->getPosition_car(), 1.0)); 189 | avgWheelRadius += currentWheel->getTotalRadius(); 190 | } 191 | 192 | avgWheelOriginPos_world /= mWheelInterfaces.size(); 193 | avgWheelRadius /= mWheelInterfaces.size(); 194 | 195 | avgTyreContactPatchHeight_world = avgWheelOriginPos_world.y - avgWheelRadius; 196 | 197 | return carState.getPosition_world().y - avgTyreContactPatchHeight_world; 198 | } 199 | 200 | void WheelSystem::updateTotalForce_world() 201 | /* Called by WheelSystem::update 202 | */ 203 | { 204 | mTotalForce_world = glm::dvec3(0.0); 205 | 206 | for (WheelInterface& w : mWheelInterfaces) { 207 | mTotalForce_world += w.getSuspension().getForce_world(); 208 | mTotalForce_world += w.getWheel().getTyreForce_world(); 209 | } 210 | } 211 | 212 | void WheelSystem::updateTotalTorque_world(glm::dvec3 carPosition_world, glm::dmat4 carToWorldTransform_car) 213 | /* Called by WheelSystem::update 214 | */ 215 | { 216 | mTotalTorque_world = glm::dvec3(0.0); 217 | 218 | for (WheelInterface& w : mWheelInterfaces) { 219 | mTotalTorque_world += glm::cross(w.getPosition_world() - carPosition_world, w.getSuspension().getForce_world()); 220 | mTotalTorque_world += glm::cross(glm::dvec3(carToWorldTransform_car * glm::dvec4(w.getWheel().getContactPatchPosition_car(), 1.0)) - carPosition_world, w.getWheel().getTyreForce_world()); 221 | } 222 | } 223 | 224 | } -------------------------------------------------------------------------------- /src/add_function.cpp: -------------------------------------------------------------------------------- 1 | int add(int a, int b) { 2 | return a + b; 3 | } -------------------------------------------------------------------------------- /src/dummy_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "add_function.h" 3 | 4 | 5 | int main() { 6 | // Example library usage 7 | const int x = add(4, 5); 8 | 9 | printf("Calling library function...\n"); 10 | printf("The result of adding 4 and 5 is %i\n", x); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "VehicleSimulation.h" 2 | 3 | int main() { 4 | VehicleSimulation sim; 5 | 6 | if (!glfwGetCurrentContext()) { 7 | printf("This application requires OpenGL 3.3 or higher.\n"); 8 | printf("OpenGL version supported by this platform (%s).\n", glGetString(GL_VERSION)); 9 | std::cin.get(); 10 | return 0; 11 | } 12 | else 13 | FreeConsole(); 14 | 15 | sim.run(); 16 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | # todo 3 | 4 | add_executable(my-tests 5 | src/test_dummy.cpp 6 | ) 7 | 8 | target_link_libraries(my-tests PRIVATE vehicle-dynamics-sim) -------------------------------------------------------------------------------- /tests/test_dummy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "add_function.h" 3 | 4 | 5 | int main() { 6 | if(add(1, 2) != 3){ 7 | printf("Failed"); 8 | } 9 | 10 | return 0; 11 | } --------------------------------------------------------------------------------