block detection

right vector

Finding the closest road on the right at a crossroad[1].

To generate the bocks of building based on the roads structure, the method I’m building is based on a simple idea: when you arrives at a crossroad, you take the first street on the right and you go on like this until you reach a dead-end or your starting point. If you reach your starting point, the succession of roads you took defines a block of building. In theory. This technique has been suggested by Michel Cleempoel, on the way back from school.

After a bit of preparation of the road network (removing orphan roads, having no connection with others, and dead-ends parts of the roads), the real problem arouse: how do you define right in a 3d environment, without an absolute ground reference. Indeed, I can configure the generator to use the Y axis (top axis in ogre3d) in addition to X & Z.

At a crossroad, you may have several possibilities of roads. In the research, these possible roads are reduced to 3d vectors, all starting at world’s origin. The goal is to find the closest vector on the right of the current one, called the main 3d vector in the graphic above..

The right is a complex idea, because it induces an idea of rotation. The closest on the right doesn’t mean the most perpendicular road on the right side. Let say I have 4 roads to choose from. Two going nearly in the opposite direction of the road i’m on, one perpendicular and one going straight on.

If I compute the angles these roads have with the current one, results are:

   5°,
   -5°,
   90°,
   and 170°.

The winner is not the 90°, but the 5° road! If I sort them, the last one must be the -5°, who is the first on the left.

3d plane from a 3d vector

The first thing to do is to define reference plane. To do so, you get the normal vector of the road by doing a cross product with the UP axis (Y axis in this case). The normal gives you a second vector, perpendicular to the road, and therefore defines a plane. Let’s call it VT plane, for Vector-Normal plane. For calculation, we need the vector perpendicular to this plane, rendered by crossing the road and its normal, let’s call it the tangent vector. Until here, it’s basic 3d geometry.

projection of 3d vectors on a plane

We can now project all the possible roads on the VT plane. These are the yellow vectors in the graphic. The math are clearly explained by tmpearce[2]. Implemented in processing, it gives:

     float d = othervector.dot( tangent );
     PVector projectedvector = new PVector();
     projectedvector.add( tangent );
     projectedvector.mult( d * -1 );
     projectedvector.add( othervector );

We are nearly done! angle between 3d vectors

The projected vectors will help the angle calculation. Indeed, the current vector and the projected ones being coplanar, they share the same normal. The way to get the angle between 2 coplanar vectors is described by Dr. Martin von Gagern[3], once again. See Plane embedded in 3D paragraph for the code i’ve used.

And… tadaaammmm! The number rendered by the method is the angle i was searching for, displayed in degrees in the graphic above.

sketches

DC scan 0001.jpeg DC scan 0002.jpeg DC scan 0003.jpeg DC scan 0004.jpeg DC scan 0005.jpeg DC scan 0006.jpeg DC scan 0007.jpeg DC scan 0008.jpeg DC scan 0009.jpeg

half-edges

Isle::collect_oriented_segments(Road* r)

cases to foresee:
0. starting the road, previous dot has one connection
1. starting the road, previous dot has >1 connections > crossroad
2. along the road, current dot has two connections 
3. along the road, current dot has >2 connections > crossroad
4. end of the road, current dot has one connection
5. end of the road, current dot has >1 connections > crossroad

A       B       C
o ----> o ----> o
        ^
        |
        |
        D

[f]  [f]  [b]  [b]  [f]
AB > BC > CB > BA > AB

[f]  [b]  [f]  [f]  [b]  [b]  [f]
AB > BD > DB > BC > CB > BA > AB


A       B       C
o ----> o ----> o
        |
        |
        v
        D

[f]  [f]  [b]  [f]  [b]  [b]  [f]
AB > BD > DB > BC > CB > BA > AB

cases [2,5] can happens simultaneously

It seems nearly impossible to create the relation between half-edges correctly in one pass: some half-edges tries to connect to not yet created half-edges. If the half-edges are generated on the fly, it will imply a check at each half-edge (HE) generation, to validate if the HE is already created or not.

A better approach seems to be: generation of all of them + creation of structures for crossroads, holding a pointer to the RoadDot and the list of all HEs connected to it. Connections can be resolved locally in a second pass. Let's try it.

Not yet functional: blocks are crossing roads...

Roadmap 2017041115263950045.png Roadmap 2017041115261223240.png

The issue was coming from a method that sort the connections of a roaddot: RoadDot::rightSort( RDList& sorted ). In its first implementation, I was just looping over the connections and storing in a list the result of the method RoadDot::getRight( RoadDot* from );, without checking in any way the angles between these dots. The block crossing street issue was caused by this inconsistency. I now use a skip list and I always use the first connection as a reference. Code is simple:

void RoadDot::rightSort( RDList& sorted ) {
   
   RDListIter it = connected_dots.begin( );
   RDListIter ite = connected_dots.end( );

   sorted.push_back( connected_dots[0] );
   RDList skip;
   skip.push_back( connected_dots[0] );
   
   for(; it != ite; ++it ) {
       RoadDot* d = getRight( connected_dots[0] , skip );
       if ( !d ) break;
       skip.push_back( d );
       if ( find( sorted.begin(), sorted.end(), d ) != sorted.end() ) {
           cout << "Double push in sorted!!!" << endl;
       }
       sorted.push_back( d );
   }
   
}

Each time a dot on the right is retrieved, it is added to the skip list for the next search. Sorting, in a word...

Algorithm can be very painful... but when they are logically correct, they work marvellously! The generation time of each map is nearly unchanged, and results are perfect: no holes!

Roadmap 2017041117415434339.png Roadmap 2017041117482334634.png Roadmap 2017041117483546662.png

Last screenshot shows the block rendered as plain shapes. The big yellow one is the outer bound of the map! With a method to measure surfaces of a random polygon, I'll be able to have the precise city area.

surfaces

Block-surface.png

The implementation of the method has been rather quick, even if the results have to be better checked, via a comparaison with a bounding box, for instance. The color mapping done in the screenshots below is as follow:

  • surface ∈ [0,3000]: ( bigger => violet & more grey )
    • hue: [0,3000] → [0,255]
    • saturation [0,3000] → [255,180]
  • huge surface (>=3000): white stroke

When blocks are displayed without fill, a small cross indicates the center of each block (third image of each set). Image are 1920x1080, click on them to see details.

Roadmap 20170412111038124919.png Roadmap 20170412111041127453.png Roadmap 20170412111042129387.png

Roadmap 20170412111242248453.png Roadmap 20170412111244250496.png Roadmap 20170412111246253251.png

References

  1. Finding the closest road on the right at a crossroad, originally posted in polymorph.cool
  2. tmpearce on stackoverflow
  3. Dr. Martin von Gagern on stackoverflow

Resources

  • Doubly connected edge list, also known as half-edge data structure - wikipedia

online identity ∋ [ social ∋ [mastodon♥, twitter®, facebook®, diaspora, linkedin®] ∥ repos ∋ [github®, gitlab♥, bitbucket®, sourceforge] ∥ media ∋ [itch.io®, vimeo®, peertube♥, twitch.tv®, tumblr®] ∥ communities ∋ [godotengine♥, openprocessing, stackoverflow, threejs]]