void CTextRenderer::Translate(float x, float y, float z) { CMatrix3D m; m.SetTranslation(x, y, z); m_Transform = m_Transform * m; m_Dirty = true; }
void SortModelRenderer::PrepareModels() { CMatrix3D worldToCam; if (m->models.size() == 0) return; g_Renderer.GetViewCamera().m_Orientation.GetInverse(worldToCam); for(std::vector<SModel*>::iterator it = m->models.begin(); it != m->models.end(); ++it) { SModel* smdl = *it; CModel* model = smdl->GetModel(); ENSURE(model->GetRenderData() == smdl); m->vertexRenderer->UpdateModelData(model, smdl->m_Data, smdl->m_UpdateFlags); smdl->m_UpdateFlags = 0; CVector3D modelpos = model->GetTransform().GetTranslation(); modelpos = worldToCam.Transform(modelpos); smdl->m_Distance = modelpos.Z; } PROFILE_START( "sorting transparent" ); std::sort(m->models.begin(), m->models.end(), SortModelsByDist()); PROFILE_END( "sorting transparent" ); }
void OverlayRenderer::RenderSphereOverlays() { PROFILE3_GPU("overlays (spheres)"); #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderSphereOverlays for GLES #else if (g_Renderer.GetRenderPath() != CRenderer::RP_SHADER) return; if (m->spheres.empty()) return; glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(0); glEnableClientState(GL_VERTEX_ARRAY); CShaderProgramPtr shader; CShaderTechniquePtr tech; tech = g_Renderer.GetShaderManager().LoadEffect(str_overlay_solid); tech->BeginPass(); shader = tech->GetShader(); m->GenerateSphere(); shader->VertexPointer(3, GL_FLOAT, 0, &m->sphereVertexes[0]); for (size_t i = 0; i < m->spheres.size(); ++i) { SOverlaySphere* sphere = m->spheres[i]; CMatrix3D transform; transform.SetIdentity(); transform.Scale(sphere->m_Radius, sphere->m_Radius, sphere->m_Radius); transform.Translate(sphere->m_Center); shader->Uniform(str_transform, transform); shader->Uniform(str_color, sphere->m_Color); glDrawElements(GL_TRIANGLES, m->sphereIndexes.size(), GL_UNSIGNED_SHORT, &m->sphereIndexes[0]); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3; } tech->EndPass(); glDisableClientState(GL_VERTEX_ARRAY); glDepthMask(1); glDisable(GL_BLEND); #endif }
void CTextRenderer::ResetTransform() { m_Transform.SetIdentity(); m_Transform.Scale(1.0f, -1.f, 1.0f); m_Transform.Translate(0.0f, (float)g_yres, -1000.0f); CMatrix3D proj; proj.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); m_Transform = proj * m_Transform; m_Dirty = true; }
void CModelParticleEmitter::SetTransform(const CMatrix3D& transform) { if (m_Transform == transform) return; m_Emitter->SetPosition(transform.GetTranslation()); m_Emitter->SetRotation(transform.GetRotation()); // call base class to set transform on this object CRenderableObject::SetTransform(transform); }
void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const { CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse(); CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f)); x = screenspace.m_X / screenspace.m_W; y = screenspace.m_Y / screenspace.m_W; x = (x + 1) * 0.5f * g_Renderer.GetWidth(); y = (1 - y) * 0.5f * g_Renderer.GetHeight(); }
void CModelDecal::SetTransform(const CMatrix3D& transform) { // Since decals are assumed to be horizontal and projected downwards // onto the terrain, use just the Y-axis rotation and the translation CMatrix3D newTransform; newTransform.SetYRotation(transform.GetYRotation() + m_Decal.m_Angle); newTransform.Translate(transform.GetTranslation()); CRenderableObject::SetTransform(newTransform); InvalidatePosition(); }
const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec() { CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary // now extend these bounds to include the props' selection bounds (if any) for (size_t i = 0; i < m_Props.size(); ++i) { const Prop& prop = m_Props[i]; if (prop.m_Hidden) continue; // prop is hidden from rendering, so it also shouldn't be used for selection CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec(); if (propSelectionBounds.IsEmpty()) continue; // submodel does not wish to participate in selection box, exclude it // We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added // to the bounds in our object-space. For that, we need the transform of the prop attachment point. // // We have the prop point information; however, it's not trivial to compute its exact location in our object-space // since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of // an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation // system and are quite opaque to use from the outside (see @ref ValidatePosition). // // However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of // our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's // world-space transform as either // // T' = T x B x O // or // T' = T x O // // where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local // offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to // (taking into account animation and everything). // // From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So, // all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily, // this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch. CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T' propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T' // Transform the prop's bounds into our object coordinate space CBoundingBoxAligned transformedPropSelectionBounds; propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds); objBounds += transformedPropSelectionBounds; } return objBounds; }
void CCinemaManager::DrawBars() const { int height = (float)g_xres / 2.39f; int shift = (g_yres - height) / 2; if (shift <= 0) return; #if CONFIG2_GLES #warning TODO : implement bars for GLES #else // Set up transform for GL bars glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBegin(GL_QUADS); glVertex2i(0, 0); glVertex2i(g_xres, 0); glVertex2i(g_xres, shift); glVertex2i(0, shift); glEnd(); glBegin(GL_QUADS); glVertex2i(0, g_yres - shift); glVertex2i(g_xres, g_yres - shift); glVertex2i(g_xres, g_yres); glVertex2i(0, g_yres); glEnd(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); // Restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif }
/////////////////////////////////////////////////////////////////// // Scissor rectangle of water patches CBoundingBoxAligned TerrainRenderer::ScissorWater(const CMatrix3D &viewproj) { CBoundingBoxAligned scissor; for (size_t i = 0; i < m->visiblePatches.size(); ++i) { CPatchRData* data = m->visiblePatches[i]; const CBoundingBoxAligned& waterBounds = data->GetWaterBounds(); if (waterBounds.IsEmpty()) continue; CVector4D v1 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f)); CVector4D v2 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f)); CVector4D v3 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f)); CVector4D v4 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f)); CBoundingBoxAligned screenBounds; #define ADDBOUND(v1, v2, v3, v4) \ if (v1[2] >= -v1[3]) \ screenBounds += CVector3D(v1[0], v1[1], v1[2]) * (1.0f / v1[3]); \ else \ { \ float t = v1[2] + v1[3]; \ if (v2[2] > -v2[3]) \ { \ CVector4D c2 = v1 + (v2 - v1) * (t / (t - (v2[2] + v2[3]))); \ screenBounds += CVector3D(c2[0], c2[1], c2[2]) * (1.0f / c2[3]); \ } \ if (v3[2] > -v3[3]) \ { \ CVector4D c3 = v1 + (v3 - v1) * (t / (t - (v3[2] + v3[3]))); \ screenBounds += CVector3D(c3[0], c3[1], c3[2]) * (1.0f / c3[3]); \ } \ if (v4[2] > -v4[3]) \ { \ CVector4D c4 = v1 + (v4 - v1) * (t / (t - (v4[2] + v4[3]))); \ screenBounds += CVector3D(c4[0], c4[1], c4[2]) * (1.0f / c4[3]); \ } \ } ADDBOUND(v1, v2, v3, v4); ADDBOUND(v2, v1, v3, v4); ADDBOUND(v3, v1, v2, v4); ADDBOUND(v4, v1, v2, v3); #undef ADDBOUND if (screenBounds[0].X >= 1.0f || screenBounds[1].X <= -1.0f || screenBounds[0].Y >= 1.0f || screenBounds[1].Y <= -1.0f) continue; scissor += screenBounds; } return CBoundingBoxAligned(CVector3D(clamp(scissor[0].X, -1.0f, 1.0f), clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f), CVector3D(clamp(scissor[1].X, -1.0f, 1.0f), clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f)); }
void CBoundingBoxAligned::Transform(const CMatrix3D& transform, CBoundingBoxOriented& result) const { // The idea is this: compute the corners of this bounding box, transform them according to the specified matrix, // then derive the box center, orientation vectors, and half-sizes. // TODO: this implementation can be further optimized; see Philip's comments on http://trac.wildfiregames.com/ticket/914 const CVector3D& pMin = m_Data[0]; const CVector3D& pMax = m_Data[1]; // Find the corners of these bounds. We only need some of the corners to derive the information we need, so let's // not actually compute all of them. The corners are numbered starting from the minimum position (m_Data[0]), going // counter-clockwise in the bottom plane, and then in the same order for the top plane (starting from the corner // that's directly above the minimum position). Hence, corner0 is pMin and corner6 is pMax, so we don't need to // custom-create those. CVector3D corner0; // corner0 is pMin, no need to copy it CVector3D corner1(pMax.X, pMin.Y, pMin.Z); CVector3D corner3(pMin.X, pMin.Y, pMax.Z); CVector3D corner4(pMin.X, pMax.Y, pMin.Z); CVector3D corner6; // corner6 is pMax, no need to copy it // transform corners to world space corner0 = transform.Transform(pMin); // = corner0 corner1 = transform.Transform(corner1); corner3 = transform.Transform(corner3); corner4 = transform.Transform(corner4); corner6 = transform.Transform(pMax); // = corner6 // Compute orientation vectors, half-size vector, and box center. We can get the orientation vectors by just taking // the directional vectors from a specific corner point (corner0) to the other corners, once in each direction. The // half-sizes are similarly computed by taking the distances of those sides and dividing them by 2. Finally, the // center is simply the middle between the transformed pMin and pMax corners. const CVector3D sideU(corner1 - corner0); const CVector3D sideV(corner4 - corner0); const CVector3D sideW(corner3 - corner0); result.m_Basis[0] = sideU.Normalized(); result.m_Basis[1] = sideV.Normalized(); result.m_Basis[2] = sideW.Normalized(); result.m_HalfSizes = CVector3D( sideU.Length()/2.f, sideV.Length()/2.f, sideW.Length()/2.f ); result.m_Center = (corner0 + corner6) * 0.5f; }
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating) { if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } float x, z, rotY; GetInterpolatedPosition2D(frameOffset, x, z, rotY); float baseY = 0; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) baseY = cmpTerrain->GetExactGroundLevel(x, z); if (m_Floating || forceFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z)); } } float y = baseY + m_YOffset.ToFloat(); // TODO: do something with m_AnchorType CMatrix3D m; CMatrix3D mXZ; float Cos = cosf(rotY); float Sin = sinf(rotY); m.SetIdentity(); m._11 = -Cos; m._13 = -Sin; m._31 = Sin; m._33 = -Cos; mXZ.SetIdentity(); mXZ.SetXRotation(m_RotX.ToFloat()); mXZ.RotateZ(m_RotZ.ToFloat()); // TODO: is this all done in the correct order? mXZ = m * mXZ; mXZ.Translate(CVector3D(x, y, z)); return mXZ; }
void TerrainTextureOverlay::RenderAfterWater() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile); ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile); pglActiveTextureARB(GL_TEXTURE0); // Recreate the texture with new size if necessary if (round_up_to_pow2(w) != m_TextureW || round_up_to_pow2(h) != m_TextureH) { m_TextureW = round_up_to_pow2(w); m_TextureH = round_up_to_pow2(h); glBindTexture(GL_TEXTURE_2D, m_Texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureW, m_TextureH, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } u8* data = (u8*)calloc(w * h, 4); BuildTextureRGBA(data, w, h); glBindTexture(GL_TEXTURE_2D, m_Texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data); free(data); CMatrix3D matrix; matrix.SetZero(); matrix._11 = m_TexelsPerTile / (m_TextureW * TERRAIN_TILE_SIZE); matrix._23 = m_TexelsPerTile / (m_TextureH * TERRAIN_TILE_SIZE); matrix._44 = 1; g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(matrix); }
//Render Manager. void CConsole::Render() { if (! (m_bVisible || m_bToggle) ) return; PROFILE3_GPU("console"); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); solidTech->BeginPass(); CShaderProgramPtr solidShader = solidTech->GetShader(); CMatrix3D transform = GetDefaultGuiMatrix(); // animation: slide in from top of screen const float DeltaY = (1.0f - m_fVisibleFrac) * m_fHeight; transform.PostTranslate(m_fX, m_fY - DeltaY, 0.0f); // move to window position solidShader->Uniform(str_transform, transform); DrawWindow(solidShader); solidTech->EndPass(); CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); textTech->BeginPass(); CTextRenderer textRenderer(textTech->GetShader()); textRenderer.Font(CStrIntern(CONSOLE_FONT)); textRenderer.SetTransform(transform); DrawHistory(textRenderer); DrawBuffer(textRenderer); textRenderer.Render(); textTech->EndPass(); glDisable(GL_BLEND); }
void CGameView::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m->ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m->ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m->ViewZoomDefault; m->PosX.SetValue(target.X - delta.X); m->PosY.SetValue(target.Y - delta.Y); m->PosZ.SetValue(target.Z - delta.Z); m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->Zoom.SetValue(m->ViewZoomDefault); FocusHeight(m, false); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; }
CMatrix3D GetDefaultGuiMatrix() { CMatrix3D m; m.SetIdentity(); m.Scale(1.0f, -1.f, 1.0f); m.Translate(0.0f, (float)g_yres, -1000.0f); CMatrix3D proj; proj.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); m = proj * m; return m; }
float PSModel::BackToFrontIndexSort(const CMatrix3D& worldToCam) { static std::vector<IntFloatPair> IndexSorter; CModelDefPtr mdef = m_Model->GetModelDef(); size_t numFaces = mdef->GetNumFaces(); const SModelFace* faces = mdef->GetFaces(); if (IndexSorter.size() < numFaces) IndexSorter.resize(numFaces); VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>(); CVector3D tmpvtx; for(size_t i = 0; i < numFaces; ++i) { tmpvtx = Position[faces[i].m_Verts[0]]; tmpvtx += Position[faces[i].m_Verts[1]]; tmpvtx += Position[faces[i].m_Verts[2]]; tmpvtx *= 1.0f/3.0f; tmpvtx = worldToCam.Transform(tmpvtx); float distsqrd = SQR(tmpvtx.X)+SQR(tmpvtx.Y)+SQR(tmpvtx.Z); IndexSorter[i].first = (int)i; IndexSorter[i].second = distsqrd; } std::sort(IndexSorter.begin(),IndexSorter.begin()+numFaces,SortFacesByDist()); // now build index list size_t idxidx = 0; for (size_t i = 0; i < numFaces; ++i) { const SModelFace& face = faces[IndexSorter[i].first]; m_Indices[idxidx++] = (u16)(face.m_Verts[0]); m_Indices[idxidx++] = (u16)(face.m_Verts[1]); m_Indices[idxidx++] = (u16)(face.m_Verts[2]); } return IndexSorter[0].second; }
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating) { if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } float x, z, rotY; GetInterpolatedPosition2D(frameOffset, x, z, rotY); float baseY = 0; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) baseY = cmpTerrain->GetExactGroundLevel(x, z); if (m_Floating || forceFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z)); } } float y = baseY + m_YOffset.ToFloat(); CMatrix3D m; // linear interpolation is good enough (for RotX/Z). // As you always stay close to zero angle. m.SetXRotation(Interpolate(m_LastInterpolatedRotX, m_InterpolatedRotX, frameOffset)); m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset)); m.RotateY(rotY + (float)M_PI); m.Translate(CVector3D(x, y, z)); return m; }
void SimRender::ConstructAxesMarker(const CMatrix3D& coordSystem, SOverlayLine& outX, SOverlayLine& outY, SOverlayLine& outZ) { outX.m_Coords.clear(); outY.m_Coords.clear(); outZ.m_Coords.clear(); outX.m_Color = CColor(1, 0, 0, .5f); // X axis; red outY.m_Color = CColor(0, 1, 0, .5f); // Y axis; green outZ.m_Color = CColor(0, 0, 1, .5f); // Z axis; blue outX.m_Thickness = 2; outY.m_Thickness = 2; outZ.m_Thickness = 2; CVector3D origin = coordSystem.GetTranslation(); outX.PushCoords(origin); outY.PushCoords(origin); outZ.PushCoords(origin); outX.PushCoords(origin + CVector3D(coordSystem(0,0), coordSystem(1,0), coordSystem(2,0))); outY.PushCoords(origin + CVector3D(coordSystem(0,1), coordSystem(1,1), coordSystem(2,1))); outZ.PushCoords(origin + CVector3D(coordSystem(0,2), coordSystem(1,2), coordSystem(2,2))); }
void AtlasViewGame::DrawOverlays() { #if CONFIG2_GLES #warning TODO: implement Atlas game overlays for GLES #else // Set up transform for overlays glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetIdentity(); transform.Scale(1.0f, -1.f, 1.0f); transform.Translate(0.0f, (float)g_yres, -1000.0f); CMatrix3D proj; proj.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); transform = proj * transform; glLoadMatrixf(&transform._11); if (m_BandboxArray.size() > 0) { glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); // Render bandbox as array of lines glVertexPointer(2, GL_FLOAT, sizeof(SBandboxVertex), &m_BandboxArray[0].x); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(SBandboxVertex), &m_BandboxArray[0].r); glDrawArrays(GL_LINES, 0, m_BandboxArray.size()); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif }
virtual CMatrix3D GetInterpolatedTransform(float frameOffset) { if (m_TurretParent != INVALID_ENTITY) { CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent); if (!cmpPosition) { LOGERROR("Turret with parent without position component"); CMatrix3D m; m.SetIdentity(); return m; } if (!cmpPosition->IsInWorld()) { LOGERROR("CCmpPosition::GetInterpolatedTransform called on turret entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } else { CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset); CMatrix3D ownTransformation = CMatrix3D(); ownTransformation.SetYRotation(m_InterpolatedRotY); ownTransformation.Translate(-m_TurretPosition.X.ToFloat(), m_TurretPosition.Y.ToFloat(), -m_TurretPosition.Z.ToFloat()); return parentTransformMatrix * ownTransformation; } } if (!m_InWorld) { LOGERROR("CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } float x, z, rotY; GetInterpolatedPosition2D(frameOffset, x, z, rotY); float baseY = 0; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); if (cmpTerrain) baseY = cmpTerrain->GetExactGroundLevel(x, z); if (m_Floating || m_ActorFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z)); } } float y = baseY + m_Y.ToFloat() + Interpolate(-1 * m_LastYDifference.ToFloat(), 0.f, frameOffset); CMatrix3D m; // linear interpolation is good enough (for RotX/Z). // As you always stay close to zero angle. m.SetXRotation(Interpolate(m_LastInterpolatedRotX, m_InterpolatedRotX, frameOffset)); m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset)); CVector3D pos(x, y, z); pos.Y += GetConstructionProgressOffset(pos); m.RotateY(rotY + (float)M_PI); m.Translate(pos); return m; }
// Render void CProfileViewer::RenderProfile() { if (!m->profileVisible) return; if (!m->path.size()) { m->profileVisible = false; return; } PROFILE3_GPU("profile viewer"); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); AbstractProfileTable* table = m->path[m->path.size() - 1]; const std::vector<ProfileColumn>& columns = table->GetColumns(); size_t numrows = table->GetNumberRows(); CStrIntern font_name("mono-stroke-10"); CFontMetrics font(font_name); int lineSpacing = font.GetLineSpacing(); // Render background GLint estimate_height; GLint estimate_width; estimate_width = 50; for(size_t i = 0; i < columns.size(); ++i) estimate_width += (GLint)columns[i].width; estimate_height = 3 + (GLint)numrows; if (m->path.size() > 1) estimate_height += 2; estimate_height = lineSpacing*estimate_height; CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); solidTech->BeginPass(); CShaderProgramPtr solidShader = solidTech->GetShader(); solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f); CMatrix3D transform = GetDefaultGuiMatrix(); solidShader->Uniform(str_transform, transform); float backgroundVerts[] = { (float)estimate_width, 0.0f, 0.0f, 0.0f, 0.0f, (float)estimate_height, 0.0f, (float)estimate_height, (float)estimate_width, (float)estimate_height, (float)estimate_width, 0.0f }; solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts); solidShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f); solidShader->Uniform(str_transform, transform); // Draw row backgrounds for (size_t row = 0; row < numrows; ++row) { if (row % 2) solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f); else solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f); float rowVerts[] = { -22.f, 2.f, estimate_width-22.f, 2.f, estimate_width-22.f, 2.f-lineSpacing, estimate_width-22.f, 2.f-lineSpacing, -22.f, 2.f-lineSpacing, -22.f, 2.f }; solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts); solidShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); transform.PostTranslate(0.0f, lineSpacing, 0.0f); solidShader->Uniform(str_transform, transform); } solidTech->EndPass(); // Print table and column titles CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); textTech->BeginPass(); CTextRenderer textRenderer(textTech->GetShader()); textRenderer.Font(font_name); textRenderer.Color(1.0f, 1.0f, 1.0f); textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str()); textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f); float colX = 0.0f; for (size_t col = 0; col < columns.size(); ++col) { CStrW text = columns[col].title.FromUTF8(); int w, h; font.CalculateStringSize(text.c_str(), w, h); float x = colX; if (col > 0) // right-align all but the first column x += columns[col].width - w; textRenderer.Put(x, 0.0f, text.c_str()); colX += columns[col].width; } textRenderer.Translate(0.0f, lineSpacing, 0.0f); // Print rows int currentExpandId = 1; for (size_t row = 0; row < numrows; ++row) { if (table->IsHighlightRow(row)) textRenderer.Color(1.0f, 0.5f, 0.5f); else textRenderer.Color(1.0f, 1.0f, 1.0f); if (table->GetChild(row)) { textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId); currentExpandId++; } float colX = 0.0f; for (size_t col = 0; col < columns.size(); ++col) { CStrW text = table->GetCellText(row, col).FromUTF8(); int w, h; font.CalculateStringSize(text.c_str(), w, h); float x = colX; if (col > 0) // right-align all but the first column x += columns[col].width - w; textRenderer.Put(x, 0.0f, text.c_str()); colX += columns[col].width; } textRenderer.Translate(0.0f, lineSpacing, 0.0f); } textRenderer.Color(1.0f, 1.0f, 1.0f); if (m->path.size() > 1) { textRenderer.Translate(0.0f, lineSpacing, 0.0f); textRenderer.Put(-15.0f, 0.0f, L"0"); textRenderer.Put(0.0f, 0.0f, L"back to parent"); } textRenderer.Render(); textTech->EndPass(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); }
void Render() { PROFILE3("render"); if (g_SoundManager) g_SoundManager->IdleTask(); ogl_WarnIfError(); g_Profiler2.RecordGPUFrameStart(); ogl_WarnIfError(); // prepare before starting the renderer frame if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->BeginFrame(); if (g_Game) g_Renderer.SetSimulation(g_Game->GetSimulation2()); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->Render(); ogl_WarnIfError(); g_Renderer.RenderTextOverlays(); if (g_DoRenderGui) g_GUI->Draw(); ogl_WarnIfError(); // If we're in Atlas game view, render special overlays (e.g. editor bandbox) if (g_AtlasGameLoop && g_AtlasGameLoop->view) { g_AtlasGameLoop->view->DrawOverlays(); ogl_WarnIfError(); } // Text: glDisable(GL_DEPTH_TEST); g_Console->Render(); ogl_WarnIfError(); if (g_DoRenderLogger) g_Logger->Render(); ogl_WarnIfError(); // Profile information g_ProfileViewer.RenderProfile(); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) if (g_DoRenderCursor) { PROFILE3_GPU("cursor"); CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, false); } else { bool forceGL = false; CFG_GET_VAL("nohwcursor", forceGL); #if CONFIG2_GLES #warning TODO: implement cursors for GLES #else // set up transform for GL cursor glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); #endif #if OS_ANDROID #warning TODO: cursors for Android #else if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, forceGL) < 0) LOGWARNING("Failed to draw cursor '%s'", utf8_from_wstring(cursorName)); #endif #if CONFIG2_GLES #warning TODO: implement cursors for GLES #else // restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif } } glEnable(GL_DEPTH_TEST); g_Renderer.EndFrame(); PROFILE2_ATTR("draw calls: %d", (int)g_Renderer.GetStats().m_DrawCalls); PROFILE2_ATTR("terrain tris: %d", (int)g_Renderer.GetStats().m_TerrainTris); PROFILE2_ATTR("water tris: %d", (int)g_Renderer.GetStats().m_WaterTris); PROFILE2_ATTR("model tris: %d", (int)g_Renderer.GetStats().m_ModelTris); PROFILE2_ATTR("overlay tris: %d", (int)g_Renderer.GetStats().m_OverlayTris); PROFILE2_ATTR("blend splats: %d", (int)g_Renderer.GetStats().m_BlendSplats); PROFILE2_ATTR("particles: %d", (int)g_Renderer.GetStats().m_Particles); ogl_WarnIfError(); g_Profiler2.RecordGPUFrameEnd(); ogl_WarnIfError(); }
// TODO: render the minimap in a framebuffer and just draw the frambuffer texture // most of the time, updating the framebuffer twice a frame. // Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling // (those operations cause a gpu sync, which slows down the way gpu works) void CMiniMap::Draw() { PROFILE3("render minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if (!(GetGUI() && g_Game && g_Game->IsGameStarted())) return; CSimulation2* sim = g_Game->GetSimulation2(); CmpPtr<ICmpRangeManager> cmpRangeManager(*sim, SYSTEM_ENTITY); ENSURE(cmpRangeManager); // Set our globals in case they hadn't been set before m_Camera = g_Game->GetView()->GetCamera(); m_Terrain = g_Game->GetWorld()->GetTerrain(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); if (!m_TerrainTexture || g_GameRestarted) CreateTextures(); // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation static double last_time; const double cur_time = timer_Time(); const bool doUpdate = cur_time - last_time > 0.5; if (doUpdate) { last_time = cur_time; if (m_TerrainDirty) RebuildTerrainTexture(); } const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float z = GetBufferedZ(); const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; const float angle = GetAngle(); const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f); // Disable depth updates to prevent apparent z-fighting-related issues // with some drivers causing units to get drawn behind the texture. glDepthMask(0); CShaderProgramPtr shader; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines); tech->BeginPass(); shader = tech->GetShader(); // Draw the main textured quad shader->BindTexture(str_baseTex, m_TerrainTexture); const CMatrix3D baseTransform = GetDefaultGuiMatrix(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, baseTextureTransform); DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z); // Draw territory boundaries glEnable(GL_BLEND); CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *territoryTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); // Draw the LOS quad in black, using alpha values from the LOS texture CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); CShaderDefines losDefines; losDefines.Add(str_MINIMAP_LOS, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines); tech->BeginPass(); shader = tech->GetShader(); shader->BindTexture(str_baseTex, losTexture.GetTexture()); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *losTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); glDisable(GL_BLEND); PROFILE_START("minimap units"); CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines); tech->BeginPass(); shader = tech->GetShader(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_pointSize, 3.f); CMatrix3D unitMatrix; unitMatrix.SetIdentity(); // Center the minimap on the origin of the axis of rotation. unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f); // Rotate the map. unitMatrix.RotateZ(angle); // Scale square maps to fit. unitMatrix.Scale(unitScale, unitScale, 1.f); // Move the minimap back to it's starting position. unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f); // Move the minimap to it's final location. unitMatrix.Translate(x, y, z); // Apply the gui matrix. unitMatrix *= GetDefaultGuiMatrix(); // Load the transform into the shader. shader->Uniform(str_transform, unitMatrix); const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap); if (doUpdate) { VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>(); VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector<MinimapUnitVertex> pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); if (cur_time > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = cur_time + m_HalfBlinkDuration; } entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast<ICmpMinimap*>(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetPlayerID()); if (vis != ICmpRangeManager::VIS_HIDDEN) { v.a = 255; v.x = posX.ToFloat() * sx; v.y = -posZ.ToFloat() * sy; // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { addVertex(v, attrColor, attrPos); ++m_EntitiesDrawn; } } } } // Add the pinged vertices at the end, so they are drawn on top for (size_t v = 0; v < pingingVertices.size(); ++v) { addVertex(pingingVertices[v], attrColor, attrPos); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); m_VertexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { #if !CONFIG2_GLES if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif u8* indexBase = m_IndexArray.Bind(); u8* base = m_VertexArray.Bind(); const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); g_Renderer.GetStats().m_DrawCalls++; CVertexBuffer::Unbind(); #if !CONFIG2_GLES if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif } tech->EndPass(); DrawViewRect(unitMatrix); PROFILE_END("minimap units"); // Reset depth mask glDepthMask(1); }
void CModelParticleEmitter::SetTransform(const CMatrix3D& transform) { m_Emitter->SetPosition(transform.GetTranslation()); }
void CGameView::Update(const float deltaRealTime) { // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) return; if (!g_app_has_focus) return; // TODO: this is probably not an ideal place for this, it should probably go // in a CCmpWaterManager or some such thing (once such a thing exists) if (!m->Game->m_Paused) g_Renderer.GetWaterManager()->m_WaterTexTimer += deltaRealTime; if (m->TrackManager.IsActive() && m->TrackManager.IsPlaying()) { if (! m->TrackManager.Update(deltaRealTime)) { // ResetCamera(); } return; } // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; if (HotkeyIsPressed("camera.rotate.cw")) m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.ccw")) m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.up")) m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.down")) m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m->ViewDragSpeed * mouse_dx; moveForward += m->ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m->ViewScrollSpeed * deltaRealTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.up")) moveForward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.down")) moveForward -= m->ViewScrollSpeed * deltaRealTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime; moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime; m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime); m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m->FollowEntity = INVALID_ENTITY; float s = sin(m->RotateY.GetSmoothedValue()); float c = cos(m->RotateY.GetSmoothedValue()); m->PosX.AddSmoothly(c * moveRightward); m->PosZ.AddSmoothly(-s * moveRightward); m->PosX.AddSmoothly(s * moveForward); m->PosZ.AddSmoothly(c * moveForward); } if (m->FollowEntity) { CmpPtr<ICmpPosition> cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity); if (cmpPosition && cmpPosition->IsInWorld()) { // Get the most recent interpolated position float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset, false); CVector3D pos = transform.GetTranslation(); if (m->FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m->ViewCamera.m_Orientation.SetIdentity(); m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f); m->ViewCamera.m_Orientation.RotateY(angle); m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); m->ViewCamera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pos - pivot; m->PosX.AddSmoothly(delta.X); m->PosY.AddSmoothly(delta.Y); m->PosZ.AddSmoothly(delta.Z); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m->FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime); if (HotkeyIsPressed("camera.zoom.out")) m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime); if (m->ConstrainCamera) m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax); float zoomDelta = -m->Zoom.Update(deltaRealTime); if (zoomDelta) { CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); m->PosX.AddSmoothly(forwards.X * zoomDelta); m->PosY.AddSmoothly(forwards.Y * zoomDelta); m->PosZ.AddSmoothly(forwards.Z * zoomDelta); } if (m->ConstrainCamera) m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax)); FocusHeight(m, true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m->ConstrainCamera) { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr<ICmpRangeManager> cmpRangeManager(*m->Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpRangeManager && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; CVector3D center(r, desiredPivot.Y, r); float dist = (desiredPivot - center).Length(); if (dist > r - CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin m->PosX.SetValueSmoothly(desiredPivot.X + delta.X); m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); } m->PosX.Update(deltaRealTime); m->PosY.Update(deltaRealTime); m->PosZ.Update(deltaRealTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateYDelta = m->RotateY.Update(deltaRealTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateXDelta = m->RotateX.Update(deltaRealTime); if (rotateXDelta) { CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f; CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m->ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m->RotateX.GetValue()); target.RotateY(m->RotateY.GetValue()); target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m->PosY.AddSmoothly(limit - nearPoint.Y); } */ m->RotateY.Wrap(-(float)M_PI, (float)M_PI); // Update the camera matrix m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); }
void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) { if (m->submissions[cullGroup].empty()) return; CMatrix3D worldToCam; g_Renderer.GetViewCamera().m_Orientation.GetInverse(worldToCam); /* * Rendering approach: * * m->submissions contains the list of CModels to render. * * The data we need to render a model is: * - CShaderTechnique * - CTexture * - CShaderUniforms * - CModelDef (mesh data) * - CModel (model instance data) * * For efficient rendering, we need to batch the draw calls to minimise state changes. * (Uniform and texture changes are assumed to be cheaper than binding new mesh data, * and shader changes are assumed to be most expensive.) * First, group all models that share a technique to render them together. * Within those groups, sub-group by CModelDef. * Within those sub-groups, sub-sub-group by CTexture. * Within those sub-sub-groups, sub-sub-sub-group by CShaderUniforms. * * Alpha-blended models have to be sorted by distance from camera, * then we can batch as long as the order is preserved. * Non-alpha-blended models can be arbitrarily reordered to maximise batching. * * For each model, the CShaderTechnique is derived from: * - The current global 'context' defines * - The CModel's material's defines * - The CModel's material's shader effect name * * There are a smallish number of materials, and a smaller number of techniques. * * To minimise technique lookups, we first group models by material, * in 'materialBuckets' (a hash table). * * For each material bucket we then look up the appropriate shader technique. * If the technique requires sort-by-distance, the model is added to the * 'sortByDistItems' list with its computed distance. * Otherwise, the bucket's list of models is sorted by modeldef+texture+uniforms, * then the technique and model list is added to 'techBuckets'. * * 'techBuckets' is then sorted by technique, to improve batching when multiple * materials map onto the same technique. * * (Note that this isn't perfect batching: we don't sort across models in * multiple buckets that share a technique. In practice that shouldn't reduce * batching much (we rarely have one mesh used with multiple materials), * and it saves on copying and lets us sort smaller lists.) * * Extra tech buckets are added for the sorted-by-distance models without reordering. * Finally we render by looping over each tech bucket, then looping over the model * list in each, rebinding the GL state whenever it changes. */ Allocators::DynamicArena arena(256 * KiB); typedef ProxyAllocator<CModel*, Allocators::DynamicArena> ModelListAllocator; typedef std::vector<CModel*, ModelListAllocator> ModelList_t; typedef boost::unordered_map<SMRMaterialBucketKey, ModelList_t, SMRMaterialBucketKeyHash, std::equal_to<SMRMaterialBucketKey>, ProxyAllocator<std::pair<const SMRMaterialBucketKey, ModelList_t>, Allocators::DynamicArena> > MaterialBuckets_t; MaterialBuckets_t materialBuckets((MaterialBuckets_t::allocator_type(arena))); { PROFILE3("bucketing by material"); for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) { CModel* model = m->submissions[cullGroup][i]; uint32_t condFlags = 0; const CShaderConditionalDefines& condefs = model->GetMaterial().GetConditionalDefines(); for (size_t j = 0; j < condefs.GetSize(); ++j) { const CShaderConditionalDefines::CondDefine& item = condefs.GetItem(j); int type = item.m_CondType; switch (type) { case DCOND_DISTANCE: { CVector3D modelpos = model->GetTransform().GetTranslation(); float dist = worldToCam.Transform(modelpos).Z; float dmin = item.m_CondArgs[0]; float dmax = item.m_CondArgs[1]; if ((dmin < 0 || dist >= dmin) && (dmax < 0 || dist < dmax)) condFlags |= (1 << j); break; } } } CShaderDefines defs = model->GetMaterial().GetShaderDefines(condFlags); SMRMaterialBucketKey key(model->GetMaterial().GetShaderEffect(), defs); MaterialBuckets_t::iterator it = materialBuckets.find(key); if (it == materialBuckets.end()) { std::pair<MaterialBuckets_t::iterator, bool> inserted = materialBuckets.insert( std::make_pair(key, ModelList_t(ModelList_t::allocator_type(arena)))); inserted.first->second.reserve(32); inserted.first->second.push_back(model); } else { it->second.push_back(model); } } } typedef ProxyAllocator<SMRSortByDistItem, Allocators::DynamicArena> SortByDistItemsAllocator; std::vector<SMRSortByDistItem, SortByDistItemsAllocator> sortByDistItems((SortByDistItemsAllocator(arena))); typedef ProxyAllocator<CShaderTechniquePtr, Allocators::DynamicArena> SortByTechItemsAllocator; std::vector<CShaderTechniquePtr, SortByTechItemsAllocator> sortByDistTechs((SortByTechItemsAllocator(arena))); // indexed by sortByDistItems[i].techIdx // (which stores indexes instead of CShaderTechniquePtr directly // to avoid the shared_ptr copy cost when sorting; maybe it'd be better // if we just stored raw CShaderTechnique* and assumed the shader manager // will keep it alive long enough) typedef ProxyAllocator<SMRTechBucket, Allocators::DynamicArena> TechBucketsAllocator; std::vector<SMRTechBucket, TechBucketsAllocator> techBuckets((TechBucketsAllocator(arena))); { PROFILE3("processing material buckets"); for (MaterialBuckets_t::iterator it = materialBuckets.begin(); it != materialBuckets.end(); ++it) { CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(it->first.effect, context, it->first.defines); // Skip invalid techniques (e.g. from data file errors) if (!tech) continue; if (tech->GetSortByDistance()) { // Add the tech into a vector so we can index it // (There might be duplicates in this list, but that doesn't really matter) if (sortByDistTechs.empty() || sortByDistTechs.back() != tech) sortByDistTechs.push_back(tech); size_t techIdx = sortByDistTechs.size()-1; // Add each model into sortByDistItems for (size_t i = 0; i < it->second.size(); ++i) { SMRSortByDistItem itemWithDist; itemWithDist.techIdx = techIdx; CModel* model = it->second[i]; itemWithDist.model = model; CVector3D modelpos = model->GetTransform().GetTranslation(); itemWithDist.dist = worldToCam.Transform(modelpos).Z; sortByDistItems.push_back(itemWithDist); } } else { // Sort model list by modeldef+texture, for batching // TODO: This only sorts by base texture. While this is an OK approximation // for most cases (as related samplers are usually used together), it would be better // to take all the samplers into account when sorting here. std::sort(it->second.begin(), it->second.end(), SMRBatchModel()); // Add a tech bucket pointing at this model list SMRTechBucket techBucket = { tech, &it->second[0], it->second.size() }; techBuckets.push_back(techBucket); } } } { PROFILE3("sorting tech buckets"); // Sort by technique, for better batching std::sort(techBuckets.begin(), techBuckets.end(), SMRCompareTechBucket()); } // List of models corresponding to sortByDistItems[i].model // (This exists primarily because techBuckets wants a CModel**; // we could avoid the cost of copying into this list by adding // a stride length into techBuckets and not requiring contiguous CModel*s) std::vector<CModel*, ModelListAllocator> sortByDistModels((ModelListAllocator(arena))); if (!sortByDistItems.empty()) { { PROFILE3("sorting items by dist"); std::sort(sortByDistItems.begin(), sortByDistItems.end(), SMRCompareSortByDistItem()); } { PROFILE3("batching dist-sorted items"); sortByDistModels.reserve(sortByDistItems.size()); // Find runs of distance-sorted models that share a technique, // and create a new tech bucket for each run size_t start = 0; // start of current run size_t currentTechIdx = sortByDistItems[start].techIdx; for (size_t end = 0; end < sortByDistItems.size(); ++end) { sortByDistModels.push_back(sortByDistItems[end].model); size_t techIdx = sortByDistItems[end].techIdx; if (techIdx != currentTechIdx) { // Start of a new run - push the old run into a new tech bucket SMRTechBucket techBucket = { sortByDistTechs[currentTechIdx], &sortByDistModels[start], end-start }; techBuckets.push_back(techBucket); start = end; currentTechIdx = techIdx; } } // Add the tech bucket for the final run SMRTechBucket techBucket = { sortByDistTechs[currentTechIdx], &sortByDistModels[start], sortByDistItems.size()-start }; techBuckets.push_back(techBucket); } } { PROFILE3("rendering bucketed submissions"); size_t idxTechStart = 0; // This vector keeps track of texture changes during rendering. It is kept outside the // loops to avoid excessive reallocations. The token allocation of 64 elements // should be plenty, though it is reallocated below (at a cost) if necessary. typedef ProxyAllocator<CTexture*, Allocators::DynamicArena> TextureListAllocator; std::vector<CTexture*, TextureListAllocator> currentTexs((TextureListAllocator(arena))); currentTexs.reserve(64); // texBindings holds the identifier bindings in the shader, which can no longer be defined // statically in the ShaderRenderModifier class. texBindingNames uses interned strings to // keep track of when bindings need to be reevaluated. typedef ProxyAllocator<CShaderProgram::Binding, Allocators::DynamicArena> BindingListAllocator; std::vector<CShaderProgram::Binding, BindingListAllocator> texBindings((BindingListAllocator(arena))); texBindings.reserve(64); typedef ProxyAllocator<CStrIntern, Allocators::DynamicArena> BindingNamesListAllocator; std::vector<CStrIntern, BindingNamesListAllocator> texBindingNames((BindingNamesListAllocator(arena))); texBindingNames.reserve(64); while (idxTechStart < techBuckets.size()) { CShaderTechniquePtr currentTech = techBuckets[idxTechStart].tech; // Find runs [idxTechStart, idxTechEnd) in techBuckets of the same technique size_t idxTechEnd; for (idxTechEnd = idxTechStart + 1; idxTechEnd < techBuckets.size(); ++idxTechEnd) { if (techBuckets[idxTechEnd].tech != currentTech) break; } // For each of the technique's passes, render all the models in this run for (int pass = 0; pass < currentTech->GetNumPasses(); ++pass) { currentTech->BeginPass(pass); const CShaderProgramPtr& shader = currentTech->GetShader(pass); int streamflags = shader->GetStreamFlags(); modifier->BeginPass(shader); m->vertexRenderer->BeginPass(streamflags); // When the shader technique changes, textures need to be // rebound, so ensure there are no remnants from the last pass. // (the vector size is set to 0, but memory is not freed) currentTexs.clear(); texBindings.clear(); texBindingNames.clear(); CModelDef* currentModeldef = NULL; CShaderUniforms currentStaticUniforms; for (size_t idx = idxTechStart; idx < idxTechEnd; ++idx) { CModel** models = techBuckets[idx].models; size_t numModels = techBuckets[idx].numModels; for (size_t i = 0; i < numModels; ++i) { CModel* model = models[i]; if (flags && !(model->GetFlags() & flags)) continue; const CMaterial::SamplersVector& samplers = model->GetMaterial().GetSamplers(); size_t samplersNum = samplers.size(); // make sure the vectors are the right virtual sizes, and also // reallocate if there are more samplers than expected. if (currentTexs.size() != samplersNum) { currentTexs.resize(samplersNum, NULL); texBindings.resize(samplersNum, CShaderProgram::Binding()); texBindingNames.resize(samplersNum, CStrIntern()); // ensure they are definitely empty std::fill(texBindings.begin(), texBindings.end(), CShaderProgram::Binding()); std::fill(currentTexs.begin(), currentTexs.end(), (CTexture*)NULL); std::fill(texBindingNames.begin(), texBindingNames.end(), CStrIntern()); } // bind the samplers to the shader for (size_t s = 0; s < samplersNum; ++s) { const CMaterial::TextureSampler& samp = samplers[s]; CShaderProgram::Binding bind = texBindings[s]; // check that the handles are current // and reevaluate them if necessary if (texBindingNames[s] == samp.Name && bind.Active()) { bind = texBindings[s]; } else { bind = shader->GetTextureBinding(samp.Name); texBindings[s] = bind; texBindingNames[s] = samp.Name; } // same with the actual sampler bindings CTexture* newTex = samp.Sampler.get(); if (bind.Active() && newTex != currentTexs[s]) { shader->BindTexture(bind, samp.Sampler->GetHandle()); currentTexs[s] = newTex; } } // Bind modeldef when it changes CModelDef* newModeldef = model->GetModelDef().get(); if (newModeldef != currentModeldef) { currentModeldef = newModeldef; m->vertexRenderer->PrepareModelDef(shader, streamflags, *currentModeldef); } // Bind all uniforms when any change CShaderUniforms newStaticUniforms = model->GetMaterial().GetStaticUniforms(); if (newStaticUniforms != currentStaticUniforms) { currentStaticUniforms = newStaticUniforms; currentStaticUniforms.BindUniforms(shader); } const CShaderRenderQueries& renderQueries = model->GetMaterial().GetRenderQueries(); for (size_t q = 0; q < renderQueries.GetSize(); q++) { CShaderRenderQueries::RenderQuery rq = renderQueries.GetItem(q); if (rq.first == RQUERY_TIME) { CShaderProgram::Binding binding = shader->GetUniformBinding(rq.second); if (binding.Active()) { double time = g_Renderer.GetTimeManager().GetGlobalTime(); shader->Uniform(binding, time, 0,0,0); } } else if (rq.first == RQUERY_WATER_TEX) { WaterManager* WaterMgr = g_Renderer.GetWaterManager(); double time = WaterMgr->m_WaterTexTimer; double period = 1.6; int curTex = (int)(time*60/period) % 60; if (WaterMgr->m_RenderWater && WaterMgr->WillRenderFancyWater()) shader->BindTexture(str_waterTex, WaterMgr->m_NormalMap[curTex]); else shader->BindTexture(str_waterTex, g_Renderer.GetTextureManager().GetErrorTexture()); } else if (rq.first == RQUERY_SKY_CUBE) { shader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube()); } } modifier->PrepareModel(shader, model); CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData()); ENSURE(rdata->GetKey() == m->vertexRenderer.get()); m->vertexRenderer->RenderModel(shader, streamflags, model, rdata); } } m->vertexRenderer->EndPass(streamflags); currentTech->EndPass(pass); } idxTechStart = idxTechEnd; } } }
// Simplistic implementation of the Scene interface virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { if (GroundEnabled) { for (ssize_t pj = 0; pj < Terrain.GetPatchesPerSide(); ++pj) for (ssize_t pi = 0; pi < Terrain.GetPatchesPerSide(); ++pi) c->Submit(Terrain.GetPatch(pi, pj)); } CmpPtr<ICmpVisual> cmpVisual(Simulation2, Entity); if (cmpVisual) { // add selection box outlines manually if (SelectionBoxEnabled) { SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue SelectionBoxOverlay.m_Thickness = 2; SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay); c->Submit(&SelectionBoxOverlay); } // add origin axis thingy if (AxesMarkerEnabled) { CMatrix3D worldSpaceAxes; // offset from the ground a little bit to prevent fighting with the floor texture (also note: SetTranslation // sets the identity 3x3 transformation matrix, which are the world axes) worldSpaceAxes.SetTranslation(cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0)); SimRender::ConstructAxesMarker(worldSpaceAxes, AxesMarkerOverlays[0], AxesMarkerOverlays[1], AxesMarkerOverlays[2]); c->Submit(&AxesMarkerOverlays[0]); c->Submit(&AxesMarkerOverlays[1]); c->Submit(&AxesMarkerOverlays[2]); } // add prop point overlays if (PropPointsMode > 0 && Props.size() > 0) { PropPointOverlays.clear(); // doesn't clear capacity, but should be ok since the number of prop points is usually pretty limited for (size_t i = 0; i < Props.size(); ++i) { CModel::Prop& prop = Props[i]; if (prop.m_Model) // should always be the case { // prop point positions are automatically updated during animations etc. by CModel::ValidatePosition const CMatrix3D& propCoordSystem = prop.m_Model->GetTransform(); SOverlayLine pointGimbal; pointGimbal.m_Color = CColor(1.f, 0.f, 1.f, 1.f); SimRender::ConstructGimbal(propCoordSystem.GetTranslation(), 0.05f, pointGimbal); PropPointOverlays.push_back(pointGimbal); if (PropPointsMode > 1) { // scale the prop axes coord system down a bit to distinguish them from the main world-space axes markers CMatrix3D displayCoordSystem = propCoordSystem; displayCoordSystem.Scale(0.5f, 0.5f, 0.5f); // revert translation scaling displayCoordSystem._14 = propCoordSystem._14; displayCoordSystem._24 = propCoordSystem._24; displayCoordSystem._34 = propCoordSystem._34; // construct an XYZ axes marker for the prop's coordinate system SOverlayLine xAxis, yAxis, zAxis; SimRender::ConstructAxesMarker(displayCoordSystem, xAxis, yAxis, zAxis); PropPointOverlays.push_back(xAxis); PropPointOverlays.push_back(yAxis); PropPointOverlays.push_back(zAxis); } } } for (size_t i = 0; i < PropPointOverlays.size(); ++i) { c->Submit(&PropPointOverlays[i]); } } } // send a RenderSubmit message so the components can submit their visuals to the renderer Simulation2.RenderSubmit(*c, frustum, false); }
/////////////////////////////////////////////////////////////////////////////////////////////////// // CalcShadowMatrices: calculate required matrices for shadow map generation - the light's // projection and transformation matrices void ShadowMapInternals::CalcShadowMatrices() { CRenderer& renderer = g_Renderer; float minZ = ShadowBound[0].Z; ShadowBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); // round off the shadow boundaries to sane increments to help reduce swim effect float boundInc = 16.0f; ShadowBound[0].X = floor(ShadowBound[0].X / boundInc) * boundInc; ShadowBound[0].Y = floor(ShadowBound[0].Y / boundInc) * boundInc; ShadowBound[1].X = ceil(ShadowBound[1].X / boundInc) * boundInc; ShadowBound[1].Y = ceil(ShadowBound[1].Y / boundInc) * boundInc; // minimum Z bound must not be clipped too much, because objects that lie outside // the shadow bounds cannot cast shadows either // the 2.0 is rather arbitrary: it should be big enough so that we won't accidently miss // a shadow generator, and small enough not to affect Z precision ShadowBound[0].Z = minZ - 2.0; // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering CVector3D scale = ShadowBound[1] - ShadowBound[0]; CVector3D shift = (ShadowBound[1] + ShadowBound[0]) * -0.5; if (scale.X < 1.0) scale.X = 1.0; if (scale.Y < 1.0) scale.Y = 1.0; if (scale.Z < 1.0) scale.Z = 1.0; scale.X = 2.0 / scale.X; scale.Y = 2.0 / scale.Y; scale.Z = 2.0 / scale.Z; // make sure a given world position falls on a consistent shadowmap texel fractional offset float offsetX = fmod(ShadowBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth)); float offsetY = fmod(ShadowBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight)); LightProjection.SetZero(); LightProjection._11 = scale.X; LightProjection._14 = (shift.X + offsetX) * scale.X; LightProjection._22 = scale.Y; LightProjection._24 = (shift.Y + offsetY) * scale.Y; LightProjection._33 = scale.Z; LightProjection._34 = shift.Z * scale.Z + renderer.m_ShadowZBias; LightProjection._44 = 1.0; // Calculate texture matrix by creating the clip space to texture coordinate matrix // and then concatenating all matrices that have been calculated so far CMatrix3D lightToTex; float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width; float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height; float texscalez = scale.Z * 0.5f; lightToTex.SetZero(); lightToTex._11 = texscalex; lightToTex._14 = (offsetX - ShadowBound[0].X) * texscalex; lightToTex._22 = texscaley; lightToTex._24 = (offsetY - ShadowBound[0].Y) * texscaley; lightToTex._33 = texscalez; lightToTex._34 = -ShadowBound[0].Z * texscalez; lightToTex._44 = 1.0; TextureMatrix = lightToTex * LightTransform; }
///////////////////////////////////////////////////////////////////////////////////////////////////////////// // ValidatePosition: ensure that current transform and bone matrices are both uptodate void CModel::ValidatePosition() { if (m_PositionValid) { ENSURE(!m_Parent || m_Parent->m_PositionValid); return; } if (m_Parent && !m_Parent->m_PositionValid) { // Make sure we don't base our calculations on // a parent animation state that is out of date. m_Parent->ValidatePosition(); // Parent will recursively call our validation. ENSURE(m_PositionValid); return; } if (m_Anim && m_BoneMatrices) { // PROFILE( "generating bone matrices" ); ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys()); m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION)); } else if (m_BoneMatrices) { // Bones but no animation - probably a buggy actor forgot to set up the animation, // so just render it in its bind pose for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) { m_BoneMatrices[i].SetIdentity(); m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation); m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation); } } // For CPU skinning, we precompute as much as possible so that the only // per-vertex work is a single matrix*vec multiplication. // For GPU skinning, we try to minimise CPU work by doing most computation // in the vertex shader instead. // Using g_Renderer.m_Options to detect CPU vs GPU is a bit hacky, // and this doesn't allow the setting to change at runtime, but there isn't // an obvious cleaner way to determine what data needs to be computed, // and GPU skinning is a rarely-used experimental feature anyway. bool worldSpaceBoneMatrices = !g_Renderer.m_Options.m_GPUSkinning; bool computeBlendMatrices = !g_Renderer.m_Options.m_GPUSkinning; if (m_BoneMatrices && worldSpaceBoneMatrices) { // add world-space transformation to m_BoneMatrices const CMatrix3D transform = GetTransform(); for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) m_BoneMatrices[i].Concatenate(transform); } // our own position is now valid; now we can safely update our props' positions without fearing // that doing so will cause a revalidation of this model (see recursion above). m_PositionValid = true; // re-position and validate all props for (size_t j = 0; j < m_Props.size(); ++j) { const Prop& prop=m_Props[j]; CMatrix3D proptransform = prop.m_Point->m_Transform;; if (prop.m_Point->m_BoneIndex != 0xff) { CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex]; if (!worldSpaceBoneMatrices) boneMatrix.Concatenate(GetTransform()); proptransform.Concatenate(boneMatrix); } else { // not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin) proptransform.Concatenate(m_Transform); } prop.m_Model->SetTransform(proptransform); prop.m_Model->ValidatePosition(); } if (m_BoneMatrices) { for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) { m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i]; } // Note: there is a special case of joint influence, in which the vertex // is influenced by the bind-shape transform instead of a particular bone, // which we indicate with the blending bone ID set to the total number // of bones. But since we're skinning in world space, we use the model's // world space transform and store that matrix in this special index. // (see http://trac.wildfiregames.com/ticket/1012) m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; if (computeBlendMatrices) m_pModelDef->BlendBoneMatrices(m_BoneMatrices); } }