Search code examples
javascriptadjacency-matrixquadtreeneighbours

Javascript Adjacency matrix at pseudo quadtree


I have a pseudo quadtree algorithm, which divides space by distance to current mouse position.

enter image description here

Why it's pseudo? It doesn't create proper tree where each node has 4 children or none (if it's a terminal node). Instead of this, the algorithm replaces partition with its four children in clock-wise order.

enter image description here

I need to easily get neighbors for each partition and pretty sure that I need to create and update adjacency matrix for all nodes. Pretty sure it has to be inside Quadtree.splitNode() function.

enter image description here

By now, I don't have any ideas how to make me.

I have attached the snipped with interactive canvas and table, which should be updated with adjacency matrix.

var mouse = {x: 0, y: 0}, colors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000'];
    
class Node{
    
    constructor(level_, index_, centerX_, centerY_, width_, height_, resolution_){
        
        
        this.level = level_;
        this.index = index_;
        this.w = width_;
        this.h = height_;
        this.x = centerX_;
        this.y = centerY_;
        this.resolution = resolution_;
        
        this.edges = this.getEdges();
    }
    
    getEdges = function(){
        
        return [
            
                {x: this.x - this.w / 2, y: this.y - this.h / 2}, 
                {x: this.x + this.w / 2, y: this.y - this.h / 2}, 
                {x: this.x + this.w / 2, y: this.y + this.h / 2}, 
                {x: this.x - this.w / 2, y: this.y + this.h / 2}
                  
               ];

    }
    
    getAdjacency = function(){
        
        return [
            
                {x: this.x - this.w / 2, y: this.y - this.h / 2}, 
            
                {x: this.x + this.w / 2, y: this.y - this.h / 2}, 
                {x: this.x + this.w / 2, y: this.y + this.h / 2}, 
                {x: this.x - this.w / 2, y: this.y + this.h / 2}
                  
               ];

    }
    
}
    
class Quadtree{
    
    constructor(root_, levels_, distance_){
        
        var this_ = this;
        this.levels = levels_;
        this.distance = distance_;
        this.root = root_;
        this.nodes = [];
        this.nodes = this.splitNode(0, this.root, false);
        this.generateLevels();
        this.last = [...this.nodes];
        this.tiles = {};
        this.debug = {};
        this.points = [];
        
        this.adjacency = [
            
            ["-", 0, 1, 2, 3],
            [0, 0, 1, 0, 1 ],
            [1, 1, 0, 1, 0],
            [2, 0, 1, 0, 1],
            [3, 1, 0, 1, 0]

        ];
                
    }
    
    generateLevels = function(){
        
        for(var i = 0; i < this.levels; i++){

            var tmpNodes = [];

            for(var j = 0; j < this.nodes.length; j++){

               tmpNodes.push(...this.splitNode(j, this.nodes[j], true));

            }

            this.nodes = tmpNodes;

        }
        
    }
    
    update = function(){
        
        var this_ = this;
        this.nodes = [];
        this.nodes = this.splitNode(0, this.root, false);
        this.generateLevels();
          
        this.debug = {};

        this.last = [...this.nodes];
        
    }
    
    splitNode = function(index_, parent_, check_){

     if((parent_.level < this.levels && this.sqrtDistance(parent_) < this.distance) || !check_){
   
       var lt = new Node(parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
       var rt = new Node(parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
       var rb = new Node(parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
       var lb = new Node(parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
       
       return [lt, rt, rb, lb];
     
     }
    
     return [parent_];
        
    }
    
    sqrtDistance = function(node_){
        
        var target = mouse;
        
        var x1 = node_.x - node_.w / 2.0;
        var y1 = node_.y - node_.h / 2.0;
        var x2 = node_.x + node_.w / 2.0;
        var y2 = node_.y + node_.h / 2.0;

        var rx = (x1 + x2) / 2.0;
        var ry = (y1 + y2) / 2.0;
        var rwidth = node_.w;
        var rheight = node_.h;

        var dx = Math.max(Math.abs(target.x - rx) - rwidth / 2, 0);
        var dy = Math.max(Math.abs(target.y - ry) - rheight / 2, 0);
        return Math.sqrt(dx * dx + dy * dy);
        
    }
    
}
 
var root = new Node(0, {x: 0, y: 0}, 512, 512, 992, 992, 1);
var quad = new Quadtree(root, 5, 16.0);
quad.update();
    
var canvas = document.getElementById("quad");
var ctx = canvas.getContext("2d");
    
generateAdjacencyTable();
 
function drawQuad(){
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    quad.nodes.forEach(function(node_, i_){

        //contours
        var points = node_.getEdges();
        ctx.beginPath();
        ctx.moveTo(points[0].x, points[0].y);
        for(var i = 1; i < points.length; i++){ ctx.lineTo(points[i].x, points[i].y); }
        ctx.lineTo(points[0].x, points[0].y);
        //ctx.strokeStyle = colors[i_ % 20];
        ctx.stroke();

    })
    
}
    
function generateAdjacencyTable(){
    
    if(document.getElementById("adjTable") != undefined){
        
        var oldTbl = document.getElementById("adjTable");
        oldTbl.parentNode.removeChild(oldTbl);
        
    }
    
    var body = document.getElementsByTagName("body")[0];
    var tbl = document.createElement("table");
    tbl.style = "margin: 8px;";
    tbl.id = "adjTable";
    
    var tblBody = document.createElement("tbody");
    
    for(var j = 0; j < quad.adjacency.length; j++) {
        
        var row = document.createElement("tr");
        
        for(var i = 0; i < quad.adjacency[j].length; i++) {
            
            var cell = document.createElement("td");
            var cellText = document.createTextNode(quad.adjacency[i][j]);
            
            cell.appendChild(cellText);
            row.appendChild(cell);
            
        }
        
         tblBody.appendChild(row);
        
    }
    
    tbl.appendChild(tblBody);
    body.appendChild(tbl);
    tbl.setAttribute("border", "1");
    
}
    
document.addEventListener("mousemove", onMouseMoveEvent, false);

function onMouseMoveEvent(event_){
    
    var rect = canvas.getBoundingClientRect();
    mouse = { 
      x: (event_.clientX - rect.left) * 2,
      y: (event_.clientY - rect.top) * 2
    };
    
    quad.update();
    drawQuad();
    
}
    
  body { margin: 0; }
        tbody td:first-child, tr:first-child { background: rgb(192, 192, 192); }
    
<!DOCTYPE html>
<html>
<head>
    
    <meta charset="utf-8" />
    
</head>
<body>
    
<canvas id="quad" width="1024" height="1024" style="width: 512px; height: 512px;"></canvas>

</body>
</html>


Solution

  • That's a interm solution.

    var mouse = {x: 600, y: 600};
    
    Number.prototype.between = function(a_, b_) {
    
      var min = Math.min.apply(Math, [a_, b_]),
        max = Math.max.apply(Math, [a_, b_]);
      return this >= min && this <= max;
      
    };
        
    class Node{
        
        constructor(id_, level_, index_, centerX_, centerY_, width_, height_, resolution_){
            
            this.id = id_;
            this.level = level_;
            this.index = index_;
            this.w = width_;
            this.h = height_;
            this.x = centerX_;
            this.y = centerY_;
            this.resolution = resolution_;
            
            this.edges = this.getEdges();
        }
        
        getEdges = function(){
            
            return [
                
                    {x: this.x - this.w / 2, y: this.y - this.h / 2}, 
                    {x: this.x + this.w / 2, y: this.y - this.h / 2}, 
                    {x: this.x + this.w / 2, y: this.y + this.h / 2}, 
                    {x: this.x - this.w / 2, y: this.y + this.h / 2}
                      
                   ];
    
        }
        
        getAdjacency = function(){
            
            return [
                
                    {x: this.x - this.w / 2, y: this.y - this.h / 2}, 
                
                    {x: this.x + this.w / 2, y: this.y - this.h / 2}, 
                    {x: this.x + this.w / 2, y: this.y + this.h / 2}, 
                    {x: this.x - this.w / 2, y: this.y + this.h / 2}
                      
                   ];
    
        }
        
    }
        
    class Quadtree{
        
        constructor(root_, levels_, distance_){
            
            var this_ = this;
            
             this.adjacency = [ ];
    
            this.pattern = [
                
               [0, 1, 0, 1],
               [1, 0, 1, 0],
               [0, 1, 0, 1],
               [1, 0, 1, 0]
    
            ];
    
            
            this.levels = levels_;
            this.distance = distance_;
            this.root = root_;
            this.nodes = [];
            this.nodes = this.splitNode(0, this.root, false);
            this.generateLevels();
            this.last = [...this.nodes];
            this.tiles = {};
            this.debug = {};
            this.points = [];
             
        }
        
        generateLevels = function(){
            
            for(var i = 0; i < this.levels; i++){
    
            
                for(var j = 0; j < this.nodes.length; j++){
    
                   var tmp = this.splitNode(j, this.nodes[j], true);
                  
                   //array.splice(2, 1, ...array2);
                   this.nodes.splice(j, 1, ...tmp);               
    
                }
    
                
            }
            
        }
        
        update = function(){
            
            this.adjacency = [
            ];
            
            var this_ = this;
            this.nodes = [];
            this.nodes = this.splitNode(0, this.root, false);
            this.generateLevels();
              
            this.debug = {};
    
            this.last = [...this.nodes];
            
        }
        
        splitNode = function(index_, parent_, check_){
    
         if((parent_.level < this.levels && this.sqrtDistance(parent_) < this.distance) || !check_){
       
           var lt = new Node(parent_.id + "0.", parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
           var rt = new Node(parent_.id + "1.", parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
           var rb = new Node(parent_.id + "2.", parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
           var lb = new Node(parent_.id + "3.", parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
           
           if(check_) { this.extend2D(index_); } else { this.adjacency = [
                
               [0, 1, 0, 1],
               [1, 0, 1, 0],
               [0, 1, 0, 1],
               [1, 0, 1, 0]
    
            ]; }
           return [lt, rt, rb, lb];
         
         }
        
         return [parent_];
            
        }
        
        extend2D = function(index_){
    
            var tmp = Array(this.adjacency.length + this.pattern.length - 1).fill().map(() => Array(this.adjacency[0].length + this.pattern[0].length - 1));
    
            for(var i = 0; i < tmp.length; i++){
                for(var j = 0; j < tmp[i].length; j++){
    
                      var pi, pj, ni, nj;
                      i < index_ ? pi = i : pi = i - this.pattern.length;
                      j < index_ ? pj = j : pj = j - this.pattern[0].length;
    
                      ni = i - index_;
                      nj = j - index_;
    
    
                      //exclude themselfves
                       if(i == j) { tmp[i][j] = 0; continue; }
                       else if(!i.between(index_, index_ + this.pattern.length - 1) && !j.between(index_, index_ + this.pattern[0].length - 1)){
    
                            tmp[i][j] = tmp[j][i] = this.adjacency[pi][pj];
    
                       }
                       else if(i.between(index_, index_ + this.pattern.length - 1) && j.between(index_, index_ + this.pattern[0].length - 1)){
    
                          tmp[i][j] = tmp[j][i] = this.pattern[ni][nj];
                          continue;
    
                       }
                       else if(i.between(index_, index_ + this.pattern.length - 1)){
    
                           
                          if(this.adjacency[index_][pj] == 0) { tmp[i][j] = tmp[j][i] = 0; }
                          else{
                              
                            var dir;
                                              
                            var theta = Math.atan2(this.nodes[pj].y - this.nodes[index_].y, this.nodes[pj].x - this.nodes[index_].x);
                            if( theta > -Math.PI / 4.0 * 3.0 && theta < -Math.PI / 4.0) { dir = [1, 1, 0, 0];  }
                            else if(theta < Math.PI / 4.0) { dir = [0, 1, 1, 0]; }
                            else if(theta < Math.PI / 4.0 * 3.0) { dir = [0, 0, 1, 1]; }
                            else { dir = [1, 0, 0, 1]; }
    
                            tmp[i][j] = tmp[j][i] = dir[i - index_];
    
                          }
    
                       }
    
                }
            }
    
            this.adjacency = [...tmp];
            
    
        }
        
        sqrtDistance = function(node_){
            
            var target = mouse;
            
            var x1 = node_.x - node_.w / 2.0;
            var y1 = node_.y - node_.h / 2.0;
            var x2 = node_.x + node_.w / 2.0;
            var y2 = node_.y + node_.h / 2.0;
    
            var rx = (x1 + x2) / 2.0;
            var ry = (y1 + y2) / 2.0;
            var rwidth = node_.w;
            var rheight = node_.h;
    
            var dx = Math.max(Math.abs(target.x - rx) - rwidth / 2, 0);
            var dy = Math.max(Math.abs(target.y - ry) - rheight / 2, 0);
            return Math.sqrt(dx * dx + dy * dy);
            
        }
        
    }
     
    var root = new Node("", 0, {x: 0, y: 0}, 512, 512, 992, 992, 1);
    var quad = new Quadtree(root, 3, 16.0);
    quad.update();
        
    var canvas = document.getElementById("quad");
    var ctx = canvas.getContext("2d");
         
    var index = 6;
        
    function checkNeighbour(index_, index2_, array_){
        
        return array_[index2_][index_] == 1 ? true : false;
        
    }
        
    function drawQuad(index_){
        
        
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        quad.nodes.forEach(function(node_, i_){
    
            if(index_ < quad.nodes.length && index_ == i_) { ctx.fillStyle = "#FF00FF"; } 
            else if(index_ < quad.nodes.length && checkNeighbour(i_, index_, quad.adjacency)) {  ctx.fillStyle = "#00FFFF"; }
            else { ctx.fillStyle = "transparent"; }
            
            //contours
            var points = node_.getEdges();
            ctx.beginPath();
            ctx.moveTo(points[0].x, points[0].y);
            for(var i = 1; i < points.length; i++){ ctx.lineTo(points[i].x, points[i].y); }
            ctx.lineTo(points[0].x, points[0].y);
            ctx.fill();
            ctx.strokeStyle = "#000000";
            ctx.stroke();
        
    
        })
        
    }
        
    function generateAdjacencyTable(){
        
        var adjacencyTbl = [];
        
        for(var x = 0; x < quad.adjacency.length + 1; x++){
            
            adjacencyTbl[x] = [];
            
            for(var y = 0; y < quad.adjacency[0].length + 1; y++){
                
                if(x == 0 && y == 0) { adjacencyTbl[x][y] = "-"; }
                else if(x == 0) {  adjacencyTbl[x][y] = quad.nodes[y - 1].id; }
                else if(y == 0) {  adjacencyTbl[x][y] = quad.nodes[x - 1].id; }
                else { adjacencyTbl[x][y] = quad.adjacency[x - 1][y - 1]; }
                
            }
            
        }
        
        if(document.getElementById("adjTable") != undefined){
            
            var oldTbl = document.getElementById("adjTable");
            oldTbl.parentNode.removeChild(oldTbl);
            
        }
        
        var body = document.getElementsByTagName("body")[0];
        var tbl = document.createElement("table");
        tbl.style = "margin: 8px;";
        tbl.id = "adjTable";
        
        var tblBody = document.createElement("tbody");
        
        for(var j = 0; j < adjacencyTbl.length; j++) {
            
            var row = document.createElement("tr");
            
            for(var i = 0; i < adjacencyTbl[j].length; i++) {
                
                var cell = document.createElement("td");
                var cellText = document.createTextNode(adjacencyTbl[i][j]);
                
                cell.appendChild(cellText);
                row.appendChild(cell);
                
            }
            
             tblBody.appendChild(row);
            
        }
        
        tbl.appendChild(tblBody);
        body.appendChild(tbl);
        tbl.setAttribute("border", "1");
        
    }
        
    document.addEventListener("mousemove", onMouseMoveEvent, false);
    
    function onMouseMoveEvent(event_){
        
        
        var rect = canvas.getBoundingClientRect();
        mouse = { 
          x: (event_.clientX - rect.left) * 2,
          y: (event_.clientY - rect.top) * 2
        };
        
        quad.update();
        drawQuad(6);
        
        generateAdjacencyTable();
    
        
    }
          body { margin: 0; }
            tbody td:first-child, tr:first-child { background: rgb(192, 192, 192); }
    <!DOCTYPE html>
    <html>
    <head>
        
        <meta charset="utf-8" />
        
    </head>
    <body>
        
    <canvas id="quad" width="1024" height="1024" style="width: 512px; height: 512px;"></canvas>
        
    </body>
    </html>