如何使用 3d 对象创建碰撞检测?

马切伊·科齐耶

我正在重新制作一个名为spindizzyatari 8bit游戏作为我的学校项目。

我想出了如何使用强制转换3d points2d perspective point.

我也可以旋转形状(渲染顺序的小问题)

我的目标是这样的:

Vector3D -> Vector2D - 完毕

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

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

但我不知道应该如何处理碰撞检测,以及如何处理它。

我的目标语言是,js但给出的答案是其他语言还是只是描述如何解决此问题都没有关系。
不允许使用框架。

我正在使用2d画布的上下文。

在这里你可以看到我的代码。

我将非常感谢链接和建议。

盲人67

曲面和法线。

忽略投影并仅在具有某些假设的 3D 空间中工作。

  • 所有块都与 x,y 轴对齐。
  • 所有块都有每个角的高度。
  • 所有块都可以有一个平面或四边形,也可以分成两个三角形。
  • 拆分块要么从左上角到右下角拆分,要么从右上角到左下角拆分。
  • Quad (unsplit block) 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 + b.z;
        }
        return (b.p2 - b.p1) * (x % 1) + b.p1 + b.z;
    }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 + b.z;
                }
                return (b.p3 - b.p1) * (y % 1) + b.p1 + b.z;
            }
            if(b.p2 === b.p3){
                return (b.p2 - b.p1) * (x % 1) + b.p1 + b.z;
            }
            return (b.p3 - b.p2) * (y % 1) + (b.p2 - b.p1) * (x % 1) + b.p1 + b.z;
        }
        // on bottom left size
        b.pNorm = b.norm2;
        if(b.p3 === b.p4){
            if(b.p1 === b.p3){
                return b.p1 + b.z;
            }
            return (b.p3 - b.p1) * (y % 1) + b.p1 + b.z;
        }
        if(b.p1 === b.p4){
            return (b.p3 - b.p1) * (x % 1) + b.p1 + b.z;
        }
        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) + b.z;
    }        
    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 + b.z;
            }
            return (b.p3 - b.p2) * (y % 1) + b.p4 + b.z;
        }
        if(b.p2 === b.p3){
            return (b.p4 - b.p2) * (x % 1) + b.p2 + b.z;
        }
        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 + b.z;
    }
    // on top left size
    b.pNorm = b.norm2;    
    if(b.p1 === b.p2){
        if(b.p1 === b.p4){
            return b.p1 + b.z;
        }
        return (b.p4 - b.p1) * (y % 1) + b.p1 + b.z;
    }
    if(b.p1 === b.p4){
        return (b.p2 - b.p1) * (x % 1) + b.p1 + b.z;
    }
    var h = (b.p4 - b.p1) * (y % 1);
    var h1 = b.p2 + h;
    return (h1 - (h + b.p1)) * (x % 1) + (h + b.p1) + b.z;

}
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(b.x,     b.y,     b.z + b.p1, workArray, 0);
    projection.toScreen(b.x + 1, b.y,     b.z + b.p2, workArray, 2);
    projection.toScreen(b.x + 1, b.y + 1, b.z + b.p3, workArray, 4);
    projection.toScreen(b.x,     b.y + 1, b.z + 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(b.x + 1, b.y,     b.z, workArray, 8);
        projection.toScreen(b.x + 1, b.y + 1, b.z, workArray, 10);
        projection.toScreen(b.x,     b.y + 1, b.z, 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);

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何设置SceneKit碰撞检测

来自分类Dev

在ActionScript 3中,碰撞检测的性能如何?

来自分类Dev

使用TiledMap进行Libgdx碰撞检测

来自分类Dev

XNA 3D边界框碰撞检测方法

来自分类Dev

碰撞检测如何?

来自分类Dev

D3:如何为项目分配大小和颜色(碰撞检测可视化)

来自分类Dev

Java中使用Vector3f进行碰撞检测操作

来自分类Dev

LibGDX 3D碰撞检测

来自分类Dev

如何在libgdx中获得旋转对象的碰撞检测

来自分类Dev

如何创建圆碰撞检测?C ++

来自分类Dev

使用Physics的碰撞检测在cocos2dx v3中不起作用

来自分类Dev

Unity 2D碰撞检测

来自分类Dev

使用Java,Slick2D和Tiled Map Editor进行碰撞检测

来自分类Dev

使用Kinect的3D对象跟踪检测

来自分类Dev

Unity:使用标签的2D碰撞检测

来自分类Dev

如何在pygame中使用图像进行碰撞检测

来自分类Dev

使用MatGeoLib进行3D碰撞检测?

来自分类Dev

在ActionScript 3中,碰撞检测的性能如何?

来自分类Dev

XNA 3D边界框碰撞检测方法

来自分类Dev

如何在LIBGDX中使用曲线检查碰撞检测?

来自分类Dev

位图碰撞检测(AS3)

来自分类Dev

在Java中使用Vector3f进行碰撞检测操作

来自分类Dev

在将GameObject与Unity 3D拖动时,如何使用GameObject与BoxCollider 2D碰撞时如何检测碰撞

来自分类Dev

如何在2D平台游戏中对碰撞检测做出反应

来自分类Dev

使用物理身体编辑器的Libgdx Box2d碰撞检测

来自分类Dev

具有顶点坐标的3D中的Java碰撞检测

来自分类Dev

如何使用碰撞检测更改画布fillStyle?

来自分类Dev

使用Kinect的3D对象跟踪检测

来自分类Dev

如何修复此碰撞检测?