Version 11.6.2 as of 16.November.2020
Copyright (c) 1995-2020 by Stefan Roettger
The author's contact address is:
stefan:at:stereofx.org
www.stereofx.org
http://stereofx.org/download
For compilation instructions see Section (B).
The latest development version of libMini is available via SVN (Subversion):
svn co http://svn.code.sf.net/p/libmini/code/libmini/mini miniThe original terrain rendering paper and the corresponding talk are available here:
http://stereofx.org/papers/TERRAIN.PDF
http://stereofx.org/papers/WSCG98.PPT
The ground fog rendering paper is available here:
http://stereofx.org/papers/PROJECTION.PDF
http://stereofx.org/papers/VG03.PPT
The vegetation rendering paper is available here:
http://stereofx.org/papers/VEGETATION.PDF
http://stereofx.org/papers/CGIM07.PPT
Additional conceptual papers about libMini are available here:
http://stereofx.org/download/libMini-Modules.pdf (libMini Module Overview)
http://stereofx.org/download/libMini-VolRen.pdf (libMini Volume Rendering Overview)
Back to top
README.html: | this file |
LICENSE.txt: | the libMini license |
libMini.css: | the README style sheet |
libMini.ppm/.jpg: | the libMini logo |
libMini.ico: | the libMini icon |
mini.cpp/.h: | contains the interface to the terrain rendering core |
minicore.cpp/P.h/.h: | contains the core of the terrain renderer |
minidefs.h: | contains basic definitions |
minibase.h: | contains basic declarations |
miniOGL.cpp/P.h/.h: | contains OpenGL rendering interface |
miniARB.cpp/.h: | contains ARB shader program interface |
mini_*.h: | contains group headers |
miniv??.cpp/.h: | contains vector class definitions |
minivec.h: | contains templated vector class definition |
minimtx.h: | contains templated matrix class definition |
minimath.cpp/.h: | contains mathematical definitions |
minicomplex.h: | contains header-only complex number definition |
minicrypt.cpp/.h: | contains cryptographic definitions |
glslmath.h: | contains header-only glsl math classes and operators |
glslmath.txt: | contains the README.txt for GLSLmath |
glvertex*.h: | contains header-only legacy OpenGL wrapper |
glvertex.txt: | contains the README.txt for glVertex |
mini3D.cpp/.h: | contains 3D software rendering pipeline |
miniwls.cpp/.h: | contains weighted least squares fit |
mininoise.cpp/.h: | contains perlin noise generator |
minimpfp.cpp/.h: | contains templated mp-fp definitions |
minidyna.h: | contains templated dynamic array |
minisort.h: | contains templated sorting method |
minikeyval.h: | contains templated key-value pairs |
minikdtree.h: | contains templated kd-tree |
ministring.h: | contains dynamic string methods |
minilog.cpp/.h: | contains logging methods |
miniref.h: | contains ref-counted item |
mininode.h: | contains ref-linked graph node |
mininodes.cpp/.h: | contains basic node definitions |
mininode_geom.h: | contains special geometry base class |
mininode_teapot.cpp/.h: | contains geometry node for the Utah teapot |
mininode_path.cpp/.h: | contains geometry node for geo-referenced paths |
mininode_clod.cpp/.h: | contains c-lod geometry node for geo-referenced paths |
minitime.cpp/.h: | contains system time abstraction |
minirgb.cpp/.h: | contains additional rgb/hsv conversion stuff |
minicrs.cpp/.h: | contains additional crs conversion stuff |
miniio.cpp/.h: | contains additional file io stuff |
minidir.cpp/.h: | contains additional directory listing stuff |
minidds.cpp/.h: | contains DDS compression stuff |
ministub.cpp/.h: | simplified stub class of the mini library |
minitile.cpp/.h: | wrapper class for tiled terrain rendering |
miniload.cpp/.h: | wrapper class for paged terrain rendering |
minicoord.cpp/.h: | container for geo-referenced coordinates |
minimeas.cpp/.h: | container for geo-referenced measurements |
minicurve.cpp/.h: | container for geo-referenced coordinate paths |
minipath.cpp/.h: | container for gps tracks |
miniclod.cpp/.h: | c-lod core class for geo-referenced paths |
minixml.cpp/.h: | container for xml data |
miniwarp.cpp/.h: | warp kernel for global coordinate systems |
minicam.cpp/.h: | camera coordinate system |
minilayer.cpp/.h: | wrapper class rendering a single layer |
miniterrain.cpp/.h: | wrapper class rendering multiple layers |
miniearth.cpp/.h: | wrapper class for rendering the earth |
miniscene.cpp/.h: | wrapper class for rendering the whole scene |
minicache.cpp/.h: | cache for speeding up tiled terrain rendering |
minishader.cpp/.h: | shader support for the render cache |
miniray.cpp/.h: | ray/triangle intersection code |
ministrip.cpp/.h: | vertex array container for triangle strips |
minipoint.cpp/.h: | simple point of interest renderer |
minitext.cpp/.h: | simple text renderer |
minisky.cpp/.h: | simple sky dome renderer |
miniglobe.cpp/.h: | night/day renderer of the earth or other textured globes |
minitree.cpp/.h: | contains algorithms for vegetation rendering |
minipano.cpp/.h: | contains panoramic addon for the minipoint renderer |
minibrick.cpp/.h: | contains algorithms for C-LOD volume rendering |
minilod.cpp/.h: | contains algorithms for S-LOD volume rendering |
minislicer.h: | contains algorithms for volume slicing |
minigeom.h: | contains geometric class templates |
minimesh.cpp/.h: | contains tetrahedral mesh class definition |
minibspt.cpp/.h: | contains BSP tree class definition |
miniproj.cpp/.h: | contains tetrahedral projection methods |
geoid.cpp/.h: | contains geoid height interpolation function |
wmm.cpp/.h: | contains magnetic declication interpolation function |
pnmbase.cpp/.h: | methods for handling PNM images |
pnmsample.cpp/.h: | methods for multi-resolution terrain resampling |
rawbase.cpp/.h: | methods for handling RAW volumes |
rekbase.cpp/.h: | methods for handling REK volumes (Fraunhofer EZRT) |
database.cpp/.h: | universal 1D/2D/3D/4D data buffer object |
datafill.cpp/.h: | generic 1D/2D/3D/4D fill-in algorithm |
datacalc.cpp/.h: | calculator for procedural images and implicit volumes |
dataparse.cpp/.h: | parser and interpreter of implicit functions |
datacloud.cpp/.h: | decouples terrain rendering from paging |
datacache.cpp/.h: | stores remote files in a local file cache |
datagrid.cpp/.h: | stores a grid of databuf objects |
example.png: | a screen shot of the example described below |
example.cpp: | the glut example (use "build.sh example" to compile) |
example.cmake/: | the cmake example (use "cmake . && make" to compile) |
stubtest.cpp: | the stub example (use "build.sh stubtest" to compile) |
viewer.cpp: | the libMini Viewer (use "build.sh viewer" to compile) |
miniview.cpp/.h: | the libMini Viewer base class |
threadbase.cpp/.h: | multi-threading support for the libMini Viewer |
curlbase.cpp/.h: | http protocol support for the libMini Viewer |
jpegbase.cpp/.h: | JPEG support for the libMini Viewer |
pngbase.cpp/.h: | PNG support for the libMini Viewer |
zlibbase.cpp/.h: | ZLIB support for the libMini Viewer |
squishbase.cpp/.h: | squish support for the libMini Viewer |
greycbase.cpp/.h: | CImg/GREYCstoration support for image denoising |
dataconv.cpp/.h: | image conversion support for external formats |
miniimg.cpp/.h: | image reading and writing support for image formats |
lunascan.cpp/.h: | text scanner for LUNA, an RPN-style computer language |
lunaparse.cpp/.h: | token parser for LUNA, an RPN-style computer language |
lunacode.cpp/.h: | code interpreter for LUNA, an RPN-style computer language |
lunafunctor.cpp/.h: | function evaluator based on LUNA |
qt_viewer.h: | Qt viewer base class |
data/SkyDome.ppm: | a sample sky dome texture (from Philo's Sky Collection) |
data/EarthDay.db: | a daylight earth texture (from NASA BlueMarble) |
data/EarthNight.db: | a night earth texture (from NASA BlueMarble) |
data/Cone.db: | a sample DB volume (implicitly defined cone) |
GL/glext.h: | backup copy of OpenGL extension header |
MiniMakefile: | Makefile for Irix, Linux and MacOS X |
build.sh: | the Unix build script |
build.bat: | the Windows build batch file |
autogen.sh: | the autogen script file |
configure.ac: | the autoconf definition file |
Makefile.am: | the automake definition file |
CMakeLists.txt: | the main cmake definition file |
libMini.cmake: | the libMini header and source list for cmake |
libMini-config.cmake: | the libMini build configuration for cmake |
libMini-app.cmake: | the libMini build configuration for simple applications |
libMini.pro: | the libMini build configuration for qmake |
sources*.pri: | the libMini source include list for qmake |
tools/*: | various command line tools |
tabify.sh: | a tool to clean up tab inconsistencies |
md5check.sh: | a tool to check the integrity of the distro |
.md5: | the md5 file list of the distro |
Additionally, the Yukon Ground Fog Demo, the Stuttgart Demo, the Hawaii Demo and the Fraenkische Schweiz Demo can be downloaded from here (please follow the usage instructions in the README):
http://stereofx.org/download/Yukon.zip
http://stereofx.org/download/Stuttgart.zip
http://stereofx.org/download/Hawaii.zip
http://stereofx.org/download/Fraenkische.zip
The libMini package also contains the libMini Viewer (see Section (P)). This application is a generic viewing utility for terrain data and tile sets. For example, it can be used to display the demo data mentioned above, load tilesets generated with the libGrid library or stream terrain data over the internet that has been produced with the Virtual Terrain Builder from vterrain.org.
Within this distribution the files needed to build the basic terrain rendering library are included. In order to keep the library portable any system dependent stuff like window management is not part of this distribution. Nevertheless, the Mini Library implements all the necessary graphics algorithms to setup a high-performance terrain rendering system.
The main goal for developing the library was to keep it as compact, stable and efficient as possible and not to blow it up by adding unnecessary features. Thus, Mini stands for "Mini Is Not Immense!" in a rather positive sense.
svn co http://svn.code.sf.net/p/libmini/code/libmini libminiThe build.sh shell script compiles the library on Irix, Linux and MacOS X. Simply type "./build.sh" in your Unix shell (requires the tcsh to be installed). OpenGL is required as the single dependency of the library. To install the library and the necessary include files in /usr/local on your Unix machine type "./build.sh install" as a super-user.
Optionally, you can use autoconf or CMake to build libMini. For the former, type "./configure && make" and for the latter type "cmake . && make" in a Unix shell.
The library also compiles on Windows by using CMake. For Windows it is recommended to check out the whole libMini package with Tortoise SVN from "http://svn.code.sf.net/p/libmini/code/libmini".
To compile, simply specify the "mini" directory as the source and binary directory in the CMake GUI and click the generator button. Then open the generated VC++ project and compile with Visual Studio. You can also run the build on the MSVC Command prompt via the "build.bat" batch file.
Alternatively, you can use cygwin in the following way:
As described in the following section, the distribution contains a libMini application example. The example can be compiled by typing "./build.sh example" in a shell. It requires GLUT to be installed. On Windows it is recommended to use the freeGLUT library as a replacement for the standard GLUT library. For convenience, the libMini distribution already contains the freeGLUT headers and the prebuilt static freeGLUT library.
The main include file is "mini.h" which contains the definition of the low level terrain rendering API (the libMini core). In order to port it to a different graphics architecture, only the file "miniOGL.cpp" needs to be adapted. It encapsulates all OpenGL calls that are made inside of the libMini core.
In the following the low level terrain rendering API is explained by drawing a small example height field. This task is broken down into eight basic steps. The full example code is included in the distribution (see "example.cpp").
#include <mini/mini.h>
// a small example height field short int hfield[]={0,0,0, 0,5,0, 0,0,0}; int size=3; // grid size of the square height field // (can be any number>2, but preferably 2^n+1, n>0) float dim=10.0f; // cell dimension = horizontal grid point spacing float scale=1.0f; // vertical scaling of the elevations void *map,*d2map; // spare void pointers map=mini::initmap(hfield,&d2map,&size,&dim,scale);
// a small example RGB texture map unsigned char texture[]={255,63,63, 255,63,63, 255,63,63, 255,63,63, 255,63,63, 63,63,255, 63,63,255, 255,63,63, 255,63,63, 63,63,255, 63,63,255, 255,63,63, 255,63,63, 255,63,63, 255,63,63, 255,63,63}; int width=4,height=4; // width and height of the texture map // (can be any number>1, but preferably 2^n, n>0) int texid; // id of the texture map int mipmaps=1; // enable mip-mapping texid=mini::inittexmap(texture,&width,&height,mipmaps);
// a small example ground fog map unsigned char ffield[]={2,3,2, 3,1,3, 2,3,2}; int fogsize=3; // size of the ground fog map // (can be any number>1, but same size as height field preferred) void *fogmap; // spare void pointer float lambda=1.0f; // vertical dimension of the ground fog layer float displace=5.0E-3f; // vertical displacement of the ground fog layer float emission=0.05f; // optical emission of ground fog per unit length float attenuation=1.0f; // triangulation importance of ground fog float fogR=1.0f,fogG=1.0f,fogB=1.0f; // fog color fogmap=mini::initfogmap(ffield,fogsize,lambda,displace,emission, attenuation,fogR,fogG,fogB);
mini::setmaps(map,d2map,size,dim,scale,texid,width,height[,mipmaps,cellaspect]);The optional mipmaps parameter determines whether or not mipmaps are enabled. The optional cellaspect parameter can be used to define a non-uniform spacing of the grid, that is the spacing along the x-axis is dim units while the spacing along the z-axis is dim*cellaspect units.
If a ground fog map is used the following parameters need to be passed:
mini::setmaps(map,d2map,size,dim,scale, texid,width,height,mipmaps, cellaspect,0.0f,0.0f,0.0f,NULL,NULL, fogmap,lambda,displace, emission);
float res=1000.0f; // global resolution of the triangulation (in the range [1..infty]) float ex=0.0f,ey=10.0f,ez=30.0f; // eye point float fx=0.0f,fy=10.0f,fz=30.0f; // focus of interest (should be equal to eye point) float dx=0.0f,dy=-0.25f,dz=-1.0f; // view direction float ux=0.0f,uy=1.0f,uz=0.0f; // up vector float fovy=60.0f; // vertical field of view float aspect=1.0f; // window width/window height float nearp=1.0f; // distance of near clipping plane float farp=100.0f; // distance of far clipping plane mini::drawlandscape(res, ex,ey,ez, fx,fy,fz, dx,dy,dz, ux,uy,uz, fovy,aspect, nearp,farp);For typical height fields the parameter res should be set to 100-10000 depending on the desired density of the generated mesh.
float height=mini::getheight(i,j); // (i,j) = integer grid position float height=mini::getheight(x,z); // (x,z) = floating point world coordinates float nx,ny,nz; mini::getnormal(x,z,&nx,&ny,&nz);Similarly, the height of the ground fog layer (if present) can be queried via:
float fogheight=mini::getfogheight(x,z);
mini::deletemaps();
The accuracy of the rendered terrain can be controlled by setting the res parameter from the minimum value of 1 to larger values of say 1,000-100,000 depending on the actual performance of the graphics hardware.
Normally, the focus of interest, that is the point with the highest resolution, should be equal to the eye point in order to minimize the screen space error of the dynamic triangulation. In some cases, however, it might be advantageous to set the focus to a different location.
Instead of choosing the default short signed integer representation (16 bit) of the height field, a floating point representation can be chosen by using the Mini namespace (capital first letter). This is equivalent to calling the constructor of the ministub class with a float height field as the first parameter.
Since the algorithm uses uniform grids of size 2^n+1 in both dimensions, a height field with this size should be supplied whenever possible. Other sizes are scaled up internally to the next possible size. A data set with unequal grid dimensions must be resampled uniformly or broken up into uniform tiles prior to passing it to the Mini Library.
for (int i=0; i<tiles; i++) { map[i]=mini::initmap(hfield[i],&d2map[i],&size[i],&dim[i],scale); texid[i]=mini::inittexmap(texture[i],&width[i],&height[i]); }Now, for each frame, the terrain is rendered in two passes:
for (phase=1; phase<=2; phase++) for (int i=0; i<tiles; i++) { mini::setmaps(map[i],d2map[i],size[i],dim[i],scale, texid[i],width[i],height[i],mipmaps, cellaspect,ox[i],oy[i],oz[i], d2map2[i],size2[i]); mini::drawlandscape(res, ex,ey,ez,fx,fy,fz,dx,dy,dz,ux,uy,uz, fovy,aspect,nearp,farp, NULL,NULL,phase); }Here, the additional parameters ox[i], oy[i], and oz[i] specify the origin (center) of each tile. The remaining additional parameters d2map2[i][0..3] and size2[i][0..3] denote the d2maps of the four adjacent tiles and the grid size of the neighbours, respectively. The neighbours must be specified for each tile to ensure crack-free rendering of the entire scene. Viewed from above, the indices 0..3 correspond to the following locations of the adjacent tiles: left, right, bottom, and top. If a neighbour does not exist, the NULL pointer may be passed instead. In order to ensure a conforming mesh, the elevations of shared grid points of adjacent tiles must be identical, meaning that the shared edges need to be duplicated. After the last frame, the allocated memory is released by subsequently passing each tile map[i] to the function mini::setmaps() and by calling mini::deletemaps() afterwards.
As an example, the center tile of a height field can be surrounded by tiles with a lower resolution. These low resolution tiles can be used to represent the horizon of a scenery without consuming a large amount of extra memory.
Preferably, each terrain data set should be broken up into tiles that fit into the L2-cache of the processor. For example a tile of size 129x129 easily fits into the L2-cache of most modern processors. In this setup each tile consumes only 48 kilobytes of memory which leads to a significantly improved cache coherency and performance.
Besides rendering a tiled terrain, the minitile class is also a convenient way to just display a single height field plus a texture without going into the details of the low level API as described in Section (C). For an example use case of the minitile class please check out the Yukon Demo or the Stuttgart Demo.
If the landscape that should be visualized is extremely large and detailed the terrain may not fit entirely into main memory. This situation is dealt with the miniload class. It offers almost the same functionality as the minitile class but dynamically pages visible and invisible tiles in and out. There are two paging modes: The first one just loads all visible tiles and displays all data up to the far clipping plane. For this to work efficiently, the distance to the far plane should be chosen to be considerably smaller than the actual extent of the entire scene. The second paging mode loads the appropriate LOD for each tile if a resolution pyramid is present. This reduces the memory foot print drastically but may also increase the latency during a movement of the viewer, since all the visible LODs need to be updated dynamically. The second mode allows much larger viewing distances as the first mode. Depending on the tile size this comes at the expense of some latency whenever a different LOD needs to be loaded from disk. To hide the latency the so-called preloading can be enabled so that the requested LODs are already available before they actually need to be rendered.
By default, the tiles are stored in the PNM (PGM/PPM) format of the netpbm library which is available at netpbm.sourceforge.net. The netpbm library, however, is not required for linking, since libMini has built-in support for the PNM format (see Section Appendix (A)).
The LODs are identified by adding the number of the corresponding LOD to the base file name which has LOD 0 by definition. As an example, let the tile with file name "tile.x-y.pgm" be the base LOD at column x and row y of the grid. Then the next corresponding LOD with level 1 is named "tile.x-y.pgm1" and so forth. If the base LOD (or LOD0) has size 2^n+1 the LODs with level l=1..n-1 (or LOD1, LOD2, ...) have size 2^(n-l)+1. Let the texture with file name "tile.x-y.ppm" be the base texture at column x and row y of the grid. Then the next corresponding texture LOD with level 1 is named "tile.x-y.ppm1" and so forth. If the base texture has size 2^m the texture LODs with level l=1..m-1 have size 2^(m-l). For different tiles the base LOD may have different tile or texture size. While one or more levels from the top of each LOD pyramid may be missing the base LOD has to be present in any case. Both the height fields and the texture maps use a corner centric (not cell centric) data representation.
A basic grid resampler which is able to produce the required pyramids is available via the pnmsample::resample(...,int pyramid) call. The pyramid parameter controls the number of generated LODs in addition to the base level. If the described file conventions are met the output of any GIS program can be used, too. In fact, the latter should be the preferred way of resampling, since the built-in resampler has several limitations and is intended to be only a minimal "reference" implementation. Among its limitations is the restriction to Lat/Lon coordinates as world coordinate system and the missing out-of-core support. The addition of these features would have blown up libMini significantly, so if one of these features is needed in a particular situation the tiles should be resampled with a more advanced GIS application such as libGrid or VTBuilder from vterrain.org. The output of VTBuilder is compatible with libMini so you are free to import the data into your own libMini project or visualize it directly with the VTEnviro application which also builds on top of libMini. However, if the restrictions of the built-in resampler are not crucial, you can call the resampler with a list of georeferenced PNM files, which will be resampled within the range of the first file on the list (for information about georeferencing see Section (H).
To load a tiled terrain consisting of c columns and r rows one needs to construct two string pointer arrays hf and tx containing the file names of the PGM height fields and the ppm textures of each tile (column first order, north-west corner first, missing tiles indicated by null pointers). Let cd be the width of each column and rd be the height of each row and let s be the vertical scaling of the elevations and let (cx,cy,cz) be the offset of the center of the entire terrain (all constants measured in meters). Then the following call does the job with the terrain lying in the (x,-z) plane and the elevations corresponding to the y-coordinates:
miniload *terrain=new miniload(hf,tx,c,r,cd,rd,s,cx,cy,cz);In order to load tiles that have been generated with the built-in resampler, we simply pass the number of columns and rows and the directory where the tiles have been stored to the miniload:load method. Then the missing parameters are determined automatically by looking at the georeferencing information of the specified tiles.
To render the scene use the following call:
terrain->draw(res, // resolution ex,ey,ez, // eye point dx,dy,dz, // view direction ux,uy,uz, // up vector fovy,aspect, // field of view and aspect nearp,farp, // near and far plane update); // optional incremental updateThe library will now load all visible tiles and page in and out the appropriate LODs automatically.
By default, the tiles are rendered directly using the built-in OpenGL graphics engine. Alternatively, a render cache (e.g. the minicache described in Section (I)) can be attached to the miniload class. Then the tiles are displayed indirectly by rendering the contents of the cache. However, we do not attach the cache to the miniload object but rather to the minitile object encapsulated in it (use terrain->getminitile() to get it).
The update parameter determines the number of frames for which the cache persists and how long it takes to completely fill the cache. A value of 1 causes the cache to be filled within a single frame and can be used to flush the cache.
Non-standard graphics effects can be implemented by using the hook mechanism or the shader plugins of the minicache backend (see Section (J)).
During run-time, the terrain can be rescaled in the range from 0% to 100% of the original elevation by calling miniload::setrelscale.
Also, the sea surface can be rendered at real-time. In order to interactively extract a specific sea level (e.g. 0) we use miniload::setsealevel(level).
For implementation reasons, this is only supported if a render cache is attached. The contour line of the sea surface is extracted precisely so that it matches the shore line and therefore does not intrude into the terrain. This approach efficiently eliminates Z-buffer fighting artifacts.
By default, texture compression is enabled which means that the textures will be compressed on-the-fly in the OpenGL driver. Since this is a very time consuming task it can be turned off via miniOGL::configure_compression(0). Texture mip-mapping is also enabled by default, but it can be switched off via minitile::configure_mipmaps(0). This further improves the texture loading performance.
The paging mechanism can be controlled via the following call:
terrain->setloader(void (*request)(...),void *data, void (*preload)(...), void (*deliver)(...), int paging, float pfarp, float prange,int pbasesize, int plazyness,int pupdate, int expire);Normally, the first four arguments should be set to NULL. Then each tile is loaded if it is within the viewing range (that is the distance to the far clipping plane farp). In order to ensure that invisible tiles are already available before they actually become visible preloading can be enabled by passing a function pointer as third argument. The referenced function is called subsequently to notify all tiles which need to be preloaded. However, if preloading is disabled, visible tiles are just requested on the fly. This is recommended if the data is available on a fast medium (e.g. a hard disk).
Each requested tile is loaded either automatically by the library or manually via the callback mechanism. The callback passes height fields, texture maps and optional fog maps by encapsulating them into a databuf object. The object format is flexible and can be used for byte, short int, float and even pre-compressed texture data with an optional alpha channel (as opposed to the PNM format which only supports plain RGB images and only 8- or 16-bit height fields). The databuf class has methods to load and store data in its native DB file format (see Section Appendix (C)). The extension for the native format is ".db". The databuf class also has convenience functions for reading PNM images and PVM volumes which are the standard format for the minibrick module (see Section Appendix (4)). Pre-compressed texture maps are preferred over uncompressed textures, because loading is much faster. This is due to the fact that neither the texture data has to be compressed nor a mipmap pyramid has to be generated on-the-fly. Currently, only S3TC/DXT1 texture pre-compression is supported.
For slow media or internet access preloading should be enabled so that each call of the preload callback can be used to spawn a thread which silently receives and stores the incoming data until it is collected by the deliver callback. While requested data should be returned instantly the delivery of preloaded data can be delayed until an arbitrary point in the future. The Mini Library has a reference implementation of an asynchronous file cache. With this cache the rendering task can be decoupled from the loading task which leads to a much smoother visual experience for large paged data sets. In such an ambitious use case, please also read Sections (K) and (L).
If a resolution pyramid is present, the library also tries to page in the appropriate LOD l from the pyramid. If preloading is enabled, the library requests level l as usual but also tries to silently preload level l+1 so that the next level is delivered before it actually becomes visible.
The other arguments of miniload::setloader have the following meaning:
If the internal OpenGL state management of the Mini Library is not needed, one can access the library through the ministub class as shown in the code example given below. It demonstrates the external handling of the graphics state using explicit calls to OpenGL. If a different graphics library should handle the graphics state we can use "build.sh stub" to compile a library that does not contain any references to OpenGL specific functions (use the switch -DNOOGL on Windows). Otherwise the library must be linked against "-lGL -lGLU -lm" to resolve the OpenGL dependencies.
#include <mini/ministub.h> // height field is a float array float hfield[]={0,0,0,0,0, 0,3,3,3,0, 0,3,5,3,0, 0,3,3,3,0, 0,0,0,0,0}; int size=5; // grid size float dim=5.0f; // cell dimension float scale=1.0f; // vertical scaling float cellaspect=1.0f; // cell aspect ratio float cx=0.0f,cy=0.0f,cz=0.0f; // grid center ministub *stub; int myfancnt; void mybeginfan() { // mandatory "beginfan" callback // called for each generated triangle fan // followed by the vertex callbacks if (myfancnt++>0) glEnd(); glBegin(GL_TRIANGLE_FAN); } void myfanvertex(float i,float y,float j) { // mandatory "fanvertex" callback // called for each vertex of a triangle fan // glVertex3f directly qualifies as a fast "fanvertex" callback // (i,j) is the grid coordinate of the vertex // y is the unscaled elevation interpolated from the height field // these coordinates are transformed by the OpenGL modelview matrix // therefore, the real world coordinates of each vertex are // (vx,vy,vz)=((i-size/2)*dim+cx,y*scale+cy,(size/2-j)*dim+cz) glVertex3f(i,y,j); } void mynotify(int i,int j,int s) { // optional "notify" callback // triggered during quadtree traversal // called for each visible node of the quadtree // to disable the callback pass the NULL pointer to the ministub // (i,j) is the center of the actual node in grid coordinates // s is the size of the actual node in grid units // only add extra code here if you know what you are doing // ... } float mygetelevation(int i,int j,int S,void *data=NULL) { // optional "getelevation" callback // if image=NULL is passed to the ministub constructor // this callback is evaluated separately for each grid point // use this for the sequential access of a height field // e.g. for memory efficient reading from an input stream // as a reference to the calling object an optional // data pointer can be passed to the callback // return the elevation at grid position (i,j) here return(hfield[i+j*S]); // the size of the grid must be equal to SxS } int main(int argc,char *argv[]) { stub=new ministub(hfield, &size,&dim,scale, cellaspect,cx,cy,cz, mybeginfan,myfanvertex, mynotify, mygetelevation, NULL); float res=1000.0f; // resolution float ex=0.0f,ey=10.0f,ez=30.0f; // eye point float dx=0.0f,dy=-0.25f,dz=-1.0f; // view direction float ux=0.0f,uy=1.0f,uz=0.0f; // up vector float fovy=60.0f; // field of view float aspect=1.0f; // aspect of view float nearp=1.0f; // near plane float farp=100.0f; // far plane // open window and create OpenGL context here // ... // change OpenGL state here // (for example, setup automatic texture coordinate generation) // ... // setup OpenGL modelview matrix glScalef(dim,scale,-dim); // scale vertices glTranslatef(-size/2+cx,cy,-size/2+cz); // translate vertices myfancnt=0; stub->draw(res, ex,ey,ez, dx,dy,dz, ux,uy,uz, fovy,aspect, nearp,farp); glEnd(); // delete OpenGL context and close window here // ... delete stub; return(0); }Since the Mini Library optionally supports ground fog rendering, the fog mesh which consists of vertically aligned prisms have to be passed to the calling framework as well. Three subsequent calls of the "prismedge" callback define one fog prism by describing the ground position (x,y,z) and the vertical size (yf) of the three vertical prism edges. The edges are already transformed into the world coordinate system.
A test version above code can be compiled by first stripping the Mini Library off its OpenGL dependent calls (type "build.sh stub"). Then the stub test is compiled with the command "build.sh stubtest".
For comparison, the text output of the stub test is:
beginfan(); fanvertex(1,1,1); // realvertex=(0,5,0) fanvertex(2,0,1); // realvertex=(10,0,0) fanvertex(2,0,2); // realvertex=(10,0,-10) prismedge(0,5.005,6.005,-0); prismedge(10,0.005,3.005,-0); prismedge(10,0.005,2.005,-10); fanvertex(1,0,2); // realvertex=(0,0,-10) prismedge(0,5.005,6.005,-0); prismedge(10,0.005,2.005,-10); prismedge(0,0.005,3.005,-10); fanvertex(0,0,2); // realvertex=(-10,0,-10) prismedge(0,5.005,6.005,-0); prismedge(0,0.005,3.005,-10); prismedge(-10,0.005,2.005,-10); fanvertex(0,0,1); // realvertex=(-10,0,0) prismedge(0,5.005,6.005,-0); prismedge(-10,0.005,2.005,-10); prismedge(-10,0.005,3.005,-0); fanvertex(0,0,0); // realvertex=(-10,0,10) prismedge(0,5.005,6.005,-0); prismedge(-10,0.005,3.005,-0); prismedge(-10,0.005,2.005,10); fanvertex(1,0,0); // realvertex=(0,0,10) prismedge(0,5.005,6.005,-0); prismedge(-10,0.005,2.005,10); prismedge(0,0.005,3.005,10); fanvertex(2,0,0); // realvertex=(10,0,10) prismedge(0,5.005,6.005,-0); prismedge(0,0.005,3.005,10); prismedge(10,0.005,2.005,10); fanvertex(2,0,1); // realvertex=(10,0,0) prismedge(0,5.005,6.005,-0); prismedge(10,0.005,2.005,10); prismedge(10,0.005,3.005,-0);Back to top
The Global Land Cover Facility
glcf.umiacs.umd.edu
Free sky dome textures can be downloaded at
Philo's Sky Collection
www.philohome.com/skycollec/skycollec.htm
In order to load a real height field or texture use the PNM reader via
#include <mini/pnmbase.h> unsigned char *data; int width,height,components; data=readPNMfile(pnmfilename,&width,&height,&components);If components==1 the function returns an unsigned char height field
If the PNM image contains an 8- or 16-bit height field we first copy it to a short array. Then we can pass this array to the libMini core or the ministub class for example:
if (width!=height) ERRORMSG(); // height field must be quadratic short int *hfield=new short int[width*height]; if (components==1) // 8-bit for (int j=0; j<height; j++) for (int i=0; i<width; i++) hfield[i+j*width]=data[i+(height-1-j)*width]; else if (components==2) // 16-bit for (int j=0; j<height; j++) for (int i=0; i<width; i++) hfield[i+j*width]=(short int)(256*data[2*(i+(height-1-j)*width)]+data[2*(i+(height-1-j)*width)+1]); else ERRORMSG(); free(data); ministub stub=new ministub(hfield,...); delete[] hfield;Alternatively, we can pass the array via the getelevation callback which prevents the array from being copied twice:
short int mygetelevation(int i,int j,int S) { if (components==1) return(data[i+(S-1-j)*S]); else if (components==2) return((short int)(256*data[2*(i+(S-1-j)*S)]+data[2*(i+(S-1-j)*S)+1])); return(0); } ministub stub=new ministub(NULL,...,mygetelevation,...); free(data);The same callback mechanism is implemented in the libMini core.
In order to georeference a PNM image, we have to put its geographic location into the comment of the PNM header. This is achieved by specifying the four corners of the image in either the geographic world coordinate system (also known as Lat/Lon) or in Universal Transverse Mercator coordinates (UTM). The built-in resampler of the library exclusively uses this extended PNM format (for more details see Appendix (A)). An example of a georeferenced header is shown below:
P6 # BOX # description=PPM example # coordinate system=LL # coordinate zone=0 # coordinate datum=0 # SW corner=198721.93993200/-75123.60940800 arc-seconds # NW corner=198722.01794400/-75081.99117600 arc-seconds # NE corner=198766.29376800/-75082.06288800 arc-seconds # SE corner=198766.21917600/-75123.68115600 arc-seconds # cell size=.086482/.086482 arc-seconds # vertical scaling=0 meters # missing value=-9999 512 512 255The identifier "P6" stands for an RGB image and the numbers at the end of the header define the width, the height and the maximum pixel value of the image. For 8-bit data the maximum value is 255, for signed 16-bit data it is 32767 (or 65535). The identifier "P5" stands for height fields (and gray scale images). The raw data of an image is appended after the header. 16-bit data is stored in MSB format.
Principally, the Mini Library generates a new triangle mesh for each frame. This is necessary to suppress the popping effect by applying the geomorphing technique. As a consequence, the dynamically generated mesh prohibits the use of high performance rendering primitives such as vertex arrays or vertex buffer objects, because there is virtually no frame to frame coherency of the vertex data.
However, we do not need to perform the geomorphing operation for each and every frame. Usually 5-10 morphing operations per second appear to be visually smooth to a human observer. If the terrain is rendered with 50 frames per second then we can cache the generated vertices for at least 5 consecutive frames.
This dramatically reduces the CPU load, since the triangle mesh can be updated over consecutive frames. For this to work, a tiled terrain needs to be used, so that the mesh update can be triggered tile after tile. The GPU load is also reduced dramatically, since the cache can be rendered in an optimized fashion. The minicache uses vertex arrays for this purpose.
To enable the minicache we simply pass the minitile object to be cached to the minicache:
minitile *tileset=new minitile(hfields,textures,cols,rows,...); minicache *cache=new minicache; cache->attach(tileset);Then, for each frame, we trigger a partial scene update with:
int update=5; // number of frames per update tileset->draw(...,update); // nothing is rendered yet cache->rendercache(); // render the cached vertex bufferThis is illustrated in the Hawaii Demo and in the libMini Viewer (see Section (P)).
The raw performance on a Linux box with an AMD Athlon 2.2 GHz CPU and an NVIDIA GeForce FX 5800 graphics accelerator is about 20 million geomorphed vertices per second.
The default vertex shader multiplies the incoming vertices with the combined modelview and projection matrix and computes the appropriate 2D texture coordinates for each tile. It is selected via minicache::setshader() and enabled via minicache::useshader(). Own vertex shaders are selected by passing a program string via minicache::setshader("!!ARBvp...").
// default vertex shader static const char *vtxprog="!!ARBvp1.0 \n\ PARAM t=program.env[0]; \n\ PARAM e=program.env[1]; \n\ PARAM u=program.env[2]; \n\ PARAM v=program.env[3]; \n\ PARAM d=program.env[4]; \n\ PARAM c0=program.env[5]; \n\ PARAM c1=program.env[6]; \n\ PARAM c2=program.env[7]; \n\ PARAM c3=program.env[8]; \n\ PARAM c4=program.env[9]; \n\ PARAM c5=program.env[10]; \n\ PARAM c6=program.env[11]; \n\ PARAM c7=program.env[12]; \n\ PARAM mat[4]={state.matrix.mvp}; \n\ PARAM matrix[4]={state.matrix.modelview}; \n\ PARAM invtra[4]={state.matrix.modelview.invtrans}; \n\ TEMP vtx,col,nrm,pos,vec,gen; \n\ ### fetch actual vertex \n\ MOV vtx,vertex.position; \n\ MOV col,vertex.color; \n\ MOV nrm,vertex.normal; \n\ ### transform vertex with combined modelview \n\ DP4 pos.x,mat[0],vtx; \n\ DP4 pos.y,mat[1],vtx; \n\ DP4 pos.z,mat[2],vtx; \n\ DP4 pos.w,mat[3],vtx; \n\ ### transform normal with inverse transpose \n\ DP4 vec.x,invtra[0],nrm; \n\ DP4 vec.y,invtra[1],nrm; \n\ DP4 vec.z,invtra[2],nrm; \n\ DP4 vec.w,invtra[3],nrm; \n\ ### write resulting vertex \n\ MOV result.position,pos; \n\ MOV result.color,col; \n\ ### calculate tex coords \n\ MAD result.texcoord[0].x,vtx.x,t.x,t.z; \n\ MAD result.texcoord[0].y,vtx.z,t.y,t.w; \n\ MUL result.texcoord[0].z,vtx.y,e.y; \n\ ### pass normal as tex coords \n\ MOV result.texcoord[1],vec; \n\ ### calculate eye linear coordinates \n\ DP4 pos.x,matrix[0],vtx; \n\ DP4 pos.y,matrix[1],vtx; \n\ DP4 pos.z,matrix[2],vtx; \n\ DP4 pos.w,matrix[3],vtx; \n\ DP4 gen.x,pos,u; \n\ DP4 gen.y,pos,v; \n\ MAD result.texcoord[2].x,gen.x,d.x,d.y; \n\ MAD result.texcoord[2].y,gen.y,d.z,d.w; \n\ ### calculate spherical fog coord \n\ DP3 result.fogcoord.x,pos,pos; \n\ END \n";The parameter t holds bias and scaling constants to compute the 2D texture coordinates in the x- and y-component of the result texture coordinate vector. The parameter e holds the scaling factor of the incoming elevations to compute the current true elevation. These true elevation values are passed to the pixel shader in the z-component of the texture coordinate vector so that per-fragment computations can easily depend on elevation. The parameters u,v and d are used for eye linear texture coordinate generation with texture unit #2. The parameter vectors t, e and d are supplied automatically by the minicache, but the parameter vectors u/v and c0-c7 may hold user-definable constants that can be supplied via minicache::setvtxshadertexgen(tileset,s1..t4) and minicache::setvtxshaderparams(x,y,z,w[,n]), respectively.
The default pixel shader takes the actual 2D texture coordinates and fetches the corresponding color from texture #0 which holds the current texture tile. After that the texture color is shaded and multiplied with the current fragment color to mimic the standard modulating texture environment.
// default pixel shader static const char *frgprog="!!ARBfp1.0 \n\ PARAM a=program.env[0]; \n\ PARAM t=program.env[1]; \n\ PARAM l=program.env[2]; \n\ PARAM p=program.env[3]; \n\ PARAM o=program.env[4]; \n\ PARAM c0=program.env[5]; \n\ PARAM c1=program.env[6]; \n\ PARAM c2=program.env[7]; \n\ PARAM c3=program.env[8]; \n\ PARAM c4=program.env[9]; \n\ PARAM c5=program.env[10]; \n\ PARAM c6=program.env[11]; \n\ PARAM c7=program.env[12]; \n\ TEMP col,colt,nrm,len; \n\ ### fetch fragment color \n\ MOV col,fragment.color; \n\ ### fetch texture color \n\ TEX colt,fragment.texcoord[0],texture[0],2D; \n\ MAD colt,colt,a.x,a.y; \n\ ### modulate with fragment color \n\ MUL col,col,colt; \n\ ### modulate with directional light \n\ MOV nrm,fragment.texcoord[1]; \n\ DP3 len.x,nrm,nrm; \n\ RSQ len.x,len.x; \n\ MUL nrm,nrm,len.x; \n\ DP3_SAT nrm.z,nrm,l; \n\ MAD nrm.z,nrm.z,p.x,p.y; \n\ MUL_SAT col.xyz,col,nrm.z; \n\ ### write resulting color \n\ MOV result.color,col; \n\ END \n";As with vertex shaders, the parameter vectors c0-c7 may hold four additional user-specific constants that can be set via minicache::setpixshaderparams(x,y,z,w[,n]).
Here is a simple usage example which adds contour lines to the bathymetry of a data set, which means that only negative elevations will show contours:
// declare the cache minicache cache; // enable default vertex shader plugin cache.setvtxshader(); cache.usevtxshader(); // fragment program for adding contour lines to the bathymetry static const char *frgprog="!!ARBfp1.0 \n\ PARAM a=program.env[0]; \n\ PARAM c0=program.env[5]; \n\ TEMP col,colt,vtx; \n\ MOV col,fragment.color; \n\ TEX colt,fragment.texcoord[0],texture[0],2D; \n\ MAD colt,colt,a.x,a.y; \n\ MUL col,col,colt; \n\ MUL vtx.x,fragment.texcoord[0].z,c0.x; \n\ FRC vtx.y,vtx.x; \n\ MAD vtx.y,vtx.y,c0.z,-c0.w; \n\ ABS vtx.y,vtx.y; \n\ SUB vtx.y,c0.w,vtx.y; \n\ MUL_SAT vtx.y,vtx.y,c0.y; \n\ CMP vtx.y,vtx.x,vtx.y,c0.w; \n\ MUL col.xyz,col,vtx.y; \n\ MOV result.color,col; \n\ END \n"; // enable pixel shader plugin cache.setpixshader(frgprog); cache.setpixshaderparams(contourspacing,contourwidth,2.0f,1.0f); cache.usepixshader(); // render actual content of the cache cache.render(...); // disable programs cache.usevtxshader(0); cache.usepixshader(0);The example is part of the Hawaii Demo (see Section (I)), so you can actually watch the shaders working together by pressing 'c' during the demo.
Another usage example is per-fragment lighting: Let us first assume that the RGB texture contains the horizontal components x and z of the normal vector mapped to the R and G channels. Let us also assume that the B channel contains a gray scale image. Then the vertical component y of the normal vector can be computed from the horizontal components using y=sqrt(1-x*x-z*z). For diffuse shading we supply a light direction in the shader parameters and compute the dot product of the light direction with the normal vector. Then we multiply this with the B channel to get a shaded gray value. The elevations provided in the z-component of the texture coordinate vector may be additionally used to derive a color mapping which modulates the shaded gray values giving a final shaded color. The advantage of using a pixel shader for the calculation of the lighting equations is that the light conditions can be changed interactively.
In order to view this data set in real-time we resampled it to a 100x80 tile set. This tile set is visualized out-of-core using the described libMini paging callback concept. Whenever a tile needs to be paged into memory, the callback is triggered and the corresponding tile is loaded. However, while loading the requested data most of the time is wasted with busy waiting for the hard disk to seek and spin to the correct file position. This can take up 150ms even for the tiniest files. Since we cannot continue rendering while we wait for the data to arrive the frame rate usually drops down to a mere 5-10 fps.
Therefore, we need to decouple the disk access from rendering in order to get a smooth rendering experience. For this purpose, the Mini Library contains an asynchronous tile cache which loads the requested tiles in a background thread without blocking the main rendering thread.
We first assume that the tile set is defined via a miniload object. The tile set should contain S3TC compressed textures for best paging performance or uncompressed textures for best image quality:
miniload *tileset=new miniload;Then we enable the asynchronous paging mechanism (with a single background thread):
#include <mini/datacloud.h> static const int numthreads=1; datacloud *cloud=new datacloud(tileset); cloud->setloader(request_callback,NULL,check_callback,1,1.25f*farp,0.01f*farp,pbasesize,1,10,1000); cloud->getterrain()->setradius(0.03f*farp,1.0f); // optional non-linear texture LOD drop-off distance cloud->setinquiry(inquiry_callback,NULL); // optional callback for better paging performance cloud->setquery(query_callback,NULL); // optional callback for better paging performance cloud->setschedule(0.02,0.5,1.0); // upload for 20ms, keep for 30sec, invalidate after 1sec cloud->setmaxsize(128.0); // allow 128 MB tile cache size cloud->setthread(startthread,NULL,jointhread,lock_cs,unlock_cs,lock_io,unlock_io); cloud->setmulti(numthreads); threadinit();We additionally need to define two mandatory and two optional callbacks (one for loading data, one for checking file existence, one for optionally checking the elevation range of a height field and one for optionally querying the image size of a texture map):
void request_callback(const unsigned char *mapfile,databuf *map,int istexture,int background,void *data) {map->loaddata((char *)mapfile);} int check_callback(const unsigned char *mapfile,int istexture,void *data) {return(checkfile((const char *)mapfile));} void inquiry_callback(int col,int row,const unsigned char *mapfile,int hlod,void *data,float *minvalue,float *maxvalue) { *minvalue=0.0f; *maxvalue=10000.0f; return(1); } void query_callback(int col,int row,const unsigned char *texfile,int tlod,void *data,int *tsizex,int *tsizey) { int tbasesize=2048; // size of texture LOD 0 while (tlod-->0) tbasesize/=2; *tsizex=*tsizey=tbasesize; }We finally have to define the callbacks for creating and locking the background thread. In the following example implementation we are using POSIX threads (pthreads), but any other multi-threading library like OpenThreads could be used as well:
#include <pthread.h> #include <mini/datacloud.h> pthread_t pthread[numthreads]; pthread_mutex_t mutex,iomutex; pthread_attr_t attr; void threadinit() { pthread_mutex_init(&mutex,NULL); pthread_mutex_init(&iomutex,NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); } void threadexit() { pthread_mutex_destroy(&mutex); pthread_mutex_destroy(&iomutex); pthread_attr_destroy(&attr); } void startthread(void *(*thread)(void *background),backarrayelem *background,void *data) {pthread_create(&pthread[background->background-1],&attr,thread,background);} void jointhread(backarrayelem *background,void *data) { void *status; pthread_join(pthread[background->background-1],&status); } void lock_cs(void *data) {pthread_mutex_lock(&mutex);} void unlock_cs(void *data) {pthread_mutex_unlock(&mutex);} void lock_io(void *data) {pthread_mutex_lock(&iomutex);} void unlock_io(void *data) {pthread_mutex_unlock(&iomutex);}The loaddata, loadPNMdata and loadPVMdata methods of a databuf object are reentrant. In principle these methods can be used safely to load data in the background thread. However, they rely on the file IO functions of the operating system (fopen, fread, fwrite, fclose, fscanf and fprintf of the stdlibc++) to be thread-safe as well. This is usually the case but it is not guaranteed for all operating systems. As a safety measurement, libMini uses the lock_io and unlock_io functions to lock the request callback which is performing the IO in the background thread. If it is known in advance that the entire request callback is thread-safe, the two functions may be omitted in the setthread call.
Similarly, the loadPPMcompressed method is not reentrant because it is using OpenGL to compress the incoming data on-the-fly. If you want to pass S3TC compressed textures in the background thread you need to store pre-compressed data on the hard disk and load this data with a standard loaddata call. This approach saves a lot of disk space and is much faster than compressing the data on-the-fly.
In order to start multiple background threads we simply set numthreads to a higher value (e.g. 10). Typically, this is not needed if data is stored on a fast hard disk, but it has a performance advantage if the data is arriving over a slow network connection.
Adding these lines to the code will lead to a very smooth out-of-core visualization experience even for the mentioned 70 GB data set of Oahu. To give some performance details, the demo is running at a consistent 25 fps on my Apple Powerbook Pro with 1.5 GB of main memory, 1.83GHz Core Duo and ATI X1600 with 128MB VRAM.
An application that is using the asynchronous tile cache should update the scene each time the view point is changed. Additionally, it should update the scene until there are no pending tiles left to be paged in. This can be achieved in the following way:
static int pending=1; if (pending!=0) tileset->draw(...); pending=cloud->getpending();If a render cache is attached to the tile set the cache should be flushed after all pending tiles have been processed. As an alternative, the application could just render continuously with a given target frame rate. This obviously ensures that all arriving tiles will be displayed eventually.
Information about the actual streaming status can be printed by the following example code snippet:
printf("streaming: pending=%d mem=%gMB\n", cloud->getpending(), // total number of pending tiles cloud->getmem()); // total memory foot printIn order to quit an application which is using the asynchronous tile cache the background thread needs to be stopped beforehand. It is stopped implicitly if the datacloud object is deleted but it can be stopped at any time with an explicit call of cloud->stopthread(). Before the application can quit it also needs to release the background thread (see threadexit() in the above example).
The performance of loading tiles from disk is mainly limited by the number of tiles and only to a certain degree by the tile size. This is different if the tiles arrive over a network connection (see also Section (L)). In such a case the startup time is determined by the size and the number of the tiles that need to be loaded initially. We can reduce the number of initially loaded tiles (and minimize startup time) by telling libMini that only a subset of the visible tiles is mandatory for startup. After these tiles have been loaded initially the remaining tiles are paged in consecutively in the background thread. Typically, a useful startup subset is a small area around the initial point of view (ex,ey,ez):
tileset->restrictroi(ex,ez,farp/3);The tile size quadratically depends on the distance to the point of view. Therefore, the selection of a view point high above the scene (bird's eye view) additionally leads to a reduced initial traffic on the net. If the initial point of view is lying on the terrain, the traffic will be much higher, but we can mimic a high point of view by applying the following trick before the first frame is rendered:
tileset->updateroi(res, ex,ey+10*farp,ez, ex,ez,farp);This has the effect, that only low resolution tiles are loaded initially. These are replaced by higher resolution tiles as soon as they are coming in over the net. To apply the trick to the entire tile set use tileset->updateall().
For very large tile sets it is also important to save disk space. With S3TC compression only a compression of 1:6 is possible. In order to go beyond this compression ratio, the tiles need to be stored in JPEG format, for example. This is not a native format of libMini so that the conversion hook of the databuf object must be registered with a function that is able to to export and reimport that data (see Appendix (C)).
Since the textures are now stored in JPEG format on disk, they need to be decoded and uploaded in raw format to the graphics memory. If S3TC compression is required to fit the textures into the graphics memory, we also need to recompress the textures on-the-fly. For this purpose the squish library of Simon Brown is highly recommended. See Section (M) how to use this library. Back to top
The libMini library contains a sample implementation of a transfer module. To use this module we make the following modifications to the example code of the previous section:
tilecache=new datacache(tileset); tilecache->setremoteid(REMOTEID); tilecache->setremoteurl(REMOTEURL); tilecache->setlocalpath(LOCALPATH); tilecache->setstartupfile(STARTUPFILE); tilecache->setloader(request_callback,NULL); tilecache->getcloud()->setschedule(0.02,5.0,1.0); // upload for 20ms, keep for 5min, invalidate after 1sec tilecache->getcloud()->setmaxsize(256.0); // allow 256 MB tile cache size tilecache->getcloud()->setthread(startthread,NULL,jointhread,lock_cs,unlock_cs); tilecache->configure_netthreads(numthreads); tilecache->setreceiver(receive_callback,NULL,check_callback); tilecache->load();
void request_callback(const char *file,int istexture,databuf *buf,void *data) {buf->loaddata(file);} void receive_callback(const char *src_url,const char *src_id,const char *src_file,const char *dst_file,int background,void *data) {geturl(src_url,src_id,src_file,dst_file,background);} int check_callback(const char *src_url,const char *src_id,const char *src_file,void *data) {return(checkurl(src_url,src_id,src_file));}The geturl and checkurl functions use libcurl to negotiate and transfer data over the net. Please see the libMini Viewer (Section (P)) how this can be done in detail.
With that approach a compression ratio of up to 1:20 can be achieved with still good image quality. However, the images now have to be recompressed with S3TC on-the-fly. For that purpose, libMini features the auto-compression hook. Whenever libMini encounters an uncompressed texture and the auto-compression hook is set it automatically tries to run the texture data through the compression hook. Below is a reference implementation of the S3TC compression hook using the squish library of Simon Brown.
void autocompress(int isrgbadata,unsigned char *rawdata,unsigned int bytes, unsigned char **s3tcdata,unsigned int *s3tcbytes,int width,int height, void *data) { int i; int mode; unsigned char *rgbadata; static const int modefast=squish::kDxt1 | squish::kColourRangeFit; // fast but produces artifacts static const int modegood=squish::kDxt1 | squish::kColourClusterFit; // almost no artifacts though much slower static const int modeslow=squish::kDxt1 | squish::kColourIterativeClusterFit; // no artifacts but really sluggish mode=modefast; // we strive to compress as fast as possible if (isrgbadata==0) { rgbadata=(unsigned char *)malloc(4*width*height); if (rgbadata==NULL) ERRORMSG(); for (i=0; i<width*height; i++) { rgbadata[4*i]=rawdata[3*i]; rgbadata[4*i+1]=rawdata[3*i+1]; rgbadata[4*i+2]=rawdata[3*i+2]; rgbadata[4*i+3]=255; } rawdata=rgbadata; } *s3tcbytes=squish::GetStorageRequirements(width,height,mode); *s3tcdata=(unsigned char *)malloc(*s3tcbytes); if (*s3tcdata==NULL) ERRORMSG(); squish::CompressImage(rawdata,width,height,*s3tcdata,mode); if (isrgbadata==0) free(rawdata); }To register the above compression hook we use the following one-liner:
databuf::setautocompress(autocompress,NULL);Finally, the S3TC auto-compression is turned on in the background thread via datacloud::configure_autocompress(1).
The auto-compression also applies to mip-mapped textures. In order to automatically generate mip-maps prior to compressing the texture data we use datacloud::configure_automipmap(1).
Please note that the auto-compression hook is triggered from the background thread. Therefore it cannot use OpenGL functionality, because the background thread has no OpenGL context. This is the reason why we need to utilize a compression library like squish instead of the OpenGL driver.
Given that an OpenGL rendering window has been setup with an appropriate viewing matrix, the task of rendering a tileset with the libMini Viewer API is as simple as follows:
#include <mini/miniview.h> miniview *viewer=new miniview; viewer->getearth()->load("url"); minicoord eye(miniv3d(ex,ey,ez)); // eye point in ECEF miniv3d dir(dx,dy,dz); // viewing direction miniv3d up(ux,uy,uz); // up vector float aspect=width/height; // aspect ratio of viewing window viewer->get()->nearp=10.0; // near clipping plane viewer->get()->farp=10000.0; // far clipping plane viewer->initeyepoint(eye); // prefetch data viewer->clear(); // clear frame buffer viewer->cache(eye,dir,up,aspect); // fill vertex cache viewer->render(); // render vertex cacheThe coordinates eye/dir/up specify the camera coordinate system in OpenGL notation. The camera coordinates are interpreted as geo-referenced ECEF coordinates.
The terrain geometry can be queried by shooting rays at the displayed scene. The rays are specified by a geo-referenced starting point (in ECEF coordinates) and a shooting direction:
double dist=viewer->shoot(eye,dir); // shoot ray from eye point if (dist==MAXFLOAT) dist=0.0; // nothing hit minicoord hitpoint=eye+dist*dir; // calculate hit point in ECEFDisplayable tilesets can be generated with libGrid or VTB. For more details, see the following section.
The libMini viewer also comes as a QT based version with intuitive mouse steering and navigation around the earth globe. For the QT based version the viewer usage guide lines apply in an analog way, except that it needs to be compiled with QT's qmake. To get the whole package, checkout the entire libMini svn repository with:
svn co http://svn.code.sf.net/p/libmini/code/libmini libminiThen the libMini QTViewer is located in the "qtviewer" directory and the libMini libraries in the "mini" directory. For more details see the QTViewer README.
For conceptual information on the modules used by the viewer see the libMini Module Overview.
To compile the viewer type "./build.sh viewer" on the command line. It requires POSIX threads (pthreads), libjpeg, libpng / zlib, libcurl and squish to be installed.
On Windows the freeGLUT library must be installed, as well. On MacOS X and Linux it comes with the default OpenGL installation. As a substitute for POSIX threads it is recommended to use either pthreads-win32 or openthreads from the OpenSceneGraph repository.
The libMini Viewer can also be compiled with CMake. To do so, enable the option BUILD_MINI_APPS in the CMake configuration (via ccmake or the Windows CMake GUI). The cmake configuration has the additional options BUILD_MINI_WITHOUT_SQUISH, BUILD_MINI_WITH_GREYC and BUILD_MINI_WITH_OPENTHREADS to compile without squish, with CIMG/GREYCstoration or with openthreads, respectively.
If some of the required or optional dependencies cannot be found, because they have not been installed at a standard location like /usr/local, you can specify each directory separately or override the standard installation directory with the variable LIBMINI_THIRDPARTY_DIR in the CMake configuration.
Once you managed to install the dependencies and compile the libMini Viewer, see the following examples how to use it from the command line:
local usage: viewer <local.base.path> <tileset.path> <elevation.subpath> imagery.subpath { <options> } local example (for loading the data of the Hawaii Demo): viewer ~user/.../Hawaii/ data/HawaiiTileset/ elev imag remote usage: viewer "<http-address>" <tileset.path> <elevation.subpath> <imagery.subpath> { <options> } remote example: viewer "http://server.inter.net/.../" tileset/ elevation imageryIf the data is resampled with VTB make sure that a metric coordinate system like UTM is chosen. Also make sure that the two ini files for the elevation and the imagery are made available in the tile set path, because the viewer automatically retrieves necessary information from the ini files. The libMini Viewer tries to guess their names by adding the .ini suffix to the respective subpath. The default setting for the vtp subpaths is "elev" and "imag". If you stick to that naming convention, when generating the elevation and imagery tile sets with vtp, you can start the libMini Viewer with just one argument:
short usage: viewer <url/path>This is equivalent to the multi-argument usage of "viewer url/ path/ elev imag", so that the directory layout has to be as follows:
url/ path/ elev/ tile.0-0.db ... elev.ini imag/ tile.0-0.db ... imag.iniFor multiple tilesets we use the following command line:
multi usage: viewer -m { <url/path> [ <detailtex.db> ] }There is no limit on the maximum number of displayable tilesets. All tilesets will be shown at their corresponding geo-referenced place on earth. But keep in mind that the performance is mainly limited by the number of visible tiles, so that the total number of visible tilesets and tiles should be kept as low as possible.
As an option, the Mini Viewer supports one geo-referenced detail texture per tileset. Currently, the only supported file format for the detail textures is libMini's internal DB format.
The initial viewing settings favor rendering performance over quality. To increase the visualization quality, you can increase the following quality parameters interactively: the far clipping distance (farp), the triangle mesh resolution (res) and the texture detail level (range). To check these parameters press the h key. This turns on the head up display (HUD) which displays information about the available keyboard controls.
Optionally, you can run the libMini Viewer in anaglyph stereo mode by appending "-s -a" to the command line. You need to put on red/cyan glasses to get the stereo effect. To start the viewer in full-screen mode use the -f option. For full usage information on all available command line options start the viewer without arguments.
On a side note, the libMini Viewer can be configured to use OpenThreads instead of pthreads by typing "./build.sh viewer useopenth". On Windows, the libMini Viewer uses OpenThreads by default but can be configured to use a pthread implementation such as pthreads-win32.
There is also the option to disable squish support ("./build.sh viewer nosquish") which provides S3TC compression on the fly. Another option is to enable CImg/GREYCstoration support ("./build.sh viewer usegreyc") to denoise uncompressed imagery on the fly.
If it is required to catch these errors, a signal handler can be provided via setminierrorhandler() as defined in minibase.h to safely handle the exceptions (e.g. by closing or restarting the terrain renderer without shutting down the main application).
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}") FIND_PACKAGE(OpenGL) FIND_PACKAGE(MINI) INCLUDE_DIRECTORIES(${MINI_INCLUDE_DIR}) IF (MINI_FOUND) MESSAGE(STATUS found libMini headers: ${MINI_INCLUDE_DIR}) MESSAGE(STATUS found libMini libs: ${MINI_LIBRARIES}) ENDIF (MINI_FOUND) IF (MINISFX_FOUND) MESSAGE(STATUS found libMiniSFX libs: ${MINISFX_LIBRARIES}) ENDIF (MINISFX_FOUND)Then add libMini for basic libMini support or libMiniSFX for additional viewer support and the OpenGL libraries as additional link target for your application, e.g. "myapp":
TARGET_LINK_LIBRARIES(myapp ${MINI_LIBRARIES} ${OPENGL_LIBRARIES})For an example, see the "example.cmake" folder, which contains an example libMini CMake project with the dependencies OpenGL and GLUT. To compile it, type the following command line in the Unix terminal:
cmake . && makeBack to top
I also would like to thank Ingo Frick of Massive Development for many interesting discussions on implementation specific details while porting the terrain renderer to the AquaNox game engine. Many thanks also go to Olivier Pascal for his valuable feedback and to the folks at Makai Ocean Engineering for their great support: Jose Andres, Tie Fang and Greg Gillenwaters.
Comments or suggestions are highly appreciated. Please do not hesitate to contact the author at the given email address.
Have fun,
Stefan
A more detailed description is given in the paper. The reader is also encouraged to try the Fraenkische Demo which applies the described approach.
For conceptual information on the volume rendering approach see the libMini Volume Rendering Overview.
A volume is given by a tile set with r rows and c columns that extend in the horizontal plane and form what is called a minibrick. The preferable tile size is 2^n+1 (n may vary to yield varying size along the tile edges). The tile data needs to be provided in a databuf object container which is passed to the library using a callback mechanism. The load callback is triggered for each visible tile. In the callback the tile to be loaded is identified by its row and column. The availability of each tile is checked with the isavailable callback. Currently only two methods are provided that load a PVM (see Section Appendix (6B)) or a MOE volume and store the data in the databuf object. So usually the application layer will implement its own methods for setting up the databuf objects being passed in the load callback. Use the minibrick.setloader method to register your own callbacks with the library. The tiles do not need to be axis-aligned, but must have a rectangular basis. Therefore, please ensure that the corner coordinates of each databuf object are set to suitable values. Otherwise seams will be visible.
The appearance of a minibrick volume is determined by a so called spectrum of iso surfaces. Each single iso surface of the spectrum is defined by using the minibrick.addiso(iso,R,G,B,A) method which specifies the iso value and the corresponding RGB color and opacity of each iso surface.
Three different rendering methods can be configured. These methods implement either 2-, 3- or 4-pass rendering. The 2-pass method renders the opaque triangles in the first pass and the semi-transparent triangles in the second pass. This is the fastest available method, but artifacts may arise because the semi-transparent geometry is only sorted by iso surface number and not by depth order. In order to suppress these artifacts, the 3-pass method accumulates the opacity in the second pass and sums up the emission in the third pass. The 4-pass method improves image quality even further by selectively neglecting the emissions behind the first encountered back-face. The 3-pass method is a good compromise between speed and visual quality, thus it is enabled by default.
It is possible to render an arbitrary number a bricks simultaneously. The bricks could even intersect each other. For this to work, the render passes of each single brick have to be interleaved in the following way:
// declare n bricks minibrick bricks[n]; // render the bricks in an interleaved fashion for (int i=MINIBRICK_FIRST_RENDER_PHASE; i<=MINIBRICK_LAST_RENDER_PHASE; i++) for (int j=0; i<n; j++) brick[j].render(ex,ey,ez,rad,farp,fovy,aspect,time,i);Additionally, each brick can have up to six clipping planes that are set via minibrick::setclip. The clip planes are defined by a number, an origin and a normal vector.
The level of detail of the visualization is determined by the radius parameter rad. Within this radius around the view point the maximum level of detail is enabled. Outside the radius the resolution gradually decreases. The library interpolates smoothly between the level of details so that the popping effect is suppressed efficiently. Since this involves a good deal of floating point arithmetic the user should use the multi-threading support of the library to decouple the update of the iso surface geometry from rendering. This means that one thread continuously updates the geometry (if the view point has changed) while the other thread is busy rendering the latest cached geometry. This approach has the advantage that the frame rate only depends on the speed of the graphics hardware and is not limited by the update time that is needed to interpolate and extract the iso surfaces. Multi-threading is enabled by passing appropriate callbacks to the minibrick::setthread method as illustrated in the Hawaii Demo (see Section (I)).
In order to get a better understanding of the capabilities of the minibrick module please check out the Hawaii Demo. Start it with the -b option, press 'm' to go to Makai Pier in Waimanalo at the east side of Oahu and look at the scene with a bird's eye view. Then you see a time-dependent visualization of the evolution of a thunder storm with one opaque and two semi-transparent iso-surfaces.
<TYPE>\n <WIDTH> <HEIGHT>\n <MAXVAL>\n ...DATA...with
<TYPE> = P5 | P6 | P8 ::: P5 = PGM, P6 = PPM, P8 = RGBA <WIDTH> = %d ::: width of texture/heightmap <HEIGHT> = %d ::: height of texture/heightmap <MAXVAL> = %d ::: maximum valueFor 8 bit images MAXVAL is 255, for 16 bit images MAXVAL is either 32767 or 65535. In the 16 bit case libMini always assumes the data to be signed 16 bit (stored in MSB format). The header may additionally contain comments starting with a '#' in each line.
The plain PNM format does not contain georeferencing information. For this purpose, libMini is using a comment section after the TYPE identifier to include the missing information (thanks to Kyle Dickerson for the compilation):
<TYPE> # description=<DESCRIPTION> # coordinate system=<COORD_SYS> # coordinate zone=<COORD_ZONE> # coordinate datum=<COORD_DATUM> # SW corner=<SW_X>/<SW_Y> <SW_UNITS> # NW corner=<NW_X>/<NW_Y> <NW_UNITS> # NE corner=<NE_X>/<NE_Y> <NE_UNITS> # SE corner=<SE_X>/<SE_Y> <SE_UNITS> # cell size=<CELL_X>/<CELL_Y> <CELL_UNITS> # vertical scaling=<VERT_SCALE> <VS_UNITS> # missing value=<MISSING_VAL> <WIDTH> <HEIGHT> <MAXVAL> ...DATA...with
<MAGIC DESCRIPTOR> = BOX | DEM | TEX ::: BOX = bounding box, DEM = digital elevation model, TEX = texture map <DESCRIPTION> = %s <COORD_SYS> = LL | UTM <COORD_ZONE> = %d if (<COORD_SYS> == LL) then <COORD_ZONE> = 0 if (<COORD_SYS> == UTM) then (<COORD_ZONE> != 0 && <COORD_ZONE> > -60 && <COORD_ZONE> < 60) <COORD_DATUM> = %d if (<COORD_SYS> == LL) then <COORD_DATUM> = 0 (assuming WGS84 datum) else if (<COORD_DATUM> <1 || <COORD_DATUM> >14) then <COORD_DATUM> = 3 (WGS84) 1 = NAD27 (Mean North American Datum of 1927) 2 = WGS72 (World Geodetic System of 1972) 3 = WGS84 (World Geodetic System of 1984) 4 = NAD83 (Mean North American Datum of 1983) 5 = Sphere (with radius 6370997 meters) 6 = ED50 (Mean European Datum of 1950, centered at the Munich Frauenkirche) 7 = ED87 (Mean European Datum of 1987) 8 = OldHawaiian (mean datum for Hawaii/Maui/Oahu/Kauai) 9 = Luzon (Philippine Datum) 10 = Tokyo (Mean Japanese Datum) 11 = OSGB1936 (mean datum of the Ordnance Survey Great Britain 1936) 12 = Australian1984 (Mean Australian Geodetic Datum of 1984) 13 = NewZealand1949 (New Zealand Datum of 1949) 14 = SouthAmerican1969 (Mean South American Datum of 1969) <SW_X>, <SW_Y>, <NW_X>, <NW_Y>, <NE_X>, <NE_Y>, <SE_X>, <SE_Y>, <CELL_X>, <CELL_Y> = %g <SW_UNITS>, <NW_UNITS>, <NE_UNITS>, <SE_UNITS>, <CELL_UNITS> = radians | feet | meters | decimeters | arc-seconds if (<COORD_SYS> == LL) then <SW|NW|NE|SE|CELL_UNITS> == radians | arc-seconds if (<COORD_SYS> == UTM) then <SW|NW|NE|SE|CELL_UNITS> == feet | meters | decimeters <VERT_SCALE> = %g <VS_UNITS> = feet | meters | decimeters <MISSING_VAL> = %dThe libMini core only supports the BOX georeferencing type meaning that the contained data is enclosed exactly within the bounding box spanned by the four corner points. The built-in resampler also distinguishes between the two following types: DEM means that the contained data is a height field and that the corner coordinates define the exact position of the four corner vertices (corner-centric grid representation). TEX means that the contained data is a texture map and that the corner coordinates define the position of the midpoint of the four corner pixels (cell-centric grid representation). The missing value field is used by DEM formats to identify cells with unknown or unspecified elevation. A typical value is -9999. The no-data value for imagery assumed by the built-in resampler is absolute black (0,0,0). This is a safe assumption since real world imagery hardly contains true black. If this is not the case, the black pixels can be easily substituted with an RGB value of (0,0,1), which is visually equal to black.
<MAGIC>\n <WIDTH> <HEIGHT> <DEPTH>\n <COMPONENTS>\n ...DATA...with
<MAGIC> = PVM ::: magic identifier <WIDTH> = %d ::: width of volume <HEIGHT> = %d ::: height of volume <COMPONENTS> = %d ::: number of componentsFor 8 bit data the number of components is 1, for 16 bit data 2 and for RGB movies it is 3. The voxel spacing is assumed to be uniform.
A PVM example header for a 256x256x256 volume with 1 byte per voxel:
PVM 256 256 256 1 raw byte data...If the magic identifier is PVM2 or PVM3, this indicates newer versions of the format, which include the voxel spacing in the header:
<MAGIC>\n <WIDTH> <HEIGHT> <DEPTH>\n <VOXEL_WIDTH> <VOXEL_HEIGHT> <VOXEL_DEPTH>\n <COMPONENTS>\n ...DATA...A PVM3 example header for a 256x256x256 volume with a non-uniform voxel spacing (the voxel spacing is 150% in z-direction) :
PVM3 256 256 256 1 1 1.5 1 raw byte data...PVM files often come in a DDS compressed data format. In that case the data startes with the prefix "DDS v3d". You can uncompress those files with the dds tool.
MAGIC=13761 ::: magic number xsize=%u ::: mandatory width ysize=%u ::: mandatory height for 2+D, 1 for 1D data zsize=%u ::: mandatory depth for 3+D, 1 for 1D and 2D data tsteps=%u ::: mandatory number of time steps for 4D, 1 for 1D, 2D and 3D data type=%u ::: mandatory cell type: 0 = unsigned byte, 1 = signed short, 2 = float, 3 = RGB, 4 = RGBA, 5 = compressed RGB (S3TC DXT1), 6 = compressed RGBA (S3TC DXT1 with 1-bit alpha), 7 - mip-mapped RGB, 8 - mip-mapped RGBA, 9 - compressed mip-mapped RGB, 10 - compressed mip-mapped RGBA swx=%g ::: x-component of south west corner (should be supplied for tile sets) swy=%g ::: y-component of south west corner (should be supplied for tile sets) nwx=%g ::: x-component of north west corner (should be supplied for tile sets) nwy=%g ::: y-component of north west corner (should be supplied for tile sets) nex=%g ::: x-component of north east corner (should be supplied for tile sets) ney=%g ::: y-component of north east corner (should be supplied for tile sets) sex=%g ::: x-component of south east corner (should be supplied for tile sets) sey=%g ::: y-component of south east corner (should be supplied for tile sets) h0=%g ::: base elevation of 3D or 4D data cube dh=%g ::: height of the 3D or 4D cube t0=%g ::: starting time of 4D series dt=%g ::: time step of 4D series scaling=%g ::: elevation scaling parameter for height fields (default is 1) bias=%g ::: elevation bias parameter for height fields (default is 0) crs=%i ::: coordinate reference system: 0 = Linear, 1 = Lat/Lon (LLH) 2 = UTM 3 = Mercator 4 = Oblique Gnomonic (OGH) zone=%i ::: crs zone (for UTM and OGH) datum=%i ::: crs datum: 0 = unspecified 1 = NAD27 (Mean North American Datum of 1927) 2 = WGS72 (World Geodetic System of 1972) 3 = WGS84 (World Geodetic System of 1984) 4 = NAD83 (Mean North American Datum of 1983) 5 = Sphere (with radius 6370997 meters) 6 = ED50 (Mean European Datum of 1950, centered at the Munich Frauenkirche) 7 = ED87 (Mean European Datum of 1987) 8 = OldHawaiian (mean datum for Hawaii/Maui/Oahu/Kauai) 9 = Luzon (Philippine Datum) 10 = Tokyo (Mean Japanese Datum) 11 = OSGB1936 (mean datum of the Ordnance Survey Great Britain 1936) 12 = Australian1984 (Mean Australian Geodetic Datum of 1984) 13 = NewZealand1949 (New Zealand Datum of 1949) 14 = SouthAmerican1969 (Mean South American Datum of 1969) nodata=%g ::: no-data value extformat=%u ::: external format indicator: a value!=0 triggers conversion hook (default 0, 1 reserved for JPEG, 2 for PNG, 3 for Z) implformat=%u ::: implicit format indicator: a value!=0 triggers implict evaluation mode LLWGS84_swx=%g ::: optional Lat/Lon/WGS84 x-coordinate of south-west corner point LLWGS84_swy=%g ::: optional Lat/Lon/WGS84 y-coordinate of south-west corner point LLWGS84_nwx=%g ::: optional Lat/Lon/WGS84 x-coordinate of north-west corner point LLWGS84_nwy=%g ::: optional Lat/Lon/WGS84 y-coordinate of north-west corner point LLWGS84_nex=%g ::: optional Lat/Lon/WGS84 x-coordinate of north-eash corner point LLWGS84_ney=%g ::: optional Lat/Lon/WGS84 y-coordinate of north-east corner point LLWGS84_sex=%g ::: optional Lat/Lon/WGS84 x-coordinate of south-east corner point LLWGS84_sey=%g ::: optional Lat/Lon/WGS84 y-coordinate of south-east corner point bytes=%u ::: mandatory byte length of the following data chunkThe data chunk is appended as raw data to the above description.
Data type 1 and 2 is stored in MSB format. After loading the data into main memory it is automatically converted into the native MSB or LSB format of the CPU. The description must end with a NUL character.
Previous versions of the DB format with magic identifier 13091 or less did not include the fields crs, zone, datum, nodata, and LLWGS84.
A value other than zero for extformat indicates that the data chunk is stored in an external format. When calling databuf::loaddata on such an object it automatically tries to trigger an external conversion hook to transform the input data into the corresponding raw format. The hook can be set via databuf::setconversion. As an example, extformat=1 in the header of a DB file means that the appended data chunk is encoded as a JPEG image. For more information on this issue, please have a look at the libMini Viewer (as described in Section (P)) which demonstrates how to use the conversion hook mechanism in order to decode JPEG images. If the extfmt parameter of databuf::savedata is set, the conversion hook is also triggered to convert the raw data into the external format.
A value other than zero for implformat indicates that the data chunk is stored in an implicit format, in which the data is interpreted as LUNA program. For example, the following db file produces a checkboard pattern in a procedural way:
MAGIC=13091 xsize=512 ysize=512 zsize=1 tsteps=1 type=3 swx=0 swy=0 nwx=0 nwy=0 nex=0 ney=0 sex=0 sey=0 h0=0 dh=0 t0=0 dt=0 scaling=1 bias=0 extformat=0 implformat=1 bytes=0 # implicit checker board function func checker(par x, par y) { return(((x*7.999)%2<1) ^ ((y*7.999)%2<1)); } # procedural checker board evaluation main(par x, par y, par z, par t) { var c = checker(x, y); return(c, c, c); }Back to top eof