【问题标题】:Isometric simple sorting with xdim and ydim使用 xdim 和 ydim 进行等距简单排序
【发布时间】:2017-06-22 02:40:20
【问题描述】:

我有一个带有此功能的简单等距排序系统(代码在Typescript/Javascript):

public Sort(a: PIXI.Sprite, b: PIXI.Sprite) {
    return ((a.IsoZ - b.IsoZ) == 0 ? (a.TileZ - b.TileZ == 0 ? (a.Tile2Z ? (a.Tile2Z < b.Tile2Z ? -1 : (a.Tile2Z > b.Tile2Z ? 1 : 0)) : 0) : a.TileZ - b.TileZ) : (a.IsoZ - b.IsoZ));
}

这取决于三个参数:

  • IsoZ:第一个排序变量,用于对tile进行排序
  • TileZ: 瓷砖 排序变量,如果a.IsoZ == b.IsoZ 使用
  • Tile2Z:如果a.TileZ == b.TileZ 使用

对于大多数对象,IsoZ 的基本计算方法如下:

this.Position 是一个 x 和 y 坐标数组

this.Position[0] + this.Position[1] + 1000;

现在我想支持对象的 x 和 y 维度,那么我该如何在这个表达式中实现这样的东西呢?

x 和 y 维度值是例如 (2, 2) 用于立方体或 (2, 4) 用于立方体

this.Position[0] + this.Position[1] + 1000 // + x dimension + y dimension ???

【问题讨论】:

    标签: javascript html canvas isometric


    【解决方案1】:

    等距视觉遮挡排序(深度排序)

    定义深度: 更高的深度值更接近屏幕。与深度是与前平面的距离的 3D 透视投影不同,此答案使用深度作为与屏幕的距离。

    等值投影

    如果你有 iso 投影

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

    这需要一个 3d 点并投影到屏幕空间

    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;
    }
    

    您可以将点的深度添加为 2D 投影点的 z 值。您需要为深度添加一个变换轴。

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

    对于 x 靠近其大小的一半,y * 1 和 z * 1。

    修改后的project 现在将 z 添加到返回的点。

    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;
    }
    

    因此,对于投影到 2D iso 屏幕空间的 3D 空间中的一组点,您在 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);
    

    所有的点都很好,但对于占据 3D 体积的精灵,这不起作用。

    您需要做的是添加一个包围体,即一个正方形。如果您的投影是静态的,那么我们可以将包围体简化为最近的点。对于右下角顶点的框,例如 (0,0,0) 处的精灵的大小为 (10,10,20),3d 中最近的点位于 (10,10,20)。

    由于问题中没有足够的信息,我无法解决您的问题,但我猜 sprite.Iso 是 sprite 的基本原点,而 sprite.Tile 和 Tile2 表示边界框。

    从而得到最近的点

    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);
    

    然后从索引 0 向上渲染。

    一个例子。

    以下内容并不完全适用,因为它按多边形排序并使用多边形平均深度而不是其最大深度(确实应该使用最大值但不能打扰 ATM)

    我添加它只是为了说明如何使用上述isoProjMat 的代码。它从画布上渲染的像素 alpha 和颜色绘制堆叠的框。

    单击渲染结果将投影从双态切换到三态(因为您没有指定使用的投影类型,这显示了深度变换在两种类型的平行投影之间如何变化。

    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;
    }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      • 2012-02-10
      • 2014-02-28
      相关资源
      最近更新 更多