簡體   English   中英

如何使用3D對象創建大腸菌病檢測?

[英]How to create colision detection with 3d objects?

我正在將我的學校項目重新制作一個名為spindizzyatari 8bit游戲。

我想出了如何使用將3d points投射到2d perspective point進行渲染的方法。

我還可以旋轉形狀(存在較小的渲染順序問題)

我的目標是這樣的:

Vector3D -> Vector2D完成

Block -> [[Vector3D]] -> [[Vector2D]] -> [Shape] -完成

Map -> [Location] -> [[[Block]]]

但是我不知道應該如何遏制大腸埃希氏菌的檢測,以及如何進行控制。

我的目標語言是js但是給出的答案是其他語言還是只是描述如何解決此問題都沒有關系。
不允許使用框架。

我正在使用2d畫布背景。

在這里您可以看到我的代碼。

對於這些鏈接和建議,我也將不勝感激。

表面和法線。

忽略一些假設,僅在3D空間中工作。

  • 所有塊均與x,y軸對齊。
  • 所有塊都有每個角的高度。
  • 所有塊都可以在一個平面上有一個四邊形,也可以分成兩個三角形。
  • 拆分塊從左上角到右下角或從右上角到左下角拆分。
  • 四邊形(未分割的塊)的4個高度點必須在同一平面上。
  • 塊始終為1單位寬度(x軸)和1單位深度(y軸)。
  • 可能有一個無效的塊。 要成為有效塊,每個面至少要有2個點具有相同的高度。 所有高度都設置為不同值的塊不是有效塊。 (這符合視頻中的游戲條件)

每個塊由一個對象定義,該對象保留位置(x,y,z),角高,類型(split1,split2或quad)和曲面范數/ s

單個函數將返回塊上x,y處的點的高度。 此時,塊pNorm屬性將設置為表面法線。 (請勿修改常規。如果需要修改它,請先創建一個副本)

表面法線是垂直於平面的線。 進行高度測試時,將屬性block.pNorm設置為適當的法線。 法線用於確定球應該滾動的方向。 (我沒有包含任何z運動,球被粘在了表面上)。 法線還用於確定陰影以及球反彈的方向。

演示

最好的解釋方法是通過演示。 有很多代碼可以使演示進行,因此請務必詢問您是否有任何問題。

請注意,該代碼是用少量ES6編寫的,因此需要babel才能在舊版瀏覽器上運行。

更新第一篇文章我有一個我沒有發現的錯誤(法線設置不正確)。 我已經修復了。 我還添加了一個錯誤,如果地圖包含無效塊,該錯誤將引發RangeError。

 var canvas = document.createElement("canvas"); canvas.width = 500; canvas.height = 300; var ctx = canvas.getContext("2d"); document.body.appendChild(canvas); // block types const types = { quad : 1, split1 : 2, // split from top left to bottom right split2 : 3, // split from top right to bottom left } /* // A block object example to define meaning of properties var blockObject = { x : 0, // top left base x pos y : 0, // top left base y pos z : 0, // top left base z pos norm1, // normal of quad or top right or bottom right triangles norm2, // normal of quad or top left or bottom left triangles p1 : 0, // top left p2 : 0, // top right p3 : 0, // bottom right p4 : 0, // bottom left type : types.quad, pNorm : null, // this is set when a height test is done. It is the normal at the point of the height test }*/ // compute the surface normal from two vectors on the surface. (cross product of v1,v2) function getSurfaceNorm(x1,y1,z1,x2,y2,z2){ // normalise vectors var d1= Math.hypot(x1,y1,z1); x1 /= d1; y1 /= d1; z1 /= d1; var d2= Math.hypot(x2,y2,z2); x2 /= d2; y2 /= d2; z2 /= d2; var norm = {} norm.x = y1 * z2 - z1 * y2; norm.y = z1 * x2 - x1 * z2; norm.z = x1 * y2 - y1 * x2; return norm; } // This defines a block with p1-p2 the height of the corners // starting top left and clockwise around to p4 bottom left // If the block is split with 2 slopes then it will be // of type.split1 or type.split2. If a single slope then it is a type.quad // Also calculates the normals function createBlock(x,y,z,h1,h2,h3,h4,type){ var norm1,norm2; if(type === types.quad){ norm1 = norm2 = getSurfaceNorm(1, 0, h2 - h1, 0, 1, h4 - h1); }else if(type === types.split1){ norm1 = getSurfaceNorm(1, 0, h2 - h1, 1, 1, h3 - h1); norm2 = getSurfaceNorm(0, 1, h2 - h1, 1, 1, h3 - h1); }else{ norm1 = getSurfaceNorm(0, 1, h2-h3, 1, 0, h4 - h3); norm2 = getSurfaceNorm(1, 0, h2 - h1, 0, 1, h4 - h1); } return { p1 : h1, // top left p2 : h2, // top right p3 : h3, // bottom right p4 : h4, // bottom left x,y,z,type, norm1,norm2, } } // get the height on the block at x,y // also sets the surface block.pNorm to match the correct normal function getHeight(block,x,y){ var b = block; // alias to make codes easier to read. if(b.type === types.quad){ b.pNorm = b.norm1; if(b.p1 === b.p2){ return (b.p3 - b.p1) * (y % 1) + b.p1 + bz; } return (b.p2 - b.p1) * (x % 1) + b.p1 + bz; }else if(b.type === types.split1){ if(x % 1 > y % 1){ // on top right side b.pNorm = b.norm1; if(b.p1 === b.p2){ if(b.p1 === b.p3){ return b.p1 + bz; } return (b.p3 - b.p1) * (y % 1) + b.p1 + bz; } if(b.p2 === b.p3){ return (b.p2 - b.p1) * (x % 1) + b.p1 + bz; } return (b.p3 - b.p2) * (y % 1) + (b.p2 - b.p1) * (x % 1) + b.p1 + bz; } // on bottom left size b.pNorm = b.norm2; if(b.p3 === b.p4){ if(b.p1 === b.p3){ return b.p1 + bz; } return (b.p3 - b.p1) * (y % 1) + b.p1 + bz; } if(b.p1 === b.p4){ return (b.p3 - b.p1) * (x % 1) + b.p1 + bz; } var h = (b.p4 - b.p1) * (y % 1); var h1 = b.p3 - (b.p4 - b.p1) + h; return (h1 - (h + b.p1)) * (x % 1) + (h + b.p1) + bz; } if(1 - (x % 1) < y % 1){ // on bottom right side b.pNorm = b.norm1; if(b.p3 === b.p4){ if(b.p3 === b.p2){ return b.p2 + bz; } return (b.p3 - b.p2) * (y % 1) + b.p4 + bz; } if(b.p2 === b.p3){ return (b.p4 - b.p2) * (x % 1) + b.p2 + bz; } var h = (b.p3 - b.p2) * (y % 1); var h1 = b.p4 - (b.p3 - b.p2) + h; return (h + b.p2 - h1) * (x % 1) + h1 + bz; } // on top left size b.pNorm = b.norm2; if(b.p1 === b.p2){ if(b.p1 === b.p4){ return b.p1 + bz; } return (b.p4 - b.p1) * (y % 1) + b.p1 + bz; } if(b.p1 === b.p4){ return (b.p2 - b.p1) * (x % 1) + b.p1 + bz; } var h = (b.p4 - b.p1) * (y % 1); var h1 = b.p2 + h; return (h1 - (h + b.p1)) * (x % 1) + (h + b.p1) + bz; } const projection = { width : 20, depth : 20, // y axis height : 8, // z axis xSlope : 0.5, ySlope : 0.5, originX : canvas.width / 2, originY : canvas.height / 4, toScreen(x,y,z,point = [],pos = 0){ point[pos] = x * this.width - y * this.depth + this.originX; point[pos + 1] = x * this.width * this.xSlope + y * this.depth * this.ySlope -z * this.height + this.originY; return point; } } // working arrays to avoid excessive GC hits var pointArray = [0,0] var workArray = [0,0,0,0,0,0,0,0,0,0,0,0,0,0]; function drawBlock(block,col,lWidth,edge){ var b = block; ctx.strokeStyle = col; ctx.lineWidth = lWidth; ctx.beginPath(); projection.toScreen(bx, by, bz + b.p1, workArray, 0); projection.toScreen(bx + 1, by, bz + b.p2, workArray, 2); projection.toScreen(bx + 1, by + 1, bz + b.p3, workArray, 4); projection.toScreen(bx, by + 1, bz + b.p4, workArray, 6); if(b.type === types.quad){ ctx.moveTo(workArray[0],workArray[1]); ctx.lineTo(workArray[2],workArray[3]); ctx.lineTo(workArray[4],workArray[5]); ctx.lineTo(workArray[6],workArray[7]); ctx.closePath(); }else if(b.type === types.split1){ ctx.moveTo(workArray[0],workArray[1]); ctx.lineTo(workArray[2],workArray[3]); ctx.lineTo(workArray[4],workArray[5]); ctx.closePath(); ctx.moveTo(workArray[0],workArray[1]); ctx.lineTo(workArray[4],workArray[5]); ctx.lineTo(workArray[6],workArray[7]); ctx.closePath(); }else if(b.type === types.split2){ ctx.moveTo(workArray[0],workArray[1]); ctx.lineTo(workArray[2],workArray[3]); ctx.lineTo(workArray[6],workArray[7]); ctx.closePath(); ctx.moveTo(workArray[2],workArray[3]); ctx.lineTo(workArray[4],workArray[5]); ctx.lineTo(workArray[6],workArray[7]); ctx.closePath(); } if(edge){ projection.toScreen(bx + 1, by, bz, workArray, 8); projection.toScreen(bx + 1, by + 1, bz, workArray, 10); projection.toScreen(bx, by + 1, bz, workArray, 12); if(edge === 1){ // right edge ctx.moveTo(workArray[2],workArray[3]); ctx.lineTo(workArray[8],workArray[9]); ctx.lineTo(workArray[10],workArray[11]); ctx.lineTo(workArray[4],workArray[5]); } if(edge === 2){ // right edge ctx.moveTo(workArray[4],workArray[5]); ctx.lineTo(workArray[10],workArray[11]); ctx.lineTo(workArray[12],workArray[13]); ctx.lineTo(workArray[6],workArray[7]); } if(edge === 3){ // right edge ctx.moveTo(workArray[2],workArray[3]); ctx.lineTo(workArray[8],workArray[9]); ctx.lineTo(workArray[10],workArray[11]); ctx.lineTo(workArray[12],workArray[13]); ctx.lineTo(workArray[6],workArray[7]); ctx.moveTo(workArray[10],workArray[11]); ctx.lineTo(workArray[4],workArray[5]); } } ctx.stroke(); } function createMap(){ var base = "0".charCodeAt(0); for(var y = 0; y < mapSize.depth; y ++){ for(var x = 0; x < mapSize.width; x ++){ var index = y * (mapSize.width + 1) + x; var b; var p1= map.charCodeAt(index)-base; var p2= map.charCodeAt(index+1)-base; var p3= map.charCodeAt(index+1+mapSize.width + 1)-base; var p4= map.charCodeAt(index+mapSize.width + 1)-base; var type; if((p1 === p2 && p3 === p4) || (p1 === p4 && p2 === p3)){ type = types.quad; }else if(p1 === p3){ type = types.split1; }else if(p4 === p2){ type = types.split2; }else{ // throw new RangeError("Map has badly formed block") type = types.split2; } blocks.push( b = createBlock( x,y,0,p1,p2,p3,p4,type ) ); } } } function drawMap(){ for(var i = 0; i < blocks.length; i ++){ var edge = 0; if(i % mapSize.width === mapSize.width- 1){ edge = 1; } if(Math.floor(i / mapSize.width) === mapSize.width- 1){ edge |= 2; } drawBlock(blocks[i],"black",1,edge); } } function drawBallShadow(ball){ var i; var x,y,ix,iy; ctx.globalAlpha = 0.5; ctx.fillStyle = "black"; ctx.beginPath(); var first = 0; for(var i = 0; i < 1; i += 1/8){ var ang = i * Math.PI * 2; x = ball.x + (ball.rad / projection.width ) * Math.cos(ang) * 0.7; y = ball.y + (ball.rad / projection.depth ) * Math.sin(ang) * 0.7; if(x < mapSize.width && x >= 0 && y < mapSize.depth && y > 0){ ix = Math.floor(x + mapSize.width) % mapSize.width; iy = Math.floor(y + mapSize.depth) % mapSize.depth; var block = blocks[ix + iy * mapSize.width]; var z = getHeight(block,x,y); projection.toScreen(x,y,z, pointArray); if(first === 0){ first = 1; ctx.moveTo(pointArray[0],pointArray[1]); }else{ ctx.lineTo(pointArray[0],pointArray[1]); } } } ctx.fill(); ctx.globalAlpha = 1; } function drawBall(ball){ projection.toScreen(ball.x, ball.y, ball.z, pointArray); ctx.fillStyle = ball.col; ctx.strokeStyle = "black"; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(pointArray[0],pointArray[1],ball.rad,0,Math.PI * 2); ctx.stroke(); ctx.fill(); ctx.fillStyle = "white"; ctx.beginPath(); ctx.arc(pointArray[0]-ball.rad/2,pointArray[1]-ball.rad/2,ball.rad/4,0,Math.PI * 2); ctx.fill(); } function updateBall(ball){ // reset ball if out of bounds; if(ball.x > mapSize.width || ball.y > mapSize.depth || ball.x < 0 || ball.y < 0){ ball.x += ball.dx; ball.y += ball.dy; ball.z += ball.dz; ball.dz -= 0.1; if(ball.z < -10){ ball.x = Math.random() * 3; ball.y = Math.random() * 3; ball.dz = 0; // give random speed ball.dx = Math.random() * 0.01; ball.dy = Math.random() * 0.01; }else{ ball.dist = Math.hypot(ball.x - 20,ball.y - 20, ball.z - 20); return; } } // get the block under the ball var block = blocks[Math.floor(ball.x) + Math.floor(ball.y) * mapSize.width]; const lastZ = ball.z; // get the height of the black at the balls position ball.z = getHeight(block,ball.x,ball.y); // use the face normal to add velocity in the direction of the normal ball.dx += block.pNorm.x * 0.01; ball.dy += block.pNorm.y * 0.01; // move the ball up by the amount of its radius ball.z += ball.rad / projection.height; ball.dz =lastZ - ball.z; // draw the shadow and ball ball.x += ball.dx; ball.y += ball.dy; // get distance from camera; ball.dist = Math.hypot(ball.x - 20,ball.y - 20, ball.z - 20); } function renderBall(ball){ drawBallShadow(ball); drawBall(ball); } function copyCanvas(canvas){ var can = document.createElement("canvas"); can.width = canvas.width; can.height = canvas.height; can.ctx = can.getContext("2d"); can.ctx.drawImage(canvas,0,0); return can; } var map = ` 9988888789999 9887787678999 9877676567899 9876765678789 9876655567789 8766555456789 7655554456678 6654443456789 6543334566678 5432345677889 4321234567789 4321234567899 5412345678999 `.replace(/\\n| |\\t/g,""); var mapSize = {width : 12, depth : 12}; // one less than map width and depth var blocks = []; ctx.clearRect(0,0,canvas.width,canvas.height) createMap(); drawMap(); var background = copyCanvas(canvas); var w = canvas.width; var h = canvas.height; var cw = w / 2; // center var ch = h / 2; var globalTime; // global to this var balls = [{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "red", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "Green", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "Blue", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "yellow", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "cyan", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "black", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "white", rad : 10, },{ x : -10, y : 0, z : 100, dx : 0, dy : 0, dz : 0, col : "orange", rad : 10, } ]; // main update function function update(timer){ globalTime = timer; ctx.setTransform(1,0,0,1,0,0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0,0,w,h); ctx.drawImage(background,0,0); // get the block under the ball for(var i = 0; i < balls.length; i ++){ updateBall(balls[i]); } balls.sort((a,b)=>b.dist - a.dist); for(var i = 0; i < balls.length; i ++){ renderBall(balls[i]); } requestAnimationFrame(update); } requestAnimationFrame(update); 

要使用塊(類似於立方體,但允許傾斜的壁)處理點x的碰撞檢測,請從定義塊的3個點a,b,c中減去點x,然后計算點的矢量積向量x與塊點向量a',b',c'一起給您3個數字A,B,C。 如果這些數字在零與向量a,b,c的長度分別成平方之間,則向量x在塊內。 或換一種說法,每個A /(長度a'平方),B /(長度b'平方),C /(長度c'平方)小於或等於1且大於零。

同樣對四面體也是如此,但附加條件是A /(長度a'平方)+ B /(長度b'平方)+ C /(長度c'平方)小於或等於1。

我希望這沒有錯,這就是我要解決的問題。 這也不能處理兩個四面體邊對邊的碰撞。 因此,我在這里所說的還有改進的余地。 特別是,對帶有立方體或四面體的邊緣進行碰撞檢查會很好。

對於其他形狀,請將這些形狀分為立方體和四面體。

一般

我認為對於此游戲,計算游樂場/基地的表面就足夠了。 然后,您可以為曲面上的每個點計算所有方向的導數。 由於它們都是平面,因此可以簡單地表示為常數矩陣。

玩家始終處於f( x, y ) = height並更改∇f( int(x), int(z) ) + v其中v是玩家輸入的力矢量。

變化為∞(實心牆)的邊界必須分別通過例如來處理。 計算f(x,y)中的“跳躍”。


更新1

途徑

因此,基本上,您可以創建以下功能:

  • function height(x,y) ->返回整數
    • 給出位置(x,y)的底座高度
  • function slope(x,y) ->返回兩個整數的元組(包括無窮大)
    • 給出在x方向和y方向上位置(x,y)的底坡度

“玩家”從位置(x_0, y_0)開始,因此身高z_0 = height(x_0, y_0)

然后下一個位置將是(x_1, y_1) = (x_0, y_0) + s_g * slope(x_0, y_0) + s_u * v ,高度z_1 = height(x_1, y_1)
v :代表用戶想要應用到“玩家”上的運動方向的矢量。
s_g :速度/〜重力,
s_u :用戶控制播放器的速度。

為了正確處理重力,必須將slope函數的方向反轉為正方向。

為了避免玩家上升90度。 斜率:只需將斜率函數的+infinity值設置為0。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM