Пример #1
0
void OcclusionBaker::bakeTexture(cocos2d::RenderTexture* tex,
                                  const cocos2d::Vec2& worldPos,
                                  const cocos2d::Vec2& scaleFactor,
                                  const std::vector<realtrick::Polygon>& polygons,
                                  const cocos2d::Size& boundarySize,
                                  const FieldOfView& fov)
{
    _eyePos = worldPos;
    
    // 경계면 정점을 만든다.
    realtrick::Polygon clipBoundary;
    
    //
    // (v2)               (v3)
    //   . --------------- .
    //   |     worldPos    |
    //   |        .        |
    //   |                 |
    //   . --------------- .
    // (v1)               (v4)
    //
    const int PAD = 0;
    Vec2 v1 = Vec2(worldPos.x - boundarySize.width / 2 + PAD, worldPos.y - boundarySize.height / 2 + PAD); // left bottom
    Vec2 v2 = Vec2(worldPos.x - boundarySize.width / 2 + PAD, worldPos.y + boundarySize.height / 2 - PAD); // left top
    Vec2 v3 = Vec2(worldPos.x + boundarySize.width / 2 - PAD, worldPos.y + boundarySize.height / 2 - PAD); // right top
    Vec2 v4 = Vec2(worldPos.x + boundarySize.width / 2 - PAD, worldPos.y - boundarySize.height / 2 + PAD); // right bottom
    
    PROFILE_BEGIN("a");
    if ( fov.isEnable )
    {
        // fov 를 적용한다면 시야각과 플레이어 주변 원 데이터를 이용해 클리핑 경계면을 만든다. ( CCW )
        //                                     *fov : field of view (시야각)
        //             (n + m)
        //   (1) . ---.---.---.-- . (n+4)
        //        \              /
        //         \            /
        //          \          /
        //           \        /
        //            \ fov  /
        //             \    /
        //           (2)'  '(n+3)
        //             . w  .
        //              '..'
        //          ( n 개의 정점 )
        //
        //
        
        // "1", "n + 4" vertex
        vector<Segment> boundaryWalls;
        boundaryWalls.push_back(Segment(v1, v2));
        boundaryWalls.push_back(Segment(v2, v3));
        boundaryWalls.push_back(Segment(v3, v4));
        boundaryWalls.push_back(Segment(v4, v1));
        
        Mat3 rotationMatrix;
        rotationMatrix.rotate(MATH_DEG_TO_RAD(fov.entryDegree/2));
        Vec2 leftDir = rotationMatrix.getTransformedVector(fov.heading);
        Segment leftRay = Segment(worldPos, worldPos + leftDir * SEGMENT_LENGTH);
        
        rotationMatrix.identity();
        rotationMatrix.rotate(MATH_DEG_TO_RAD(-fov.entryDegree/2));
        Vec2 rightDir = rotationMatrix.getTransformedVector(fov.heading);
        Segment rightRay = Segment(worldPos, worldPos + rightDir * SEGMENT_LENGTH);
        
        Vec2 leftIntersectPoint = _getClosestIntersectPointToWall(boundaryWalls, leftRay);
        Vec2 rightIntersectPoint = _getClosestIntersectPointToWall(boundaryWalls, rightRay);
        
        
        // "n + m" vertex
        std::vector<Vec2> belongPoints;
        if ( leftDir.cross(v1 - worldPos) < 0 && rightDir.cross(v1 - worldPos) > 0 ) belongPoints.push_back(v1);
        if ( leftDir.cross(v2 - worldPos) < 0 && rightDir.cross(v2 - worldPos) > 0 ) belongPoints.push_back(v2);
        if ( leftDir.cross(v3 - worldPos) < 0 && rightDir.cross(v3 - worldPos) > 0 ) belongPoints.push_back(v3);
        if ( leftDir.cross(v4 - worldPos) < 0 && rightDir.cross(v4 - worldPos) > 0 ) belongPoints.push_back(v4);
        
        std::sort(std::begin(belongPoints), std::end(belongPoints), [&](const Vec2& v1, const Vec2& v2){
            
            Vec2 tempV1 = (v1 - _eyePos).getNormalized();
            Vec2 tempV2 = (v2 - _eyePos).getNormalized();
            
            float angle1 = physics::getAngleFromAxis(tempV1, fov.heading);
            float angle2 = physics::getAngleFromAxis(tempV2, fov.heading);
            
            return angle1 > angle2;
            
        });
        
        
        // "2", "n + 3" vertex
        Vec2 leftCirclePoint = worldPos + leftDir * fov.aroundCircleRadius;
        Vec2 rightCirclePoint = worldPos + rightDir * fov.aroundCircleRadius;
        
        
        // "n" vertices
        std::vector<Vec2> circleVertices;
        int remainCircleDegree = 360 - fov.entryDegree;
        int sliceCount = std::max(fov.aroundCircleSlice, 10); // 최소 10개의 정점을 이용해 정점 n을 만든다.
        int degreePerLoop = remainCircleDegree / sliceCount;
        for(int i = 1 ; i < sliceCount ; ++ i)
        {
            Vec2 dir = (leftCirclePoint - worldPos).getNormalized();
            Mat3 rotateMatrix;
            rotateMatrix.rotate(MATH_DEG_TO_RAD(degreePerLoop * i));
            Vec2 rotatedPoint = worldPos + rotateMatrix.getTransformedVector(dir) * fov.aroundCircleRadius;
            circleVertices.push_back(Vec2(rotatedPoint));
        }
        
        // 구한 정점을 순서에 맞게 합친다. ( 1, 2, ... , n + 3, n + 4, n + m )
        clipBoundary.pushVertex(leftIntersectPoint);
        clipBoundary.pushVertex(leftCirclePoint);
        
        for(const auto& vert : circleVertices)
            clipBoundary.pushVertex(vert);
        
        clipBoundary.pushVertex(rightCirclePoint);
        clipBoundary.pushVertex(rightIntersectPoint);
        
        for(const auto& vert : belongPoints)
            clipBoundary.pushVertex(vert);
        
    }
    else
    {
        // fov를 적용하지 않는다면 영역 사이즈를 이용해 사각형 클리핑 경계면을 만든다. ( CCW )
        clipBoundary.pushVertex(v4);
        clipBoundary.pushVertex(v3);
        clipBoundary.pushVertex(v2);
        clipBoundary.pushVertex(v1);
    }
    PROFILE_END("a");
    
    PROFILE_BEGIN("a-1");
    // 폴리곤을 클리핑 한다. ( CCW )
    vector<realtrick::Polygon> clippedPloygon = clipping::getClippedPolygons(polygons, clipBoundary);
    PROFILE_END("a-1");
    
    vector<Segment> visibleWalls;
    vector<Vec2> storedVert;
    
    PROFILE_BEGIN("b");
    // 클리핑된 도형에 대해 벽을 생성한다.
    for(const auto& polygon : clippedPloygon)
    {
        realtrick::Polygon p = polygon;
        p.pushVertex(p.vertices.front());
        for(int i = 0 ; i < p.vertices.size() - 1 ; ++ i)
        {
            Segment wall = Segment(p.vertices[i], p.vertices[i + 1]);
            if ( _isVisibleWall(wall) )
                visibleWalls.push_back(wall);
        }
    }
    PROFILE_END("b");
    
    PROFILE_BEGIN("c");
    // 경계면에 대해 벽을 생성.
    if ( _windingOrder == WindingOrder::CCW )
    {
        int vertSize = (int)clipBoundary.vertices.size();
        for(int i = 0 ; i < vertSize - 1 ; ++i )
        {
            Segment wall(clipBoundary.vertices[i], clipBoundary.vertices[i + 1]);
            visibleWalls.push_back(wall);
        }
        Segment wall(clipBoundary.vertices.back(), clipBoundary.vertices.front());
        visibleWalls.push_back(wall);
    }
    else
    {
        for(int i = 0 ; i < clipBoundary.vertices.size() ; ++ i)
        {
            Segment wall(clipBoundary.vertices[i], clipBoundary.vertices[i + 1]);
            visibleWalls.push_back(wall);
        }
        Segment wall(clipBoundary.vertices.back(), clipBoundary.vertices.front());
        visibleWalls.push_back(wall);
    }
    PROFILE_END("c");
    
    PROFILE_BEGIN("d");
    // 같은 정점을 제거한다.
    {
        std::unordered_map<int, bool> uniqueVertice;
        for(const auto &wall : visibleWalls)
        {
            int hash = _hashFunc(wall.start);
            if( uniqueVertice[hash] == false )
            {
                storedVert.push_back(wall.start);
                uniqueVertice[hash] = true;
            }
            
            hash = _hashFunc(wall.end);
            if( uniqueVertice[hash] == false )
            {
                storedVert.push_back(wall.end);
                uniqueVertice[hash] = true;
            }
        }
    }
    PROFILE_END("d");
    
    PROFILE_BEGIN("e");
    // 두 번 이상 충돌되는 점 모두제거.
    {
        storedVert.erase(remove_if(begin(storedVert), end(storedVert), [&](const Vec2& v){
            
            return !_isOneHitVertex(visibleWalls, v);
            
        }), end(storedVert));
    }
    PROFILE_END("e");
    
    PROFILE_BEGIN("f");
    // 각 정점으로 세 갈레로 광선을 쏴본 후 걸리는 부분을 추가한다.
    {
        int tempSize = (int)storedVert.size();
        for(int i = 0 ; i < tempSize; ++ i)
        {
            Mat3 rotMat;
            Vec2 direction, desti, closestVert;
            
            // left
            rotMat.identity();
            rotMat.rotate(CC_DEGREES_TO_RADIANS(0.05f));
            direction = rotMat.getTransformedVector(storedVert[i] - _eyePos).getNormalized();
            desti = _eyePos + direction * SEGMENT_LENGTH;
            closestVert = _getClosestIntersectPointToWall(visibleWalls, Segment(_eyePos, desti));
            
            if( (storedVert[i] - closestVert).getLengthSq() > 1.0f )
                storedVert.push_back(closestVert);
            
            // right
            rotMat.identity();
            rotMat.rotate(-CC_DEGREES_TO_RADIANS(0.05f));
            direction = rotMat.getTransformedVector(storedVert[i] - _eyePos).getNormalized();
            desti = _eyePos + direction * SEGMENT_LENGTH;
            closestVert = _getClosestIntersectPointToWall(visibleWalls, Segment(_eyePos, desti));
            
            if( (storedVert[i] - closestVert).getLengthSq() > 1.0f )
                storedVert.push_back(closestVert);
        }
    }
    PROFILE_END("f");
    
    PROFILE_BEGIN("g");
    // 점을 각도값으로 정렬.
    {
        std::sort(std::begin(storedVert), std::end(storedVert), [this](const Vec2& v1, const Vec2& v2) {
            
            Vec2 tempV1 = (v1 - _eyePos).getNormalized();
            Vec2 tempV2 = (v2 - _eyePos).getNormalized();
            
            float angle1 = physics::getAngleFromZero(tempV1);
            float angle2 = physics::getAngleFromZero(tempV2);
            
            return angle1 > angle2;
            
        });
    }
    PROFILE_END("g");
    
    PROFILE_BEGIN("h");
    // 삼각형 이어그리기
    {
        if( !storedVert.empty() ) storedVert.push_back(storedVert.front());
        
        tex->beginWithClear(_shadowColor.r, _shadowColor.g, _shadowColor.b, _shadowColor.a);
        _dNode->clear();
        _dNode->setScale(scaleFactor.x, scaleFactor.y);
        for(int i = 0 ; i < (int)storedVert.size() - 1 ; ++ i)
        {
            _dNode->drawTriangle(_worldToLocal(_eyePos), _worldToLocal(storedVert[i]), _worldToLocal(storedVert[i + 1]), _visibleColor);
        }
        _dNode->visit();
        tex->end();
        
        if ( !storedVert.empty() ) storedVert.pop_back();
    }
    PROFILE_END("h");
}