///Defines the terrain class module terrain; import std.stdio; import syst; //get the camera object import derelict.opengl.gl; import derelict.opengl.glu; import std.string:toString; import std.format; import std.stream; import std.math; alias std.string.toString strfy; //solve std.Object.toString ambiguity private static const ubyte PatchSize=64; //must be power of 2 //The following fails because of sqrt //const ubyte MAX_DEPTH=cast(ubyte)(2*sqrt(PatchSize)+1); private static const MaxDepth=17; private struct CamInfo { uint x, y, z; } private CamInfo camInfo; /** The Terrain engine class. */ public final class Terrain { ///The TreeNode class is used to store information about the patch tree private final class TreeNode { public TreeNode lChild, rChild, bNeigh, lNeigh, rNeigh; ~this() { writefln("TreeNode died"); } } ///Patch Class. A terrain is composed of several patches private final class Patch { this (ushort x, ushort y) { this.x=x; this.y=y; visible=true; rNode=new TreeNode(); //these never get reassigned lNode=new TreeNode(); //they're const, remmember? assert(rNode !is null); assert(lNode !is null); //nodes come already clean rNode.bNeigh=lNode; lNode.bNeigh=rNode; calcError(); } ///Resets the patch. It becomes undivided and ready for the next tesselation. void reset() { rNode.rNeigh=rNode.lNeigh=rNode.lChild=rNode.rChild= lNode.rNeigh=lNode.lNeigh=lNode.lChild=lNode.rChild=null; rNode.bNeigh=lNode; lNode.bNeigh=rNode; //TODO: check visibility here visible=true; } ///Tesselates this patch and it becomes ready for rendering void update() { cError[]=lError; tesselate(lNode, x, cast(ushort)(y+PatchSize), cast(ushort)(x+PatchSize), y, x, y, 1); cError[]=rError; tesselate(rNode, cast(ushort)(x+PatchSize), y, x, cast(ushort)(y+PatchSize), cast(ushort)(x+PatchSize), cast(ushort)(y+PatchSize), 1); } ///This patch's error measure is updated from the data on hData. void calcError() { Calculate(x, cast(ushort)(y+PatchSize), cast(ushort)(x+PatchSize), y, x, y, 1); lError[]=cError; //copy the error data Calculate(cast(ushort)(x+PatchSize), y, x, cast(ushort)(y+PatchSize), cast(ushort)(x+PatchSize), cast(ushort)(y+PatchSize), 1); rError[]=cError; return; } void render() { glPushMatrix(); glTranslatef(x, y, 0); //move into camera's position + patch coord glBegin(GL_TRIANGLES); auxRender(lNode, x, y, 0, PatchSize, PatchSize, 0, 0, 0); auxRender(rNode, x, y, PatchSize, 0, 0, PatchSize, PatchSize, PatchSize); glEnd(); glPopMatrix(); return; } ~this() { delete rNode; delete lNode; } //no need to protect this vars because this class won't be accessed from the exterior const ushort x, y; //coordinates aren't expected to change bool visible; ///Result of lastest cull check ushort[MaxDepth] lError, rError; const TreeNode rNode, lNode; } public: this(in File file, float detail) in { assert(file !is null); assert(detail>=0); //negative error values make no sense. 0 values are purely debugging technices } body { debug Log("Creating terrain object"); this.detail=detail; file.read(sizeX); file.read(sizeY); assert((sizeX>0)&&(sizeY>0)); HMult=1; //TODO: Replace with resolution from file hData=new ushort[][](sizeX, sizeY); //initialize data debug Log(" * Loading terrain data..."); foreach (stripe; hData) foreach (inout vertex; stripe) { file.read(vertex); vertex*=HMult; } maxH=0; //Maximum height present on the map. Used somewhere in debug code foreach (stripe; hData) foreach (vertex; stripe) if (vertex>maxH) maxH=vertex; xPatch=cast(ubyte)(sizeX/PatchSize); yPatch=cast(ubyte)(sizeY/PatchSize); nodes.length=(sizeX*sizeY/PatchSize); debug Log(" * Creating terrain nodes ("~strfy(nodes.length)~")..."); foreach(inout node; nodes) node=new TreeNode; debug Log(" * Creating patches..."); patches.length=xPatch;//how many columns will we have? Patch[col][row] foreach (size_t row, inout Patch[] strip; patches) { assert(strip.length==0, "Oups! Redefining a length of a strip"); strip.length=yPatch; //How many rows? foreach (size_t col, inout Patch patch; strip) patch=new Patch(cast(ushort)(col*PatchSize), cast(ushort)(row*PatchSize)); } debug Log("Terrain creation complete"); } public float heightAt(in float x, in float y) out(result) { assert((result>=0.0f)||(result==float.nan)); } body { if ((x<0.0)||(y<0.0f)) return float.nan; //triangulate height at the given point return 0.0f; } ///Makes the terrain ready for rendering. Tesselates all the patches. void update() in { assert(focus !is null, "Can't update terrain when focus is null"); } body { //reset the engine first currNode=0; //reset current node foreach (size_t row, inout Patch[] strip; patches) foreach (size_t col, inout Patch patch; strip) { patch.reset(); //Recalculate the visibility flag if (patch.visible) //bail on non-visible patches { if (col>0) patch.lNode.lNeigh=patches[col-1][row].rNode; else patch.lNode.lNeigh=null; if (col<(xPatch-1)) patch.rNode.lNeigh=patches[col+1][row].lNode; else patch.rNode.lNeigh=null; if (row>0) patch.lNode.rNeigh=patches[col][row-1].rNode; else patch.lNode.rNeigh=null; if (row<(yPatch-1)) patch.rNode.rNeigh=patches[col][row+1].lNode; else patch.rNode.rNeigh=null; } } //Unfloatize camera information. Remember the case in which the coords are <0 camInfo.x=cast(uint)(focus.posX); camInfo.y=cast(uint)(focus.posY); camInfo.z=cast(uint)(focus.height); foreach (Patch[] strip; patches) foreach (Patch patch; strip) if (patch.visible) patch.update(); return; } void render() { foreach (strip; patches) foreach (patch; strip) patch.render(); return; } ~this() { /*Since the terrain is the last thing to be deleted when a game is over, a gc.fullCollect() will be preformed sometime soon, so it is not necessary to clean up manually*/ foreach (inout stripe; hData) { stripe.length=0; delete stripe; } hData.length=0; delete hData; foreach (inout Patch[] strip; patches) { foreach (inout Patch patch; strip) delete patch; strip.length=0; delete strip; } patches.length=0; delete patches; foreach(inout node; nodes) delete node; nodes.length=0; delete nodes; } private: ///Returns the next available TreeNode. Creates a new one if there are no more available. TreeNode newNode() out(result) { assert(result !is null, "newNode() attempted to return a null TreeNode."); } body { assert(currNode<=nodes.length, "currNode advanced too much"); if (currNode==nodes.length) { nodes~=new TreeNode; //no more nodes available, create another one debug Log("Insufficient TreeNodes for current tesselation (detail="~strfy(detail)~"). Increasing TreeNode pool (currNode="~strfy(currNode)~")."); } TreeNode ret=nodes[currNode]; //get a new node ret.lChild=ret.rChild=ret.bNeigh=ret.lNeigh=ret.rNeigh=null; //clear new node's affiliations currNode++; return ret; } ///To be used by functions of the class, returns raw height value. ushort heightAt(ushort x, ushort y) { if ((x==sizeX)||(y==sizeY)) return 0; //Out of bounds values are not drawn (taken has void in game) assert(x=2)||(abs(rY-lY))>=2) //smaller than 2x2 { //calculate the error of one of the child triangles ushort tempErrRatio=Calculate(aX, aY, lX, lY, cX, cY, cast(ushort)(node*2)); if (errRatiodistance) //should this triangle be split? { split(myNode); //won't fail because TreeNodes will not go out //I have children and they aren't too small, //TODO: Unnecessary check: split never fails: (myNode.lChild !is null)&&(myNode.rChild !is null)&& if ((node=2)||(abs(lY-rY)>=2))) { tesselate(myNode.lChild, aX, aY, lX, lY, cX, cY, cast(ushort)(node*2)); tesselate(myNode.rChild, rX, rY, aX, aY, cX, cY, cast(ushort)(node*2+1)); } } } void auxRender(TreeNode myNode, ushort fX, ushort fY, ushort lX, ushort lY, ushort rX, ushort rY, ushort aX, ushort aY) in { assert(myNode !is null, "Cannot render null tree node"); } body { if ((myNode.lChild !is null)&&(myNode.rChild !is null)) //I'm split { ushort cX=cast(ushort)((lX+rX)/2); ushort cY=cast(ushort)((lY+rY)/2); auxRender(myNode.lChild, fX, fY, aX, aY, lX, lY, cX, cY); auxRender(myNode.rChild, fX, fY, rX, rY, aX, aY, cX, cY); } else { static float cor; //cor=heightAt(cast(ushort)(aX+fX), cast(ushort)(aY+fY))/maxH; //glColor3f(cor, cor, cor); glVertex3f(aX, aY, /*heightAt(cast(ushort)(aX+fX), cast(ushort)(aY+fY))*/0); //cor=heightAt(cast(ushort)(rX+fX), cast(ushort)(rY+fY))/maxH; //glColor3f(cor, cor, cor); glVertex3f(rX, rY, /*heightAt(cast(ushort)(rX+fX), cast(ushort)(rY+fY))*/0); //cor=heightAt(cast(ushort)(lX+fX), cast(ushort)(lY+fY))/maxH; //glColor3f(cor, cor, cor); glVertex3f(lX, lY, /*heightAt(cast(ushort)(lX+fX), cast(ushort)(lY+fY))*/0); } return; } const float maxH; float detail; ///Detail Threshold TreeNode nodes[]; ///Node pool size_t currNode; ///Current node being assigned Texture textures[]; ///Stores the Textures required by the terrain const ushort sizeX, sizeY; const ubyte xPatch, yPatch; ushort hData[][]; ///The Height Data static ushort[MaxDepth] cError; //error buffer const ubyte HMult; ///Height multiplier, determines the resolution of the terrain Patch patches[][]; ///The pathes that compose the terrain. //const must be commented due to bug in DMD } ///Public terrain handler public Terrain hTerrain;