Wednesday 6 April 2016

3D Rotating Cube in Perspective Projection

I have been playing with SVG.  Starting with the code from here: Rotating 3D SVG Cube, I have add perspective projection.

How about this?




Here's the code:
<svg width='500' height='500'
     xmlns='http://www.w3.org/2000/svg'
     xmlns:xlink='http://www.w3.org/1999/xlink'
     onload='init(evt)' >

  <!-- Starting point was
       http://www.petercollingridge.co.uk/blog/rotating-3d-svg-cube
  -->

  <style>
    .surface{
        stroke-width: 0;
        stroke: black;
        stroke-width: 2;
    }
    .hidden-edge{
        stroke: black;
        stroke-width: .5;
        stroke-dasharray:3,3;
    }
    .button{
        fill: #2060dd;
        stroke: #2580ff;
        stroke-width: 1;
    }
    .button:hover{
        stroke-width: 3;
    }
  </style>

  <script type='text/ecmascript'>
    <![CDATA[
    // TODO: stop using globals and create object, for >1 diagram per page.

    // Distance along z axis from viewer (and origin) to plane that image is
    // projected onto.
    focal_length = 2.0;

    // Centre of rotation
    //cofr = {x:0.0, y:0.0, z:3.5};
    cofr = {x:0.2, y:0.2, z:3.3};

    function init(evt)
    {
        if ( window.svgDocument == null )
        {
            svgDocument = evt.target.ownerDocument;
            init_model();
            init_edge_elements();
        }
        drawModel();
    }
   
    function init_edge_elements()
    {
        edge_elements = new Array(edges.length);
        for(var i=0; i<edges.length; i++)
        {
            edge_elements[i] = svgDocument.getElementById('edge-'+i);
        }
    }

    function init_model()
    {
        var left_x = -0.5;
        var right_x = 0.5;
        var top_y = -0.5;
        var bottom_y = 0.5;
        var front_z = focal_length + 1.0;
        var back_z = focal_length + 2.0;
   
        var tlf = {y:top_y,    x:left_x,  z:front_z};
        var trf = {y:top_y,    x:right_x, z:front_z};
        var brf = {y:bottom_y, x:right_x, z:front_z};
        var blf = {y:bottom_y, x:left_x,  z:front_z};

        var tlb = {y:top_y,    x:left_x,  z:back_z};
        var trb = {y:top_y,    x:right_x, z:back_z};
        var brb = {y:bottom_y, x:right_x, z:back_z};
        var blb = {y:bottom_y, x:left_x,  z:back_z};
   
        coords = [tlf, trf, brf, blf, tlb, trb, brb, blb];
   
        // Surfaces defined clockwise when view from outside.
        var front  = {points:[tlf, trf, brf, blf], colour:'red'};
        var back   = {points:[tlb, blb, brb, trb], colour:'green'};
        var top    = {points:[tlb, trb, trf, tlf], colour:'pink'};
        var bottom = {points:[blb, blf, brf, brb], colour:'yellow'};
        var left   = {points:[tlb, tlf, blf, blb], colour:'purple'};
        var right  = {points:[trb, brb, brf, trf], colour:'orange'};

        surfaces = [front, back, top, bottom, left, right];

        edges = [
            {p1:tlf, p2:trf},
            {p1:trf, p2:brf},
            {p1:brf, p2:blf},
            {p1:blf, p2:tlf},
   
            {p1:tlb, p2:trb},
            {p1:trb, p2:brb},
            {p1:brb, p2:blb},
            {p1:blb, p2:tlb},

            {p1:tlf, p2:tlb},
            {p1:trf, p2:trb},
            {p1:brf, p2:brb},
            {p1:blf, p2:blb},
        ];
    }

    function drawModel()
    {
        // Project all points onto the view port with perspective.
        // http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MOHR_TRIGGS/node9.html
        // http://www.cse.unr.edu/~bebis/CS791E/Notes/PerspectiveProjection.pdf
        //
        for(var i=0; i<coords.length; i++)
        {
            var p = coords[i];
            p.vpx = focal_length * p.x / p.z * 250 + 200;
            p.vpy = focal_length * p.y / p.z * 250 + 200;
        }

        // Rough sort of surfaces from back to front.
        // Using z value midway between opposite corners.
        var z_order = new Array(surfaces.length);
        for(var i=0; i<surfaces.length; i++)
        {
            var s = surfaces[i];
            var z1 = s.points[0].z;
            var z2 = s.points[2].z;
            s.mid_z = (z1 > z2) ? z2+(z1-z2)/2 : z1+(z2-z1)/2;
            z_order[i] = s;
        }
        z_order.sort(function (a, b){return b.mid_z-a.mid_z;});

        // Fill surfaces from back to front, with hard outline.
        // This only really works for simple convex solids like a cube.
        // And then only if your rotation point isn't too far from the centre
        // of the cube.
        for(var i=0; i<z_order.length; i++)
        {
            var s = z_order[i];
            var attr = '' + s.points[0].vpx+','+s.points[0].vpy+' '+
                            s.points[1].vpx+','+s.points[1].vpy+' '+
                            s.points[2].vpx+','+s.points[2].vpy+' '+
                            s.points[3].vpx+','+s.points[3].vpy;

            surfaceElement = svgDocument.getElementById('surface-'+i);
            surfaceElement.setAttributeNS(null, 'points', attr);
            surfaceElement.setAttributeNS(null, 'style', 'fill:'+s.colour);
        }

        // Fill in lines as if hidden (big trick).
        for(var i=0; i<edges.length; i++)
        {
            var edge = edge_elements[i];

            var p1 = edges[i].p1;
            edge.setAttributeNS(null, 'x1', p1.vpx);
            edge.setAttributeNS(null, 'y1', p1.vpy);

            var p2 = edges[i].p2;
            edge.setAttributeNS(null, 'x2', p2.vpx);
            edge.setAttributeNS(null, 'y2', p2.vpy);
        }
    }
   
    function rotateAboutX(radians)
    {
        for(var i=0; i<coords.length; i++)
        {
            y = coords[i].y - cofr.y;
            z = coords[i].z - cofr.z;
            d = Math.sqrt(y*y + z*z);
            theta  = Math.atan2(y, z) + radians;
            coords[i].y = cofr.y + d * Math.sin(theta);
            coords[i].z = cofr.z + d * Math.cos(theta);
        }
        drawModel();
    }
   
    function rotateAboutY(radians)
    {
        for(var i=0; i<coords.length; i++)
        {
            x = coords[i].x - cofr.x;
            z = coords[i].z - cofr.z;
            d = Math.sqrt(x*x + z*z);
            theta  = Math.atan2(x, z) + radians;
            coords[i].x = cofr.x + d * Math.sin(theta);
            coords[i].z = cofr.z + d * Math.cos(theta);
        }
        drawModel();
    }

    function rotateAboutZ(radians)
    {
        for(var i=0; i<coords.length; i++)
        {
            x = coords[i].x - cofr.x;
            y = coords[i].y - cofr.y;
            d = Math.sqrt(x*x + y*y);
            theta  = Math.atan2(x, y) + radians;
            coords[i].x = cofr.x + d * Math.sin(theta);
            coords[i].y = cofr.y + d * Math.cos(theta);
        }
        drawModel();
    }
   
    function beginRotateX(radians)
    {
        rotateAboutX(radians);
        rotateX_timeout = setInterval("rotateAboutX(" + radians + ")", 20);
    }
   
    function endRotateX()
    {
        if (typeof(rotateX_timeout) != "undefined")
        {
            clearTimeout(rotateX_timeout);
        }
    }
   
    function beginRotateY(radians)
    {
        rotateAboutY(radians);
        rotateY_timeout = setInterval("rotateAboutY(" + radians + ")", 20);
    }
   
    function endRotateY()
    {
        if (typeof(rotateY_timeout) != "undefined")
        {
            clearTimeout(rotateY_timeout);
        }
    }

    function beginRotateZ(radians)
    {
        rotateAboutZ(radians);
        rotateZ_timeout = setInterval("rotateAboutZ(" + radians + ")", 20);
    }
   
    function endRotateZ()
    {
        if (typeof(rotateZ_timeout) != "undefined")
        {
            clearTimeout(rotateZ_timeout);
        }
    }
   
    ]]>
  </script>

    <polygon id='surface-0' points='0,0 1,0 1,1 0,1' class='surface' style='' />
    <polygon id='surface-1' points='0,0 1,0 1,1 0,1' class='surface' style='' />
    <polygon id='surface-2' points='0,0 1,0 1,1 0,1' class='surface' style='' />
    <polygon id='surface-3' points='0,0 1,0 1,1 0,1' class='surface' style='' />
    <polygon id='surface-4' points='0,0 1,0 1,1 0,1' class='surface' style='' />
    <polygon id='surface-5' points='0,0 1,0 1,1 0,1' class='surface' style='' />

    <line id='edge-0' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-1' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-2' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-3' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-4' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-5' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-6' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-7' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-8' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-9' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-10' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>
    <line id='edge-11' class='hidden-edge' x1='100' y1='100' x2='100' y2='100'/>

    <path class="button"
          d="m50.5 470.5 15 -15 0 8 45 0 0 14 -45 0 0 8 z"
          onmousedown='beginRotateY(0.08)'
          onmouseout='endRotateY()'
          onmouseup='endRotateY()'/>
         
    <path class="button"
          d="m190.5 470.5 -15 -15 0 8 -45 0 0 14 45 0 0 8 z"
          onmousedown='beginRotateY(-0.08)'
          onmouseout='endRotateY()'
          onmouseup='endRotateY()'/>
         
    <path class="button"
          d="m470.5 50.5 15 15 -8 0 0 45 -14 0 0 -45 -8 0 z"
          onmousedown='beginRotateX(0.08)'
          onmouseout='endRotateX()'
          onmouseup='endRotateX()'/>
         
    <path class="button"
          d="m470.5 190.5 15 -15 -8 0 0 -45 -14 0 0 45 -8 0 z"
          onmousedown='beginRotateX(-0.08)'
          onmouseout='endRotateX()'
          onmouseup='endRotateX()'/>
   
    <path class="button"
          d="m470.5 250.5 15 15 -8 0 0 45 -14 0 0 -45 -8 0 z"
          onmousedown='beginRotateZ(0.08)'
          onmouseout='endRotateZ()'
          onmouseup='endRotateZ()'/>
         
    <path class="button"
          d="m470.5 390.5 15 -15 -8 0 0 -45 -14 0 0 45 -8 0 z"
          onmousedown='beginRotateZ(-0.08)'
          onmouseout='endRotateZ()'
          onmouseup='endRotateZ()'/>
   
</svg>

No comments:

Post a Comment