void CBitmap::Blur(int iterations, float weight) { if (type == BitmapTypeDDS) { return; } CBitmap* src = this; CBitmap* dst = new CBitmap(); dst->channels = src->channels; dst->Alloc(xsize,ysize); for (int i=0; i < iterations; ++i){ { for_mt(0, ysize, [&](const int y) { for (int x=0; x < xsize; x++) { for (int j=0; j < channels; j++) { kernelBlur(dst, src->mem, x, y, j, weight); } } }); } std::swap(src, dst); } if (dst == this) { // make sure we don't delete `this` std::swap(src, dst); } delete dst; }
void CGrassDrawer::DrawNearBillboards(const std::vector<InviewNearGrass>& inviewNearGrass) { if (farnearVA->drawIndex() == 0) { auto* va_tn = farnearVA->GetTypedVertexArray<VA_TYPE_TN>(inviewNearGrass.size() * numTurfs * 4); for_mt(0, inviewNearGrass.size(), [&](const int i){ const InviewNearGrass& gi = inviewNearGrass[i]; DrawBillboard(gi.x, gi.y, gi.dist, &va_tn[i * numTurfs * 4]); }); } farnearVA->DrawArrayTN(GL_QUADS); }
/* * Draws a trigonometric circle in 'resolution' steps, with a slope modifier */ void glBallisticCircle(const float3& center, const float radius, const CWeapon* weapon, unsigned int resolution, float slope) { int rdiv = 50; resolution *= 2; rdiv *= 1; CVertexArray* va = GetVertexArray(); va->Initialize(); va->EnlargeArrays(resolution, 0, VA_SIZE_0); float3* vertices = reinterpret_cast<float3*>(va->drawArray); va->drawArrayPos = va->drawArray + resolution * 3; for_mt(0, resolution, [&](const int i) { const float radians = (2.0f * PI) * (float)i / (float)resolution; float rad = radius; float sinR = fastmath::sin(radians); float cosR = fastmath::cos(radians); float3 pos; pos.x = center.x + (sinR * rad); pos.z = center.z + (cosR * rad); pos.y = CGround::GetHeightAboveWater(pos.x, pos.z, false); float heightDiff = (pos.y - center.y) * 0.5f; rad -= heightDiff * slope; float adjRadius = weapon ? weapon->GetRange2D(heightDiff * weapon->heightMod) : rad; float adjustment = rad * 0.5f; float ydiff = 0; for(int j = 0; j < rdiv && math::fabs(adjRadius - rad) + ydiff > .01 * rad; j++) { if (adjRadius > rad) { rad += adjustment; } else { rad -= adjustment; adjustment /= 2; } pos.x = center.x + (sinR * rad); pos.z = center.z + (cosR * rad); float newY = CGround::GetHeightAboveWater(pos.x, pos.z, false); ydiff = math::fabs(pos.y - newY); pos.y = newY; heightDiff = (pos.y - center.y); adjRadius = weapon ? weapon->GetRange2D(heightDiff * weapon->heightMod) : rad; } pos.x = center.x + (sinR * adjRadius); pos.z = center.z + (cosR * adjRadius); pos.y = CGround::GetHeightAboveWater(pos.x, pos.z, false) + 5.0f; vertices[i] = pos; }); va->DrawArray0(GL_LINE_LOOP); }
inline static void BlurVertical( const int maxx, const int maxy, const int smoothrad, const float resolution, const std::vector<float>& mesh, std::vector<float>& smoothed) { const float n = 2.0f * smoothrad + 1.0f; const float recipn = 1.0f / n; const int lineSize = maxx + 1; for_mt(0, maxx+1, [&](const int x) { float avg = 0.0f; for (int y = 0; y <= 2 * smoothrad; ++y) { avg += mesh[x + y * lineSize]; } for (int y = 0; y <= maxy; ++y) { const int idx = x + y * lineSize; if (y <= smoothrad || y > (maxy - smoothrad)) { // map-border case smoothed[idx] = 0.0f; const int ystart = std::max(y - smoothrad, 0); const int yend = std::min(y + smoothrad, maxy); for (int y1 = ystart; y1 <= yend; ++y1) { smoothed[idx] += mesh[x + y1 * lineSize]; } const float gh = CGround::GetHeightAboveWater(x * resolution, y * resolution); const float sh = smoothed[idx] / (yend - ystart + 1); smoothed[idx] = std::min(readMap->GetCurrMaxHeight(), std::max(gh, sh)); } else { // non-border case avg += mesh[x + (y + smoothrad) * lineSize] - mesh[x + (y - smoothrad - 1) * lineSize]; const float gh = CGround::GetHeightAboveWater(x * resolution, y * resolution); const float sh = recipn * avg; smoothed[idx] = std::min(readMap->GetCurrMaxHeight(), std::max(gh, sh)); } assert(smoothed[idx] <= std::max(readMap->GetCurrMaxHeight(), 0.0f)); assert(smoothed[idx] >= readMap->GetCurrMinHeight() ); } }); }
inline static void BlurHorizontal( const int maxx, const int maxy, const int smoothrad, const float resolution, const std::vector<float>& mesh, std::vector<float>& smoothed) { const float n = 2.0f * smoothrad + 1.0f; const float recipn = 1.0f / n; const int lineSize = maxx + 1; for_mt(0, maxy+1, [&](const int y) { float avg = 0.0f; for (int x = 0; x <= 2 * smoothrad; ++x) { avg += mesh[x + y * lineSize]; } for (int x = 0; x <= maxx; ++x) { const int idx = x + y * lineSize; if (x <= smoothrad || x > (maxx - smoothrad)) { // map-border case smoothed[idx] = 0.0f; const int xstart = std::max(x - smoothrad, 0); const int xend = std::min(x + smoothrad, maxx); for (int x1 = xstart; x1 <= xend; ++x1) { smoothed[idx] += mesh[x1 + y * lineSize]; } const float gh = CGround::GetHeightAboveWater(x * resolution, y * resolution); const float sh = smoothed[idx] / (xend - xstart + 1); smoothed[idx] = std::min(readMap->GetCurrMaxHeight(), std::max(gh, sh)); } else { // non-border case avg += mesh[idx + smoothrad] - mesh[idx - smoothrad - 1]; const float gh = CGround::GetHeightAboveWater(x * resolution, y * resolution); const float sh = recipn * avg; smoothed[idx] = std::min(readMap->GetCurrMaxHeight(), std::max(gh, sh)); } assert(smoothed[idx] <= std::max(readMap->GetCurrMaxHeight(), 0.0f)); assert(smoothed[idx] >= readMap->GetCurrMinHeight() ); } }); }
void CGrassDrawer::DrawFarBillboards(const std::vector<GrassStruct*>& inviewFarGrass) { // update far grass blocks if (updateBillboards) { updateBillboards = false; for_mt(0, inviewFarGrass.size(), [&](const int i) { GrassStruct& g = *inviewFarGrass[i]; if (g.lastFar == 0) { // TODO: VA's need to be uploaded each frame, switch to VBO's // force the patch-quads to be recreated g.lastFar = globalRendering->drawFrame; g.lastDist = -1.0f; } const float distSq = GetGrassBlockCamDist((g.posX + 0.5f) * grassBlockSize, (g.posZ + 0.5f) * grassBlockSize, true); if (distSq == g.lastDist) return; const bool inAlphaRange1 = ( distSq < Square(maxDetailedDist + 128.0f * 1.5f)) || ( distSq > Square(maxGrassDist - 128.0f)); const bool inAlphaRange2 = (g.lastDist < Square(maxDetailedDist + 128.0f * 1.5f)) || (g.lastDist > Square(maxGrassDist - 128.0f)); if (!inAlphaRange1 && (inAlphaRange1 == inAlphaRange2)) return; g.lastDist = distSq; CVertexArray* va = &g.va; va->Initialize(); // (4*4)*numTurfs quads for (int y2 = g.posZ * grassBlockSize; y2 < (g.posZ + 1) * grassBlockSize; ++y2) { for (int x2 = g.posX * grassBlockSize; x2 < (g.posX + 1) * grassBlockSize; ++x2) { if (!grassMap[y2 * mapDims.mapx / grassSquareSize + x2]) continue; const float dist = GetGrassBlockCamDist(x2, y2); auto* va_tn = va->GetTypedVertexArray<VA_TYPE_TN>(numTurfs * 4); DrawBillboard(x2, y2, dist, va_tn); } } }); } // render far grass blocks for (GrassStruct* g: inviewFarGrass) { g->va.DrawArrayTN(GL_QUADS); } }
void CGrassDrawer::DrawFarBillboards(const std::vector<GrassStruct*>& inviewFarGrass) { // update far grass blocks if (updateBillboards) { updateBillboards = false; for_mt(0, inviewFarGrass.size(), [&](const int i){ GrassStruct& g = *inviewFarGrass[i]; if (!g.va) { //TODO vertex arrays need to be send each frame to the gpu, that's slow. switch to VBOs. CVertexArray* va = new CVertexArray; g.va = va; g.lastDist = -1; // force a recreate } const float distSq = GetCamDistOfGrassBlock((g.posX + 0.5f) * grassBlockSize, (g.posZ + 0.5f) * grassBlockSize, true); if (distSq == g.lastDist) return; bool inAlphaRange1 = ( distSq < Square(maxDetailedDist + 128.f * 1.5f)) || ( distSq > Square(maxGrassDist - 128.f)); bool inAlphaRange2 = (g.lastDist < Square(maxDetailedDist + 128.f * 1.5f)) || (g.lastDist > Square(maxGrassDist - 128.f)); if (!inAlphaRange1 && (inAlphaRange1 == inAlphaRange2)) { return; } g.lastDist = distSq; CVertexArray* va = g.va; va->Initialize(); for (int y2 = g.posZ * grassBlockSize; y2 < (g.posZ + 1) * grassBlockSize; ++y2) { for (int x2 = g.posX * grassBlockSize; x2 < (g.posX + 1) * grassBlockSize; ++x2) { if (!grassMap[y2 * mapDims.mapx / grassSquareSize + x2]) { continue; } const float dist = GetCamDistOfGrassBlock(x2, y2); auto* va_tn = va->GetTypedVertexArray<VA_TYPE_TN>(numTurfs * 4); DrawBillboard(x2, y2, dist, va_tn); } } }); } // render far grass blocks for (const GrassStruct* g: inviewFarGrass) { g->va->DrawArrayTN(GL_QUADS); } }
// --------------------------------------------------------------------- // Create an approximate mesh of the landscape. // bool CRoamMeshDrawer::Tessellate(const float3& campos, int viewradius) { // Perform Tessellation // hint: threading just helps a little with huge cpu usage in retessellation, still better than nothing // _____ // |0|_|_|.. // |_|_|_|.. // |_|_|8|.. // ..... // split the patches in 3x3 sized blocks. The tessellation itself can // extend into the neighbor patches (check Patch::Split). So we could // not multi-thread the whole loop w/o mutexes (in ::Split). // But instead we take a safety distance between the thread's working // area (which is 2 patches), so they don't conflict with each other. bool forceTess = false; for (int idx = 0; idx < 9; ++idx) { for_mt(0, roamPatches.size(), [&](const int i){ Patch* it = &roamPatches[i]; const int X = it->m_WorldX; const int Z = it->m_WorldY; const int subindex = (X % 3) + (Z % 3) * 3; if ((subindex == idx) && it->IsVisible()) { if (!it->Tessellate(campos, viewradius)) forceTess = true; } }); if (forceTess) return true; } return false; }
/** * Get CRC of the data in the specified archive. * Returns 0 if file could not be opened. */ unsigned int CArchiveScanner::GetCRC(const std::string& arcName) { CRC crc; std::list<std::string> files; // Try to open an archive boost::scoped_ptr<IArchive> ar(archiveLoader.OpenArchive(arcName)); if (!ar) { return 0; // It wasn't an archive } // Load ignore list. boost::scoped_ptr<IFileFilter> ignore(CreateIgnoreFilter(ar.get())); // Insert all files to check in lowercase format for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) { std::string name; int size; ar->FileInfo(fid, name, size); if (ignore->Match(name)) { continue; } StringToLowerInPlace(name); // case insensitive hash files.push_back(name); } // Sort by FileName files.sort(); // Push the filenames into a std::vector, cause OMP can better iterate over those std::vector<CRCPair> crcs; crcs.reserve(files.size()); CRCPair crcp; for (std::string& f: files) { crcp.filename = &f; crcs.push_back(crcp); } // Compute CRCs of the files // Hint: Multithreading only speedups `.sdd` loading. For those the CRC generation is extremely slow - // it has to load the full file to calc it! For the other formats (sd7, sdz, sdp) the CRC is saved // in the metainformation of the container and so the loading is much faster. Neither does any of our // current (2011) packing libraries support multithreading :/ for_mt(0, crcs.size(), [&](const int i) { CRCPair& crcp = crcs[i]; const unsigned int nameCRC = CRC::GetCRC(crcp.filename->data(), crcp.filename->size()); const unsigned fid = ar->FindFile(*crcp.filename); const unsigned int dataCRC = ar->GetCrc32(fid); crcp.nameCRC = nameCRC; crcp.dataCRC = dataCRC; #if !defined(DEDICATED) && !defined(UNITSYNC) Watchdog::ClearTimer(WDT_MAIN); #endif }); // Add file CRCs to the main archive CRC for (CRCPair& crcp: crcs) { crc.Update(crcp.nameCRC); crc.Update(crcp.dataCRC); #if !defined(DEDICATED) && !defined(UNITSYNC) Watchdog::ClearTimer(); #endif } // A value of 0 is used to indicate no crc.. so never return that // Shouldn't happen all that often unsigned int digest = crc.GetDigest(); if (digest == 0) digest = 4711; return digest; }
/** * Get CRC of the data in the specified archive. * Returns 0 if file could not be opened. */ unsigned int CArchiveScanner::GetCRC(const std::string& arcName) { CRC crc; struct CRCPair { std::string* filename; unsigned int nameCRC; unsigned int dataCRC; }; // try to open an archive std::unique_ptr<IArchive> ar(archiveLoader.OpenArchive(arcName)); if (ar == nullptr) return 0; // load ignore list, and insert all files to check in lowercase format std::unique_ptr<IFileFilter> ignore(CreateIgnoreFilter(ar.get())); std::vector<std::string> files; std::vector<CRCPair> crcs; files.reserve(ar->NumFiles()); crcs.reserve(ar->NumFiles()); for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) { const std::pair<std::string, int>& info = ar->FileInfo(fid); if (ignore->Match(info.first)) continue; // create case-insensitive hashes files.push_back(StringToLower(info.first)); } // sort by filename std::stable_sort(files.begin(), files.end()); for (std::string& f: files) { crcs.push_back(CRCPair{&f, 0, 0}); } // compute CRCs of the files // Hint: Multithreading only speedups `.sdd` loading. For those the CRC generation is extremely slow - // it has to load the full file to calc it! For the other formats (sd7, sdz, sdp) the CRC is saved // in the metainformation of the container and so the loading is much faster. Neither does any of our // current (2011) packing libraries support multithreading :/ for_mt(0, crcs.size(), [&](const int i) { CRCPair& crcp = crcs[i]; assert(crcp.filename == &files[i]); const unsigned int nameCRC = CRC::GetCRC(crcp.filename->data(), crcp.filename->size()); const unsigned fid = ar->FindFile(*crcp.filename); const unsigned int dataCRC = ar->GetCrc32(fid); crcp.nameCRC = nameCRC; crcp.dataCRC = dataCRC; #if !defined(DEDICATED) && !defined(UNITSYNC) Watchdog::ClearTimer(WDT_MAIN); #endif }); // Add file CRCs to the main archive CRC for (const CRCPair& crcp: crcs) { crc.Update(crcp.nameCRC); crc.Update(crcp.dataCRC); #if !defined(DEDICATED) && !defined(UNITSYNC) Watchdog::ClearTimer(); #endif } // A value of 0 is used to indicate no crc.. so never return that // Shouldn't happen all that often const unsigned int digest = crc.GetDigest(); return (digest == 0)? 4711: digest; }
/** * Retessellates the current terrain */ void CRoamMeshDrawer::Update() { //FIXME this retessellates with the current camera frustum, shadow pass and others don't have to see the same patches! // CCamera* cam = (inShadowPass)? camera: cam2; CCamera* cam = cam2; // Update Patch visibility Patch::UpdateVisibility(cam, roamPatches, numPatchesX); // Check if a retessellation is needed #define RETESSELLATE_MODE 1 bool retessellate = false; { SCOPED_TIMER("ROAM::ComputeVariance"); for (int i = 0; i < (numPatchesX * numPatchesY); ++i) { //FIXME multithread? Patch& p = roamPatches[i]; #if (RETESSELLATE_MODE == 2) if (p.IsVisible()) { if (patchVisGrid[i] == 0) { patchVisGrid[i] = 1; retessellate = true; } if (p.IsDirty()) { //FIXME don't retessellate on small heightmap changes? p.ComputeVariance(); retessellate = true; } } else { patchVisGrid[i] = 0; } #else if (char(p.IsVisible()) != patchVisGrid[i]) { patchVisGrid[i] = char(p.IsVisible()); retessellate = true; } if (p.IsVisible() && p.IsDirty()) { //FIXME don't retessellate on small heightmap changes? p.ComputeVariance(); retessellate = true; } #endif } } // Further conditions that can cause a retessellation #if (RETESSELLATE_MODE == 2) static const float maxCamDeltaDistSq = 500.0f * 500.0f; retessellate |= ((cam->GetPos() - lastCamPos).SqLength() > maxCamDeltaDistSq); #endif retessellate |= forceRetessellate; retessellate |= (lastGroundDetail != smfGroundDrawer->GetGroundDetail()); bool retessellateAgain = false; // Retessellate if (retessellate) { { SCOPED_TIMER("ROAM::Tessellate"); //FIXME this tessellates with current camera + viewRadius // so it doesn't retessellate patches that are e.g. only vis. in the shadow frustum Reset(); retessellateAgain = Tessellate(cam->GetPos(), smfGroundDrawer->GetGroundDetail()); } { SCOPED_TIMER("ROAM::GenerateIndexArray"); for_mt(0, roamPatches.size(), [&](const int i){ Patch* it = &roamPatches[i]; if (it->IsVisible()) { it->GenerateIndices(); } }); } { SCOPED_TIMER("ROAM::Upload"); for (std::vector<Patch>::iterator it = roamPatches.begin(); it != roamPatches.end(); ++it) { if (it->IsVisible()) { it->Upload(); } } } /*{ int tricount = 0; for (std::vector<Patch>::iterator it = roamPatches.begin(); it != roamPatches.end(); it++) { if (it->IsVisible()) { tricount += it->GetTriCount(); } } LOG_L(L_DEBUG, "ROAM dbg: Framechange, fram=%i tris=%i, viewrad=%i, cd=%f, camera=(%5.0f, %5.0f, %5.0f) camera2= (%5.0f, %5.0f, %5.0f)", globalRendering->drawFrame, tricount, smfGroundDrawer->viewRadius, (cam->pos - lastCamPos).SqLength();, camera->GetPos().x, camera->GetPos().y, camera->GetPos().z, cam2->pos.x, cam2->pos.y, cam2->pos.z ); }*/ lastGroundDetail = smfGroundDrawer->GetGroundDetail(); lastCamPos = cam->GetPos(); forceRetessellate = retessellateAgain; } }