使用 xdim 和 ydim 进行等距简单排序

Isometric simple sorting with xdim and ydim

我有一个简单的等距分类系统具有这个功能(代码在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 计算方式:

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 ???

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

定义深度: 深度值越高,离屏幕越近。与深度是与前平面的距离的 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 点并投影到屏幕上 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;
}

您可以添加一个点的深度作为二维投影点的 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 屏幕 space 的一组 3D 点 space,您在 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.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 的代码。它根据 canvas.

上渲染的像素 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;
}
<canvas id="canvas"></canvas>