Created
May 18, 2015 07:12
ti.augmentedreality.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var isAndroid = Ti.Platform.osname == 'android'; | |
var numberOfpanels = 4; | |
/*if (isAndroid) { | |
// Landscape Mode | |
var screenWidth = Ti.Platform.displayCaps.platformHeight; | |
var screenHeight = Ti.Platform.displayCaps.platformWidth; | |
} else {*/ | |
var LDF = Ti.Platform.displayCaps.logicalDensityFactor || 1; | |
var screenWidth = (Math.max(Ti.Platform.displayCaps.platformWidth, Ti.Platform.displayCaps.platformHeight)) / LDF; | |
var screenHeight = (Math.min(Ti.Platform.displayCaps.platformWidth, Ti.Platform.displayCaps.platformHeight)) / LDF; | |
console.log('SCREENWIDTH=' + screenWidth); | |
//} | |
// https://github.com/jeffbonnes/parmavision | |
var MAX_ZOOM = 1.2; | |
var MIN_ZOOM = 0.1; | |
var DELTA_ZOOM = MAX_ZOOM - MIN_ZOOM; | |
var MIN_Y = Math.floor(screenHeight * 0.1); | |
var MAX_Y = Math.floor(screenHeight * 1); | |
var DELTA_Y = MAX_Y - MIN_Y; | |
// Setup the location properties for callbacks | |
Ti.Geolocation.purpose = 'Augmented Reality'; | |
Ti.Geolocation.getCurrentHeading(function() { | |
}); | |
Ti.Geolocation.headingFilter = 1; | |
Ti.Geolocation.showCalibration = false; | |
// https://jira.appcelerator.org/browse/TIMOB-9434 | |
if (isAndroid) { | |
Ti.Geolocation.accuracy = Ti.Geolocation.ACCURACY_LOW; | |
} else { | |
Ti.Geolocation.distanceFilter = 10; | |
Ti.Geolocation.preferredProvider = "gps"; | |
Ti.Geolocation.accuracy = Ti.Geolocation.ACCURACY_NEAREST_TEN_METERS; | |
Ti.Geolocation.purpose = "Augmented Reality"; | |
} | |
// function to create the window | |
exports.createARWindow = function(params) { | |
function showAR() { | |
Ti.Geolocation.addEventListener('heading', headingCallback); | |
Ti.Geolocation.addEventListener('location', locationCallback); | |
Ti.Media.showCamera({ | |
success : function() { | |
}, | |
cancel : function() { | |
// only gets called if Android | |
closeAR(); | |
}, | |
error : function(error) { | |
alert('unable to open AR Window'); | |
closeAR(); | |
}, | |
mediaTypes : [Ti.Media.MEDIA_TYPE_VIDEO, Ti.Media.MEDIA_TYPE_PHOTO], | |
showControls : false, | |
autohide : false, | |
autofocus : "off", | |
animated : false, | |
overlay : overlay | |
}); | |
} | |
function closeAR() { | |
Ti.Geolocation.removeEventListener('heading', headingCallback); | |
Ti.Geolocation.removeEventListener('location', locationCallback); | |
if (!isAndroid) { | |
Ti.Media.hideCamera(); | |
} | |
setTimeout(function() { | |
win.close(); | |
}, 500); | |
} | |
var panels = []; | |
// background colors for debugging | |
var showColors = false; | |
var colors = ['red', 'yellow', 'pink', 'green', 'purple', 'orange', 'blue', 'aqua', 'white', 'silver']; | |
var myLocation = null; | |
// Create the main view - only as wide as the viewport | |
var overlay = Ti.UI.createView({ | |
top : 0, | |
height : screenHeight, | |
left : 0, | |
width : screenWidth, | |
backgroundColor : 'transparent' | |
}); | |
// Create all the view that will contain the points of interest | |
for (var i = 0; i < numberOfpanels; i++) { | |
// create a view 1.6x the screen width | |
// they will overlap so any poi view that | |
// are near the edge will continue over into the | |
// 'next' view. | |
panels[i] = Ti.UI.createView({ | |
top : 0, | |
height : screenHeight, | |
right : 0, | |
width : (screenWidth * 1.6), | |
visible : false | |
}); | |
if (showColors) { | |
panels[i].backgroundColor = colors[i]; | |
panels[i].opacity = 0.6; | |
} | |
overlay.add(panels[i]); | |
}; | |
var radar = Ti.UI.createView({ | |
backgroundImage : '/assets/radar.png', | |
width : '80dp', | |
height : '80dp', | |
bottom : '10dp', | |
left : '10dp', | |
zIndex : 9999, | |
opacity : 0.6 | |
}); | |
var compass = Ti.UI.createLabel({ | |
left : '50dp',color:'#009FE0', | |
bottom : 2,visible:false | |
}); | |
overlay.add(compass); | |
overlay.add(radar); | |
if (params.overlay) { | |
overlay.add(params.overlay); | |
} | |
if (!isAndroid) { | |
var button = Ti.UI.createButton({ | |
top : '5dp', | |
right : '5dp', | |
height : '45dp', | |
width : '45dp', | |
backgroundImage : '/images/close.png' | |
}); | |
button.addEventListener('click', closeAR); | |
overlay.add(button); | |
} | |
var lastActiveView = -1; | |
var viewChange = false; | |
var centerY = screenHeight / 2; | |
var activePois; | |
var lastBearing = 0; | |
var TP = new (require('vendor/tiefpass'))(); | |
function headingCallback(e) { | |
var currBearing = e.heading.trueHeading || e.heading.magneticHeading; | |
currBearing = TP.add(currBearing); | |
if (currBearing == lastBearing) | |
return; | |
lastBearing = currBearing; | |
// Rotate the radar | |
radar.transform = Ti.UI.create2DMatrix().rotate(-currBearing); | |
compass.text = currBearing; | |
var internalBearing = currBearing / (360 / panels.length); | |
var activeView = Math.floor(internalBearing); | |
var pixelOffset = screenWidth - (Math.floor((internalBearing % 1) * screenWidth)); | |
// console.log('currBearing ' + currBearing); | |
// console.log('internalBearing ' + internalBearing); | |
// console.log('activeView ' + activeView); | |
if (activeView != lastActiveView) { | |
viewChange = true; | |
lastActiveView = activeView; | |
} else { | |
viewChange = false; | |
} | |
for (var i = 0; i < panels.length; i++) { | |
var diff = activeView - i; | |
if (diff >= -1 && diff <= 1) { | |
panels[i].center = { | |
y : centerY, | |
x : pixelOffset - (diff * screenWidth) | |
}; | |
if (viewChange) { | |
panels[i].visible = true; | |
} | |
} else { | |
if (viewChange) { | |
panels[i].visible = false; | |
} | |
} | |
} | |
if (activeView == 0) { // first panel | |
panels[panels.length - 1].center = { | |
y : centerY, | |
x : panels[0].center.x - screenWidth | |
}; | |
if (viewChange) { | |
panels[panels.length - 1].visible = true; | |
} | |
} else if (activeView == (panels.length - 1 )) { // last panel | |
panels[0].center = { | |
y : centerY, | |
x : panels[panels.length - 1].center.x + screenWidth | |
}; | |
if (viewChange) { | |
panels[0].visible = true; | |
} | |
} | |
}// end of heading changing | |
// Just a container window to hold all these objects | |
// user will never know | |
var win = Ti.UI.createWindow({ | |
modal : true, | |
navBarHidden : true, | |
fullscreen : true, | |
theme : 'Theme.NoActionBar', | |
orientationModes : [Ti.UI.LANDSCAPE_LEFT, Ti.UI.LANDSCAPE_RIGHT] | |
}); | |
if (params.maxDistance) { | |
win.maxDistance = params.maxDistance; | |
} | |
win.doClose = function() { | |
closeAR(); | |
}; | |
win.addEventListener('open', function() { | |
Ti.API.debug('AR Window Open...'); | |
setTimeout(showAR, 500); | |
}); | |
win.assignPOIs = function(pois) { | |
win.pois = pois; | |
// TODO - something here to make sure the pois redraw | |
// even if the location doesn't update | |
}; | |
function poiClick(e) { | |
Ti.API.debug('heard a click'); | |
Ti.API.debug('number=' + e.source.number); | |
var poi = activePois[e.source.number]; | |
var view = poi.view; | |
view.fireEvent('click', { | |
source : poi.view, | |
poi : poi | |
}); | |
} | |
function locationCallback(e) { | |
myLocation = e.coords; | |
redrawPois(); | |
}; | |
function redrawPois() { | |
if (!myLocation) { | |
Ti.API.warn("location not known. Can't draw pois"); | |
return; | |
} | |
// remove any existing panels | |
for (var i = 0; i < panels.length; i++) { | |
panels[i].removeAllChildren(); | |
} | |
radar.removeAllChildren(); | |
// Draw the Points of Interest on the panels | |
activePois = []; | |
for (var i = 0; i < win.pois.length; i++) { | |
var poi = win.pois[i]; | |
if (poi.view) { | |
var distance = calculateDistance(myLocation, poi); | |
var addPoint = true; | |
if (win.maxDistance && distance > win.maxDistance) { | |
addPoint = false; | |
} | |
if (addPoint) { | |
var bearing = calculateBearing(myLocation, poi); | |
var internalBearing = bearing / (360 / panels.length); | |
var activeView = Math.floor(internalBearing); | |
if (activeView >= panels.length) { | |
activeView = 0; | |
} | |
var pixelOffset = Math.floor((internalBearing % 1) * screenWidth) + ((panels[0].width - screenWidth) / 2); | |
poi.distance = distance; | |
poi.pixelOffset = pixelOffset; | |
poi.activeView = activeView; | |
poi.bearing = bearing; | |
activePois.push(poi); | |
} else { | |
Ti.API.debug(poi.title + " not added, maxDistance=" + win.maxDistance); | |
} | |
} | |
} | |
// Sort by Distance | |
activePois.sort(function(a, b) { | |
return b.distance - a.distance; | |
}); | |
if (activePois[0]) { | |
var maxDistance = activePois[0].distance; | |
var minDistance = activePois[activePois.length - 1].distance; | |
var distanceDelta = maxDistance - minDistance; | |
// Add the view | |
for (var i = 0; i < activePois.length; i++) { | |
var poi = activePois[i]; | |
Ti.API.debug(poi.title); | |
if (showColors) { | |
Ti.API.debug('viewColor======' + panels[poi.activeView].backgroundColor); | |
} | |
// Ti.API.debug('bearing=' + poi.bearing); | |
// Calcuate the Scaling (for distance) | |
var distanceFromSmallest = poi.distance - minDistance; | |
var percentFromSmallest = 1 - (distanceFromSmallest / distanceDelta); | |
var zoom = (percentFromSmallest * DELTA_ZOOM) + MIN_ZOOM; | |
// Calculate the y (farther away = higher ) | |
var y = MIN_Y + (percentFromSmallest * DELTA_Y); | |
var view = poi.view; | |
// Apply the transform | |
var transform = Ti.UI.create2DMatrix(); | |
transform = transform.scale(zoom); | |
view.transform = transform; | |
// Ti.API.debug('pixelOffset=' + poi.pixelOffset); | |
view.center = { | |
x : poi.pixelOffset, | |
y : y | |
}; | |
// Testing Click Handlers | |
/* | |
if (view.clickHandler) { | |
view.clickHandler.removeEventListener('click', poiClick); | |
view.remove(view.clickHandler); | |
view.clickHandler = null; | |
} | |
var clickHandler = Ti.UI.createView({ | |
width : Ti.UI.FILL, | |
height : Ti.UI.FILL | |
}); | |
var number = i; | |
clickHandler.number = number; | |
clickHandler.addEventListener('click', poiClick); | |
view.add(clickHandler); | |
view.clickHandler = clickHandler; | |
*/ | |
panels[poi.activeView].add(view); | |
// Ti.API.debug('panelsize=' + view.width + "," + view.height); | |
// need to create a second click handler | |
// on the closest view in case there is overlap | |
/*var clickHandler2 = Ti.UI.createView({ | |
width : view.width, | |
height : view.height, | |
transform : transform | |
}); | |
clickHandler2.number = number; | |
clickHandler2.addEventListener('click', poiClick); | |
*/ | |
var nextView; | |
var nextOffset; | |
if (poi.pixelOffset > (panels[0].width / 2 )) { | |
nextView = poi.activeView + 1; | |
nextOffset = poi.pixelOffset - screenWidth; | |
} else { | |
nextView = poi.activeView - 1; | |
nextOffset = poi.pixelOffset + screenWidth; | |
} | |
if (nextView < 0) { | |
nextView = panels.length - 1; | |
} else if (nextView == panels.length) { | |
nextView = 0; | |
} | |
// Ti.API.debug('nextView=' + nextView); | |
// Ti.API.debug('nextOffset=' + nextOffset); | |
/* clickHandler2.center = { | |
x : nextOffset, | |
y : y | |
};*/ | |
// panels[nextView].add(clickHandler2); | |
// End Click Handlers | |
// add to blip to the radar | |
// The Radar Blips .... | |
var rad = toRad(poi.bearing); | |
var relativeDistance = poi.distance / (maxDistance * 1.2); | |
var centerX = (40 + (relativeDistance * 40 * Math.sin(rad))); | |
var centerY = (40 - (relativeDistance * 40 * Math.cos(rad))); | |
var displayBlip = Ti.UI.createView({ | |
height : '2dp', | |
width : '2dp', | |
backgroundColor : 'white', | |
borderRadius : 2, | |
top : (centerY - 1) + "dp", | |
left : (centerX - 1) + "dp" | |
}); | |
radar.add(displayBlip); | |
} | |
} | |
}; | |
if (params.pois) { | |
win.assignPOIs(params.pois); | |
} | |
return win; | |
}; | |
function toRad(val) { | |
return val * Math.PI / 180; | |
}; | |
function calculateBearing(point1, point2) { | |
var lat1 = toRad(point1.latitude); | |
var lat2 = toRad(point2.latitude); | |
var dlng = toRad((point2.longitude - point1.longitude)); | |
var y = Math.sin(dlng) * Math.cos(lat2); | |
var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dlng); | |
var brng = Math.atan2(y, x); | |
return ((brng * (180 / Math.PI)) + 360) % 360; | |
}; | |
function calculateDistance(loc1, loc2) { | |
var R = 6371; | |
// Radius of the earth in km | |
var dLat = (toRad(loc2.latitude - loc1.latitude)); | |
// Javascript functions in radians | |
var dLon = (toRad(loc2.longitude - loc1.longitude)); | |
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(loc1.latitude)) * Math.cos(toRad(loc2.latitude)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); | |
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
// Distance in m | |
return R * c * 1000; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment