2011-01-14 75 views
1

我寫在畫布上一個小的3D「引擎」 ..不是一個真正的引擎,但更多的是基於代碼Eric Pascarello's demo的應用程序(命中數「7」幾次看到它旋轉)如何檢測3D立方體的哪一側被點擊?

這一切都非常好,我甚至增加了每個平面(「邊」)的平均z值的排序 - 這樣我就可以使用由多個立方體組成的物體,以便最後繪製接近相機的形狀。

現在,我想檢測哪一側被點擊,當鼠標點擊畫布時。 像this

提示:

  1. 每側由4個(X,Y,Z)的角落定義
  2. 每個角使用的是什麼,我相信是有規律的 「透視投影」
  3. 吸引到畫布

這裏是我的代碼 - 它的冷靜:使用鼠標旋轉

//edit: see the code in my answer below 
+1

只是一個挑逗 - 如果你使用你的DOCTYPE應該是<!DOCTYPE html>的HTML5 – 2011-01-14 14:00:14

+0

如果只有我編碼它時,HTML5存在。 :) – epascarello 2011-01-14 15:36:01

回答

1

完美!猜我需要問這個問題,所以我可以回答自己:-)

該解決方案是基於一些令人驚歎的片段,我發現用於檢測多邊形中的點的存在。這是2D功能,但是嘿......我的畫布也是 - 傻了我。

這裏是完整的,跨瀏覽器:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html> 
    <head> 
     <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> 
     <title>קובייה</title> 

     <link rel="shortcut icon" href="Rubiks.png" /> 
     <script>var isIE = false</script> 
     <!--[if IE]> 
      <script language="javascript" type="text/javascript" src="../js_canvas/excanvas_r69.js"></script> 
      <script>isIE = true</script> 
     <![endif]-->   
     <style> 
      body { 
       padding: 2px; 
       font-family: arial; 
      } 
      canvas { 
       border:1px solid black; 
       background:white; 
       cursor:default; 
      } 
      .move { 
       cursor:move; 
      } 
      div, td, input { 
       font-size:15px; 
      } 
      .header { 
       font-size:22px; 
       font-weight:bold; 
       margin-bottom:10px; 
      } 
      .subHeader { 
       font-size:16px; 
      } 
      .tblHeader { 
       background:#003f00; 
       color:white; 
       font-weight:bold; 
       font-size:24px; 
       border:0px; 
      } 
      .info { 
       background:lightyellow; 
       border:1px solid black; 
       font-size:15px; 
       width:350px; 
      } 
      .opac { 
       /* 
       opacity: .85; 
       filter: alpha(opacity=85); 
       -ms-filter: "alpha(opacity=85)"; 
       -khtml-opacity: .85; 
       -moz-opacity: .85; 
       */ 
      }   
      .btn { 
       color: black; 
       display: inline-block; 
       width:100px; 
       border: 2px outset #ddd; 
       text-decoration:none; 
       padding:2px; 
       background: #ddd; 
       text-align:center; 
       font-family: arial; 
       font-size:12px; 
       font-weight:bold; 
      } 
      .btn_hover { 
       color: blue; 
      } 
      .btn_down { 
       border: 2px inset #ddd; 
      } 

     </style> 

    </head> 

<script type="text/javascript"> 
// cube code shared by Eric Pascarello 
var sideLength = 50; 
var width = 600; 
var height = 450; 
var center = new Point(width/2, height/2) 
var perspective = sideLength * 16; 
var xzRotation = -Math.PI/2; 
var yzRotation = 0; 
var xyRotation = 0; 
var isColored = true; 
var cube, calcCube, lgth 
var canvas, ctx, animation 
var mouse = new Point(0,0); 
var absMouse = new Point(0,0) 
var clickedMouse = new Point(0,0) 
var posCanvas 
var clickRGB = [248, 128, 23]  // orange = #F88017 
var clickRGB = [255,192,203]  // pink 
var arrPolygons = [] 
var arrSortedIndex 


/** cube stuff **/ 
function rotate(bForce) { 
    if (!bForce && (this.last_xyRotation == xyRotation && 
     this.last_xzRotation == xzRotation && 
     this.last_yzRotation == yzRotation 
     || !dragCube)) { 
     return 
    } 

    var drawStyle = getRadioValue("drawStyle")  // color, bw, or trans 
    if (drawStyle=="trans") { 
     $("chkWire").disabled = true 
     $("chkWire").checked = true 
    } else { 
     $("chkWire").disabled = false 
    } 

    // rotate cube into calcCube. also set colors. 
    for (var i=0; i<lgth; i++) { 
     var side = cube.sides[i]; 
     var calcSide = calcCube.sides[i]; 
     var avgZ = 0 
     var side_polygon = [] 
     for (var j=0 ; j<4; j++){ 
      var corner = side.corners[j]; 
      var calc1 = calc(corner.x, corner.y, xyRotation, 1); 
      var calc2 = calc(calc1.p1, corner.z, xzRotation, 1); 
      var calc3 = calc(calc1.p2, calc2.p2, yzRotation, -1); 
      var x = (calc2.p1 * perspective)/(perspective - calc3.p2) + center.x; 
      var y = (calc3.p1 * perspective)/(perspective - calc3.p2) + center.y; 
      calcSide.corners[j].x = x 
      calcSide.corners[j].y = y 
      side_polygon.push (new Point(x, y)) 
      avgZ += calc3.p2 
     } 
     calcSide.avgZ = avgZ // /4 
     calcSide.polygon = side_polygon 

     var light = side.light; 
     var calc1 = calc(light.x, light.y, xyRotation, 1); 
     var calc2 = calc(calc1.p1, light.z, xzRotation, 1); 
     var calc3 = calc(calc1.p2, calc2.p2, yzRotation, -1); 
     calcSide.light = calc3.p2; 

     // decide color 
     var brightness = Math.floor(calcSide.light); 
     brightness = trimVal(brightness, 0, 255) 
     var colorRGB = [] 
     var colorCodeRGB = (side.clickState) ? clickRGB : (drawStyle=="color" ? side.color : "b,b,b").split(",") 
     for (var c=0; c<3; c++) { 
      colorRGB[c] = (colorCodeRGB[c]=="b") ? brightness : colorCodeRGB[c] 
     } 
     fillRGBA = "rgba(" + colorRGB + ",255)"; 
     calcSide.fillRGBA = fillRGBA 
    } 

    // sort sides by avgZ !!  
    arrSortedIndex = [] 
    for (var i=0; i<lgth; i++) { 
     arrSortedIndex[i] = i 
    } 
    for (var i=0; i<lgth-1; i++) { 
     for (var j=i+1; j<lgth; j++) { 
      if (calcCube.sides[i].avgZ > calcCube.sides[j].avgZ) { 
       var temp = calcCube.sides[i].avgZ 
       calcCube.sides[i].avgZ = calcCube.sides[j].avgZ 
       calcCube.sides[j].avgZ = temp 

       var temp = arrSortedIndex[i] 
       arrSortedIndex[i] = arrSortedIndex[j] 
       arrSortedIndex[j] = temp 
      } 
     } 
    } 


    // draw all sides 
    ctx.clearRect (0,0, width, height);  
    for (var i=0; i<lgth; i++) { 
     var calcSide = calcCube.sides[arrSortedIndex[i]]; 
     ctx.fillStyle = calcSide.fillRGBA 
     var corners = calcSide.corners; 
     ctx.beginPath(); 
     ctx.moveTo (corners[0].x, corners[0].y); 
     ctx.lineTo (corners[1].x, corners[1].y); 
     ctx.lineTo (corners[2].x, corners[2].y); 
     ctx.lineTo (corners[3].x, corners[3].y); 
     ctx.lineTo (corners[0].x, corners[0].y); 
     if (drawStyle!="trans") { 
      ctx.fill(); 
     } 
     if ($("chkWire").checked) { 
      ctx.stroke(); 
     } 
    } 

    this.last_xyRotation = xyRotation 
    this.last_xzRotation = xzRotation 
    this.last_yzRotation = yzRotation 
} 
function calc(p1,p2,ang,pn){ 

    var cosAng = Math.cos(ang); 
    var sinAng = Math.sin(ang); 

    var r1 = cosAng * p1 - pn * sinAng * p2; 
    var r2 = cosAng * p2 + pn * sinAng * p1; 

    return { "p1": r1,"p2":r2}; 
} 
function getCube(sideLength) { 

    var ret = { 
     sides : [ 
      { //FRONT 
      corners : [ {x:-1*sideLength, y: 1*sideLength, z: 1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z: 1*sideLength}, 
         {x: 1*sideLength, y:-1*sideLength, z: 1*sideLength}, 
         {x:-1*sideLength, y:-1*sideLength, z: 1*sideLength} ], 
      light : {x: 0, y: 0, z: 255 }, 
      color : "0,b,0" 
      }, 

      { //BACK 
      corners : [ {x:-1*sideLength, y: 1*sideLength, z:-1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z:-1*sideLength}, 
         {x: 1*sideLength, y:-1*sideLength, z:-1*sideLength}, 
         {x:-1*sideLength, y:-1*sideLength, z:-1*sideLength} ], 
      light : {x: 0, y: 0, z: -255 }, 
      color : "0,0,b" 
      }, 

      { //RIGHT 
      corners : [ {x: 1*sideLength, y: 1*sideLength, z: 1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z:-1*sideLength}, 
         {x: 1*sideLength, y:-1*sideLength, z:-1*sideLength}, 
         {x: 1*sideLength, y:-1*sideLength, z: 1*sideLength} ], 
      light : {x: 255, y: 0, z: 0 }, 
      color : "b,0,0" 
      }, 

      { //LEFT 
      corners : [ {x:-1*sideLength, y: 1*sideLength, z: 1*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z:-1*sideLength}, 
         {x:-1*sideLength, y:-1*sideLength, z:-1*sideLength}, 
         {x:-1*sideLength, y:-1*sideLength, z: 1*sideLength} ], 
      light : {x: -255, y: 0, z: 0}, 
      color : "0,b,b" 
      }, 

      { //top 
      corners : [ {x:-1*sideLength, y:-1*sideLength, z: 1*sideLength},  
         {x: 1*sideLength, y:-1*sideLength, z: 1*sideLength}, 
         {x: 1*sideLength, y:-1*sideLength, z:-1*sideLength}, 
         {x:-1*sideLength, y:-1*sideLength, z:-1*sideLength} ], 
      light : {x: 0, y:-255 , z: 0}, 
      color : "b,b,0" 
      }, 

      { //bottom 
      corners : [ {x:-1*sideLength, y: 1*sideLength, z: 1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z: 1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z:-1*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z:-1*sideLength} ], 
      light : {x: 0, y: 255, z: 0}, 
      color : "b,0,b" 
      }, 

      // anoter cube behind and above - my addition 

      { //FRONT 
      corners : [ {x:-1*sideLength, y: 3*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 3*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z: -1*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z: -1*sideLength} ], 
      light : {x: 0, y: 0, z: 255 }, 
      color : "0,b,0" 
      }, 

      { //BACK 
      corners : [ {x:-1*sideLength, y: 3*sideLength, z:-3*sideLength}, 
         {x: 1*sideLength, y: 3*sideLength, z:-3*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z:-3*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z:-3*sideLength} ], 
      light : {x: 0, y: 0, z: -255 }, 
      color : "0,0,b" 
      }, 

      { //RIGHT 
      corners : [ {x: 1*sideLength, y: 3*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 3*sideLength, z:-3*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z:-3*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z: -1*sideLength} ], 
      light : {x: 255, y: 0, z: 0 }, 
      color : "b,0,0" 
      }, 

      { //LEFT 
      corners : [ {x:-1*sideLength, y: 3*sideLength, z: -1*sideLength}, 
         {x:-1*sideLength, y: 3*sideLength, z:-3*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z:-3*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z: -1*sideLength} ], 
      light : {x: -255, y: 0, z: 0}, 
      color : "0,b,b" 
      }, 

      { //top 
      corners : [ {x:-1*sideLength, y: 1*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 1*sideLength, z:-3*sideLength}, 
         {x:-1*sideLength, y: 1*sideLength, z:-3*sideLength} ], 
      light : {x: 0, y:-255 , z: 0}, 
      color : "b,b,0" 
      }, 

      { //bottom 
      corners : [ {x:-1*sideLength, y: 3*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 3*sideLength, z: -1*sideLength}, 
         {x: 1*sideLength, y: 3*sideLength, z:-3*sideLength}, 
         {x:-1*sideLength, y: 3*sideLength, z:-3*sideLength} ], 
      light : {x: 0, y: 255, z: 0}, 
      color : "b,0,b" 
      }   

     ] 
    } 
    lgth = ret.sides.length; 

    calcCube = {sides:[]} 
    for (var i=0; i<lgth; i++) { 
     calcCube.sides[i] = {corners : [{},{},{},{}], light:0, avgZ:0} 
     ret.sides[0].clickState=false 
    } 
    return ret 
} 

/** 2d stuff **/ 
function Point(x,y) { 
    this.x = x 
    this.y = y 
} 
function Polygon() { 
    var ret = [] 
    for (var i=0; i<arguments.length; i++) { 
     ret.push (arguments[i]) 
    } 
    return ret 
} 
function isPointInPoly(poly, pt) { 
    // from http://snippets.dzone.com/posts/show/5295 - wow! 
    for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) 
     ((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y)) 
     && (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y)/(poly[j].y - poly[i].y) + poly[i].x) 
     && (c = !c); 
    return c; 
} 

/** event handlers **/ 
var dragCube = null 
function mouseMove(e) { 
    var posx = 0; 
    var posy = 0; 
    e = e || window.event; 
    if (isIE&&false) { 
     posx = e.offsetX 
     posy = e.offsetY 
    } else { 
     if (e.pageX || e.pageY) { 
      posx = e.pageX; 
      posy = e.pageY; 
     } else if (e.clientX || e.clientY) { 
      posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 
      posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 
     } 
     absMouse = (new Point(posx, posy)) 
    } 
    //$("debug").innerHTML = myStringify(absMouse) 
    if (dragCube) { 
     var diff = new Point (posx - dragCube.anchor.x, posy - dragCube.anchor.y) 
     if (!dragCube.moved) { 
      dragCube.moved = true 
      addClass ($("cv"), "move") 
     } 
     dragCube.anchor = new Point (posx, posy) 
     xzRotation -= diff.x/100 
     yzRotation += diff.y/100 
     //$("debug").innerHTML = myStringify(diff) 
    } 
} 
function mouseDown(e) { 
    e = e || window.event; 
    dragCube = { 
     anchor : new Point(absMouse.x, absMouse.y), 
     moved : false, 
     side: -1 
    } 
    var inside = new Point (absMouse.x - posCanvas.x, absMouse.y - posCanvas.y) 
    //for (var i=0; i<lgth; i++) { 
    for (var i=lgth-1; i>=0; i--) { 
     if (isPointInPoly(calcCube.sides[arrSortedIndex[i]].polygon, inside)) { 
      dragCube.side = arrSortedIndex[i] 
      break; 
     } 
    } 
    /* 
    ctx.beginPath() 
    ctx.arc(inside.x, inside.y, 1, 0, Math.PI*2, false);  
    ctx.stroke(); 
    */ 
} 
function mouseUp(e) { 
    if (dragCube && !dragCube.moved) { 
     var index = dragCube.side 
     if (index>=0) { 
      cube.sides[index].clickState = !cube.sides[index].clickState 
      rotate(true) 
     }  
    } 
    dragCube = null 
    removeClass ($("cv"), "move") 
} 

/** buttons **/ 
function addClass(objElement, strClass) { 
    if (!objElement) return; 
    if (objElement.className) { 
     removeClass(objElement, strClass); 
     objElement.className += ' '+strClass; 
    } else { 
     objElement.className = strClass; 
    } 
} 
function removeClass(objElement, strClass) { 
    if (!objElement) return; 
    if (objElement.className) { 
     var arrList = objElement.className.split(' '); 
     var strClassUpper = strClass.toUpperCase(); 
     for (var i = 0; i < arrList.length; i++) { 
      if (arrList[i].toUpperCase() == strClassUpper) { 
       arrList.splice(i, 1); 
       i--; 
      } 
     } 
     objElement.className = arrList.join(' '); 
    } 
} 

/** misc and util **/ 
function $(id) { 
    return document.getElementById(id); 
} 
function findPos(obj) { 
    //http://www.quirksmode.org/js/findpos.html 
    var curleft = curtop = 0; 
    if (obj && obj.offsetParent) { 
     do { 
      curleft += obj.offsetLeft; 
      curtop += obj.offsetTop; 
     } while (obj = obj.offsetParent); 
    } 
    return new Point(curleft,curtop); 
} 
function dec2hex(d, padding) { 
    var hex = Number(d).toString(16); 
    padding = padding || 2 
    while (hex.length < padding) { 
     hex = "0" + hex; 
    } 
    return hex; 
} 
function trimVal(val, min, max) { 
    return Math.max(Math.min(val, max), min) 
} 
function getRadioValue (name) { 
    for (i=0;i<document.forms["frm"][name].length;i++) { 
     if (document.forms["frm"][name][i].checked) { 
      return document.forms["frm"][name][i].value; 
     } 
    } 
    return null 
} 

/** init **/ 
function init() { 
    canvas = $("cv"); 
    canvas.style.width = width; 
    canvas.style.height = height; 
    canvas.setAttribute("width", width) 
    canvas.setAttribute("height", height) 
    ctx = canvas.getContext('2d'); 
    posCanvas = findPos(canvas) 

    cube = getCube(sideLength) 

    rotate(true) 
    animation = window.setInterval("rotate()", 50); 
} 


</script> 

<body onload="init()" 
    onmousemove="mouseMove(event)" 
    onmouseup="mouseUp(event)"> 
    <div dir=rtl class="header">קובייה</div> 
    <div dir=rtl class="subHeader">גיררו את הקוביה עם העכבר על מנת לסובב אותה.</div> 
    <div dir=rtl class="subHeader">ליחצו על פאה על מנת לסמן אותה.</div> 

    <BR> 
    <!-- main canvas --> 
    <center> 
     <div id="wrapper" dir="ltr"> 
      <canvas onmousedown="mouseDown(event)" id="cv" width="100" height="100"></canvas> 
     </div> 
    </center> 

    <!-- control panel --> 
    <div dir="rtl" class="opac" style="position:absolute; background:lightyellow; border:1px solid black; right:10px; top:100px; font-size:12px; padding:4px; width:120px"> 
     <div dir=rtl> 
      <form name=frm> 
       <input name="drawStyle" type="radio" value="color" onclick="rotate(true)" onchange="rotate(true)" checked>צבעוני<br> 
       <input name="drawStyle" type="radio" value="bw" onclick="rotate(true)" onchange="rotate(true)">שחור לבן<br> 
       <input name="drawStyle" type="radio" value="trans" onclick="rotate(true)" onchange="rotate(true)">שקוף<br> 
       <input id="chkWire" type="checkbox" onclick="rotate(true)" onchange="rotate(true)">מסגרת<BR> 
      </form> 
     </div> 
    </div> 

    <div id="debug"></div> 
</body> 
</html> 

哦...我不介意多的文檔類型,但感謝評論。