clockwerk-opensim-stable – Rev 1

Subversion Repositories:
Rev:
/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyrightD
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Text;

using OpenSim.Framework;
using OpenSim.Region.Physics.Manager;
using OpenSim.Region.Physics.Meshing;
using OpenSim.Region.Physics.ConvexDecompositionDotNet;

using OMV = OpenMetaverse;

namespace OpenSim.Region.Physics.BulletSPlugin
{
public abstract class BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE]";

    public int referenceCount { get; set; }
    public DateTime lastReferenced { get; set; }
    public BulletShape physShapeInfo { get; set; }

    public BSShape()
    {
        referenceCount = 1;
        lastReferenced = DateTime.Now;
        physShapeInfo = new BulletShape();
    }
    public BSShape(BulletShape pShape)
    {
        referenceCount = 1;
        lastReferenced = DateTime.Now;
        physShapeInfo = pShape;
    }

    // Get another reference to this shape.
    public abstract BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim);

    // Called when this shape is being used again.
    // Used internally. External callers should call instance.GetReference() to properly copy/reference
    //       the shape.
    protected virtual void IncrementReference()
    {
        referenceCount++;
        lastReferenced = DateTime.Now;
    }

    // Called when this shape is being used again.
    protected virtual void DecrementReference()
    {
        referenceCount--;
        lastReferenced = DateTime.Now;
    }

    // Release the use of a physical shape.
    public abstract void Dereference(BSScene physicsScene);

    // Return 'true' if there is an allocated physics physical shape under this class instance.
    public virtual bool HasPhysicalShape
    {
        get
        {
            if (physShapeInfo != null)
                return physShapeInfo.HasPhysicalShape;
            return false;
        }
    }
    public virtual BSPhysicsShapeType ShapeType
    {
        get
        {
            BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN;
            if (physShapeInfo != null && physShapeInfo.HasPhysicalShape)
                ret = physShapeInfo.shapeType;
            return ret;
        }
    }

    // Returns a string for debugging that uniquily identifies the memory used by this instance
    public virtual string AddrString
    {
        get
        {
            if (physShapeInfo != null)
                return physShapeInfo.AddrString;
            return "unknown";
        }
    }

    public override string ToString()
    {
        StringBuilder buff = new StringBuilder();
        if (physShapeInfo == null)
        {
            buff.Append("<noPhys");
        }
        else
        {
            buff.Append("<phy=");
            buff.Append(physShapeInfo.ToString());
        }
        buff.Append(",c=");
        buff.Append(referenceCount.ToString());
        buff.Append(">");
        return buff.ToString();
    }

    #region Common shape routines
    // Create a hash of all the shape parameters to be used as a key for this particular shape.
    public static System.UInt64 ComputeShapeKey(OMV.Vector3 size, PrimitiveBaseShape pbs, out float retLod)
    {
        // level of detail based on size and type of the object
        float lod = BSParam.MeshLOD;
        if (pbs.SculptEntry)
            lod = BSParam.SculptLOD;

        // Mega prims usually get more detail because one can interact with shape approximations at this size.
        float maxAxis = Math.Max(size.X, Math.Max(size.Y, size.Z));
        if (maxAxis > BSParam.MeshMegaPrimThreshold)
            lod = BSParam.MeshMegaPrimLOD;

        retLod = lod;
        return pbs.GetMeshKey(size, lod);
    }

    // The creation of a mesh or hull can fail if an underlying asset is not available.
    // There are two cases: 1) the asset is not in the cache and it needs to be fetched;
    //     and 2) the asset cannot be converted (like failed decompression of JPEG2000s).
    //     The first case causes the asset to be fetched. The second case requires
    //     us to not loop forever.
    // Called after creating a physical mesh or hull. If the physical shape was created,
    //     just return.
    public static BulletShape VerifyMeshCreated(BSScene physicsScene, BulletShape newShape, BSPhysObject prim)
    {
        // If the shape was successfully created, nothing more to do
        if (newShape.HasPhysicalShape)
            return newShape;

        // VerifyMeshCreated is called after trying to create the mesh. If we think the asset had been
        //    fetched but we end up here again, the meshing of the asset must have failed.
        // Prevent trying to keep fetching the mesh by declaring failure.
        if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched)
        {
            prim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedMeshing;
            physicsScene.Logger.WarnFormat("{0} Fetched asset would not mesh. prim={1}, texture={2}",
                                            LogHeader, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture);
            physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,setFailed,prim={1},tex={2}",
                                            prim.LocalID, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture);
        }
        else
        {
            // If this mesh has an underlying asset and we have not failed getting it before, fetch the asset
            if (prim.BaseShape.SculptEntry
                && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.FailedAssetFetch
                && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.FailedMeshing
                && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.Waiting
                && prim.BaseShape.SculptTexture != OMV.UUID.Zero
                )
            {
                physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,fetchAsset,objNam={1},tex={2}",
                                            prim.LocalID, prim.PhysObjectName, prim.BaseShape.SculptTexture);
                // Multiple requestors will know we're waiting for this asset
                prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Waiting;

                BSPhysObject xprim = prim;
                Util.FireAndForget(delegate
                    {
                        // physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,inFireAndForget", xprim.LocalID);
                        RequestAssetDelegate assetProvider = physicsScene.RequestAssetMethod;
                        if (assetProvider != null)
                        {
                            BSPhysObject yprim = xprim; // probably not necessary, but, just in case.
                            assetProvider(yprim.BaseShape.SculptTexture, delegate(AssetBase asset)
                            {
                                // physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,assetProviderCallback", xprim.LocalID);
                                bool assetFound = false;
                                string mismatchIDs = String.Empty;  // DEBUG DEBUG
                                if (asset != null && yprim.BaseShape.SculptEntry)
                                {
                                    if (yprim.BaseShape.SculptTexture.ToString() == asset.ID)
                                    {
                                        yprim.BaseShape.SculptData = asset.Data;
                                        // This will cause the prim to see that the filler shape is not the right
                                        //    one and try again to build the object.
                                        // No race condition with the normal shape setting since the rebuild is at taint time.
                                        yprim.PrimAssetState = BSPhysObject.PrimAssetCondition.Fetched;
                                        yprim.ForceBodyShapeRebuild(false /* inTaintTime */);
                                        assetFound = true;
                                    }
                                    else
                                    {
                                        mismatchIDs = yprim.BaseShape.SculptTexture.ToString() + "/" + asset.ID;
                                    }
                                }
                                if (!assetFound)
                                {
                                    yprim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedAssetFetch;
                                }
                                physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,fetchAssetCallback,found={1},isSculpt={2},ids={3}",
                                            yprim.LocalID, assetFound, yprim.BaseShape.SculptEntry, mismatchIDs );
                            });
                        }
                        else
                        {
                            xprim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedAssetFetch;
                            physicsScene.Logger.ErrorFormat("{0} Physical object requires asset but no asset provider. Name={1}",
                                                        LogHeader, physicsScene.Name);
                        }
                    });
            }
            else
            {
                if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch)
                {
                    physicsScene.Logger.WarnFormat("{0} Mesh failed to fetch asset. prim={1}, texture={2}",
                                                LogHeader, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture);
                    physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,wasFailed,prim={1},tex={2}",
                                                prim.LocalID, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture);
                }
                if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing)
                {
                    physicsScene.Logger.WarnFormat("{0} Mesh asset would not mesh. prim={1}, texture={2}",
                                                LogHeader, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture);
                    physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,wasFailedMeshing,prim={1},tex={2}",
                                                prim.LocalID, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture);
                }
            }
         }

        // While we wait for the mesh defining asset to be loaded, stick in a simple box for the object.
        BSShape fillShape = BSShapeNative.GetReference(physicsScene, prim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX);
        physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,boxTempShape", prim.LocalID);

        return fillShape.physShapeInfo;
     }

    public static String UsefulPrimInfo(BSScene pScene, BSPhysObject prim)
    {
        StringBuilder buff = new StringBuilder(prim.PhysObjectName);
        buff.Append("/pos=");
        buff.Append(prim.RawPosition.ToString());
        if (pScene != null)
        {
            buff.Append("/rgn=");
            buff.Append(pScene.Name);
        }
        return buff.ToString();
    }

    #endregion // Common shape routines
}

// ============================================================================================================
public class BSShapeNull : BSShape
{
    public BSShapeNull() : base()
    {
    }
    public static BSShape GetReference() { return new BSShapeNull();  }
    public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) { return new BSShapeNull();  }
    public override void Dereference(BSScene physicsScene) { /* The magic of garbage collection will make this go away */ }
}

// ============================================================================================================
public class BSShapeNative : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE NATIVE]";
    public BSShapeNative(BulletShape pShape) : base(pShape)
    {
    }

    public static BSShape GetReference(BSScene physicsScene, BSPhysObject prim,
                                            BSPhysicsShapeType shapeType, FixedShapeKey shapeKey)
    {
        // Native shapes are not shared and are always built anew.
        return new BSShapeNative(CreatePhysicalNativeShape(physicsScene, prim, shapeType, shapeKey));
    }

    public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim)
    {
        // Native shapes are not shared so we return a new shape.
        BSShape ret = null;
        lock (physShapeInfo)
        {
            ret = new BSShapeNative(CreatePhysicalNativeShape(pPhysicsScene, pPrim,
                                        physShapeInfo.shapeType, (FixedShapeKey)physShapeInfo.shapeKey));
        }
        return ret;
    }

    // Make this reference to the physical shape go away since native shapes are not shared.
    public override void Dereference(BSScene physicsScene)
    {
        // Native shapes are not tracked and are released immediately
        lock (physShapeInfo)
        {
            if (physShapeInfo.HasPhysicalShape)
            {
                physicsScene.DetailLog("{0},BSShapeNative.Dereference,deleteNativeShape,shape={1}", BSScene.DetailLogZero, this);
                physicsScene.PE.DeleteCollisionShape(physicsScene.World, physShapeInfo);
            }
            physShapeInfo.Clear();
            // Garbage collection will free up this instance.
        }
    }

    private static BulletShape CreatePhysicalNativeShape(BSScene physicsScene, BSPhysObject prim,
                                            BSPhysicsShapeType shapeType, FixedShapeKey shapeKey)
    {
        BulletShape newShape;

        ShapeData nativeShapeData = new ShapeData();
        nativeShapeData.Type = shapeType;
        nativeShapeData.ID = prim.LocalID;
        nativeShapeData.Scale = prim.Scale;
        nativeShapeData.Size = prim.Scale;
        nativeShapeData.MeshKey = (ulong)shapeKey;
        nativeShapeData.HullKey = (ulong)shapeKey;

        if (shapeType == BSPhysicsShapeType.SHAPE_CAPSULE)
        {
            newShape = physicsScene.PE.BuildCapsuleShape(physicsScene.World, 1f, 1f, prim.Scale);
            physicsScene.DetailLog("{0},BSShapeNative,capsule,scale={1}", prim.LocalID, prim.Scale);
        }
        else
        {
            newShape = physicsScene.PE.BuildNativeShape(physicsScene.World, nativeShapeData);
        }
        if (!newShape.HasPhysicalShape)
        {
            physicsScene.Logger.ErrorFormat("{0} BuildPhysicalNativeShape failed. ID={1}, shape={2}",
                                    LogHeader, prim.LocalID, shapeType);
        }
        newShape.shapeType = shapeType;
        newShape.isNativeShape = true;
        newShape.shapeKey = (UInt64)shapeKey;
        return newShape;
    }

}

// ============================================================================================================
public class BSShapeMesh : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE MESH]";
    public static Dictionary<System.UInt64, BSShapeMesh> Meshes = new Dictionary<System.UInt64, BSShapeMesh>();

    public BSShapeMesh(BulletShape pShape) : base(pShape)
    {
    }
    public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim)
    {
        float lod;
        System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod);

        BSShapeMesh retMesh = null;
        lock (Meshes)
        {
            if (Meshes.TryGetValue(newMeshKey, out retMesh))
            {
                // The mesh has already been created. Return a new reference to same.
                retMesh.IncrementReference();
            }
            else
            {
                retMesh = new BSShapeMesh(new BulletShape());
                // An instance of this mesh has not been created. Build and remember same.
                BulletShape newShape = retMesh.CreatePhysicalMesh(physicsScene, prim, newMeshKey, prim.BaseShape, prim.Size, lod);

                // Check to see if mesh was created (might require an asset).
                newShape = VerifyMeshCreated(physicsScene, newShape, prim);
                if (!newShape.isNativeShape
                            || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing
                            || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch)
                {
                    // If a mesh was what was created, remember the built shape for later sharing.
                    // Also note that if meshing failed we put it in the mesh list as there is nothing else to do about the mesh.
                    Meshes.Add(newMeshKey, retMesh);
                }

                retMesh.physShapeInfo = newShape;
            }
        }
        physicsScene.DetailLog("{0},BSShapeMesh,getReference,mesh={1},size={2},lod={3}", prim.LocalID, retMesh, prim.Size, lod);
        return retMesh;
    }
    public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim)
    {
        BSShape ret = null;
        // If the underlying shape is native, the actual shape has not been build (waiting for asset)
        //     and we must create a copy of the native shape since they are never shared.
        if (physShapeInfo.HasPhysicalShape && physShapeInfo.isNativeShape)
        {
            // TODO: decide when the native shapes should be freed. Check in Dereference?
            ret = BSShapeNative.GetReference(pPhysicsScene, pPrim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX);
        }
        else
        {
            // Another reference to this shape is just counted.
            IncrementReference();
            ret = this;
        }
        return ret;
    }
    public override void Dereference(BSScene physicsScene)
    {
        lock (Meshes)
        {
            this.DecrementReference();
            physicsScene.DetailLog("{0},BSShapeMesh.Dereference,shape={1}", BSScene.DetailLogZero, this);
            // TODO: schedule aging and destruction of unused meshes.
        }
    }
    // Loop through all the known meshes and return the description based on the physical address.
    public static bool TryGetMeshByPtr(BulletShape pShape, out BSShapeMesh outMesh)
    {
        bool ret = false;
        BSShapeMesh foundDesc = null;
        lock (Meshes)
        {
            foreach (BSShapeMesh sm in Meshes.Values)
            {
                if (sm.physShapeInfo.ReferenceSame(pShape))
                {
                    foundDesc = sm;
                    ret = true;
                    break;
                }

            }
        }
        outMesh = foundDesc;
        return ret;
    }

    public delegate BulletShape CreateShapeCall(BulletWorld world, int indicesCount, int[] indices, int verticesCount, float[] vertices );
    private BulletShape CreatePhysicalMesh(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey,
                                            PrimitiveBaseShape pbs, OMV.Vector3 size, float lod)
    {
        return BSShapeMesh.CreatePhysicalMeshShape(physicsScene, prim, newMeshKey, pbs, size, lod,
                            (w, iC, i, vC, v) => physicsScene.PE.CreateMeshShape(w, iC, i, vC, v) );
    }

    // Code that uses the mesher to create the index/vertices info for a trimesh shape.
    // This is used by the passed 'makeShape' call to create the Bullet mesh shape.
    // The actual build call is passed so this logic can be used by several of the shapes that use a
    //     simple mesh as their base shape.
    public static BulletShape CreatePhysicalMeshShape(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey,
                                            PrimitiveBaseShape pbs, OMV.Vector3 size, float lod, CreateShapeCall makeShape)
    {
        BulletShape newShape = new BulletShape();

        IMesh meshData = null;
        lock (physicsScene.mesher)
        {
            meshData = physicsScene.mesher.CreateMesh(prim.PhysObjectName, pbs, size, lod,
                                            false,  // say it is not physical so a bounding box is not built
                                            false   // do not cache the mesh and do not use previously built versions
                                            );
        }

        if (meshData != null)
        {
            if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched)
            {
                // Release the fetched asset data once it has been used.
                pbs.SculptData = new byte[0];
                prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Unknown;
            }

            int[] indices = meshData.getIndexListAsInt();
            int realIndicesIndex = indices.Length;
            float[] verticesAsFloats = meshData.getVertexListAsFloat();

            if (BSParam.ShouldRemoveZeroWidthTriangles)
            {
                // Remove degenerate triangles. These are triangles with two of the vertices
                //    are the same. This is complicated by the problem that vertices are not
                //    made unique in sculpties so we have to compare the values in the vertex.
                realIndicesIndex = 0;
                for (int tri = 0; tri < indices.Length; tri += 3)
                {
                    // Compute displacements into vertex array for each vertex of the triangle
                    int v1 = indices[tri + 0] * 3;
                    int v2 = indices[tri + 1] * 3;
                    int v3 = indices[tri + 2] * 3;
                // Check to see if any two of the vertices are the same
                    if (!( (  verticesAsFloats[v1 + 0] == verticesAsFloats[v2 + 0]
                           && verticesAsFloats[v1 + 1] == verticesAsFloats[v2 + 1]
                           && verticesAsFloats[v1 + 2] == verticesAsFloats[v2 + 2])
                        || (  verticesAsFloats[v2 + 0] == verticesAsFloats[v3 + 0]
                           && verticesAsFloats[v2 + 1] == verticesAsFloats[v3 + 1]
                           && verticesAsFloats[v2 + 2] == verticesAsFloats[v3 + 2])
                        || (  verticesAsFloats[v1 + 0] == verticesAsFloats[v3 + 0]
                           && verticesAsFloats[v1 + 1] == verticesAsFloats[v3 + 1]
                           && verticesAsFloats[v1 + 2] == verticesAsFloats[v3 + 2]) )
                    )
                    {
                        // None of the vertices of the triangles are the same. This is a good triangle;
                        indices[realIndicesIndex + 0] = indices[tri + 0];
                        indices[realIndicesIndex + 1] = indices[tri + 1];
                        indices[realIndicesIndex + 2] = indices[tri + 2];
                        realIndicesIndex += 3;
                    }
                }
            }
            physicsScene.DetailLog("{0},BSShapeMesh.CreatePhysicalMesh,key={1},origTri={2},realTri={3},numVerts={4}",
                        BSScene.DetailLogZero, newMeshKey.ToString("X"), indices.Length / 3, realIndicesIndex / 3, verticesAsFloats.Length / 3);

            if (realIndicesIndex != 0)
            {
                newShape = makeShape(physicsScene.World, realIndicesIndex, indices, verticesAsFloats.Length / 3, verticesAsFloats);
            }
            else
            {
                // Force the asset condition to 'failed' so we won't try to keep fetching and processing this mesh.
                prim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedMeshing;
                physicsScene.Logger.DebugFormat("{0} All mesh triangles degenerate. Prim={1}", LogHeader, UsefulPrimInfo(physicsScene, prim) );
                physicsScene.DetailLog("{0},BSShapeMesh.CreatePhysicalMesh,allDegenerate,key={1}", prim.LocalID, newMeshKey);
            }
        }
        newShape.shapeKey = newMeshKey;

        return newShape;
    }
}

// ============================================================================================================
public class BSShapeHull : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE HULL]";
    public static Dictionary<System.UInt64, BSShapeHull> Hulls = new Dictionary<System.UInt64, BSShapeHull>();

    public BSShapeHull(BulletShape pShape) : base(pShape)
    {
    }
    public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim)
    {
        float lod;
        System.UInt64 newHullKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod);

        BSShapeHull retHull = null;
        lock (Hulls)
        {
            if (Hulls.TryGetValue(newHullKey, out retHull))
            {
                // The mesh has already been created. Return a new reference to same.
                retHull.IncrementReference();
            }
            else
            {
                retHull = new BSShapeHull(new BulletShape());
                // An instance of this mesh has not been created. Build and remember same.
                BulletShape newShape = retHull.CreatePhysicalHull(physicsScene, prim, newHullKey, prim.BaseShape, prim.Size, lod);

                // Check to see if hull was created (might require an asset).
                newShape = VerifyMeshCreated(physicsScene, newShape, prim);
                if (!newShape.isNativeShape
                            || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing
                            || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch)
                {
                    // If a mesh was what was created, remember the built shape for later sharing.
                    Hulls.Add(newHullKey, retHull);
                }
                retHull.physShapeInfo = newShape;
            }
        }
        physicsScene.DetailLog("{0},BSShapeHull,getReference,hull={1},size={2},lod={3}", prim.LocalID, retHull, prim.Size, lod);
        return retHull;
    }
    public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim)
    {
        BSShape ret = null;
        // If the underlying shape is native, the actual shape has not been build (waiting for asset)
        //     and we must create a copy of the native shape since they are never shared.
        if (physShapeInfo.HasPhysicalShape && physShapeInfo.isNativeShape)
        {
            // TODO: decide when the native shapes should be freed. Check in Dereference?
            ret = BSShapeNative.GetReference(pPhysicsScene, pPrim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX);
        }
        else
        {
            // Another reference to this shape is just counted.
            IncrementReference();
            ret = this;
        }
        return ret;
    }
    public override void Dereference(BSScene physicsScene)
    {
        lock (Hulls)
        {
            this.DecrementReference();
            physicsScene.DetailLog("{0},BSShapeHull.Dereference,shape={1}", BSScene.DetailLogZero, this);
            // TODO: schedule aging and destruction of unused meshes.
        }
    }
    List<ConvexResult> m_hulls;
    private BulletShape CreatePhysicalHull(BSScene physicsScene, BSPhysObject prim, System.UInt64 newHullKey,
                                            PrimitiveBaseShape pbs, OMV.Vector3 size, float lod)
    {
        BulletShape newShape = new BulletShape();

        IMesh meshData = null;
        List<List<OMV.Vector3>> allHulls = null;
        lock (physicsScene.mesher)
        {
            // Pass true for physicalness as this prevents the creation of bounding box which is not needed
            meshData = physicsScene.mesher.CreateMesh(prim.PhysObjectName, pbs, size, lod, true /* isPhysical */, false /* shouldCache */);

            // If we should use the asset's hull info, fetch it out of the locked mesher
            if (meshData != null && BSParam.ShouldUseAssetHulls)
            {
                Meshmerizer realMesher = physicsScene.mesher as Meshmerizer;
                if (realMesher != null)
                {
                    allHulls = realMesher.GetConvexHulls(size);
                }
                if (allHulls == null)
                {
                    physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,assetHulls,noAssetHull", prim.LocalID);
                }
            }
        }

        // If there is hull data in the mesh asset, build the hull from that
        if (allHulls != null && BSParam.ShouldUseAssetHulls)
        {
            int hullCount = allHulls.Count;
            int totalVertices = 1;      // include one for the count of the hulls
            // Using the structure described for HACD hulls, create the memory sturcture
            //     to pass the hull data to the creater.
            foreach (List<OMV.Vector3> hullVerts in allHulls)
            {
                totalVertices += 4;                     // add four for the vertex count and centroid
                totalVertices += hullVerts.Count * 3;   // one vertex is three dimensions
            }
            float[] convHulls = new float[totalVertices];

            convHulls[0] = (float)hullCount;
            int jj = 1;
            foreach (List<OMV.Vector3> hullVerts in allHulls)
            {
                convHulls[jj + 0] = hullVerts.Count;
                convHulls[jj + 1] = 0f;   // centroid x,y,z
                convHulls[jj + 2] = 0f;
                convHulls[jj + 3] = 0f;
                jj += 4;
                foreach (OMV.Vector3 oneVert in hullVerts)
                {
                    convHulls[jj + 0] = oneVert.X;
                    convHulls[jj + 1] = oneVert.Y;
                    convHulls[jj + 2] = oneVert.Z;
                    jj += 3;
                }
            }

            // create the hull data structure in Bullet
            newShape = physicsScene.PE.CreateHullShape(physicsScene.World, hullCount, convHulls);

            physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,assetHulls,hulls={1},totVert={2},shape={3}",
                                        prim.LocalID, hullCount, totalVertices, newShape);
        }

        // If no hull specified in the asset and we should use Bullet's HACD approximation...
        if (!newShape.HasPhysicalShape && BSParam.ShouldUseBulletHACD)
        {
            // Build the hull shape from an existing mesh shape.
            // The mesh should have already been created in Bullet.
            physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,bulletHACD,entry", prim.LocalID);
            BSShape meshShape = BSShapeMesh.GetReference(physicsScene, true, prim);

            if (meshShape.physShapeInfo.HasPhysicalShape)
            {
                HACDParams parms;
                parms.maxVerticesPerHull = BSParam.BHullMaxVerticesPerHull;
                parms.minClusters = BSParam.BHullMinClusters;
                parms.compacityWeight = BSParam.BHullCompacityWeight;
                parms.volumeWeight = BSParam.BHullVolumeWeight;
                parms.concavity = BSParam.BHullConcavity;
                parms.addExtraDistPoints = BSParam.NumericBool(BSParam.BHullAddExtraDistPoints);
                parms.addNeighboursDistPoints = BSParam.NumericBool(BSParam.BHullAddNeighboursDistPoints);
                parms.addFacesPoints = BSParam.NumericBool(BSParam.BHullAddFacesPoints);
                parms.shouldAdjustCollisionMargin = BSParam.NumericBool(BSParam.BHullShouldAdjustCollisionMargin);

                physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,hullFromMesh,beforeCall", prim.LocalID, newShape.HasPhysicalShape);
                newShape = physicsScene.PE.BuildHullShapeFromMesh(physicsScene.World, meshShape.physShapeInfo, parms);
                physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,hullFromMesh,shape={1}", prim.LocalID, newShape);

                // Now done with the mesh shape.
                meshShape.Dereference(physicsScene);
            }
            physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,bulletHACD,exit,hasBody={1}", prim.LocalID, newShape.HasPhysicalShape);
        }

        // If no other hull specifications, use our HACD hull approximation.
        if (!newShape.HasPhysicalShape && meshData != null)
        {
            if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched)
            {
                // Release the fetched asset data once it has been used.
                pbs.SculptData = new byte[0];
                prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Unknown;
            }

            int[] indices = meshData.getIndexListAsInt();
            List<OMV.Vector3> vertices = meshData.getVertexList();

            //format conversion from IMesh format to DecompDesc format
            List<int> convIndices = new List<int>();
            List<float3> convVertices = new List<float3>();
            for (int ii = 0; ii < indices.GetLength(0); ii++)
            {
                convIndices.Add(indices[ii]);
            }
            foreach (OMV.Vector3 vv in vertices)
            {
                convVertices.Add(new float3(vv.X, vv.Y, vv.Z));
            }

            uint maxDepthSplit = (uint)BSParam.CSHullMaxDepthSplit;
            if (BSParam.CSHullMaxDepthSplit != BSParam.CSHullMaxDepthSplitForSimpleShapes)
            {
                // Simple primitive shapes we know are convex so they are better implemented with
                //    fewer hulls.
                // Check for simple shape (prim without cuts) and reduce split parameter if so.
                if (BSShapeCollection.PrimHasNoCuts(pbs))
                {
                    maxDepthSplit = (uint)BSParam.CSHullMaxDepthSplitForSimpleShapes;
                }
            }

            // setup and do convex hull conversion
            m_hulls = new List<ConvexResult>();
            DecompDesc dcomp = new DecompDesc();
            dcomp.mIndices = convIndices;
            dcomp.mVertices = convVertices;
            dcomp.mDepth = maxDepthSplit;
            dcomp.mCpercent = BSParam.CSHullConcavityThresholdPercent;
            dcomp.mPpercent = BSParam.CSHullVolumeConservationThresholdPercent;
            dcomp.mMaxVertices = (uint)BSParam.CSHullMaxVertices;
            dcomp.mSkinWidth = BSParam.CSHullMaxSkinWidth;
            ConvexBuilder convexBuilder = new ConvexBuilder(HullReturn);
            // create the hull into the _hulls variable
            convexBuilder.process(dcomp);

            physicsScene.DetailLog("{0},BSShapeCollection.CreatePhysicalHull,key={1},inVert={2},inInd={3},split={4},hulls={5}",
                                BSScene.DetailLogZero, newHullKey, indices.GetLength(0), vertices.Count, maxDepthSplit, m_hulls.Count);

            // Convert the vertices and indices for passing to unmanaged.
            // The hull information is passed as a large floating point array.
            // The format is:
            //  convHulls[0] = number of hulls
            //  convHulls[1] = number of vertices in first hull
            //  convHulls[2] = hull centroid X coordinate
            //  convHulls[3] = hull centroid Y coordinate
            //  convHulls[4] = hull centroid Z coordinate
            //  convHulls[5] = first hull vertex X
            //  convHulls[6] = first hull vertex Y
            //  convHulls[7] = first hull vertex Z
            //  convHulls[8] = second hull vertex X
            //  ...
            //  convHulls[n] = number of vertices in second hull
            //  convHulls[n+1] = second hull centroid X coordinate
            //  ...
            //
            // TODO: is is very inefficient. Someday change the convex hull generator to return
            //   data structures that do not need to be converted in order to pass to Bullet.
            //   And maybe put the values directly into pinned memory rather than marshaling.
            int hullCount = m_hulls.Count;
            int totalVertices = 1;          // include one for the count of the hulls
            foreach (ConvexResult cr in m_hulls)
            {
                totalVertices += 4;                         // add four for the vertex count and centroid
                totalVertices += cr.HullIndices.Count * 3;  // we pass just triangles
            }
            float[] convHulls = new float[totalVertices];

            convHulls[0] = (float)hullCount;
            int jj = 1;
            foreach (ConvexResult cr in m_hulls)
            {
                // copy vertices for index access
                float3[] verts = new float3[cr.HullVertices.Count];
                int kk = 0;
                foreach (float3 ff in cr.HullVertices)
                {
                    verts[kk++] = ff;
                }

                // add to the array one hull's worth of data
                convHulls[jj++] = cr.HullIndices.Count;
                convHulls[jj++] = 0f;   // centroid x,y,z
                convHulls[jj++] = 0f;
                convHulls[jj++] = 0f;
                foreach (int ind in cr.HullIndices)
                {
                    convHulls[jj++] = verts[ind].x;
                    convHulls[jj++] = verts[ind].y;
                    convHulls[jj++] = verts[ind].z;
                }
            }
            // create the hull data structure in Bullet
            newShape = physicsScene.PE.CreateHullShape(physicsScene.World, hullCount, convHulls);
        }
        newShape.shapeKey = newHullKey;
        return newShape;
    }
    // Callback from convex hull creater with a newly created hull.
    // Just add it to our collection of hulls for this shape.
    private void HullReturn(ConvexResult result)
    {
        m_hulls.Add(result);
        return;
    }
    // Loop through all the known hulls and return the description based on the physical address.
    public static bool TryGetHullByPtr(BulletShape pShape, out BSShapeHull outHull)
    {
        bool ret = false;
        BSShapeHull foundDesc = null;
        lock (Hulls)
        {
            foreach (BSShapeHull sh in Hulls.Values)
            {
                if (sh.physShapeInfo.ReferenceSame(pShape))
                {
                    foundDesc = sh;
                    ret = true;
                    break;
                }

            }
        }
        outHull = foundDesc;
        return ret;
    }
}

// ============================================================================================================
public class BSShapeCompound : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE COMPOUND]";
    public BSShapeCompound(BulletShape pShape) : base(pShape)
    {
    }
    public static BSShape GetReference(BSScene physicsScene)
    {
        // Base compound shapes are not shared so this returns a raw shape.
        // A built compound shape can be reused in linksets.
        return new BSShapeCompound(CreatePhysicalCompoundShape(physicsScene));
    }
    public override BSShape GetReference(BSScene physicsScene, BSPhysObject prim)
    {
        // Calling this reference means we want another handle to an existing compound shape
        //     (usually linksets) so return this copy.
        IncrementReference();
        return this;
    }
    // Dereferencing a compound shape releases the hold on all the child shapes.
    public override void Dereference(BSScene physicsScene)
    {
        lock (physShapeInfo)
        {
            this.DecrementReference();
            physicsScene.DetailLog("{0},BSShapeCompound.Dereference,shape={1}", BSScene.DetailLogZero, this);
            if (referenceCount <= 0)
            {
                if (!physicsScene.PE.IsCompound(physShapeInfo))
                {
                    // Failed the sanity check!!
                    physicsScene.Logger.ErrorFormat("{0} Attempt to free a compound shape that is not compound!! type={1}, ptr={2}",
                                                LogHeader, physShapeInfo.shapeType, physShapeInfo.AddrString);
                    physicsScene.DetailLog("{0},BSShapeCollection.DereferenceCompound,notACompoundShape,type={1},ptr={2}",
                                                BSScene.DetailLogZero, physShapeInfo.shapeType, physShapeInfo.AddrString);
                    return;
                }

                int numChildren = physicsScene.PE.GetNumberOfCompoundChildren(physShapeInfo);
                physicsScene.DetailLog("{0},BSShapeCollection.DereferenceCompound,shape={1},children={2}",
                                        BSScene.DetailLogZero, physShapeInfo, numChildren);

                // Loop through all the children dereferencing each.
                for (int ii = numChildren - 1; ii >= 0; ii--)
                {
                    BulletShape childShape = physicsScene.PE.RemoveChildShapeFromCompoundShapeIndex(physShapeInfo, ii);
                    DereferenceAnonCollisionShape(physicsScene, childShape);
                }
                physicsScene.PE.DeleteCollisionShape(physicsScene.World, physShapeInfo);
            }
        }
    }
    private static BulletShape CreatePhysicalCompoundShape(BSScene physicsScene)
    {
        BulletShape cShape = physicsScene.PE.CreateCompoundShape(physicsScene.World, false);
        return cShape;
    }
    // Sometimes we have a pointer to a collision shape but don't know what type it is.
    // Figure out type and call the correct dereference routine.
    // Called at taint-time.
    private void DereferenceAnonCollisionShape(BSScene physicsScene, BulletShape pShape)
    {
        // TODO: figure a better way to go through all the shape types and find a possible instance.
        BSShapeMesh meshDesc;
        if (BSShapeMesh.TryGetMeshByPtr(pShape, out meshDesc))
        {
            meshDesc.Dereference(physicsScene);
        }
        else
        {
            BSShapeHull hullDesc;
            if (BSShapeHull.TryGetHullByPtr(pShape, out hullDesc))
            {
                hullDesc.Dereference(physicsScene);
            }
            else
            {
                BSShapeConvexHull chullDesc;
                if (BSShapeConvexHull.TryGetHullByPtr(pShape, out chullDesc))
                {
                    chullDesc.Dereference(physicsScene);
                }
                else
                {
                    BSShapeGImpact gImpactDesc;
                    if (BSShapeGImpact.TryGetGImpactByPtr(pShape, out gImpactDesc))
                    {
                        gImpactDesc.Dereference(physicsScene);
                    }
                    else
                    {
                        // Didn't find it in the lists of specific types. It could be compound.
                        if (physicsScene.PE.IsCompound(pShape))
                        {
                            BSShapeCompound recursiveCompound = new BSShapeCompound(pShape);
                            recursiveCompound.Dereference(physicsScene);
                        }
                        else
                        {
                            // If none of the above, maybe it is a simple native shape.
                            if (physicsScene.PE.IsNativeShape(pShape))
                            {
                                BSShapeNative nativeShape = new BSShapeNative(pShape);
                                nativeShape.Dereference(physicsScene);
                            }
                        }
                    }
                }
            }
        }
    }
}

// ============================================================================================================
public class BSShapeConvexHull : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE CONVEX HULL]";
    public static Dictionary<System.UInt64, BSShapeConvexHull> ConvexHulls = new Dictionary<System.UInt64, BSShapeConvexHull>();

    public BSShapeConvexHull(BulletShape pShape) : base(pShape)
    {
    }
    public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim)
    {
        float lod;
        System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod);

        physicsScene.DetailLog("{0},BSShapeConvexHull,getReference,newKey={1},size={2},lod={3}",
                                prim.LocalID, newMeshKey.ToString("X"), prim.Size, lod);

        BSShapeConvexHull retConvexHull = null;
        lock (ConvexHulls)
        {
            if (ConvexHulls.TryGetValue(newMeshKey, out retConvexHull))
            {
                // The mesh has already been created. Return a new reference to same.
                retConvexHull.IncrementReference();
            }
            else
            {
                retConvexHull = new BSShapeConvexHull(new BulletShape());
                BulletShape convexShape = null;

                // Get a handle to a mesh to build the hull from
                BSShape baseMesh = BSShapeMesh.GetReference(physicsScene, false /* forceRebuild */, prim);
                if (baseMesh.physShapeInfo.isNativeShape)
                {
                    // We get here if the mesh was not creatable. Could be waiting for an asset from the disk.
                    // In the short term, we return the native shape and a later ForceBodyShapeRebuild should
                    //     get back to this code with a buildable mesh.
                    // TODO: not sure the temp native shape is freed when the mesh is rebuilt. When does this get freed?
                    convexShape = baseMesh.physShapeInfo;
                }
                else
                {
                    convexShape = physicsScene.PE.BuildConvexHullShapeFromMesh(physicsScene.World, baseMesh.physShapeInfo);
                    convexShape.shapeKey = newMeshKey;
                    ConvexHulls.Add(convexShape.shapeKey, retConvexHull);
                }

                // Done with the base mesh
                baseMesh.Dereference(physicsScene);

                retConvexHull.physShapeInfo = convexShape;
            }
        }
        return retConvexHull;
    }
    public override BSShape GetReference(BSScene physicsScene, BSPhysObject prim)
    {
        // Calling this reference means we want another handle to an existing shape
        //     (usually linksets) so return this copy.
        IncrementReference();
        return this;
    }
    // Dereferencing a compound shape releases the hold on all the child shapes.
    public override void Dereference(BSScene physicsScene)
    {
        lock (ConvexHulls)
        {
            this.DecrementReference();
            physicsScene.DetailLog("{0},BSShapeConvexHull.Dereference,shape={1}", BSScene.DetailLogZero, this);
            // TODO: schedule aging and destruction of unused meshes.
        }
    }
    // Loop through all the known hulls and return the description based on the physical address.
    public static bool TryGetHullByPtr(BulletShape pShape, out BSShapeConvexHull outHull)
    {
        bool ret = false;
        BSShapeConvexHull foundDesc = null;
        lock (ConvexHulls)
        {
            foreach (BSShapeConvexHull sh in ConvexHulls.Values)
            {
                if (sh.physShapeInfo.ReferenceSame(pShape))
                {
                    foundDesc = sh;
                    ret = true;
                    break;
                }

            }
        }
        outHull = foundDesc;
        return ret;
    }
}
// ============================================================================================================
public class BSShapeGImpact : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE GIMPACT]";
    public static Dictionary<System.UInt64, BSShapeGImpact> GImpacts = new Dictionary<System.UInt64, BSShapeGImpact>();

    public BSShapeGImpact(BulletShape pShape) : base(pShape)
    {
    }
    public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim)
    {
        float lod;
        System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod);

        physicsScene.DetailLog("{0},BSShapeGImpact,getReference,newKey={1},size={2},lod={3}",
                                prim.LocalID, newMeshKey.ToString("X"), prim.Size, lod);

        BSShapeGImpact retGImpact = null;
        lock (GImpacts)
        {
            if (GImpacts.TryGetValue(newMeshKey, out retGImpact))
            {
                // The mesh has already been created. Return a new reference to same.
                retGImpact.IncrementReference();
            }
            else
            {
                retGImpact = new BSShapeGImpact(new BulletShape());
                BulletShape newShape = retGImpact.CreatePhysicalGImpact(physicsScene, prim, newMeshKey, prim.BaseShape, prim.Size, lod);

                // Check to see if mesh was created (might require an asset).
                newShape = VerifyMeshCreated(physicsScene, newShape, prim);
                newShape.shapeKey = newMeshKey;
                if (!newShape.isNativeShape 
                            || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing
                            || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch)
                {
                    // If a mesh was what was created, remember the built shape for later sharing.
                    // Also note that if meshing failed we put it in the mesh list as there is nothing
                    //         else to do about the mesh.
                    GImpacts.Add(newMeshKey, retGImpact);
                }

                retGImpact.physShapeInfo = newShape;
            }
        }
        return retGImpact;
    }

    private BulletShape CreatePhysicalGImpact(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey,
                                            PrimitiveBaseShape pbs, OMV.Vector3 size, float lod)
    {
        return BSShapeMesh.CreatePhysicalMeshShape(physicsScene, prim, newMeshKey, pbs, size, lod,
                            (w, iC, i, vC, v) => physicsScene.PE.CreateGImpactShape(w, iC, i, vC, v) );
    }

    public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim)
    {
        BSShape ret = null;
        // If the underlying shape is native, the actual shape has not been build (waiting for asset)
        //     and we must create a copy of the native shape since they are never shared.
        if (physShapeInfo.HasPhysicalShape && physShapeInfo.isNativeShape)
        {
            // TODO: decide when the native shapes should be freed. Check in Dereference?
            ret = BSShapeNative.GetReference(pPhysicsScene, pPrim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX);
        }
        else
        {
            // Another reference to this shape is just counted.
            IncrementReference();
            ret = this;
        }
        return ret;
    }
    // Dereferencing a compound shape releases the hold on all the child shapes.
    public override void Dereference(BSScene physicsScene)
    {
        lock (GImpacts)
        {
            this.DecrementReference();
            physicsScene.DetailLog("{0},BSShapeGImpact.Dereference,shape={1}", BSScene.DetailLogZero, this);
            // TODO: schedule aging and destruction of unused meshes.
        }
    }
    // Loop through all the known hulls and return the description based on the physical address.
    public static bool TryGetGImpactByPtr(BulletShape pShape, out BSShapeGImpact outHull)
    {
        bool ret = false;
        BSShapeGImpact foundDesc = null;
        lock (GImpacts)
        {
            foreach (BSShapeGImpact sh in GImpacts.Values)
            {
                if (sh.physShapeInfo.ReferenceSame(pShape))
                {
                    foundDesc = sh;
                    ret = true;
                    break;
                }

            }
        }
        outHull = foundDesc;
        return ret;
    }
}

// ============================================================================================================
public class BSShapeAvatar : BSShape
{
    private static string LogHeader = "[BULLETSIM SHAPE AVATAR]";
    public BSShapeAvatar() : base()
    {
    }
    public static BSShape GetReference(BSPhysObject prim)
    {
        return new BSShapeNull();
    }
    public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim)
    {
        return new BSShapeNull();
    }
    public override void Dereference(BSScene physicsScene) { }

    // From the front:
    //     A---A
    //    /     \
    //   B-------B
    //  /         \        +Z
    // C-----------C        |
    // \           /   -Y --+-- +Y
    //  \         /         |
    //   \       /         -Z
    //    D-----D
    //     \   /
    //      E-E

    // From the top A and E are just lines.
    //              B, C and D are hexagons:
    //
    //     C1--C2            +X
    //    /      \            |
    //  C0        C3     -Y --+-- +Y
    //    \      /            |
    //     C5--C4            -X

    // Zero goes directly through the middle so the offsets are from that middle axis
    //     and up and down from a middle horizon (A and E are the same distance from the zero).
    // The height, width and depth is one. All scaling is done by the simulator.

    // Z component -- how far the level is from the middle zero
    private const float Aup = 0.5f;
    private const float Bup = 0.4f;
    private const float Cup = 0.3f;
    private const float Dup = -0.4f;
    private const float Eup = -0.5f;

    // Y component -- distance from center to x0 and x3
    private const float Awid = 0.25f;
    private const float Bwid = 0.3f;
    private const float Cwid = 0.5f;
    private const float Dwid = 0.3f;
    private const float Ewid = 0.2f;

    // Y component -- distance from center to x1, x2, x4 and x5
    private const float Afwid = 0.0f;
    private const float Bfwid = 0.2f;
    private const float Cfwid = 0.4f;
    private const float Dfwid = 0.2f;
    private const float Efwid = 0.0f;

    // X component -- distance from zero to the front or back of a level
    private const float Adep = 0f;
    private const float Bdep = 0.3f;
    private const float Cdep = 0.5f;
    private const float Ddep = 0.2f;
    private const float Edep = 0f;

    private OMV.Vector3[] avatarVertices = {
           new OMV.Vector3( 0.0f, -Awid,  Aup),   // A0
           new OMV.Vector3( 0.0f, +Awid,  Aup),   // A3

           new OMV.Vector3( 0.0f, -Bwid,  Bup),   // B0
           new OMV.Vector3(+Bdep, -Bfwid, Bup),   // B1
           new OMV.Vector3(+Bdep, +Bfwid, Bup),   // B2
           new OMV.Vector3( 0.0f, +Bwid,  Bup),   // B3
           new OMV.Vector3(-Bdep, +Bfwid, Bup),   // B4
           new OMV.Vector3(-Bdep, -Bfwid, Bup),   // B5

           new OMV.Vector3( 0.0f, -Cwid,  Cup),   // C0
           new OMV.Vector3(+Cdep, -Cfwid, Cup),   // C1
           new OMV.Vector3(+Cdep, +Cfwid, Cup),   // C2
           new OMV.Vector3( 0.0f, +Cwid,  Cup),   // C3
           new OMV.Vector3(-Cdep, +Cfwid, Cup),   // C4
           new OMV.Vector3(-Cdep, -Cfwid, Cup),   // C5

           new OMV.Vector3( 0.0f, -Dwid,  Dup),   // D0
           new OMV.Vector3(+Ddep, -Dfwid, Dup),   // D1
           new OMV.Vector3(+Ddep, +Dfwid, Dup),   // D2
           new OMV.Vector3( 0.0f, +Dwid,  Dup),   // D3
           new OMV.Vector3(-Ddep, +Dfwid, Dup),   // D4
           new OMV.Vector3(-Ddep, -Dfwid, Dup),   // D5

           new OMV.Vector3( 0.0f, -Ewid,  Eup),   // E0
           new OMV.Vector3( 0.0f, +Ewid,  Eup),   // E3
    };

    // Offsets of the vertices in the vertices array
    private enum Ind : int
    {
        A0, A3,
        B0, B1, B2, B3, B4, B5,
        C0, C1, C2, C3, C4, C5,
        D0, D1, D2, D3, D4, D5,
        E0, E3
    }

    // Comments specify trianges and quads in clockwise direction
    private Ind[] avatarIndices = {
        Ind.A0, Ind.B0, Ind.B1,                         // A0,B0,B1
        Ind.A0, Ind.B1, Ind.B2, Ind.B2, Ind.A3, Ind.A0, // A0,B1,B2,A3
        Ind.A3, Ind.B2, Ind.B3,                         // A3,B2,B3
        Ind.A3, Ind.B3, Ind.B4,                         // A3,B3,B4
        Ind.A3, Ind.B4, Ind.B5, Ind.B5, Ind.A0, Ind.A3, // A3,B4,B5,A0
        Ind.A0, Ind.B5, Ind.B0,                         // A0,B5,B0

        Ind.B0, Ind.C0, Ind.C1, Ind.C1, Ind.B1, Ind.B0, // B0,C0,C1,B1
        Ind.B1, Ind.C1, Ind.C2, Ind.C2, Ind.B2, Ind.B1, // B1,C1,C2,B2
        Ind.B2, Ind.C2, Ind.C3, Ind.C3, Ind.B3, Ind.B2, // B2,C2,C3,B3
        Ind.B3, Ind.C3, Ind.C4, Ind.C4, Ind.B4, Ind.B3, // B3,C3,C4,B4
        Ind.B4, Ind.C4, Ind.C5, Ind.C5, Ind.B5, Ind.B4, // B4,C4,C5,B5
        Ind.B5, Ind.C5, Ind.C0, Ind.C0, Ind.B0, Ind.B5, // B5,C5,C0,B0

        Ind.C0, Ind.D0, Ind.D1, Ind.D1, Ind.C1, Ind.C0, // C0,D0,D1,C1
        Ind.C1, Ind.D1, Ind.D2, Ind.D2, Ind.C2, Ind.C1, // C1,D1,D2,C2
        Ind.C2, Ind.D2, Ind.D3, Ind.D3, Ind.C3, Ind.C2, // C2,D2,D3,C3
        Ind.C3, Ind.D3, Ind.D4, Ind.D4, Ind.C4, Ind.C3, // C3,D3,D4,C4
        Ind.C4, Ind.D4, Ind.D5, Ind.D5, Ind.C5, Ind.C4, // C4,D4,D5,C5
        Ind.C5, Ind.D5, Ind.D0, Ind.D0, Ind.C0, Ind.C5, // C5,D5,D0,C0

        Ind.E0, Ind.D0, Ind.D1,                         // E0,D0,D1
        Ind.E0, Ind.D1, Ind.D2, Ind.D2, Ind.E3, Ind.E0, // E0,D1,D2,E3
        Ind.E3, Ind.D2, Ind.D3,                         // E3,D2,D3
        Ind.E3, Ind.D3, Ind.D4,                         // E3,D3,D4
        Ind.E3, Ind.D4, Ind.D5, Ind.D5, Ind.E0, Ind.E3, // E3,D4,D5,E0
        Ind.E0, Ind.D5, Ind.D0,                         // E0,D5,D0

    };

}
}