Skip to content
Browse files

Major Update including 3D terrain!

Major update and a sync! 3D terrain! Faster drawing! Much improved
memory management! And more!
  • Loading branch information...
1 parent 7be260f commit dd70b87b439e55d9c13ad7aa5bd20056906b6fce @RossAnderson committed May 13, 2012
View
40 DRAppDelegate.m
@@ -10,6 +10,10 @@
#import "RASceneGraphController.h"
+// NOTICE:
+// The imagery tile sets below are for example use only. Please consult with the
+// individual copyright holder of each tile set before using it in your app. The
+// Dancing Robots tile sets (the defaults below) may not be used without permission.
#define IMAGERY_DATASET 1
#define TERRAIN_DATASET 1
@@ -29,6 +33,10 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
self.viewController = [[RASceneGraphController alloc] initWithNibName:@"SceneView_iPad" bundle:nil];
}
+ // allow caching for tile images
+ [[NSURLCache sharedURLCache] setMemoryCapacity:4*1024*1024];
+ [[NSURLCache sharedURLCache] setDiskCapacity:128*1024*1024];
+
// setup the tile set used
RATileDatabase * database = [RATileDatabase new];
database.bounds = CGRectMake( -180,-90,360,180 );
@@ -37,7 +45,18 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
switch( IMAGERY_DATASET ) {
case 1:
- // MapBox Streets: http://a.tiles.mapbox.com/v3/mapbox.mapbox-streets.jsonp
+ // Dancing Robots Streets: https://tiles.mapbox.com/v3/dancingrobots.map-zlkx39ti.jsonp
+ database.baseUrlStrings = [NSArray arrayWithObjects:
+ @"http://a.tiles.mapbox.com/v3/dancingrobots.map-zlkx39ti/{z}/{x}/{y}.png",
+ @"http://b.tiles.mapbox.com/v3/dancingrobots.map-zlkx39ti/{z}/{x}/{y}.png",
+ @"http://c.tiles.mapbox.com/v3/dancingrobots.map-zlkx39ti/{z}/{x}/{y}.png",
+ @"http://d.tiles.mapbox.com/v3/dancingrobots.map-zlkx39ti/{z}/{x}/{y}.png",
+ nil];
+ database.maxzoom = 17;
+ break;
+
+ case 2:
+ // MapBox Streets: http://tiles.mapbox.com/v3/mapbox.mapbox-streets.jsonp
database.baseUrlStrings = [NSArray arrayWithObjects:
@"http://a.tiles.mapbox.com/v3/mapbox.mapbox-streets/{z}/{x}/{y}.png",
@"http://b.tiles.mapbox.com/v3/mapbox.mapbox-streets/{z}/{x}/{y}.png",
@@ -47,8 +66,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
database.maxzoom = 17;
break;
- case 2:
- // Sample database from: http://a.tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jul.jsonp
+ case 3:
+ // Sample database from: http://tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jul.jsonp
database.baseUrlStrings = [NSArray arrayWithObjects:
@"http://a.tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jul/{z}/{x}/{y}.png",
@"http://b.tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jul/{z}/{x}/{y}.png",
@@ -58,7 +77,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
database.maxzoom = 8;
break;
- case 3:
+ case 4:
// OpenStreetMap
database.baseUrlStrings = [NSArray arrayWithObjects:
@"http://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
@@ -68,7 +87,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
database.maxzoom = 18;
break;
- case 4:
+ case 5:
// Stamen Maps Watercolor - http://maps.stamen.com/watercolor
database.baseUrlStrings = [NSArray arrayWithObjects:
@"http://a.tile.stamen.com/watercolor/{z}/{x}/{y}.png",
@@ -92,12 +111,13 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
switch ( TERRAIN_DATASET ) {
case 1:
- // World Topography: https://tiles.mapbox.com/v3/dancingrobots.world-topo
+ // Dancing Robots Topography: http://a.tiles.mapbox.com/v3/dancingrobots.globe-topo.json
+ // Based on NOAA GLOBE dataset: http://www.ngdc.noaa.gov/mgg/topo/gltiles.html
database.baseUrlStrings = [NSArray arrayWithObjects:
- @"http://a.tiles.mapbox.com/v3/dancingrobots.world-topo/{z}/{x}/{y}.png",
- @"http://b.tiles.mapbox.com/v3/dancingrobots.world-topo/{z}/{x}/{y}.png",
- @"http://c.tiles.mapbox.com/v3/dancingrobots.world-topo/{z}/{x}/{y}.png",
- @"http://d.tiles.mapbox.com/v3/dancingrobots.world-topo/{z}/{x}/{y}.png",
+ @"http://a.tiles.mapbox.com/v3/dancingrobots.globe-topo/{z}/{x}/{y}.png",
+ @"http://b.tiles.mapbox.com/v3/dancingrobots.globe-topo/{z}/{x}/{y}.png",
+ @"http://c.tiles.mapbox.com/v3/dancingrobots.globe-topo/{z}/{x}/{y}.png",
+ @"http://d.tiles.mapbox.com/v3/dancingrobots.globe-topo/{z}/{x}/{y}.png",
nil];
database.maxzoom = 8;
break;
View
14 EarthViewExample.xcodeproj/project.pbxproj
@@ -12,6 +12,9 @@
916EEB8D1552D4E800951ACC /* RAPageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 916EEB8C1552D4E800951ACC /* RAPageNode.m */; };
91B9BF1E15549FB100A7602E /* RAImageSampler.m in Sources */ = {isa = PBXBuildFile; fileRef = 91B9BF1D15549FB100A7602E /* RAImageSampler.m */; };
91C1D9BA15575D0C008717A9 /* RAWorldTour.m in Sources */ = {isa = PBXBuildFile; fileRef = 91C1D9B915575D0C008717A9 /* RAWorldTour.m */; };
+ 91C1D9DE155B2CBC008717A9 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91C1D9DB155B2CBC008717A9 /* CFNetwork.framework */; };
+ 91C1D9DF155B2CBC008717A9 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91C1D9DC155B2CBC008717A9 /* Security.framework */; };
+ 91C1D9E0155B2CBC008717A9 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91C1D9DD155B2CBC008717A9 /* SystemConfiguration.framework */; };
91C9975C1548E1BF00B69A8A /* Icon114.png in Resources */ = {isa = PBXBuildFile; fileRef = 91C9975B1548E1BF00B69A8A /* Icon114.png */; };
91C9975E1548E1F000B69A8A /* Icon57.png in Resources */ = {isa = PBXBuildFile; fileRef = 91C9975D1548E1F000B69A8A /* Icon57.png */; };
91C997601548E21400B69A8A /* Icon144.png in Resources */ = {isa = PBXBuildFile; fileRef = 91C9975F1548E21400B69A8A /* Icon144.png */; };
@@ -59,6 +62,9 @@
91BBDFB1153A49A900CEF4BA /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = SOURCE_ROOT; };
91C1D9B815575D0C008717A9 /* RAWorldTour.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RAWorldTour.h; sourceTree = "<group>"; };
91C1D9B915575D0C008717A9 /* RAWorldTour.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RAWorldTour.m; sourceTree = "<group>"; };
+ 91C1D9DB155B2CBC008717A9 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
+ 91C1D9DC155B2CBC008717A9 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+ 91C1D9DD155B2CBC008717A9 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
91C9975B1548E1BF00B69A8A /* Icon114.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon114.png; sourceTree = "<group>"; };
91C9975D1548E1F000B69A8A /* Icon57.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon57.png; sourceTree = "<group>"; };
91C9975F1548E21400B69A8A /* Icon144.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon144.png; sourceTree = "<group>"; };
@@ -123,6 +129,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 91C1D9DE155B2CBC008717A9 /* CFNetwork.framework in Frameworks */,
+ 91C1D9DF155B2CBC008717A9 /* Security.framework in Frameworks */,
+ 91C1D9E0155B2CBC008717A9 /* SystemConfiguration.framework in Frameworks */,
91F77EA7153A089A00F8AE05 /* QuartzCore.framework in Frameworks */,
91F77E271539335000F8AE05 /* UIKit.framework in Frameworks */,
91F77E291539335000F8AE05 /* Foundation.framework in Frameworks */,
@@ -170,6 +179,9 @@
91F77E2C1539335000F8AE05 /* GLKit.framework */,
91F77E2E1539335000F8AE05 /* OpenGLES.framework */,
91F77EA6153A089A00F8AE05 /* QuartzCore.framework */,
+ 91C1D9DB155B2CBC008717A9 /* CFNetwork.framework */,
+ 91C1D9DC155B2CBC008717A9 /* Security.framework */,
+ 91C1D9DD155B2CBC008717A9 /* SystemConfiguration.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -412,6 +424,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = MKNetworkKit;
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -431,6 +444,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = MKNetworkKit;
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
SDKROOT = iphoneos;
View
BIN ....xcodeproj/project.xcworkspace/xcuserdata/ross.xcuserdatad/UserInterfaceState.xcuserstate
Binary file not shown.
View
7 README.md
@@ -5,11 +5,11 @@ EarthView is an open-source 3D visualization of the Earth globe for iOS that use
The project uses a quad-tree to page in map tiles that conform to the Tile Map Service standard (or, the flipped Google equivalent). The level of detail to display is determined by the estimated screen-space error of a given page.
-Besides fixing the bugs listed below, I am interested in adding topographical terrain detail to the map. I intend to load heightmap data using the same map tile system as the visible content.
+A recent update added realistic topography to the face of the globe for realistic mountains and valleys. The data source I used is NOAA GLOBE (http://www.ngdc.noaa.gov/mgg/topo/gltiles.html) which was converted to a grayscale tileset and uploaded to MapBox.
Enjoy!
-![](https://github.com/RossAnderson/EarthView/raw/master/Screenshot-iphone.png)
+![](https://github.com/RossAnderson/EarthView/raw/master/screenshot1.png)
How to Use
----------
@@ -39,7 +39,6 @@ Bugs and Limitations
--------------------
- The derived bounding boxes don't appear to be calculated correctly.
-- Should redraw in reaction to Manipulator movement, rather than a constant frame rate. There doesn't appear to be a good way to do this with GLKit.
+- Performance gets worse when the camera is zoomed in. I think this is because offscreen nodes are not properly skipped in the scene graph.
- There are holes in the globe at the poles because there is no map tile content there.
- The tilt control should bounce when you hit the hard stops.
-- Add support for MBTiles (http://mapbox.com/mbtiles-spec/) for locally stored/cached content.
View
BIN Screenshot 1.png
Deleted file not rendered
View
BIN Screenshot 2.png
Deleted file not rendered
View
BIN Screenshot 3.png
Deleted file not rendered
View
BIN Screenshot-iphone.png
Deleted file not rendered
View
2 Source/RAGeometry.h
@@ -25,7 +25,7 @@
@property (assign, nonatomic) GLKVector4 color; // set 1st component to -1 to disable
@property (assign, nonatomic) GLenum elementStyle; // default: GL_TRIANGLES
-+ (void)cleanup;
++ (void)cleanupAll:(BOOL)all;
- (void)setObjectData:(const void *)data withSize:(NSUInteger)length withStride:(NSUInteger)stride;
- (void)setIndexData:(const void *)data withSize:(NSUInteger)length withStride:(NSUInteger)stride;
View
56 Source/RAGeometry.m
@@ -14,11 +14,15 @@
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
+#import <libkern/OSAtomic.h>
+
#import "RABoundingSphere.h"
#define BUFFER_INVALID ((GLuint)-1)
#define kMaxDeleteBatchSize (8)
+static int64_t sGeometryObjectCount = 0;
+
@interface GLBufferSet : NSObject
@property (assign) GLuint vertexArray;
@@ -40,16 +44,22 @@ - (id)init
}
- (void)generateAndBind {
- if ( _vertexArray == BUFFER_INVALID )
+ if ( _vertexArray == BUFFER_INVALID ) {
glGenVertexArraysOES(1, &_vertexArray);
- glBindVertexArrayOES(_vertexArray);
+
+ // simple way to check that we don't have too many geometries active
+ if ( _vertexArray > 500 )
+ NSLog(@"Warning: vertex array id = %d", _vertexArray);
+ }
if ( _vertexBuffer == BUFFER_INVALID )
glGenBuffers(1, &_vertexBuffer);
- glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
if ( _indexBuffer == BUFFER_INVALID )
glGenBuffers(1, &_indexBuffer);
+
+ glBindVertexArrayOES(_vertexArray);
+ glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
}
@@ -91,15 +101,11 @@ @implementation RAGeometry {
@synthesize elementStyle = _elementStyle;
-+ (NSMutableDictionary *)geometrySetDictionary {
++ (NSMutableSet *)geometryDeletionSetForKey:(NSString *)key {
static NSMutableDictionary * dict = nil;
if ( dict == nil ) dict = [NSMutableDictionary dictionary];
- return dict;
-}
-
-+ (NSMutableSet *)geometrySetForKey:(NSString *)key {
+
// get or create a set for this context
- NSMutableDictionary * dict = [[self class] geometrySetDictionary];
NSMutableSet * set = [dict objectForKey:key];
if ( !set ) {
set = [NSMutableSet set];
@@ -108,19 +114,17 @@ + (NSMutableSet *)geometrySetForKey:(NSString *)key {
return set;
}
-+ (void)cleanup {
- EAGLContext * context = [EAGLContext currentContext];
- NSAssert( context, @"OpenGL ES context must be valid!" );
++ (void)cleanupAll:(BOOL)all {
+ NSAssert( [EAGLContext currentContext], @"OpenGL ES context must be valid!" );
- NSString * key = [context description];
- NSMutableSet * set = [self geometrySetForKey:key];
+ NSString * key = [[[EAGLContext currentContext] sharegroup] description];
+ NSMutableSet * set = [self geometryDeletionSetForKey:key];
NSUInteger count = 0;
-
@synchronized (set) {
if ( [set count] < 1 ) return;
- while( count < kMaxDeleteBatchSize && [set count] ) {
+ while( ( all || count < kMaxDeleteBatchSize ) && [set count] ) {
GLBufferSet * buffer = [set anyObject];
[set removeObject:buffer];
@@ -131,6 +135,8 @@ + (void)cleanup {
}
}
+ //NSLog(@"Deleted %d geometries.", count);
+
// check for errors
GLenum err = glGetError();
if ( err != GL_NO_ERROR )
@@ -153,14 +159,21 @@ - (id)init
_elementStyle = GL_TRIANGLES;
_vertexDataDirty = _indexDataDirty = YES;
+
+ OSAtomicIncrement64( &sGeometryObjectCount );
+ if ( sGeometryObjectCount > 500 ) {
+ NSLog(@"Warning: there are now %lld geometry objects!", sGeometryObjectCount);
+ }
}
return self;
}
- (void)dealloc
{
+ OSAtomicDecrement64( &sGeometryObjectCount );
+
if ( _buffers && _contextKey ) {
- NSMutableSet * set = [[self class] geometrySetForKey:_contextKey];
+ NSMutableSet * set = [[self class] geometryDeletionSetForKey:_contextKey];
@synchronized (set) {
// mark for cleanup
@@ -256,10 +269,10 @@ - (void)setupGL
@synchronized(self) {
if ( _buffers == nil ) {
- EAGLContext * context = [EAGLContext currentContext];
+ NSString * key = [[[EAGLContext currentContext] sharegroup] description];
_buffers = [GLBufferSet new];
- _contextKey = [context description];
+ _contextKey = key;
}
[_buffers generateAndBind];
@@ -309,11 +322,8 @@ - (void)releaseGL
{
NSAssert( [EAGLContext currentContext], @"must be called with an active context" );
- NSLog(@"releaseGL!");
-
@synchronized(self) {
[_buffers releaseGL];
-
_vertexDataDirty = _indexDataDirty = YES;
}
}
@@ -351,7 +361,7 @@ - (void)renderGL
glDrawElements(self.elementStyle, [_indexData length]/_indexStride, type, 0);
} else {
- NSLog(@"Nothing to draw in %@", self);
+ NSLog(@"-[%@ renderGL]: nothing to draw", self);
}
}
}
View
3 Source/RAImageSampler.h
@@ -12,6 +12,9 @@
- (id)initWithImage:(UIImage *)img;
+- (CGFloat)grayAtNearestPixel:(CGPoint)p;
+- (CGFloat)grayByInterpolatingPixels:(CGPoint)p;
+
- (UIColor *)colorAtNearestPixel:(CGPoint)p;
- (UIColor *)colorByInterpolatingPixels:(CGPoint)p;
View
69 Source/RAImageSampler.m
@@ -49,32 +49,32 @@ - (void)dealloc {
if ( _rawData ) free( _rawData );
}
-- (UIColor *)colorAtNearestPixel:(CGPoint)p {
+- (BOOL)extractNearestRgba:(CGPoint)p to:(CGFloat*)rgba {
// y-flip
p.y = _height - 1.0f - p.y;
int x = round(p.x);
int y = round(p.y);
- if ( x < 0 || x >= _width ) return nil;
- if ( y < 0 || y >= _height ) return nil;
+ if ( x < 0 || x >= _width ) return NO;
+ if ( y < 0 || y >= _height ) return NO;
unsigned char * pixel = _rawData + (_bytesPerRow * y) + (_bytesPerPixel * x);
- CGFloat red = pixel[0] / 255.0;
- CGFloat green = pixel[1] / 255.0;
- CGFloat blue = pixel[2] / 255.0;
- CGFloat alpha = pixel[3] / 255.0;
-
- return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
+ rgba[0] = pixel[0] / 255.0;
+ rgba[1] = pixel[1] / 255.0;
+ rgba[2] = pixel[2] / 255.0;
+ rgba[3] = pixel[3] / 255.0;
+ return YES;
}
-- (UIColor *)colorByInterpolatingPixels:(CGPoint)p {
- // snap to bounds of image
- if ( p.x < 0 ) return [self colorByInterpolatingPixels:CGPointMake(0, p.y)];
- if ( p.x > _width-2 ) return [self colorByInterpolatingPixels:CGPointMake(_width-2, p.y)];
- if ( p.y < 0 ) return [self colorByInterpolatingPixels:CGPointMake(p.x, 0)];
- if ( p.y > _height-2 ) return [self colorByInterpolatingPixels:CGPointMake(p.x, _height-2)];
+- (BOOL)extractInterpolatedRgba:(CGPoint)p to:(CGFloat*)rgba {
+ // snap to bounds of image
+ if ( p.x < 0 ) return [self extractInterpolatedRgba:CGPointMake(0, p.y) to:rgba];
+ if ( p.x > _width-2 ) return [self extractInterpolatedRgba:CGPointMake(_width-2, p.y) to:rgba];
+ if ( p.y < 0 ) return [self extractInterpolatedRgba:CGPointMake(p.x, 0) to:rgba];
+ if ( p.y > _height-2 ) return [self extractInterpolatedRgba:CGPointMake(p.x, _height-2) to:rgba];
+
// y-flip
p.y = _height-2 - p.y;
@@ -88,12 +88,12 @@ - (UIColor *)colorByInterpolatingPixels:(CGPoint)p {
CGFloat yf0 = 1.0f - modff(p.y, &ipart);
CGFloat xf1 = 1.0f - xf0;
CGFloat yf1 = 1.0f - yf0;
-
+
unsigned char * pixel_x0y0 = _rawData + (_bytesPerRow * y0) + (_bytesPerPixel * x0);
unsigned char * pixel_x1y0 = _rawData + (_bytesPerRow * y0) + (_bytesPerPixel * x1);
unsigned char * pixel_x0y1 = _rawData + (_bytesPerRow * y1) + (_bytesPerPixel * x0);
unsigned char * pixel_x1y1 = _rawData + (_bytesPerRow * y1) + (_bytesPerPixel * x1);
-
+
// do bilinear interpolation
float sy0[4] = { pixel_x0y0[0] * xf0 + pixel_x1y0[0] * xf1,
pixel_x0y0[1] * xf0 + pixel_x1y0[1] * xf1,
@@ -108,12 +108,35 @@ - (UIColor *)colorByInterpolatingPixels:(CGPoint)p {
sy0[2] * yf0 + sy1[2] * yf1,
sy0[3] * yf0 + sy1[3] * yf1 };
- CGFloat red = sxy[0] / 255.0;
- CGFloat green = sxy[1] / 255.0;
- CGFloat blue = sxy[2] / 255.0;
- CGFloat alpha = sxy[3] / 255.0;
-
- return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
+ rgba[0] = sxy[0] / 255.0;
+ rgba[1] = sxy[1] / 255.0;
+ rgba[2] = sxy[2] / 255.0;
+ rgba[3] = sxy[3] / 255.0;
+ return YES;
+}
+
+- (CGFloat)grayAtNearestPixel:(CGPoint)p {
+ CGFloat rgba[4];
+ if ( ![self extractNearestRgba:p to:rgba] ) return 0.0;
+ return ( rgba[0] + rgba[1] + rgba[2] ) * 0.33f;
+}
+
+- (CGFloat)grayByInterpolatingPixels:(CGPoint)p {
+ CGFloat rgba[4];
+ if ( ![self extractInterpolatedRgba:p to:rgba] ) return 0.0;
+ return ( rgba[0] + rgba[1] + rgba[2] ) * 0.33f;
+}
+
+- (UIColor *)colorAtNearestPixel:(CGPoint)p {
+ CGFloat rgba[4];
+ if ( ![self extractNearestRgba:p to:rgba] ) return nil;
+ return [UIColor colorWithRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]];
+}
+
+- (UIColor *)colorByInterpolatingPixels:(CGPoint)p {
+ CGFloat rgba[4];
+ if ( ![self extractInterpolatedRgba:p to:rgba] ) return nil;
+ return [UIColor colorWithRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]];
}
@end
View
2 Source/RAManipulator.h
@@ -28,4 +28,6 @@
- (GLKMatrix4)modelViewMatrix;
+- (BOOL)needsDisplay;
+
@end
View
27 Source/RAManipulator.m
@@ -40,6 +40,7 @@ @implementation RAManipulator {
CameraState _state;
UIView * _view;
+ BOOL _needsDisplay;
}
@synthesize camera;
@@ -48,6 +49,7 @@ - (id)init
{
self = [super init];
if (self) {
+ _needsDisplay = YES;
}
return self;
}
@@ -93,6 +95,7 @@ - (void)setLatitude:(double)latitude {
NSAssert( !isnan(latitude), @"angle cannot be NAN" );
_state.latitude = NormalizeLatitude(latitude);
+ _needsDisplay = YES;
}
- (double)longitude {
@@ -103,6 +106,7 @@ - (void)setLongitude:(double)longitude {
NSAssert( !isnan(longitude), @"angle cannot be NAN" );
_state.longitude = NormalizeLongitude(longitude);
+ _needsDisplay = YES;
}
- (double)azimuth {
@@ -113,6 +117,7 @@ - (void)setAzimuth:(double)azimuth {
NSAssert( !isnan(azimuth), @"angle cannot be NAN" );
_state.azimuth = NormalizeLongitude(azimuth);
+ _needsDisplay = YES;
}
- (double)elevation {
@@ -125,6 +130,7 @@ - (void)setElevation:(double)elevation {
if ( elevation > 90. ) elevation = 90.;
_state.elevation = elevation;
+ _needsDisplay = YES;
}
- (double)distance {
@@ -137,6 +143,13 @@ - (void)setDistance:(double)distance {
if ( distance > 1.e7 ) distance = 1.e7;
_state.distance = distance;
+ _needsDisplay = YES;
+}
+
+- (BOOL)needsDisplay {
+ BOOL flag = _needsDisplay;
+ _needsDisplay = NO;
+ return flag;
}
/*
@@ -216,6 +229,7 @@ - (void)scale:(id)sender {
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
_state = startState;
+ _needsDisplay = YES;
break;
case UIGestureRecognizerStateBegan:
[self stop:nil];
@@ -267,6 +281,7 @@ - (void)move:(id)sender {
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
_state = startState;
+ _needsDisplay = YES;
break;
case UIGestureRecognizerStateBegan:
{
@@ -505,16 +520,4 @@ - (void)debugZoomInOut:(id)sender {
[anim1 beginWithTarget:self];
}
-- (void)debugWorldTour:(id)sender {
- // rotate slowly along line of constant latitude
- double duration = 12.*3600.; // around the world in twelve short hours
-
- TPPropertyAnimation *anim = [TPPropertyAnimation propertyAnimationWithKeyPath:@"longitude"];
- anim.duration = duration;
- anim.fromValue = [NSNumber numberWithDouble:_state.longitude];
- anim.toValue = [NSNumber numberWithDouble:_state.longitude+360];
- anim.timing = TPPropertyAnimationTimingLinear;
- [anim beginWithTarget:self];
-}
-
@end
View
23 Source/RAPage.h
@@ -14,6 +14,14 @@
#import "RACamera.h"
#import "RATileDatabase.h"
+typedef enum {
+ NotLoaded = 0,
+ Loading,
+ Complete,
+ Failed,
+ NeedsUpdate
+} RAPageLoadState;
+
@interface RAPage : NSObject
@@ -27,26 +35,27 @@
@property (strong, nonatomic) RAPage * child3;
@property (strong, nonatomic) RAPage * child4;
+@property (assign, nonatomic) NSTimeInterval lastRequestedTimestamp;
+
+@property (assign, nonatomic) RAPageLoadState geometryState;
@property (strong, nonatomic) RAGeometry * geometry;
+@property (assign, nonatomic) RAPageLoadState imageryState;
@property (strong, nonatomic) RATextureWrapper * imagery;
-@property (strong, nonatomic) UIImage * terrain;
-@property (assign, atomic) BOOL needsUpdate;
-@property (weak, atomic) NSOperation * imageryLoadOp;
-@property (weak, atomic) NSOperation * terrainLoadOp;
-@property (weak, atomic) NSOperation * updatePageOp;
+@property (assign, nonatomic) RAPageLoadState terrainState;
+@property (strong, nonatomic) UIImage * terrain;
+ (NSUInteger)count;
- (RAPage *)initWithTileID:(TileID)t andParent:(RAPage *)parent;
- (void)setCenter:(GLKVector3)center andRadius:(double)radius;
-- (void)cancelOps;
-
- (float)calculateTiltWithCamera:(RACamera *)camera;
- (float)calculateScreenSpaceErrorWithCamera:(RACamera *)camera;
- (BOOL)isOnscreenWithCamera:(RACamera *)camera;
+- (BOOL)isReady;
+
@end
View
33 Source/RAPage.m
@@ -18,8 +18,8 @@ @implementation RAPage {
@synthesize tile, key;
@synthesize bound = _bound;
@synthesize parent = _parent, child1, child2, child3, child4;
-@synthesize geometry, imagery, terrain;
-@synthesize needsUpdate, imageryLoadOp, terrainLoadOp, updatePageOp;
+@synthesize lastRequestedTimestamp;
+@synthesize geometryState, geometry, imageryState, imagery, terrainState, terrain;
+ (NSUInteger)count {
return sTotalPageCount;
@@ -31,15 +31,17 @@ - (RAPage *)initWithTileID:(TileID)t andParent:(RAPage *)parent;
if (self) {
tile = t;
key = [NSString stringWithFormat:@"{%d,%d,%d}", t.z, t.x, t.y];
- needsUpdate = YES;
_parent = parent;
sTotalPageCount++;
+
+ geometryState = NotLoaded;
+ imageryState = NotLoaded;
+ terrainState = NotLoaded;
}
return self;
}
- (void)dealloc {
- [self cancelOps];
sTotalPageCount--;
}
@@ -49,12 +51,6 @@ - (void)setCenter:(GLKVector3)center andRadius:(double)radius {
_bound.radius = radius;
}
-- (void)cancelOps {
- [imageryLoadOp cancel];
- [terrainLoadOp cancel];
- [updatePageOp cancel];
-}
-
- (float)calculateTiltWithCamera:(RACamera *)camera {
// calculate dot product between page normal and camera vector
const GLKVector3 unitZ = { 0, 0, -1 };
@@ -64,18 +60,17 @@ - (float)calculateTiltWithCamera:(RACamera *)camera {
}
- (float)calculateScreenSpaceErrorWithCamera:(RACamera *)camera {
- // !!! this does not work so well on large, curved pages
// in this case, should test all four corners of the tile and take min distance
GLKVector3 center = GLKMatrix4MultiplyAndProjectVector3( camera.modelViewMatrix, self.bound.center );
- double distance = GLKVector3Length(center);
- //double distance = -center.z; // seem like this should be more accurate, but the math clearing isn't quite right, as it favors pages near the equator
-
+ double distance = GLKVector3Length(center); // !!! this does not work so well on large, curved pages
+ //double distance = -center.z; // seem like this should be more accurate, but the math isn't quite right. It favors pages near the equator
+
// !!! this should be based upon the Camera parameters
double theta = GLKMathDegreesToRadians(65.0f);
double w = 2. * distance * tan(theta/2.);
// convert object error to screen error
- double x = 1024; // screen size
+ double x = camera.viewport.size.width; // screen size
double epsilon = ( 2. * self.bound.radius ) / 256.; // object error
return ( epsilon * x ) / w;
}
@@ -84,10 +79,14 @@ - (BOOL)isOnscreenWithCamera:(RACamera *)camera {
GLKMatrix4 modelViewProjectionMatrix = GLKMatrix4Multiply( camera.projectionMatrix, camera.modelViewMatrix );
RABoundingSphere * sb = [self.bound transform:modelViewProjectionMatrix];
- if ( sb.center.x + sb.radius < -1.5 || sb.center.x - sb.radius > 1.5 ) return NO;
- if ( sb.center.y + sb.radius < -1.5 || sb.center.y - sb.radius > 1.5 ) return NO;
+ if ( sb.center.x + sb.radius < -1 || sb.center.x - sb.radius > 1 ) return NO;
+ if ( sb.center.y + sb.radius < -1 || sb.center.y - sb.radius > 1 ) return NO;
return YES;
}
+- (BOOL)isReady {
+ return ( geometryState == Complete ) || ( geometryState == NeedsUpdate );
+}
+
@end
View
36 Source/RARenderVisitor.m
@@ -114,6 +114,8 @@ - (void)tearDownGL
- (void)render
{
+ //NSLog(@"Rendering %d objects", renderQueue.count);
+
[self sortBackToFront];
if ( ! [shader isReady] ) [self setupGL];
@@ -172,39 +174,43 @@ - (void)applyGeometry:(RAGeometry *)node
- (void)applyPageNode:(RAPageNode *)node
{
- [self traversePage: node.page];
+ GLKMatrix4 modelViewMatrix = GLKMatrix4Multiply( self.camera.modelViewMatrix, [self currentTransform] );
+ GLKMatrix4 modelViewProjectionMatrix = GLKMatrix4Multiply( self.camera.projectionMatrix, modelViewMatrix );
+
+ [self traversePage:node.page withModelViewProjectionMatrix:modelViewProjectionMatrix];
}
-- (bool)traversePage:(RAPage *)page {
+- (BOOL)traversePage:(RAPage *)page withModelViewProjectionMatrix:(GLKMatrix4)matrix {
if ( page == nil ) return NO;
+ // don't bother traversing if we are offscreen
+ if ( ! [page isOnscreenWithCamera:self.camera] ) {
+ return YES;
+ }
+
// is the page facing away from the camera?
if ( page.tile.z > 2 && [page calculateTiltWithCamera:self.camera] < 0.0f ) return YES;
- float texelError = 0.0f;
- texelError = [page calculateScreenSpaceErrorWithCamera:self.camera];
+ float texelError = [page calculateScreenSpaceErrorWithCamera:self.camera];
// should we choose to display this page?
- if ( texelError < 3.f && page.geometry ) {
- // don't bother traversing if we are offscreen
- if ( ! [page isOnscreenWithCamera:self.camera] ) return YES;
-
+ if ( texelError < 3.0f && page.isReady ) {
[self applyGeometry: page.geometry];
return YES;
}
BOOL success = YES;
- // !!! this doesn't work well because it can't skip to grandchildren
- success = ( page.child1.geometry && page.child2.geometry && page.child3.geometry && page.child4.geometry );
+ // !!! this doesn't work well because it can't skip to grandchildren: each level must be loaded consecutively
+ success = ( page.child1.isReady && page.child2.isReady && page.child3.isReady && page.child4.isReady );
// traverse children
if ( success ) {
- [self traversePage: page.child1];
- [self traversePage: page.child2];
- [self traversePage: page.child3];
- [self traversePage: page.child4];
- } else {
+ [self traversePage:page.child1 withModelViewProjectionMatrix:matrix];
+ [self traversePage:page.child2 withModelViewProjectionMatrix:matrix];
+ [self traversePage:page.child3 withModelViewProjectionMatrix:matrix];
+ [self traversePage:page.child4 withModelViewProjectionMatrix:matrix];
+ } else if ( page.isReady ) {
// don't bother traversing if we are offscreen
if ( ! [page isOnscreenWithCamera:self.camera] ) return YES;
View
2 Source/RASceneGraphController.h
@@ -12,7 +12,7 @@
#import "RACamera.h"
#import "RATilePager.h"
-@interface RASceneGraphController : GLKViewController
+@interface RASceneGraphController : UIViewController <GLKViewDelegate>
@property (readonly, nonatomic) RANode * sceneRoot;
@property (readonly, nonatomic) RACamera * camera;
View
153 Source/RASceneGraphController.m
@@ -8,7 +8,8 @@
#import "RASceneGraphController.h"
-#import <GLKit/GLKTextureLoader.h>
+#import <GLKit/GLKit.h>
+#import <QuartzCore/QuartzCore.h>
#import "RABoundingSphere.h"
#import "RANodeVisitor.h"
@@ -23,19 +24,6 @@
#pragma mark -
-@interface SetupGeometryVisitor : RANodeVisitor
-@end
-
-@implementation SetupGeometryVisitor
-- (void)applyGeometry:(RAGeometry *)node
-{
- [node setupGL];
-}
-@end
-
-
-#pragma mark -
-
@interface ReleaseGeometryVisitor : RANodeVisitor
@end
@@ -50,13 +38,15 @@ - (void)applyGeometry:(RAGeometry *)node
@interface RASceneGraphController () {
- RARenderVisitor * renderVisitor;
- RAManipulator * manipulator;
- RAWorldTour * tourController;
+ RARenderVisitor * _renderVisitor;
+ RAManipulator * _manipulator;
+ RAWorldTour * _tourController;
- EAGLContext * context;
- //GLKBaseEffect * effect;
- //GLKSkyboxEffect * skybox;
+ EAGLContext * _context;
+ GLKSkyboxEffect * _skybox;
+ CADisplayLink * _displayLink;
+
+ BOOL _needsDisplay;
}
- (void)setupGL;
@@ -81,11 +71,11 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
_camera.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), 1, 1, 100);
_camera.modelViewMatrix = GLKMatrix4Identity;
- manipulator = [RAManipulator new];
- manipulator.camera = self.camera;
+ _manipulator = [RAManipulator new];
+ _manipulator.camera = self.camera;
- renderVisitor = [RARenderVisitor new];
- renderVisitor.camera = self.camera;
+ _renderVisitor = [RARenderVisitor new];
+ _renderVisitor.camera = self.camera;
RATileDatabase * database = [RATileDatabase new];
database.bounds = CGRectMake( -180,-90,360,180 );
@@ -100,6 +90,8 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
_pager = [RATilePager new];
_pager.imageryDatabase = database;
_pager.camera = self.camera;
+
+ _needsDisplay = YES;
}
return self;
}
@@ -108,29 +100,35 @@ - (void)viewDidLoad
{
[super viewDidLoad];
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- if (!context) {
+ if (!_context) {
NSLog(@"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
- view.context = context;
+ view.context = _context;
+ view.delegate = self;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.drawableMultisample = GLKViewDrawableMultisample4X;
- manipulator.view = self.view;
+ _manipulator.view = self.view;
+
+ // setup display link to update the view
+ _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkUpdate:)];
+ [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// create another context for threaded operations
- self.pager.auxilliaryContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:[context sharegroup]];
+ self.pager.auxilliaryContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:[_context sharegroup]];
// add world tour
- tourController = [RAWorldTour new];
- tourController.manipulator = manipulator;
+ _tourController = [RAWorldTour new];
+ _tourController.manipulator = _manipulator;
- UITapGestureRecognizer * recognizer = [[UITapGestureRecognizer alloc] initWithTarget:tourController action:@selector(startOrStop:)];
+ UITapGestureRecognizer * recognizer = [[UITapGestureRecognizer alloc] initWithTarget:_tourController action:@selector(startOrStop:)];
[recognizer setNumberOfTapsRequired:4];
[self.view addGestureRecognizer:recognizer];
+ //[tourController start: self];
[self.pager setup];
[self setupGL];
@@ -141,17 +139,21 @@ - (void)viewDidUnload
[super viewDidUnload];
[self tearDownGL];
+ [_displayLink invalidate];
- if ([EAGLContext currentContext] == context) {
+ if ([EAGLContext currentContext] == _context) {
[EAGLContext setCurrentContext:nil];
}
- context = nil;
+ _context = nil;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
+
// Release any cached data, images, etc. that aren't in use.
+ [RATextureWrapper cleanupAll: YES];
+ [RAGeometry cleanupAll: YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
@@ -163,6 +165,20 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interface
}
}
+- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
+ _needsDisplay = YES;
+}
+
+- (void)displayLinkUpdate:(CADisplayLink *)sender {
+ GLKView *view = (GLKView *)self.view;
+
+ if ( [_manipulator needsDisplay] || [_pager needsDisplay] || _needsDisplay ) {
+ [self update];
+ [view display];
+ }
+
+ _needsDisplay = NO;
+}
#pragma mark - Scene Graph
@@ -245,64 +261,55 @@ - (RANode *)createBlueMarble
- (void)setupGL
{
- [EAGLContext setCurrentContext:context];
+ [EAGLContext setCurrentContext:_context];
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
- /*
// setup skybox
NSString * starPath = [[NSBundle mainBundle] pathForResource:@"star1" ofType:@"png"];
NSArray * starPaths = [NSArray arrayWithObjects: starPath, starPath, starPath, starPath, starPath, starPath, nil];
NSError * error = nil;
- NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
- forKey:GLKTextureLoaderOriginBottomLeft];
+ NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft,
+ [NSNumber numberWithBool:YES], GLKTextureLoaderGenerateMipmaps,
+ nil];
GLKTextureInfo * starTexture = [GLKTextureLoader cubeMapWithContentsOfFiles:starPaths options:options error:&error];
-
- skybox = [[GLKSkyboxEffect alloc] init];
- skybox.label = @"Stars";
- skybox.xSize = skybox.ySize = skybox.zSize = 40;
- skybox.textureCubeMap.name = starTexture.name;
-
- NSLog(@"sky = %d, err = %@", starTexture.name, error);
- */
+
+ _skybox = [[GLKSkyboxEffect alloc] init];
+ _skybox.label = @"Stars";
+ _skybox.xSize = _skybox.ySize = _skybox.zSize = 40;
+ _skybox.textureCubeMap.name = starTexture.name;
// set as scene
_sceneRoot = [self createBlueMarble];
- //SetupGeometryVisitor * setupVisitor = [[SetupGeometryVisitor alloc] init];
- //[self.sceneRoot accept: setupVisitor];
-
[self update];
- [renderVisitor setupGL];
+ [_renderVisitor setupGL];
}
- (void)tearDownGL
{
- [EAGLContext setCurrentContext:context];
+ [EAGLContext setCurrentContext:_context];
ReleaseGeometryVisitor * releaseVisitor = [[ReleaseGeometryVisitor alloc] init];
[self.sceneRoot accept: releaseVisitor];
- //effect = nil;
- [renderVisitor tearDownGL];
+ [_renderVisitor tearDownGL];
}
-#pragma mark - GLKView and GLKViewController delegate methods
-
- (void)update
{
- self.camera.modelViewMatrix = [manipulator modelViewMatrix];
+ self.camera.modelViewMatrix = [_manipulator modelViewMatrix];
// position light directly above the globe
RAPolarCoordinate lightPolar = {
- manipulator.latitude, manipulator.longitude, 1e7
+ _manipulator.latitude, _manipulator.longitude, 1e7
};
GLKVector3 lightEcef = ConvertPolarToEcef( lightPolar );
- //effect.light0.position = GLKVector4MakeWithVector3(lightEcef, 1.0);
- renderVisitor.lightPosition = lightEcef;
+ _renderVisitor.lightPosition = lightEcef;
// !!! the scene view bound is incorrect
@@ -311,8 +318,8 @@ - (void)update
float minDistance = -center.z - self.sceneRoot.bound.radius;
float maxDistance = -center.z + self.sceneRoot.bound.radius;
//NSLog(@"Z Buffer: %f - %f, Scene Radius: %f", minDistance, maxDistance, self.sceneRoot.bound.radius);
- if ( minDistance < 0.0001f ) minDistance = 0.0001f;
- if ( maxDistance < 50.0f ) maxDistance = 50.0f; // room for skybox
+ if ( minDistance < 0.001f ) minDistance = 0.001f;
+ if ( maxDistance < 60.0f ) maxDistance = 60.0f; // room for skybox
// update projection
float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
@@ -321,27 +328,27 @@ - (void)update
self.camera.projectionMatrix = projectionMatrix;
self.camera.viewport = self.view.bounds;
- //skybox.transform.projectionMatrix = projectionMatrix;
- //skybox.transform.modelviewMatrix = self.camera.modelViewMatrix;
+ _skybox.transform.projectionMatrix = projectionMatrix;
+ _skybox.transform.modelviewMatrix = self.camera.modelViewMatrix;
[self.pager update];
}
+#pragma mark - GLKView delegate methods
+
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.0f, 0.2f, 0.0f, 1.0f);
// render the skybox
- //glDisable(GL_DEPTH_TEST);
- //[skybox prepareToDraw];
- //[skybox draw];
- //glEnable(GL_DEPTH_TEST);
+ [_skybox prepareToDraw];
+ [_skybox draw];
// run the render visitor
- [renderVisitor clear];
- [self.sceneRoot accept: renderVisitor];
- [renderVisitor render];
+ [_renderVisitor clear];
+ [self.sceneRoot accept: _renderVisitor];
+ [_renderVisitor render];
// check for errors
GLenum err = glGetError();
@@ -355,9 +362,9 @@ - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
case GL_OUT_OF_MEMORY: NSLog(@"glGetError: out of memory"); break;
default: NSLog(@"glGetError: unknown error = 0x%04X", err); break;
}
-
- [RATextureWrapper cleanup];
- [RAGeometry cleanup];
+
+ [RATextureWrapper cleanupAll:NO];
+ [RAGeometry cleanupAll:NO];
}
@end
View
6 Source/RATextureWrapper.h
@@ -18,12 +18,10 @@
@property (readonly) GLenum target;
@property (readonly) GLuint width;
@property (readonly) GLuint height;
-@property (readonly) GLKTextureInfoAlphaState alphaState;
-@property (readonly) GLKTextureInfoOrigin textureOrigin;
-@property (readonly) BOOL containsMipmaps;
-+ (void)cleanup;
++ (void)cleanupAll:(BOOL)all;
- (id)initWithTextureInfo:(GLKTextureInfo *)info;
+- (id)initWithImage:(UIImage *)image;
@end
View
123 Source/RATextureWrapper.m
@@ -8,7 +8,7 @@
#import "RATextureWrapper.h"
-#define kMaxDeleteBatchSize (8)
+#define kMaxDeleteBatchSize (32)
@implementation RATextureWrapper {
@@ -19,19 +19,12 @@ @implementation RATextureWrapper {
@synthesize target = _target;
@synthesize width = _width;
@synthesize height = _height;
-@synthesize alphaState = _alphaState;
-@synthesize textureOrigin = _textureOrigin;
-@synthesize containsMipmaps = _containsMipmaps;
-+ (NSMutableDictionary *)textureSetDictionary {
++ (NSMutableSet *)textureDeletionSetForKey:(NSString *)key {
static NSMutableDictionary * dict = nil;
if ( dict == nil ) dict = [NSMutableDictionary dictionary];
- return dict;
-}
-
-+ (NSMutableSet *)textureSetForKey:(NSString *)key {
+
// get or create a set for this context
- NSMutableDictionary * dict = [[self class] textureSetDictionary];
NSMutableSet * set = [dict objectForKey:key];
if ( !set ) {
set = [NSMutableSet set];
@@ -40,60 +33,118 @@ + (NSMutableSet *)textureSetForKey:(NSString *)key {
return set;
}
-+ (void)cleanup {
- EAGLContext * context = [EAGLContext currentContext];
- NSAssert( context, @"OpenGL ES context must be valid!" );
++ (void)cleanupAll:(BOOL)all {
+ NSAssert( [EAGLContext currentContext], @"OpenGL ES context must be valid!" );
- NSString * key = [context description];
- NSMutableSet * set = [self textureSetForKey:key];
+ NSString * key = [[[EAGLContext currentContext] sharegroup] description];
+ NSMutableSet * set = [self textureDeletionSetForKey:key];
- GLuint * textureNames = NULL;
+ GLuint textureNames[kMaxDeleteBatchSize];
NSUInteger count = 0;
@synchronized (set) {
- if ( [set count] < 1 ) return;
-
- textureNames = (GLuint *)alloca( kMaxDeleteBatchSize );
+ if ( set.count < 1 ) return;
- while( count < kMaxDeleteBatchSize && [set count] ) {
- NSNumber * nameValue = [set anyObject];
+ NSNumber * nameValue = nil;
+ while( (nameValue = [set anyObject]) ) {
[set removeObject:nameValue];
textureNames[count] = [nameValue intValue];
count++;
+
+ if ( count == kMaxDeleteBatchSize ) break;
}
}
+
+ if ( count > 0 ) {
+ glDeleteTextures(count, textureNames);
+ //NSLog(@"Deleted %d textures.", count);
+
+ // check for errors
+ GLenum err = glGetError();
+ if ( err != GL_NO_ERROR )
+ NSLog(@"+[RATextureWrapper cleanup]: glGetError = %d", err);
+ }
+
+ // cleanup more if requested
+ if ( all ) [self cleanupAll:YES];
+}
- glDeleteTextures(count, textureNames);
- NSLog(@"Deleted %d textures.", count);
-
- // check for errors
- GLenum err = glGetError();
- if ( err != GL_NO_ERROR )
- NSLog(@"+[RATextureWrapper cleanup]: glGetError = %d", err);
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ NSAssert( [EAGLContext currentContext], @"OpenGL ES context must be valid!" );
+ NSString * key = [[[EAGLContext currentContext] sharegroup] description];
+ _contextKey = key;
+ }
+ return self;
}
- (id)initWithTextureInfo:(GLKTextureInfo *)info {
- self = [super init];
+ self = [self init];
if ( self && info ) {
_name = info.name;
_target = info.target;
_width = info.width;
_height = info.height;
- _alphaState = info.alphaState;
- _textureOrigin = info.textureOrigin;
- _containsMipmaps = info.containsMipmaps;
+ }
+ return self;
+}
+
+- (id)initWithImage:(UIImage *)image {
+ self = [self init];
+ if ( self && image ) {
+ // get raw access to image data
+ CGImageRef imageRef = [image CGImage];
+ _width = CGImageGetWidth(imageRef);
+ _height = CGImageGetHeight(imageRef);
+
+ char * pixels = (char *)calloc( _height * _width * 4, sizeof(char) );
+ NSUInteger bitsPerComponent = 8;
+ NSUInteger bytesPerPixel = 4;
+ NSUInteger bytesPerRow = bytesPerPixel * _width;
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+
+ CGContextRef context = CGBitmapContextCreate( pixels, _width, _height,
+ bitsPerComponent, bytesPerRow, colorSpace,
+ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
+
+ // draw, y-flipped
+ CGContextTranslateCTM( context, 0, _height );
+ CGContextScaleCTM( context, 1.0f, -1.0f );
+ CGContextDrawImage(context, CGRectMake(0, 0, _width, _height), imageRef);
+
+ // generate texture object
+ GLuint texture;
+ glGenTextures( 1, &texture );
+ glBindTexture( GL_TEXTURE_2D, texture );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ _target = GL_TEXTURE_2D;
+ _name = texture;
+
+ // upload image
+ glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels );
+ glGenerateMipmap( GL_TEXTURE_2D );
+
+ CGContextRelease(context);
+ CGColorSpaceRelease(colorSpace);
+ free( pixels );
- EAGLContext * context = [EAGLContext currentContext];
- NSAssert( context, @"OpenGL ES context must be valid!" );
- _contextKey = [context description];
+ // simple way to check that we don't have too many textures active
+ if ( texture > 500 )
+ NSLog(@"Warning: texture id = %d", texture);
}
return self;
}
- (void)dealloc {
if ( _name && _contextKey ) {
- NSMutableSet * set = [[self class] textureSetForKey:_contextKey];
+ NSMutableSet * set = [[self class] textureDeletionSetForKey:_contextKey];
// mark for cleanup
@synchronized (set) {
View
2 Source/RATilePager.h
@@ -30,4 +30,6 @@
- (void)setup; // call once the databases are configured
- (void)update;
+- (BOOL)needsDisplay;
+
@end
View
409 Source/RATilePager.m
@@ -29,11 +29,12 @@ @implementation RAPageDelta
@implementation RATilePager {
RATextureWrapper * _defaultTexture;
-
- NSOperationQueue * _loadQueue;
- NSOperationQueue * _traverseQueue;
+
+ NSOperationQueue * _updateQueue;
+ NSOperationQueue * _connectionQueue;
NSOperationQueue * _graphicsQueue;
- __weak NSOperation * _traverseOp;
+
+ BOOL _needsDisplay;
}
@synthesize imageryDatabase, terrainDatabase, auxilliaryContext, rootNode, rootPages, camera;
@@ -42,30 +43,27 @@ - (id)init
{
self = [super init];
if (self) {
- // enlarge shared cache
- NSURLCache * cache = [NSURLCache sharedURLCache];
- [cache setMemoryCapacity: 5*1000*1000];
- [cache setDiskCapacity: 250*1000*1000];
-
- _loadQueue = [[NSOperationQueue alloc] init];
- [_loadQueue setName:@"Loading Queue"];
+ _updateQueue = [[NSOperationQueue alloc] init];
+ [_updateQueue setName:@"org.dancingrobots.traversequeue"];
- _traverseQueue = [[NSOperationQueue alloc] init];
- [_traverseQueue setName:@"Traverse Queue"];
+ _connectionQueue = [[NSOperationQueue alloc] init];
+ [_connectionQueue setName:@"org.dancingrobots.connectionqueue"];
_graphicsQueue = [[NSOperationQueue alloc] init];
- [_graphicsQueue setName:@"OpenGL ES Serial Queue"];
+ [_graphicsQueue setName:@"org.dancingrobots.graphicsqueue"];
[_graphicsQueue setMaxConcurrentOperationCount: 1];
+
+ _needsDisplay = YES;
}
return self;
}
- (void)dealloc {
- [_loadQueue cancelAllOperations];
- [_loadQueue waitUntilAllOperationsAreFinished];
+ [_updateQueue cancelAllOperations];
+ [_updateQueue waitUntilAllOperationsAreFinished];
- [_traverseQueue cancelAllOperations];
- [_traverseQueue waitUntilAllOperationsAreFinished];
+ [_connectionQueue cancelAllOperations];
+ [_connectionQueue waitUntilAllOperationsAreFinished];
[_graphicsQueue cancelAllOperations];
[_graphicsQueue waitUntilAllOperationsAreFinished];
@@ -97,6 +95,12 @@ - (void)setup {
rootNode = root;
}
+- (BOOL)needsDisplay {
+ BOOL flag = _needsDisplay;
+ _needsDisplay = NO;
+ return flag;
+}
+
- (RAGeometry *)createGeometryForTile:(TileID)tile
{
// create geometry node
@@ -108,26 +112,29 @@ - (RAGeometry *)createGeometryForTile:(TileID)tile
}
- (void)setupGeometry:(RAGeometry *)geom forPage:(RAPage *)page withTextureFromPage:(RAPage *)texPage withHeightFromPage:(RAPage *)hgtPage {
- int gridSize = 64;
RAPolarCoordinate lowerLeft = [self.imageryDatabase tileLatLonOrigin:page.tile];
RAPolarCoordinate upperRight = [self.imageryDatabase tileLatLonOrigin:TileOppositeCorner(page.tile)];
// use more precision for large tiles
- if ( upperRight.latitude - lowerLeft.latitude > 10. ||
- upperRight.longitude - lowerLeft.longitude > 10. ) gridSize = 32;
+ const int gridSize = 64;
+ const int border = 1;
- double latInterval = ( upperRight.latitude - lowerLeft.latitude ) / (gridSize-1);
- double lonInterval = ( upperRight.longitude - lowerLeft.longitude ) / (gridSize-1);
+ double latInterval = ( upperRight.latitude - lowerLeft.latitude ) / (gridSize-1-border-border);
+ double lonInterval = ( upperRight.longitude - lowerLeft.longitude ) / (gridSize-1-border-border);
+ lowerLeft.latitude -= latInterval;
+ lowerLeft.longitude -= lonInterval;
// fits in index value?
NSAssert( gridSize*gridSize < 65535, @"too many grid elements" );
- size_t vertexDataSize = 8*sizeof(GLfloat) * gridSize*gridSize;
- GLfloat * vertexData = (GLfloat *)calloc(gridSize*gridSize, 8*sizeof(GLfloat));
+ const NSUInteger vertexElements = 8;
+ size_t vertexDataSize = vertexElements*sizeof(GLfloat) * gridSize*gridSize;
+ GLfloat * vertexData = (GLfloat *)calloc(gridSize*gridSize, vertexElements*sizeof(GLfloat));
- size_t indexDataSize = 6*sizeof(GLushort) * (gridSize-1)*(gridSize-1);
- GLushort * indexData = (GLushort *)calloc((gridSize-1)*(gridSize-1), 6*sizeof(GLushort));
+ const NSUInteger indexElements = 6;
+ size_t indexDataSize = indexElements*sizeof(GLushort) * (gridSize-1)*(gridSize-1);
+ GLushort * indexData = (GLushort *)calloc((gridSize-1)*(gridSize-1), indexElements*sizeof(GLushort));
RAImageSampler * sampler = [[RAImageSampler alloc] initWithImage:hgtPage.terrain];
@@ -145,30 +152,33 @@ - (void)setupGeometry:(RAGeometry *)geom forPage:(RAPage *)page withTextureFromP
GLKVector3 ecef = ConvertPolarToEcef(gpos);
GLKVector2 tex = [self.imageryDatabase textureCoordsForLatLon:gpos inTile:texPage.tile];
- if ( sampler ) {
+ GLKVector3 normal = GLKVector3Normalize(ecef);
+ float height = 0.0f;
+
+ if ( gx < border || gy < border || gx > gridSize-1-border || gy > gridSize-1-border )
+ height = -0.2f; // skirt
+ else if ( sampler ) {
GLKVector2 hgtPixel = [self.imageryDatabase textureCoordsForLatLon:gpos inTile:hgtPage.tile];
hgtPixel.x = (hgtPage.terrain.size.width-1) * hgtPixel.x;
hgtPixel.y = (hgtPage.terrain.size.height-1) * hgtPixel.y;
CGPoint p = CGPointMake(hgtPixel.x, hgtPixel.y);
- UIColor * color = [sampler colorByInterpolatingPixels:p];
- if ( color == nil ) NSLog(@"Failed to sample: %f %f", hgtPixel.x, hgtPixel.y);
-
- GLKVector3 normal = GLKVector3Normalize(ecef);
- CGFloat r, g, b, a;
- if ( [color getRed:&r green:&g blue:&b alpha:&a] ) {
- // extrude by height
- float height = 0.015f * ( r + g + b ) / 3.0f;
- ecef = GLKVector3Add( ecef, GLKVector3MultiplyScalar(normal, height) );
- }
+ height = 0.015f * [sampler grayByInterpolatingPixels:p];
}
+ // extrude by height
+ ecef = GLKVector3Add( ecef, GLKVector3MultiplyScalar(normal, height) );
+
// fill vertex data
vertexData[vertexDataPos+0] = ecef.x;
vertexData[vertexDataPos+1] = ecef.y;
vertexData[vertexDataPos+2] = ecef.z;
+ vertexData[vertexDataPos+3] = normal.x;
+ vertexData[vertexDataPos+4] = normal.y;
+ vertexData[vertexDataPos+5] = normal.z;
+
vertexData[vertexDataPos+6] = tex.x;
vertexData[vertexDataPos+7] = tex.y;
@@ -182,54 +192,51 @@ - (void)setupGeometry:(RAGeometry *)geom forPage:(RAPage *)page withTextureFromP
indexData[indexDataPos+4] = baseElement + gridSize + 1;
indexData[indexDataPos+5] = baseElement + gridSize;
- indexDataPos += 6;
+ indexDataPos += indexElements;
}
- vertexDataPos += 8;
+ vertexDataPos += vertexElements;
}
}
- NSAssert( vertexDataPos == 8*gridSize*gridSize, @"didn't fill vertex array" );
- NSAssert( indexDataPos == 6*(gridSize-1)*(gridSize-1), @"didn't fill index array" );
-
- vertexDataPos = 0;
+ NSAssert( vertexDataPos == vertexElements*gridSize*gridSize, @"didn't fill vertex array" );
+ NSAssert( indexDataPos == indexElements*(gridSize-1)*(gridSize-1), @"didn't fill index array" );
// calculate normals
- for( unsigned int gy = 0; gy < gridSize; gy++ ) {
- for( unsigned int gx = 0; gx < gridSize; gx++ ) {
+ const int rowOffset = vertexElements * gridSize;
+ GLKVector3 ecef0, ecef1;
+ int offset = 0;
+ for( unsigned int gy = border; gy < gridSize-border; gy++ ) {
+ for( unsigned int gx = border; gx < gridSize-border; gx++ ) {
GLKVector3 vectorLeft, vectorRight;
- GLKVector3 ecef0 = GLKVector3Make( vertexData[vertexDataPos+0], vertexData[vertexDataPos+1], vertexData[vertexDataPos+2] );
- int rowOffset = 8 * gridSize;
+ // index of center element
+ vertexDataPos = ( gy * rowOffset ) + ( gx * vertexElements );
- if ( gx < gridSize-1 ) {
- GLKVector3 ecef1 = GLKVector3Make( vertexData[vertexDataPos+0+8], vertexData[vertexDataPos+1+8], vertexData[vertexDataPos+2+8] );
- vectorLeft = GLKVector3Subtract(ecef1, ecef0);
- } else {
- GLKVector3 ecef1 = GLKVector3Make( vertexData[vertexDataPos+0-8], vertexData[vertexDataPos+1-8], vertexData[vertexDataPos+2-8] );
- vectorLeft = GLKVector3Subtract(ecef0, ecef1);
- }
-
- if ( gy < gridSize-1 ) {
- GLKVector3 ecef1 = GLKVector3Make( vertexData[vertexDataPos+0+rowOffset], vertexData[vertexDataPos+1+rowOffset], vertexData[vertexDataPos+2+rowOffset] );
- vectorRight = GLKVector3Subtract(ecef1, ecef0);
- } else {
- GLKVector3 ecef1 = GLKVector3Make( vertexData[vertexDataPos+0-rowOffset], vertexData[vertexDataPos+1-rowOffset], vertexData[vertexDataPos+2-rowOffset] );
- vectorRight = GLKVector3Subtract(ecef0, ecef1);
- }
+ // eastward component
+ offset = ( gx > border ) ? -vertexElements : 0;
+ ecef0 = GLKVector3Make( vertexData[vertexDataPos+offset+0], vertexData[vertexDataPos+offset+1], vertexData[vertexDataPos+offset+2] );
+ offset = ( gx < gridSize-1-border ) ? vertexElements : 0;
+ ecef1 = GLKVector3Make( vertexData[vertexDataPos+offset+0], vertexData[vertexDataPos+offset+1], vertexData[vertexDataPos+offset+2] );
+ vectorLeft = GLKVector3Subtract(ecef1, ecef0);
+
+ // northward component
+ offset = ( gy > border ) ? -rowOffset : 0;
+ ecef0 = GLKVector3Make( vertexData[vertexDataPos+offset+0], vertexData[vertexDataPos+offset+1], vertexData[vertexDataPos+offset+2] );
+ offset = ( gy < gridSize-1-border ) ? rowOffset : 0;
+ ecef1 = GLKVector3Make( vertexData[vertexDataPos+offset+0], vertexData[vertexDataPos+offset+1], vertexData[vertexDataPos+offset+2] );
+ vectorRight = GLKVector3Subtract(ecef1, ecef0);
GLKVector3 normal = GLKVector3CrossProduct( vectorLeft, vectorRight );
normal = GLKVector3Normalize(normal);
vertexData[vertexDataPos+3] = normal.x;
vertexData[vertexDataPos+4] = normal.y;
vertexData[vertexDataPos+5] = normal.z;
-
- vertexDataPos += 8;
}
}
- [geom setObjectData:vertexData withSize:vertexDataSize withStride:(8*sizeof(GLfloat))];
+ [geom setObjectData:vertexData withSize:vertexDataSize withStride:(vertexElements*sizeof(GLfloat))];
[geom setIndexData:indexData withSize:indexDataSize withStride:sizeof(GLushort)];
free( vertexData );
@@ -238,14 +245,18 @@ - (void)setupGeometry:(RAGeometry *)geom forPage:(RAPage *)page withTextureFromP
- (void)updatePageIfNeeded:(RAPage *)page {
NSAssert( page != nil, @"the requested page must be valid");
-
- if ( page.needsUpdate == YES && page.updatePageOp == nil ) {
- RAGeometry * geometry = page.geometry;
+
+ // build geometry if needed
+ if ( page.geometryState == NotLoaded ) {
+ NSAssert( page.geometry == nil, @"geometry must be nil if unloaded" );
- // build geometry if needed
- if ( geometry == nil ) {
- geometry = [self createGeometryForTile:page.tile];
- }
+ page.geometry = [self createGeometryForTile:page.tile];
+ page.geometryState = Loading;
+ }
+
+ // update if needed
+ if ( page.geometryState == NeedsUpdate || page.geometryState == Loading ) {
+ NSAssert( page.geometry != nil, @"geometry must be not nil if updating" );
// find an ancestor tile with a valid texture
RAPage * imgAncestor = page;
@@ -266,106 +277,145 @@ - (void)updatePageIfNeeded:(RAPage *)page {
if ( imgAncestor ) {
// recycle texture with appropriate tex coords
- [self setupGeometry:geometry forPage:page withTextureFromPage:imgAncestor withHeightFromPage:hgtAncestor];
- geometry.texture0 = imgAncestor.imagery;
+ [self setupGeometry:page.geometry forPage:page withTextureFromPage:imgAncestor withHeightFromPage:hgtAncestor];
+ page.geometry.texture0 = imgAncestor.imagery;
} else {
// show grid if necessary
- [self setupGeometry:geometry forPage:page withTextureFromPage:page withHeightFromPage:hgtAncestor];
- geometry.texture0 = _defaultTexture;
+ [self setupGeometry:page.geometry forPage:page withTextureFromPage:page withHeightFromPage:hgtAncestor];
+ page.geometry.texture0 = _defaultTexture;
}
-
- if ( page.geometry == nil )
- page.geometry = geometry;
- page.needsUpdate = NO;
+ page.geometryState = Complete;
+ _needsDisplay = YES;
}
}
- (void)requestPage:(RAPage *)page {
NSAssert( page != nil, @"the requested page must be valid");
+ const NSUInteger kMaxQueueDepth = 32;
+ const NSTimeInterval kTimeoutInterval = 5.0f;
- // !!! still a memory leak here...
-
+ __block RATilePager * mySelf = self;
+
// request the tile image if needed
- if ( page.imagery == nil && page.imageryLoadOp == nil && self.imageryDatabase ) {
+ if ( page.imageryState == NotLoaded ) {
NSURL * url = [self.imageryDatabase urlForTile: page.tile];
- NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15.0];
- if ( url && request && _loadQueue.operationCount < 16 ) {
- NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
+ if ( url == nil ) {
+ page.imageryState = Failed;
+ } else if ( _connectionQueue.operationCount < kMaxQueueDepth ) {
+ page.imageryState = Loading;
+
+ // capture ivar locally to avoid retain cycle
+ NSOperationQueue * graphicsQueue = _graphicsQueue;
+
+ [_connectionQueue addOperationWithBlock:^{
+ NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:kTimeoutInterval];
NSURLResponse * response = nil;
NSError * error = nil;
+
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if ( error ) {
- NSLog(@"URL Error loading (%@): %@", url, error);
+ // attempt to reload if the connection timed out
+ if ( [[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorTimedOut ) {
+ page.imageryState = NotLoaded;
+ return;
+ }
+
+ NSLog(@"URL loading error): %@", error);
+ page.imageryState = Failed;
+ return;
} else if ( [[response MIMEType] isEqualToString:@"text/html"] ) {
NSString * content = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@"Request Returned: %@", content);
- } else {
- // without converting the image, I get a "data preprocessing error". I have no idea why
- UIImage * image = [UIImage imageWithData:data];
- image = [UIImage imageWithData:UIImageJPEGRepresentation(image, 1.0)];
- if ( image == nil ) return;
+ page.imageryState = Failed;
+ return;
+ }
+
+ [graphicsQueue addOperationWithBlock:^{
+ [EAGLContext setCurrentContext: self.auxilliaryContext];
- @synchronized(self.auxilliaryContext) {
- [EAGLContext setCurrentContext: self.auxilliaryContext];
-
- // create texture
- GLKTextureInfo * textureInfo = nil;
- NSError * error = nil;
- NSDictionary * options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:GLKTextureLoaderOriginBottomLeft];
- textureInfo = [GLKTextureLoader textureWithCGImage:image.CGImage options:options error:&error];
- if ( error ) {
- NSLog(@"Error loading texture: %@", error);
- } else {
- // generate texture wrapper
- RATextureWrapper * texture = [[RATextureWrapper alloc] initWithTextureInfo:textureInfo];
- page.imagery = texture;
- page.needsUpdate = YES;
- }
-
- glFlush();
- [EAGLContext setCurrentContext: nil];
+ UIImage * image = [UIImage imageWithData:data];
+ if ( image == nil ) {
+ NSLog(@"Bad image for URL: %@", url);
+ page.imageryState = Failed;
+ return;
}
- }
+
+ // create texture
+ RATextureWrapper * texture = [[RATextureWrapper alloc] initWithImage:image];
+ page.imagery = texture;
+ page.imageryState = Complete;
+
+ // mark the geometry to get refreshed
+ page.geometryState = NeedsUpdate;
+ mySelf->_needsDisplay = YES;
+
+ glFlush();
+ [EAGLContext setCurrentContext: nil];
+ }];
}];
- page.imageryLoadOp = op;
- [_loadQueue addOperation:op];
}
}
// request the terrain if needed
- if ( page.terrain == nil && page.terrainLoadOp == nil && self.terrainDatabase ) {
+ if ( page.terrainState == NotLoaded ) {
NSURL * url = [self.terrainDatabase urlForTile: page.tile];
- if ( url && _loadQueue.operationCount < 16 ) {
- NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
- NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15.0];
+ if ( url == nil ) {
+ page.terrainState = Failed;
+ } else if ( _connectionQueue.operationCount < kMaxQueueDepth ) {
+ page.terrainState = Loading;
+
+ // capture ivar locally to avoid retain cycle
+ NSOperationQueue * updateQueue = _updateQueue;
+
+ [_connectionQueue addOperationWithBlock:^{
+ NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:kTimeoutInterval];
+
NSURLResponse * response = nil;
NSError * error = nil;
+
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if ( error ) {
+ // attempt to reload if the connection timed out
+ if ( [[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorTimedOut ) {
+ page.terrainState = NotLoaded;
+ return;
+ }
+
NSLog(@"URL Error loading (%@): %@", url, error);
+ page.terrainState = Failed;
+ return;
} else if ( [[response MIMEType] isEqualToString:@"text/html"] ) {
NSString * content = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@"Request Returned: %@", content);
- } else {
- // without converting the image, I get a "data preprocessing error". I have no idea why
+ page.terrainState = Failed;
+ return;
+ }
+
+ [updateQueue addOperationWithBlock:^{
UIImage * image = [UIImage imageWithData:data];
-
+ if ( image == nil ) {
+ NSLog(@"Bad terrain for URL: %@", url);
+ page.terrainState = Failed;
+ return;
+ }
+
page.terrain = image;
- page.needsUpdate = YES;
- }
+ page.terrainState = Complete;
+
+ // mark the geometry to get refreshed
+ page.geometryState = NeedsUpdate;
+ mySelf->_needsDisplay = YES;
+ }];
}];
- page.terrainLoadOp = op;
- [_loadQueue addOperation:op];
}
}
}
-
#pragma mark Page Traversal Methods
- (RAPage *)makeLeafPageForTile:(TileID)t withParent:(RAPage *)parent {
@@ -389,96 +439,67 @@ - (void)preparePageForTraversal:(RAPage *)page {
if ( page.child4 == nil ) page.child4 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+1, 2*page.tile.y+1, page.tile.z+1 } withParent:page];
}
-- (void)traversePage:(RAPage *)page {
+- (void)traversePage:(RAPage *)page withTimestamp:(NSTimeInterval)timestamp {
NSAssert( page != nil, @"the traversed page must be valid");
- [self requestPage: page];
+
[self updatePageIfNeeded: page];
+ [self requestPage: page];
- // is the page facing away from the camera?
- //if ( [page calculateTiltWithCamera:self.camera] < -0.5f ) goto pruneChildren;
+ float texelError = [page calculateScreenSpaceErrorWithCamera:self.camera];
- float texelError = 0.0f;
- if ( page.tile.z <= self.imageryDatabase.maxzoom ) // force display at maximum level
- texelError = [page calculateScreenSpaceErrorWithCamera:self.camera];
+ if ( page.tile.z >= self.imageryDatabase.maxzoom )
+ texelError = 0.0f; // force display at maximum zoom level
// should we choose to display this page?
- if ( texelError < 3.f ) {
- // don't bother traversing if we are offscreen
- if ( ! [page isOnscreenWithCamera:self.camera] ) goto pruneChildren;
-
- //[self requestPage: page];
+ if ( texelError < 3.0f ) {
//[self updatePageIfNeeded: page];
+ //[self requestPage: page];
+
+ // update the page's timestamp
+ page.lastRequestedTimestamp = timestamp;
+
goto pruneChildren;
+ } else {
+ // traverse children
+ [self preparePageForTraversal:page];
+
+ [self traversePage:page.child1 withTimestamp:timestamp];
+ [self traversePage:page.child2 withTimestamp:timestamp];
+ [self traversePage:page.child3 withTimestamp:timestamp];
+ [self traversePage:page.child4 withTimestamp:timestamp];
+ return;
}
-
- // traverse children
- [self preparePageForTraversal:page];
-
- [self traversePage: page.child1];
- [self traversePage: page.child2];
- [self traversePage: page.child3];
- [self traversePage: page.child4];
- return;
pruneChildren:
// prune children
- page.child1 = page.child2 = page.child3 = page.child4 = nil; // !!! replace this with another method
+ // !!! replace this with another method that doesn't remove children immediately
+ page.child1 = page.child2 = page.child3 = page.child4 = nil;
return;
}
- (void)update {
+ //NSLog(@"Ops: %d updates, %d url loading, %d graphics updates", _updateQueue.operationCount, _connectionQueue.operationCount, _graphicsQueue.operationCount);
+
+ // only traverse when all previous update operations have completed
+ if ( _updateQueue.operationCount > 0 ) return;
+
// load default texture
if ( _defaultTexture == nil ) {
- UIImage * image = [UIImage imageNamed:@"grid256"];
-
- NSError * err = nil;
- NSDictionary * options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:GLKTextureLoaderOriginBottomLeft];
- GLKTextureInfo * textureInfo = [GLKTextureLoader textureWithCGImage:[image CGImage] options:options error:&err];
- if ( err ) NSLog(@"Error loading default texture: %@", err);
-
- _defaultTexture = [[RATextureWrapper alloc] initWithTextureInfo:textureInfo];
+ UIImage * gridImage = [UIImage imageNamed:@"grid256"];
+ _defaultTexture = [[RATextureWrapper alloc] initWithImage:gridImage];
}
-
- if ( _traverseOp == nil ) {
- _traverseOp = [NSBlockOperation blockOperationWithBlock:^{
- // traverse pages gathering ones that are active and should be displayed
- [rootPages enumerateObjectsUsingBlock:^(RAPage *page, BOOL *stop) {
- [self traversePage:page];
- }];
- }];
- [_traverseQueue addOperation:_traverseOp];
- }
-
- //NSLog(@"Ops: %d traverse, %d url loading, %d graphics updates", _traverseQueue.operationCount, _loadQueue.operationCount, _graphicsQueue.operationCount);
- //NSLog(@"Total count: Page %d, Geom %d", [RAPage count], [RAGeometry count]);
-
- //[self printPageTree];
-}
-static NSUInteger sTraverseCount = 0;
-
-- (void)printPage:(RAPage *)page withIndent:(int)indent {
- sTraverseCount++;
-
- NSMutableString * indentString = [NSMutableString string];
- for( int i = 0; i < indent; ++i )
- [indentString appendString:@" "];
+ NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
- //NSLog(@"%@Tile: %@, Geom: %@", indentString, page.key, page.geometry);
+ // capture self to avoid a retain cycle
+ __block RATilePager *mySelf = self;
- if ( page.child1 ) [self printPage:page.child1 withIndent:indent+1];
- if ( page.child2 ) [self printPage:page.child2 withIndent:indent+1];
- if ( page.child3 ) [self printPage:page.child3 withIndent:indent+1];
- if ( page.child4 ) [self printPage:page.child4 withIndent:indent+1];
-}
-
-- (void)printPageTree {
- sTraverseCount = 0;
- //NSLog(@"Page Tree:");
- [rootPages enumerateObjectsUsingBlock:^(RAPage *page, BOOL *stop) {
- [self printPage:page withIndent:1];
+ [_updateQueue addOperationWithBlock:^{
+ // traverse pages gathering ones that are active and should be displayed
+ [mySelf->rootPages enumerateObjectsUsingBlock:^(RAPage *page, BOOL *stop) {
+ [mySelf traversePage:page withTimestamp:currentTime];
+ }];
}];
- NSLog(@"Traversed: %d, Total: %d", sTraverseCount, [RAPage count]);
}
@end
View
21 Source/RAWorldTour.m
@@ -9,6 +9,7 @@
#import "RAWorldTour.h"
#import "TPPropertyAnimation.h"
+static const double kAnimationDuration = 5;
@implementation RAWorldTour {
NSTimer * timer;
@@ -18,6 +19,21 @@ @implementation RAWorldTour {
- (void)start:(id)sender {
timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(next:) userInfo:nil repeats:YES];
+
+ TPPropertyAnimation *anim1 = [TPPropertyAnimation propertyAnimationWithKeyPath:@"distance"];
+ anim1.duration = kAnimationDuration;
+ anim1.fromValue = [NSNumber numberWithDouble:manipulator.distance];
+ anim1.toValue = [NSNumber numberWithDouble:5e5];
+ anim1.timing = TPPropertyAnimationTimingEaseInEaseOut;
+ [anim1 beginWithTarget:self.manipulator];
+
+ TPPropertyAnimation *anim2 = [TPPropertyAnimation propertyAnimationWithKeyPath:@"elevation"];
+ anim2.duration = kAnimationDuration;
+ anim2.fromValue = [NSNumber numberWithDouble:manipulator.elevation];
+ anim2.toValue = [NSNumber numberWithDouble:80];
+ anim2.timing = TPPropertyAnimationTimingEaseInEaseOut;
+ [anim2 beginWithTarget:self.manipulator];
+
[self next:sender];
}
@@ -34,19 +50,18 @@ - (void)startOrStop:(id)sender {
}
- (void)next:(id)sender {
- double duration = 5;
double lat = -90 + ((double)rand() / RAND_MAX * 180.);
double lon = -180 + ((double)rand() / RAND_MAX * 360.);
TPPropertyAnimation *anim1 = [TPPropertyAnimation propertyAnimationWithKeyPath:@"latitude"];
- anim1.duration = duration;
+ anim1.duration = kAnimationDuration;
anim1.fromValue = [NSNumber numberWithDouble:manipulator.latitude];
anim1.toValue = [NSNumber numberWithDouble:lat];
anim1.timing = TPPropertyAnimationTimingEaseInEaseOut;
[anim1 beginWithTarget:self.manipulator];
TPPropertyAnimation *anim2 = [TPPropertyAnimation propertyAnimationWithKeyPath:@"longitude"];
- anim2.duration = duration;
+ anim2.duration = kAnimationDuration;
anim2.fromValue = [NSNumber numberWithDouble:manipulator.longitude];
anim2.toValue = [NSNumber numberWithDouble:lon];
anim2.timing = TPPropertyAnimationTimingEaseInEaseOut;
View
9 en.lproj/SceneView_iPhone.xib
@@ -2,10 +2,10 @@
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1280</int>
- <string key="IBDocument.SystemVersion">11D50</string>
+ <string key="IBDocument.SystemVersion">11E53</string>
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
- <string key="IBDocument.AppKitVersion">1138.32</string>
- <string key="IBDocument.HIToolboxVersion">568.00</string>
+ <string key="IBDocument.AppKitVersion">1138.47</string>
+ <string key="IBDocument.HIToolboxVersion">569.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">1181</string>
@@ -36,7 +36,6 @@
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
- <reference key="NSNextKeyView"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
@@ -103,7 +102,7 @@
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
<object class="IBPartialClassDescription">
<string key="className">RASceneGraphController</string>
- <string key="superclassName">GLKViewController</string>
+ <string key="superclassName">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/RASceneGraphController.h</string>
View
BIN screenshot1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<