[Issue 1190] Reference becoming null
d-bugmail at puremagic.com
d-bugmail at puremagic.com
Tue Sep 4 01:10:07 PDT 2007
http://d.puremagic.com/issues/show_bug.cgi?id=1190
davidl at 126.com changed:
What |Removed |Added
----------------------------------------------------------------------------
Status|NEW |RESOLVED
Resolution| |INVALID
------- Comment #6 from davidl at 126.com 2007-09-04 03:10 -------
I've noticed that your code create something like :
myNode.rNeigh.bNeigh == myNode
So I assume it's your code fault, if you can't provide a smaller test case.
import std.string;
import std.stream;
import std.math;
alias std.string.toString strfy;
alias std.stdio.writefln Log;
int staticc;
private static const ubyte PatchSize=64; //must be power of 2
//const ubyte MAX_DEPTH=cast(ubyte)(2*sqrt(PatchSize)+1);
private static const MaxDepth=4;//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
{
private TreeNode lChild, rChild, bNeigh, lNeigh, rNeigh;
public this()
{
reset();
}
public void reset()
{
lChild=rChild=bNeigh=lNeigh=rNeigh=null;
}
}
///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.reset();
lNode.reset();
rNode.bNeigh=lNode;
lNode.bNeigh=rNode;
//TODO: check visibility here
visible=true;
}
///Tesselates this patch and it becomes ready for rendering
void update()
{
std.gc.disable();
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);
std.gc.enable();
}
///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;
}
~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()
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
foreach (Patch[] strip; patches)
foreach (Patch patch; strip)
if (patch.visible) patch.update();
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;
foreach (inout Patch[] strip; patches)
foreach (inout Patch patch; strip)
delete patch;
foreach(inout node; nodes) delete node;
nodes.length=0;
}
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.reset(); //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<sizeX, "Out of bounds value requested");
assert(y<sizeY, "Out of bounds value requested");
return hData[x][y];
}
ushort Calculate(ushort lX, ushort lY, ushort rX, ushort rY, ushort aX,
ushort aY, ushort node)
{
//Calculate hipotenuse
ushort cX=cast(ushort)((rX+lX)/2);
ushort cY=cast(ushort)((rY+lY)/2);
ushort cZ=heightAt(cX, cY);
ushort rZ=heightAt(rX, rY);
ushort lZ=heightAt(lX, lY);
ushort errRatio=cast(ushort)abs(cZ-((lZ+rZ)/2)); //this iteraction's
primary error ratio
if (errRatio==0) return 0; //reached perfectness
if ((abs((rX-lX))>=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 (errRatio<tempErrRatio) errRatio=tempErrRatio; //error is
greater, adopt it
//and then try the other child
tempErrRatio=Calculate(rX, rY, aX, aY, cX, cY,
cast(ushort)(node*2+1));
if (errRatio<tempErrRatio) errRatio=tempErrRatio; //error is
greater, adopt it
}
//TODO: this "if" may not need to be here: required for range check,
nothing else
if (node<MaxDepth) cError[node]=errRatio; //store error ratio as we
descend on the tree
return errRatio; //return this levels error ratio
}
///Splits a TreeNode, used by Patch sub-class
void split(inout TreeNode myNode)
in
{
if (myNode.rNeigh !is null)
assert(myNode.rNeigh.bNeigh !is myNode);
assert(myNode !is null, "Attempted to split a null node");
}
body
{
if (myNode.lChild !is null) return; //already split
//Check to see if current triangle is diamond with bNeigh
if ((myNode.bNeigh !is null)&&(myNode.bNeigh.bNeigh !is myNode))
{
split(myNode.bNeigh); //it has to be split, so that current
triangle can be a diamond with it's bNeigh
assert(myNode.bNeigh.lChild !is null, "Split failed!");
}
//asserts that I am a diamond with my bNeigh, or I'm a border node
assert ((myNode.bNeigh is null)||(myNode.bNeigh.bNeigh is myNode),
"Attempted to split a node that is not part of a diamond");
myNode.lChild=newNode();
myNode.rChild=newNode();
//with (myNode) //in this block, all the relations of myNode are
updated
{
myNode.lChild.bNeigh=myNode.lNeigh;
myNode.lChild.lNeigh=myNode.rChild;
myNode.rChild.bNeigh=myNode.rNeigh;
myNode.rChild.rNeigh=myNode.lChild;
if (myNode.lNeigh !is null) //I have a left neigh
{
if (myNode.lNeigh.bNeigh is myNode)
myNode.lNeigh.bNeigh=myNode.lChild;
else
{
if (myNode.lNeigh.lNeigh is myNode)
myNode.lNeigh.lNeigh=myNode.lChild;
else if (myNode.lNeigh.rNeigh is myNode)
myNode.lNeigh.rNeigh=myNode.lChild;
}
}
assert((myNode.lChild !is null)&&(myNode.rChild !is null),
"Children died at lNeigh update");
//The following if block causes the children to become null
if (myNode.rNeigh !is null) //I have a right neigh
{
if (myNode.rNeigh.bNeigh is myNode)
{
assert(myNode.lChild !is null);
assert(myNode.rChild !is null);
assert(myNode.rNeigh.bNeigh !is null);
assert(myNode.rNeigh.bNeigh !is
myNode);
myNode.rNeigh.bNeigh=myNode.rChild; //This causes the
children (both) to die
assert(myNode.lChild !is
myNode.rNeigh.bNeigh);
assert(myNode.lChild !is null); //crash here
assert(myNode.rChild !is null);
}
else
{
if (myNode.rNeigh.rNeigh is myNode)
myNode.rNeigh.rNeigh=myNode.rChild;
else if (myNode.rNeigh.lNeigh is myNode)
myNode.rNeigh.lNeigh=myNode.rChild;
}
}
assert((myNode.lChild !is null)&&(myNode.rChild !is null),
"Children died at rNeigh update");
if (myNode.bNeigh !is null) //I have a base neigh
{
if (myNode.bNeigh.lChild !is null) //My bNeigh is already split
{
assert(myNode.bNeigh.rChild !is null, "bNeigh is only
partially split, cannot update relations.");
myNode.bNeigh.lChild.rNeigh=myNode.rChild;
myNode.bNeigh.rChild.lNeigh=myNode.lChild;
myNode.lChild.rNeigh=myNode.bNeigh.rChild;
myNode.rChild.lNeigh=myNode.bNeigh.lChild;
}
else split(myNode.bNeigh); //Split my base. He'll take care of
Neigh relations
}
else //I don't have bNeigh
{
myNode.lChild.rNeigh=null;
myNode.rChild.lNeigh=null;
}
assert((myNode.lChild !is null)&&(myNode.rChild !is null),
"Children died at bNeigh update");
} //updates to myNode are over
assert((myNode.lChild !is null)&&(myNode.rChild !is null), "Node
children have been lost!");
return;
}
void tesselate (TreeNode myNode, ushort lX, ushort lY, ushort rX, ushort
rY, ushort aX, ushort aY, ushort node)
in
{
if (myNode.rNeigh !is null)
assert(myNode.rNeigh.bNeigh !is myNode);
assert(myNode !is null, "Attempted to tesselate a null node");
}
body
{
//Calculate hipotenuse
ushort cX=cast(ushort)((rX+lX)/2);
ushort cY=cast(ushort)((rY+lY)/2);
ushort cZ=heightAt(cX, cY);
ulong
distance=(cX-camInfo.x)*(cX-camInfo.x)+(cY-camInfo.y)*(cY-camInfo.y)+(cZ-camInfo.z)*(cZ-camInfo.z);
ulong threshold=cError[node]*cError[node];
if ((threshold*detail)>distance) //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<cast(ushort)(MaxDepth/2))&&((abs(lX-rX)>=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));
}
}
}
const ushort maxH;
float detail; ///Detail Threshold
TreeNode nodes[]; ///Node pool
size_t currNode; ///Current node being assigned
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
const Patch patches[][]; ///The pathes that compose the terrain.
}
void main()
{
File este=new File("novomapa.raw", FileMode.In);
Terrain terrain=new Terrain(este, 2500);
delete este;
std.stdio.writefln("Preparing for rendering...");
terrain.update();
delete terrain;
}
--
More information about the Digitalmars-d-bugs
mailing list