void Labels::update(const View& _view, float _dt, const std::vector<std::unique_ptr<Style>>& _styles, const std::vector<std::shared_ptr<Tile>>& _tiles) { m_needUpdate = false; // float zoom = _view.getZoom(); // int lodDiscard = LODDiscardFunc(View::s_maxZoom, zoom); // logMsg("loddiscard %f %d\n", zoom, lodDiscard); std::set<std::pair<Label*, Label*>> occlusions; // Could clear this at end of function unless debug draw is active m_labels.clear(); m_aabbs.clear(); glm::vec2 screenSize = glm::vec2(_view.getWidth(), _view.getHeight()); //// Collect labels from visible tiles for (const auto& tile : _tiles) { // discard based on level of detail // if ((zoom - tile->getID().z) > lodDiscard) { // logMsg("discard %d %d %d\n", tile->getID().z, tile->getID().x, tile->getID().y); // continue; // } glm::mat4 mvp = _view.getViewProjectionMatrix() * tile->getModelMatrix(); for (const auto& style : _styles) { const auto& mesh = tile->getMesh(*style); if (!mesh) { continue; } const LabelMesh* labelMesh = dynamic_cast<const LabelMesh*>(mesh.get()); if (!labelMesh) { continue; } for (auto& label : labelMesh->getLabels()) { m_needUpdate |= label->update(mvp, screenSize, _dt); if (label->canOcclude()) { m_aabbs.push_back(label->getAABB()); m_aabbs.back().m_userData = (void*)label.get(); } m_labels.push_back(label.get()); } } } //// Manage occlusions // broad phase auto pairs = intersect(m_aabbs, {4, 4}, {_view.getWidth(), _view.getHeight()}); for (auto pair : pairs) { const auto& aabb1 = m_aabbs[pair.first]; const auto& aabb2 = m_aabbs[pair.second]; auto l1 = (Label*)aabb1.m_userData; auto l2 = (Label*)aabb2.m_userData; // narrow phase if (intersect(l1->getOBB(), l2->getOBB())) { occlusions.insert({l1, l2}); } } for (auto& pair : occlusions) { if (!pair.first->occludedLastFrame() || !pair.second->occludedLastFrame()) { // lower numeric priority means higher priority if (pair.first->getOptions().priority < pair.second->getOptions().priority) { pair.second->setOcclusion(true); } else { pair.first->setOcclusion(true); } } } //// Update label meshes for (auto label : m_labels) { label->occlusionSolved(); label->pushTransform(); } // Request for render if labels are in fading in/out states if (m_needUpdate) { requestRender(); } }
MTV Collision::getCollision(const sf::Sprite& object1,Entity::ENTITY_SHAPE shape1,const sf::Sprite& object2,Entity::ENTITY_SHAPE shape2){ //Use Separating Axis Theorem to determine whether two objects are overlapping //See documentation for full explanation of this algorithm //Get the oriented bounding box in world coordinates (includes rotation) of objects OBB obb1 = getOBB(object1); OBB obb2 = getOBB(object2); float rot = object1.getRotation(); double overlap = LONG_MAX; maths::Vector2 smallest(0,0); std::vector<maths::Vector2> axis1; std::vector<maths::Vector2> axis2; maths::Vector2 circleCentre1; maths::Vector2 circleCentre2; sf::FloatRect gbounds1 = object1.getGlobalBounds(); sf::FloatRect gbounds2 = object2.getGlobalBounds(); //Find all the axes to check using SAT based on shapes of objects //Works with squares/rectangles or circles //Rectangles only need 2 axes because they have 2 sets of parallel lines if(shape1 == Entity::SHAPE_CIRCLE && shape2 == Entity::SHAPE_CIRCLE){ //If both shapes are circles, the only axis needed is the axis between centres of circles circleCentre1 = maths::Vector2(gbounds1.left + gbounds1.width / 2, gbounds1.top + gbounds1.height / 2); circleCentre2 = maths::Vector2(gbounds2.left + gbounds2.width / 2, gbounds2.top + gbounds2.height / 2); axis1.push_back(maths::Vector2(circleCentre1 - circleCentre2).normalise()); }else if(shape1 != shape2){ //if one shape is circle and one shape is rectangle maths::Vector2 circleCentre; sf::FloatRect squareRect; float rotation; //First get the unrotated bounding box and centre of the circle of the 2 objects if(shape1 == Entity::SHAPE_CIRCLE){ circleCentre = maths::Vector2(gbounds1.left + gbounds1.width / 2, gbounds1.top + gbounds1.height / 2); circleCentre1 = circleCentre; squareRect = getOriginalBoundingBox(object2); rotation = object2.getRotation(); }else if(shape2 == Entity::SHAPE_CIRCLE){ circleCentre = maths::Vector2(gbounds2.left + gbounds2.width / 2, gbounds2.top + gbounds2.height / 2); circleCentre2 = circleCentre; squareRect = getOriginalBoundingBox(object1); rotation = object1.getRotation(); } maths::Vector2 squareCentre(squareRect.left + squareRect.width / 2, squareRect.top + squareRect.height / 2); OBB* square = (shape1 == Entity::SHAPE_SQUARE ? &obb1 : &obb2); maths::Vector2 relativeCircleCentre = circleCentre.rotate(-rotation, squareCentre); //get circle centre in relation to the rotated square bool vertice = false; maths::Vector2 axis; maths::Vector2 topLeft(squareRect.left, squareRect.top); maths::Vector2 topRight(squareRect.left + squareRect.width, squareRect.top); maths::Vector2 botLeft(squareRect.left, squareRect.top + squareRect.height); maths::Vector2 botRight(squareRect.left + squareRect.width, squareRect.top + squareRect.height); //Get the closest vertex of the rectangle to the circle centre. //The axis to check is the vector between these 2 points. if(circleCentre.x < topLeft.x){ if(circleCentre.y < topLeft.y){ vertice = true; axis = topLeft; }else if(circleCentre.y > botLeft.y){ vertice = true; axis = botLeft; }else{ axis = maths::Vector2(topLeft - botLeft).normalise(); } }else if(circleCentre.x > topRight.x){ if(circleCentre.y < topLeft.y){ vertice = true; axis = topRight; }else if(circleCentre.y > botLeft.y){ vertice = true; axis = botRight; }else{ axis = maths::Vector2(topRight - botRight).normalise(); } }else{ if(circleCentre.y < topLeft.y){ axis = maths::Vector2(topRight - topLeft).normalise(); }else if(circleCentre.y > botLeft.y){ axis = maths::Vector2(botLeft - botRight).normalise(); }else{ //contains point! } } if(vertice){ axis1.push_back(maths::Vector2(circleCentre - axis).normalise()); }else{ axis1.push_back(maths::Vector2(topRight - topLeft).normalise()); axis1.push_back(maths::Vector2(topRight - botRight).normalise()); } }else{ //If both shapes are rectangles //Get vectors for sides of shapes maths::Vector2 Xside1(obb1.bot_left - obb1.bot_right); maths::Vector2 Yside1(obb1.top_left - obb1.bot_left); maths::Vector2 Xside2(obb2.bot_left - obb2.bot_right); maths::Vector2 Yside2(obb2.top_left - obb2.bot_left); //Axes requires are perpendicular to the sides of the shape //Vector2.perpendicular() normalises for greater accuracy axis1.push_back(Xside1.perpendicular()); axis1.push_back(Yside1.perpendicular()); axis2.push_back(Xside2.perpendicular()); axis2.push_back(Yside2.perpendicular()); } //We have all the axes to check. //Now find details on collisions with projections. for(int i=0;i<axis1.size();i++){ //Get projection of axis for both shapes maths::Vector2 axis = axis1[i]; Projection projection1 = project(obb1, axis); if(shape1 == Entity::SHAPE_CIRCLE){ float radius = gbounds1.width / 2; projection1 = projectCircle(circleCentre1, radius, axis); } Projection projection2 = project(obb2, axis); if (shape2 == Entity::SHAPE_CIRCLE){ float radius = gbounds2.width / 2; projection2 = projectCircle(circleCentre2, radius, axis); } //If a projection does not overlap, we know the objects do not collide so we can exit the function. //Otherwise, the MTV (minimum translation vector required to make the objects not collide) is calculated. if(!projection1.overlap(projection2)){ return MTV::NONE; }else{ //The axis with the smallest overlap is the axis used to calculate MTV, so record it. double o = projection1.getOverlap(projection2); if(o < overlap){ overlap = o; //set smallest overlap smallest = axis; //set smallest separation vector } } } //Repeat the same process as above with the other set of axes. for(int i=0;i<axis2.size();i++){ maths::Vector2 axis = axis2[i]; Projection projection1 = project(obb1, axis); if(shape1 == Entity::SHAPE_CIRCLE){ float radius = gbounds1.width/2; projection1 = projectCircle(circleCentre1, radius, axis); } Projection projection2 = project(obb2,axis); if(shape2 == Entity::SHAPE_CIRCLE){ float radius = gbounds2.width / 2; projection2 = projectCircle(circleCentre2, radius, axis); } if(!projection1.overlap(projection2)){ return MTV::NONE; }else{ double o = projection1.getOverlap(projection2); if(o < overlap){ overlap = o; smallest = axis; } } } //Get the vector from the centre of object 2 to the centre of object 1 maths::Vector2 centre1 = maths::Vector2(gbounds1.left + gbounds1.width / 2, gbounds1.top + gbounds1.height / 2); maths::Vector2 centre2 = maths::Vector2(gbounds2.left + gbounds2.width / 2, gbounds2.top + gbounds2.height / 2); maths::Vector2 between = centre1 - centre2; //If the separation vector is in the opposite direction of 'between', flip it round by negating it if(between.dot(smallest) < 0){ smallest = -smallest; } MTV mtv(overlap, smallest); return mtv; }