void CModelAbstract::CalcSelectionBox() { if (m_CustomSelectionShape) { // custom shape switch(m_CustomSelectionShape->m_Type) { case CustomSelectionShape::BOX: { // create object-space bounds according to the information in the descriptor, and transform them to world-space. // the box is centered on the X and Z axes, but extends from 0 to its height on the Y axis. const float width = m_CustomSelectionShape->m_Size0; const float depth = m_CustomSelectionShape->m_Size1; const float height = m_CustomSelectionShape->m_Height; CBoundingBoxAligned bounds; bounds += CVector3D(-width/2.f, 0, -depth/2.f); bounds += CVector3D( width/2.f, height, depth/2.f); bounds.Transform(GetTransform(), m_SelectionBox); } break; case CustomSelectionShape::CYLINDER: { // TODO: unimplemented m_SelectionBox.SetEmpty(); LOGWARNING(L"[ModelAbstract] TODO: Cylinder selection boxes are not yet implemented. Use BOX or BOUNDS selection shapes instead."); } break; default: { m_SelectionBox.SetEmpty(); //LOGWARNING(L"[ModelAbstract] Unrecognized selection shape type: %ld", m_CustomSelectionShape->m_Type); debug_warn("[ModelAbstract] Unrecognized selection shape type"); } break; } } else { // standard method // Get the object-space bounds that should be used to construct this model (and its children)'s selection box CBoundingBoxAligned objBounds = GetObjectSelectionBoundsRec(); if (objBounds.IsEmpty()) { m_SelectionBox.SetEmpty(); // model does not wish to participate in selection return; } // Prevent the bounding box from extending through the terrain; clip the lower plane at Y=0 in object space. if (objBounds[1].Y > 0.f) // should always be the case, unless the models are defined really weirdly objBounds[0].Y = std::max(0.f, objBounds[0].Y); // transform object-space axis-aligned bounds to world-space arbitrary-aligned box objBounds.Transform(GetTransform(), m_SelectionBox); } }
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; }
CBoundingBoxOriented::CBoundingBoxOriented(const CBoundingBoxAligned& bound) { if (bound.IsEmpty()) { SetEmpty(); } else { bound.GetCentre(m_Center); // the axes of an AABB are the world-space axes m_Basis[0].X = 1.f; m_Basis[0].Y = 0.f; m_Basis[0].Z = 0.f; m_Basis[1].X = 0.f; m_Basis[1].Y = 1.f; m_Basis[1].Z = 0.f; m_Basis[2].X = 0.f; m_Basis[2].Y = 0.f; m_Basis[2].Z = 1.f; // element-wise division by two to get half sizes (remember, [1] and [0] are the max and min coord points) m_HalfSizes = (bound[1] - bound[0]) * 0.5f; } }
void SimRender::ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine) { overlayLine.m_Coords.clear(); if (bound.IsEmpty()) return; const CVector3D& pMin = bound[0]; const CVector3D& pMax = bound[1]; // floor square overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMin.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMin.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMin.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z); // roof square overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMax.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMax.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMax.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z); }
/////////////////////////////////////////////////////////////////////////////////////////////////// // 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()); // ShadowBound might have been empty to begin with, producing an empty result if (ShadowBound.IsEmpty()) { // no-op LightProjection.SetIdentity(); TextureMatrix = LightTransform; return; } // 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 accidentally 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 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; CMatrix3D lightToTex; 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; }
std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables) { CVector3D origin, dir; camera.BuildCameraRay(screenX, screenY, origin, dir); CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY); ENSURE(cmpRangeManager); std::vector<std::pair<float, entity_id_t> > hits; // (dist^2, entity) pairs const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable); for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) { entity_id_t ent = it->first; // Check if this entity is only selectable in Atlas if (!allowEditorSelectables && static_cast<ICmpSelectable*>(it->second)->IsEditorOnly()) continue; // Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld) if (cmpRangeManager->GetLosVisibility(ent, player) == ICmpRangeManager::VIS_HIDDEN) continue; CmpPtr<ICmpVisual> cmpVisual(simulation.GetSimContext(), ent); if (!cmpVisual) continue; CVector3D center; float tmin, tmax; CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox(); if (selectionBox.IsEmpty()) { if (!allowEditorSelectables) continue; // Fall back to using old AABB selection method for decals // see: http://trac.wildfiregames.com/ticket/1032 CBoundingBoxAligned aABBox = cmpVisual->GetBounds(); if (aABBox.IsEmpty()) continue; if (!aABBox.RayIntersect(origin, dir, tmin, tmax)) continue; aABBox.GetCentre(center); } else { if (!selectionBox.RayIntersect(origin, dir, tmin, tmax)) continue; center = selectionBox.m_Center; } // Find the perpendicular distance from the object's centre to the picker ray float dist2; CVector3D closest = origin + dir * (center - origin).Dot(dir); dist2 = (closest - center).LengthSquared(); hits.push_back(std::make_pair(dist2, ent)); } // Sort hits by distance std::sort(hits.begin(), hits.end()); // lexicographic comparison // Extract the entity IDs std::vector<entity_id_t> hitEnts; hitEnts.reserve(hits.size()); for (size_t i = 0; i < hits.size(); ++i) hitEnts.push_back(hits[i].second); return hitEnts; }
void TerrainRenderer::RenderTerrainOverlayTexture(CMatrix3D& textureMatrix) { #if CONFIG2_GLES #warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES UNUSED2(textureMatrix); #else ENSURE(m->phase == Phase_Render); std::vector<CPatchRData*>& visiblePatches = m->visiblePatches; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glMatrixMode(GL_TEXTURE); glLoadMatrixf(&textureMatrix._11); glMatrixMode(GL_MODELVIEW); CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("fixed:dummy", CShaderDefines()); dummyShader->Bind(); CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS|STREAM_POSTOUV0); dummyShader->Unbind(); // To make the overlay visible over water, render an additional map-sized // water-height patch CBoundingBoxAligned waterBounds; for (size_t i = 0; i < m->visiblePatches.size(); ++i) { CPatchRData* data = m->visiblePatches[i]; waterBounds += data->GetWaterBounds(); } if (!waterBounds.IsEmpty()) { float h = g_Renderer.GetWaterManager()->m_WaterHeight + 0.05f; // add a delta to avoid z-fighting float waterPos[] = { waterBounds[0].X, h, waterBounds[0].Z, waterBounds[1].X, h, waterBounds[0].Z, waterBounds[0].X, h, waterBounds[1].Z, waterBounds[1].X, h, waterBounds[1].Z }; glVertexPointer(3, GL_FLOAT, 3*sizeof(float), waterPos); glTexCoordPointer(3, GL_FLOAT, 3*sizeof(float), waterPos); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glDepthMask(1); glDisable(GL_BLEND); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); #endif }
std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables, int range) { PROFILE2("PickEntitiesAtPoint"); CVector3D origin, dir; camera.BuildCameraRay(screenX, screenY, origin, dir); CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY); ENSURE(cmpRangeManager); /* We try to approximate where the mouse is hovering by drawing a ray from * the center of the camera and through the mouse then taking the position * at which the ray intersects the terrain. */ // TODO: Do this smarter without being slow. CVector3D pos3d = camera.GetWorldCoordinates(screenX, screenY, true); // Change the position to 2D by removing the terrain height. CFixedVector2D pos(fixed::FromFloat(pos3d.X), fixed::FromFloat(pos3d.Z)); // Get a rough group of entities using our approximated origin. SpatialQueryArray ents; cmpRangeManager->GetSubdivision()->GetNear(ents, pos, entity_pos_t::FromInt(range)); // Filter for relevent entities and calculate precise distances. std::vector<std::pair<float, entity_id_t> > hits; // (dist^2, entity) pairs for (int i = 0; i < ents.size(); ++i) { CmpPtr<ICmpSelectable> cmpSelectable(simulation, ents[i]); if (!cmpSelectable) continue; CEntityHandle handle = cmpSelectable->GetEntityHandle(); // Check if this entity is only selectable in Atlas if (!allowEditorSelectables && cmpSelectable->IsEditorOnly()) continue; // Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld) if (cmpRangeManager->GetLosVisibility(handle, player) == ICmpRangeManager::VIS_HIDDEN) continue; CmpPtr<ICmpVisual> cmpVisual(handle); if (!cmpVisual) continue; CVector3D center; float tmin, tmax; CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox(); if (selectionBox.IsEmpty()) { if (!allowEditorSelectables) continue; // Fall back to using old AABB selection method for decals // see: http://trac.wildfiregames.com/ticket/1032 CBoundingBoxAligned aABBox = cmpVisual->GetBounds(); if (aABBox.IsEmpty()) continue; if (!aABBox.RayIntersect(origin, dir, tmin, tmax)) continue; aABBox.GetCentre(center); } else { if (!selectionBox.RayIntersect(origin, dir, tmin, tmax)) continue; center = selectionBox.m_Center; } // Find the perpendicular distance from the object's centre to the picker ray float dist2; CVector3D closest = origin + dir * (center - origin).Dot(dir); dist2 = (closest - center).LengthSquared(); hits.push_back(std::make_pair(dist2, ents[i])); } // Sort hits by distance std::sort(hits.begin(), hits.end()); // lexicographic comparison // Extract the entity IDs std::vector<entity_id_t> hitEnts; hitEnts.reserve(hits.size()); for (size_t i = 0; i < hits.size(); ++i) hitEnts.push_back(hits[i].second); return hitEnts; }