Isometric simple sorting with xdim and ydim

后端 未结 1 1037
故里飘歌
故里飘歌 2021-01-23 22:03

I have a simple isometric sorting system with this function (code is in Typescript/Javascript) :

public Sort(a: PIXI.S         


        
1条回答
  •  说谎
    说谎 (楼主)
    2021-01-23 22:12

    Isometric visual occlusion sort (depth sort)

    Defining depth: Higher depths values are closer to the screen. Unlike 3D perspective projection where depth is distance from the front plane, this answer uses depth as distance towards the screen.

    Iso projection

    If you have a iso projection

    const P2 = (x = 0,y = 0) => ({x, y});
    const isoProjMat = {
        xAxis : P2(1   , 0.5),
        yAxis : P2(-0.5, 1  ),
        zAxis : P2(0   , -1 ),
    }
    

    That takes a 3d point and projects to screen space

    const P3 = (x = 0, y = 0, z = 0) => ({x, y, z});
    isoProjMat.project = function (p, retP = P2()) { // p is 3D point
        retP.x = p.x * this.xAxis.x + p.y * this.yAxis.x + p.z * this.zAxis.x;
        retP.y = p.x * this.xAxis.y + p.y * this.yAxis.y + p.z * this.zAxis.y;
        return retP;
    }
    

    You can add the depth of a point as the z value of the 2D projected point. You need to add a transform axis for the depth.

    isoProjMat.depth = P3(0.5,1, 1 );
    

    For x move closer by half its size, y * 1 and z * 1.

    The modified project now adds z to the returned point.

    isoProjMat.project = function (p, retP = P3()) { 
        retP.x = p.x * this.xAxis.x + p.y * this.yAxis.x + p.z * this.zAxis.x;
        retP.y = p.x * this.xAxis.y + p.y * this.yAxis.y + p.z * this.zAxis.y;
        retP.z = p.x * this.depth.x + p.y * this.depth.y + p.z * this.depth.z; 
        return retP;
    }
    

    Thus for a set of points in 3D space projected to 2D iso screen space you sort on the z

    const points = mySetOfPoints(); // what ever your points come from
    const projected = points.map(p => isoProjMat.project(p));
    projected.sort((a,b) => a.z - b.z);
    

    All good for points but for sprites which occupy a 3D volume this does not work.

    What you need to do is add a bounding volume ie a square. If your projection is static then we can simplify the bounding volume to the nearest point. For the box that is the vertex at the top bottom right eg sprite at (0,0,0) has a size (10,10,20) the nearest point in 3d is at (10,10,20).

    I can not work your sort out as there is not enough info in the question but I am guessing sprite.Iso is the base origin of the sprite and sprite.Tile & Tile2 represent bounding box.

    Thus to get the nearest point

    const depthProj = P3(0.5,1, 1 ); // depth projection matrix
    // get the depth of each sprite adding the property depth
    sprites.forEach(spr => {
        const p = {
            x : spr.IsoX + Math.max(spr.TileX,spr.Tile2X),
            y : spr.IsoY + Math.max(spr.TileY,spr.Tile2Y),
            z : spr.IsoZ + Math.max(spr.TileZ,spr.Tile2Z)
        };
        spr.depth = p.x * depthProj.x + p.y * depthProj.y + p.z * depthProj.z; 
    })
    sprites.sort((a,b) => a.depth - b.depth);
    

    Then render from index 0 up.

    An example.

    The following is not fully applicable as it sorts by polygons and uses the polygons mean depth rather than its max depth (really should use max but cant be bothered ATM)

    I add it only to show how the above code for the isoProjMat is used. It draws stacked boxes from pixel alpha and color rendered on a canvas.

    Click rendered result to switch projections from bi-morphic to tri-morphic (as you did not specify the type of projection you used this shows how the depth transform changes between two types of parallel projection.

    const ctx = canvas.getContext("2d");
    var count = 0;
    var firstRun = 0;
    function doIt(){
    
      // 3d 2d points
      const P3 = (x=0, y=0, z=0) => ({x,y,z});
      const P2 = (x=0, y=0) => ({x, y});
    
      // isomorphic projection matrix
      const isoProjMat = {
          xAxis : count ? P2(1 , 0.5) : P2(1 , 0.5) ,
          yAxis : count ? P2(-0.5, 1) : P2(-1 , 0.5) ,
          zAxis : count ? P2(0   , -1) : P2(0 , -1) ,
          depth : count ? P3(0.5,1, 1) : P3(0.5,0.5,1) , // projections have z as depth
          origin : P2(), // (0,0) default 2D point
          project (p, retP = P3()) {
              retP.x = p.x * this.xAxis.x + p.y * this.yAxis.x + p.z * this.zAxis.x + this.origin.x;
              retP.y = p.x * this.xAxis.y + p.y * this.yAxis.y + p.z * this.zAxis.y + this.origin.y;
              retP.z = p.x * this.depth.x + p.y * this.depth.y + p.z * this.depth.z; 
              return retP;
          }
      }
    
      // isomorphic mesh shape as vertices and polygons
      const isoMesh = (()=>{
          const polygon = {
              inds : null,
              depth : 0,
              fillStyle : "#888",
              lineWidth : 0.5,
              strokeStyle : "#000",
              setStyle(ctx) {
                  ctx.fillStyle = this.fillStyle;
                  ctx.lineWidth = this.lineWidth;
                  ctx.strokeStyle = this.strokeStyle;
              },
          }
          const isoShape = {
              verts : null,
              pVerts : null, // projected verts
              polys : null,
              addVert(p3 = P3()) { this.verts.push(p3); return p3 },
              addPoly(poly = isoShape.createPoly()) { this.polys.push(poly); return poly },
              createPoly(options = {}) { return Object.assign({}, polygon, {inds : []}, options) },
              render(ctx,mat = isoProjMat) {
                  var i,j,d;
                  const pv = this.pVerts === null ? this.pVerts = [] : this.pVerts;
                  const v = this.verts;
                  const ps = this.polys;
                  for(i = 0; i < v.length; i += 1){  pv[i] = mat.project(v[i], pv[i]) }
                  for(i = 0; i < ps.length; i += 1) {
                      const p = ps[i];
                      j = 0; d = 0;
                      while(j < p.inds.length) { d += pv[p.inds[j++]].z }
                      p.depth = d / p.inds.length;
                  }
                  ps.sort((a,b)=>a.depth - b.depth);
                  for(i = 0; i < ps.length; i += 1) {
                      const p = ps[i];
                      p.setStyle(ctx);
                      ctx.beginPath();
                      j = 0;
                      while(j < p.inds.length) { ctx.lineTo(pv[p.inds[j]].x, pv[p.inds[j++]].y) }
                      if (p.fillStyle !== "") { ctx.fill() }
                      if (p.strokeStyle !== "" && p.lineWidth !== 0) {ctx.closePath(); ctx.stroke() }
                  }
              }
          }
          return () => Object.assign({},isoShape,{verts : [], polys : []});
      })();
    
      // Lazy coding I am using Point3 (P3) to hold RGB values
      function createBoxMesh(box = isoMesh(), pos = P3(), size = P3(10,10,10), rgb = P3(128,128,128)){ // x,y,z are sizes in those directions
          const PA3 = (x,y,z) => P3(x + pos.x, y + pos.y, z + pos.z);
          const RGB = (s) => `rgb(${(rgb.x * s) | 0},${(rgb.y * s) | 0},${(rgb.z * s) | 0})`;
          const indA = (inds) => inds.map(ind => ind + i);
          const i = box.verts.length; // get top vert index
          if(typeof size === "number") { size = P3(size,size,size) }
          const x = size.x / 2;
          const y = size.y / 2;
          const z = size.z;
          box.addVert(PA3(-x,-y, 0)); // ind 0
          box.addVert(PA3( x,-y, 0));
          box.addVert(PA3( x, y, 0));
          box.addVert(PA3(-x, y, 0));
          box.addVert(PA3(-x,-y, z)); // ind 4
          box.addVert(PA3( x,-y, z));
          box.addVert(PA3( x, y, z));
          box.addVert(PA3(-x, y, z));
         // box.addPoly(box.createPoly({ inds : indA([0,1,5,4]), fillStyle : RGB(0.5) }));
          box.addPoly(box.createPoly({ inds : indA([1,2,6,5]), fillStyle : RGB(0.7) }));
          box.addPoly(box.createPoly({ inds : indA([2,3,7,6]), fillStyle : RGB(1) }));
         // box.addPoly(box.createPoly({ inds : indA([3,0,4,7]), fillStyle : RGB(0.8) }));
          box.addPoly(box.createPoly({ inds : indA([4,5,6,7]), fillStyle : RGB(1.5) }));
          return box;
      }
    
      function createDrawable(w,h){
          const c = document.createElement("canvas");
          c.width = w;
          c.height = h;
          c.ctx = c.getContext("2d");
          return c;
      }
      const map = createDrawable(40,30);
      map.ctx.font = "20px arial";
      map.ctx.textAlign = "center";
      map.ctx.textBaseline = "middle";
      map.ctx.fillStyle = "rgba(0,128,0,0.5)";
      map.ctx.strokeStyle = "rgba(255,0,0,0.5)";
      map.ctx.lineWidth = 2;
      map.ctx.fillRect(1,1,map.width - 2, map.height - 2);
      map.ctx.strokeRect(1,1,map.width - 2, map.height - 2);
      map.ctx.fillStyle = "#AAA";
      map.ctx.strokeStyle = "rgba(255,128,0,0.5)";
      map.ctx.strokeText("text",map.width / 2, map.height / 2);
      map.ctx.fillText("text",map.width / 2, map.height / 2);
      var dat = map.ctx.getImageData(0, 0, map.width , map.height).data;
    
      ctx.setTransform(1,0,0,1,0,0);
    
      // get total projection area and size canvas so that the iso projection fits
      const boxSize = P3(10,10,5);
      const topLeft = isoProjMat.project(P3(0,0,10 * boxSize.z));
      const botRight = isoProjMat.project(P3(map.width * boxSize.x,map.height * boxSize.y,0));
      const topRight = isoProjMat.project(P3(map.width * boxSize.x,0,0));
      const botLeft = isoProjMat.project(P3(0,map.height * boxSize.y,0));
    
      canvas.width = ((topRight.x - botLeft.x) + 10)|0;
      canvas.height = ((botRight.y - topLeft.y) + 10)|0;
      ctx.clearRect(0,0,canvas.width,canvas.height);
      ctx.font = "32px arial";
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText("Rendering will take a moment.",Math.min(innerWidth,canvas.width)/2,Math.min(innerHeight,canvas.height)/2)
      setTimeout(function(){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.setTransform(1,0,0,1,-botLeft.x+10,-topLeft.y+10);
    
        const alphaThresh = 100;
        const boxes = isoMesh();
        for(var y = 0; y < map.height; y ++){
            for(var x = 0; x < map.width; x ++){
                const ind = (x + y * map.width) * 4;
                if(dat[ind + 3] > alphaThresh){
                    const h = (((dat[ind + 3]-alphaThresh)/(255-alphaThresh)) * 10) | 0;
                    for(var z = 0; z < h; z++){
                        createBoxMesh(
                            boxes,
                            P3(x * boxSize.x,y * boxSize.y, z * boxSize.z),
                            boxSize,
                            P3(dat[ind],dat[ind+1],dat[ind+2])
                        );
                    }
                }
            }
        }
    
        boxes.render(ctx);
        if(firstRun === 0){
            firstRun = 1;
            ctx.setTransform(1,0,0,1,0,0);
            ctx.font = "24px arial";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillStyle = "black";
            ctx.fillText("Bimorphic projection. Click for Trimorphic projection..",canvas.width/2,30)
            canvas.onclick =()=>{
               count += 1;
               count %= 2;
               doIt();
             };
         }
      },0);
    
    };
    doIt();
    canvas {
       border : 2px solid black;
    }

    0 讨论(0)
提交回复
热议问题