Exemple #1
0
        public static void lua_AddCharacterHair(int entID, int setupIndex)
        {
            var ent = EngineWorld.GetCharacterByID((uint)entID);

            if (ent != null)
            {
                var hairSetup = new HairSetup();

                hairSetup.GetSetup((uint)setupIndex);
                ent.Hairs.Add(new Hair());

                if (!ent.Hairs.Last().Create(hairSetup, ent))
                {
                    ConsoleInfo.Instance.Warning(SYSWARN_CANT_CREATE_HAIR, entID);
                    ent.Hairs.RemoveAt(ent.Hairs.Count - 1);
                }
            }
            else
            {
                ConsoleInfo.Instance.Warning(SYSWARN_NO_CHARACTER, entID);
            }
        }
Exemple #2
0
        /// <summary>
        /// Creates hair into allocated hair structure, using previously defined setup and entity index.
        /// </summary>
        public bool Create(HairSetup setup, Entity parentEntity)
        {
            // No setup or parent to link to - bypass function.
            if (parentEntity == null || setup == null || setup.LinkBody >= parentEntity.Bf.BoneTags.Count ||
                parentEntity.Bt.BtBody[(int)setup.LinkBody] == null)
                return false;

            var model = EngineWorld.GetModelByID(setup.Model);

            // No model to link to - bypass function.
            if (model == null || model.MeshCount == 0) return false;

            // Setup engine container. FIXME: DOESN'T WORK PROPERLY ATM.
            Container = new EngineContainer();
            Container.Room = parentEntity.Self.Room;
            Container.ObjectType = OBJECT_TYPE.Hair;
            Container.Object = this;

            // Setup initial hair parameters.
            OwnerChar = parentEntity; // Entity to refer to.
            OwnerBody = setup.LinkBody; // Entity body to refer to.

            // Setup initial position / angles.
            var ownerBodyTransform = parentEntity.Transform * parentEntity.Bf.BoneTags[(int) OwnerBody].FullTransform;

            // Number of elements (bodies) is equal to number of hair meshes.
            Elements = new List<HairElement>();
            Elements.Resize(model.MeshCount, () => new HairElement());

            // Root index should be always zero, as it is how engine determines that it is
            // connected to head and renders it properly. Tail index should be always the
            // last element of the hair, as it indicates absence of "child" constraint.
            RootIndex = 0;
            TailIndex = (byte)(Elements.Count - 1);

            // Weight step is needed to determine the weight of each hair body.
            // It is derived from root body weight and tail body weight.
            var weightStep = (setup.RootWeight - setup.TailWeight) / Elements.Count;
            var currentWeight = setup.RootWeight;

            for (var i = 0; i < Elements.Count; i++)
            {
                // Point to corresponding mesh.
                Elements[i].Mesh = model.MeshTree[i].MeshBase;

                // Begin creating ACTUAL physical hair mesh.
                var localInertia = BulletSharp.Math.Vector3.Zero;

                // Make collision shape out of mesh.
                Elements[i].Shape = BT_CSfromMesh(Elements[i].Mesh, true, true, false);
                Elements[i].Shape.CalculateLocalInertia(currentWeight * setup.HairInertia, out localInertia);

                // Decrease next body weight to weight_step parameter.
                currentWeight -= weightStep;

                // Initialize motion state for body.
                var startTransform = ownerBodyTransform;
                var motionState = new DefaultMotionState(((Matrix4)startTransform).ToBullet());

                // Make rigid body.
                Elements[i].Body = new RigidBody(new RigidBodyConstructionInfo(currentWeight, motionState, Elements[i].Shape, localInertia));

                // Damping makes body stop in space by itself, to prevent it from continous movement.
                Elements[i].Body.SetDamping(setup.HairDamping[0], setup.HairDamping[1]);

                // Restitution and friction parameters define "bounciness" and "dullness" of hair.
                Elements[i].Body.Restitution = setup.HairRestitution;
                Elements[i].Body.Friction = setup.HairFriction;

                // Since hair is always moving with Lara, even if she's in still state (like, hanging
                // on a ledge), hair bodies shouldn't deactivate over time.
                Elements[i].Body.ForceActivationState(ActivationState.DisableDeactivation);

                // Hair bodies must not collide with each other, and also collide ONLY with kinematic
                // bodies (e. g. animated meshes), or else Lara's ghost object or anything else will be able to
                // collide with hair!
                Elements[i].Body.UserObject = Container;
                BtEngineDynamicsWorld.AddRigidBody(Elements[i].Body, CollisionFilterGroups.CharacterFilter, CollisionFilterGroups.KinematicFilter);

                Elements[i].Body.Activate();
            }

            // GENERATE CONSTRAINTS.
            // All constraints are generic 6-DOF type, as they seem perfect fit for hair.

            // Joint count is calculated from overall body amount multiplied by per-body constraint
            // count.
            Joints = new List<Generic6DofConstraint>();
            Joints.Resize(Elements.Count);

            // If multiple joints per body is specified, joints are placed in circular manner,
            // with obvious step of (SIMD_2_PI) / joint count. It means that all joints will form
            // circle-like figure.
            var currJoint = 0;

            for (var i = 0; i < Elements.Count; i++)
            {
                float bodyLength;
                var localA = new Transform();
                localA.SetIdentity();
                var localB = new Transform();
                localB.SetIdentity();

                var jointX = 0.0f;
                var jointY = 0.0f;

                RigidBody prevBody;
                if(i == 0) // First joint group
                {
                    // Adjust pivot point A to parent body.
                    localA.Origin = setup.HeadOffset + new Vector3(jointX, 0.0f, jointY);
                    Helper.SetEulerZYX(ref localA.Basis, setup.RootAngle.X, setup.RootAngle.Y, setup.RootAngle.Z);
                    // Stealing this calculation because I need it for drawing
                    OwnerBodyHairRoot = localA;

                    localB.Origin = new Vector3(jointX, 0.0f, jointY);
                    Helper.SetEulerZYX(ref localB.Basis, 0, -HalfPI, 0);

                    prevBody = parentEntity.Bt.BtBody[(int) OwnerBody]; // Previous body is parent body.
                }
                else
                {
                    // Adjust pivot point A to previous mesh's length, considering mesh overlap multiplier.
                    bodyLength = Math.Abs(Elements[i - 1].Mesh.BBMax.Y - Elements[i - 1].Mesh.BBMin.Y) *
                                 setup.JointOverlap;

                    localA.Origin = new Vector3(jointX, bodyLength, jointY);
                    Helper.SetEulerZYX(ref localA.Basis, 0, -HalfPI, 0);

                    // Pivot point B is automatically adjusted by Bullet.
                    localB.Origin = new Vector3(jointX, 0.0f, jointY);
                    Helper.SetEulerZYX(ref localB.Basis, 0, -HalfPI, 0);

                    prevBody = Elements[i - 1].Body; // Previous body is preceding hair mesh.
                }

                // Create 6DOF constraint.
                Joints[currJoint] = new Generic6DofConstraint(prevBody, Elements[i].Body, ((Matrix4) localA).ToBullet(),
                    ((Matrix4) localB).ToBullet(), true);

                // CFM and ERP parameters are critical for making joint "hard" and link
                // to Lara's head. With wrong values, constraints may become "elastic".
                for (var axis = 0; axis < 6; axis++)
                {
                    Joints[currJoint].SetParam(ConstraintParam.StopCfm, setup.JointCfm, axis);
                    Joints[currJoint].SetParam(ConstraintParam.StopErp, setup.JointErp, axis);
                }

                Joints[currJoint].LinearLowerLimit = BulletSharp.Math.Vector3.Zero;
                Joints[currJoint].LinearUpperLimit = BulletSharp.Math.Vector3.Zero;

                if(i == 0)
                {
                    // First joint group should be more limited in motion, as it is connected
                    // right to the head. NB: Should we make it scriptable as well?
                    Joints[currJoint].AngularLowerLimit = new BulletSharp.Math.Vector3(-HalfPI, 0.0f, -HalfPI * 0.4f);
                    Joints[currJoint].AngularLowerLimit = new BulletSharp.Math.Vector3(-HalfPI * 0.3f, 0.0f, HalfPI * 0.4f);

                    // Increased solver iterations make constraint even more stable.
                    Joints[currJoint].OverrideNumSolverIterations = 100;
                }
                else
                {
                    // Normal joint with more movement freedom.
                    Joints[currJoint].AngularLowerLimit = new BulletSharp.Math.Vector3(-HalfPI * 0.5f, 0.0f, -HalfPI * 0.5f);
                    Joints[currJoint].AngularLowerLimit = new BulletSharp.Math.Vector3(HalfPI * 0.5f, 0.0f, HalfPI * 0.5f);
                }

                Joints[currJoint].DebugDrawSize = 5.0f; // Draw constraint axes.

                // Add constraint to the world.
                BtEngineDynamicsWorld.AddConstraint(Joints[currJoint], true);

                currJoint++; // Point to the next joint.
            }

            createHairMesh(model);

            return true;
        }