使用 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
:第一个排序变量,用于对瓦片进行排序
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 ???
等距视觉遮挡排序(深度排序)
定义深度:
深度值越高,离屏幕越近。与深度是与前平面的距离的 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>
我有一个简单的等距分类系统具有这个功能(代码在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
:第一个排序变量,用于对瓦片进行排序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 ???
等距视觉遮挡排序(深度排序)
定义深度: 深度值越高,离屏幕越近。与深度是与前平面的距离的 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.
点击渲染结果将投影从双态切换到三态(因为您没有指定您使用的投影类型,这显示了深度变换如何在两种平行投影之间变化。
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>