【问题标题】:Canvas get perspective point画布获取透视点
【发布时间】:2017-01-12 21:11:34
【问题描述】:

我在 div #content 中有一个 canvas dom 元素,带有变换 rotateX(23deg) 和 #view 的透视为 990px​​

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

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

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

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

感谢您的支持。

【问题讨论】:

  • 本来打算添加答案,但新的 DOMMatrix、DOMMatrixReadOnly 接口与任何文档都不相似 w3.org/TR/2013/WD-matrix-20130919 值得注意的是构造函数 new DOMMatrix(element.style.transform) 抛出错误(它不应该)并且 DOMMatrix.invert() 是尽管invertSelf 是未定义的,但它没有按预期运行。并且没有设置视角的界面。你必须 JS 转换,你可以在 github 上找到一些好的 3D 矩阵库
  • 我肯定会使用 JS,我正在努力寻找有用的东西。我发现的是正确的方程式或步骤,而不是真正的 js 库。
  • 我认为我不需要这种方法。我想知道数学/代数步骤。输入:originalPoint 和degrees 输出:透视中的新点坐标 谢谢!
  • @Blindman67,所以我只是做了一个快速测试,我设法使用inverse()(不是invert)方法获得了正确的DOMMatrix 对象。您需要传递一个包含元素所有点的数组,而不仅仅是其样式属性 (var mat = new DOMMatrix(getComputedStyle(element).transform.split('(')[1].split(')')[0].split(','))) 的字符串。我希望它能帮助你写下你的答案,我想看看;-)
  • @Kaiido DOMMatrix 继承自 DOMMatrixReadOnly,所以 invert 应该可以从 DOMMatrix 对象调用。我使用var mat = new DOMMatrix().setMatrixValue(test.style.transform); 来获取元素的转换并至少在 Chrome 上工作。剩下的我暂时放弃,因为 MDN 只是断开的链接,标准 DOC 并没有反映实现。

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


【解决方案1】:

替代解决方案。

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

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

注意:我找不到太多关于 CSS 透视数学及其实现方式的信息,所以这只是我的猜测。

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

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

确保画布分辨率与显示尺寸相匹配。如果不是,您将需要缩放轴和结果以适应。

演示

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

注意代码在 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};
}
 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-08
    • 2016-01-14
    • 2023-04-09
    • 1970-01-01
    • 2012-12-27
    • 2015-04-25
    • 2019-08-18
    相关资源
    最近更新 更多