Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Major update to add 3D terrain and shader rendering

  • Loading branch information...
commit 69115fc017216ce01a180da458d644d2737620db 1 parent 971464a
@RossAnderson authored
View
1  .gitignore
@@ -1,2 +1 @@
-xcuserdata
.DS_Store
View
74 DRAppDelegate.m
@@ -10,7 +10,8 @@
#import "RASceneGraphController.h"
-#define SAMPLE_DATASET 4
+#define IMAGERY_DATASET 2
+#define TERRAIN_DATASET 1
@implementation DRAppDelegate
@@ -29,19 +30,23 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
}
// setup the tile set used
- RATileDatabase * database = self.viewController.database;
- switch( SAMPLE_DATASET ) {
+ RATileDatabase * database = [RATileDatabase new];
+ database.bounds = CGRectMake( -180,-90,360,180 );
+ database.googleTileConvention = YES;
+ database.minzoom = 2;
+
+ switch( IMAGERY_DATASET ) {
case 1:
- // Sample database from: http://a.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jul-bw.jsonp
+ // MapBox Streets: http://a.tiles.mapbox.com/v3/mapbox.mapbox-streets.jsonp
database.baseUrlStrings = [NSArray arrayWithObjects:
- @"http://a.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jul-bw/{z}/{x}/{y}.png",
- @"http://b.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jul-bw/{z}/{x}/{y}.png",
- @"http://c.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jul-bw/{z}/{x}/{y}.png",
- @"http://d.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jul-bw/{z}/{x}/{y}.png",
- nil];
-
- database.maxzoom = 8;
+ @"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",
+ @"http://c.tiles.mapbox.com/v3/mapbox.mapbox-streets/{z}/{x}/{y}.png",
+ @"http://d.tiles.mapbox.com/v3/mapbox.mapbox-streets/{z}/{x}/{y}.png",
+ nil];
+ database.maxzoom = 17;
break;
+
case 2:
// Sample database from: http://a.tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jul.jsonp
database.baseUrlStrings = [NSArray arrayWithObjects:
@@ -52,6 +57,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
nil];
database.maxzoom = 8;
break;
+
case 3:
// OpenStreetMap
database.baseUrlStrings = [NSArray arrayWithObjects:
@@ -61,24 +67,46 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
nil];
database.maxzoom = 18;
break;
+
case 4:
- // MapBox Streets: http://a.tiles.mapbox.com/v3/mapbox.mapbox-streets.jsonp
+ // Stamen Maps Watercolor - http://maps.stamen.com/watercolor
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",
- @"http://c.tiles.mapbox.com/v3/mapbox.mapbox-streets/{z}/{x}/{y}.png",
- @"http://d.tiles.mapbox.com/v3/mapbox.mapbox-streets/{z}/{x}/{y}.png",
- nil];
+ @"http://a.tile.stamen.com/watercolor/{z}/{x}/{y}.png",
+ @"http://b.tile.stamen.com/watercolor/{z}/{x}/{y}.png",
+ @"http://c.tile.stamen.com/watercolor/{z}/{x}/{y}.png",
+ nil];
database.maxzoom = 17;
break;
- /*
- case 5:
+
+ default:
+ database = nil;
+ break;
+ }
+ self.viewController.pager.imageryDatabase = database;
+
+ // setup height tile dataset
+ database = [RATileDatabase new];
+ database.bounds = CGRectMake( -180,-90,360,180 );
+ database.googleTileConvention = YES;
+ database.minzoom = 2;
+
+ switch ( TERRAIN_DATASET ) {
+ case 1:
+ // World Topography: https://tiles.mapbox.com/v3/dancingrobots.world-topo
+ 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",
+ nil];
+ database.maxzoom = 8;
+ break;
+
default:
- // Stamen Maps Watercolor - http://maps.stamen.com/watercolor
- database.baseUrlString = @"http://a.tile.stamen.com/watercolor/{z}/{x}/{y}.png";
- database.maxzoom = 17;
- */
+ database = nil;
+ break;
}
+ self.viewController.pager.terrainDatabase = database;
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
View
26 EarthViewExample.xcodeproj/project.pbxproj
@@ -9,11 +9,13 @@
/* Begin PBXBuildFile section */
9109E03C153D86100008286D /* star1.png in Resources */ = {isa = PBXBuildFile; fileRef = 9109E03B153D86100008286D /* star1.png */; };
9109E03E153D864F0008286D /* RASceneGraphController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9109E03D153D864F0008286D /* RASceneGraphController.m */; };
- 91BBDFB2153A49A900CEF4BA /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 91BBDFB1153A49A900CEF4BA /* README.md */; };
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 */; };
91C997621548E22E00B69A8A /* Icon72.png in Resources */ = {isa = PBXBuildFile; fileRef = 91C997611548E22E00B69A8A /* Icon72.png */; };
+ 91C9979D1549DBD400B69A8A /* Shader.fsh in Resources */ = {isa = PBXBuildFile; fileRef = 91C9979B1549DBD400B69A8A /* Shader.fsh */; };
+ 91C9979E1549DBD400B69A8A /* Shader.vsh in Resources */ = {isa = PBXBuildFile; fileRef = 91C9979C1549DBD400B69A8A /* Shader.vsh */; };
+ 91F22019154C7B4700A5F74E /* RAShaderProgram.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F22018154C7B4700A5F74E /* RAShaderProgram.m */; };
91F77E271539335000F8AE05 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F77E261539335000F8AE05 /* UIKit.framework */; };
91F77E291539335000F8AE05 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F77E281539335000F8AE05 /* Foundation.framework */; };
91F77E2B1539335000F8AE05 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F77E2A1539335000F8AE05 /* CoreGraphics.framework */; };
@@ -42,7 +44,6 @@
91F77EA51539C32D00F8AE05 /* TPPropertyAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F77E731539341B00F8AE05 /* TPPropertyAnimation.m */; };
91F77EA7153A089A00F8AE05 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F77EA6153A089A00F8AE05 /* QuartzCore.framework */; };
91F77EA8153A08C300F8AE05 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F77E8D1539349900F8AE05 /* main.m */; };
- 91F77EAC153A0F9B00F8AE05 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 91F77EAB153A0F9A00F8AE05 /* LICENSE.txt */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -53,6 +54,10 @@
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>"; };
91C997611548E22E00B69A8A /* Icon72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon72.png; sourceTree = "<group>"; };
+ 91C9979B1549DBD400B69A8A /* Shader.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = Shader.fsh; sourceTree = "<group>"; };
+ 91C9979C1549DBD400B69A8A /* Shader.vsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = Shader.vsh; sourceTree = "<group>"; };
+ 91F22017154C7B4600A5F74E /* RAShaderProgram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RAShaderProgram.h; sourceTree = "<group>"; };
+ 91F22018154C7B4700A5F74E /* RAShaderProgram.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RAShaderProgram.m; sourceTree = "<group>"; };
91F77E221539335000F8AE05 /* EarthViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EarthViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
91F77E261539335000F8AE05 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
91F77E281539335000F8AE05 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@@ -121,6 +126,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 91C9979A1549DBD300B69A8A /* Shaders */ = {
+ isa = PBXGroup;
+ children = (
+ 91C9979B1549DBD400B69A8A /* Shader.fsh */,
+ 91C9979C1549DBD400B69A8A /* Shader.vsh */,
+ );
+ path = Shaders;
+ sourceTree = SOURCE_ROOT;
+ };
91F77E171539335000F8AE05 = {
isa = PBXGroup;
children = (
@@ -162,6 +176,7 @@
91F77E461539335100F8AE05 /* SceneView_iPad.xib */,
91F77E531539341B00F8AE05 /* Source */,
91F77E841539342A00F8AE05 /* Images */,
+ 91C9979A1549DBD300B69A8A /* Shaders */,
91F77E311539335000F8AE05 /* Supporting Files */,
);
path = EarthViewExample;
@@ -211,6 +226,8 @@
91F77E6F1539341B00F8AE05 /* RATilePager.m */,
91F77E701539341B00F8AE05 /* RATransform.h */,
91F77E711539341B00F8AE05 /* RATransform.m */,
+ 91F22017154C7B4600A5F74E /* RAShaderProgram.h */,
+ 91F22018154C7B4700A5F74E /* RAShaderProgram.m */,
91F77E721539341B00F8AE05 /* TPPropertyAnimation.h */,
91F77E731539341B00F8AE05 /* TPPropertyAnimation.m */,
);
@@ -288,11 +305,11 @@
91F77E481539335100F8AE05 /* SceneView_iPad.xib in Resources */,
91F77E891539342A00F8AE05 /* clear256.png in Resources */,
91F77E8B1539342A00F8AE05 /* grid256.png in Resources */,
- 91F77EAC153A0F9B00F8AE05 /* LICENSE.txt in Resources */,
- 91BBDFB2153A49A900CEF4BA /* README.md in Resources */,
9109E03C153D86100008286D /* star1.png in Resources */,
91C9975C1548E1BF00B69A8A /* Icon114.png in Resources */,
91C9975E1548E1F000B69A8A /* Icon57.png in Resources */,
+ 91C9979D1549DBD400B69A8A /* Shader.fsh in Resources */,
+ 91C9979E1549DBD400B69A8A /* Shader.vsh in Resources */,
91C997601548E21400B69A8A /* Icon144.png in Resources */,
91C997621548E22E00B69A8A /* Icon72.png in Resources */,
);
@@ -323,6 +340,7 @@
91F77E971539C31E00F8AE05 /* RACamera.m in Sources */,
91F77E981539C31E00F8AE05 /* RAGeographicUtils.c in Sources */,
9109E03E153D864F0008286D /* RASceneGraphController.m in Sources */,
+ 91F22019154C7B4700A5F74E /* RAShaderProgram.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
BIN  ....xcodeproj/project.xcworkspace/xcuserdata/ross.xcuserdatad/UserInterfaceState.xcuserstate
Binary file not shown
View
10 ...le.xcodeproj/project.xcworkspace/xcuserdata/ross.xcuserdatad/WorkspaceSettings.xcsettings
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
+ <true/>
+ <key>SnapshotAutomaticallyBeforeSignificantChanges</key>
+ <false/>
+</dict>
+</plist>
View
9 EarthViewExample.xcodeproj/xcuserdata/ross.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
@@ -2,4 +2,13 @@
<Bucket
type = "1"
version = "1.0">
+ <ExceptionBreakpoints>
+ <ExceptionBreakpoint
+ shouldBeEnabled = "Yes"
+ ignoreCount = "0"
+ continueAfterRunningActions = "No"
+ scope = "0"
+ stopOnStyle = "0">
+ </ExceptionBreakpoint>
+ </ExceptionBreakpoints>
</Bucket>
View
1  README.md
@@ -38,7 +38,6 @@ The example application connects to various map tile services over the web such
Bugs and Limitations
--------------------
-- The light position lags by 1 frame for some reason.
- 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.
- There are holes in the globe at the poles because there is no map tile content there.
View
17 Shaders/Shader.fsh
@@ -0,0 +1,17 @@
+//
+// Shader.fsh
+// EarthViewExample
+//
+// Created by Ross Anderson on 4/26/12.
+// Copyright (c) 2012 Ross Anderson. All rights reserved.
+//
+
+uniform sampler2D texture0;
+
+varying mediump vec2 fragmentTextureCoordinates;
+varying lowp vec4 fragmentColor;
+
+void main()
+{
+ gl_FragColor = texture2D(texture0, fragmentTextureCoordinates) * fragmentColor;
+}
View
35 Shaders/Shader.vsh
@@ -0,0 +1,35 @@
+//
+// Shader.vsh
+// EarthViewExample
+//
+// Created by Ross Anderson on 4/26/12.
+// Copyright (c) 2012 Ross Anderson. All rights reserved.
+//
+
+attribute vec4 position;
+attribute vec2 textureCoordinate;
+attribute vec3 normal;
+
+uniform mat4 modelViewProjectionMatrix;
+uniform mat3 normalMatrix;
+
+uniform vec3 lightDirection;
+uniform vec4 lightAmbientColor;
+uniform vec4 lightDiffuseColor;
+
+varying mediump vec2 fragmentTextureCoordinates;
+varying lowp vec4 fragmentColor;
+
+const float c_zero = 0.0;
+const float c_one = 1.0;
+
+void main()
+{
+ vec4 color = lightAmbientColor;
+ float ndotl = max( c_zero, dot( normal, lightDirection ) );
+ color += ndotl * ndotl * lightDiffuseColor;
+
+ gl_Position = modelViewProjectionMatrix * position;
+ fragmentTextureCoordinates = textureCoordinate;
+ fragmentColor = color;
+}
View
1  Source/RAGeometry.h
@@ -23,7 +23,6 @@
@property (strong, nonatomic) RATextureWrapper * texture0;
@property (strong, nonatomic) RATextureWrapper * texture1;
@property (assign, nonatomic) GLKVector4 color; // set 1st component to -1 to disable
-@property (retain, nonatomic) GLKEffectPropertyMaterial * material;
@property (assign, nonatomic) GLenum elementStyle; // default: GL_TRIANGLES
- (void)setObjectData:(const void *)data withSize:(NSUInteger)length withStride:(NSUInteger)stride;
View
17 Source/RAGeometry.m
@@ -43,7 +43,6 @@ @implementation RAGeometry {
@synthesize textureOffset = _textureOffset;
@synthesize texture0 = _texture0, texture1 = _texture1;
@synthesize color = _color;
-@synthesize material = _material;
@synthesize elementStyle = _elementStyle;
@@ -241,8 +240,22 @@ - (void)renderGL
[self setupGL];
@synchronized(self) {
+ glActiveTexture (GL_TEXTURE0);
+ if ( _texture0 ) {
+ glBindTexture(GL_TEXTURE_2D, _texture0.name);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ glActiveTexture (GL_TEXTURE1);
+ if ( _texture1 ) {
+ glBindTexture(GL_TEXTURE_2D, _texture1.name);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
glBindVertexArrayOES(_vertexArray);
-
+
if ( _indexStride > 0 && [_indexData length] > 0 ) {
GLenum type = -1;
switch( _indexStride ) {
View
17 Source/RAManipulator.m
@@ -219,8 +219,6 @@ - (void)scale:(id)sender {
static CameraState startState;
static CGFloat startScale = 1;
- [self stop:nil];
-
switch( [pinch state] ) {
case UIGestureRecognizerStatePossible:
break;
@@ -229,6 +227,8 @@ - (void)scale:(id)sender {
_state = startState;
break;
case UIGestureRecognizerStateBegan:
+ [self stop:nil];
+
startState = _state;
startScale = pinch.scale;
break;
@@ -270,8 +270,6 @@ - (void)move:(id)sender {
CGPoint pt = [pan locationInView:self.view];
- [self stop:nil];
-
switch( [pan state] ) {
case UIGestureRecognizerStatePossible:
break;
@@ -281,6 +279,8 @@ - (void)move:(id)sender {
break;
case UIGestureRecognizerStateBegan:
{
+ [self stop:nil];
+
CGFloat yThresh = self.view.bounds.size.height / 10.;
CGFloat xThresh = self.view.bounds.size.width / 10.;
@@ -384,6 +384,9 @@ - (void)move:(id)sender {
{
// continue movement in the same direction
CGPoint dir = CGPointMake( _state.longitude - startState.longitude, _state.latitude - startState.latitude );
+ if ( dir.x > 180. ) dir.x -= 360.;
+ if ( dir.x < -180. ) dir.x += 360.;
+
CGFloat length = sqrt( dir.x*dir.x + dir.y*dir.y );
if ( length < 1 ) break;
dir.x /= length;
@@ -397,7 +400,7 @@ - (void)move:(id)sender {
CGPoint destination = CGPointMake( _state.longitude + dir.x*angle, _state.latitude + dir.y*angle );
/*if ( destination.y > kMaximumLatitude ) destination.y = kMaximumLatitude;
if ( destination.y < -kMaximumLatitude ) destination.y = -kMaximumLatitude;*/
-
+
// zoom to that location
TPPropertyAnimation *anim = [TPPropertyAnimation propertyAnimationWithKeyPath:@"latitude"];
anim.duration = kAnimationDuration;
@@ -446,6 +449,8 @@ - (void)move:(id)sender {
- (void)stop:(id)sender {
// cancel animations in progress
[[TPPropertyAnimation allPropertyAnimationsForTarget:self] makeObjectsPerformSelector:@selector(cancel)];
+
+ //printf("Stop\n");
}
- (void)zoomToLocation:(id)sender {
@@ -457,7 +462,7 @@ - (void)zoomToLocation:(id)sender {
// get the current touch position on the globe
[self intersectPoint:pt atLatitude:&lat atLongitude:&lon withState:_state];
- printf("Zoom to: %f, %f\n", lat, lon);
+ //printf("Zoom to: %f, %f\n", lat, lon);
double duration = 1.0;
View
13 Source/RAPage.h
@@ -19,7 +19,6 @@
@property (readonly, nonatomic) TileID tile;
@property (readonly, nonatomic) NSString * key;
@property (readonly, nonatomic) RABoundingSphere * bound;
-//@property (assign, nonatomic) NSTimeInterval lastRequestTime;
@property (readonly, nonatomic) RAPage * parent;
@property (strong, nonatomic) RAPage * child1;
@@ -27,13 +26,17 @@
@property (strong, nonatomic) RAPage * child3;
@property (strong, nonatomic) RAPage * child4;
-@property (strong, nonatomic) RATextureWrapper * texture;
@property (strong, nonatomic) RAGeometry * geometry;
-- (RAPage *)initWithTileID:(TileID)t andParent:(RAPage *)parent;
+@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;
-- (BOOL)isReady;
-- (BOOL)isLeaf;
+- (RAPage *)initWithTileID:(TileID)t andParent:(RAPage *)parent;
- (void)setCenter:(GLKVector3)center andRadius:(double)radius;
View
16 Source/RAPage.m
@@ -12,8 +12,6 @@ @implementation RAPage {
RABoundingSphere * _bound;
RAPage * _parent;
- RAGeometry * _geometry;
-
NSURLConnection * _connection;
NSMutableData * _imageData;
}
@@ -21,9 +19,8 @@ @implementation RAPage {
@synthesize tile, key;
@synthesize bound = _bound;
@synthesize parent = _parent, child1, child2, child3, child4;
-@synthesize texture = _texture;
-@synthesize geometry = _geometry;
-//@synthesize lastRequestTime;
+@synthesize geometry, imagery, terrain;
+@synthesize needsUpdate, imageryLoadOp, terrainLoadOp, updatePageOp;
- (RAPage *)initWithTileID:(TileID)t andParent:(RAPage *)parent;
{
@@ -31,6 +28,7 @@ - (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;
}
return self;
@@ -46,12 +44,4 @@ - (void)setCenter:(GLKVector3)center andRadius:(double)radius {
_bound.radius = radius;
}
-- (BOOL)isReady {
- return _geometry != nil;
-}
-
-- (BOOL)isLeaf {
- return ( child1 == nil && child2 == nil && child3 == nil && child4 == nil );
-}
-
@end
View
9 Source/RARenderVisitor.h
@@ -15,9 +15,16 @@
@interface RARenderVisitor : RANodeVisitor
@property (strong) RACamera * camera;
+@property (assign) GLKVector3 lightPosition;
+@property (assign) GLKVector4 lightAmbientColor;
+@property (assign) GLKVector4 lightDiffuseColor;
- (void)clear;
- (void)sortBackToFront;
-- (void)renderWithEffect:(GLKBaseEffect *)effect;
+
+- (void)setupGL;
+- (void)tearDownGL;
+
+- (void)render;
@end
View
106 Source/RARenderVisitor.m
@@ -13,6 +13,27 @@
#import <GLKit/GLKMathUtils.h>
#import "RABoundingSphere.h"
+#import "RAShaderProgram.h"
+
+// Uniform index.
+enum
+{
+ UNIFORM_MODELVIEWPROJECTION_MATRIX,
+// UNIFORM_NORMAL_MATRIX,
+ UNIFORM_TEXTURE0,
+ UNIFORM_LIGHT_DIRECTION,
+ UNIFORM_LIGHT_AMBIENT_COLOR,
+ UNIFORM_LIGHT_DIFFUSE_COLOR,
+ NUM_UNIFORMS
+};
+
+// Attribute index.
+/*enum
+{
+ ATTRIB_VERTEX,
+ ATTRIB_NORMAL,
+ NUM_ATTRIBUTES
+};*/
#pragma mark -
@@ -32,20 +53,27 @@ @implementation RenderData
#pragma mark -
@implementation RARenderVisitor {
- NSMutableArray * renderQueue;
+ NSMutableArray * renderQueue;
+ RAShaderProgram * shader;
}
@synthesize camera;
+@synthesize lightPosition = _lightPosition, lightAmbientColor = _lightAmbientColor, lightDiffuseColor = _lightDiffuseColor;
- (id)init
{
self = [super init];
if (self) {
renderQueue = [NSMutableArray new];
+ shader = [[RAShaderProgram alloc] init];
self.camera = [RACamera new];
self.camera.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), 1, 1, 100);
self.camera.modelViewMatrix = GLKMatrix4Identity;
+
+ self.lightPosition = GLKVector3Make(1.0, 1.0, 1.0);
+ self.lightAmbientColor = GLKVector4Make(0.1, 0.1, 0.1, 1.0);
+ self.lightDiffuseColor = GLKVector4Make(0.9, 0.9, 0.9, 1.0);
}
return self;
}
@@ -62,52 +90,49 @@ - (void)sortBackToFront
}];
}
-- (void)renderWithEffect:(GLKBaseEffect *)effect
+- (void)setupGL
+{
+ if ( [shader loadShader:@"Shader"] ) {
+ [shader bindAttribute:@"position" toIdentifier:GLKVertexAttribPosition];
+ [shader bindAttribute:@"normal" toIdentifier:GLKVertexAttribNormal];
+ [shader bindAttribute:@"textureCoordinate" toIdentifier:GLKVertexAttribTexCoord0];
+
+ [shader link];
+
+ [shader bindUniform:@"modelViewProjectionMatrix" toIdentifier:UNIFORM_MODELVIEWPROJECTION_MATRIX];
+ [shader bindUniform:@"lightDirection" toIdentifier:UNIFORM_LIGHT_DIRECTION];
+ [shader bindUniform:@"lightAmbientColor" toIdentifier:UNIFORM_LIGHT_AMBIENT_COLOR];
+ [shader bindUniform:@"lightDiffuseColor" toIdentifier:UNIFORM_LIGHT_DIFFUSE_COLOR];
+ [shader bindUniform:@"texture0" toIdentifier:UNIFORM_TEXTURE0];
+ }
+}
+
+- (void)tearDownGL
+{
+ [shader tearDownGL];
+}
+
+- (void)render
{
[self sortBackToFront];
- effect.transform.projectionMatrix = self.camera.projectionMatrix;
+ if ( ! [shader isReady] ) [self setupGL];
+ [shader use];
+ // set light source
+ [shader setUniform:UNIFORM_LIGHT_DIRECTION toVector3:GLKVector3Normalize(self.lightPosition)];
+ [shader setUniform:UNIFORM_LIGHT_AMBIENT_COLOR toVector4:self.lightAmbientColor];
+ [shader setUniform:UNIFORM_LIGHT_DIFFUSE_COLOR toVector4:self.lightDiffuseColor];
+
+ [shader setUniform:UNIFORM_TEXTURE0 toInt:0];
+
[renderQueue enumerateObjectsUsingBlock:^(RenderData * child, NSUInteger idx, BOOL *stop) {
- effect.transform.modelviewMatrix = child.modelviewMatrix;
- effect.colorMaterialEnabled = child.geometry.colorOffset > -1;
+ //GLKMatrix3 normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(child.modelviewMatrix), NULL);
+ GLKMatrix4 modelViewProjectionMatrix = GLKMatrix4Multiply(self.camera.projectionMatrix, child.modelviewMatrix);
- if ( child.geometry.material ) {
- effect.material.ambientColor = child.geometry.material.ambientColor;
- effect.material.diffuseColor = child.geometry.material.diffuseColor;
- effect.material.specularColor = child.geometry.material.specularColor;
- effect.material.emissiveColor = child.geometry.material.emissiveColor;
- effect.material.shininess = child.geometry.material.shininess;
- } else {
- effect.material.ambientColor = (GLKVector4){ 0.2, 0.2, 0.2, 1.0};
- effect.material.diffuseColor = (GLKVector4){ 0.8, 0.8, 0.8, 1.0};
- effect.material.specularColor = (GLKVector4){ 0.0, 0.0, 0.0, 1.0};
- effect.material.emissiveColor = (GLKVector4){ 0.0, 0.0, 0.0, 1.0};
- effect.material.shininess = 0.0;
- }
+ [shader setUniform:UNIFORM_MODELVIEWPROJECTION_MATRIX toMatrix4:modelViewProjectionMatrix];
+ //[shader setUniform:UNIFORM_NORMAL_MATRIX toMatrix4:normalMatrix];
- effect.useConstantColor = child.geometry.color.x > -1;
- effect.constantColor = child.geometry.color;
-
- if (child.geometry.texture0 != nil) {
- effect.texture2d0.envMode = GLKTextureEnvModeModulate;
- effect.texture2d0.target = GLKTextureTarget2D;
- effect.texture2d0.name = child.geometry.texture0.name;
- effect.texture2d0.enabled = YES;
- } else {
- effect.texture2d0.enabled = NO;
- }
-
- if (child.geometry.texture1 != nil) {
- effect.texture2d1.envMode = GLKTextureEnvModeModulate;
- effect.texture2d1.target = GLKTextureTarget2D;
- effect.texture2d1.name = child.geometry.texture1.name;
- effect.texture2d1.enabled = YES;
- } else {
- effect.texture2d1.enabled = NO;
- }
-
- [effect prepareToDraw];
[child.geometry renderGL];
}];
}
@@ -145,5 +170,4 @@ - (void)applyGeometry:(RAGeometry *)node
[renderQueue addObject: data];
}
-
@end
View
4 Source/RASceneGraphController.h
@@ -10,12 +10,12 @@
#import "RANode.h"
#import "RACamera.h"
-#import "RATileDatabase.h"
+#import "RATilePager.h"
@interface RASceneGraphController : GLKViewController
@property (readonly, nonatomic) RANode * sceneRoot;
@property (readonly, nonatomic) RACamera * camera;
-@property (readonly, nonatomic) RATileDatabase * database;
+@property (readonly, nonatomic) RATilePager * pager;
@end
View
49 Source/RASceneGraphController.m
@@ -51,11 +51,9 @@ - (void)applyGeometry:(RAGeometry *)node
@interface RASceneGraphController () {
RARenderVisitor * renderVisitor;
RAManipulator * manipulator;
- RATileDatabase * database;
- RATilePager * pager;
EAGLContext * context;
- GLKBaseEffect * effect;
+ //GLKBaseEffect * effect;
GLKSkyboxEffect * skybox;
}
@@ -68,7 +66,7 @@ @implementation RASceneGraphController
@synthesize sceneRoot = _sceneRoot;
@synthesize camera = _camera;
-@synthesize database = database;
+@synthesize pager = _pager;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
@@ -84,7 +82,7 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
renderVisitor = [RARenderVisitor new];
renderVisitor.camera = self.camera;
- database = [RATileDatabase new];
+ RATileDatabase * database = [RATileDatabase new];
database.bounds = CGRectMake( -180,-90,360,180 );
database.googleTileConvention = YES;
@@ -94,9 +92,9 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
database.maxzoom = 18;
// setup the database pager
- pager = [RATilePager new];
- pager.database = database;
- pager.camera = self.camera;
+ _pager = [RATilePager new];
+ _pager.imageryDatabase = database;
+ _pager.camera = self.camera;
}
return self;
}
@@ -119,8 +117,9 @@ - (void)viewDidLoad
manipulator.view = self.view;
// create another context to load textures into
- pager.loadingContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:[context sharegroup]];
+ self.pager.loadingContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:[context sharegroup]];
+ [self.pager setup];
[self setupGL];
}
@@ -151,8 +150,8 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interface
}
}
-#pragma mark - Scene Graph
+#pragma mark - Scene Graph
- (RAGeometry *)makeBoxWithHalfWidth:(GLfloat)half
{
@@ -226,7 +225,7 @@ - (RAGeometry *)makeBoxWithHalfWidth:(GLfloat)half
- (RANode *)createBlueMarble
{
RAGroup * root = [RAGroup new];
- [root addChild: pager.nodes];
+ [root addChild: self.pager.nodes];
return root;
}
@@ -240,9 +239,9 @@ - (void)setupGL
glEnable(GL_BLEND);
glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
- effect = [[GLKBaseEffect alloc] init];
+ /*effect = [[GLKBaseEffect alloc] init];
effect.label = @"Base Effect";
- effect.light0.enabled = GL_TRUE;
+ effect.light0.enabled = GL_TRUE;*/
// setup skybox
NSString * starPath = [[NSBundle mainBundle] pathForResource:@"star1" ofType:@"png"];
@@ -266,6 +265,7 @@ - (void)setupGL
//[self.sceneRoot accept: setupVisitor];
[self update];
+ [renderVisitor setupGL];
}
- (void)tearDownGL
@@ -275,7 +275,8 @@ - (void)tearDownGL
ReleaseGeometryVisitor * releaseVisitor = [[ReleaseGeometryVisitor alloc] init];
[self.sceneRoot accept: releaseVisitor];
- effect = nil;
+ //effect = nil;
+ [renderVisitor tearDownGL];
}
#pragma mark - GLKView and GLKViewController delegate methods
@@ -283,13 +284,14 @@ - (void)tearDownGL
- (void)update
{
self.camera.modelViewMatrix = [manipulator modelViewMatrix];
-
+
// position light directly above the globe
RAPolarCoordinate lightPolar = {
manipulator.latitude, manipulator.longitude, 1e7
};
GLKVector3 lightEcef = ConvertPolarToEcef( lightPolar );
- effect.light0.position = GLKVector4MakeWithVector3(lightEcef, 1.0);
+ //effect.light0.position = GLKVector4MakeWithVector3(lightEcef, 1.0);
+ renderVisitor.lightPosition = lightEcef;
// !!! the scene view bound is incorrect
@@ -311,7 +313,7 @@ - (void)update
skybox.transform.projectionMatrix = projectionMatrix;
skybox.transform.modelviewMatrix = self.camera.modelViewMatrix;
- [pager updateSceneGraph];
+ [self.pager updateSceneGraph];
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
@@ -328,12 +330,19 @@ - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
// run the render visitor
[renderVisitor clear];
[self.sceneRoot accept: renderVisitor];
- [renderVisitor renderWithEffect: effect];
+ [renderVisitor render];
// check for errors
GLenum err = glGetError();
- if ( err != GL_NO_ERROR ) {
- NSLog(@"glGetError = %d", err);
+ switch( err ) {
+ case GL_NO_ERROR: break;
+ case GL_INVALID_ENUM: NSLog(@"glGetError: invalid enum"); break;
+ case GL_INVALID_VALUE: NSLog(@"glGetError: invalid value"); break;
+ case GL_INVALID_OPERATION: NSLog(@"glGetError: invalid operation"); break;
+ case GL_STACK_OVERFLOW: NSLog(@"glGetError: stack overflow"); break;
+ case GL_STACK_UNDERFLOW: NSLog(@"glGetError: stack underflow"); break;
+ case GL_OUT_OF_MEMORY: NSLog(@"glGetError: out of memory"); break;
+ default: NSLog(@"glGetError: unknown error = 0x%04X", err); break;
}
}
View
25 Source/RAShaderProgram.h
@@ -1,5 +1,5 @@
//
-// RAShader.h
+// RAShaderProgram.h
// EarthViewExample
//
// Created by Ross Anderson on 4/28/12.
@@ -8,6 +8,27 @@
#import <Foundation/Foundation.h>
-@interface RAShader : NSObject
+#import <GLKit/GLKVector3.h>
+#import <GLKit/GLKMatrix4.h>
+
+
+@interface RAShaderProgram : NSObject
+
+- (BOOL)isReady;
+
+// GLES context must be valid when calling these methods, and they must be called in this order:
+- (BOOL)loadShader:(NSString *)resourceName;
+- (void)bindAttribute:(NSString *)name toIdentifier:(NSUInteger)ident;
+- (BOOL)link;
+- (NSUInteger)indexForIdentifier:(NSUInteger)ident;
+- (BOOL)bindUniform:(NSString *)name toIdentifier:(NSUInteger)ident;
+
+- (void)use;
+- (void)setUniform:(NSUInteger)ident toInt:(GLint)v;
+- (void)setUniform:(NSUInteger)ident toVector3:(GLKVector3)v;
+- (void)setUniform:(NSUInteger)ident toVector4:(GLKVector4)v;
+- (void)setUniform:(NSUInteger)ident toMatrix4:(GLKMatrix4)m;
+
+- (void)tearDownGL;
@end
View
263 Source/RAShaderProgram.m
@@ -1,13 +1,270 @@
//
-// RAShader.m
+// RAShaderProgram.m
// EarthViewExample
//
// Created by Ross Anderson on 4/28/12.
// Copyright (c) 2012 Ross Anderson. All rights reserved.
//
-#import "RAShader.h"
+#import "RAShaderProgram.h"
-@implementation RAShader
+@implementation RAShaderProgram {
+ GLuint _program;
+ BOOL _linked;
+ GLuint _vertShader;
+ GLuint _fragShader;
+
+ NSInteger _uniformsCount;
+ GLint * _uniforms;
+}
+
+- (id)init
+{
+ self = [super init];
+ if ( self ) {
+ _program = 0;
+ _linked = NO;
+
+ _uniformsCount = -1;
+ _uniforms = NULL;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if ( _uniforms ) free( _uniforms );
+}
+
+- (BOOL)isReady
+{
+ return _linked;
+}
+
+- (BOOL)loadShader:(NSString *)resourceName
+{
+ NSString * vertexShaderPath;
+ NSString * fragmentShaderPath;
+
+ vertexShaderPath = [[NSBundle mainBundle] pathForResource:resourceName ofType:@"vsh"];
+ fragmentShaderPath = [[NSBundle mainBundle] pathForResource:resourceName ofType:@"fsh"];
+
+ NSAssert( vertexShaderPath, @"vertex shader path must be valid" );
+ NSAssert( fragmentShaderPath, @"fragment shader path must be valid" );
+
+ // Create shader program.
+ _program = glCreateProgram();
+
+ // Create and compile vertex shader.
+ if (![self compileShader:&_vertShader type:GL_VERTEX_SHADER file:vertexShaderPath]) {
+ NSLog(@"Failed to compile vertex shader");
+ return NO;
+ }
+
+ // Create and compile fragment shader.
+ if (![self compileShader:&_fragShader type:GL_FRAGMENT_SHADER file:fragmentShaderPath]) {
+ NSLog(@"Failed to compile fragment shader");
+ return NO;
+ }
+
+ // Attach vertex shader to program.
+ glAttachShader(_program, _vertShader);
+
+ // Attach fragment shader to program.
+ glAttachShader(_program, _fragShader);
+ return YES;
+}
+
+- (void)bindAttribute:(NSString *)name toIdentifier:(NSUInteger)ident
+{
+ NSAssert( _program > 0, @"program must be loaded when calling bindAttribute:toIndex:");
+ NSAssert( _linked == NO, @"program cannot be linked when calling bindAttribute:toIndex:");
+
+ glBindAttribLocation( _program, ident, [name UTF8String] );
+}
+
+- (BOOL)link
+{
+ GLint status;
+ glLinkProgram(_program);
+
+#if defined(DEBUG)
+ GLint logLength;
+ glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ GLchar *log = (GLchar *)malloc(logLength);
+ glGetProgramInfoLog(_program, logLength, &logLength, log);
+ NSLog(@"Program link log:\n%s", log);
+ free(log);
+ }
+#endif
+
+ glGetProgramiv(_program, GL_LINK_STATUS, &status);
+ if (status == 0) {
+ NSLog(@"Failed to link program: %d", _program);
+
+ if (_vertShader) {
+ glDeleteShader(_vertShader);
+ _vertShader = 0;
+ }
+ if (_fragShader) {
+ glDeleteShader(_fragShader);
+ _fragShader = 0;
+ }
+ if (_program) {
+ glDeleteProgram(_program);
+ _program = 0;
+ }
+
+ return NO;
+ }
+
+ // Release vertex and fragment shaders.
+ if (_vertShader) {
+ glDetachShader(_program, _vertShader);
+ glDeleteShader(_vertShader);
+ _vertShader = 0;
+ }
+ if (_fragShader) {
+ glDetachShader(_program, _fragShader);
+ glDeleteShader(_fragShader);
+ _fragShader = 0;
+ }
+
+ [self validateProgram: _program];
+
+ _linked = YES;
+ return YES;
+}
+
+- (NSUInteger)indexForIdentifier:(NSUInteger)ident
+{
+ NSAssert( ident < _uniformsCount, @"identifier out of range" );
+ return _uniforms[ident];
+}
+
+- (BOOL)bindUniform:(NSString *)name toIdentifier:(NSUInteger)ident
+{
+ NSAssert( _linked == YES, @"program must be linked before calling indexForUniform:");
+
+ GLuint index = glGetUniformLocation( _program, [name UTF8String] );
+ if ( index == -1 ) {
+ NSLog(@"failed to find index for uniform: %@", name);
+ return NO;
+ }
+
+ if ( (signed)ident >= _uniformsCount ) {
+ GLint * uniforms = (GLint *)malloc( (ident + 1) * sizeof(GLint) );
+ memset( uniforms, -1, (ident + 1) * sizeof(GLint) );
+ if ( _uniforms ) {
+ memcpy( uniforms, _uniforms, _uniformsCount * sizeof(GLint) );
+ free( _uniforms );
+ }
+
+ _uniformsCount = ident + 1;
+ _uniforms = uniforms;
+ }
+
+ _uniforms[ident] = index;
+ return YES;
+}
+
+- (void)use
+{
+ glUseProgram(_program);
+}
+
+- (void)setUniform:(NSUInteger)ident toInt:(GLint)v
+{
+ NSAssert( ident < _uniformsCount, @"identifier out of range" );
+ GLint location = _uniforms[ident];
+ glUniform1i( location, v );
+}
+
+- (void)setUniform:(NSUInteger)ident toVector3:(GLKVector3)v
+{
+ NSAssert( ident < _uniformsCount, @"identifier out of range" );
+ GLint location = _uniforms[ident];
+ glUniform3fv( location, 1, v.v );
+}
+
+- (void)setUniform:(NSUInteger)ident toVector4:(GLKVector4)v
+{
+ NSAssert( ident < _uniformsCount, @"identifier out of range" );
+ GLint location = _uniforms[ident];
+ glUniform4fv( location, 1, v.v );
+}
+
+- (void)setUniform:(NSUInteger)ident toMatrix4:(GLKMatrix4)m
+{
+ NSAssert( ident < _uniformsCount, @"identifier out of range" );
+ GLint location = _uniforms[ident];
+ glUniformMatrix4fv( location, 1, 0, m.m );
+}
+
+- (void)tearDownGL
+{
+ if (_program) {
+ glDeleteProgram(_program);
+ _program = 0;
+ _linked = NO;
+ }
+}
+
+- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
+{
+ GLint status;
+ const GLchar *source;
+
+ source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
+ if (!source) {
+ NSLog(@"Failed to load vertex shader");
+ return NO;
+ }
+
+ *shader = glCreateShader(type);
+ glShaderSource(*shader, 1, &source, NULL);
+ glCompileShader(*shader);
+
+#if defined(DEBUG)
+ GLint logLength;
+ glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ GLchar *log = (GLchar *)malloc(logLength);
+ glGetShaderInfoLog(*shader, logLength, &logLength, log);
+ NSLog(@"Shader compile log:\n%s", log);
+ free(log);
+ }
+#endif
+
+ glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
+ if (status == 0) {
+ glDeleteShader(*shader);
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)validateProgram:(GLuint)prog
+{
+ GLint logLength, status;
+
+ glValidateProgram(prog);
+ glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ GLchar *log = (GLchar *)malloc(logLength);
+ glGetProgramInfoLog(prog, logLength, &logLength, log);
+ NSLog(@"Program validate log:\n%s", log);
+ free(log);
+ }
+
+ glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
+ if (status == 0) {
+ return NO;
+ }
+
+ return YES;
+}
@end
View
4 Source/RATilePager.h
@@ -19,13 +19,15 @@
@interface RATilePager : NSObject
-@property (strong) RATileDatabase * database;
+@property (strong) RATileDatabase * imageryDatabase;
+@property (strong) RATileDatabase * terrainDatabase;
@property (strong) EAGLContext * loadingContext;
@property (readonly) RAGroup * nodes;
@property (readonly) NSSet * rootPages;
@property (strong) RACamera * camera;
+- (void)setup; // call once the databases are configured
- (void)updateSceneGraph;
@end
View
410 Source/RATilePager.m
@@ -14,7 +14,6 @@
#import <Foundation/Foundation.h>
#import <GLKit/GLKVector2.h>
-#define SHOW_DEBUG_GRID 0
@interface RAPageDelta : NSObject
@property (strong) NSSet * pagesToAdd;
@@ -27,18 +26,17 @@ @implementation RAPageDelta
@implementation RATilePager {
- RATileDatabase * _database;
RATextureWrapper * _defaultTexture;
- NSOperationQueue * _loadQueue;
- __weak NSOperation * _traverseOp;
+ NSOperationQueue * _urlLoadingQueue;
+ NSOperationQueue * _pageUpdateQueue;
NSMutableSet * _activePages;
NSMutableSet * _insertPages;
NSMutableSet * _removePages;
}
-@synthesize database = _database, loadingContext, nodes, rootPages, camera;
+@synthesize imageryDatabase, terrainDatabase, loadingContext, nodes, rootPages, camera;
- (id)init
{
@@ -51,29 +49,30 @@ - (id)init
[cache setMemoryCapacity: 5*1000*1000];
[cache setDiskCapacity: 250*1000*1000];
- _loadQueue = [[NSOperationQueue alloc] init];
- [_loadQueue setName:@"URL Loading Queue"];
- [_loadQueue setMaxConcurrentOperationCount: 1];
+ _urlLoadingQueue = [[NSOperationQueue alloc] init];
+ [_urlLoadingQueue setName:@"URL Loading Queue"];
+ [_urlLoadingQueue setMaxConcurrentOperationCount: 10];
+
+ _pageUpdateQueue = [[NSOperationQueue alloc] init];
+ [_pageUpdateQueue setName:@"Tile Update Queue"];
+ [_pageUpdateQueue setMaxConcurrentOperationCount: 1];
}
return self;
}
- (void)dealloc {
- [_loadQueue cancelAllOperations];
- [_loadQueue waitUntilAllOperationsAreFinished];
-}
+ [_urlLoadingQueue cancelAllOperations];
+ [_urlLoadingQueue waitUntilAllOperationsAreFinished];
-- (RATileDatabase *)database {
- return _database;
+ [_pageUpdateQueue cancelAllOperations];
+ [_pageUpdateQueue waitUntilAllOperationsAreFinished];
}
-- (void)setDatabase:(RATileDatabase *)database {
- _database = database;
-
+- (void)setup {
// build root pages
NSMutableSet * pages = [NSMutableSet set];
- int basezoom = self.database.minzoom;
+ int basezoom = self.imageryDatabase.minzoom;
if ( basezoom < 2 ) basezoom = 2;
int tilecount = 1 << basezoom; // fast way to calc 2 ^ basezoom
@@ -94,26 +93,16 @@ - (RAGeometry *)createGeometryForTile:(TileID)tile
RAGeometry * geom = [RAGeometry new];
geom.positionOffset = (0*sizeof(GLfloat));
geom.normalOffset = (3*sizeof(GLfloat));
- geom.textureOffset = (6*sizeof(GLfloat));
-
- static GLKEffectPropertyMaterial * sPageMaterial = nil;
- if ( sPageMaterial == nil ) {
- sPageMaterial = [GLKEffectPropertyMaterial new];
- sPageMaterial.ambientColor = (GLKVector4){ 0.8f, 0.8f, 0.8f, 1.0};
- sPageMaterial.diffuseColor = (GLKVector4){ 1.0f, 1.0f, 1.0f, 1.0};
- sPageMaterial.specularColor = (GLKVector4){ 0.0f, 0.0f, 0.0f, 1.0};
- sPageMaterial.shininess = 0.0f;
- }
- geom.material = sPageMaterial;
-
+ geom.textureOffset = (6*sizeof(GLfloat));
return geom;
}
-- (void)setupPageGeometry:(RAGeometry *)geom forTile:(TileID)tile withTexForTile:(TileID)texTile {
- int gridSize = 8;
+- (void)setupGeometryForPage:(RAPage *)page withTextureFromPage:(RAPage *)texPage withHeightFromPage:(RAPage *)hgtPage {
+ int gridSize = 64;
+ RAGeometry * geom = page.geometry;
- RAPolarCoordinate lowerLeft = [self.database tileLatLonOrigin:tile];
- RAPolarCoordinate upperRight = [self.database tileLatLonOrigin:TileOppositeCorner(tile)];
+ 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. ||
@@ -131,9 +120,32 @@ - (void)setupPageGeometry:(RAGeometry *)geom forTile:(TileID)tile withTexForTile
size_t indexDataSize = 6*sizeof(GLushort) * (gridSize-1)*(gridSize-1);
GLushort * indexData = (GLushort *)alloca(indexDataSize);
- // specify mesh vertices and indices
+ // get raw access to image data
+ NSUInteger bytesPerPixel = 4;
+ NSUInteger bytesPerRow = 0;
+ NSUInteger width = 0, height = 0;
+ unsigned char * rawData = NULL;
+ if ( hgtPage.terrain ) {
+ CGImageRef imageRef = [hgtPage.terrain CGImage];
+ width = CGImageGetWidth(imageRef);
+ height = CGImageGetHeight(imageRef);
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ rawData = (unsigned char*)calloc(height * width * 4, sizeof(unsigned char));
+ bytesPerRow = bytesPerPixel * width;
+ NSUInteger bitsPerComponent = 8;
+ CGContextRef context = CGBitmapContextCreate(rawData, width,
+ height,bitsPerComponent, bytesPerRow, colorSpace,
+ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
+ CGColorSpaceRelease(colorSpace);
+
+ CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
+ CGContextRelease(context);
+ }
+
size_t vertexDataPos = 0;
size_t indexDataPos = 0;
+
+ // calculate mesh vertices and indices
for( unsigned int gy = 0; gy < gridSize; gy++ ) {
for( unsigned int gx = 0; gx < gridSize; gx++ ) {
RAPolarCoordinate gpos;
@@ -143,105 +155,159 @@ - (void)setupPageGeometry:(RAGeometry *)geom forTile:(TileID)tile withTexForTile
GLKVector3 ecef = ConvertPolarToEcef(gpos);
GLKVector3 normal = GLKVector3Normalize(ecef);
- GLKVector2 tex = [self.database textureCoordsForLatLon:gpos inTile:texTile];
+ GLKVector2 tex = [self.imageryDatabase textureCoordsForLatLon:gpos inTile:texPage.tile];
- // fill vertex data
- vertexData[vertexDataPos++] = ecef.x;
- vertexData[vertexDataPos++] = ecef.y;
- vertexData[vertexDataPos++] = ecef.z;
+ if ( rawData ) {
+ GLKVector2 hgtPixel = [self.imageryDatabase textureCoordsForLatLon:gpos inTile:hgtPage.tile];
+ hgtPixel.x = (width-1) * hgtPixel.x;
+ hgtPixel.y = (height-1) * (1.0-hgtPixel.y);
+
+ // extrude by height
+ unsigned char * pixel = rawData + (bytesPerRow * (int)hgtPixel.y) + (bytesPerPixel * (int)hgtPixel.x);
+ CGFloat red = pixel[0] / 255.0;
+ CGFloat green = pixel[1] / 255.0;
+ CGFloat blue = pixel[2] / 255.0;
+
+ float height = 0.01 * ( red + green + blue );
+ ecef = GLKVector3Add( ecef, GLKVector3MultiplyScalar(normal, height) );
+ }
- vertexData[vertexDataPos++] = normal.x;
- vertexData[vertexDataPos++] = normal.y;
- vertexData[vertexDataPos++] = normal.z;
+ // fill vertex data
+ vertexData[vertexDataPos+0] = ecef.x;
+ vertexData[vertexDataPos+1] = ecef.y;
+ vertexData[vertexDataPos+2] = ecef.z;
- vertexData[vertexDataPos++] = tex.x;
- vertexData[vertexDataPos++] = tex.y;
+ vertexData[vertexDataPos+6] = tex.x;
+ vertexData[vertexDataPos+7] = tex.y;
if ( gx < gridSize-1 && gy < gridSize-1 ) {
GLushort baseElement = gy*gridSize + gx;
- indexData[indexDataPos++] = baseElement;
- indexData[indexDataPos++] = baseElement + 1;
- indexData[indexDataPos++] = baseElement + gridSize;
+ indexData[indexDataPos+0] = baseElement;
+ indexData[indexDataPos+1] = baseElement + 1;
+ indexData[indexDataPos+2] = baseElement + gridSize;
+
+ indexData[indexDataPos+3] = baseElement + 1;
+ indexData[indexDataPos+4] = baseElement + gridSize + 1;
+ indexData[indexDataPos+5] = baseElement + gridSize;
- indexData[indexDataPos++] = baseElement + 1;
- indexData[indexDataPos++] = baseElement + gridSize + 1;
- indexData[indexDataPos++] = baseElement + gridSize;
+ indexDataPos += 6;
}
+
+ vertexDataPos += 8;
}
}
+
NSAssert( vertexDataPos == 8*gridSize*gridSize, @"didn't fill vertex array" );
NSAssert( indexDataPos == 6*(gridSize-1)*(gridSize-1), @"didn't fill index array" );
+
+ vertexDataPos = 0;
+ indexDataPos = 0;
+ // calculate normals
+ for( unsigned int gy = 0; gy < gridSize; gy++ ) {
+ for( unsigned int gx = 0; gx < gridSize; gx++ ) {
+ GLKVector3 vectorLeft, vectorRight;
+
+ GLKVector3 ecef0 = GLKVector3Make( vertexData[vertexDataPos+0], vertexData[vertexDataPos+1], vertexData[vertexDataPos+2] );
+ int rowOffset = 8 * gridSize;
+
+ 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);
+ }
+
+ 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 setIndexData:indexData withSize:indexDataSize withStride:sizeof(GLushort)];
-}
-
-- (RAPage *)makeLeafPageForTile:(TileID)t withParent:(RAPage *)parent{
- RAPage * page = [[RAPage alloc] initWithTileID:t andParent:parent];
- // calculate tile center and radius
- GLKVector3 center = ConvertPolarToEcef( [self.database tileLatLonCenter:page.tile] );
- GLKVector3 corner = ConvertPolarToEcef( [self.database tileLatLonOrigin:page.tile] );
- [page setCenter:center andRadius:GLKVector3Distance(center, corner)];
-
- return page;
+ if ( rawData ) free( rawData );
}
-- (void)preparePageForTraversal:(RAPage *)page {
- NSAssert( page != nil, @"the prepared page must be valid");
+- (void)updatePageIfNeeded:(RAPage *)page {
+ NSAssert( page != nil, @"the requested page must be valid");
- // create child pages
- if ( page.child1 == nil ) page.child1 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+0, 2*page.tile.y+0, page.tile.z+1 } withParent:page];
- if ( page.child2 == nil ) page.child2 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+1, 2*page.tile.y+0, page.tile.z+1 } withParent:page];
- if ( page.child3 == nil ) page.child3 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+0, 2*page.tile.y+1, page.tile.z+1 } withParent: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];
+ if ( page.needsUpdate == YES && page.updatePageOp == nil ) {
+ if ( page.geometry == nil ) {
+ // generate the geometry
+ page.geometry = [self createGeometryForTile:page.tile];
+ }
+
+ //NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
+ // find an ancestor tile with a valid texture
+ RAPage * imgAncestor = page;
+ while( imgAncestor ) {
+ // texture valid? use this page
+ if ( imgAncestor.imagery ) break;
+
+ imgAncestor = imgAncestor.parent;
+ }
+
+ RAPage * hgtAncestor = page;
+ while( hgtAncestor ) {
+ // terrain valid? use this page
+ if ( hgtAncestor.terrain ) break;
+
+ hgtAncestor = hgtAncestor.parent;
+ }
+
+ if ( imgAncestor ) {
+ // recycle texture with appropriate tex coords
+ [self setupGeometryForPage:page withTextureFromPage:imgAncestor withHeightFromPage:hgtAncestor];
+ page.geometry.texture0 = imgAncestor.imagery;
+ } else {
+ // show grid if necessary
+ [self setupGeometryForPage:page withTextureFromPage:page withHeightFromPage:hgtAncestor];
+ page.geometry.texture0 = _defaultTexture;
+ }
+
+ page.needsUpdate = NO;
+ page.updatePageOp = nil;
+ //}];
+ //page.updatePageOp = op;
+ //[_pageUpdateQueue addOperation:op];
+ }
}
- (void)requestPage:(RAPage *)page {
NSAssert( page != nil, @"the requested page must be valid");
- // page.lastRequestTime = [NSDate timeIntervalSinceReferenceDate]; // not currently used
- if ( page.geometry == nil ) {
- // generate the geometry
- page.geometry = [self createGeometryForTile:page.tile];
-
- // set this macro to 1 to skip page loading and display a grid instead
-#if SHOW_DEBUG_GRID
- [self setupPageGeometry:page.geometry forTile:page.tile withTexForTile:page.tile];
- page.geometry.texture0 = _defaultTexture;
- return;
-#endif
+ // request the tile image if needed
+ if ( page.imagery == nil && page.imageryLoadOp == nil && self.imageryDatabase ) {
+ NSURL * url = [self.imageryDatabase urlForTile: page.tile];
+ NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15.0];
- // find an ancestor tile with a valid texture
- RAPage * ancestor = page;
- while( ancestor ) {
- // texture valid? use this page
- if ( ancestor.texture ) break;
-
- ancestor = ancestor.parent;
- }
-
- if ( ancestor ) {
- // recycle texture with appropriate tex coords
- [self setupPageGeometry:page.geometry forTile:page.tile withTexForTile:ancestor.tile];
- page.geometry.texture0 = ancestor.texture;
- } else {
- // show grid if necessary
- [self setupPageGeometry:page.geometry forTile:page.tile withTexForTile:page.tile];
- page.geometry.texture0 = _defaultTexture;
- }
-
- // if we already have a texture, we don't need to load it
- if ( page.texture ) return;
-
- // request the tile image
- NSURL * url = [self.database urlForTile: page.tile];
- if ( url ) {
- NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15.0];
-
- [NSURLConnection sendAsynchronousRequest:request queue:_loadQueue completionHandler:^(NSURLResponse* response, NSData* data, NSError* error) {
+ if ( url && request ) {
+ NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
+ NSURLResponse * response = nil;
+ NSError * error = nil;
+ NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
+
if ( error ) {
NSLog(@"URL Error loading (%@): %@", url, error);
+ } else if ( [[response MIMEType] isEqualToString:@"text/html"] ) {
+ NSString * content = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
+ NSLog(@"Request Returned: %@", content);
} else {
[EAGLContext setCurrentContext: self.loadingContext];
@@ -258,20 +324,60 @@ - (void)requestPage:(RAPage *)page {
} else {
// generate texture wrapper
RATextureWrapper * texture = [[RATextureWrapper alloc] initWithTextureInfo:textureInfo];
- page.texture = texture;
-
- [self setupPageGeometry:page.geometry forTile:page.tile withTexForTile:page.tile];
- page.geometry.texture0 = texture;
+ page.imagery = texture;
}
glFlush();
[EAGLContext setCurrentContext: nil];
}
+
+ page.needsUpdate = YES;
+ page.imageryLoadOp = nil;
}];
+ page.imageryLoadOp = op;
+ [_urlLoadingQueue addOperation:op];
+ }
+ }
+
+ // request the terrain if needed
+ if ( page.terrain == nil && page.terrainLoadOp == nil && self.terrainDatabase ) {
+ NSURL * url = [self.terrainDatabase urlForTile: page.tile];
+ NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15.0];
+
+ // !!! if url is invalid (i.e. if beyond the max zoom level) then we will repeatedly try to load it!
+
+ if ( url && request ) {
+ NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
+ NSURLResponse * response = nil;
+ NSError * error = nil;
+ NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
+
+ if ( error ) {
+ NSLog(@"URL Error loading (%@): %@", url, error);
+ } else if ( [[response MIMEType] isEqualToString:@"text/html"] ) {
+ NSString * content = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
+ NSLog(@"Request Returned: %@", content);
+ } else {
+ [EAGLContext setCurrentContext: self.loadingContext];
+
+ // without converting the image, I get a "data preprocessing error". I have no idea why
+ UIImage * image = [UIImage imageWithData:data];
+
+ page.terrain = image;
+ }
+
+ page.needsUpdate = YES;
+ page.terrainLoadOp = nil;
+ }];
+ page.terrainLoadOp = op;
+ [_urlLoadingQueue addOperation:op];
}
}
}
+
+#pragma mark Page Traversal Methods
+
- (float)calculatePageTilt:(RAPage *)page {
// calculate dot product between page normal and camera vector
const GLKVector3 unitZ = { 0, 0, -1 };
@@ -307,6 +413,27 @@ - (BOOL)isPageOnscreen:(RAPage *)page {
return YES;
}
+- (RAPage *)makeLeafPageForTile:(TileID)t withParent:(RAPage *)parent {
+ RAPage * page = [[RAPage alloc] initWithTileID:t andParent:parent];
+
+ // calculate tile center and radius
+ GLKVector3 center = ConvertPolarToEcef( [self.imageryDatabase tileLatLonCenter:page.tile] );
+ GLKVector3 corner = ConvertPolarToEcef( [self.imageryDatabase tileLatLonOrigin:page.tile] );
+ [page setCenter:center andRadius:GLKVector3Distance(center, corner)];
+
+ return page;
+}
+
+- (void)preparePageForTraversal:(RAPage *)page {
+ NSAssert( page != nil, @"the prepared page must be valid");
+
+ // create child pages
+ if ( page.child1 == nil ) page.child1 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+0, 2*page.tile.y+0, page.tile.z+1 } withParent:page];
+ if ( page.child2 == nil ) page.child2 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+1, 2*page.tile.y+0, page.tile.z+1 } withParent:page];
+ if ( page.child3 == nil ) page.child3 = [self makeLeafPageForTile:(TileID){ 2*page.tile.x+0, 2*page.tile.y+1, page.tile.z+1 } withParent: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 collectActivePages:(NSMutableSet *)activeSet {
NSAssert( page != nil, @"the traversed page must be valid");
@@ -314,7 +441,7 @@ - (void)traversePage:(RAPage *)page collectActivePages:(NSMutableSet *)activeSet
if ( [self calculatePageTilt:page] < -0.5f ) return;
float texelError = 0.0f;
- if ( page.tile.z <= self.database.maxzoom ) // force display at maximum level
+ if ( page.tile.z <= self.imageryDatabase.maxzoom ) // force display at maximum level
texelError = [self calculatePageScreenSpaceError:page];
// should we choose to display this page?
@@ -323,24 +450,25 @@ - (void)traversePage:(RAPage *)page collectActivePages:(NSMutableSet *)activeSet
if ( ! [self isPageOnscreen:page] ) return;
[self requestPage: page];
+ [self updatePageIfNeeded: page];
[activeSet addObject:page];
// prune children
page.child1 = page.child2 = page.child3 = page.child4 = nil;
- } else {
- // traverse children
- [self preparePageForTraversal:page];
-
- [self traversePage: page.child1 collectActivePages:activeSet];
- [self traversePage: page.child2 collectActivePages:activeSet];
- [self traversePage: page.child3 collectActivePages:activeSet];
- [self traversePage: page.child4 collectActivePages:activeSet];
+ return;
}
+
+ // traverse children
+ [self preparePageForTraversal:page];
+
+ [self traversePage: page.child1 collectActivePages:activeSet];
+ [self traversePage: page.child2 collectActivePages:activeSet];
+ [self traversePage: page.child3 collectActivePages:activeSet];
+ [self traversePage: page.child4 collectActivePages:activeSet];
}
- (void)updateSceneGraph {
// this is the only method where the GL context is valid!
- NSAssert( self.database, @"database must be valid" );
// load default texture
if ( _defaultTexture == nil ) {
@@ -367,37 +495,28 @@ - (void)updateSceneGraph {
}
*/
- BOOL doTraversal = ( _loadQueue.operationCount == 0 );
+ NSMutableSet * currentPages = [[NSMutableSet alloc] init];
- // begin a traversal if necessary
- if ( _traverseOp == nil && doTraversal ) {
- NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
- NSMutableSet * currentPages = [[NSMutableSet alloc] init];
-
- // traverse pages gathering ones that are active and should be displayed
- [rootPages enumerateObjectsUsingBlock:^(RAPage *page, BOOL *stop) {
- [self traversePage:page collectActivePages:currentPages];
- }];
+ // traverse pages gathering ones that are active and should be displayed
+ [rootPages enumerateObjectsUsingBlock:^(RAPage *page, BOOL *stop) {
+ [self traversePage:page collectActivePages:currentPages];
+ }];
- @synchronized(self) {
- NSMutableSet * insertPages = [currentPages mutableCopy];
- [insertPages minusSet: _activePages];
-
- NSMutableSet * removePages = [_activePages mutableCopy];
- [removePages minusSet: currentPages];
-
- _insertPages = insertPages;
- _removePages = removePages;
- _activePages = currentPages;
- }
- }];
+ @synchronized(self) {
+ NSMutableSet * insertPages = [currentPages mutableCopy];
+ [insertPages minusSet: _activePages];
+
+ NSMutableSet * removePages = [_activePages mutableCopy];
+ [removePages minusSet: currentPages];
- [_loadQueue addOperation:op];
- _traverseOp = op;
+ _insertPages = insertPages;
+ _removePages = removePages;
+ _activePages = currentPages;
}
@synchronized(self) {
- //NSLog(@"Active: %d, Insert: %d, Remove: %d, Load Ops: %d", _activePages.count, _insertPages.count, _removePages.count, _loadQueue.operationCount);
+ //NSLog(@"Active: %d, Insert: %d, Remove: %d", _activePages.count, _insertPages.count, _removePages.count);
+ //NSLog(@"Ops: %d urls loading, %d page updates", _urlLoadingQueue.operationCount, _pageUpdateQueue.operationCount);
// add new pages
[_insertPages enumerateObjectsUsingBlock:^(RAPage * page, BOOL *stop) {
@@ -412,6 +531,7 @@ - (void)updateSceneGraph {
// delete the page data if not a root page
if ( ! [rootPages containsObject:page] ) {
page.geometry = nil;
+ page.needsUpdate = YES;
}
}];
Please sign in to comment.
Something went wrong with that request. Please try again.