画布获得透视点

时间:2017-01-12 21:11:34

标签: javascript canvas matrix linear-algebra pixi.js

我在div #content里面有一个canvas dom元素,变换rotateX(23deg)和#view with perspective 990px​​

<div id="view">
   <div id="content">
      <canvas></canvas>
   </div>
</div>

如果我在画布内绘制一个点(300,300),则投影坐标不同(350,250)。

真正的问题是当画布中绘制的对象是交互式的(单击或拖放)时,命中区域会被翻译。

我要使用哪个等式?某种矩阵?

感谢您的支持。

1 个答案:

答案 0 :(得分:0)

替代解决方案。

解决问题的一种方法是将光线从鼠标跟踪到页面中,并在画布上找到该光线截取的点。

您需要转换画布的x和y轴以匹配其变换。您还必须将光线从所需点投射到透视点。 (由x,y,z定义,其中z是透视CSS值)

  

注意:我找不到很多关于CSS透视数学的信息以及它是如何实现的,所以它只是猜测我的工作。

涉及到很多数学,我必须构建一个快速的3dpoint对象来管理它。我会警告你它没有很好的设计(我没有时间在需要的地方内联它)并且会导致重大的GC收费。你应该重写射线拦截并删除所有的点克隆调用和重用点,而不是每次需要时都创建新的。

有一些捷径。光线/面部截距假设定义面的3个点是实际的x和y轴,但它不会检查是否如此。如果您的轴错误,则无法获得正确的像素坐标。此外,返回的坐标相对于点face.p1(0,0)并且在0-1的范围内,其中0 <= x <= 1并且0 <= y <= 1是画布上的点

确保画布分辨率与显示尺寸相匹配。如果不是,则需要缩放轴并使结果适合。

样本

演示项目一组点在画布中心创建一个交叉点。您会注意到投影圆的半径将根据与相机的距离而变化。

  

注意代码在ES6中,并且要求Babel在旧版浏览器上运行。

var divCont = document.createElement("div");
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center 
var ch = h / 2;
var ctx = canvas.getContext("2d");
// perspectiveOrigin
var px = cw;  // canvas center
var py = 50;  //
// perspective  
var pd = 700;
var mat;

divCont.style.perspectiveOrigin = px + "px "+py+"px";
divCont.style.perspective = pd + "px";
divCont.style.transformStyle = "preserve-3d";
divCont.style.margin = "10px";
divCont.style.border = "1px black solid";
divCont.style.width = (canvas.width+8) + "px";
divCont.style.height = (canvas.height+8) + "px";


divCont.appendChild(canvas);
document.body.appendChild(divCont);

function getMatrix(){  // get canvas matrix
    if(mat === undefined){
        mat = new DOMMatrix().setMatrixValue(canvas.style.transform);
    }else{
        mat.setMatrixValue(canvas.style.transform);
    }
}
function getPoint(x,y){  // get point on canvas
    var ww = canvas.width;
    var hh = canvas.height;
    var face = createFace(
        createPoint(mat.transformPoint(new DOMPoint(-ww / 2, -hh / 2))),
        createPoint(mat.transformPoint(new DOMPoint(ww / 2, -hh / 2))),
        createPoint(mat.transformPoint(new DOMPoint(-ww / 2, hh / 2)))
    );
    var ray = createRay(
        createPoint(x - ww / 2, y - hh / 2, 0),
        createPoint(px - ww / 2, py - hh / 2, pd)
    );
    return intersectCoord3DRayFace(ray, face);
    
}

// draw point projected onto the canvas
function drawPoint(x,y){
    var p = getPoint(x,y);
    if(p !== undefined){
        p.x *= canvas.width;
        p.y *= canvas.height;
        ctx.beginPath();
        ctx.arc(p.x,p.y,8,0,Math.PI * 2);
        ctx.fill();
    }
}

// main update function
function update(timer){
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.fillStyle = "green";
    ctx.fillRect(0,0,w,h);
    ctx.lineWidth = 10;
    ctx.strokeRect(0,0,w,h);
    canvas.style.transform = "rotateX("+timer/100+"deg)" + " rotateY("+timer/50+"deg)";
    getMatrix();
    ctx.fillStyle = "gold";
    drawPoint(cw,ch);
    for(var i = -200; i <= 200; i += 40){
        drawPoint(cw + i,ch);
        drawPoint(cw ,ch + i);
    }
    requestAnimationFrame(update);
}
requestAnimationFrame(update);



// Math functions to find x,y pos on plain.
// Warning this code is not built for SPEED and will incure a lot of GC hits

const small = 1e-6; 
var pointFunctions = {
    add(p){
        this.x += p.x;
        this.y += p.y;
        this.z += p.z;
        return this;
    },
    sub(p){
        this.x -= p.x;
        this.y -= p.y;
        this.z -= p.z;
        return this;
    },
    mul(mag){
        this.x *= mag;
        this.y *= mag;
        this.z *= mag;
        return this;
    },
    mag(){ // get length
        return Math.hypot(this.x,this.y,this.z);
    },
    cross(p){
         var p1 = this.clone();
         p1.x = this.y * p.z - this.z * p.y;
         p1.y = this.z * p.x - this.x * p.z;
         p1.z = this.x * p.y - this.y * p.x;
         return p1; 
    },
    dot(p){
         return this.x * p.x + this.y * p.y + this.z * p.z; 
    },   
    isZero(){
        return Math.abs(this.x) < small && Math.abs(this.y) < small && Math.abs(this.z) < small;
    },
    clone(){
        return Object.assign({
            x : this.x,
            y : this.y,
            z : this.z,
        },pointFunctions);
    }
    
}
function createPoint(x,y,z){
    if(y === undefined){ // quick add overloaded for DOMPoint
        y = x.y;
        z = x.z;
        x = x.x;
    }
    return Object.assign({
        x, y, z,
    }, pointFunctions);
}
function createRay(p1, p2){
    return { p1, p2 };
}
function createFace(p1, p2, p3){
    return { p1,p2, p3 };
}

 


// Returns the x,y coord of ray intercepting face
// ray is defined by two 3D points and is infinite in length
// face is 3 points on the intereceptin plane
// For correct intercept point face p1-p2 should be at 90deg to p1-p3 (x, and y Axis)
// returns unit coordinates x,y on the face with the origin at face.p1
// If there is no solution then returns undefined
function intersectCoord3DRayFace(ray, face ){
    var u = face.p2.clone().sub(face.p1);
    var v = face.p3.clone().sub(face.p1);
    var n = u.cross(v);
    if(n.isZero()){
        return; // return undefined
    }
    var vr = ray.p2.clone().sub(ray.p1);
    var b = n.dot(vr);
    if (Math.abs(b) < small) {     // ray is  parallel face
        return; // no intercept return undefined
    }
    var w = ray.p1.clone().sub(face.p1);
    var a = -n.dot(w);
    var uDist = a / b;
    var intercept = ray.p1.clone().add(vr.mul(uDist));  // intersect point
    var uu = u.dot(u);
    var uv = u.dot(v);
    var vv = v.dot(v);
    var dot = uv * uv - uu * vv;
    w = intercept.clone().sub(face.p1);
    var wu = w.dot(u);
    var wv = w.dot(v);
    var x = (uv * wv - vv * wu) / dot;
    var y = (uv * wu - uu * wv) / dot;
    return {x,y};
}