describe 'Camera (Surface point of view)', -> Camera = require 'lib/surface/Camera' expectPositionsEqual = (p1, p2) -> expect(p1.x).toBeCloseTo p2.x expect(p1.y).toBeCloseTo p2.y expect(p1.z).toBeCloseTo p2.z if p2.z? checkConversionsFromWorldPos = (wop, cam) -> # wop = world pos # sup = surface pos # cap = canvas pos # scp = screen pos sup = cam.worldToSurface wop expect(sup.x).toBeCloseTo wop.x * Camera.PPM expect(sup.y).toBeCloseTo cam.surfaceHeight - (wop.y + wop.z * cam.z2y) * cam.y2x * Camera.PPM cap = cam.worldToCanvas wop expect(cap.x).toBeCloseTo (sup.x - cam.surfaceViewport.x) * cam.zoom expect(cap.y).toBeCloseTo (sup.y - cam.surfaceViewport.y) * cam.zoom scp = cam.worldToScreen wop # If we ever want to use screen conversion, then make it and add this test #expect(scp.x).toBeCloseTo cap.x * @someCanvasToScreenXScaleFactor #expect(scp.y).toBeCloseTo cap.y * @someCanvasToScreenYScaleFactor wop2 = cam.surfaceToWorld sup expect(wop2.x).toBeCloseTo wop.x expect(wop2.y).toBeCloseTo wop.y + wop.z * cam.z2y # Make sure to call all twelve conversions in here. Can be redundant. expectPositionsEqual sup, cam.worldToSurface wop2 # 0 expectPositionsEqual cap, cam.surfaceToCanvas sup # 1 expectPositionsEqual scp, cam.canvasToScreen cap # 2 expectPositionsEqual cap, cam.screenToCanvas scp # 3 expectPositionsEqual sup, cam.canvasToSurface cap # 4 expectPositionsEqual wop2, cam.surfaceToWorld sup # 5 expectPositionsEqual wop2, cam.canvasToWorld cap # 6 expectPositionsEqual cap, cam.worldToCanvas wop # 7 expectPositionsEqual scp, cam.worldToScreen wop # 8 expectPositionsEqual scp, cam.surfaceToScreen sup # 9 expectPositionsEqual sup, cam.screenToSurface scp # 10 expectPositionsEqual wop2, cam.screenToWorld scp # 11 checkCameraPos = (cam, wop) -> botFOV = cam.x2y * cam.vFOV / (cam.y2x + cam.x2y) botDist = (cam.worldViewport.height) * Math.sin(cam.angle) / Math.sin(botFOV) camDist = (cam.worldViewport.height / 2) * Math.sin(Math.PI - cam.angle - botFOV) / Math.sin(botFOV) targetPos = x: cam.worldViewport.cx y: cam.worldViewport.cy - camDist * cam.y2x * cam.z2y z: camDist * cam.z2x * cam.y2z #console.log "botFOV", botFOV * 180 / Math.PI, "botDist", botDist, "camDist", camDist, "target pos", targetPos, "actual pos", cam.cameraWorldPos() expectPositionsEqual cam.cameraWorldPos(), targetPos if wop dx = targetPos.x - wop.x dy = targetPos.y - wop.y dz = targetPos.z - wop.z d = cam.distanceTo wop expect(d).toBeCloseTo Math.sqrt(dx * dx + dy * dy + dz * dz) # This is fairly vulnerable to numerical instability, so we limit the number of digits to consider. decimalPlaces = 3 - Math.floor(Math.log(d / camDist) / Math.log(10)) expect(cam.distanceRatioTo wop).toBeCloseTo d / camDist, decimalPlaces testWops = [ {x: 3, y: 4, z: 7} {x: -4, y: 12, z: 2} {x: 0, y: 0, z: 0} ] testCanvasSizes = [ {width: 100, height: 100} {width: 200, height: 50} ] testLayer = {scaleX: 1, scaleY: 1, regX: 0, regY: 0} testZooms = [0.5, 1, 2] testZoomTargets = [ null, {x: 50, y: 50} {x: 0, y: 150} ] testAngles = [0, Math.PI / 4, null, Math.PI / 2] testFOVs = [Math.PI / 6, Math.PI / 3, Math.PI / 2, Math.PI] it 'handles lots of different cases correctly', -> for wop in testWops for size in testCanvasSizes for zoom in testZooms for target in testZoomTargets for angle in testAngles for fov in testFOVs cam = new Camera size.width, size.height, size.width * Camera.MPP, size.height * Camera.MPP, testLayer, zoom, null, angle, fov checkCameraPos cam, wop cam.zoomTo target, zoom, 0 checkConversionsFromWorldPos wop, cam checkCameraPos cam, wop it 'works at 90 degrees', -> cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1, null, Math.PI / 2 expect(cam.x2y).toBeCloseTo 1 expect(cam.x2z).toBeGreaterThan 9001 expect(cam.z2y).toBeCloseTo 0 it 'works at 0 degrees', -> cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1, null, 0 expect(cam.x2y).toBeGreaterThan 9001 expect(cam.x2z).toBeCloseTo 1 expect(cam.z2y).toBeGreaterThan 9001 it 'works at 45 degrees', -> cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1, null, Math.PI / 4 expect(cam.x2y).toBeCloseTo Math.sqrt(2) expect(cam.x2z).toBeCloseTo Math.sqrt(2) expect(cam.z2y).toBeCloseTo 1 it 'works at default angle of asin(0.75) ~= 48.9 degrees', -> cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1 angle = Math.asin 3 / 4 expect(cam.angle).toBeCloseTo angle expect(cam.x2y).toBeCloseTo 4 / 3 expect(cam.x2z).toBeCloseTo 1 / Math.cos angle expect(cam.z2y).toBeCloseTo (4 / 3) * Math.cos angle it 'works at 2x zoom, 90 degrees', -> cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 2, null, Math.PI / 2 checkCameraPos cam wop = x: 5, y: 2.5, z: 7 cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 50, y: 100} cam.zoomTo {x: 50, y: 75}, 2, 0 checkCameraPos cam cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 50, y: 50} cam.zoomTo {x: 50, y: 75}, 4, 0 checkCameraPos cam cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 50, y: 50} # Now let's try zooming on the edge of the screen; we should be bounded to the surface viewport cam.zoomTo {x: 100, y: 100}, 2, 0 checkCameraPos cam cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 0, y: 50} it 'works at 2x zoom, 30 degrees', -> cam = new Camera 100, 100, 100 * Camera.MPP, 2 * 100 * Camera.MPP, testLayer, 2, null, Math.PI / 6 expect(cam.x2y).toBeCloseTo 2 expect(cam.x2z).toBeCloseTo 1 / Math.cos(Math.PI / 6) checkCameraPos cam wop = x: 5, y: 4, z: 6 * cam.y2z # like x: 5, y: 10 out of world width: 10, height: 20 sup = cam.worldToSurface wop expect(cam.surfaceToWorld(sup).y).toBeCloseTo 10 expectPositionsEqual sup, {x: 50, y: 50} cap = cam.surfaceToCanvas sup expectPositionsEqual cap, {x: 50, y: 50} # Zoom to bottom edge of screen cam.zoomTo {x: 50, y: 100}, 2, 0 checkCameraPos cam cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 50, y: 0} cam.zoomTo {x: 50, y: 100}, 4, 0 checkCameraPos cam cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 50, y: -100} it 'works at 2x zoom, 60 degree hFOV', -> cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 2, null, null, 0.01 checkCameraPos cam it 'works at 2x zoom, 60 degree hFOV, 40 degree hFOV', -> cam = new Camera 100, 63.041494, 100 * Camera.MPP, 63.041494 * Camera.MPP, testLayer, 2, null, null, Math.PI / 3 checkCameraPos cam it 'works on a surface wider than it is tall, 30 degrees, default viewing upper left corner', -> cam = new Camera 100, 100, 200 * Camera.MPP, 2 * 50 * Camera.MPP, testLayer, 1, {x: 0, y: 0}, Math.PI / 6 checkCameraPos cam expect(cam.zoom).toBeCloseTo 2 wop = x: 5, y: 4, z: 6 * cam.y2z # like x: 5, y: 10 out of world width: 20, height: 10 cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: 100, y: 0} # Zoom to far right edge of screen and try to zoom out cam.zoomTo {x: 9001, y: 25}, 0.1, 0 checkCameraPos cam cap = cam.worldToCanvas wop expectPositionsEqual cap, {x: -200, y: 0}