www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 1190] New: Reference becoming null

reply d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190

           Summary: Reference becoming null
           Product: D
           Version: 1.013
          Platform: PC
        OS/Version: All
            Status: NEW
          Keywords: wrong-code
          Severity: blocker
          Priority: P2
         Component: DMD
        AssignedTo: bugzilla digitalmars.com
        ReportedBy: arkangath gmail.com


The following code is part of a LOD terrain engine I'm coding and the code
fails on a split operation, where references to Nodes are updated (copied) to
generate child nodes. The code has been ported from C++ (I can post it as well)
and all lines has been checked and re-checked for errors on the implementation.

The code has been striped of all unnecessary methods to improve readability and
reduce dependencies. When compiled with "dmd -g -debug teste.d" the program
fails to generate a viable mesh, crashing on an assert. This assert can be
removed and the code would generate a mesh, but with null Nodes, if could not
be rendered (Access Violation if attempted).

The assert occurs in both Windows and Linux, the only platforms available for
testing.

The line in which the assert fails is 307, when the current node's right
neighbor's base neighbor is set to current node's right child node. Before that
line, both are asserted as NOT being null. I tried to analyze the disassembled
code, but I couldn't find the source of the error.

------------------ teste.d -------------------------
///Contains all declarations concerning the terrain engine
import std.string;
import std.stream;
import std.math;

alias std.string.toString strfy;
alias std.stdio.writefln Log;

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
    {
        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);
                    myNode.rNeigh.bNeigh=myNode.rChild; //This causes the
children (both) to die
                    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
    {
        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;
}


-- 
Apr 25 2007
next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190





------- Comment #1 from arkangath gmail.com  2007-04-25 08:41 -------
Created an attachment (id=142)
 --> (http://d.puremagic.com/issues/attachment.cgi?id=142&action=view)
This file contains the terrain height data

Unzip this file to the exe's directory.


-- 
Apr 25 2007
prev sibling next sibling parent reply d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190


arkangath gmail.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
            Version|1.013                       |1.015




------- Comment #2 from arkangath gmail.com  2007-06-06 11:22 -------
This bug still exists on version 1.15, in spite of the corrections made to the
GC.


-- 
Jun 06 2007
parent torhu <fake address.dude> writes:
d-bugmail puremagic.com wrote:
 http://d.puremagic.com/issues/show_bug.cgi?id=1190
 
 
 arkangath gmail.com changed:
 
            What    |Removed                     |Added
 ----------------------------------------------------------------------------
             Version|1.013                       |1.015
 
 
 
 
 ------- Comment #2 from arkangath gmail.com  2007-06-06 11:22 -------
 This bug still exists on version 1.15, in spite of the corrections made to the
 GC.
 
 

I think the version number is supposed to be set to the oldest version where the bug is known to exist. This way, Walter will know where to begin looking for changes that might have caused the bug.
Jun 06 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190





------- Comment #3 from bugzilla digitalmars.com  2007-06-30 14:41 -------
I'd try:

1) removing the delete statements

-or-

2) disabling the gc

and see if either of those affect the results.


-- 
Jun 30 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190





------- Comment #4 from arkangath gmail.com  2007-07-01 17:38 -------
Tried that, didn't work. I also added a debug printf on the TreeNode destructor
and it didn't get called.


-- 
Jul 01 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190


deewiant gmail.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
            Version|1.015                       |1.013




------- Comment #5 from deewiant gmail.com  2007-08-31 12:26 -------
Please keep the version at the oldest possible:
http://www.digitalmars.com/d/archives/digitalmars/D/bugs/bugzilla_usage_tips_10071.html


-- 
Aug 31 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190


davidl 126.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |RESOLVED
         Resolution|                            |INVALID




------- Comment #6 from davidl 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;
}


-- 
Sep 04 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190


arkangath gmail.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|RESOLVED                    |REOPENED
         Resolution|INVALID                     |




------- Comment #7 from arkangath gmail.com  2007-09-04 07:43 -------
(In reply to comment #6)
 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.

There is no such comparison on the code. All comparisons between nodes are made with the "is" keyword. No "==" comparisons are performed on references and you can confirm by searching all "==" on the code. And as I said on the main post, I have tracked the problem to a single line (marked in the source as "//This causes the children (both) to die"). Decompilation of the source lines gives out terrain.d:334 assert(myNode.rChild !is null); 00403402: 8b5d08 mov ebx, [ebp+0x8] 00403405: 8b0b mov ecx, [ebx] 00403407: 83790c00 cmp dword [ecx+0xc], 0x0 0040340b: 750a jnz 0x403417 MFKC7terrain7Terrain8TreeNodeZv terrain.Terrain.split terrain.d:336 0040340d: b84e010000 mov eax, 0x14e 00403412: e849040000 call 0x403860 void terrain.__assert(int) terrain.d:423 terrain.d:336 myNode.rNeigh.bNeigh=myNode.rChild; 00403417: 8b13 mov edx, [ebx] 00403419: 8b720c mov esi, [edx+0xc] 0040341c: 8b7a18 mov edi, [edx+0x18] 0040341f: 897710 mov [edi+0x10], esi terrain.d:337 assert(myNode.rChild !is null); 00403422: 8b0b mov ecx, [ebx] 00403424: 83790c00 cmp dword [ecx+0xc], 0x0 00403428: 750a jnz 0x403434 MFKC7terrain7Terrain8TreeNodeZv terrain.Terrain.split terrain.d:337 0040342a: b851010000 mov eax, 0x151 0040342f: e82c040000 call 0x403860 void terrain.__assert(int) terrain.d:423 00403434: eb3c jmp 0x403472 MFKC7terrain7Terrain8TreeNodeZv terrain.Terrain.split terrain.d:342 terrain.d:339 else if (myNode.rNeigh.rNeigh is myNode) myNode.rNeigh.rNeigh=myNode.rChild; 00403436: 8b5508 mov edx, [ebp+0x8] 00403439: 8b1a mov ebx, [edx] 0040343b: 8b7318 mov esi, [ebx+0x18] 0040343e: 8b4e18 mov ecx, [esi+0x18] 00403441: 3bcb cmp ecx, ebx 00403443: 7510 jnz 0x403455 MFKC7terrain7Terrain8TreeNodeZv terrain.Terrain.split terrain.d:340 00403445: 8b4508 mov eax, [ebp+0x8] 00403448: 8b10 mov edx, [eax] 0040344a: 8b5a0c mov ebx, [edx+0xc] 0040344d: 8b7218 mov esi, [edx+0x18] 00403450: 895e18 mov [esi+0x18], ebx 00403453: eb1d jmp 0x403472 MFKC7terrain7Terrain8TreeNodeZv terrain.Terrain.split terrain.d:342 You can confirm the code with ddbg. The problem isn't with my code, as I have said on the original post, the code was copied from my functional implementation on C (which I can provide if necessary) and I have confimed that EVERY LINE was been correctly ported. A single "copy" operation is cause two references to become null, references which are only read, not written to, on the faulty operation. --
Sep 04 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190





------- Comment #8 from wbaxter gmail.com  2007-09-04 11:43 -------
This seems to be a bad interaction between inout parameters and a tangly web of
pointers.  In your case, I don't think you need to pass the TreeNode by
reference (inout) to split, because nowhere in that function is the parameter
assigned to directly.  So I think just removing "inout" on that function will
fix your problem.  (I got another error after removing it, but it got past the
trouble spot).

But this still may be a bug.
You've got a graph like this basically:

                   bNeigh----->    
    null<---rNeigh[root]      [tree]bNeigh---> null
                       <------rNeigh

and you pass in root.bNeigh by reference, then you try to set
tree.rNeigh.bNeigh, which is the same as tree.  

I'll attach a simplified test case.


-- 
Sep 04 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190





------- Comment #9 from wbaxter gmail.com  2007-09-04 11:46 -------
Created an attachment (id=175)
 --> (http://d.puremagic.com/issues/attachment.cgi?id=175&action=view)
Simpler test case


-- 
Sep 04 2007
prev sibling next sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190


davidl 126.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|REOPENED                    |RESOLVED
         Resolution|                            |INVALID




------- Comment #10 from davidl 126.com  2007-09-05 03:06 -------
The Simpler test case just should access violation as Bill said in his last
comment. Kill the ref keyword, the simpler test case goes fine. 
But without inout the original code fails at an assertion. 

And I'm kind enough to illustrate the new problem for you. :)

Here is the code with a little bit correction, and illustrate the new assertion
fail reason:

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(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))
        {
                        TreeNode v=myNode.bNeigh;
            split(myNode.bNeigh); //it has to be split, so that current
triangle can be a diamond with it's bNeigh
                        if (myNode.bNeigh !is v){
                                printf("oh my god!");
                        }
            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!");      // this assertion doesn't test the problem
actually , in this scope testing myNode is useless
                                                         // you should test the
copy of myNode
        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;
}


-- 
Sep 05 2007
prev sibling parent d-bugmail puremagic.com writes:
http://d.puremagic.com/issues/show_bug.cgi?id=1190





------- Comment #11 from wbaxter gmail.com  2007-09-05 03:26 -------
If you're going to add gigantic gobs of code to bugzilla, please do it as an
attachment so that the lines don't get all wrapped.


-- 
Sep 05 2007