2011-01-14 5 views
1

Je suis en train d'écrire un petit 3d « moteur » en toile .. pas vraiment un moteur, mais plus d'une application basée sur le code de Eric Pascarello's demo (numéro de hit « 7 » à quelques reprises pour le voir tourner)comment détecter quel côté du cube 3D a été cliqué?

il est tout Très bien, j'ai même ajouté le tri par moyenne z-valeur pour chaque plan ("side") - de sorte que je puisse utiliser un corps composé de plusieurs cubes afin que les formes proches de la caméra soient dessinées en dernier.

maintenant, je veux détecter quel côté a été cliqué, quand une souris clique sur la toile. quelque chose comme this

indices:

  1. chaque côté est défini par quatre (x, y, z) des coins
  2. chaque coin est attirée sur la toile en utilisant ce que je crois "projection en perspective" régulière

voici mon code - il est cool: utiliser la souris pour faire pivoter

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

Juste un nitpick - si vous utilisez votre DOCTYPE devrait être pour HTML5 –

+0

Si seulement je l'ai codé quand HTML5 existait. :) – epascarello

Répondre

1

parfait! suppose que je devais poser cette question pour que je puisse me répondre :-)

la solution est basée sur un extrait étonnant que j'ai trouvé pour détecter la présence d'un point dans un polygone. c'est la fonction 2d, mais bon ... ainsi est ma toile - idiot moi.

ici est, complète et multi-navigateur:

<!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> 

oh ... et je ne me dérange pas beaucoup pour le doctype, mais grâce à des commentaires.

+0

Merci pour cela, je travaille également sur un moteur 3D de toile, et votre code m'a appris quelques bons conseils :) – pimvdb

+0

Le code ci-dessus fonctionne très bien. si nous ajoutons plus de 3 problème de profondeur de cube se pose. comment puis-je rectifier cela. – Arunkumar

Questions connexes