dblume/pizza-slice

View on GitHub
main.js

Summary

Maintainability
B
5 hrs
Test Coverage
var canvas = document.getElementById('main_canvas');
var ctx = canvas.getContext('2d');

var width = parseInt(canvas.getAttribute("width"), 10);
var height = parseInt(canvas.getAttribute("height"), 10);
var radius = height;
var cx = width / 2;
var cy = height;

var pizza_angle = Math.PI / 4;


function toRadians(deg) {
    return deg * Math.PI / 180;
}


function getAngle (x1, y1, x2, y2) {
  var dx = x2 - x1;
  var dy = y2 - y1;
  return Math.atan2(dx,dy);
}


function drawSlice(angle) {
    la = -(Math.PI / 2) - (angle / 2);
    ra = la + angle;
    // Crust
    len = cy;
    ctx.moveTo(cx, cy);
    ctx.beginPath();
    ctx.arc(cx, cy, len, la, ra);
    len = cy * 91 / 100;
    ctx.arc(cx, cy, len, ra, la, true);
    ctx.closePath();

    var radgrad = ctx.createRadialGradient(cx,cy,radius - 60,cx,cy,radius);
    radgrad.addColorStop(0, '#D5A81B');
    radgrad.addColorStop(0.90, '#D5A81B');
    radgrad.addColorStop(1, '#B0821D');
    ctx.fillStyle = radgrad;
    ctx.fill();

    // Sauce
    ctx.moveTo(cx, cy);
    ctx.beginPath();
    ctx.arc(cx, cy, len, la, ra);
    len = cy * 9 / 10;
    ctx.arc(cx, cy, len, ra, la, true);
    ctx.closePath();
    ctx.fillStyle = '#BB0B04';
    ctx.fill();

    // Cheese
    ctx.beginPath();
    ctx.moveTo(cx, cy);
    ctx.arc(cx, cy, len, la, ra);
    ctx.lineTo(cx, cy);
    ctx.closePath();
    ctx.fillStyle = '#F3D255';
    ctx.fill();
//    document.write("Pizza angle is " + (angle * (180/Math.PI)).toFixed(2) + ".<br \>");

    var area_of_half_slice = radius * radius * angle / 4.0;
    var l = 0;
    if (angle < 1.9024) {
        // We happen to know that the cut will intersect the straight edge of the slice.
        //            w
        //   \-----|-----/
        //    \    |90  /
        //     \  l|   /
        //      \  |  / h
        //       \ |a/
        //        \|/
        //         v
        var a = angle / 2.0;
    if (a !== 0) {
            var h = Math.sqrt(area_of_half_slice/(Math.cos(a)*Math.sin(a)));
            l = h * Math.cos(a);
    }
    } else {
        // We happen to know that the cut will intersect the crust.
    l = halve_slice_big_angle(angle, radius);

    }
    ctx.beginPath();
    ctx.moveTo(0, height - l);
    ctx.lineTo(600, height - l);
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#FFF2DF';
    ctx.stroke();
}


function calc_area_for_big_angle(angle, fraction_in_triangles, radius) {
    /* Calculate the area for wide slices where fraction_in_triangles of angle
    is used in the triangles.
           ________________________
          /\           |           /\
         /   \         |90       /   \
        |      \      l|       /      |
        |      r \     |     / r      |
         ---.__    \   |a  /    __.---
            rx --.__ \ | /b__.-- ry     "angle" is angle from line segments rx to ry.
                    --.v.--

    If theta is minimum, 1.8956, then fraction_in_triangles will be around 0.9999655 for a good match
    If theta is maximum, pi, then fraction_in_trangles will be around 0.7352581 for a good match
    */
    var angle_in_rect = angle * fraction_in_triangles;
    var a = angle_in_rect / 2.0  // angle in each triangle part;
    var area_of_rectangle_part = Math.cos(a) * Math.sin(a) * radius * radius;
    var angle_in_sector = angle - angle_in_rect;
    var area_of_sector_parts = radius * radius * angle_in_sector / 2.0;
    return area_of_rectangle_part + area_of_sector_parts;
}


function halve_slice_big_angle(angle, radius) {
    // angle must be > 1.8956 (where length is 0.5835 * radius) and < 3.1416
    var area_of_half_slice = radius * radius * angle / 4.0;

    // if angle = 1.8956, fraction_in_triangles = 0.9999655
    // if angle = 3.1416, fraction_in_trangles = 0.7352581

    var count = 0;
    var min_angle = 1.8956;
    var min_angle_trifract = 0.9999655;
    var max_angle = Math.PI;
    var max_angle_trifract = 0.7352581;
    var fraction_in_triangles = min_angle_trifract - ((angle - min_angle) / (max_angle - min_angle) * (min_angle_trifract - max_angle_trifract));
    var done = false;

    while (!done) {
        var area = calc_area_for_big_angle(angle, fraction_in_triangles, radius);
        diff_pct = (area_of_half_slice - area) / area_of_half_slice;
        // console.log("Loop " + count + ": angle " + angle.toFixed(3) + ", tried fraction " + fraction_in_triangles.toFixed(3) + " and got difference of " + area_of_half_slice.toFixed(4) + " - " + area.toFixed(4) + " = " + (diff_pct * 100).toFixed(2) + "%");
        height_of_line = Math.cos((angle * fraction_in_triangles) / 2) * radius;  //  l
        if (Math.abs(diff_pct) < 0.001) {
            done = true;
            break;
        }
        if (area_of_half_slice > area) {
            // Need to increase area. So decrease the percentage that goes to triangles.
            // Drive fraction_in_triangles closer to the low value in max_angle_trifract.
            min_angle_trifract = fraction_in_triangles;
        } else {
            // Need to decrease the area. So increase the percentage that goes to triangles.
            // Drive fraction_in_triangles closer to the high value in min_angle_trifract.
            max_angle_trifract = fraction_in_triangles;
        }
        fraction_in_triangles = min_angle_trifract - ( min_angle_trifract - max_angle_trifract ) * 0.5;
        // Set fraction_in_triangles proportionally to where angle
        // sits between last_angle_min an
        count += 1;
        if (count > 10) {
            console.log("Ran too many loops. Exiting.");
            break;
        }
    }
    return height_of_line;
}


function redrawCanvas(angle) {
    ctx.clearRect(0, 0, width, height);
    drawSlice(angle);
}


function tryToChangeAngle(angle) {
    if (angle < 0) {
        angle = 0;
    } else if (angle > Math.PI) {
        angle = Math.PI;
    }

    pizza_angle = angle;
    // console.log("Change angle to " + pizza_angle + ".");
    redrawCanvas(pizza_angle);
}


canvas.addEventListener("wheel", function(e) {
    var evt = e || event;
    if (!evt.shiftKey && !evt.ctrlKey && !evt.metaKey && !evt.altKey) {
        tryToChangeAngle(pizza_angle + (evt.deltaY / 20 * Math.PI / 180));
        e.preventDefault();
    }
  });


// Also see http://photos.dlma.com/swipe.js
var touches = {x1:0, y1:0, x2:0, y2:0, distance:0};
var isTouching = false;
var everMultiTouch = false;

canvas.addEventListener('touchstart', onTouchStart);
canvas.addEventListener('touchmove', onTouchMove);
canvas.addEventListener('touchend', onTouchEnd);

var lastMouseX;
var isMouseDown = false;

canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mouseup', onMouseUp);
canvas.addEventListener('mouseleave', onMouseLeave);
canvas.addEventListener('mousemove', onMouseMove);


function onTouchStart(event) {
    event.preventDefault();
    if (event.targetTouches.length === 1) {
        everMultiTouch = false;
    } else {
        everMultiTouch = true;
        touches.x2 = event.touches[1].clientX;
        touches.y2 = event.touches[1].clientY;
    }
    isTouching = true;
    touches.x1 = event.touches[0].clientX;
    touches.y1 = event.touches[0].clientY;
    if (event.targetTouches.length > 1) {
        touches.distance = Math.sqrt((touches.x2 - touches.x1) * (touches.x2 - touches.x1) + (touches.y2 - touches.y1) * (touches.y2 - touches.y1));
    }
}


function onTouchEnd(event) {
    event.preventDefault();
    isTouching = false;
}


function onTouchMove(event) {
    var touchX1 = event.touches[0].clientX;
    if (event.targetTouches.length > 1) {
        if (!everMultiTouch) {
            touches.x2 = event.touches[1].clientX;
            touches.y2 = event.touches[1].clientY;
            touches.distance = Math.sqrt((touches.x2 - touches.x1) * (touches.x2 - touches.x1) + (touches.y2 - touches.y1) * (touches.y2 - touches.y1));
            everMultiTouch = true;
        }
    }
    if (isTouching) {
        if (!everMultiTouch) {
            var dx = touchX1 - touches.x1;
            // console.log("Change angle by " + dx + " to " + pizza_angle + ".");
            tryToChangeAngle(pizza_angle + (dx / 2 * Math.PI / 180));
            touches.x1 = touchX1;
            touches.y1 = event.touches[0].clientY;
        } else if (event.targetTouches.length > 1) {
            var touchY1 = event.touches[0].clientY;
            var touchX2 = event.touches[1].clientX;
            var touchY2 = event.touches[1].clientY;
            var distance = Math.sqrt((touchX2 - touchX1) * (touchX2 - touchX1) + (touchX2 - touchY1) * (touchY2 - touchY1));
            tryToChangeAngle(pizza_angle + ((distance - touches.distance) / 2 * Math.PI / 180));
            touches.x1 = touchX1;
            touches.y1 = touchY1;
            touches.x2 = touchX2;
            touches.y2 = touchY2;
            touches.distance = distance;
            
        }
        event.preventDefault();
    }
}


function onMouseDown(event) {
    // event.preventDefault();
    lastMouseX = event.clientX;
    isMouseDown = true;
}


function onMouseUp(event) {
    // event.preventDefault();
    isMouseDown = false;
}


function onMouseLeave(event) {
    if (event.clientY < canvas.offsetTop || event.clientY > canvas.offsetTop + cy) {
        // event.preventDefault();
        isMouseDown = false;
    }
}


function onMouseMove(event) {
    event.preventDefault();
    if (isMouseDown) {
        var dx = event.clientX - lastMouseX;
        // console.log("Change angle by " + dx + " to " + pizza_angle + ".");
        tryToChangeAngle(pizza_angle + (dx / 2 * Math.PI / 180));
        lastMouseX = event.clientX;
        event.preventDefault();
    }
}


drawSlice(pizza_angle);


/*
var offscreenCanvas = document.createElement('canvas');
var offscreenContext = offscreenCanvas.getContext('2d');

function draw_something(ctx) {
    ctx.save();
    ctx.fillText(ctx, ...);
    ctx.restore();
}

function repaint() {
    context.drawImage(offscreenCanvas, 0, 0);
    redrawPos(context);
}
    
function redraw() {
    redrawCanvas(offscreenContext);
    repaint();
}
*/