<chapter>
<title>Data Access</title>
<xi:include href="xml/gis-data.xml"/>
+ <xi:include href="xml/gis-http.xml"/>
<xi:include href="xml/gis-wms.xml"/>
</chapter>
return TRUE;
}
-static gpointer expose(GisCallback *callback, gpointer _teapot)
+static void expose(GisCallback *callback, gpointer _teapot)
{
GisPluginTeapot *teapot = GIS_PLUGIN_TEAPOT(_teapot);
g_debug("GisPluginTeapot: expose");
glColor4f(0.9, 0.9, 0.7, 1.0);
glDisable(GL_CULL_FACE);
gdk_gl_draw_teapot(TRUE, 0.25);
-
- return NULL;
}
#include "gis-data.h"
-/*
+/**
* Open a file, creating parent directories if needed
*/
FILE *fopen_p(const gchar *path, const gchar *mode)
}
/* For passing data to the chunck callback */
-struct _cache_info {
+struct _CacheInfo {
FILE *fp;
gchar *path;
GisChunkCallback callback;
*/
static void _chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _info)
{
- struct _cache_info *info = _info;
+ struct _CacheInfo *info = _info;
if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) {
g_warning("GisHttp: _chunk_cb - soup failed with %d",
gint height;
} GisWms;
-
GisWms *gis_wms_new(
const gchar *uri_prefix, const gchar *uri_layer,
const gchar *uri_format, const gchar *prefix,
#include "objects/gis-marker.h"
#include "objects/gis-callback.h"
-#define FOV_DIST 2000.0
-#define MPPX(dist) (4*dist/FOV_DIST)
-
// #define ROAM_DEBUG
+/* Tessellation, "finding intersecting triangles" */
+/* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */
+/* http://www.opengl.org/wiki/Alpha_Blending */
+
/***********
* Helpers *
***********/
static void _draw_marker(GisOpenGL *opengl, GisMarker *marker)
{
- GisPoint *point = gis_object_center(GIS_OBJECT(marker));
+ GisPoint *point = gis_object_center(marker);
gdouble px, py, pz;
gis_viewer_project(GIS_VIEWER(opengl),
point->lat, point->lon, point->elev,
typedef struct _GisPlugin GisPlugin;
typedef struct _GisPluginInterface GisPluginInterface;
-typedef struct _GisPlugins GisPlugins;
struct _GisPluginInterface
{
GtkWidget *gis_plugin_get_config(GisPlugin *plugin);
/* Plugins API */
+typedef struct _GisPlugins GisPlugins;
+
#include "gis-viewer.h"
#include "gis-prefs.h"
+typedef GisPlugin *(*GisPluginConstructor)(GisViewer *viewer, GisPrefs *prefs);
+
struct _GisPlugins {
gchar *dir;
GList *plugins;
GisPrefs *prefs;
};
-typedef GisPlugin *(*GisPluginConstructor)(GisViewer *viewer, GisPrefs *prefs);
-
GisPlugins *gis_plugins_new(const gchar *dir, GisPrefs *prefs);
void gis_plugins_free();
GisPlugin *gis_plugins_load(GisPlugins *plugins, const char *name,
GisViewer *viewer, GisPrefs *prefs);
-GisPlugin *gis_plugins_enable(GisPlugins *plugin, const char *name,
+GisPlugin *gis_plugins_enable(GisPlugins *plugins, const char *name,
GisViewer *viewer, GisPrefs *prefs);
-GList *gis_plugins_load_enabled(GisPlugins *plugin,
+GList *gis_plugins_load_enabled(GisPlugins *plugins,
GisViewer *viewer, GisPrefs *prefs);
-gboolean gis_plugins_disable(GisPlugins *plugin, const char *name);
+gboolean gis_plugins_disable(GisPlugins *plugins, const char *name);
gboolean gis_plugins_unload(GisPlugins *plugins, const char *name);
}
#define make_pref_type(name, c_type, g_type) \
-c_type gis_prefs_get_##name##_v(GisPrefs *prefs, \
+c_type gis_prefs_get_##name##_v(GisPrefs *prefs, \
const gchar *group, const gchar *key, GError **_error) \
{ \
GError *error = NULL; \
- c_type value = g_key_file_get_##name(prefs->key_file, group, key, &error); \
+ c_type value = g_key_file_get_##name(prefs->key_file, group, key, &error); \
if (error && error->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND) \
g_warning("GisPrefs: get_value_##name - error getting key %s: %s\n", \
key, error->message); \
*_error = error; \
return value; \
} \
-c_type gis_prefs_get_##name(GisPrefs *prefs, const gchar *key, GError **error) \
+c_type gis_prefs_get_##name(GisPrefs *prefs, const gchar *key, GError **error) \
{ \
gchar **keys = g_strsplit(key, "/", 2); \
- c_type value = gis_prefs_get_##name##_v(prefs, keys[0], keys[1], error); \
+ c_type value = gis_prefs_get_##name##_v(prefs, keys[0], keys[1], error); \
g_strfreev(keys); \
return value; \
} \
\
-void gis_prefs_set_##name##_v(GisPrefs *prefs, \
+void gis_prefs_set_##name##_v(GisPrefs *prefs, \
const gchar *group, const gchar *key, const c_type value) \
{ \
- g_key_file_set_##name(prefs->key_file, group, key, value); \
+ g_key_file_set_##name(prefs->key_file, group, key, value); \
gchar *all = g_strconcat(group, "/", key, NULL); \
- g_signal_emit(prefs, signals[SIG_PREF_CHANGED], 0, \
+ g_signal_emit(prefs, signals[SIG_PREF_CHANGED], 0, \
all, g_type, &value); \
g_free(all); \
} \
-void gis_prefs_set_##name(GisPrefs *prefs, const gchar *key, const c_type value) \
+void gis_prefs_set_##name(GisPrefs *prefs, const gchar *key, const c_type value) \
{ \
gchar **keys = g_strsplit(key, "/", 2); \
- gis_prefs_set_##name##_v(prefs, keys[0], keys[1], value); \
+ gis_prefs_set_##name##_v(prefs, keys[0], keys[1], value); \
g_strfreev(keys); \
} \
{
g_debug("GisPrefs: class_init");
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
- gobject_class->dispose = gis_prefs_dispose;
+ gobject_class->dispose = gis_prefs_dispose;
signals[SIG_PREF_CHANGED] = g_signal_new(
"pref-changed",
G_TYPE_FROM_CLASS(gobject_class),
NULL,
gis_cclosure_marshal_VOID__STRING_UINT_POINTER,
G_TYPE_NONE,
- 1,
+ 3,
G_TYPE_STRING,
G_TYPE_UINT,
G_TYPE_POINTER);
* xyz2lle: 0.0, 0.0, 10.0 -> 0.0, 0.0, 0.0
*/
-#define azim2lon(azim) ((azim)*180/G_PI)
-#define lon2azim(lon) ((lon)*G_PI/180)
#define incl2lat(incl) (90-(incl)*180/G_PI)
#define lat2incl(lat) ((90-(lat))*G_PI/180)
#define rad2elev(rad) ((rad)-EARTH_R)
void gis_viewer_set_location(GisViewer *viewer, gdouble lat, gdouble lon, gdouble elev);
void gis_viewer_get_location(GisViewer *viewer, gdouble *lat, gdouble *lon, gdouble *elev);
-void gis_viewer_pan(GisViewer *viewer, gdouble forward, gdouble sideways, gdouble up);
+void gis_viewer_pan(GisViewer *viewer, gdouble forward, gdouble right, gdouble up);
void gis_viewer_zoom(GisViewer *viewer, gdouble scale);
void gis_viewer_set_rotation(GisViewer *viewer, gdouble x, gdouble y, gdouble z);
typedef struct _GisCallback GisCallback;
typedef struct _GisCallbackClass GisCallbackClass;
-typedef gpointer (*GisCallbackFunc)(GisCallback *callback, gpointer user_data);
+typedef void (*GisCallbackFunc)(GisCallback *callback, gpointer user_data);
struct _GisCallback {
GisObject parent;
GType gis_object_get_type(void);
-static inline GisPoint *gis_object_center(GisObject *object)
-{
- return &GIS_OBJECT(object)->center;
-}
+#define gis_object_center(object) \
+ (&GIS_OBJECT(object)->center)
#endif
gchar *gis_tile_get_path(GisTile *child)
{
- /* This could be easily cached if necessasairy */
+ /* This could be easily cached if necessary */
int x, y;
GList *parts = NULL;
for (GisTile *parent = child->parent; parent; child = parent, parent = child->parent)
return g_string_free(path, FALSE);
}
-gdouble _gis_tile_get_min_dist(GisTile *tile,
+static gdouble _gis_tile_get_min_dist(GisTile *tile,
gdouble lat, gdouble lon, gdouble elev)
{
gdouble tlat = lat > tile->edge.n ? tile->edge.n :
return distd(a, b);
}
-gboolean _gis_tile_needs_split(GisTile *tile,
+static gboolean _gis_tile_needs_split(GisTile *tile,
gdouble max_res, gint width, gint height,
gdouble lat, gdouble lon, gdouble elev)
{
return view_res < tile_res;
}
-void gis_tile_update(GisTile *tile,
+void gis_tile_update(GisTile *root,
gdouble res, gint width, gint height,
gdouble lat, gdouble lon, gdouble elev,
GisTileLoadFunc load_func, gpointer user_data)
{
- tile->atime = time(NULL);
- //g_debug("GisTile: update - %p->atime = %u", tile, (guint)tile->atime);
- gdouble lat_dist = tile->edge.n - tile->edge.s;
- gdouble lon_dist = tile->edge.e - tile->edge.w;
- if (_gis_tile_needs_split(tile, res, width, height, lat, lon, elev)) {
- gdouble lat_step = lat_dist / G_N_ELEMENTS(tile->children);
- gdouble lon_step = lon_dist / G_N_ELEMENTS(tile->children[0]);
+ root->atime = time(NULL);
+ //g_debug("GisTile: update - %p->atime = %u", root, (guint)root->atime);
+ gdouble lat_dist = root->edge.n - root->edge.s;
+ gdouble lon_dist = root->edge.e - root->edge.w;
+ if (_gis_tile_needs_split(root, res, width, height, lat, lon, elev)) {
+ gdouble lat_step = lat_dist / G_N_ELEMENTS(root->children);
+ gdouble lon_step = lon_dist / G_N_ELEMENTS(root->children[0]);
int x, y;
- gis_tile_foreach_index(tile, x, y) {
- if (!tile->children[x][y]) {
- tile->children[x][y] = gis_tile_new(tile,
- tile->edge.n-(lat_step*(x+0)),
- tile->edge.n-(lat_step*(x+1)),
- tile->edge.w+(lon_step*(y+1)),
- tile->edge.w+(lon_step*(y+0)));
- load_func(tile->children[x][y], user_data);
+ gis_tile_foreach_index(root, x, y) {
+ if (!root->children[x][y]) {
+ root->children[x][y] = gis_tile_new(root,
+ root->edge.n-(lat_step*(x+0)),
+ root->edge.n-(lat_step*(x+1)),
+ root->edge.w+(lon_step*(y+1)),
+ root->edge.w+(lon_step*(y+0)));
+ load_func(root->children[x][y], user_data);
}
- gis_tile_update(tile->children[x][y],
+ gis_tile_update(root->children[x][y],
res, width, height,
lat, lon, elev,
load_func, user_data);
}
}
-GisTile *gis_tile_find(GisTile *tile, gdouble lat, gdouble lon)
+GisTile *gis_tile_find(GisTile *root, gdouble lat, gdouble lon)
{
- gint rows = G_N_ELEMENTS(tile->children);
- gint cols = G_N_ELEMENTS(tile->children[0]);
+ gint rows = G_N_ELEMENTS(root->children);
+ gint cols = G_N_ELEMENTS(root->children[0]);
- gdouble lat_step = (tile->edge.n - tile->edge.s) / rows;
- gdouble lon_step = (tile->edge.e - tile->edge.w) / cols;
+ gdouble lat_step = (root->edge.n - root->edge.s) / rows;
+ gdouble lon_step = (root->edge.e - root->edge.w) / cols;
- gdouble lat_offset = tile->edge.n - lat;;
- gdouble lon_offset = lon - tile->edge.w;
+ gdouble lat_offset = root->edge.n - lat;;
+ gdouble lon_offset = lon - root->edge.w;
gint row = lat_offset / lat_step;
gint col = lon_offset / lon_step;
if (row < 0 || row >= rows || col < 0 || col >= cols)
return NULL;
- else if (tile->children[row][col] && tile->children[row][col]->data)
- return gis_tile_find(tile->children[row][col], lat, lon);
+ else if (root->children[row][col] && root->children[row][col]->data)
+ return gis_tile_find(root->children[row][col], lat, lon);
else
- return tile;
+ return root;
}
-GisTile *gis_tile_gc(GisTile *tile, time_t atime,
+GisTile *gis_tile_gc(GisTile *root, time_t atime,
GisTileFreeFunc free_func, gpointer user_data)
{
- if (!tile)
+ if (!root)
return NULL;
gboolean has_children = FALSE;
int x, y;
- gis_tile_foreach_index(tile, x, y) {
- tile->children[x][y] = gis_tile_gc(
- tile->children[x][y], atime,
+ gis_tile_foreach_index(root, x, y) {
+ root->children[x][y] = gis_tile_gc(
+ root->children[x][y], atime,
free_func, user_data);
- if (tile->children[x][y])
+ if (root->children[x][y])
has_children = TRUE;
}
//g_debug("GisTile: gc - %p->atime=%u < atime=%u",
- // tile, (guint)tile->atime, (guint)atime);
- if (!has_children && tile->atime < atime && tile->data) {
- free_func(tile, user_data);
- g_object_unref(tile);
+ // root, (guint)root->atime, (guint)atime);
+ if (!has_children && root->atime < atime && root->data) {
+ free_func(root, user_data);
+ g_object_unref(root);
return NULL;
}
- return tile;
+ return root;
}
/* Use GObject for this */
-void gis_tile_free(GisTile *tile, GisTileFreeFunc free_func, gpointer user_data)
+void gis_tile_free(GisTile *root, GisTileFreeFunc free_func, gpointer user_data)
{
- if (!tile)
+ if (!root)
return;
GisTile *child;
- gis_tile_foreach(tile, child)
+ gis_tile_foreach(root, child)
gis_tile_free(child, free_func, user_data);
if (free_func)
- free_func(tile, user_data);
- g_object_unref(tile);
+ free_func(root, user_data);
+ g_object_unref(root);
}
/* GObject code */
typedef void (*GisTileFreeFunc)(GisTile *tile, gpointer user_data);
/* Forech functions */
-#define gis_tile_foreach(tile, child) \
- for (int _x = 0; _x < G_N_ELEMENTS(tile->children); _x++) \
- for (int _y = 0; child = tile->children[_x][_y], \
- _y < G_N_ELEMENTS(tile->children[_x]); _y++) \
-
-#define gis_tile_foreach_index(tile, x, y) \
- for (x = 0; x < G_N_ELEMENTS(tile->children); x++) \
- for (y = 0; y < G_N_ELEMENTS(tile->children[x]); y++)
+#define gis_tile_foreach(parent, child) \
+ for (int _x = 0; _x < G_N_ELEMENTS(parent->children); _x++) \
+ for (int _y = 0; child = parent->children[_x][_y], \
+ _y < G_N_ELEMENTS(parent->children[_x]); _y++)
+
+#define gis_tile_foreach_index(parent, x, y) \
+ for (x = 0; x < G_N_ELEMENTS(parent->children); x++) \
+ for (y = 0; y < G_N_ELEMENTS(parent->children[x]); y++)
/* Path to string table, keep in sync with tile->children */
extern gchar *gis_tile_path_table[2][2];
/***********
* Helpers *
***********/
-static gpointer expose(GisCallback *callback, gpointer _env)
+static void expose(GisCallback *callback, gpointer _env)
{
GisPluginEnv *env = GIS_PLUGIN_ENV(_env);
g_debug("GisPluginEnv: expose");
glPopMatrix();
}
*/
-
- return NULL;
}
*/
#include <time.h>
+#include <stdlib.h>
#include <glib/gstdio.h>
#include <GL/gl.h>
#define TILE_WIDTH 1024
#define TILE_HEIGHT 512
-const guchar colormap[][2][4] = {
+static const guchar colormap[][2][4] = {
{{0x73, 0x91, 0xad}, {0x73, 0x91, 0xad, 0x20}}, // Oceans
{{0xf6, 0xee, 0xee}, {0xf6, 0xee, 0xee, 0x00}}, // Ground
{{0xff, 0xff, 0xff}, {0xff, 0xff, 0xff, 0xff}}, // Borders
GisTile *tile;
GdkPixbuf *pixbuf;
};
-#include <stdlib.h>
static gboolean _load_tile_cb(gpointer _data)
{
struct _LoadTileData *data = _data;
test->viewer = g_object_ref(viewer);
GisMarker *marker = gis_marker_new("St. Charles");
- gis_point_set_lle(gis_object_center(GIS_OBJECT(marker)), 38.841847, -90.491982, 0);
+ gis_point_set_lle(gis_object_center(marker), 38.841847, -90.491982, 0);
GIS_OBJECT(marker)->lod = EARTH_R/4;
test->marker = gis_viewer_add(test->viewer, GIS_OBJECT(marker), GIS_LEVEL_OVERLAY, 0);
#include <glib.h>
#include <math.h>
#include <string.h>
-#include "gpqueue.h"
#include <GL/gl.h>
#include <GL/glu.h>
+#include "gpqueue.h"
#include "gis-util.h"
#include "roam.h"
lle2xyz(lat, lon, elev, &point->x, &point->y, &point->z);
return point;
}
-RoamPoint *roam_point_dup(RoamPoint *point)
+
+static RoamPoint *roam_point_dup(RoamPoint *point)
{
RoamPoint *new = g_memdup(point, sizeof(RoamPoint));
new->tris = 0;
&point->x, &point->y, &point->z);
}
}
-void roam_point_update_projection(RoamPoint *point, RoamSphere *sphere)
+
+void roam_point_update_projection(RoamPoint *point, RoamView *view)
{
static int count = 0;
static int version = 0;
- if (version != sphere->view->version) {
+ if (version != view->version) {
g_debug("RoamPoint: Projected %d points", count);
count = 0;
- version = sphere->view->version;
+ version = view->version;
}
- if (point->pversion != sphere->view->version) {
+ if (point->pversion != view->version) {
/* Cache projection */
gluProject(point->x, point->y, point->z,
- sphere->view->model, sphere->view->proj, sphere->view->view,
+ view->model, view->proj, view->view,
&point->px, &point->py, &point->pz);
- point->pversion = sphere->view->version;
+ point->pversion = view->version;
count++;
}
}
g_pqueue_remove(sphere->triangles, triangle->handle);
}
-void roam_triangle_sync_neighbors(RoamTriangle *new, RoamTriangle *old, RoamTriangle *neigh)
+static void roam_triangle_sync_neighbors(RoamTriangle *new, RoamTriangle *old, RoamTriangle *neigh)
{
if (neigh->t.l == old) neigh->t.l = new;
else if (neigh->t.b == old) neigh->t.b = new;
else g_assert_not_reached();
}
-gboolean roam_point_visible(RoamPoint *triangle, RoamSphere *sphere)
+static gboolean roam_point_visible(RoamPoint *triangle, RoamSphere *sphere)
{
gint *view = sphere->view->view;
return triangle->px > view[0] && triangle->px < view[2] &&
triangle->py > view[1] && triangle->py < view[3] &&
triangle->pz > 0 && triangle->pz < 1;
}
-gboolean roam_triangle_visible(RoamTriangle *triangle, RoamSphere *sphere)
+static gboolean roam_triangle_visible(RoamTriangle *triangle, RoamSphere *sphere)
{
/* Do this with a bounding box */
return roam_point_visible(triangle->p.l, sphere) ||
void roam_triangle_update_errors(RoamTriangle *triangle, RoamSphere *sphere)
{
/* Update points */
- roam_point_update_projection(triangle->p.l, sphere);
- roam_point_update_projection(triangle->p.m, sphere);
- roam_point_update_projection(triangle->p.r, sphere);
+ roam_point_update_projection(triangle->p.l, sphere->view);
+ roam_point_update_projection(triangle->p.m, sphere->view);
+ roam_point_update_projection(triangle->p.r, sphere->view);
/* Not exactly correct, could be out on both sides (middle in) */
if (!roam_triangle_visible(triangle, sphere)) {
triangle->error = -1;
} else {
- roam_point_update_projection(triangle->split, sphere);
+ roam_point_update_projection(triangle->split, sphere->view);
RoamPoint *l = triangle->p.l;
RoamPoint *m = triangle->p.m;
RoamPoint *r = triangle->p.r;
list, all, n, s, e, w);
return list;
}
-void roam_sphere_free_tri(RoamTriangle *triangle)
+
+static void roam_sphere_free_tri(RoamTriangle *triangle)
{
if (--triangle->p.l->tris == 0) g_free(triangle->p.l);
if (--triangle->p.m->tris == 0) g_free(triangle->p.m);
* RoamPoint *
*************/
struct _RoamPoint {
- gdouble x,y,z; // Model coordinates
- gdouble px,py,pz; // Projected coordinates
- gint pversion; // Version of cached projection
+ /*< private >*/
+ gdouble x, y, z; /* Model coordinates */
+ gdouble px, py, pz; /* Projected coordinates */
+ gint pversion; /* Version of cached projection */
- gint tris; // Associated triangles
- gdouble norm[3]; // Vertex normal
+ gint tris; /* Count of associated triangles */
+ gdouble norm[3]; /* Vertex normal */
/* For get_intersect */
gdouble lat, lon, elev;
RoamHeightFunc height_func;
gpointer height_data;
};
-RoamPoint *roam_point_new(double x, double y, double z);
+RoamPoint *roam_point_new(double lat, double lon, double elev);
void roam_point_add_triangle(RoamPoint *point, RoamTriangle *triangle);
void roam_point_remove_triangle(RoamPoint *point, RoamTriangle *triangle);
void roam_point_update_height(RoamPoint *point);
-void roam_point_update_projection(RoamPoint *point, RoamSphere *sphere);
+void roam_point_update_projection(RoamPoint *point, RoamView *view);
/****************
* RoamTriangle *
****************/
struct _RoamTriangle {
+ /*< private >*/
+ /* Left, middle and right vertices */
struct { RoamPoint *l,*m,*r; } p;
+
+ /* Left, base, and right neighbor triangles */
struct { RoamTriangle *l,*b,*r; } t;
- RoamPoint *split;
- RoamDiamond *parent;
- double norm[3];
- double error;
+
+ RoamPoint *split; /* Split point */
+ RoamDiamond *parent; /* Parent diamond */
+ double norm[3]; /* Surface normal */
+ double error; /* Screen space error */
GPQueueHandle handle;
/* For get_intersect */
RoamTriangle *kids[2];
};
RoamTriangle *roam_triangle_new(RoamPoint *l, RoamPoint *m, RoamPoint *r);
+void roam_triangle_free(RoamTriangle *triangle);
void roam_triangle_add(RoamTriangle *triangle,
RoamTriangle *left, RoamTriangle *base, RoamTriangle *right,
RoamSphere *sphere);
void roam_triangle_remove(RoamTriangle *triangle, RoamSphere *sphere);
void roam_triangle_update_errors(RoamTriangle *triangle, RoamSphere *sphere);
void roam_triangle_split(RoamTriangle *triangle, RoamSphere *sphere);
+void roam_triangle_draw(RoamTriangle *triangle);
void roam_triangle_draw_normal(RoamTriangle *triangle);
/***************
* RoamDiamond *
***************/
struct _RoamDiamond {
- RoamTriangle *kids[4];
- RoamTriangle *parents[2];
- double error;
- gboolean active;
+ /*< private >*/
+ RoamTriangle *kids[4]; /* Child triangles */
+ RoamTriangle *parents[2]; /* Parent triangles */
+ double error; /* Screen space error */
+ gboolean active; /* For internal use */
GPQueueHandle handle;
};
RoamDiamond *roam_diamond_new(
* RoamSphere *
**************/
struct _RoamSphere {
- GPQueue *triangles;
- GPQueue *diamonds;
- RoamView *view;
- gint polys;
+ /*< private >*/
+ GPQueue *triangles; /* List of triangles */
+ GPQueue *diamonds; /* List of diamonds */
+ RoamView *view; /* Current projection */
+ gint polys; /* Polygon count */
/* For get_intersect */
- RoamTriangle *roots[8];
+ RoamTriangle *roots[8]; /* Original 8 triangles */
};
RoamSphere *roam_sphere_new();
void roam_sphere_update_view(RoamSphere *sphere);