Search code examples
javascriptfabricjs

how to add random circles with no overlap inside a fabricjs canvas


I am trying to come up with an algorithm of adding random circles with no overlap inside fabricj canvas; The bellow snippet is what i have tried.

const canvas = new fabric.Canvas('gameCanvas', {selection: false});
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
let rectangle;

document.addEventListener('DOMContentLoaded', function(){
    rectangle = new fabric.Rect({
                    width: 250,
                    height: 100,
                    fill: 'blue',
                    opacity: 0.2,
                    left: 200,
                    top: 140
      });
      canvas.add(rectangle);
});

document.getElementById('addCirclesBtn').addEventListener('click', function(){
    drawCircles();
});

function drawCircles()
{
   for(let i = 1; i <=20; i++)
   {
       let x= Math.random() * (rectangle.width - rectangle.left) + rectangle.left;
       let y = Math.random() * (rectangle.height - rectangle.top) + rectangle.top;
       let circle =  new fabric.Circle({
            left: x,
            top: y,
            strokeWidth: 2,
            radius: 10,
            fill: 'white',
            stroke: '#666',
            selectable: false,
            hoverCursor: 'default',
            hasControls: false,
            hasBorders: false
      });
      
      canvas.add(circle);
   }
   
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.min.js"></script>
<canvas id="gameCanvas" width="500" height="300" style="border: 2px solid green;"></canvas>
<button id="addCirclesBtn">ADD CIRCLES</button>


Solution

  • This is what i came up with and the main logic is in the function drawCircles() which checks is circles are overlapping before adding inside the rectangle. It is to be noted that the program add 100 circles inside the rectangle

    const canvas = new fabric.Canvas('gameCanvas', {selection: false});
    fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
    let rectangle;
    const radius = 10;
    let min_x;
    let min_y;
    let max_x;
    let max_y;
    let circles;
    
    document.addEventListener('DOMContentLoaded', function(){
        drawRectangle();
        min_x = rectangle.left - rectangle.width / 2 ;
        //min_x = 250 - 200/2;
        max_x = rectangle.left + rectangle.width / 2;
        //max_x = 250 + 200/2;
        
        min_y = rectangle.top - rectangle.height / 2 ;
        //min_y = 150 - 100/2;
        max_y = rectangle.top + rectangle.height / 2;
        //max_y = 150 + 100/2;
    });
    
    function drawRectangle()
    {
        rectangle = new fabric.Rect({
                        width: 200,
                        height: 100,
                        fill: 'blue',
                        opacity: 0.2,
                        left: 250,
                        top: 150
          });
          
          canvas.add(rectangle);
    }
    
    document.getElementById('addCirclesBtn').addEventListener('click', function(){
        canvas.clear();
        circles = [];
        drawRectangle();
        drawCircles();
    });
    
    function drawCircles()
    {
       let anti_crash_count = 0
       while(circles.length < 100)
       {
           let circle =  new fabric.Circle({
                left: getRandomNumber(min_x + radius, max_x - radius),
                top: getRandomNumber(min_y + radius, max_y  - radius),
                strokeWidth: 2,
                radius: radius,
                fill: 'white',
                stroke: '#666',
                selectable: false,
                hoverCursor: 'default',
                hasControls: false,
                hasBorders: false,
                originX:'center',
                originY:'center'
           });
           
           let isOverlapping = false;
           
           for(let j = 0; j < circles.length; j++)
           {
              let previous_circle = circles[j];
              let distance = Math.hypot(circle.left - previous_circle.left, circle.top - previous_circle.top);
              
              if(distance < (circle.radius + previous_circle.radius))
              {
                  isOverlapping = true;
                  break;
              }
           }
           
           if(!isOverlapping)
           {
              canvas.add(circle);
              circles.push(circle);
           }
           anti_crash_count ++;
           if(anti_crash_count > 1000)
           {
              break;
           }
       }
    }
    
    
    function getRandomNumber(min, max)
    {
        return Math.floor((Math.random() * (max - min)) + min);
    
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.min.js"></script>
    <canvas id="gameCanvas" width="500" height="300" style="border: 2px solid green;"></canvas>
    <button id="addCirclesBtn">ADD CIRCLES</button>

    I would appreciate any idea on how to improve it if necessary.