Javascript 伪四叉树的邻接矩阵

Javascript Adjacency matrix at pseudo quadtree

我有一个伪四叉树算法,它将 space 除以到当前鼠标位置的距离。

为什么是伪造的?它不会创建适当的树,其中每个节点都有 4 个子节点或 none(如果它是终端节点)。取而代之的是,该算法按顺时针顺序将分区替换为其四个子项。

我需要为每个分区轻松获取邻居,并且非常确定我需要为所有节点创建和更新邻接矩阵。很确定它必须在 Quadtree.splitNode() 函数内。

到目前为止,我还不知道如何制作我。

我附上了交互式 canvas 和 table 的片段,应该使用邻接矩阵进行更新。

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>

这是一个临时解决方案。

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>