我有一个带有此功能的简单等距排序系统(代码位于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 ???
答案 0 :(得分:2)
定义深度: 较高的深度值更接近屏幕。与深度距离前平面的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。我是sprite和sprite的基本原点.Tile&amp; 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;
}
<canvas id="canvas"></canvas>