Combine vector shapes and convert them to blobs

Hello,

I have approximately 8000 rectangles that are tiled on the screen to form a circle.

Each rectangle can be activated or deactivated.

Once activated they becomes visible on the screen. So naturally these rectangles can form some shapes and I would like to being able to detect these shapes and treat them as polygons.

Question1: what kind of primitives should I use to store each rectangular ? vector ofPath ? ofMesh?

Question2: Is there some existing mechanism, that would allow to detect rectangles that touch each other and form a single continuous polygon?

many thanks
M

are the rectangles rotated?

If they are axis alligned, perhaps storing them as ofRectangle would be the best. There’s a function in ofRectangle that check if one rectangle is touching another if that’s helpful. if you want to make new polygons out of the intersection, you may look to an add-on like ofxClipper, which can take two polygons and compute the union of them – but you’d have to do them in groups of two at a time (IIRC)… Also, depending on what you need to do, one solution may be to use a raster image, and draw the rectangles into a raster image, and use something like contour finding to find shapes.

Hello,

thanks for a quick answer. The rectangles are not rotated, so it’s already a good thing ) I will check the ofxClipper, but I suspect it won’t be very fast in terms of performance.

As for contourFinder, I just tried this and the FPS drop is two-fold even for empty images. Actually even copying ofFbo to pixels and then loading them to CvGrayImage also takes lots of resources (I work with 1920x1920 resolution). So maybe the solution would be to use smaller resolution for blob detection ?

Actually the final result I’m seeking is having nice rounded blobs … and get rid of these pixelated shapes. It doesn’t need to be perfect, there is another process that hides all imperfections.
Something similar to “matt choker” in After-Effects would be sufficient…

any ideas ?

Thanks!
M

Is the idea to get a smoothed outline something like this?

Will share some code which uses ofxClipper to get the union.

Ended up making a fairly good example / demo of this anyway :slight_smile:
The hardest part was merging multiple rect overlap pairs

add these lines to ofApp.h

        vector <ofRectangle> rects;
        vector <ofPolyline> mResults; 
        vector <ofPolyline> mSmoothResults;
        bool bNeedsUpdate = false; 

Then ofApp.cpp

#include "ofApp.h"
#include "ofxClipper.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(10);
    
    //generate some rects using ofApp::keyPressed
    keyPressed(' ');
}

//--------------------------------------------------------------
void ofApp::update(){

    if( bNeedsUpdate ){
        bNeedsUpdate = false;
        map <int, vector <ofRectangle>> mInterRects;
            
        //build a list of intersecting rects;
        for(int i = 0; i < rects.size(); i++){
            auto r1 = rects[i];
            mInterRects[i].push_back(r1);
            
            for(int j = 0; j < i; j++){
                auto & r2 = rects[j];
                
                if( r1.intersects(r2) ){
                    mInterRects[i].push_back(r2);
                }
            }
        }
        
        //merge rect sets that have rects shared between them
        for(auto & set1 : mInterRects){
            for(auto & set2 : mInterRects){
                if( set1.first == set2.first )continue;;
                if( set2.second.size() == 0 )continue;;
                
                bool bIntersects = false;
                int matchedRect = -1;
                for(auto & rect1 : set1.second ){
                    for( int d = 0; d < set2.second.size(); d++ ){
                        auto & rect2 = set2.second[d];
                        if( rect1 == rect2 ){
                            //intersects - lets merge them
                            bIntersects = true;
                            matchedRect = d;
                            break;
                        }
                    }
                }
                
                if( bIntersects ){
                    for( int d = 0; d < set2.second.size(); d++ ){
                        if( d != matchedRect ){
                            set1.second.push_back(set2.second[d]);
                        }
                    }
                    set2.second.clear(); //clear the vector so we don't have any rects as the've been moved to set1
                }
            }
        }
        
        ofxClipper clipper;
        mResults.clear();
        mSmoothResults.clear();
        
        //now combine with cipper
        for(auto & set : mInterRects){
        
            //get the vector<ofRectangle> of rects
            auto & vec = set.second;
            
            if( vec.size() > 0 ){
                clipper.clear();

                for( auto & rec : vec){
                    clipper.addRectangle(rec, OFX_CLIPPER_CLIP);
                }
                
                vector <ofPolyline> tResults;
                clipper.clip(OFX_CLIPPER_UNION, tResults, OF_POLY_WINDING_NONZERO, OF_POLY_WINDING_NONZERO);

                if( tResults.size() ){
                
                    auto outline = tResults[0];
                    outline.setClosed(true); //helps with smoothing
                    
                    //just add the first one as its the outline
                    mResults.push_back(outline);
            
                    outline = outline.getResampledBySpacing(5); //otherwise we get a low poly shape

                    //smooth outline
                    mSmoothResults.push_back(outline.getSmoothed(15)); //play with this value to make outline smoother
                }
            }
        }
    }
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    ofFill();
    ofSetColor(40);
    for( auto r : rects ){
        ofDrawRectangle(r);
    }

    ofNoFill();
    ofSetColor(255, 255, 0);
    for( auto poly : mResults ){
        poly.draw();
    }
    
    ofTessellator tess;
    
    ofSetColor(0, 255, 255);
    for( auto poly : mSmoothResults ){
        poly.draw();
        //to draw filled uncomment below
        //ofMesh mesh;
        //tess.tessellateToMesh(poly, OF_POLY_WINDING_NONZERO, mesh);
        //mesh.draw();
    }
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    
    if( key == ' ' ){
        rects.clear();
        for( int i = 0; i < 5; i++){
            rects.push_back(ofRectangle(ofRandom(100, 800), ofRandom(100, 500), ofRandom(150, 400), ofRandom(150, 400)));
        }
        bNeedsUpdate = true;
    }
}
3 Likes

Wow, @theo ! Thank you so much. Yes, that’s exactly what I needed to get at the end. Let me try to integrate this into my code!