/*************************************************************************** car.cpp - A car, being a moving object ------------------- begin : Wed Dec 4 2002 copyright : (C) 2002 by CJP email : cornware-cjp@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include "pi.h" #define DBLPI (2.0*M_PI) #include "bound.h" #include "car.h" #include "carinput.h" #include "world.h" #include "physics.h" #include "datafile.h" #include "lconfig.h" CCar::CCar(CDataManager *manager) : CMovingObject(manager) { m_Ground.nor = CVector(0,0,0); m_SimState = eFlying; } CCar::~CCar() { } bool CCar::load(const CString &filename, const CParamList &list) { CMovingObject::load(filename, list); //initial state m_DesiredSteering = 0.0; //m_xAngle = 0.0; //m_zAngle = 0.0; //m_BodyHeight = 0.0; CDataFile dfile(getFilename()); CLConfig cfile(dfile.useExtern()); //TODO: make a way to find out if this file exists //and return false if it doesn't //Description m_CarName = cfile.getValue("description", "fullname"); //Mass + moment of inertia m_InvMass = 1.0 / cfile.getValue("body", "mass").toFloat(); m_BodySize = cfile.getValue("body", "size").toVector(); m_InvInertia.setElement(0, 0, 1.0 / (m_BodySize.y*m_BodySize.y + m_BodySize.z*m_BodySize.z)); m_InvInertia.setElement(1, 1, 1.0 / (m_BodySize.x*m_BodySize.x + m_BodySize.z*m_BodySize.z)); m_InvInertia.setElement(2, 2, 1.0 / (m_BodySize.x*m_BodySize.x + m_BodySize.y*m_BodySize.y)); m_InvInertia *= 12.0 * m_InvMass; m_CenterOfMass = cfile.getValue("body", "centerofmass").toVector(); //body CString bodygeomfile = cfile.getValue("body", "geometry"); m_CameraPos = cfile.getValue("body", "camerapos").toVector(); m_cwA = cfile.getValue("body", "cwa").toFloat(); m_RotationDamping = cfile.getValue("body", "rotationdamping").toFloat(); //wheels CString frontwheelgeomfile = cfile.getValue("frontwheels", "geometry"); CString rearwheelgeomfile = cfile.getValue("rearwheels", "geometry"); m_FrontSteerMax = cfile.getValue("frontwheels", "steermax").toFloat(); m_RearSteerMax = -cfile.getValue("rearwheels", "steermax").toFloat(); m_Engine.m_FrontTraction = cfile.getValue("frontwheels", "traction").toFloat(); m_Engine.m_RearTraction = cfile.getValue("rearwheels", "traction").toFloat(); m_FrontDownforce = cfile.getValue("frontwheels", "downforce").toFloat(); m_RearDownforce = cfile.getValue("rearwheels", "downforce").toFloat(); //Engine torque curve: m_Engine.m_M0 = cfile.getValue("engine", "zerotorque").toFloat(); m_Engine.m_Mmax = cfile.getValue("engine", "maxtorque").toFloat(); m_Engine.m_w_Mmax = cfile.getValue("engine", "w_maxtorque").toFloat(); m_Engine.m_Pmax = cfile.getValue("engine", "maxpower").toFloat(); m_Engine.m_w_Pmax = cfile.getValue("engine", "w_maxpower").toFloat(); m_Engine.m_w_Zero = cfile.getValue("engine", "w_zero").toFloat(); //driving pipeline float gear0 = cfile.getValue("engine", "gear0").toFloat(); float gear1 = cfile.getValue("engine", "gear1").toFloat(); float gear2 = cfile.getValue("engine", "gear2").toFloat(); float gear3 = cfile.getValue("engine", "gear3").toFloat(); float gear4 = cfile.getValue("engine", "gear4").toFloat(); float gear5 = cfile.getValue("engine", "gear5").toFloat(); float gear6 = cfile.getValue("engine", "gear6").toFloat(); m_Engine.m_GearRatios.push_back(gear0); m_Engine.m_GearRatios.push_back(gear1); if(gear2 > 0.00001) m_Engine.m_GearRatios.push_back(gear2); if(gear3 > 0.00001) m_Engine.m_GearRatios.push_back(gear3); if(gear4 > 0.00001) m_Engine.m_GearRatios.push_back(gear4); if(gear5 > 0.00001) m_Engine.m_GearRatios.push_back(gear5); if(gear6 > 0.00001) m_Engine.m_GearRatios.push_back(gear6); m_Engine.m_DifferentialRatio = cfile.getValue("engine", "differentialratio").toFloat(); //The sounds: m_Sounds.push_back(theWorld->loadObject(cfile.getValue("sound", "engine"), CParamList(), CDataObject::eSample)); { CString baseRPS = cfile.getValue("sound", "enginerps"); m_EngineSoundBaseRPS = 388.0; if(baseRPS != "") m_EngineSoundBaseRPS = baseRPS.toFloat(); } //One texture: m_Textures.push_back(theWorld->loadObject(cfile.getValue("skin", "texture"), CParamList(), CDataObject::eMaterial)); //Dashboard info: m_Dashboard.background_tex = cfile.getValue("dashboard", "background_tex"); m_Dashboard.background_hth = cfile.getValue("dashboard", "background_hth").toFloat(); m_Dashboard.crash_background_tex = cfile.getValue("dashboard", "crash_background_tex"); m_Dashboard.crash_tex = cfile.getValue("dashboard", "crash_tex"); m_Dashboard.steer_tex = cfile.getValue("dashboard", "steer_tex"); m_Dashboard.steer_pos = cfile.getValue("dashboard", "steer_pos").toVector(); m_Dashboard.steer_rad = cfile.getValue("dashboard", "steer_rad").toFloat(); m_Dashboard.steer_ang = cfile.getValue("dashboard", "steer_ang").toFloat(); m_Dashboard.analog_vel_tex = cfile.getValue("dashboard", "analog_vel_tex"); m_Dashboard.analog_vel_pos = cfile.getValue("dashboard", "analog_vel_pos").toVector(); m_Dashboard.analog_vel_rad = cfile.getValue("dashboard", "analog_vel_rad").toFloat(); m_Dashboard.analog_vel_an0 = cfile.getValue("dashboard", "analog_vel_an0").toFloat(); m_Dashboard.analog_vel_an1 = cfile.getValue("dashboard", "analog_vel_an1").toFloat(); m_Dashboard.analog_vel_max = cfile.getValue("dashboard", "analog_vel_max").toFloat(); m_Dashboard.analog_rpm_tex = cfile.getValue("dashboard", "analog_rpm_tex"); m_Dashboard.analog_rpm_pos = cfile.getValue("dashboard", "analog_rpm_pos").toVector(); m_Dashboard.analog_rpm_rad = cfile.getValue("dashboard", "analog_rpm_rad").toFloat(); m_Dashboard.analog_rpm_an0 = cfile.getValue("dashboard", "analog_rpm_an0").toFloat(); m_Dashboard.analog_rpm_an1 = cfile.getValue("dashboard", "analog_rpm_an1").toFloat(); m_Dashboard.analog_rpm_max = cfile.getValue("dashboard", "analog_rpm_max").toFloat(); m_Dashboard.digital_vel_pos = cfile.getValue("dashboard", "digital_vel_pos").toVector(); m_Dashboard.digital_vel_hth = cfile.getValue("dashboard", "digital_vel_hth").toFloat(); m_Dashboard.digital_vel_wth = cfile.getValue("dashboard", "digital_vel_wth").toFloat(); m_Dashboard.digital_rpm_pos = cfile.getValue("dashboard", "digital_rpm_pos").toVector(); m_Dashboard.digital_rpm_hth = cfile.getValue("dashboard", "digital_rpm_hth").toFloat(); m_Dashboard.digital_rpm_wth = cfile.getValue("dashboard", "digital_rpm_wth").toFloat(); m_Dashboard.songtitle_pos = cfile.getValue("dashboard", "songtitle_pos").toVector(); m_Dashboard.songtitle_hth = cfile.getValue("dashboard", "songtitle_hth").toFloat(); m_Dashboard.songtitle_wth = cfile.getValue("dashboard", "songtitle_wth").toFloat(); m_SteerSpeedOut = cfile.getValue("controls", "steerspeed_out").toFloat(); m_SteerSpeedIn = cfile.getValue("controls", "steerspeed_in").toFloat(); m_SteerSpeed_v_factor = cfile.getValue("controls", "steerspeed_v_factor").toFloat(); //The input object: CCarInput instead of CMovObjInput delete m_InputData; m_InputData = new CCarInput; m_InputData->m_MovObjID = m_MovObjID; //Five bodies: CBody body, wheel1, wheel2, wheel3, wheel4; //texture settings: CParamList plist; { SParameter p; p.name = "subset"; p.value = (int)m_Textures[0]; plist.push_back(p); p.name = "color"; p.value = list.getValue("color", cfile.getValue("skin", "defaultcolor")); plist.push_back(p); } //Set the indices to the body array body.m_Body = theWorld->loadObject(bodygeomfile, plist, CDataObject::eBound); if(body.m_Body < 0) printf("Error: body geometry %s was not loaded\n", bodygeomfile.c_str()); wheel1.m_Body = theWorld->loadObject(frontwheelgeomfile, plist, CDataObject::eBound); wheel2.m_Body = wheel1.m_Body; if(wheel1.m_Body < 0) printf("Error: frontwheel geometry %s was not loaded\n", frontwheelgeomfile.c_str()); wheel3.m_Body = theWorld->loadObject(rearwheelgeomfile, plist, CDataObject::eBound); wheel4.m_Body = wheel3.m_Body; if(wheel3.m_Body < 0) printf("Error: rearwheel geometry %s was not loaded\n", rearwheelgeomfile.c_str()); m_Bodies.push_back(body); m_Bodies.push_back(wheel1); m_Bodies.push_back(wheel2); m_Bodies.push_back(wheel3); m_Bodies.push_back(wheel4); //Size of the wheels m_Wheel[0].m_Radius = ((CBound *)theWorld->getObject(CDataObject::eBound, wheel1.m_Body))->m_CylinderRadius; m_Wheel[1].m_Radius = ((CBound *)theWorld->getObject(CDataObject::eBound, wheel2.m_Body))->m_CylinderRadius; m_Wheel[2].m_Radius = ((CBound *)theWorld->getObject(CDataObject::eBound, wheel3.m_Body))->m_CylinderRadius; m_Wheel[3].m_Radius = ((CBound *)theWorld->getObject(CDataObject::eBound, wheel4.m_Body))->m_CylinderRadius; //Important: do this AFTER setting m_Radius: m_Wheel[0].load(cfile, "frontwheels"); m_Wheel[1].load(cfile, "frontwheels"); m_Wheel[2].load(cfile, "rearwheels"); m_Wheel[3].load(cfile, "rearwheels"); //Correcting the neutral position m_Wheel[0].m_NeutralPos.x = -m_Wheel[0].m_NeutralPos.x; m_Wheel[2].m_NeutralPos.x = -m_Wheel[2].m_NeutralPos.x; for(unsigned int i=0; i<4; i++) m_Wheel[i].m_NeutralPos -= m_CenterOfMass; //save as relative to center of mass //Calculate the position above the ground: m_PositionAboveGround = 0.5 * ( m_Wheel[0].m_Radius - m_Wheel[0].m_NeutralPos.y + m_Wheel[2].m_Radius - m_Wheel[2].m_NeutralPos.y); //Setting the initial positions m_Position = CVector(0,0,0); m_OrientationMatrix = CMatrix(); resetBodyPositions(); return true; } void CCar::unload() { //TODO CMovingObject::unload(); } void CCar::resetBodyPositions() { placeBodies(); } void CCar::placeBodies() { m_Bodies[0].m_Position = m_Position - m_OrientationMatrix * m_CenterOfMass; m_Bodies[0].m_OrientationMatrix = m_OrientationMatrix; for(unsigned int i=0; i < 4; i++) { float steeringAngle = m_Wheel[i].m_DesiredSt; float rotationAngle = m_Wheel[i].m_a; if(i == 1 || i == 3) //the right wheels { steeringAngle += M_PI; rotationAngle = -rotationAngle; } //s = wheel steering matrix //r = wheel rotation matrix CMatrix s, r; s.rotY(steeringAngle); r.rotX(rotationAngle); //i+1 because body 0 is the car main body m_Bodies[i+1].m_Position = m_Position + m_OrientationMatrix * (m_Wheel[i].m_NeutralPos + CVector(0,m_Wheel[i].m_Height,0)); m_Bodies[i+1].m_OrientationMatrix = r * s * m_OrientationMatrix; } } void CCar::determineGroundPlane(CPhysics *simulator) { vector wheelGround; vector contactPoint; //Debugging: //m_Ground.nor = CVector(0,1,0); //m_Ground.d = 0.0; //return; //fprintf(stderr, "\n\ndetermineGroundPlane\n"); //get the ground faces for the wheels for(unsigned int i=0; i < 4; i++) { CVector pos = m_OrientationMatrix * (m_Wheel[i].m_NeutralPos + CVector(0,m_Wheel[i].m_Radius,0)); //fprintf(stderr, "Wheel %d height: %.3f\n", i, (pos + m_Position).y); const CCollisionFace * theFace = theWorld->m_Detector.getGroundFace(pos + m_Position); if(theFace != NULL) { //In absolute coordinates: m_Wheel[i].m_Ground = *theFace; //relative to the car center: CCollisionFace cf = *theFace; cf.d -= m_Position.dotProduct(cf.nor); float dpos = pos.dotProduct(cf.nor); //check if it's close enough to the wheel if(dpos - cf.d > 5.0 * m_Wheel[i].m_Radius) continue; //fprintf(stderr, " -> on the ground\n"); //contact position pos += (cf.d - dpos) * cf.nor; contactPoint.push_back(pos); wheelGround.push_back(cf); } } //printf("%d\n", wheelGround.size()); switch(wheelGround.size()) { case 4: { //a plane through each combination of 3 wheels CCollisionFace planes[4]; for(unsigned int i=0; i < 4; i++) { int indices[3]; //the 3 points //only the points that are not i int count = 0; for(unsigned int j=0; j < 4; j++) if(j != i) { indices[count] = j; count++; } CVector line1 = contactPoint[indices[1]] - contactPoint[indices[0]]; CVector line2 = contactPoint[indices[2]] - contactPoint[indices[0]]; planes[i].nor = line1.crossProduct(line2).normal(); CVector norsum = wheelGround[indices[0]].nor + wheelGround[indices[1]].nor + wheelGround[indices[2]].nor; if(norsum.dotProduct(planes[i].nor) < 0.0) //the real normals point to the other side planes[i].nor = -planes[i].nor; planes[i].d = contactPoint[indices[0]].dotProduct(planes[i].nor); } //now choose a plane. The other wheel should be as much above its own plane as possible //so its contact point should be as much below the chosen plane as possible //to make the simulation more continuous, we don't choose, but //we use the height as a weight m_Ground.nor = CVector(0,0,0); m_Ground.d = 0.0; float weight[4]; for(unsigned int i=0; i < 4; i++) { float height = contactPoint[i].dotProduct(planes[i].nor) - planes[i].d; if(height < 0.0) {weight[i] = 1.0;} else {weight[i] = 0.1;} //weight[i] = exp(-height); //always positive, >> 0 for height = 0 m_Ground.nor += weight[i] * planes[i].nor; m_Ground.d += weight[i] * planes[i].d; } float weightsum = weight[0] + weight[1] + weight[2] + weight[3]; if(fabs(weightsum) > 0.01) { m_Ground.d /= weightsum; } else { m_Ground.d = 0.0; } m_Ground.nor.normalise(); float norcomp0, norcomp1, norcomp2, norcomp3; norcomp0 = wheelGround[0].nor.dotProduct(m_Ground.nor); norcomp1 = wheelGround[1].nor.dotProduct(m_Ground.nor); norcomp2 = wheelGround[2].nor.dotProduct(m_Ground.nor); norcomp3 = wheelGround[3].nor.dotProduct(m_Ground.nor); if(norcomp0 > norcomp1 && norcomp0 > norcomp2 && norcomp0 > norcomp3) { m_Ground.material = wheelGround[0].material; m_Ground.isSurface = wheelGround[0].isSurface; m_Ground.isWater = wheelGround[0].isWater; } else if(norcomp1 > norcomp2 && norcomp1 > norcomp3) { m_Ground.material = wheelGround[1].material; m_Ground.isSurface = wheelGround[1].isSurface; m_Ground.isWater = wheelGround[1].isWater; } else if(norcomp2 > norcomp3) { m_Ground.material = wheelGround[2].material; m_Ground.isSurface = wheelGround[2].isSurface; m_Ground.isWater = wheelGround[2].isWater; } else { m_Ground.material = wheelGround[3].material; m_Ground.isSurface = wheelGround[3].isSurface; m_Ground.isWater = wheelGround[3].isWater; } break; } case 3: { CVector line1 = contactPoint[1] - contactPoint[0]; CVector line2 = contactPoint[2] - contactPoint[0]; m_Ground.nor = line1.crossProduct(line2).normal(); CVector norsum = wheelGround[0].nor + wheelGround[1].nor + wheelGround[2].nor; if(norsum.dotProduct(m_Ground.nor) < 0.0) //the real normals point to the other side m_Ground.nor = -m_Ground.nor; m_Ground.d = contactPoint[0].dotProduct(m_Ground.nor); float norcomp0, norcomp1, norcomp2; norcomp0 = wheelGround[0].nor.dotProduct(m_Ground.nor); norcomp1 = wheelGround[1].nor.dotProduct(m_Ground.nor); norcomp2 = wheelGround[2].nor.dotProduct(m_Ground.nor); if(norcomp0 > norcomp1 && norcomp0 > norcomp2) { m_Ground.material = wheelGround[0].material; m_Ground.isSurface = wheelGround[0].isSurface; m_Ground.isWater = wheelGround[0].isWater; } else if(norcomp1 > norcomp2) { m_Ground.material = wheelGround[1].material; m_Ground.isSurface = wheelGround[1].isSurface; m_Ground.isWater = wheelGround[1].isWater; } else { m_Ground.material = wheelGround[2].material; m_Ground.isSurface = wheelGround[2].isSurface; m_Ground.isWater = wheelGround[2].isWater; } break; } case 2: { CVector line = (contactPoint[1] - contactPoint[0]).normal(); CVector nor0_comp = wheelGround[0].nor - wheelGround[0].nor.component(line); CVector nor1_comp = wheelGround[1].nor - wheelGround[1].nor.component(line); m_Ground.nor = (nor0_comp + nor1_comp).normal(); m_Ground.d = contactPoint[0].dotProduct(m_Ground.nor); if(nor0_comp.abs2() > nor1_comp.abs2()) { m_Ground.material = wheelGround[0].material; m_Ground.isSurface = wheelGround[0].isSurface; m_Ground.isWater = wheelGround[0].isWater; } else { m_Ground.material = wheelGround[1].material; m_Ground.isSurface = wheelGround[1].isSurface; m_Ground.isWater = wheelGround[1].isWater; } break; } case 1: { m_Ground = wheelGround[0]; break; } case 0: { m_Ground.nor = CVector(0,0,0); break; } } //back to absolute coordinates: m_Ground.d += m_Position.dotProduct(m_Ground.nor); } void CCar::placeOnGround() { //Place wheels on ground for(unsigned int i=0; i < 4; i++) { const CCollisionFace &ground = m_Wheel[i].m_Ground; float ndoty = ground.nor.dotProduct(m_OrientationMatrix * CVector(0,1,0)); if(ndoty < 0.1) { m_Wheel[i].m_Height = 0.0; m_Wheel[i].m_dHeight = 0.0; continue; } float ndoty_inv = 1.0 / ndoty; CVector neutral_rel = m_OrientationMatrix * m_Wheel[i].m_NeutralPos; CVector neutral = m_Position + neutral_rel; m_Wheel[i].m_Height = ndoty_inv * (ground.d + m_Wheel[i].m_Radius - ground.nor.dotProduct(neutral)); //Assuming that ndoty is more or less constant: m_Wheel[i].m_dHeight = -ndoty_inv * ground.nor.dotProduct( m_Velocity - m_AngularVelocity.crossProduct(neutral_rel)); if(m_Wheel[i].m_Height < 0.0) { m_Wheel[i].m_Height = 0.0; m_Wheel[i].m_dHeight = 0.0; } } } void CCar::landOnGround() { /* All models are wrong, but some are useful. G.E.P. Box */ if(m_Ground.nor.abs2() < 0.25) return; //The right rotation: CMatrix &Mnu = m_OrientationMatrix; CVector ynu; ynu.x = Mnu.Element(1,0); ynu.y = Mnu.Element(1,1); ynu.z = Mnu.Element(1,2); CVector ystr = m_Ground.nor; CVector cp = ystr.crossProduct(ynu); float abscp = cp.abs(); if(abscp > 0.001) cp *= (asin(abscp) / abscp); CMatrix rotate; rotate.setRotation(cp); Mnu *= rotate; //align the angular velocity m_AngularVelocity = m_AngularVelocity.component(m_Ground.nor); //The right position: CVector &pos = m_Position; float dobj = pos.dotProduct(m_Ground.nor) - m_Ground.d; pos -= (dobj - m_PositionAboveGround) * m_Ground.nor; //align the linear velocity: float vvert = m_Velocity.dotProduct(m_Ground.nor); if(vvert < 0.0) { m_Velocity -= vvert * m_Ground.nor; } printf("Landed\n"); } void CCar::fixFlyingOrientation() { //Goal: align the nose more or less with the velocity vector //More precise: the vertical y-axis is made perpendicular to //the velocity vector //don't do it at low speeds if(m_Velocity.abs2() < 100.0) return; //The Y-axis orientation: CVector yNow = m_OrientationMatrix * CVector(0,1,0); //The velocity vector, normalised: CVector v = m_Velocity.normal(); //The component of the Y-axis perpendicular to v, normalised: CVector yNew = (yNow - yNow.component(v)).normal(); if(yNew.dotProduct(yNow) < 0.0) yNew = -yNew; //The rotation axis CVector rAxis = yNew.crossProduct(yNow); float rAxisSize = rAxis.abs(); if(rAxisSize < 0.001) return; //rotation is not needed; prevent division by zero float angle = fabsf(asin(rAxisSize)); //Don't rotate too much: angle *= 0.5; //some kind of under-compensation if(angle > 0.01) angle = 0.01; rAxis *= (angle / rAxisSize); CMatrix rot; rot.setRotation(rAxis); m_OrientationMatrix *= rot; m_AngularVelocity -= m_AngularVelocity.component(rAxis.normal()); } void CCar::update(CPhysics *simulator, float dt) { //Reset the skid volume to 0.0 //Reset the torque on the wheel to 0.0 for(unsigned int i=0; i < 4; i++) { m_Wheel[i].m_SkidVolume = 0.0; m_Wheel[i].m_M = 0.0; } //Override input on crash if(m_RuleStatus.state == CCarRuleStatus::eCrashed) { m_InputData->m_Up = 0.0; m_InputData->m_Forward = 0.0; m_InputData->m_Backward = 1.0; //This is always the brake, even in reverse gear m_InputData->m_Right = 0.0; } //Wheel stuff doSteering(dt); simulateGeneral(simulator, dt); determineGroundPlane(simulator); if(m_Ground.nor.abs2() < 0.25) { //fprintf(stderr, "ground plane NOT found\n"); simulateAir(simulator, dt); } else { float vvert = m_Velocity.dotProduct(m_Ground.nor); float dobj = m_Bodies[0].m_Position.dotProduct(m_Ground.nor) - m_Ground.d; unsigned numFloatingWheels = 0; for(unsigned int i=0; i<4; i++) if(m_Wheel[i].m_Height < 0.001) numFloatingWheels++; if(vvert > 0.1 && numFloatingWheels == 4) { //fprintf(stderr, "vertical speed pointing away from gound\n"); //printf("Airborne!\n"); simulateAir(simulator, dt); } else if(dobj > 10.0) { //fprintf(stderr, "ground plane far below\n"); //printf("Airborne!\n"); simulateAir(simulator, dt); } else { //fprintf(stderr, "ground plane found\n"); simulateGround(simulator, dt); } } //integration step for the wheels for(unsigned int i=0; i < 4; i++) { m_Wheel[i].m_a += m_Wheel[i].m_w * dt; if(m_Wheel[i].m_a > DBLPI) m_Wheel[i].m_a -= DBLPI; if(m_Wheel[i].m_a < 0.0) m_Wheel[i].m_a += DBLPI; m_Wheel[i].m_w += dt * m_Wheel[i].m_M * m_Wheel[i].m_Iinv_eff; } //---------------------- //Integration step //---------------------- CMovingObject::update(simulator, dt); } void CCar::correctCollisions() { for(unsigned int c=0; c < m_SimCollisions.size(); c++) { CCollisionData col = m_SimCollisions[c]; //First do a crash test if(col.getRadVel() > 10.0) //10 m/s = 36 km/h { m_RuleStatus.crash(); m_SimCollisions[c].fatal = true; } //Then do position correction CVector dr = col.nor * col.depth; //no correction towards the ground if(m_Ground.nor.abs2() > 0.25) { if(col.nor.dotProduct(m_Ground.nor) < -0.9) continue; CVector newcolnor = (col.nor - col.nor.component(m_Ground.nor)).normal(); float dotpr = newcolnor.dotProduct(col.nor); if(fabs(dotpr) < 0.001) continue; dr = newcolnor * (col.depth / dotpr); } //correct the position m_Position += dr; //set the collision velocity (relative to vmean) to zero m_Velocity -= col.vmean; float radcomp = m_Velocity.dotProduct(col.nor); if(radcomp < 0.0) m_Velocity -= radcomp * col.nor; m_Velocity += col.vmean; } } void CCar::simulateGeneral(CPhysics *simulator, float dt) { /* I know that this defies the law of gravity, but, you see, I never studied law. Bugs Bunny */ addForce(CVector(0,-9.81 / m_InvMass, 0)); addDownforce(); //---------------------- //Air resistance //---------------------- addForce(-m_cwA * m_Velocity.abs() * m_Velocity); //---------------------- //Rotation damping //---------------------- addTorque(-m_RotationDamping * m_AngularVelocity.abs() * m_AngularVelocity); //---------------------- //Engine simulation //---------------------- m_Engine.m_Gear = ((CCarInput *)m_InputData)->m_Gear; if(m_Engine.m_Gear >= m_Engine.m_GearRatios.size()) m_Engine.m_Gear = m_Engine.m_GearRatios.size() - 1; m_Engine.m_Gas = ((CCarInput *)m_InputData)->m_Forward; m_Engine.update(dt, m_Wheel[0].m_w, m_Wheel[1].m_w, m_Wheel[2].m_w, m_Wheel[3].m_w); //---------------------- //Wheel simulation //---------------------- updateWheelOrientation(); updateWheelTorques(); } void CCar::simulateAir(CPhysics *simulator, float dt) { //printf("Flying\n"); m_SimState = eFlying; for(unsigned int i=0; i<4; i++) { m_Wheel[i].m_Fnormal = 0.0; m_Wheel[i].m_Height = 0.0; m_Wheel[i].m_dHeight = 0.0; } fixFlyingOrientation(); } void CCar::simulateGround(CPhysics *simulator, float dt) { if(m_SimState == eFlying) landOnGround(); m_SimState = eRiding; //printf("Riding\n"); placeOnGround(); //Places wheels on ground calculateNormalForces(); applyWheelForces(); } void CCar::addDownforce() { const CMatrix &ori = m_Bodies[0].m_OrientationMatrix; CVector v = m_Velocity; v /= ori; if(v.z < 0.0) //moving forward { CVector frontpos(0.0, 0.0, m_Wheel[0].m_NeutralPos.z); CVector rearpos(0.0, 0.0, m_Wheel[2].m_NeutralPos.z); CVector frontF(0.0, -m_FrontDownforce*v.z*v.z, 0.0); CVector rearF(0.0, -m_RearDownforce*v.z*v.z, 0.0); frontpos *= ori; rearpos *= ori; frontF *= ori; rearF *= ori; addForceAt(frontF, frontpos); addForceAt(rearF, rearpos); } } void CCar::doSteering(float dt) { CCarInput *input = (CCarInput *)m_InputData; float steer = input->m_Right; //the speed of steering float steerspeed = m_SteerSpeedOut; if((m_DesiredSteering < 0.0 && steer > m_DesiredSteering) || (m_DesiredSteering > 0.0 && steer < m_DesiredSteering)) steerspeed = m_SteerSpeedIn; //steer back to neutral is faster //faster velocity -> slower steering steerspeed /= 1.0 + m_SteerSpeed_v_factor * m_Velocity.abs(); //linear function: float steerchange = copysign(steerspeed * dt, steer - m_DesiredSteering); m_DesiredSteering += steerchange; if(m_DesiredSteering < -1.0) m_DesiredSteering = -1.0; if(m_DesiredSteering > 1.0) m_DesiredSteering = 1.0; //fixing straight road driving: if(fabsf(steer) < 0.05 && fabsf(m_DesiredSteering) < 0.05) m_DesiredSteering = 0.0; //if the wheels were in the middle of the car float desiredfront = m_FrontSteerMax * m_DesiredSteering; float desiredrear = m_RearSteerMax * m_DesiredSteering; //desired angles //important: default to zero for(unsigned int i=0; i < 4; i++) m_Wheel[i].m_DesiredSt = 0.0; if(fabs(desiredfront) > 0.0001 || fabs(desiredrear) > 0.0001) { //This correction is actually better than that //is possible in a real car float lth = m_Wheel[2].m_NeutralPos.z - m_Wheel[0].m_NeutralPos.z; float halfwthf = m_Wheel[1].m_NeutralPos.x; float halfwthr = m_Wheel[3].m_NeutralPos.x; float tanaf = tanf(desiredfront); float tanar = tanf(-desiredrear); float yf = lth * tanaf / (tanar + tanaf); float yr = lth - yf; float radius = 0.0; if(fabs(tanaf) > 0.0001) {radius = 0.5*lth / tanaf;} //yf / tanaf} else {radius = yr / tanar;} m_Wheel[0].m_DesiredSt = atanf(yf / (radius + halfwthf)); m_Wheel[1].m_DesiredSt = atanf(yf / (radius - halfwthf)); m_Wheel[2].m_DesiredSt = -atanf(yr / (radius + halfwthr)); m_Wheel[3].m_DesiredSt = -atanf(yr / (radius - halfwthr)); } } void CCar::updateWheelOrientation() { CVector z1(-sin(m_Wheel[0].m_DesiredSt), 0.0, cos(m_Wheel[0].m_DesiredSt)), z2(-sin(m_Wheel[1].m_DesiredSt), 0.0, cos(m_Wheel[1].m_DesiredSt)), z3(-sin(m_Wheel[2].m_DesiredSt), 0.0, cos(m_Wheel[2].m_DesiredSt)), z4(-sin(m_Wheel[3].m_DesiredSt), 0.0, cos(m_Wheel[3].m_DesiredSt)); m_Wheel[0].m_Z = m_OrientationMatrix * z1; m_Wheel[1].m_Z = m_OrientationMatrix * z2; m_Wheel[2].m_Z = m_OrientationMatrix * z3; m_Wheel[3].m_Z = m_OrientationMatrix * z4; } void CCar::updateWheelTorques() //engine + brakes { CCarInput *input = (CCarInput *)m_InputData; m_Wheel[0].m_M += m_Engine.getWheelM(0) - m_Wheel[0].getBrakeTorque(input->m_Backward); m_Wheel[1].m_M += m_Engine.getWheelM(1) - m_Wheel[1].getBrakeTorque(input->m_Backward); m_Wheel[2].m_M += m_Engine.getWheelM(2) - m_Wheel[2].getBrakeTorque(input->m_Backward); m_Wheel[3].m_M += m_Engine.getWheelM(3) - m_Wheel[3].getBrakeTorque(input->m_Backward); //adding the opposite torques to the body for(unsigned int i=0; i < 3; i++) { CVector wz = m_Wheel[i].m_Z; //make sure that the z axis is not flipped if(i == 1 || i == 3) wz = -wz; //x direction: (TODO: write out explicitly; this is inefficient) CVector wx = wz.crossProduct(CVector(0,-1,0)); addTorque(-m_Wheel[i].m_M * wx); } } void CCar::calculateNormalForces() { for(unsigned int i=0; i < 4; i++) { //y difference: float y = m_Wheel[i].m_Height, dy = m_Wheel[i].m_dHeight; m_Wheel[i].m_Fnormal = m_Wheel[i].m_suspk * y + m_Wheel[i].m_suspd * dy; if(m_Wheel[i].m_Fnormal < 0.0) m_Wheel[i].m_Fnormal = 0.0; //no "glue" forces } } void CCar::applyWheelForces() { //This function mainly works in the car coordinate system CMatrix Rmat_inv = m_OrientationMatrix.transpose(); CVector LinVel = Rmat_inv * m_Velocity; CVector AngVel = Rmat_inv * m_AngularVelocity; for(unsigned int i=0; i < 4; i++) { if(m_Wheel[i].m_Ground.nor.abs2() < 0.25 || m_Wheel[i].m_Ground.material == NULL) continue; float muGround = m_Wheel[i].m_Ground.material->m_Mu; float rollGround = m_Wheel[i].m_Ground.material->m_Roll; //position of the wheel: CVector r = m_Wheel[i].m_NeutralPos; //z axis: CVector wz = m_Wheel[i].m_Z; wz *= Rmat_inv; //make sure that the z axis is not flipped if(wz.z < 0.0) wz = -wz; //x direction: //TODO: write out explicitly (this is inefficient) CVector wx = wz.crossProduct(CVector(0,-1,0)); //velocity of the center of the wheel: CVector Vcent = LinVel + r.crossProduct(AngVel); float vlong = Vcent.dotProduct(wz); //longitudinal velocity. Positive is backward float vlat = Vcent.dotProduct(wx); //lateral velocity. Positive is right CVector Mwheel; CVector Fwheel = m_Wheel[i].getGroundForce( Mwheel, vlong, vlat, m_Wheel[i].m_Mu * muGround, m_Wheel[i].m_Roll * rollGround); //Transform from wheel coordinates to car coordinates: Fwheel = CVector(0,Fwheel.y,0) + Fwheel.x * wx + Fwheel.z * wz; //feedback to the net torque on the wheel: m_Wheel[i].m_M += Fwheel.z * m_Wheel[i].m_Radius + Mwheel.x; //forces attach on surface, not on middle: r += CVector(0.0, -m_Wheel[i].m_Radius, 0.0); //fprintf(stderr, "%f\t%f\t%f\t%f\t%f\t", // wz.x, wz.y, wz.z, // vlong, vlat); //Back to world orientation: Fwheel *= m_OrientationMatrix; //TODO: transform to ground plane r *= m_OrientationMatrix; //add forces to the wheel addForceAt(Fwheel, r); } //fprintf(stderr, "\n"); } CBinBuffer &CCar::getData(CBinBuffer &b) const { CMovingObject::getData(b); //general b.addFloat8(m_DesiredSteering, 0.008); //b.addFloat8(m_xAngle, 0.002); //b.addFloat8(m_zAngle, 0.002); //engine b += Uint8(m_Engine.m_Gear); b.addFloat16(m_Engine.m_MainAxisW, 0.4); b.addFloat8(m_Engine.m_Gas, 0.008); //wheels for(unsigned int i=0; i < 4; i++) { b.addFloat16(m_Wheel[i].m_w, 0.4); b.addFloat8(m_Wheel[i].m_a, 0.025); b.addFloat8(m_Wheel[i].m_DesiredSt, 0.008); b.addFloat8(m_Wheel[i].m_SkidVolume, 0.008); b.addFloat8(m_Wheel[i].m_Height, 0.008); } //Flag data Uint8 flags = 0; if(m_AllCollisions.size() != 0) flags = flags | 0x1; //collision data will be added to the message if(m_RuleStatus.state == CCarRuleStatus::eCrashed) flags = flags | 0x2; b += flags; //Add collision data if(m_AllCollisions.size() != 0) { bool fatal = false; float radMax = 0.0, tangMax = 0.0; for(unsigned int i=0; i < m_AllCollisions.size(); i++) { float rad = m_AllCollisions[i].getRadVel(); float tang = m_AllCollisions[i].getTangVel(); if(rad > radMax) radMax = rad; if(tang > tangMax) tangMax = tang; if(m_AllCollisions[i].fatal) fatal = true; } if(radMax > 50.0) radMax = 50.0; if(tangMax > 50.0) tangMax = 50.0; b.addFloat8(radMax, 0.40); b.addFloat8(tangMax, 0.40); b += (Uint8) fatal; } return b; } bool CCar::setData(const CBinBuffer &b, unsigned int &pos) { if(!CMovingObject::setData(b, pos)) return false; //general m_DesiredSteering = b.getFloat8(pos, 0.008); //m_xAngle = b.getFloat8(pos, 0.002); //m_zAngle = b.getFloat8(pos, 0.002); //engine m_Engine.m_Gear = b.getUint8(pos); m_Engine.m_MainAxisW = b.getFloat16(pos, 0.4); m_Engine.m_Gas = b.getFloat8(pos, 0.008); //wheels for(unsigned int i=0; i < 4; i++) { m_Wheel[i].m_w = b.getFloat16(pos, 0.4); m_Wheel[i].m_a = b.getFloat8(pos, 0.025); m_Wheel[i].m_DesiredSt = b.getFloat8(pos, 0.008); m_Wheel[i].m_SkidVolume = b.getFloat8(pos, 0.008); m_Wheel[i].m_Height = b.getFloat8(pos, 0.008); } //Flag data Uint8 flags = b.getUint8(pos); bool hasCollisions = (flags & 0x1) != 0; if((flags & 0x2) != 0) m_RuleStatus.crash(); //Collision data if(hasCollisions) { float radMax = b.getFloat8(pos, 0.40); float tangMax = b.getFloat8(pos, 0.40); bool fatal = b.getUint8(pos); //Make a virtual collision that mimics the collision sound CCollisionData collision; collision.fatal = fatal; collision.nor = CVector(1,0,0); collision.pos = m_Position; collision.vmean = CVector(0,0,0); collision.vdiff = CVector(radMax, 0.0, tangMax); m_AllCollisions.push_back(collision); } placeBodies(); return true; }