// Loads the given OBJ file, creates a RigidBody with the given mess, and places
    // at the origin of the ground.
    protected void PrepareSimObj(string objFile, float mass, BulletSharp.Math.Vector3 inertia)
    {
        Debug.Log("Loading " + objFile + "...");
        // Load wavefront file
        OBJLoader.OBJMesh objloadermesh = OBJLoader.LoadOBJMesh(objFile);
        Debug.Assert(objloadermesh.vertices.Count > 0);
        Debug.Assert(objloadermesh.faces.Count > 0);
        //		Debug.Log("VERTS: " + objloadermesh.vertices.Count.ToString());
        //		Debug.Log("FACES: " + objloadermesh.faces.Count.ToString());

        m_btmesh = DataGenUtils.BulletMeshFromUnity(objloadermesh);
        Debug.Assert(m_btmesh.vertices.Length > 0);
        Debug.Assert(m_btmesh.indices.Length > 0);
        //		Debug.Log("btVERTS: " + (btmesh.vertices.Length / 3).ToString());
        //		Debug.Log("btFACES: " + (btmesh.indices.Length / 3).ToString());

        // Create a GImpactMeshShape for collider
        var triVtxarray = new TriangleIndexVertexArray(m_btmesh.indices, m_btmesh.vertices);

        m_cs = new GImpactMeshShape(triVtxarray);
        m_cs.LocalScaling = new BulletSharp.Math.Vector3(1);
        m_cs.Margin       = bodyMargin;
        m_cs.UpdateBound();
        AddCollisionShape(m_cs);

        // move it up so resting on the ground plane
        float miny = float.MaxValue;
        float cury;

        for (int i = 0; i < objloadermesh.vertices.Count; i++)
        {
            cury = objloadermesh.vertices[i][1];
            if (cury < miny)
            {
                miny = cury;
            }
        }
        miny               = -miny;
        m_rbInitTransVec   = new BulletSharp.Math.Vector3(0, miny + bodyMargin + m_groundMargin, 0);
        m_rbInitTrans      = Matrix.Translation(m_rbInitTransVec);   // * Matrix.RotationY(Random.Range(0.0f, 360.0f));
        m_rb               = CreateRigidBody(mass, inertia, m_rbInitTrans, m_cs, bodyMat, bodyFriction, viz: RENDER_MODE);
        m_rb.AngularFactor = angularFactor;
        m_rb.SetSleepingThresholds(linearSleepThresh, angularSleepThresh);
        if (DEBUG)
        {
            Debug.Log("LOADED MOMENT: " + m_rb.LocalInertia.ToString());
        }
        // if (DEBUG) Debug.Log("WORLD MOMENT: " + m_rb.InvInertiaTensorWorld.ToString());
//		Debug.Log("Min y: " + (-miny).ToString());
        if (DEBUG)
        {
            Debug.Log(m_rb.CenterOfMassPosition.ToString());
        }
    }
    protected void SetupSim()
    {
        // randomly choose scaling before resetting rigid body
        float randomMass = 0;

        BulletSharp.Math.Vector3 randomInertia;
        if (randomScale)
        {
            if (varyScale)
            {
                m_pclScale.x = Random.Range(this.scaleMin, this.scaleMax);
                m_pclScale.y = Random.Range(this.scaleMin, this.scaleMax);
                m_pclScale.z = Random.Range(this.scaleMin, this.scaleMax);
            }
            else
            {
                float uniformScale = Random.Range(this.scaleMin, this.scaleMax);
                m_pclScale.x = uniformScale;
                m_pclScale.y = uniformScale;
                m_pclScale.z = uniformScale;
            }
            //// z can't be more than thrice or less than half of x scale
            //float zmin = Mathf.Max(this.scaleMin, 0.5f * m_pclScale.x);
            //float zmax = Mathf.Min(this.scaleMax, 3.0f * m_pclScale.x);
            //randomScale = Random.Range(zmin, zmax);
            //m_pclScale.z = randomScale;
            //// y can't be greater than 2 times the smallest of x and z
            //float ymax = 2.0f * Mathf.Min(m_pclScale.x, m_pclScale.z);
            //randomScale = Random.Range(this.scaleMin, Mathf.Min(ymax, this.scaleMax));
            //m_pclScale.y = randomScale;

            if (DEBUG)
            {
                Debug.Log("Scaling by " + m_pclScale.ToString());
            }

            // randomMass = m_masses[m_curObjIdx] * m_pclScale.x * m_pclScale.y * m_pclScale.z;
            float randomDensity;
            if (useConstantDensity)
            {
                // density is constant so mass must scale with volume
                randomDensity = densityMin;
                randomMass    = randomDensity * m_masses[m_curObjIdx] * m_pclScale.x * m_pclScale.y * m_pclScale.z;
            }
            else
            {
                randomDensity = Random.Range(densityMin, densityMax);
                randomMass    = randomDensity * m_masses[m_curObjIdx];
            }
            // inertia must scale with volume no matter if the density is constant or not
            BulletSharp.Math.Vector3 objInertiaInfo = m_inertias[m_curObjIdx];
            float scalexyz = m_pclScale.x * m_pclScale.y * m_pclScale.z;
            float scalex2  = m_pclScale.x * m_pclScale.x;
            float scaley2  = m_pclScale.y * m_pclScale.y;
            float scalez2  = m_pclScale.z * m_pclScale.z;
            float inertiax = randomDensity * scalexyz * (scaley2 * objInertiaInfo[1] + scalez2 * objInertiaInfo[2]);
            float inertiay = randomDensity * scalexyz * (scalex2 * objInertiaInfo[0] + scalez2 * objInertiaInfo[2]);
            float inertiaz = randomDensity * scalexyz * (scalex2 * objInertiaInfo[0] + scaley2 * objInertiaInfo[1]);
            randomInertia = new BulletSharp.Math.Vector3(inertiax, inertiay, inertiaz);

            // need to completely destory rigid body because need new mass/moment of inertia
            DestroySimObj();

            DataGenUtils.BulletOBJMesh scaledMesh = m_btmesh.Scale(m_pclScale.x, m_pclScale.y, m_pclScale.z);
            var triVtxarray = new TriangleIndexVertexArray(scaledMesh.indices, scaledMesh.vertices);
            m_cs = new GImpactMeshShape(triVtxarray);
            m_cs.LocalScaling = new BulletSharp.Math.Vector3(1);
            m_cs.Margin       = bodyMargin;
            m_cs.UpdateBound();
            AddCollisionShape(m_cs);

            // move it up so resting on the ground plane
            float miny = float.MaxValue;
            float maxz = float.MinValue;
            float cury;
            float curz;
            for (int i = 0; i < scaledMesh.vertices.Length / 3; i++)
            {
                cury = scaledMesh.vertices[i * 3 + 1];
                if (cury < miny)
                {
                    miny = cury;
                }
                curz = scaledMesh.vertices[i * 3 + 2];
                if (curz > maxz)
                {
                    maxz = curz;
                }
            }
            miny             = -miny;
            m_rbInitTransVec = new BulletSharp.Math.Vector3(0, miny + bodyMargin + m_groundMargin, 0);
            m_rbInitTrans    = Matrix.Translation(m_rbInitTransVec);          //* Matrix.RotationY(Random.Range(0.0f, 360.0f));

            //float gtInertiaX = (1.0f / 12.0f) * randomMass * (3.0f * maxz * maxz + (2.0f * miny) * (2.0f * miny));
            //float gtInertiaZ = gtInertiaX;
            //float gtInertiaY = 0.5f * randomMass * maxz * maxz;
            //BulletSharp.Math.Vector3 gtInertia = new BulletSharp.Math.Vector3(gtInertiaX, gtInertiaY, gtInertiaZ);
            //Debug.Log("GT INERTIA: " + gtInertia.ToString());
            //randomInertia = gtInertia;

            m_rb = CreateRigidBody(randomMass, randomInertia, m_rbInitTrans, m_cs, bodyMat, bodyFriction, viz: RENDER_MODE);
            //m_rb = CreateRigidBody(randomMass, m_rbInitTrans, m_cs, bodyMat, bodyFriction);
            m_rb.AngularFactor = angularFactor;
            m_rb.SetSleepingThresholds(linearSleepThresh, angularSleepThresh);

            m_mass    = randomMass;
            m_inertia = randomInertia;
            m_density = randomDensity;
        }
        else
        {
            // using the same mesh just need to choose a new density
            // steps for determinism
            // https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=3143


            float randomDensity;
            if (useConstantDensity)
            {
                randomDensity = densityMin;
            }
            else
            {
                randomDensity = Random.Range(densityMin, densityMax);
            }
            randomMass = randomDensity * m_masses[m_curObjIdx];
            BulletSharp.Math.Vector3 objInertiaInfo = m_inertias[m_curObjIdx];
            float inertiax = randomDensity * (objInertiaInfo[1] + objInertiaInfo[2]);
            float inertiay = randomDensity * (objInertiaInfo[0] + objInertiaInfo[2]);
            float inertiaz = randomDensity * (objInertiaInfo[0] + objInertiaInfo[1]);
            randomInertia = new BulletSharp.Math.Vector3(inertiax, inertiay, inertiaz);

            m_rb.SetMassProps(randomMass, randomInertia);

            m_rbInitTrans      = Matrix.Translation(m_rbInitTransVec);       // * Matrix.RotationY(Random.Range(0.0f, 360.0f));
            m_rb               = ResetRigidBody(m_rb, randomMass, randomInertia, m_rbInitTrans, m_cs, bodyFriction);
            m_rb.AngularFactor = angularFactor;
            m_rb.SetSleepingThresholds(linearSleepThresh, angularSleepThresh);

            // HingeConstraint hingeConstraint = new HingeConstraint(m_rb, new BulletSharp.Math.Vector3(0.0f), new BulletSharp.Math.Vector3(0.0f, 1.0f, 0.0f), false);
            // m_world.AddConstraint(hingeConstraint);

            // DestroySimObj(); // have to do this to set mass properties but can reuse previously calculated everything else
            // if (m_cs == null) Debug.Log("NOT NULL");
            // m_rb = CreateRigidBody(randomMass, randomInertia, m_rbInitTrans, m_cs, bodyMat, bodyFriction);
            // m_rb.AngularFactor = new BulletSharp.Math.Vector3(angularFactor);
            // m_rb.SetSleepingThresholds(linearSleepThresh, angularSleepThresh);

            m_mass    = randomMass;
            m_inertia = randomInertia;
            m_density = randomDensity;
        }

        m_stepCount = 0;

        m_broadphase.ResetPool(m_colDispatcher);
        m_solver.Reset();

        float curMass = 1.0f / m_rb.InvMass;

        if (DEBUG)
        {
            Debug.Log("Mass: " + curMass.ToString());
        }
        if (DEBUG)
        {
            Debug.Log("LOCAL MOMENT: " + m_rb.LocalInertia.ToString());
        }
        if (DEBUG)
        {
            Debug.Log("COM " + m_rb.CenterOfMassPosition.ToString());
        }
        if (DEBUG)
        {
            Debug.Log("Density " + m_density.ToString());
        }

        // determine impulse position
        ClosestRayResultCallback cb;

        BulletSharp.Math.Vector3 vertexNormal = new BulletSharp.Math.Vector3();
        int missCount = 0;

        do
        {
            // choose random vertex to apply force to
            // pick a random point around in the plane around y position
            float   offsetx   = UnityEngine.Random.Range(-100.0f, 100.0f);
            float   offsetz   = UnityEngine.Random.Range(-100.0f, 100.0f);
            Vector2 offsetvec = new Vector2(offsetx, offsetz);
            // offsetvec.Normalize();
            //float relForceHeight = 0.75f;
            UnityEngine.Vector3 offsetPt = new UnityEngine.Vector3(offsetvec[0],
                                                                   m_rb.CenterOfMassPosition.Y,
                                                                   offsetvec[1]);

            BulletSharp.Math.Vector3 btOffsetPt = BSExtensionMethods2.ToBullet(offsetPt);
            BulletSharp.Math.Vector3 btInnerPt  = m_rb.CenterOfMassPosition;
            cb = new ClosestRayResultCallback(ref btOffsetPt, ref btInnerPt);

            // Debug.DrawLine(BSExtensionMethods2.ToUnity(btInnerPt), offsetPt, Color.red, 2.0f);

            m_world.RayTest(btOffsetPt, btInnerPt, cb);
            if (cb.HasHit)
            {
                m_forcePoint = cb.HitPointWorld;
                vertexNormal = cb.HitNormalWorld;
            }
            else
            {
                missCount++;
                //Debug.Log("ERROR - couldn't find point to apply force to. Retrying...");
                //return;
            }
        } while (!cb.HasHit);

        if (DEBUG)
        {
            Debug.Log("Missed impulse " + missCount.ToString() + " times.");
        }
        if (DEBUG)
        {
            Debug.LogFormat("ForcePoint: " + m_forcePoint.ToString());
        }

        // get force vector
        // loop until force is applied to outside of object
        UnityEngine.Vector3 uForceVec = new UnityEngine.Vector3();

        // initialize force vector to coincide with center of mass
        BulletSharp.Math.Vector3 btForceVec = m_rb.CenterOfMassPosition - m_forcePoint;
        // then randomly vary it within the x/z plane to be within the specified distance
        BulletSharp.Math.Vector3 btVariationVec = new BulletSharp.Math.Vector3(-btForceVec[2], 0.0f, btForceVec[0]);
        btVariationVec.Normalize();
        float varyForce;

        BulletSharp.Math.Vector3 proposedForceVec;
        do
        {
            varyForce        = UnityEngine.Random.Range(-forceDistMax, forceDistMax);
            proposedForceVec = btVariationVec * varyForce + btForceVec;
        } while (proposedForceVec.Dot(vertexNormal) >= 0);         // must also be on the outside of the object
        btForceVec = proposedForceVec;
        btForceVec.Normalize();
        uForceVec = BSExtensionMethods2.ToUnity(btForceVec);
        if (DEBUG)
        {
            Debug.Log("FORCE DIST: " + varyForce.ToString());
        }

        //UnityEngine.Vector3 uVtxNormal = BSExtensionMethods2.ToUnity(vertexNormal);
        //uVtxNormal.Normalize();
        //do
        //{
        //	float forcex = UnityEngine.Random.Range(-1.0f, 1.0f);
        //	float forcez = UnityEngine.Random.Range(-1.0f, 1.0f);
        //	uForceVec.Set(forcex, 0.0f, forcez);
        //	uForceVec.Normalize();
        //} while (UnityEngine.Vector3.Dot(uForceVec, uVtxNormal) >= 0);
        // random constrained magnitude
        float mag = UnityEngine.Random.Range(impulseMin, impulseMax);

        //Debug.Log("Vol: " + objectVolume.ToString());
        // if (varyScale) {
        //  mag *= randomMass; // scale impulse t unity
        //according to object scale
        // } else {
        //  mag *= curMass;
        // }
        mag       *= m_mass;   // scale impulse according to object mass
        uForceVec *= mag;

        // set directly for debugging
        //uForceVec.Set(2.5f, 0.0f, 0.0f);
        //m_forcePoint = new BulletSharp.Math.Vector3(0.0f, m_rb.CenterOfMassPosition.Y, -0.15f);

        m_forceVec = BSExtensionMethods2.ToBullet(uForceVec);

        if (DEBUG)
        {
            Debug.LogFormat("ForceVec: " + m_forceVec.ToString());
        }

        if (DEBUG)
        {
            UnityEngine.Vector3 debugVec = -uForceVec;
            debugVec.Scale(new UnityEngine.Vector3(0.5f, 0.5f, 0.5f));
            Debug.DrawRay(BSExtensionMethods2.ToUnity(m_forcePoint), debugVec, Color.green, 1.0f);
            Debug.DrawLine(BSExtensionMethods2.ToUnity(m_rb.CenterOfMassPosition), BSExtensionMethods2.ToUnity(m_forcePoint), Color.cyan, 1.0f);
            Debug.DrawLine(BSExtensionMethods2.ToUnity(m_rb.CenterOfMassPosition), BSExtensionMethods2.ToUnity(m_rb.CenterOfMassPosition) + BSExtensionMethods2.ToUnity(btVariationVec) * varyForce, Color.blue, 1.0f);
        }

        // apply the random impulse
        BulletSharp.Math.Vector3 radius = m_forcePoint - m_rb.CenterOfMassPosition;
        m_rb.ApplyImpulse(m_forceVec, radius);
        // m_rb.ApplyTorqueImpulse(new BulletSharp.Math.Vector3(0.0f, 1.0f, 0.0f));
        // m_rb.ApplyCentralImpulse(new BulletSharp.Math.Vector3(4.0f, 0.0f, 2.0f));
        // BulletSharp.Math.Vector3 newAngVel = m_rb.AngularVelocity;
        // newAngVel.X = 0.0f;
        // newAngVel.Z = 0.0f;
        // m_rb.AngularVelocity = newAngVel;

        // calculate ground truth for debugging
        //BulletSharp.Math.Vector3 gtAngVel = radius.Cross(m_forceVec) / m_inertia;
        //BulletSharp.Math.Vector3 gtLinVel = m_forceVec / m_mass;
        //Debug.Log("GT LIN VEL: " + gtLinVel.ToString());
        //Debug.Log("GT ANG VEL: " + gtAngVel.ToString());
    }