// newPoses - Get new car pos from game // caution: called from GAME, 2nd thread, no Ogre stuff here /// Todo: move arrow update and ChampionshipAdvance to updatePoses ... //--------------------------------------------------------------------------------------------------------------- void App::newPoses(float time) // time only for camera update { if (!pGame || bLoading || pGame->cars.empty() /*|| carPoses.empty() || iCurPoses.empty()*/) return; PROFILER.beginBlock(".newPos "); double rplTime = pGame->timer.GetReplayTime(0); // from start double lapTime = pGame->timer.GetPlayerTime(0); double rewTime = pSet->rpl_ghostrewind ? pGame->timer.GetRewindTimeGh(0) : lapTime; // iterate through all car models and set new pos info (from vdrift sim or replay) CarModel* carM0 = carModels[0]; for (int c = 0; c < carModels.size(); ++c) { CarModel* carM = carModels[c]; bool bGhost = carM->isGhost(); CAR* pCar = carM->pCar; PosInfo pi; // new, to fill data // car perf test logic //-------------------------------- if (bPerfTest && c==0) newPerfTest(time); // play get data from replay / ghost ///----------------------------------------------------------------------- if (bGhost) { ///>> track's ghost if (carM->isGhostTrk()) { TrackFrame tf; // par: sec after, 1st lap float lap1 = pGame->timer.GetCurrentLap(0) > 0 ? 2.f : 0.f; bool ok = ghtrk.GetFrame(rewTime + lap1, &tf); // car Axes::toOgre(pi.pos, tf.pos); pi.rot = Axes::toOgre(tf.rot); pi.braking = tf.brake; pi.steer = tf.steer / 127.f; //pi.fboost = 0.f; pi.speed = 0.f; pi.percent = 0.f; //pi.fHitTime = 0.f; pi.fParIntens = 0.f; pi.fParVel = 0.f; //pi.vHitPos = Vector3::ZERO; pi.vHitNorm = Vector3::UNIT_Y; // wheels //dynamics.SetSteering(state.steer, pGame->GetSteerRange()); //peers can have other game settins.. for (int w=0; w < carM->numWheels; ++w) { MATHVECTOR<float,3> whP = carM->whPos[w]; whP[2] += 0.05f; // up tf.rot.RotateVector(whP); Axes::toOgre(pi.whPos[w], tf.pos + whP); if (w < 2) // front steer { float a = (pi.steer * carM->maxangle) * -PI_d/180.f; QUATERNION<float> q; q.Rotate(a, 0,0,1); pi.whRot[w] = Axes::toOgreW(tf.rot * carM->qFixWh[w%2] * q); } else pi.whRot[w] = Axes::toOgreW(tf.rot * carM->qFixWh[w%2]); } } else ///>> ghost { ReplayFrame2 gf; float ti = std::min((float)rewTime, ghplay.GetTimeLength()); bool ok = ghplay.GetFrame(ti, &gf, 0); if (ok) pi.FromRpl2(&gf, 0); if (carM->vtype == V_Sphere) { // weird fix, mini rot pi.carY = Vector3::UNIT_Y; pi.hov_roll = -pi.hov_roll; } } } else ///>> replay if (bRplPlay) { #ifdef DEBUG assert(c < frm.size()); #endif ReplayFrame2& rf = frm[c]; // frm also used in car.cpp for sounds if (c < replay.header.numPlayers) { bool ok = replay.GetFrame(rplTime, &rf, c); if (ok) { pi.FromRpl2(&rf, &pCar->dynamics); pCar->SetPosition(rf.pos, rf.rot); // for objs hit carM->trackPercent = rf.percent /255.f*100.f; } else { carM->fCam->First(); pGame->timer.RestartReplay(0); //at end } } } else ///>> sim, game - get data from vdrift if (pCar) { pi.FromCar(pCar); pi.percent = carM->trackPercent; } pi.bNew = true; //<< rewind ///----------------------------------------------------------------------- if (!bRplPlay && !pGame->pause && !bGhost && pCar) if (pCar->bRewind && pSet->game.rewind_type > 0) { // do rewind (go back) double& gtime = pGame->timer.GetRewindTime(c); gtime = std::max(0.0, gtime - time * gPar.rewindSpeed); double& ghtim = pGame->timer.GetRewindTimeGh(c); ghtim = std::max(0.0, ghtim - time * gPar.rewindSpeed); //rewind ghost time too if (gPar.backTime) { pGame->timer.Back(c, - time * gPar.rewindSpeed); ghost.DeleteFrames(0, ghtim); } RewindFrame rf; bool ok = rewind.GetFrame(gtime, &rf, c); pCar->SetPosRewind(rf.pos, rf.rot, rf.vel, rf.angvel); pCar->dynamics.fDamage = rf.fDamage; // take damage back if (carModels[c]->vtype == V_Sphere) pCar->dynamics.sphereYaw = rf.hov_roll; carModels[c]->First(); } else if (c < 4) // save data { const CARDYNAMICS& cd = pCar->dynamics; RewindFrame fr; fr.time = pGame->timer.GetRewindTime(c); fr.pos = cd.body.GetPosition(); fr.rot = cd.body.GetOrientation(); fr.vel = cd.GetVelocity(); fr.angvel = cd.GetAngularVelocity(); fr.fDamage = cd.fDamage; if (cd.vtype == V_Sphere) fr.hov_roll = cd.sphereYaw; else fr.hov_roll = cd.hov_roll; //? fr.hov_throttle = cd.hov_throttle; rewind.AddFrame(fr, c); // rec rewind } //<< record save data ///----------------------------------------------------------------------- if (pSet->rpl_rec && !bRplPlay && !pGame->pause && !bGhost && pCar) { if (iRplSkip++ >= 1) // 1 half game framerate { iRplSkip = 0; ReplayFrame2 fr; fr.time = rplTime; fr.percent = carM->trackPercent /100.f*255.f; fr.FromCar(pCar, replay.GetLastHitTime(c)); replay.AddFrame(fr, c); // rec replay if (c==0) /// rec ghost lap { fr.time = lapTime; ghost.AddFrame(fr, 0); } // recorded info ..in update { int size = replay.GetNumFrames() * 232; //par approx sizeof(ReplayFrame); std::string s = fToStr( float(size)/1000000.f, 2,5); String ss = String( TR("#{RplRecTime}: ")) + CHud::StrTime(replay.GetTimeLength()) + TR(" #{RplSize}: ") + s + TR(" #{UnitMB}"); gui->valRplName2->setCaption(ss); } } } if (bRplPlay) gui->valRplName2->setCaption(""); ///----------------------------------------------------------------------- // checkpoints, lap start //----------------------------------------------------------------------- if (bRplPlay || bGhost) // dont check for replay or ghost carM->bWrongChk = false; else { /// arrow update -------------------------------------- SplineRoad* road = scn->road; if (pSet->check_arrow && carM->eType == CarModel::CT_LOCAL && !bRplPlay && hud->arrow.node && road && road->mChks.size()>0) { // set animation start to old orientation hud->arrow.qStart = hud->arrow.qCur; // game start: no animation bool noAnim = carM->iNumChks == 0; // get vector from camera to checkpoint Vector3 chkPos = road->mChks[std::max(0, std::min((int)road->mChks.size()-1, carM->iNextChk))].pos; // last checkpoint, point to start pos if (carM->iNumChks == road->mChks.size()) chkPos = carM->vStartPos; const Vector3& playerPos = pi.pos; Vector3 dir = chkPos - playerPos; dir[1] = 0; // only x and z rotation Quaternion quat = Vector3::UNIT_Z.getRotationTo(-dir); const bool valid = !quat.isNaN(); if (valid) { if (noAnim) hud->arrow.qStart = quat; hud->arrow.qEnd = quat; // calc angle towards cam Real angle = (hud->arrow.qCur.zAxis().dotProduct(carM->fCam->mCamera->getOrientation().zAxis())+1)/2.0f; // set color in material (red for wrong dir) Vector3 col1 = angle * Vector3(0.0, 1.0, 0.0) + (1-angle) * Vector3(1.0, 0.0, 0.0); Vector3 col2 = angle * Vector3(0.0, 0.4, 0.0) + (1-angle) * Vector3(0.4, 0.0, 0.0); sh::Vector3* v1 = new sh::Vector3(col1.x, col1.y, col1.z); sh::Vector3* v2 = new sh::Vector3(col2.x, col2.y, col2.z); sh::Factory::getInstance().setSharedParameter("arrowColour1", sh::makeProperty <sh::Vector3>(v1)); sh::Factory::getInstance().setSharedParameter("arrowColour2", sh::makeProperty <sh::Vector3>(v2)); } } //---------------------------------------------------------------------------- if (carM->bGetStPos) // first pos is at start { carM->bGetStPos = false; carM->matStPos.makeInverseTransform(pi.pos, Vector3::UNIT_SCALE, pi.rot); carM->ResetChecks(); } if (road && !carM->bGetStPos) { // start/finish box dist Vector4 carP(pi.pos.x,pi.pos.y,pi.pos.z,1); carM->vStDist = carM0->matStPos * carP; // start pos from 1st car always carM->bInSt = abs(carM->vStDist.x) < road->vStBoxDim.x && abs(carM->vStDist.y) < road->vStBoxDim.y && abs(carM->vStDist.z) < road->vStBoxDim.z; carM->iInChk = -1; carM->bWrongChk = false; bool locar = carM->eType == CarModel::CT_LOCAL; int ncs = road->mChks.size(); if (ncs > 0) { // Finish -------------------------------------- if (locar && (carM->bInSt && carM->iNumChks == ncs && carM->iCurChk != -1)) { /// Lap bool finished = (pGame->timer.GetCurrentLap(c) >= pSet->game.num_laps) && (mClient || pSet->game.local_players > 1); // multiplay or split bool best = finished ? false : // dont inc laps when race over (in multiplayer or splitscreen mode) pGame->timer.Lap(c, !finished, pSet->game.trackreverse); //,boost_type? double timeCur = pGame->timer.GetPlayerTimeTot(c); // Network notification, send: car id, lap time ---- if (mClient && c == 0 && !finished) mClient->lap(pGame->timer.GetCurrentLap(c), pGame->timer.GetLastLap(c)); /// new best lap, save ghost bool newbest = false; if (!pSet->rpl_bestonly || best || gPar.backTime) if (c==0 && pSet->rpl_rec) // for many, only 1st car { ghost.SaveFile(gui->GetGhostFile()); //,boost_type? ghplay.CopyFrom(ghost); isGhost2nd = false; // hide 2nd ghost newbest = true; } bool champ = pSet->game.champ_num >= 0, chall = pSet->game.chall_num >= 0; bool chs = champ || chall; if (!chs) { if (newbest) pGame->snd_lapbest->start(); //) else pGame->snd_lap->start(); //) } ghost.Clear(); carM->ResetChecks(); // restore boost fuel, each lap ---- if (pSet->game.boost_type == 1 && carM->pCar) carM->pCar->dynamics.boostFuel = carM->pCar->dynamics.boostFuelStart; // damage decrease, each lap --- if (pSet->game.damage_dec > 0.f) carM->pCar->dynamics.fDamage = std::max(0.f, carM->pCar->dynamics.fDamage - pSet->game.damage_dec); // upd lap results ---- carM->updLap = false; carM->fLapAlpha = 1.f; /// all laps finished = pGame->timer.GetCurrentLap(c) >= pSet->game.num_laps; if (finished && !mClient) { if (!chs) { if (carM->iWonPlace == 0) // split screen winner places { if (pSet->game.local_players > 1) { int n = std::min(2, std::max(0, 3 - carIdWin)); pGame->snd_win[n]->start(); //) } carM->iWonPlace = carIdWin++; } } else if (champ) gui->ChampionshipAdvance(timeCur); else gui->ChallengeAdvance(timeCur); } } // checkpoints -------------------------------------- for (int i=0; i < ncs; ++i) { const CheckSphere& cs = road->mChks[i]; Real d2 = pi.pos.squaredDistance(cs.pos); if (d2 < cs.r2) // car in checkpoint { carM->iInChk = i; //\ loop camera change if (pSet->cam_loop_chng && carM->fCam && cs.loop && (carM->iLoopChk == -1 || carM->iLoopChk != i) && pCar->dynamics.vtype != V_Sphere) { carM->iLoopChk = i; if (carM->iLoopLastCam == -1) { carM->iLoopLastCam = carM->fCam->miCurrent; carM->fCam->setCamera(pSet->cam_in_loop); } else { carM->fCam->setCamera(carM->iLoopLastCam); carM->iLoopLastCam = -1; } } // next check if (i == carM->iNextChk && carM->iNumChks < ncs) { carM->iCurChk = i; carM->iNumChks++; carM->timeAtCurChk = pGame->timer.GetPlayerTime(c); int ii = (pSet->game.trackreverse ? -1 : 1) * road->iDir; carM->iNextChk = (carM->iCurChk + ii + ncs) % ncs; carM->UpdNextCheck(); // save car pos and rot carM->pCar->SavePosAtCheck(); carM->updTimes = true; if (pSet->snd_chk && locar) pGame->snd_chk->start(); //) } else if (carM->iInChk != carM->iCurChk && !scn->sc->noWrongChks) // denies { carM->bWrongChk = true; if (carM->iInWrChk != carM->iInChk) { carM->iInWrChk = carM->iInChk; if (pSet->snd_chkwr && locar) pGame->snd_chkwr->start(); //) } } break; } } } } } /// store new pos info in queue _________ if (!(isFocGui || isTweakTab()) || mClient) // dont if gui, but network always { int qn = (iCurPoses[c] + 1) % CarPosCnt; // next index in queue carPoses[qn][c] = pi; // update camera if (carM->fCam) carM->fCam->update(time, pi, &carPoses[qn][c], &pGame->collision, !bRplPlay && pSet->cam_bounce); iCurPoses[c] = qn; // atomic, set new index in queue ///)) upd sound camera if (c == 0 && pGame->snd) { Vector3 x,y,z; carPoses[qn][c].camRot.ToAxes(x,y,z); pGame->snd->setCamera(carPoses[qn][c].camPos, -z, y, Vector3::ZERO); } } } PROFILER.endBlock(".newPos "); }
// updatePoses - Set car pos for Ogre nodes, update particles, trails //--------------------------------------------------------------------------------------------------------------- void App::updatePoses(float time) { if (carModels.empty()) return; PROFILER.beginBlock(".updPos "); // Update all carmodels from their carPos const CarModel* playerCar = carModels.front(); int cgh = -1; for (int c = 0; c < carModels.size(); ++c) { CarModel* carM = carModels[c]; if (!carM) { PROFILER.endBlock(".updPos "); return; } /// ghosts visibility . . . // hide when empty or near car bool bGhostCar = carM->eType == (isGhost2nd ? CarModel::CT_GHOST2 : CarModel::CT_GHOST), // show only actual bGhTrkVis = carM->isGhostTrk() && ghtrk.GetTimeLength()>0 && pSet->rpl_trackghost, bGhostVis = ghplay.GetNumFrames()>0 && pSet->rpl_ghost, bGhostEnd = pGame->timer.GetPlayerTime(0) > ghplay.GetTimeLength(); if (bGhostCar) cgh = c; if (carM->isGhost()) // for all { bool loading = iLoad1stFrames >= 0; // show during load ?.. bool curVisible = carM->mbVisible; bool newVisible = bGhostVis && bGhostCar /**/&& !bGhostEnd/**/ || bGhTrkVis; if (loading) carM->setVisible(true); //!carM->isGhost()); else { // hide ghost when close to player float d = carM->pMainNode->getPosition().squaredDistance(playerCar->pMainNode->getPosition()); if (d < pSet->ghoHideDist * pSet->ghoHideDist) newVisible = false; if (carM->isGhostTrk() && cgh >= 0) // hide track's ghost when near ghost { float d = carM->pMainNode->getPosition().squaredDistance(carModels[cgh]->pMainNode->getPosition()); if (d < pSet->ghoHideDistTrk * pSet->ghoHideDistTrk) newVisible = false; } if (curVisible == newVisible) carM->hideTime = 0.f; else { carM->hideTime += time; // change vis after delay if (carM->hideTime > gPar.ghostHideTime) carM->setVisible(newVisible); } } } // update car pos int q = iCurPoses[c]; int cc = (c + iRplCarOfs) % carModels.size(); // replay offset, camera from other car int qq = iCurPoses[cc]; PosInfo& pi = carPoses[q][c], &pic = carPoses[qq][cc]; carM->Update(carPoses[q][c], carPoses[qq][cc], time); // nick text pos upd 3d to 2d if (carM->pNickTxt && carM->pMainNode) { Camera* cam = playerCar->fCam->mCamera; //above car 1m Vector3 p = hud->projectPoint(cam, carM->pMainNode->getPosition() + Vector3(0,1.f,0)); p.x = p.x * mSplitMgr->mDims[0].width * 0.5f; //1st viewport dims p.y = p.y * mSplitMgr->mDims[0].height * 0.5f; carM->pNickTxt->setPosition(p.x-40, p.y-16); //center doesnt work carM->pNickTxt->setVisible(p.z > 0.f); } } /// Replay info if (bRplPlay && !pGame->cars.empty()) { double pos = pGame->timer.GetPlayerTime(0); float len = replay.GetTimeLength(); gui->valRplPerc->setCaption(fToStr(pos/len*100.f, 1,4)+" %"); gui->valRplCur->setCaption(CHud::StrTime(pos)); gui->valRplLen->setCaption(CHud::StrTime(len)); float v = pos/len; gui->slRplPos->setValue(v); } /// objects - dynamic (props) ------------------------------------------------------------- for (int i=0; i < scn->sc->objects.size(); ++i) { Object& o = scn->sc->objects[i]; if (o.ms) { btTransform tr, ofs; o.ms->getWorldTransform(tr); const btVector3& p = tr.getOrigin(); const btQuaternion& q = tr.getRotation(); o.pos[0] = p.x(); o.pos[1] = p.y(); o.pos[2] = p.z(); o.rot[0] = q.x(); o.rot[1] = q.y(); o.rot[2] = q.z(); o.rot[3] = q.w(); o.SetFromBlt(); } } PROFILER.endBlock(".updPos "); }
///--------------------------------------------------------------------------------------------------------------- // Update HUD ///--------------------------------------------------------------------------------------------------------------- void CHud::Update(int carId, float time) { PROFILER.beginBlock("g.hud"); // update HUD elements for all cars that have a viewport (local or replay) //----------------------------------------------------------------------------------- int cnt = std::min(6/**/, (int)app->carModels.size()); // all cars int cntC = std::min(4/**/, cnt - (app->isGhost2nd && !app->bRplPlay ? 1 : 0)); // all vis plr int c; // gui viewport - done once for all if (carId == -1) for (c = 0; c < cntC; ++c) if (app->carModels[c]->eType == CarModel::CT_LOCAL) { // hud rpm,vel float vel=0.f, rpm=0.f, clutch=1.f; int gear=1; GetVals(c,&vel,&rpm,&clutch,&gear); // update all mini pos tri for (int i=0; i < cnt; ++i) UpdRot(c, i, vel, rpm); } /// all minimap car pos-es rot const static Real tc[4][2] = {{0,1}, {1,1}, {0,0}, {1,0}}; const float z = pSet->size_minipos; // tri size if (carId == -1 && moPos) { moPos->beginUpdate(0); const int plr = app->mSplitMgr->mNumViewports; for (int v = 0; v < plr; ++v) // all viewports { const Hud& h = hud[v]; const float sc = pSet->size_minimap * app->mSplitMgr->mDims[v].avgsize; const Vector3& pos = h.ndMap->getPosition(); for (c = 0; c < cntC; ++c) // all mini pos for one car { const SMiniPos& sp = h.vMiniPos[c]; const ColourValue& clr = app->carModels[c]->color; for (int p=0; p < 4; ++p) // all 4 points { float x = pos.x + (sp.x + sp.px[p]*z)*sc; float y = pos.y + (sp.y + sp.py[p]*z)*sc*asp; moPos->position(x, y, 0); moPos->textureCoord(tc[p][0], tc[p][1]); moPos->colour(clr); } } } int ii = plr * cntC; for (int i=0; i < ii; ++i) { int n = i*4; moPos->quad(n,n+1,n+3,n+2); } moPos->end(); } // track% local, updated always for (c = 0; c < cntC; ++c) { CarModel* cm = app->carModels[c]; if (cm->eType == CarModel::CT_LOCAL || cm->eType == CarModel::CT_REPLAY) cm->UpdTrackPercent(); } if (carId == -1 || app->carModels.empty()) { PROFILER.endBlock("g.hud"); return; } #ifdef DEBUG assert(carId >= 0); assert(carId < app->carModels.size()); #endif CarModel* pCarM = app->carModels[carId]; CAR* pCar = pCarM ? pCarM->pCar : 0; float vel=0.f, rpm=0.f, clutch=1.f; int gear=1; GetVals(carId,&vel,&rpm,&clutch,&gear); Hud& h = hud[carId]; /// multiplayer // ----------------------------------------------------------------------------------- static float tm = 0.f; tm += time; if (tm > 0.2f /**/&& app->mClient/**/) // not every frame, each 0.2s // if (pSet->game.isNetw) .. { // sort winners std::list<CarModel*> cms; for (c=0; c < cnt; ++c) cms.push_back(app->carModels[c]); cms.sort(SortWin); //stable_sort(cms.begin(), cms.end(), SortWin); String msg = ""; int place = 1; // assing places for (std::list<CarModel*>::iterator it = cms.begin(); it != cms.end(); ++it) { CarModel* cm = *it; bool end = app->pGame->timer.GetCurrentLap(cm->iIndex) >= pSet->game.num_laps; cm->iWonPlace = end ? place++ : 0; // when ended race // detect change (won), can happen more than once, if time diff < ping delay if (cm->iWonPlace != cm->iWonPlaceOld) { cm->iWonPlaceOld = cm->iWonPlace; cm->iWonMsgTime = gPar.timeWonMsg; if (cm->iIndex == 0) // for local player, show end wnd app->mWndNetEnd->setVisible(true); } if (cm->iWonMsgTime > 0.f) { cm->iWonMsgTime -= tm; if (cm->iWonPlace != 0) msg += cm->sDispName + " " + TR("#{FinishedCommaPlace}") + ": " + toStr(cm->iWonPlace) + "\n"; } } if (app->mClient && /*ap->pGame->timer.pretime <= 0.f &&*/ app->pGame->timer.waiting) msg += TR("#{NetWaitingForOthers}")+"...\n"; // chat 2 last lines if (gui->sChatLast1 != "") msg += gui->sChatLast1 + "\n"; if (gui->sChatLast2 != "") msg += gui->sChatLast2; ++gui->iChatMove; if (gui->iChatMove >= 10) //par 2sec { gui->iChatMove = 0; gui->sChatLast1 = gui->sChatLast2; gui->sChatLast2 = ""; } // upd hud msgs if (txMsg) { txMsg->setCaption(msg); bckMsg->setVisible(!msg.empty()); } // upd end list if (app->mWndNetEnd->getVisible()) { MyGUI::MultiList2* li = gui->liNetEnd; li->removeAllItems(); for (std::list<CarModel*>::iterator it = cms.begin(); it != cms.end(); ++it) { CarModel* cm = *it; String clr = StrClr(cm->color); li->addItem(""/*clr+ toStr(c+1)*/, 0); int l = li->getItemCount()-1; li->setSubItemNameAt(1,l, clr+ (cm->iWonPlace == 0 ? "--" : toStr(cm->iWonPlace))); li->setSubItemNameAt(2,l, clr+ cm->sDispName); li->setSubItemNameAt(3,l, clr+ StrTime( cm->iWonPlace == 0 ? 0.f : app->pGame->timer.GetPlayerTimeTot(cm->iIndex) )); //li->setSubItemNameAt(4,l, clr+ fToStr(cm->iWonMsgTime,1,3)); li->setSubItemNameAt(4,l, clr+ StrTime( app->pGame->timer.GetBestLapRace(cm->iIndex) )); li->setSubItemNameAt(5,l, clr+ toStr( app->pGame->timer.GetCurrentLap(cm->iIndex) )); } } tm = 0.f; } /// opponents list // ----------------------------------------------------------------------------------- bool visOpp = h.txOpp[0] && pSet->show_opponents; if (visOpp && pCarM && pCarM->pMainNode) { std::list<CarModel*> cms; // sorted list for (c=0; c < cnt; ++c) { // cars only CarModel* cm = app->carModels[c]; if (!cm->isGhost()) { if (app->bRplPlay) cm->trackPercent = app->carPoses[app->iCurPoses[c]][c].percent; cms.push_back(cm); } } if (pSet->opplist_sort) cms.sort(SortPerc); for (c=0; c < cnt; ++c) { // add last, if visible, ghost1 (dont add 2nd) and track's ghost CarModel* cm = app->carModels[c]; if (cm->eType == (app->isGhost2nd ? CarModel::CT_GHOST2 : CarModel::CT_GHOST) && pSet->rpl_ghost || cm->isGhostTrk() && pSet->rpl_trackghost) { cm->trackPercent = app->carPoses[app->iCurPoses[c]][c].percent; // ghost,rpl cms.push_back(cm); } } bool bGhostEnd = app->pGame->timer.GetPlayerTime(0) > app->ghplay.GetTimeLength(); String s0,s1,s2; // Track% Dist Nick ColourValue clr; c = 0; for (std::list<CarModel*>::iterator it = cms.begin(); it != cms.end(); ++it) { CarModel* cm = *it; if (cm->pMainNode) { bool bGhost = cm->isGhost() && !cm->isGhostTrk(); bool bGhostVis = (app->ghplay.GetNumFrames() > 0) && pSet->rpl_ghost; bool bGhEmpty = bGhost && !bGhostVis; // dist ----------- if (cm == pCarM || bGhEmpty) // no dist to self or to empty ghost s1 += "\n"; else { Vector3 v = cm->pMainNode->getPosition() - pCarM->pMainNode->getPosition(); float dist = v.length(); // meters Real h = std::min(60.f, dist) / 60.f; clr.setHSB(0.5f - h * 0.4f, 1, 1); s1 += StrClr(clr)+ fToStr(dist,0,3)+"m\n"; } // percent % ----------- if (bGhEmpty || cm->isGhostTrk()) s0 += "\n"; else { float perc = bGhost && bGhostEnd ? 100.f : cm->trackPercent; clr.setHSB(perc*0.01f * 0.4f, 0.7f, 1); s0 += StrClr(clr)+ fToStr(perc,0,3)+"%\n"; } // nick name ----------- if (cm->eType != CarModel::CT_REPLAY) { s2 += StrClr(cm->color)+ cm->sDispName; bool end = app->pGame->timer.GetCurrentLap(cm->iIndex) >= pSet->game.num_laps && (app->mClient || pSet->game.local_players > 1); // multiplay or split if (end) // place (1) s2 += " (" + toStr(cm->iWonPlace) + ")"; } s2 += "\n"; ++c; } } // upd pos, size if (h.lastOppH != c) { h.lastOppH = c; int y = c*25 +4, yo = h.yOpp - y-4; for (int n=0; n < 3; ++n) { h.txOpp[n]->setPosition(h.xOpp + n*65+5, yo + 3); h.txOpp[n]->setSize(90,y); } h.bckOpp->setPosition(h.xOpp, yo); h.bckOpp->setSize(230,y); } h.txOpp[0]->setCaption(s0); h.txOpp[1]->setCaption(s1); h.txOpp[2]->setCaption(s2); } // Set motion blur intensity for this viewport, depending on car's linear velocity //----------------------------------------------------------------------------------- if (pSet->blur) { // use velocity squared to achieve an exponential motion blur float speed = pCar->GetVelocity().MagnitudeSquared(); // peak at 250 kmh (=69 m/s), 69² = 4761 // motion blur slider: 1.0 = peak at 100 km/h 0.0 = peak at 400 km/h -> 0.5 = peak at 250 km/h // lerp(100, 400, 1-motionBlurIntensity) float peakSpeed = 100 + (1-pSet->blur_int) * (400-100); float intens = fabs(speed) / pow((peakSpeed/3.6f), 2); // higher fps = less perceived motion blur time a frame will be still visible on screen: // each frame, 1-intens of the original image is lost // example (intens = 0.7): // frame 1: full img frame 2: 0.7 * image // frame 3: 0.7² * image frame 4: 0.7³ * image // portion of image visible after 'n' frames: pow(intens, n); // example 1: 60 fps 0.7³ image after 4 frames: 0.066 sec // example 2: 120 fps 0.7³ image after 4 frames: 0.033 sec // now: need to achieve *same* time for both fps values // to do this, adjust intens // (1.0/fps) * pow(intens, n) == (1.0/fps2) * pow(intens2, n) // set n=4 intens_new = sqrt(sqrt((intens^4 * fpsReal/desiredFps)) intens = sqrt(sqrt( pow(intens, 4) * ((1.0f/time) / 120.0f) )); intens = std::min(intens, 0.9f); // clamp to 0.9f app->motionBlurIntensity = intens; } /// gear, vel texts ----------------------------- if (h.txVel && h.txGear && pCar) { float cl = clutch*0.8f + 0.2f; if (gear == -1) { h.txGear->setCaption("R"); h.txGear->setTextColour(Colour(0.3,1,1,cl)); } else if (gear == 0) { h.txGear->setCaption("N"); h.txGear->setTextColour(Colour(0.3,1,0.3,cl)); } else if (gear > 0 && gear < 8) { h.txGear->setCaption(toStr(gear)); h.txGear->setTextColour(Colour(1,1-gear*0.1,0.2,cl)); } h.txVel->setCaption(fToStr(fabs(vel),0,3)); float k = pCar->GetSpeedometer() * 3.6f * 0.0025f; // vel clr #define m01(x) std::min(1.0f, std::max(0.0f, (float) (x) )) h.txVel->setTextColour(Colour(m01(k*2), m01(0.5+k*1.5-k*k*2.5), m01(1+k*0.8-k*k*3.5))); } // boost fuel (time) ------ if (h.txBFuel && pCar && h.txBFuel->getVisible()) { h.txBFuel->setCaption(fToStr(pCar->dynamics.boostFuel,1,3)); } // damage % ------ if (h.txDamage && pCar && h.txDamage->getVisible()) { float d = std::min(100.f, pCar->dynamics.fDamage); //h.txDamage->setCaption(TR("#{Damage}\n ")+fToStr(d,0,3)+" %"); d*=0.01f; h.txDamage->setCaption(fToStr(d,0,3)+" %"); d*=0.01f; float e = std::min(1.f, 0.8f + d*2.f); h.txDamage->setTextColour(Colour(e-d*d*0.4f, std::max(0.f, e-d), std::max(0.f, e-d*2.f) )); } // abs, tcs on ------ if (h.txAbs && h.txTcs && pCar) { bool vis = pCar->GetABSEnabled(); h.txAbs->setVisible(vis); if (vis) h.txAbs->setAlpha(pCar->GetABSActive() ? 1.f : 0.6f); vis = pCar->GetTCSEnabled(); h.txTcs->setVisible(vis); if (vis) h.txTcs->setAlpha(pCar->GetTCSActive() ? 1.f : 0.6f); } /// times, race pos ----------------------------- if (pSet->show_times && pCar) { TIMER& tim = app->pGame->timer; bool hasLaps = pSet->game.local_players > 1 || pSet->game.champ_num >= 0 || pSet->game.chall_num >= 0 || app->mClient; if (hasLaps) { // place if (pCarM->iWonPlace > 0 && h.txPlace) { String s = TR("--- "+toStr(pCarM->iWonPlace)+" #{TBPlace} ---"); h.txPlace->setCaption(s); const static Colour clrPlace[4] = { Colour(0.4,1,0.2), Colour(1,1,0.3), Colour(1,0.7,0.2), Colour(1,0.5,0.2) }; h.txPlace->setTextColour(clrPlace[pCarM->iWonPlace-1]); h.bckPlace->setVisible(true); } } // times ------------------------------ bool cur = pCarM->iCurChk >= 0 && !app->vTimeAtChks.empty(); float ghTimeES = cur ? app->vTimeAtChks[pCarM->iCurChk] : 0.f; float part = ghTimeES / app->fLastTime; // fraction which track ghost has driven bool coldStart = tim.GetCurrentLap(carId) == 1; // was 0 float carMul = app->GetCarTimeMul(pSet->game.car[carId], pSet->game.sim_mode); //| cur float ghTimeC = ghTimeES + (coldStart ? 0 : 1); float ghTime = ghTimeC * carMul; // scaled float diffT = pCarM->timeAtCurChk - ghTime; // cur car diff at chk float diff = 0.f; // on hud //!- if (pCarM->updTimes || pCarM->updLap) { pCarM->updTimes = false; // track time, points float last = tim.GetLastLap(carId), best = tim.GetBestLap(carId, pSet->game.trackreverse); float timeCur = last < 0.1f ? best : last; float timeTrk = app->data->tracks->times[pSet->game.track]; bool b = timeTrk > 0.f && timeCur > 0.f; //bool coldStart = tim.GetCurrentLap(carId) == 1; // was 0 float time = (/*place*/1 * app->data->cars->magic * timeTrk + timeTrk) / carMul; // trk time (for 1st place) //float t1pl = data->carsXml.magic * timeTrk; float points = 0.f, curPoints = 0.f; int place = app->GetRacePos(timeCur, timeTrk, carMul, coldStart, &points); //| cur float timCC = timeTrk + (coldStart ? 0 : 1); float timCu = timCC * carMul; diff = pCarM->timeAtCurChk + /*(coldStart ? 1:0)*carMul*/ - time * part; ///new float chkPoints = 0.f; // cur, at chk, assume diff time later than track ghost int chkPlace = app->GetRacePos(timCu + diffT, timeTrk, carMul, coldStart, &chkPoints); bool any = cur || b; h.sTimes = "\n#80E080" + StrTime(time)+ "\n#D0D040" + (cur ? toStr( chkPlace ) : "--")+ "\n#F0A040" + (cur ? fToStr(chkPoints,1,3) : "--"); float dlap = last - time; h.sLap = "#D0E8FF"+TR("#{TBLapResults}") + "\n#80C8FF" + StrTime(last)+ (last > 0.f ? String(" ") + (dlap > 0.f ? "#80E0FF+" : "#60FF60-") + fToStr(fabs(dlap), 1,3) : "")+ "\n#80E0E0" + StrTime(best)+ "\n#80E080" + StrTime(time)+ "\n#D0D040" + (b ? toStr(place) : "--")+ "\n#F0A040" + (b ? fToStr(points,1,3) : "--"); if (h.txLap) h.txLap->setCaption(h.sLap); } if (h.txTimes) h.txTimes->setCaption( (hasLaps ? "#A0E0D0"+toStr(tim.GetCurrentLap(carId)+1)+" / "+toStr(pSet->game.num_laps) : "") + "\n#A0E0E0" + StrTime(tim.GetPlayerTime(carId))+ (cur ? String(" ") + (diff > 0.f ? "#80E0FF+" : "#60FF60-")+ fToStr(fabs(diff), 1,3) : "")+ h.sTimes+ "\n#E0B090" + fToStr(pCarM->trackPercent,0,1)+"%" ); if (h.txLap) { //if (pCarM->updLap) //{ pCarM->updLap = false; //h.txLap->setCaption(h.sLap); //} float a = std::min(1.f, pCarM->fLapAlpha * 2.f); bool hasRoad = app->scn->road && app->scn->road->getNumPoints() > 2; bool vis = pSet->show_times && hasRoad && a > 0.f; if (vis) { if (app->iLoad1stFrames == -2) //bLoading) // fade out { pCarM->fLapAlpha -= !hasRoad ? 1.f : time * gPar.fadeLapResults; if (pCarM->fLapAlpha < 0.f) pCarM->fLapAlpha = 0.f; } h.bckLap->setAlpha(a); h.txLapTxt->setAlpha(a); h.txLap->setAlpha(a); } h.bckLap->setVisible(vis); h.txLapTxt->setVisible(vis); h.txLap->setVisible(vis); } } // checkpoint warning -------- if (app->scn->road && h.bckWarn && pCarM) { /* checks debug * if (ov[0].oU) { //"ghost: " + GetTimeString(ghost.GetTimeLength()) + " " + toStr(ghost.GetNumFrames()) + "\n" + //"ghplay: " + GetTimeString(ghplay.GetTimeLength()) + " " + toStr(ghplay.GetNumFrames()) + "\n" + ov[0].oU->setCaption(String("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") + " st " + toStr(pCarM->bInSt ? 1:0) + " in" + toStr(pCarM->iInChk) + " | cur" + toStr(pCarM->iCurChk) + " > next " + toStr(pCarM->iNextChk) + " | Num " + toStr(pCarM->iNumChks) + " / All " + toStr(road->mChks.size())); } /**/ if (pCarM->bWrongChk) pCarM->fChkTime = gPar.timeShowChkWarn; bool show = pCarM->fChkTime > 0.f; if (show) pCarM->fChkTime -= time; h.bckWarn->setVisible(show && pSet->show_times); } // race countdown ------ if (h.txCountdown) { bool vis = app->pGame->timer.pretime > 0.f && !app->pGame->timer.waiting; if (vis) h.txCountdown->setCaption(fToStr(app->pGame->timer.pretime,1,3)); h.txCountdown->setVisible(vis); } // camera cur ------ if (h.txCam) { FollowCamera* cam = pCarM->fCam; if (cam && cam->updName) { cam->updName = false; h.txCam->setCaption(cam->sName); } } //------------------------------------------------------------------------------------------------------------------- /// debug infos //------------------------------------------------------------------------------------------------------------------- static int oldTxtClr = -2; // clr upd if (oldTxtClr != pSet->car_dbgtxtclr) { oldTxtClr = pSet->car_dbgtxtclr; UpdDbgTxtClr(); } // car debug text -------- static bool oldCarTxt = false; if (pCar && ov[0].oU) { if (pSet->car_dbgtxt) { std::stringstream s1,s2,s3,s4; pCar->DebugPrint(s1, true, false, false, false); ov[0].oU->setCaption(s1.str()); pCar->DebugPrint(s2, false, true, false, false); ov[1].oU->setCaption(s2.str()); pCar->DebugPrint(s3, false, false, true, false); ov[2].oU->setCaption(s3.str()); pCar->DebugPrint(s4, false, false, false, true); ov[3].oU->setCaption(s4.str()); }else if (pSet->car_dbgtxt != oldCarTxt) { ov[0].oU->setCaption(""); /*ovU[1]->setCaption(""); ovU[2]->setCaption(""); ovU[3]->setCaption("");*/ } } oldCarTxt = pSet->car_dbgtxt; // profiling times -------- if (pSet->profilerTxt && ov[1].oU) { PROFILER.endCycle(); static int frame=0; ++frame; if (frame > 10) //par { frame = 0; std::string sProf = PROFILER.getAvgSummary(quickprof::MILLISECONDS); //sProf = "PROF "+fToStr(1000.f/PROFILER.getAvgDuration(" frameSt",quickprof::MILLISECONDS), 2,4); ov[1].oU->setCaption(sProf); } //if (newPosInfos.size() > 0) //ov[3].oU->setCaption("carm: " + toStr(ap->carModels.size()) + " newp: " + toStr((*newPosInfos.begin()).pos)); } // bullet profiling text -------- static bool oldBltTxt = false; if (ov[1].oU) { if (pSet->bltProfilerTxt) { static int cc = 0; ++cc; if (cc > 40) { cc = 0; std::stringstream os; bltDumpAll(os); ov[1].oU->setCaption(os.str()); } } else if (pSet->bltProfilerTxt != oldBltTxt) ov[1].oU->setCaption(""); } oldBltTxt = pSet->bltProfilerTxt; // wheels slide, susp bars -------- if (pSet->car_dbgbars && pCar) { const Real xp = 80, yp = -530, ln = 20, y4 = 104; //const static char swh[4][6] = {"F^L<","F^R>","RvL<","RvR>"}; for (int w=0; w < 4; ++w) if (ov[3-w].oL && ov[3-w].oR && ov[3-w].oS) { float slide = /*-1.f*/0.f, sLong = 0.f, sLat = 0.f; float squeal = pCar->GetTireSquealAmount((WHEEL_POSITION)w, &slide, &sLong, &sLat); //MATHVECTOR<float,3> vwhVel = pCar->dynamics.GetWheelVelocity((WHEEL_POSITION)w); //float whVel = vwhVel.Magnitude() * 3.6f; /** // info static char ss[256]; sprintf(ss, "%s %6.3f %6.3f %6.3f %6.3f\n", swh[w], sLong/4.f, sLat/3.f, slide, squeal); ColourValue clr; clr.setHSB( slide/20.f, 0.8f, 1.f ); //clr.a = min(0.f, slide/2.f); ovL[3-w]->setCaption(String(ss)); ovL[3-w]->setColour(clr); //ovL[3-w]->setPosition(0.f, 230 + w*22); /**/ // bar meters | float susp = pCar->dynamics.GetSuspension(WHEEL_POSITION(w)).GetDisplacementPercent(); sLong = fabs(sLong); float slng = sLong / sLong * powf(sLong, 0.3f); // slide*20.f ov[3-w].oR->setPosition(slng * 14.f +xp, yp + w*ln); ov[3-w].oL->setPosition(sLat * 14.f +xp, yp + w*ln +y4); ov[3-w].oS->setPosition(susp * 70.f +xp, yp + w*ln -y4); } if (ov[4].oL) ov[4].oL->setPosition(xp, yp + -20 +y4+3); if (ov[4].oS) ov[4].oS->setPosition(xp + 70, yp + -20 -104-3); //ov[3-w].oR->setCaption("|"); ov[3-w].oR->setColour(ColourValue(0.6,1.0,0.7)); } // input values /*if (pCar && ap->pGame && ap->pGame->profilingmode) { const std::vector<float>& inp = pCar->dynamics.inputsCopy; if (ov[2].oU && inp.size() == CARINPUT::ALL) { sprintf(s, " Throttle %5.2f\n Brake %5.2f\n Steer %5.2f\n" " Handbrake %5.2f\n Boost %5.2f\n Flip %5.2f\n" ,inp[CARINPUT::THROTTLE], inp[CARINPUT::BRAKE], -inp[CARINPUT::STEER_LEFT]+inp[CARINPUT::STEER_RIGHT] ,inp[CARINPUT::HANDBRAKE],inp[CARINPUT::BOOST], inp[CARINPUT::FLIP] ); ov[2].oU->setCaption(String(s)); } }/**/ // wheels ter mtr, surface info --------- if (pSet->car_dbgsurf && pCar) { String ss = ""; ss = pCarM->txtDbgSurf; // surfaces info /*ss += "\n"; for (int i=0; i < ap->pGame->track.tracksurfaces.size(); ++i) ss += String(ap->pGame->track.tracksurfaces[i].name.c_str()) + "\n";/**/ //ovCarDbg->show(); if (ov[4].oX) { //ov[4].oL->setTop(400); ov[4].oX->setCaption(ss); } } /// tire vis circles + + + + if (pCar && moTireVis[0] && pSet->car_tirevis) { const Real z = 6000.f / pSet->tc_r, zy = pSet->tc_xr, m_z = 2.f * z; // scale, max factor const int na = 32; // circle quality const Real ad = 2.f*PI_d/na, u = 0.02f; // u line thickness const ColourValue cb(0.8,0.8,0.8),cl(0.2,1,0.2),cr(0.9,0.4,0),cc(1,1,0); for (int i=0; i < 4; ++i) { const CARDYNAMICS& cd = pCar->dynamics; const CARWHEEL::SlideSlip& t = cd.wheel[i].slips; float d = cd.wheel_contact[i].GetDepth() - 2*cd.wheel[i].GetRadius(); bool off = !(d > -0.1f && d <= -0.01f); // not in air //bool on = cd.wheel_contact[i].GetColObj(); ManualObject* m = moTireVis[i]; m->beginUpdate(0); // back + m->position(-1,0,0); m->colour(cb); m->position( 1,0,0); m->colour(cb); m->position(0,-1,0); m->colour(cb); m->position(0, 1,0); m->colour(cb); // tire, before combine Real lx = off ? 0.f : t.preFy/z*zy, ly = off ? 0.f : t.preFx/z; for (int y=-1; y<=1; ++y) for (int x=-1; x<=1; ++x) { m->position(0 +x*u, 0 +y*u, 0); m->colour(cr); m->position(lx +x*u, ly +y*u, 0); m->colour(cr); } // tire line / lx = off ? 0.f : t.Fy/z*zy; ly = off ? 0.f : t.Fx/z; for (int y=-2; y<=2; ++y) for (int x=-2; x<=2; ++x) { m->position(0 +x*u, 0 +y*u, 0); m->colour(cl); m->position(lx +x*u, ly +y*u, 0); m->colour(cl); } // max circle o Real rx = off || t.Fym > m_z ? 0.f : t.Fym/z, ry = off || t.Fxm > m_z ? 0.f : t.Fxm/z, a = 0.f; Vector3 p(0,0,0),po(0,0,0); for (int n=0; n <= na; ++n) { p.x = rx*cosf(a)*zy; p.y =-ry*sinf(a); if (n > 0) { m->position(po); m->colour(cc); m->position(p); m->colour(cc); } a += ad; po = p; } m->end(); } } PROFILER.endBlock("g.hud"); }