Example #1
0
        public static void TR_GenCameras(World world, Level tr)
        {
            world.CamerasSinks.Clear();

            foreach(var trcam in tr.Cameras)
            {
                world.CamerasSinks.Add(
                    new StatCameraSink
                    {
                        X = trcam.Position.X,
                        Y = trcam.Position.Y,
                        Z = -trcam.Position.Z,
                        RoomOrStrength = (ushort)trcam.Room,
                        FlagOrZone = trcam.Flag
                    });
            }
        }
Example #2
0
        // Functions generating native OpenTomb structs from legacy TR structs.

        public static void TR_GenWorld(World world, Level tr)
        {
            world.EngineVersion = Loader.Helper.GameToEngine(tr.GameVersion);

            Res_AutoexecOpen(tr.GameVersion); // Open and do preload autoexec.
            EngineLua.Call("autoexec_PreLoad");
            Gui.DrawLoadScreen(150);

            Res_GenRBTrees(world);
            Gui.DrawLoadScreen(200);

            TR_GenTextures(world, tr); // a bit slow (1-2sec)
            Gui.DrawLoadScreen(300);

            TR_GenAnimCommands(world, tr);
            Gui.DrawLoadScreen(310);

            TR_GenAnimTextures(world, tr);
            Gui.DrawLoadScreen(320);

            TR_GenMeshes(world, tr); // a bit slow (1sec)
            Gui.DrawLoadScreen(400);

            TR_GenSprites(world, tr);
            Gui.DrawLoadScreen(420);

            TR_GenBoxes(world, tr);
            Gui.DrawLoadScreen(440);

            TR_GenCameras(world, tr);
            Gui.DrawLoadScreen(460);

            TR_GenRooms(world, tr);
            Gui.DrawLoadScreen(500);

            Res_GenRoomFlipMap(world);
            Gui.DrawLoadScreen(520);

            TR_GenSkeletalModels(world, tr); // very slow (10sec)
            Gui.DrawLoadScreen(600);

            TR_GenEntities(world, tr); // very slow (5sec)
            Gui.DrawLoadScreen(650);

            Res_GenBaseItems(world);
            Gui.DrawLoadScreen(680);

            Res_GenSpritesBuffer(world); // Should be done ONLY after TR_GenEntities.
            Gui.DrawLoadScreen(700);

            TR_GenRoomProperties(world, tr);
            Gui.DrawLoadScreen(750);

            Res_GenRoomCollision(world);
            Gui.DrawLoadScreen(800);

#if !NO_AUDIO
            TR_GenSamples(world, tr);
#endif
            Gui.DrawLoadScreen(850);

            world.SkyBox = Res_GetSkybox(world, world.EngineVersion);
            Gui.DrawLoadScreen(860);

            Res_GenEntityFunctions(world.EntityTree); // a bit very slow (2-3sec)
            Gui.DrawLoadScreen(910);

            Res_GenVBOs(world);
            Gui.DrawLoadScreen(950);

            EngineLua.DoFile("scripts/autoexec.lua"); // Postload autoexec.
            EngineLua.Call("autoexec_PostLoad");
            Gui.DrawLoadScreen(960);

            Res_FixRooms(world); // Fix initial room states
            Gui.DrawLoadScreen(970);
        }
Example #3
0
        public static void TR_GenBoxes(World world, Level tr)
        {
            world.RoomBoxes.Clear();

            foreach (var trbox in tr.Boxes)
            {
                world.RoomBoxes.Add(
                    new RoomBox
                    {
                        OverlapIndex = trbox.OverlapIndex,
                        TrueFloor = -trbox.TrueFloor,
                        Xmin = (int) trbox.Xmin,
                        Xmax = (int) trbox.Xmax,
                        Ymin = -(int) trbox.Zmax,
                        Ymax = -(int) trbox.Zmin
                    });
            }
        }
Example #4
0
        public static void TR_Sector_Calculate(World world, Level tr, int room_index)
        {
            var room = world.Rooms[room_index];
            var trRoom = tr.Rooms[room_index];

            // Sectors loading

            for (var i = 0; i < room.Sectors.Count; i++)
            {
                var sector = room.Sectors[i];

                // Let us fill pointers to sectors above and sectors below

                var rp = trRoom.Sectors[i].RoomBelow;
                sector.SectorBelow = null;
                if (rp < world.Rooms.Count && rp != 255)
                {
                    sector.SectorBelow = world.Rooms[rp].GetSectorRaw(sector.Position);
                }
                rp = trRoom.Sectors[i].RoomAbove;
                sector.SectorBelow = null;
                if (rp < world.Rooms.Count && rp != 255)
                {
                    sector.SectorAbove = world.Rooms[rp].GetSectorRaw(sector.Position);
                }

                RoomSector nearSector = null;

                // OX
                if (sector.IndexY.IsBetween(0, room.SectorsY - 1, IB.aEbE) && sector.IndexX == 0)
                {
                    nearSector = room.Sectors[i + room.SectorsY];
                }
                if (sector.IndexY.IsBetween(0, room.SectorsY - 1, IB.aEbE) && sector.IndexX == room.SectorsX - 1)
                {
                    nearSector = room.Sectors[i - room.SectorsY];
                }
                // OY
                if (sector.IndexX.IsBetween(0, room.SectorsX - 1, IB.aEbE) && sector.IndexY == 0)
                {
                    nearSector = room.Sectors[i + 1];
                }
                if (sector.IndexX.IsBetween(0, room.SectorsX - 1, IB.aEbE) && sector.IndexY == room.SectorsY - 1)
                {
                    nearSector = room.Sectors[i - 1];
                }

                if (nearSector != null && sector.PortalToRoom >= 0)
                {
                    foreach (var p in room.Portals)
                    {
                        if(p.Normal.Normal.Z.IsBetween(-0.01f, 0.01f, IB.aEbE))
                        {
                            var dst = p.DestRoom?.GetSectorRaw(sector.Position);
                            var origDst = EngineWorld.Rooms[sector.PortalToRoom].GetSectorRaw(sector.Position);

                            if (dst != null && dst.PortalToRoom < 0 && dst.Floor != TR_METERING_WALLHEIGHT &&
                                dst.Ceiling != TR_METERING_WALLHEIGHT && sector.PortalToRoom != p.DestRoom.ID &&
                                dst.Floor < origDst.Floor && TR_IsSectorsIn2SideOfPortal(nearSector, dst, p))
                            {
                                sector.PortalToRoom = (int)p.DestRoom.ID;
                                origDst = dst; // TODO: What's the point of that?
                            }
                        }
                    }
                }
            }
        }
Example #5
0
        public static void tr_setupColoredFace(Mesh trMesh, Level tr, BaseMesh mesh, ushort[] vertexIndices, int color,
            Polygon p)
        {
            var tmp = trMesh.Lights.Length == trMesh.Vertices.Length;
            for (var i = 0; i < p.Vertices.Count; i++)
            {
                p.Vertices[i].Color[0] = tr.Palette.Colour[color].R / 255.0f;
                p.Vertices[i].Color[1] = tr.Palette.Colour[color].G / 255.0f;
                p.Vertices[i].Color[2] = tr.Palette.Colour[color].B / 255.0f;
                if(tmp)
                {
                    p.Vertices[i].Color[0] = p.Vertices[i].Color[0] * 1.0f - trMesh.Lights[vertexIndices[i]] / 8192.0f;
                    p.Vertices[i].Color[1] = p.Vertices[i].Color[1] * 1.0f - trMesh.Lights[vertexIndices[i]] / 8192.0f;
                    p.Vertices[i].Color[2] = p.Vertices[i].Color[2] * 1.0f - trMesh.Lights[vertexIndices[i]] / 8192.0f;
                }
                p.Vertices[i].Color[3] = 1.0f;

                p.Vertices[i].TexCoord[0] = (i & 2) == 2 ? 1.0f : 0.0f;
                p.Vertices[i].TexCoord[1] = i >= 2 ? 1.0f : 0.0f;
            }
            mesh.UsesVertexColors = true;
        }
Example #6
0
        public static int TR_GetNumAnimationsForMoveable(Level tr, int moveable_ind)
        {
            int ret;
            var currMoveable = tr.Moveables[moveable_ind];

            if(currMoveable.AnimationIndex == 0xFFFF)
            {
                return 0;
            }

            if(moveable_ind == tr.Moveables.Length - 1)
            {
                ret = tr.Animations.Length - currMoveable.AnimationIndex;
                return ret < 0 ? 1 : ret;
            }

            var nextMoveable = tr.Moveables[moveable_ind + 1];
            if(nextMoveable.AnimationIndex == 0xFFFF)
            {
                if(moveable_ind + 2 < tr.Moveables.Length) // I hope there is no two neighboard movables with animation_index'es == 0xFFFF
                {
                    nextMoveable = tr.Moveables[moveable_ind + 2];
                }
                else
                {
                    return 1;
                }
            }

            ret = Math.Min(nextMoveable.AnimationIndex, tr.Animations.Length);
            ret -= currMoveable.AnimationIndex;

            return ret;
        }
Example #7
0
        public static long TR_GetOriginalAnimationFrameOffset(uint offset, uint anim, Level tr)
        {
            if(anim >= tr.Animations.Length)
            {
                return -1;
            }

            var trAnim = tr.Animations[(int) anim];
            if(anim + 1 == tr.Animations.Length)
            {
                if(offset < trAnim.FrameOffset)
                {
                    return -2;
                }
            }
            else
            {
                if (offset < trAnim.FrameOffset && offset >= tr.Animations[(int)anim + 1].FrameOffset)
                {
                    return -2;
                }
            }

            return trAnim.FrameOffset;
        }
Example #8
0
        public static void TR_GenSprites(World world, Level tr)
        {
            if(tr.SpriteTextures.Length == 0)
            {
                world.Sprites.Clear();
                return;
            }

            for (var i = 0; i < tr.SpriteTextures.Length; i++)
            {
                var s = new Sprite();

                var tr_st = tr.SpriteTextures[i];

                s.Left = tr_st.LeftSide;
                s.Right = tr_st.RightSide;
                s.Top = tr_st.TopSide;
                s.Bottom = tr_st.BottomSide;

                world.Sprites.Add(s);

                world.TextureAtlas.GetSpriteCoordinates((uint)i, out s.Texture, s.TexCoord);
            }

            foreach (var seq in tr.SpriteSequences)
            {
                if(seq.Offset.IsBetween(0, world.Sprites.Count - 1))
                {
                    world.Sprites[seq.Offset].ID = (uint)seq.ObjectID;
                }
            }
        }
Example #9
0
        public static void TR_GenTextures(World world, Level tr)
        {
            var borderSize = Renderer.Settings.TextureBorder.Clamp(0, 64);

            world.TextureAtlas = new BorderedTextureAtlas(
                borderSize, 
                Renderer.Settings.SaveTextureMemory, 
                tr.Textures,
                tr.ObjectTextures, 
                tr.SpriteTextures);

            world.Textures.Resize((int)world.TextureAtlas.NumAtlasPages + 1);

            GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
            GL.PixelZoom(1, 1);
            unsafe
            {
                var tmp = world.Textures.ToArray();
                fixed(uint* ptr = tmp)
                    world.TextureAtlas.CreateTextures(ptr, 1);
            }

            // white texture data for coloured polygons and debug lines.
            var whtx = new[]
            {
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            };

            // Select mipmap mode
            switch(Renderer.Settings.MipmapMode)
            {
                case 0:
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.NearestMipmapNearest);
                    break;

                case 1:
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapNearest);
                    break;

                case 2:
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.NearestMipmapLinear);
                    break;

                case 3:
                default:
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
                    break;
            }

            // Set mipmaps number
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, Renderer.Settings.Mipmaps);

            // Set anisotropy degree
            GL.TexParameter(TextureTarget.Texture2D,
                (TextureParameterName) ExtTextureFilterAnisotropic.TextureMaxAnisotropyExt, Renderer.Settings.Anisotropy);

            // Read lod bias
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureLodBias, Renderer.Settings.LodBias);

            GL.BindTexture(TextureTarget.Texture2D, world.Textures.Last()); // solid color =)
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
                (int) TextureMagFilter.Linear);

            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, 4, 4, 0, PixelFormat.Rgba,
                PixelType.UnsignedByte, whtx);
            GL.TexImage2D(TextureTarget.Texture2D, 1, PixelInternalFormat.Rgba, 2, 2, 0, PixelFormat.Rgba,
             PixelType.UnsignedByte, whtx);
            GL.TexImage2D(TextureTarget.Texture2D, 2, PixelInternalFormat.Rgba, 1, 1, 0, PixelFormat.Rgba,
             PixelType.UnsignedByte, whtx);
            //GL.Disable(EnableCap.Texture2D); // Why it is here? It is blocking loading screen.
        }
Example #10
0
        public static void TR_GenSkeletalModel(World world, int model_num, SkeletalModel model, Level tr)
        {
            var trMoveable = tr.Moveables[model_num]; // original tr structure

            model.CollisionMap.Resize(model.MeshCount);
            for (ushort i = 0; i < model.MeshCount; i++)
            {
                model.CollisionMap[i] = i;
            }

            model.MeshTree.Resize(model.MeshCount, () => new MeshTreeTag());
            var treeTag = model.MeshTree[0];

            var meshIndex = tr.MeshIndices.SkipEx(trMoveable.StartingMesh);

            for (var k = 0; k < model.MeshCount; k++)
            {
                treeTag = model.MeshTree[k];
                treeTag.MeshBase = world.Meshes[(int) meshIndex[k]];
                treeTag.MeshSkin = null; // PARANOID: I use calloc for tree_tag's
                treeTag.ReplaceAnim = 0x00;
                treeTag.ReplaceMesh = 0x00;
                treeTag.BodyPart = 0x00;
                treeTag.Offset = Vector3.Zero;
                if (k == 0)
                {
                    treeTag.Flag = 0x02;
                }
                else
                {
                    var tr_mesh_tree = tr.MeshTreeData.SkipEx((int) trMoveable.MeshTreeIndex + (k - 1) * 4);
                    treeTag.Flag = (ushort) (tr_mesh_tree[0] & 0xFF);
                    treeTag.Offset.X = tr_mesh_tree[1];
                    treeTag.Offset.Y = tr_mesh_tree[3];
                    treeTag.Offset.Z = -tr_mesh_tree[2];
                }
            }

            /*
             * =================    now, animation loading    ========================
             */

            if (trMoveable.AnimationIndex >= tr.Animations.Length)
            {
                // model has no start offset and any animation
                model.Animations.Resize(1, () => new AnimationFrame());
                model.Animations[0].Frames.Resize(1, () => new BoneFrame());
                var boneFrame = model.Animations[0].Frames[0];

                model.Animations[0].ID = TR_ANIMATION.LaraRun;
                model.Animations[0].NextAnim = null;
                model.Animations[0].NextFrame = 0;
                model.Animations[0].StateChange.Clear();
                model.Animations[0].OriginalFrameRate = 1;

                boneFrame.BoneTags.Resize(model.MeshCount, () => new BoneTag());

                boneFrame.Position = Vector3.Zero;
                boneFrame.Move = Vector3.Zero;
                boneFrame.V_Horizontal = 0.0f;
                boneFrame.V_Vertical = 0.0f;
                boneFrame.Command = 0x00;
                for (var k = 0; k < boneFrame.BoneTags.Count; k++)
                {
                    treeTag = model.MeshTree[k];
                    var boneTag = boneFrame.BoneTags[k];

                    VMath.Vec4_SetTRRotations(ref boneTag.QRotate, Vector3.Zero);
                    boneTag.Offset = treeTag.Offset;
                }
                return;
            }
            //Sys.DebugLog(LOG_FILENAME, "model = {0}, anims = {1}", trMoveable.ObjectID, TR_GetNumAnimationsForMoveable(tr, model_num));
            model.Animations.Resize(Math.Max(1, TR_GetNumAnimationsForMoveable(tr, model_num)),
                () => new AnimationFrame()); // the animation count must be >= 1

            /*
             *   Ok, let us calculate animations;
             *   there is no difficult:
             * - first 9 words are bounding box and frame offset coordinates.
             * - 10's word is a rotations count, must be equal to number of meshes in model.
             *   BUT! only in TR1. In TR2 - TR5 after first 9 words begins next section.
             * - in the next follows rotation's data. one word - one rotation, if rotation is one-axis (one angle).
             *   two words in 3-axis rotations (3 angles). angles are calculated with bit mask.
             */
            for (var i = 0; i < model.Animations.Count; i++)
            {
                var anim = model.Animations[i];
                var trAnimation = tr.Animations[trMoveable.AnimationIndex + i];

                var frameOffset = trAnimation.FrameOffset / 2;
                var l_start = 0x09;

                if (tr.GameVersion.IsAnyOf(TRGame.TR1, TRGame.TR1Demo, TRGame.TR1UnfinishedBusiness))
                {
                    l_start = 0x0A;
                }

                var frameStep = trAnimation.FrameSize;

                anim.ID = (TR_ANIMATION) i;
                anim.OriginalFrameRate = trAnimation.FrameRate;

                anim.SpeedX = trAnimation.Speed;
                anim.AccelX = trAnimation.Acceleration;
                anim.SpeedY = trAnimation.AccelerationLateral;
                anim.AccelY = trAnimation.SpeedLateral; // TODO: Inverted?

                anim.AnimCommand = trAnimation.AnimCommand;
                anim.NumAnimCommands = trAnimation.NumAnimCommands;
                anim.StateID = (TR_STATE) trAnimation.StateID;

                anim.Frames.Resize(TR_GetNumFramesForAnimation(tr, trMoveable.AnimationIndex + i), () => new BoneFrame());

                //Sys.DebugLog(LOG_FILENAME, "Anim[{0}], {1}", trMoveable.AnimationIndex, TR_GetNumFramesForAnimation(tr, trMoveable.AnimationIndex));

                // Parse AnimCommands
                // Max. amount of AnimCommands is 255, larger numbers are considered as 0.
                // See http://evpopov.com/dl/TR4format.html#Animations for details.

                if (anim.NumAnimCommands.IsBetween(0, 255, IB.aEbI))
                {
                    // Calculate current animation anim command block offset.
                    Assert(anim.AnimCommand < world.AnimCommands.Length);
                    unsafe
                    {
                        fixed (short* tmp = &world.AnimCommands[(int) anim.AnimCommand])
                        {
                            var pointer = tmp;
                            for (uint count = 0; count < anim.NumAnimCommands; count++)
                            {
                                var command = *pointer;
                                ++pointer;
                                switch ((TR_ANIMCOMMAND) command)
                                {
                                    case TR_ANIMCOMMAND.PlayEffect:
                                    case TR_ANIMCOMMAND.PlaySound:
                                        // Recalculate absolute frame number to relative.
                                        pointer[0] -= (short) trAnimation.FrameStart;
                                        pointer += 2;
                                        break;

                                    case TR_ANIMCOMMAND.SetPosition:
                                        // Parse through 3 operands.
                                        pointer += 3;
                                        break;

                                    case TR_ANIMCOMMAND.JumpDistance:
                                        // Parse through 2 operands.
                                        pointer += 2;
                                        break;

                                    default:
                                        // All other commands have no operands.
                                        break;
                                }
                            }
                        }
                    }
                }

                if (anim.Frames.Count == 0)
                {
                    // number of animations must be >= 1, because frame contains base model offset
                    anim.Frames.Resize(1, () => new BoneFrame());
                }

                // let us begin to load animations
                foreach (var boneFrame in anim.Frames)
                {
                    boneFrame.BoneTags.Resize(model.MeshCount, () => new BoneTag());
                    boneFrame.Position = Vector3.Zero;
                    boneFrame.Move = Vector3.Zero;
                    TR_GetBFrameBB_Pos(tr, (int) frameOffset, boneFrame);

                    if (frameOffset >= tr.FrameData.Length)
                    {
                        for (var k = 0; k < boneFrame.BoneTags.Count; k++)
                        {
                            treeTag = model.MeshTree[k];
                            var boneTag = boneFrame.BoneTags[k];
                            VMath.Vec4_SetTRRotations(ref boneTag.QRotate, Vector3.Zero);
                            boneTag.Offset = treeTag.Offset;
                        }
                    }
                    else
                    {
                        var l = l_start;
                        ushort temp1, temp2;
                        float ang;

                        for (var k = 0; k < boneFrame.BoneTags.Count; k++)
                        {
                            treeTag = model.MeshTree[k];
                            var boneTag = boneFrame.BoneTags[k];
                            VMath.Vec4_SetTRRotations(ref boneTag.QRotate, Vector3.Zero);
                            boneTag.Offset = treeTag.Offset;

                            switch (tr.GameVersion)
                            {
                                case TRGame.TR1:
                                case TRGame.TR1UnfinishedBusiness:
                                case TRGame.TR1Demo:
                                    temp2 = tr.FrameData[frameOffset + l];
                                    l++;
                                    temp1 = tr.FrameData[frameOffset + l];
                                    l++;
                                    VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(
                                        (temp1 & 0x3ff0) >> 4,
                                        -(((temp1 & 0x000f) << 6) | ((temp2 & 0xfc00) >> 10)),
                                        temp2 & 0x03ff) * (360.0f / 1024.0f));
                                    break;

                                default:
                                    temp1 = tr.FrameData[frameOffset + l];
                                    l++;
                                    if (tr.GameVersion >= TRGame.TR4)
                                    {
                                        ang = (temp1 & 0x0fff) * (360.0f / 4096.0f);
                                    }
                                    else
                                    {
                                        ang = (temp1 & 0x03ff) * (360.0f / 1024.0f);
                                    }

                                    switch (temp1 & 0xc000)
                                    {
                                        case 0x4000: // x only
                                            VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(ang, 0, 0));
                                            break;

                                        case 0x8000: // y only
                                            VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(0, 0, -ang));
                                            break;

                                        case 0xc000: // z only
                                            VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(0, ang, 0));
                                            break;

                                        default: // all three
                                            temp2 = tr.FrameData[frameOffset + l];
                                            VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(
                                                (temp1 & 0x3ff0) >> 4,
                                                -(((temp1 & 0x000f) << 6) | ((temp2 & 0xfc00) >> 10)),
                                                temp2 & 0x03ff) * (360.0f / 1024.0f));
                                            l++;
                                            break;
                                    }
                                    break;
                            }
                        }
                    }

                    frameOffset += frameStep;
                }
            }

            // Animations interpolation to 1/30 sec like in original. Needed for correct state change works.
            model.InterpolateFrames();
            // state change's loading

            if (LOG_ANIM_DISPATCHES)
            {
                if (model.Animations.Count > 1)
                {
                    Sys.DebugLog(LOG_FILENAME, "MODEL[{0}], anims = {1}", model_num, model.Animations.Count);
                }
            }
            for (var i = 0; i < model.Animations.Count; i++)
            {
                var anim = model.Animations[i];
                anim.StateChange.Clear();

                var trAnimation = tr.Animations[trMoveable.AnimationIndex + i];
                var animId = (trAnimation.NextAnimation - trMoveable.AnimationIndex) & 0x7fff;
                    // this masks out the sign bit
                Assert(animId >= 0);
                if (animId < model.Animations.Count)
                {
                    anim.NextAnim = model.Animations[animId];
                    anim.NextFrame = Math.Max(0,
                        (trAnimation.NextFrame - tr.Animations[trAnimation.NextAnimation].FrameStart) %
                        anim.NextAnim.Frames.Count);

                    if (LOG_ANIM_DISPATCHES)
                    {
                        Sys.DebugLog(LOG_FILENAME, "ANIM[{0}], next_anim = {1}, next_frame = {2}", i,
                            (int) anim.NextAnim.ID,
                            anim.NextFrame);
                    }
                }
                else
                {
                    anim.NextAnim = null;
                    anim.NextFrame = 0;
                }

                anim.StateChange.Clear(); // TODO: Needed?

                if (trAnimation.NumStateChanges > 0 && model.Animations.Count > 1)
                {
                    if (LOG_ANIM_DISPATCHES)
                    {
                        Sys.DebugLog(LOG_FILENAME, "ANIM[{0}], next_anim = {1}, next_frame = {2}", i,
                            anim.NextAnim == null ? -1 : (int) anim.NextAnim.ID, anim.NextFrame);
                    }
                    anim.StateChange.Resize(trAnimation.NumStateChanges, () => new StateChange());

                    for (var j = 0; j < trAnimation.NumStateChanges; j++)
                    {
                        var schP = anim.StateChange[j];
                        var trSch = tr.StateChanges[j + trAnimation.StateChangeOffset];
                        schP.ID = (TR_STATE) trSch.StateID;
                        schP.AnimDispatch.Clear();
                        for (var l = 0; l < trSch.NumAnimDispatches; l++)
                        {
                            var trAdisp = tr.AnimDispatches[trSch.AnimDispatch + l];
                            var nextAnim = trAdisp.NextAnimation & 0x7fff;
                            var nextAnimInd = nextAnim - (trMoveable.AnimationIndex & 0x7fff);
                            if (nextAnimInd < model.Animations.Count)
                            {
                                var adsp = new AnimDispatch();
                                var nextFramesCount =
                                    model.Animations[nextAnim - trMoveable.AnimationIndex].Frames.Count;
                                var nextFrame = trAdisp.NextFrame - tr.Animations[nextAnim].FrameStart;

                                var low = trAdisp.Low - trAnimation.FrameStart;
                                var high = trAdisp.High - trAnimation.FrameStart;

                                adsp.FrameLow = (ushort) (low % anim.Frames.Count);
                                adsp.FrameHigh = (ushort) ((high - 1) % anim.Frames.Count);
                                adsp.NextAnim = (TR_ANIMATION) (nextAnim - trMoveable.AnimationIndex);
                                adsp.NextFrame = (ushort) (nextFrame % nextFramesCount);

                                schP.AnimDispatch.Add(adsp);

                                if (LOG_ANIM_DISPATCHES)
                                {
                                    Sys.DebugLog(LOG_FILENAME,
                                        "anim_disp[{0}], frames.size() = {1}: interval[{2}.. {3}], next_anim = {4}, next_frame = {5}",
                                        l, anim.Frames.Count, adsp.FrameLow, adsp.FrameHigh, (int) adsp.NextAnim,
                                        adsp.NextFrame);
                                }
                            }
                        }
                    }
                }
            }
            GenerateAnimCommandsTransform(model);
        }
Example #11
0
        public static void TR_GenEntities(World world, Level tr)
        {
            for (var i = 0; i < tr.Items.Length; i++)
            {
                var trItem = tr.Items[i];
                var entity = trItem.ObjectID == 0 ? new Character((uint) i) : new Entity((uint) i);
                entity.Transform.Origin.X = trItem.Position.X;
                entity.Transform.Origin.Y = -trItem.Position.Z;
                entity.Transform.Origin.Z = trItem.Position.Y;
                entity.Angles.X = trItem.Rotation;
                entity.Angles.Y = 0;
                entity.Angles.Z = 0;
                entity.UpdateTransform();
                entity.Self.Room = trItem.Room.IsBetween(0, world.Rooms.Count - 1) ? world.Rooms[trItem.Room] : null;

                entity.TriggerLayout = (ENTITY_TLAYOUT)trItem.ActivationMash; // FIXME: Ignore INVISIBLE and CLEAR BODY flags for a moment.
                entity.OCB = trItem.ObjectCodeBit;
                entity.Timer = 0.0f;

                entity.Self.CollisionType = COLLISION_TYPE.Kinematic;
                entity.Self.CollisionShape = COLLISION_SHAPE.TrimeshConvex;
                entity.MoveType = MoveType.StaticPos;
                entity.InertiaLinear = 0.0f;
                entity.InertiaAngular = Vector2.Zero;

                entity.Bf.Animations.Model = world.GetModelByID((uint)trItem.ObjectID);

                if(entity.Bf.Animations.Model == null)
                {
                    var id = EngineLua.Call("getOverridedID", Loader.Helper.GameToEngine(tr.GameVersion), trItem.ObjectID)[0];
                    entity.Bf.Animations.Model = world.GetModelByID((uint) id);
                }

                var replaceAnimId = (int)EngineLua.Call("getOverridedAnim", Loader.Helper.GameToEngine(tr.GameVersion), trItem.ObjectID)[0];
                if(replaceAnimId > 0)
                {
                    var replaceAnimModel = world.GetModelByID((uint)replaceAnimId);
                    var tmp = entity.Bf.Animations.Model.Animations;
                    entity.Bf.Animations.Model.Animations = replaceAnimModel.Animations;
                    replaceAnimModel.Animations = tmp;
                }

                if(entity.Bf.Animations.Model == null)
                {
                    // SPRITE LOADING
                    var sp = world.GetSpriteByID((uint)trItem.ObjectID);
                    if(sp != null && entity.Self.Room != null)
                    {
                        var rsp = new RoomSprite();
                        rsp.Sprite = sp;
                        rsp.Position = entity.Transform.Origin;
                        rsp.WasRendered = false;
                        entity.Self.Room.Sprites.Add(rsp);
                    }

                    continue; // that entity has no model. may be it is a some trigger or look at object
                }

                if(tr.GameVersion < TRGame.TR2 && trItem.ObjectID == 83) // FIXME: brutal magick hardcode! ;-)
                {
                    // skip PSX save model
                    continue;
                }

                entity.Bf.FromModel(entity.Bf.Animations.Model);

                if(trItem.ObjectID == 0) // Lara is unical model
                {
                    var lara = (Character) entity;
                    Assert(lara != null);

                    lara.MoveType = MoveType.OnFloor;
                    world.Character = lara;
                    lara.Self.CollisionType = COLLISION_TYPE.Actor;
                    lara.Self.CollisionShape = COLLISION_SHAPE.TrimeshConvex;
                    lara.TypeFlags |= ENTITY_TYPE.TriggerActivator;
                    SkeletalModel LM;

                    EngineLua.Set("player", lara.ID);

                    switch (Loader.Helper.GameToEngine(tr.GameVersion))
                    {
                        case Loader.Engine.TR1:
                            if (GameflowManager.LevelID == 0)
                            {
                                LM = world.GetModelByID((uint) TR_ITEM_LARA.AlternateTR1);
                                if (LM != null)
                                {
                                    // In TR1, Lara has unified head mesh for all her alternate skins.
                                    // Hence, we copy all meshes except head, to prevent Potato Raider bug.
                                    SkeletonCopyMeshes(world.SkeletalModels[0].MeshTree, LM.MeshTree,
                                        world.SkeletalModels[0].MeshCount - 1);
                                }
                            }
                            break;

                        case Loader.Engine.TR3:
                            LM = world.GetModelByID((uint) TR_ITEM_LARA.TR3);
                            if (LM != null)
                            {
                                SkeletonCopyMeshes(world.SkeletalModels[0].MeshTree, LM.MeshTree,
                                    world.SkeletalModels[0].MeshCount);
                                var tmp = world.GetModelByID(11); // moto / quadro cycle animations
                                if (tmp != null)
                                {
                                    SkeletonCopyMeshes(tmp.MeshTree, LM.MeshTree, world.SkeletalModels[0].MeshCount);
                                }
                            }
                            break;

                        case Loader.Engine.TR4:
                        case Loader.Engine.TR5:
                            LM = world.GetModelByID((uint)TR_ITEM_LARA.TR4_5); // base skeleton meshes
                            if (LM != null)
                            {
                                SkeletonCopyMeshes(world.SkeletalModels[0].MeshTree, LM.MeshTree,
                                    world.SkeletalModels[0].MeshCount);
                            }
                            LM = world.GetModelByID((uint)TR_ITEM_LARA.Joints_TR4_5); // skin skeleton meshes
                            if (LM != null)
                            {
                                SkeletonCopyMeshes2(world.SkeletalModels[0].MeshTree, LM.MeshTree,
                                    world.SkeletalModels[0].MeshCount);
                            }
                            world.SkeletalModels[0].FillSkinnedMeshMap();
                            break;

                        case Loader.Engine.Unknown:
                            break;
                    }

                    for (var j = 0; j < lara.Bf.BoneTags.Count; j++)
                    {
                        lara.Bf.BoneTags[j].MeshBase = lara.Bf.Animations.Model.MeshTree[j].MeshBase;
                        lara.Bf.BoneTags[j].MeshSkin = lara.Bf.Animations.Model.MeshTree[j].MeshSkin;
                        lara.Bf.BoneTags[i].MeshSlot = null;
                    }

                    world.Character.SetAnimation(TR_ANIMATION.LaraStayIdle, 0);
                    lara.GenRigidBody();
                    lara.CreateGhosts();
                    lara.Height = 768.0f;
                    lara.StateFunc = AnimStateControl.StateControlLara;

                    continue;
                }

                entity.SetAnimation(TR_ANIMATION.LaraRun, 0); // Set zero animation and zero frame

                Res_SetEntityProperties(entity);
                entity.RebuildBV();
                entity.GenRigidBody();

                entity.Self.Room.AddEntity(entity);
                world.AddEntity(entity);
            }
        }
Example #12
0
        public static void TR_GenSkeletalModels(World world, Level tr)
        {
            world.SkeletalModels.Resize(tr.Moveables.Length);

            for (var i = 0; i < tr.Moveables.Length; i++)
            {
                var tr_moveable = tr.Moveables[i];
                var smodel = new SkeletalModel();
                smodel.ID = tr_moveable.ObjectID;
                smodel.MeshCount = tr_moveable.NumMeshes;
                TR_GenSkeletalModel(world, i, smodel, tr);
                smodel.FillTransparency();
                world.SkeletalModels[i] = smodel;
            }
        }
Example #13
0
        public static void TR_GenMesh(World world, int mesh_index, BaseMesh mesh, Level tr)
        {
            var texMask = world.EngineVersion == Loader.Engine.TR4 ? TextureIndexMaskTr4 : TextureIndexMask;

            /* TR WAD FORMAT DOCUMENTATION!
             * tr4_face[3,4]_t:
             * flipped texture & 0x8000 (1 bit  ) - horizontal flipping.
             * shape texture   & 0x7000 (3 bits ) - texture sample shape.
             * index texture   & $0FFF  (12 bits) - texture sample index.
             *
             * if bit [15] is set, as in ( texture and $8000 ), it indicates that the texture
             * sample must be flipped horizontally prior to be used.
             * Bits [14..12] as in ( texture and $7000 ), are used to store the texture
             * shape, given by: ( texture and $7000 ) shr 12.
             * The valid values are: 0, 2, 4, 6, 7, as assigned to a square starting from
             * the top-left corner and going clockwise: 0, 2, 4, 6 represent the positions
             * of the square angle of the triangles, 7 represents a quad.
             */

            var trMesh = tr.Meshes[mesh_index];
            mesh.ID = (uint)mesh_index;
            mesh.Center.X = trMesh.Centre.X;
            mesh.Center.Y = trMesh.Centre.Z;
            mesh.Center.Z = trMesh.Centre.Y;
            mesh.Radius = trMesh.CollisionSize;
            mesh.TexturePageCount = world.TextureAtlas.NumAtlasPages + 1;

            mesh.Vertices.Resize(trMesh.Vertices.Length, () => new Vertex());
            for (var i = 0; i < mesh.Vertices.Count; i++)
            {
                mesh.Vertices[i].Position = trMesh.Vertices[i].ToVector3();
                mesh.Vertices[i].Normal = Vector3.Zero; // paranoid
            }
            
            mesh.FindBB();

            mesh.Polygons.Clear();

            // textured triangles
            foreach (var face3 in trMesh.TexturedTriangles)
            {
                var p = new Polygon();

                var tex = tr.ObjectTextures[face3.Texture & texMask];

                p.DoubleSide = face3.Texture >> 15 != 0; // CORRECT, BUT WRONG IN TR3-5

                SetAnimTexture(p, (uint)face3.Texture & texMask, world);

                p.BlendMode = face3.Lighting.HasFlagUns(0x01) ? BlendingMode.Multiply : tex.TransparencyFlags;

                tr_accumulateNormals(trMesh, mesh, 3, face3.Vertices, p);
                tr_setupTexturedFace(trMesh, mesh, face3.Vertices, p);

                world.TextureAtlas.GetCoordinates((uint) face3.Texture & texMask, false, p);

                mesh.Polygons.Add(p);
            }

            // coloured triangles
            foreach (var face3 in trMesh.ColouredTriangles)
            {
                var p = new Polygon();

                var col = face3.Texture & 0xff;
                p.TexIndex = (ushort)world.TextureAtlas.NumAtlasPages;
                p.BlendMode = BlendingMode.Opaque;
                p.AnimID = 0;

                tr_accumulateNormals(trMesh, mesh, 3, face3.Vertices, p);
                tr_setupColoredFace(trMesh, tr, mesh, face3.Vertices, col, p);

                mesh.Polygons.Add(p);
            }

            // textured rectangles
            foreach (var face4 in trMesh.TexturedRectangles)
            {
                var p = new Polygon();

                var tex = tr.ObjectTextures[face4.Texture & texMask];

                p.DoubleSide = face4.Texture >> 15 != 0; // CORRECT, BUT WRONG IN TR3-5

                SetAnimTexture(p, (uint)face4.Texture & texMask, world);

                p.BlendMode = face4.Lighting.HasFlagUns(0x01) ? BlendingMode.Multiply : tex.TransparencyFlags;

                tr_accumulateNormals(trMesh, mesh, 4, face4.Vertices, p);
                tr_setupTexturedFace(trMesh, mesh, face4.Vertices, p);

                world.TextureAtlas.GetCoordinates((uint)face4.Texture & texMask, false, p);

                mesh.Polygons.Add(p);
            }

            // coloured rectangles
            foreach (var face4 in trMesh.ColouredRectangles)
            {
                var p = new Polygon();

                var col = face4.Texture & 0xff;
                p.TexIndex = (ushort)world.TextureAtlas.NumAtlasPages;
                p.BlendMode = BlendingMode.Opaque;
                p.AnimID = 0;

                tr_accumulateNormals(trMesh, mesh, 4, face4.Vertices, p);
                tr_setupColoredFace(trMesh, tr, mesh, face4.Vertices, col, p);

                mesh.Polygons.Add(p);
            }

            // let us normalise normals %)
            foreach (var v in mesh.Vertices)
            {
                v.Normal = v.Normal.SafeNormalize();
            }

            // triangles
            var j = 0;
            for (var i = 0; i < trMesh.TexturedTriangles.Length; i++, j++)
            {
                tr_copyNormals(mesh.Polygons[j], mesh, trMesh.TexturedTriangles[i].Vertices);
            }

            for (var i = 0; i < trMesh.ColouredTriangles.Length; i++, j++)
            {
                tr_copyNormals(mesh.Polygons[j], mesh, trMesh.ColouredTriangles[i].Vertices);
            }

            // triangles
            for (var i = 0; i < trMesh.TexturedRectangles.Length; i++, j++)
            {
                tr_copyNormals(mesh.Polygons[j], mesh, trMesh.TexturedRectangles[i].Vertices);
            }

            for (var i = 0; i < trMesh.ColouredRectangles.Length; i++, j++)
            {
                tr_copyNormals(mesh.Polygons[j], mesh, trMesh.ColouredRectangles[i].Vertices);
            }

            mesh.Vertices.Clear();
            mesh.GenFaces();
            mesh.PolySortInMesh();
        }
Example #14
0
 public static void TR_GenMeshes(World world, Level tr)
 {
     world.Meshes.Resize(tr.Meshes.Length, () => new BaseMesh());
     for (var i = 0; i < world.Meshes.Count; i++)
     {
         TR_GenMesh(world, i, world.Meshes[i], tr);
     }
 }
Example #15
0
        public static unsafe void TR_GenSamples(World world, Level tr)
        {
            world.AudioBuffers = new uint[tr.SamplesCount];
            fixed (uint* ptr = world.AudioBuffers)
                AL.GenBuffers(world.AudioBuffers.Length, ptr);

            // Generate stream track map array.
            // We use scripted amount of tracks to define map bounds.
            // If script had no such parameter, we define map bounds by default.
            var n = EngineLua.GetNumTracks();
            world.StreamTrackMap.Resize(n == 0 ? TR_AUDIO_STREAM_MAP_SIZE : n);

            // Generate new audio effects array.
            world.AudioEffects.Resize(tr.SoundDetails.Length, () => new AudioEffect());

            // Generate new audio emitters array.
            world.AudioEmitters.Resize(tr.SoundSources.Length, () => new AudioEmitter());

            // Cycle through raw samples block and parse them to OpenAL buffers.

            // Different TR versions have different ways of storing samples.
            // TR1:     sample block size, sample block, num samples, sample offsets.
            // TR2/TR3: num samples, sample offsets. (Sample block is in MAIN.SFX.)
            // TR4/TR5: num samples, (uncomp_size-comp_size-sample_data) chain.
            //
            // Hence, we specify certain parse method for each game version.

            if (tr.SamplesData.Length > 0)
            {
                fixed (byte* tmp = tr.SamplesData)
                {
                    var pointer = tmp;
                    switch (tr.GameVersion)
                    {
                        case TRGame.TR1:
                        case TRGame.TR1Demo:
                        case TRGame.TR1UnfinishedBusiness:
                        {
                            world.AudioMap = tr.SoundMap.ToList();

                            for (var i = 0; i < tr.SampleIndices.Length - 1; i++)
                            {
                                pointer = tmp + tr.SampleIndices[i];
                                var size = tr.SampleIndices[i + 1] - tr.SampleIndices[i];
                                Audio.LoadALBufferFromMem(world.AudioBuffers[i], pointer, size);
                            }
                        }
                            break;

                        case TRGame.TR2:
                        case TRGame.TR2Demo:
                        case TRGame.TR3:
                        {
                            // TODO: TR3 gold ??
                            world.AudioMap = tr.SoundMap.ToList();
                            uint ind1 = 0;
                            uint ind2 = 0;
                            var flag = false;
                            var i = 0;
                            while (pointer < tmp + tr.SamplesData.Length - 4)
                            {
                                pointer = tmp + ind2;
                                if (*(int*) pointer == 0x46464952)
                                {
                                    // RIFF
                                    if (!flag)
                                    {
                                        ind1 = ind2;
                                        flag = true;
                                    }
                                    else
                                    {
                                        var uncompSize = ind2 - ind1;
                                        var srcData = tmp + ind1;
                                        Audio.LoadALBufferFromMem(world.AudioBuffers[i], srcData, uncompSize);
                                        i++;
                                        if (i >= world.AudioBuffers.Length)
                                        {
                                            break;
                                        }
                                        ind1 = ind2;
                                    }
                                }
                                ind2++;
                            }
                            var uncomp_Size = (uint) tr.SamplesData.Length - ind1;
                            pointer = tmp + ind1;
                            if (i < world.AudioBuffers.Length)
                            {
                                Audio.LoadALBufferFromMem(world.AudioBuffers[i], pointer, uncomp_Size);
                            }
                            break;
                        }

                        case TRGame.TR4:
                        case TRGame.TR4Demo:
                        case TRGame.TR5:
                            world.AudioMap = tr.SoundMap.ToList();

                            for (var i = 0; i < tr.SamplesCount; i++)
                            {
                                // Parse sample sizes.
                                // Always use comp_size as block length, as uncomp_size is used to cut raw sample data.
                                var uncompSize = *(uint*) pointer;
                                pointer += 4;
                                var compSize = *(uint*) pointer;
                                pointer += 4;

                                // Load WAV sample into OpenAL buffer.
                                Audio.LoadALBufferFromMem(world.AudioBuffers[i], pointer, compSize, uncompSize);

                                // Now we can safely move pointer through current sample data.
                                pointer += compSize;
                            }
                            break;

                        default:
                            world.AudioMap.Clear();
                            tr.SamplesData.Clear();
                            return;
                    }
                }
            }

            // Cycle through SoundDetails and parse them into native OpenTomb
            // audio effects structure.
            for (var i = 0; i < world.AudioEffects.Count; i++)
            {
                if (tr.GameVersion < TRGame.TR3)
                {
                    world.AudioEffects[i].Gain = tr.SoundDetails[i].Volume / 32767.0f;
                        // Max. volume in TR1/TR2 is 32767.
                    world.AudioEffects[i].Chance = tr.SoundDetails[i].Chance;
                }
                else if (tr.GameVersion > TRGame.TR3)
                {
                    world.AudioEffects[i].Gain = tr.SoundDetails[i].Volume / 255.0f; // Max. volume in TR3 is 255.
                    world.AudioEffects[i].Chance = tr.SoundDetails[i].Chance * 255;
                }
                else
                {
                    world.AudioEffects[i].Gain = tr.SoundDetails[i].Volume / 255.0f; // Max. volume in TR3 is 255.
                    world.AudioEffects[i].Chance = tr.SoundDetails[i].Chance * 127;
                }

                world.AudioEffects[i].RandomizeGainVar = 50;
                world.AudioEffects[i].RandomizePitchVar = 50;

                world.AudioEffects[i].Pitch = tr.SoundDetails[i].Pitch / 127.0f + 1.0f;
                world.AudioEffects[i].Range = tr.SoundDetails[i].SoundRange * 1024.0f;

                world.AudioEffects[i].RandomizePitch = tr.SoundDetails[i].UseRandomPitch;
                world.AudioEffects[i].RandomizeGain = tr.SoundDetails[i].UseRandomVolume;

                world.AudioEffects[i].Loop = tr.SoundDetails[i].GetLoopType(Loader.Helper.GameToEngine(tr.GameVersion));

                world.AudioEffects[i].SampleIndex = tr.SoundDetails[i].Sample;
                world.AudioEffects[i].SampleCount = tr.SoundDetails[i].SampleCount;
            }

            // Try to override samples via script.
            // If there is no script entry exist, we just leave default samples.
            // NB! We need to override samples AFTER audio effects array is inited, as override
            //     routine refers to existence of certain audio effect in level.

            Audio.LoadOverridedSamples(world);

            // Hardcoded version-specific fixes!

            switch (world.EngineVersion)
            {
                case Loader.Engine.TR1:
                    // Fix for underwater looped sound.
                    if (world.AudioMap[(int) TR_AUDIO_SOUND.Underwater] >= 0)
                    {
                        world.AudioEffects[world.AudioMap[(int) TR_AUDIO_SOUND.Underwater]].Loop = LoopType.Forward;
                    }
                    break;
                case Loader.Engine.TR2:
                    // Fix for helicopter sound range.
                    if (world.AudioMap[297] >= 0)
                    {
                        world.AudioEffects[world.AudioMap[297]].Range *= 10.0f;
                    }
                    break;
                default:
                    break;
            }

            // Cycle through sound emitters and
            // parse them to native OpenTomb sound emitters structure.

            for (var i = 0; i < world.AudioEmitters.Count; i++)
            {
                world.AudioEmitters[i].EmitterIndex = (uint) i;
                world.AudioEmitters[i].SoundIndex = tr.SoundSources[i].SoundID;
                world.AudioEmitters[i].Position = new Vector3(tr.SoundSources[i].X, tr.SoundSources[i].Z,
                    -tr.SoundSources[i].Y);
                world.AudioEmitters[i].Flags = tr.SoundSources[i].Flags;
            }
        }
Example #16
0
 public static void TR_GenAnimCommands(World world, Level tr)
 {
     world.AnimCommands = tr.AnimCommands.ToArray();
 }
Example #17
0
        // Functions for getting various parameters from legacy TR structs.

        public static void TR_GetBFrameBB_Pos(Level tr, int frame_offset, BoneFrame bone_frame)
        {
            if(frame_offset < tr.FrameData.Length)
            {
                var frame = tr.FrameData.Skip(frame_offset).ToArray();

                bone_frame.BBMin[0] = frame[0];
                bone_frame.BBMin[1] = frame[4];
                bone_frame.BBMin[2] = -frame[3];

                bone_frame.BBMax[0] = frame[1];
                bone_frame.BBMax[1] = frame[5];
                bone_frame.BBMax[2] = -frame[2];

                bone_frame.Position[0] = frame[6];
                bone_frame.Position[1] = frame[8];
                bone_frame.Position[2] = -frame[7];
            }
            else
            {
                bone_frame.BBMin = Vector3.Zero;

                bone_frame.BBMax = Vector3.Zero;

                bone_frame.Position = Vector3.Zero;
            }

            bone_frame.Centre = (bone_frame.BBMin - bone_frame.BBMax) / 2.0f;
        }
Example #18
0
        /**   Animated textures loading.
          *   Natively, animated textures stored as a stream of bitu16s, which
          *   is then parsed on the fly. What we do is parse this stream to the
          *   proper structures to be used later within renderer.
          */
        public static unsafe void TR_GenAnimTextures(World world, Level tr)
        {
            var p0 = new Polygon();
            var p = new Polygon();

            p0.Vertices.Resize(3, () => new Vertex());
            p.Vertices.Resize(3, () => new Vertex());

            fixed(ushort* tmp = tr.AnimatedTextures)
            {
                var pointer = tmp;
                var numUvrotates = tr.AnimatedTexturesUVCount;

                var numSequences = *pointer++; // First word in a stream is sequence count.

                world.AnimSequences.Resize(numSequences, () => new AnimSeq());

                for(var i = 0; i < numSequences; i++)
                {
                    var seq = world.AnimSequences[i];

                    seq.Frames.Resize(*pointer++ + 1, () => new TexFrame());
                    seq.FrameList.Resize(seq.Frames.Count);

                    // Fill up new sequence with frame list
                    seq.AnimType = TR_ANIMTEXTURE.Forward;
                    seq.FrameLock = false; // by default anim is playing
                    seq.UVRotate = false; // by default uvrotate
                    seq.ReverseDirection = false; // Needed for proper reverse-type start-up.
                    seq.FrameRate = 0.05f; // Should be passed as 1 / FPS.
                    seq.FrameTime = 0.0f; // Reset frame time to initial state.
                    seq.CurrentFrame = 0; // Reset current frame to zero.

                    for(var j = 0; j < seq.Frames.Count; j++)
                    {
                        seq.FrameList[j] = *pointer++; // Add one frame.
                    }

                    // UVRotate textures case.
                    // In TR4-5, it is possible to define special UVRotate animation mode.
                    // It is specified by num_uvrotates variable. If sequence belongs to
                    // UVRotate range, each frame will be divided in half and continously
                    // scrolled from one part to another by shifting UV coordinates.
                    // In OpenTomb, we can have BOTH UVRotate and classic frames mode
                    // applied to the same sequence, but there we specify compatibility
                    // method for TR4-5.

                    var uvrotateScript = 0;
                    var tmp1 = EngineLua["UVRotate"];
                    if(tmp1 != null)
                    {
                        uvrotateScript = (int) tmp1;
                    }

                    if(i < numUvrotates)
                    {
                        seq.FrameLock = false; // by default anim is playing

                        seq.UVRotate = true;
                        // Get texture height and divide it in half.
                        // This way, we get a reference value which is used to identify
                        // if scrolling is completed or not.
                        seq.Frames.Resize(8, () => new TexFrame());
                        seq.UVRotateMax = world.TextureAtlas.GetTextureHeight(seq.FrameList[0]) / 2f;
                        seq.UVRotateSpeed = seq.UVRotateMax / seq.Frames.Count;
                        seq.FrameList.Resize(8);

                        if(uvrotateScript > 0)
                        {
                            seq.AnimType = TR_ANIMTEXTURE.Forward;
                        }
                        else if(uvrotateScript < 0)
                        {
                            seq.AnimType = TR_ANIMTEXTURE.Backward;
                        }

                        EngineWorld.TextureAtlas.GetCoordinates(seq.FrameList[0], false, p, 0, true);
                        for(var j = 0; j < seq.Frames.Count; j++)
                        {
                            EngineWorld.TextureAtlas.GetCoordinates(seq.FrameList[0], false, p, (int)(j * seq.UVRotateSpeed), true);
                            seq.Frames[j].TextureIndex = p.TexIndex;

                            var A0 = new[]
                            {
                                p0.Vertices[1].TexCoord[0] - p0.Vertices[0].TexCoord[0], // TODO: p0 hasn't been modified??
                                p0.Vertices[1].TexCoord[1] - p0.Vertices[0].TexCoord[1]
                            };
                            var B0 = new[]
                            {
                                p0.Vertices[2].TexCoord[0] - p0.Vertices[0].TexCoord[0],
                                p0.Vertices[2].TexCoord[1] - p0.Vertices[0].TexCoord[1]
                            };

                            var A = new[]
                            {
                                p.Vertices[1].TexCoord[0] - p.Vertices[0].TexCoord[0],
                                p.Vertices[1].TexCoord[1] - p.Vertices[0].TexCoord[1]
                            };
                            var B = new[]
                            {
                                p.Vertices[2].TexCoord[0] - p.Vertices[0].TexCoord[0],
                                p.Vertices[2].TexCoord[1] - p.Vertices[0].TexCoord[1]
                            };

                            var d = A0[0] * B0[1] - A0[1] * B0[0];
                            seq.Frames[j].Mat[0 + 0 * 2] = (A[0] * B0[1] - A0[1] * B[0]) / d;
                            seq.Frames[j].Mat[1 + 0 * 2] = -(A[1] * B0[1] - A0[1] * B[1]) / d;
                            seq.Frames[j].Mat[0 + 1 * 2] = -(A0[0] * B[0] - A[0] * B0[0]) / d;
                            seq.Frames[j].Mat[1 + 1 * 2] = (A0[0] * B[1] - A[1] * B0[0]) / d;

                            seq.Frames[j].Move[0] = p.Vertices[0].TexCoord[0] -
                                                    (p0.Vertices[0].TexCoord[0] * seq.Frames[j].Mat[0 + 0 * 2] +
                                                     p0.Vertices[0].TexCoord[1] * seq.Frames[j].Mat[0 + 1 * 2]);
                            seq.Frames[j].Move[1] = p.Vertices[0].TexCoord[1] -
                                                    (p0.Vertices[0].TexCoord[0] * seq.Frames[j].Mat[1 + 0 * 2] +
                                                     p0.Vertices[0].TexCoord[1] * seq.Frames[j].Mat[1 + 1 * 2]);
                        }
                    }
                    else
                    {
                        EngineWorld.TextureAtlas.GetCoordinates(seq.FrameList[0], false, p0);
                        for (var j = 0; j < seq.Frames.Count; j++)
                        {
                            EngineWorld.TextureAtlas.GetCoordinates(seq.FrameList[j], false, p);
                            seq.Frames[j].TextureIndex = p.TexIndex;

                            var A0 = new[]
                            {
                                p0.Vertices[1].TexCoord[0] - p0.Vertices[0].TexCoord[0],
                                p0.Vertices[1].TexCoord[1] - p0.Vertices[0].TexCoord[1]
                            };
                            var B0 = new[]
                            {
                                p0.Vertices[2].TexCoord[0] - p0.Vertices[0].TexCoord[0],
                                p0.Vertices[2].TexCoord[1] - p0.Vertices[0].TexCoord[1]
                            };

                            var A = new[]
                            {
                                p.Vertices[1].TexCoord[0] - p.Vertices[0].TexCoord[0],
                                p.Vertices[1].TexCoord[1] - p.Vertices[0].TexCoord[1]
                            };
                            var B = new[]
                            {
                                p.Vertices[2].TexCoord[0] - p.Vertices[0].TexCoord[0],
                                p.Vertices[2].TexCoord[1] - p.Vertices[0].TexCoord[1]
                            };

                            var d = A0[0] * B0[1] - A0[1] * B0[0];
                            seq.Frames[j].Mat[0 + 0 * 2] = (A[0] * B0[1] - A0[1] * B[0]) / d;
                            seq.Frames[j].Mat[1 + 0 * 2] = -(A[1] * B0[1] - A0[1] * B[1]) / d;
                            seq.Frames[j].Mat[0 + 1 * 2] = -(A0[0] * B[0] - A[0] * B0[0]) / d;
                            seq.Frames[j].Mat[1 + 1 * 2] = (A0[0] * B[1] - A[1] * B0[0]) / d;

                            seq.Frames[j].Move[0] = p.Vertices[0].TexCoord[0] -
                                                    (p0.Vertices[0].TexCoord[0] * seq.Frames[j].Mat[0 + 0 * 2] +
                                                     p0.Vertices[0].TexCoord[1] * seq.Frames[j].Mat[0 + 1 * 2]);
                            seq.Frames[j].Move[1] = p.Vertices[0].TexCoord[1] -
                                                    (p0.Vertices[0].TexCoord[0] * seq.Frames[j].Mat[1 + 0 * 2] +
                                                     p0.Vertices[0].TexCoord[1] * seq.Frames[j].Mat[1 + 1 * 2]);
                        }
                    }
                }
            }
        }
Example #19
0
        /// <summary>
        /// Returns real animation frame count
        /// </summary>
        public static int TR_GetNumFramesForAnimation(Level tr, int animation_ind)
        {
            int ret;

            var currAnim = tr.Animations[animation_ind];
            if(currAnim.FrameSize <= 0) // TODO: WTF? byte? < 0 ?
            {
                return 1; // impossible!
            }

            if(animation_ind == tr.Animations.Length - 1)
            {
                ret = (int)(2 * tr.FrameData.Length - currAnim.FrameOffset);
                ret /= currAnim.FrameSize * 2; // it is fully correct!
                return ret;
            }

            var nextAnim = tr.Animations[animation_ind + 1];
            ret = (int)((long)nextAnim.FrameOffset - currAnim.FrameOffset);
            ret /= currAnim.FrameSize * 2;

            return ret;
        }
Example #20
0
 public static void TR_GenRooms(World world, Level tr)
 {
     world.Rooms.Resize(tr.Rooms.Length, () => new Room());
     for (var i = 0; i < world.Rooms.Count; i++)
     {
         TR_GenRoom(i, world.Rooms[i], world, tr);
     }
 }
Example #21
0
        // Main functions which are used to translate legacy TR floor data
        // to native OpenTomb structs.

        public static unsafe int TR_Sector_TranslateFloorData(RoomSector sector, Level tr)
        {
            var ret = 0;

#region Depth 1

            if (sector == null || !sector.TrigIndex.IsBetween(0, tr.FloorData.Length, IB.aEbE))
            {
                return 0;
            }

            sector.Flags = 0; // Clear sector flags before parsing.

            // PARSE FUNCTIONS

            fixed(ushort* tmp = tr.FloorData)
            {
                var end_p = tmp + tr.FloorData.Length - 1;
                var entry = tmp + sector.TrigIndex;
                
                int endBit;

                do
                {
#region Depth 2

                    // TR1 - TR2
                    //function = (*entry) & 0x00FF;                   // 0b00000000 11111111
                    //sub_function = ((*entry) & 0x7F00) >> 8;        // 0b01111111 00000000

                    //TR3+, but works with TR1 - TR2
                    var function = *entry & 0x001F; // 0b00000000 00011111
                    // var functionValue = (*entry & 0x00E0) >> 5; // 0b00000000 11100000  TR3+
                    var subFunction = (*entry & 0x7F00) >> 8; // 0b01111111 00000000

                    endBit = (*entry & 0x8000) >> 15; // 0b10000000 00000000

                    entry++;

                    switch ((FD_FUNC) function)
                    {
                        case FD_FUNC.PortalSector: // PORTAL DATA
                            if (subFunction == 0x00)
                            {
                                if (*entry < EngineWorld.Rooms.Count)
                                {
                                    sector.PortalToRoom = *entry;
                                    sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Ghost;
                                    sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Ghost;
                                }
                                entry++;
                            }
                            break;

                        case FD_FUNC.FloorSlant: // FLOOR SLANT
                            if (subFunction == 0x00)
                            {
                                var rawYslant = *entry & 0x00FF;
                                var rawXslant = (*entry & 0xFF00) >> 8;

                                sector.FloorDiagonalType = TR_SECTOR_DIAGONAL_TYPE.None;
                                sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Solid;

                                if (rawXslant > 0)
                                {
                                    sector.FloorCorners[2][2] -= rawXslant * TR_METERING_STEP;
                                    sector.FloorCorners[3][2] -= rawXslant * TR_METERING_STEP;
                                }
                                else if (rawXslant < 0)
                                {
                                    sector.FloorCorners[0][2] -= Math.Abs(rawXslant) * TR_METERING_STEP;
                                    sector.FloorCorners[1][2] -= Math.Abs(rawXslant) * TR_METERING_STEP;
                                }

                                if (rawYslant > 0)
                                {
                                    sector.FloorCorners[0][2] -= rawYslant * TR_METERING_STEP;
                                    sector.FloorCorners[3][2] -= rawYslant * TR_METERING_STEP;
                                }
                                else if (rawYslant < 0)
                                {
                                    sector.FloorCorners[1][2] -= Math.Abs(rawYslant) * TR_METERING_STEP;
                                    sector.FloorCorners[2][2] -= Math.Abs(rawYslant) * TR_METERING_STEP;
                                }

                                entry++;
                            }
                            break;

                        case FD_FUNC.CeilingSlant: // CEILING SLANT
                            if (subFunction == 0x00)
                            {
                                var rawYslant = *entry & 0x00FF;
                                var rawXslant = (*entry & 0xFF00) >> 8;

                                sector.CeilingDiagonalType = TR_SECTOR_DIAGONAL_TYPE.None;
                                sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Solid;

                                if (rawXslant > 0)
                                {
                                    sector.CeilingCorners[3][2] += rawXslant * TR_METERING_STEP;
                                    sector.CeilingCorners[2][2] += rawXslant * TR_METERING_STEP;
                                }
                                else if (rawXslant < 0)
                                {
                                    sector.CeilingCorners[1][2] += Math.Abs(rawXslant) * TR_METERING_STEP;
                                    sector.CeilingCorners[0][2] += Math.Abs(rawXslant) * TR_METERING_STEP;
                                }

                                if (rawYslant > 0)
                                {
                                    sector.CeilingCorners[1][2] += rawYslant * TR_METERING_STEP;
                                    sector.CeilingCorners[2][2] += rawYslant * TR_METERING_STEP;
                                }
                                else if (rawYslant < 0)
                                {
                                    sector.CeilingCorners[0][2] += Math.Abs(rawYslant) * TR_METERING_STEP;
                                    sector.CeilingCorners[3][2] += Math.Abs(rawYslant) * TR_METERING_STEP;
                                }

                                entry++;
                            }
                            break;

                        case FD_FUNC.Trigger: // TRIGGERS
                        {
#region Trigger

                            var header = ""; // Header condition
                            var once_condition = ""; // One-shot condition
                            var cont_events = ""; // Continous trigger events
                            var single_events = ""; // One-shot trigger events
                            var item_events = ""; // Item activation events
                            var anti_events = ""; // Item deactivation events, if needed

                            var script = ""; // Final script compile

                            var buf = "\0"; // Stream buffer
                            var buf2 = "\0"; // Conditional pre-buffer for SWITCH triggers

                            var activator = Activator.Normal; // Activator is normal by default.
                            var actionType = ActionType.Normal; // Action type is normal by default.
                            var condition = 0; // No condition by default.
                            var maskMode = AMASK_OP_OR; // Activation mask by default.

                            var timerField = *entry & 0x00FF; // Used as common parameter for some commands.
                            var triggerMask = (*entry & 0x3E00) >> 9;
                            var onlyOnce = (*entry & 0x0100) >> 8; // Lock out triggered items after activation.

                            // Processed entities lookup array initialization.

                            var entLookupTable = Helper.FillArray(0xFF, 64);

                            // Activator type is LARA for all triggers except HEAVY ones, which are triggered by
                            // some specific entity classes.

                            var activatorType = ((TriggerTypes) subFunction).IsAnyOf(TriggerTypes.Heavy,
                                TriggerTypes.HeavyAntiTrigger, TriggerTypes.HeavySwitch)
                                ? ActivatorType.Misc
                                : ActivatorType.Lara;

                            // Table cell header

                            buf =
                                Helper.Format(
                                    "trigger_list[{0}] = {{activator_type = {1}, func = function(entity_index) \n",
                                    sector.TrigIndex, (int) activatorType);

                            script += buf;
                            buf = ""; // Zero out buffer to prevent further trashing.

                            switch ((TriggerTypes) subFunction)
                            {
                                case TriggerTypes.Trigger:
                                case TriggerTypes.Heavy:
                                    activator = Activator.Normal;
                                    break;

                                case TriggerTypes.Pad:
                                case TriggerTypes.AntiPad:
                                    // Check move type for triggering entity.
                                    buf = Helper.Format("if(getEntityMoveType(entity_index) == {0}) then \n",
                                        (int) MoveType.OnFloor);
                                    if (subFunction == (int) TriggerTypes.AntiPad) actionType = ActionType.Anti;
                                    condition = 1; // Set additional condition.
                                    break;

                                case TriggerTypes.Switch:
                                    // Set activator and action type for now; conditions are linked with first item in operand chain.
                                    activator = Activator.Switch;
                                    actionType = ActionType.Switch;
                                    maskMode = AMASK_OP_XOR;
                                    break;

                                case TriggerTypes.HeavySwitch:
                                    // Action type remains normal, as HEAVYSWITCH acts as "heavy trigger" with activator mask filter.
                                    activator = Activator.Switch;
                                    maskMode = AMASK_OP_XOR;
                                    break;

                                case TriggerTypes.Key:
                                    // Action type remains normal, as key acts one-way (no need in switch routines).
                                    activator = Activator.Key;
                                    break;

                                case TriggerTypes.Pickup:
                                    // Action type remains normal, as pick-up acts one-way (no need in switch routines).
                                    activator = Activator.Pickup;
                                    break;

                                case TriggerTypes.Combat:
                                    // Check weapon status for triggering entity.
                                    buf = " if(getCharacterCombatMode(entity_index) > 0) then \n";
                                    condition = 1; // Set additional condition.
                                    break;

                                case TriggerTypes.Dummy:
                                case TriggerTypes.Skeleton: // TODO: Find the meaning later!!!
                                    // These triggers are being parsed, but not added to trigger script!
                                    actionType = ActionType.Bypass;
                                    break;

                                case TriggerTypes.AntiTrigger:
                                case TriggerTypes.HeavyAntiTrigger:
                                    actionType = ActionType.Anti;
                                    break;

                                case TriggerTypes.Monkey:
                                case TriggerTypes.Climb:
                                    // Check move type for triggering entity.
                                    buf = Helper.Format(" if(getEntityMoveType(entity_index) == {0}) then \n",
                                        (int)
                                            (subFunction == (int) TriggerTypes.Monkey
                                                ? MoveType.Monkeyswing
                                                : MoveType.Climbing));
                                    condition = 1; // Set additional condition.
                                    break;

                                case TriggerTypes.Tightrope:
                                    // Check state range for triggering entity.
                                    buf =
                                        Helper.Format(
                                            " local state = getEntityState(entity_index) \n" +
                                            " if((state >= {0}) and (state <= {1})) then \n",
                                            (int) TR_STATE.LaraTightropeIdle, (int) TR_STATE.LaraTightropeExit);
                                    condition = 1; // Set additional condition.
                                    break;

                                case TriggerTypes.CrawlDuck:
                                    // Check state range for triggering entity.
                                    buf =
                                        Helper.Format(
                                            " local state = getEntityState(entity_index) \n" +
                                            " if((state >= {0}) and (state <= {1})) then \n",
                                            (int) TR_ANIMATION.LaraCrouchRollForwardBegin,
                                            (int) TR_ANIMATION.LaraCrawlSmashLeft);
                                    // TODO: Inverted STATE and ANIMATION?
                                    condition = 1; // Set additional condition.
                                    break;
                            }

                            header += buf; // Add condition to header.

                            int contBit;
                            var argn = 0;

                            // Now parse operand chain for trigger function!

                            do
                            {
                                entry++;

                                var triggerFunction = (*entry & 0x7C00) >> 10; // 0b01111100 00000000
                                var operands = *entry & 0x03FF; // 0b00000011 11111111
                                contBit = (*entry & 0x8000) >> 15; // 0b10000000 00000000

                                switch ((FD_TRIGFUNC) triggerFunction)
                                {
                                    case FD_TRIGFUNC.Object: // ACTIVATE / DEACTIVATE object
                                        // If activator is specified, first item operand counts as activator index (except
                                        // heavy switch case, which is ordinary heavy trigger case with certain differences).
                                        if (argn == 0 && activator != Activator.Normal)
                                        {
                                            switch (activator)
                                            {
                                                case Activator.Switch:
                                                    if (actionType == ActionType.Switch)
                                                    {
                                                        // Switch action type case.
                                                        script +=
                                                            Helper.Format(
                                                                " local switch_state = getEntityState({0}); \n" +
                                                                " local switch_sectorstatus = getEntitySectorStatus({0}); \n" +
                                                                " local switch_mask = getEntityMask({0}); \n\n",
                                                                operands);
                                                    }
                                                    else
                                                    {
                                                        // Ordinary type case (e.g. heavy switch).
                                                        script +=
                                                            Helper.Format(
                                                                " local switch_sectorstatus = getEntitySectorStatus(entity_index); \n" +
                                                                " local switch_mask = getEntityMask(entity_index); \n\n");
                                                    }

                                                    script +=
                                                        Helper.Format(
                                                            " if(switch_mask == 0) then switch_mask = 0x1F end; \n" +
                                                            " switch_mask = bit32.band(switch_mask, 0x{0:X2}); \n\n",
                                                            triggerMask);
                                                    if (actionType == ActionType.Switch)
                                                    {
                                                        // Switch action type case.
                                                        buf =
                                                            Helper.Format(
                                                                " if((switch_state == 0) and switch_sectorstatus) then \n" +
                                                                "   setEntitySectorStatus({0}, false); \n" +
                                                                "   setEntityTimer({0}, {1}); \n", operands,
                                                                timerField);
                                                        if (EngineWorld.EngineVersion >= Loader.Engine.TR3 &&
                                                            onlyOnce != 0)
                                                        {
                                                            // Just lock out activator, no anti-action needed.
                                                            buf2 = Helper.Format(" setEntityLock({0}, true) \n",
                                                                operands);
                                                        }
                                                        else
                                                        {
                                                            // Create statement for antitriggering a switch.
                                                            buf2 =
                                                                Helper.Format(
                                                                    " elseif((switch_state == 1) and switch_sectorstatus) then\n" +
                                                                    "   setEntitySectorStatus({0}, false); \n   setEntityTimer({0}, 0); \n",
                                                                    operands);
                                                        }
                                                    }
                                                    else
                                                    {
                                                        // Ordinary type case (e.g. heavy switch).
                                                        item_events +=
                                                            Helper.Format(
                                                                "   activateEntity({0}, entity_index, switch_mask, {1}, {2}, {3}); \n",
                                                                operands, maskMode, onlyOnce != 0 ? "true" : "false",
                                                                timerField);
                                                        buf =
                                                            " if(not switch_sectorstatus) then \n" +
                                                            "   setEntitySectorStatus(entity_index, true) \n";
                                                    }
                                                    break;

                                                case Activator.Key:
                                                    buf =
                                                        Helper.Format(
                                                            " if((getEntityLock({0})) and (not getEntitySectorStatus({0}))) then \n" +
                                                            "   setEntitySectorStatus({0}, true); \n",
                                                            operands);
                                                    break;

                                                case Activator.Pickup:
                                                    buf =
                                                        Helper.Format(
                                                            " if((not getEntityEnability({0})) and (not getEntitySectorStatus({0}))) then \n" +
                                                            "   setEntitySectorStatus({0}, true); \n",
                                                            operands);
                                                    break;
                                            }

                                            script += buf;
                                        }
                                        else
                                        {
                                            // In many original Core Design levels, level designers left dublicated entity activation operands.
                                            // This results in setting same activation mask twice, effectively blocking entity from activation.
                                            // To prevent this, a lookup table was implemented to know if entity already had its activation
                                            // command added.
                                            if (!Res_IsEntityProcessed(entLookupTable, (ushort) operands))
                                            {
                                                // Other item operands are simply parsed as activation functions. Switch case is special, because
                                                // function is fed with activation mask argument derived from activator mask filter (switch_mask),
                                                // and also we need to process deactivation in a same way as activation, excluding resetting timer
                                                // field. This is needed for two-way switch combinations (e.g. Palace Midas).
                                                if (activator == Activator.Switch)
                                                {
                                                    item_events +=
                                                        Helper.Format(
                                                            "   activateEntity({0}, entity_index, switch_mask, {1}, {2}, {3}); \n",
                                                            operands, maskMode, onlyOnce != 0 ? "true" : "false",
                                                            timerField);
                                                    anti_events +=
                                                        Helper.Format(
                                                            "   activateEntity({0}, entity_index, switch_mask, {1}, {2}, 0); \n",
                                                            operands, maskMode, onlyOnce != 0 ? "true" : "false");
                                                }
                                                else
                                                {
                                                    item_events +=
                                                        Helper.Format(
                                                            "   activateEntity({0}, entity_index, 0x{1:X2}, {2}, {3}, {4}); \n",
                                                            operands, triggerMask, maskMode,
                                                            onlyOnce != 0 ? "true" : "false",
                                                            timerField);
                                                    anti_events +=
                                                        Helper.Format(
                                                            "   deactivateEntity({0}, entity_index, {1}); \n",
                                                            operands, onlyOnce != 0 ? "true" : "false");
                                                }
                                            }
                                        }
                                        argn++;
                                        break;

                                    case FD_TRIGFUNC.CameraTarget:
                                    {
                                        var camIndex = *entry & 0x007F;
                                        entry++;
                                        var camTimer = *entry & 0x00FF;
                                        var camOnce = (*entry & 0x0100) >> 8;
                                        var camZoom = EngineWorld.EngineVersion < Loader.Engine.TR2
                                            ? (*entry & 0x0400) >> 10
                                            : (*entry & 0x1000) >> 12;
                                        contBit = (*entry & 0x8000) >> 15; // 0b10000000 00000000

                                        single_events += Helper.Format("   setCamera({0}, {1}, {2}, {3}); \n",
                                            camIndex, camTimer, camOnce, camZoom);
                                    }
                                        break;

                                    case FD_TRIGFUNC.UwCurrent:
                                        cont_events += Helper.Format("   moveToSink(entity_index, {0}); \n",
                                            operands);
                                        break;

                                    case FD_TRIGFUNC.FlipMap:
                                        // FLIPMAP trigger acts two-way for switch cases, so we add FLIPMAP off event to
                                        // anti-events array.
                                        if (activator == Activator.Switch)
                                        {
                                            single_events +=
                                                Helper.Format(
                                                    "   setFlipMap({0}, switch_mask, 1); \n" +
                                                    "   setFlipState({0}, true); \n",
                                                    operands);
                                        }
                                        else
                                        {
                                            single_events +=
                                                Helper.Format(
                                                    "   setFlipMap({0}, 0x{1:X2}, 0); \n" +
                                                    "   setFlipState({0}, true); \n",
                                                    operands, triggerMask);
                                        }
                                        break;

                                    case FD_TRIGFUNC.FlipOn:
                                        // FLIP_ON trigger acts one-way even in switch cases, i.e. if you un-pull
                                        // the switch with FLIP_ON trigger, room will remain flipped.
                                        single_events += Helper.Format("   setFlipState({0}, true); \n", operands);
                                        break;

                                    case FD_TRIGFUNC.FlipOff:
                                        // FLIP_OFF trigger acts one-way even in switch cases, i.e. if you un-pull
                                        // the switch with FLIP_OFF trigger, room will remain unflipped.
                                        single_events += Helper.Format("   setFlipState({0}, false); \n", operands);
                                        break;

                                    case FD_TRIGFUNC.LookAt:
                                        single_events += Helper.Format("   setCamTarget({0}, {1}); \n", operands,
                                            timerField);
                                        break;

                                    case FD_TRIGFUNC.EndLevel:
                                        single_events += Helper.Format("   setLevel({0}); \n", operands);
                                        break;

                                    case FD_TRIGFUNC.PlayTrack:
#if !NO_AUDIO
                                            // Override for looped BGM tracks in TR1: if there are any sectors
                                            // triggering looped tracks, ignore it, as BGM is always set in script.
                                            if (EngineWorld.EngineVersion < Loader.Engine.TR2)
                                        {
                                            TR_AUDIO_STREAM_TYPE looped;
                                            string tmp1;
                                            TR_AUDIO_STREAM_METHOD tmp2;
                                            EngineLua.GetSoundtrack(operands, out tmp1, out tmp2, out looped);
                                            if (looped == TR_AUDIO_STREAM_TYPE.Background) break;
                                        }
#endif
                                        single_events += Helper.Format("   playStream({0}, 0x{1:X2}); \n", operands,
                                            (triggerMask << 1) + onlyOnce);
                                        break;

                                    case FD_TRIGFUNC.FlipEffect:
                                        cont_events += Helper.Format("   doEffect({0}, entity_index, {1}); \n",
                                            operands, timerField);
                                        break;

                                    case FD_TRIGFUNC.Secret:
                                        single_events += Helper.Format("   findSecret({0}); \n", operands);
                                        break;

                                    case FD_TRIGFUNC.ClearBodies:
                                        single_events += "   clearBodies(); \n";
                                        break;

                                    case FD_TRIGFUNC.FlyBy:
                                        entry++;
                                        var flybyOnce = (*entry & 0x0100) >> 8;
                                        contBit = (*entry & 0x8000) >> 15;

                                        cont_events += Helper.Format("   playFlyby({0}, {1}); \n", operands,
                                            flybyOnce);
                                        break;

                                    case FD_TRIGFUNC.Cutscene:
                                        single_events += Helper.Format("   playCutscene({0}); \n", operands);
                                        break;

                                    default: // UNKNOWN!
                                        break;
                                }
                            } while (contBit == 0 && entry < end_p);

                            if (!string.IsNullOrWhiteSpace(script))
                            {
                                script += header;

                                // Heavy trigger and antitrigger item events are engaged ONLY
                                // once, when triggering item is approaching sector. Hence, we
                                // copy item events to single events and nullify original item
                                // events sequence to prevent it to be merged into continous
                                // events.

                                if (((TriggerTypes) subFunction).IsAnyOf(TriggerTypes.Heavy,
                                    TriggerTypes.HeavyAntiTrigger))
                                {
                                    if (actionType == ActionType.Anti)
                                    {
                                        single_events += anti_events;
                                    }
                                    else
                                    {
                                        single_events += item_events;
                                    }

                                    anti_events = "";
                                    item_events = "";
                                }

                                if (activator == Activator.Normal) // Ordinary trigger cases.
                                {
                                    if (!string.IsNullOrWhiteSpace(single_events))
                                    {
                                        if (condition != 0)
                                            once_condition += " ";
                                        once_condition += " if(not getEntitySectorStatus(entity_index)) then \n";
                                        script += once_condition;
                                        script += single_events;
                                        script += "   setEntitySectorStatus(entity_index, true); \n";

                                        if (condition != 0)
                                        {
                                            script += "  end;\n"; // First ENDIF is tabbed for extra condition.
                                        }
                                        else
                                        {
                                            script += " end;\n";
                                        }
                                    }

                                    // Item commands kind depends on action type. If type is ANTI, then item
                                    // antitriggering is engaged. If type is normal, ordinary triggering happens
                                    // in cycle with other continous commands. It is needed to prevent timer dispatch
                                    // before activator leaves trigger sector.

                                    if (actionType == ActionType.Anti) // TODO: Duplicate of L3543?
                                    {
                                        script += anti_events;
                                    }
                                    else
                                    {
                                        script += item_events;
                                    }

                                    script += cont_events;
                                    if (condition != 0)
                                        script += " end;\n"; // Additional ENDIF for extra condition.
                                }
                                else // SWITCH, KEY and ITEM cases.
                                {
                                    script += single_events;
                                    script += item_events;
                                    script += cont_events;
                                    if (actionType == ActionType.Switch && activator == Activator.Switch)
                                    {
                                        script += buf2;
                                        if (EngineWorld.EngineVersion < Loader.Engine.TR3 || onlyOnce == 0)
                                        {
                                            script += single_events;
                                            script += anti_events; // Single/continous events are engaged along with
                                            script += cont_events; // antitriggered items, as described above.
                                        }
                                    }
                                    script += " end;\n";
                                }

                                script += "return 1;\n" +
                                          "end }\n"; // Finalize the entry.
                            }

                            if (actionType != ActionType.Bypass)
                            {
                                //Sys.DebugLog("triggers.lua", script);    // Debug!
                                EngineLua.DoString(script);
                            }

#endregion
                        }
                            break;

                        case FD_FUNC.Death:
                            sector.Flags |= SectorFlag.Death;
                            break;

                        case FD_FUNC.Climb:
                            // First 4 sector flags are similar to subfunction layout.
                            sector.Flags |= (SectorFlag) subFunction;
                            break;

                        case FD_FUNC.Monkey:
                            sector.Flags |= SectorFlag.ClimbCeiling;
                            break;

                        case FD_FUNC.MinecartLeft:
                            // Minecart left (TR3) and trigger triggerer mark (TR4-5) has the same flag value.
                            // We re-parse them properly here.
                            if (tr.GameVersion < TRGame.TR4)
                            {
                                sector.Flags |= SectorFlag.MinecartLeft;
                            }
                            else
                            {
                                sector.Flags |= SectorFlag.TriggererMark;
                            }
                            break;

                        case FD_FUNC.MinecartRight:
                            // Minecart right (TR3) and beetle mark (TR4-5) has the same flag value.
                            // We re-parse them properly here.
                            if (tr.GameVersion < TRGame.TR4)
                            {
                                sector.Flags |= SectorFlag.MinecartRight;
                            }
                            else
                            {
                                sector.Flags |= SectorFlag.BeetleMark;
                            }
                            break;

                        default:
                            // Other functions are TR3+ collisional triangle functions.
                            if (((FD_FUNC) function).IsBetween(FD_FUNC.FloorTriangleNW,
                                FD_FUNC.CeilingTriangleNEPortalSE))
                            {
                                entry--; // Go back, since these functions are parsed differently.

                                endBit = (*entry & 0x8000) >> 15; // 0b10000000 00000000

#if NOPE
                    int16_t  slope_t01 = ((*entry) & 0x7C00) >> 10;      // 0b01111100 00000000
                    int16_t  slope_t00 = ((*entry) & 0x03E0) >> 5;       // 0b00000011 11100000
                    // uint16_t slope_func = ((*entry) & 0x001F);            // 0b00000000 00011111
                    // t01/t02 are 5-bit values, where sign is specified by 0x10 mask.
                    if(slope_t01 & 0x10) slope_t01 |= 0xFFF0;
                    if(slope_t00 & 0x10) slope_t00 |= 0xFFF0;
#endif

                                entry++;

                                var slope_t13 = (uint) (*entry & 0xF000) >> 12; // 0b11110000 00000000
                                var slope_t12 = (uint) (*entry & 0x0F00) >> 8; // 0b00001111 00000000
                                var slope_t11 = (uint) (*entry & 0x00F0) >> 4; // 0b00000000 11110000
                                var slope_t10 = (uint) (*entry & 0x000F); // 0b00000000 00001111

                                entry++;

                                var overallAdjustment =
                                    Res_Sector_BiggestCorner(slope_t10, slope_t11, slope_t12, slope_t13) *
                                    TR_METERING_STEP;

                                if (((FD_FUNC) function).IsAnyOf(FD_FUNC.FloorTriangleNW,
                                    FD_FUNC.FloorTriangleNWPortalSW, FD_FUNC.FloorTriangleNWPortalNE))
                                {
                                    sector.FloorDiagonalType = TR_SECTOR_DIAGONAL_TYPE.NorthWest;

                                    sector.FloorCorners[0][2] -= overallAdjustment - slope_t12 * TR_METERING_STEP;
                                    sector.FloorCorners[1][2] -= overallAdjustment - slope_t13 * TR_METERING_STEP;
                                    sector.FloorCorners[2][2] -= overallAdjustment - slope_t10 * TR_METERING_STEP;
                                    sector.FloorCorners[3][2] -= overallAdjustment - slope_t11 * TR_METERING_STEP;

                                    if (function == (int) FD_FUNC.FloorTriangleNWPortalSW)
                                    {
                                        sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalA;
                                    }
                                    else if (function == (int) FD_FUNC.FloorTriangleNWPortalSW)
                                    {
                                        sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalB;
                                    }
                                    else
                                    {
                                        sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Solid;
                                    }
                                }
                                else if (((FD_FUNC) function).IsAnyOf(FD_FUNC.FloorTriangleNE,
                                    FD_FUNC.FloorTriangleNEPortalNW, FD_FUNC.FloorTriangleNEPortalSE))
                                {
                                    sector.FloorDiagonalType = TR_SECTOR_DIAGONAL_TYPE.NorthEast;

                                    sector.FloorCorners[0][2] -= overallAdjustment - slope_t12 * TR_METERING_STEP;
                                    sector.FloorCorners[1][2] -= overallAdjustment - slope_t13 * TR_METERING_STEP;
                                    sector.FloorCorners[2][2] -= overallAdjustment - slope_t10 * TR_METERING_STEP;
                                    sector.FloorCorners[3][2] -= overallAdjustment - slope_t11 * TR_METERING_STEP;

                                    if (function == (int) FD_FUNC.FloorTriangleNEPortalNW)
                                    {
                                        sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalA;
                                    }
                                    else if (function == (int) FD_FUNC.FloorTriangleNEPortalSE)
                                    {
                                        sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalB;
                                    }
                                    else
                                    {
                                        sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Solid;
                                    }
                                }
                                else if (((FD_FUNC) function).IsAnyOf(FD_FUNC.CeilingTriangleNW,
                                    FD_FUNC.CeilingTriangleNWPortalSW, FD_FUNC.CeilingTriangleNWPortalNE))
                                {
                                    sector.CeilingDiagonalType = TR_SECTOR_DIAGONAL_TYPE.NorthWest;

                                    sector.CeilingCorners[0][2] += overallAdjustment - slope_t11 * TR_METERING_STEP;
                                    sector.CeilingCorners[1][2] += overallAdjustment - slope_t10 * TR_METERING_STEP;
                                    sector.CeilingCorners[2][2] += overallAdjustment - slope_t13 * TR_METERING_STEP;
                                    sector.CeilingCorners[3][2] += overallAdjustment - slope_t12 * TR_METERING_STEP;

                                    if (function == (int) FD_FUNC.CeilingTriangleNWPortalSW)
                                    {
                                        sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalA;
                                    }
                                    else if (function == (int) FD_FUNC.CeilingTriangleNWPortalSW)
                                    {
                                        sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalB;
                                    }
                                    else
                                    {
                                        sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Solid;
                                    }
                                }
                                else if (((FD_FUNC) function).IsAnyOf(FD_FUNC.CeilingTriangleNE,
                                    FD_FUNC.CeilingTriangleNEPortalNW, FD_FUNC.CeilingTriangleNEPortalSE))
                                {
                                    sector.CeilingDiagonalType = TR_SECTOR_DIAGONAL_TYPE.NorthEast;

                                    sector.CeilingCorners[0][2] += overallAdjustment - slope_t11 * TR_METERING_STEP;
                                    sector.CeilingCorners[1][2] += overallAdjustment - slope_t10 * TR_METERING_STEP;
                                    sector.CeilingCorners[2][2] += overallAdjustment - slope_t13 * TR_METERING_STEP;
                                    sector.CeilingCorners[3][2] += overallAdjustment - slope_t12 * TR_METERING_STEP;

                                    if (function == (int) FD_FUNC.CeilingTriangleNEPortalNW)
                                    {
                                        sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalA;
                                    }
                                    else if (function == (int) FD_FUNC.CeilingTriangleNEPortalSE)
                                    {
                                        sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.DoorVerticalB;
                                    }
                                    else
                                    {
                                        sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Solid;
                                    }
                                }
                            }
                            else
                            {
                                // Unknown floordata function!
                            }
                            break;
                    }

#endregion
                    
                    ret++;
                } while (endBit == 0 && entry < end_p);
            }
#endregion

            return ret;
        }
Example #22
0
        public static void TR_GenRoom(int roomIndex, Room room, World world, Level tr)
        {
            var trRoom = tr.Rooms[roomIndex];

#region Room properties

            room.ID = (uint) roomIndex;
            room.Active = true;
            room.Frustum = new List<Frustum>();
            room.Flags = trRoom.Flags;
            room.LightMode = trRoom.LightMode;
            room.ReverbInfo = (byte) trRoom.ReverbInfo;
            room.WaterScheme = trRoom.WaterScheme;
            room.AlternateGroup = (byte) trRoom.AlternateGroup;

            room.Transform = new Transform();
            room.Transform.SetIdentity();
            room.Transform.Origin = trRoom.Offset.ToVector3();
            room.AmbientLighting = new float[3];
            room.AmbientLighting[0] = trRoom.LightColor.R * 2;
            room.AmbientLighting[1] = trRoom.LightColor.G * 2;
            room.AmbientLighting[2] = trRoom.LightColor.B * 2;
            room.Self = new EngineContainer();
            room.Self.Room = room;
            room.Self.Object = room;
            room.Self.ObjectType = OBJECT_TYPE.RoomBase;
            room.NearRoomList = new List<Room>();
            room.OverlappedRoomList = new List<Room>();

            room.GenMesh(world, (uint) roomIndex, tr);

            room.BtBody = null;
            // let's load static room meshes
            room.StaticMesh = new List<StaticMesh>();

#endregion

#region Static meshes

            Loader.StaticMesh trStatic;

            for (var i = 0; i < trRoom.StaticMeshes.Length; i++)
            {
                var trsm = trRoom.StaticMeshes[i];
                trStatic = tr.FindStaticMeshById(trsm.ObjectID);
                if (trStatic.Equals(default(Loader.StaticMesh)))
                {
                    continue;
                }
                var rStatic = new StaticMesh();
                rStatic.Self = new EngineContainer();
                rStatic.Self.Room = room;
                rStatic.Self.Object = rStatic;
                rStatic.Self.ObjectType = OBJECT_TYPE.StaticMesh;
                rStatic.ObjectID = trsm.ObjectID;
                rStatic.Mesh = world.Meshes[(int) tr.MeshIndices[trStatic.Mesh]];
                rStatic.Position = trsm.Position.ToVector3();
                rStatic.Rotation = new Vector3(trsm.Rotation, 0.0f, 0.0f);
                rStatic.Tint[0] = trsm.Tint.R * 2;
                rStatic.Tint[1] = trsm.Tint.G * 2;
                rStatic.Tint[2] = trsm.Tint.B * 2;
                rStatic.Tint[3] = trsm.Tint.A * 2;

                rStatic.CBBMin.X = trStatic.CollisionBox[0].X;
                rStatic.CBBMin.Y = -trStatic.CollisionBox[0].Z;
                rStatic.CBBMin.Z = trStatic.CollisionBox[1].Y;
                rStatic.CBBMax.X = trStatic.CollisionBox[1].X;
                rStatic.CBBMax.Y = -trStatic.CollisionBox[1].Z;
                rStatic.CBBMax.Z = trStatic.CollisionBox[0].Y;

                rStatic.VBBMin.X = trStatic.VisibilityBox[0].X;
                rStatic.VBBMin.Y = -trStatic.VisibilityBox[0].Z;
                rStatic.VBBMin.Z = trStatic.VisibilityBox[1].Y;

                rStatic.VBBMax.X = trStatic.VisibilityBox[1].X;
                rStatic.VBBMax.Y = -trStatic.VisibilityBox[1].Z;
                rStatic.VBBMax.Z = trStatic.VisibilityBox[0].Y;

                rStatic.OBB.Transform = rStatic.Transform;
                rStatic.OBB.Radius = rStatic.Mesh.Radius;
                rStatic.Transform.SetIdentity();
                VMath.Mat4_Translate(rStatic.Transform, rStatic.Position);
                VMath.Mat4_RotateZ(rStatic.Transform, rStatic.Rotation.X);
                rStatic.WasRendered = 0;
                rStatic.OBB.Rebuild(rStatic.VBBMin, rStatic.VBBMax);
                rStatic.OBB.DoTransform();

                rStatic.BtBody = null;
                rStatic.Hide = false;

                // Disable static mesh collision, if flag value is 3 (TR1) or all bounding box
                // coordinates are equal (TR2-5).

                if (trStatic.Flags == 3 ||
                    trStatic.CollisionBox[0].X == -trStatic.CollisionBox[0].Y &&
                    trStatic.CollisionBox[0].Y == trStatic.CollisionBox[0].Z &&
                    trStatic.CollisionBox[1].X == -trStatic.CollisionBox[1].Y &&
                    trStatic.CollisionBox[1].Y == trStatic.CollisionBox[1].Z)
                {
                    rStatic.Self.CollisionType = COLLISION_TYPE.None;
                }
                else
                {
                    rStatic.Self.CollisionType = COLLISION_TYPE.Static;
                    rStatic.Self.CollisionShape = COLLISION_SHAPE.Box;
                }

                // Set additional static mesh properties from level script override.

                Res_SetStaticMeshProperties(rStatic);

                // Set static mesh collision.

                if (rStatic.Self.CollisionType != COLLISION_TYPE.None)
                {
                    CollisionShape cshape;
                    switch (rStatic.Self.CollisionShape)
                    {
                        case COLLISION_SHAPE.Box:
                            cshape = BT_CSfromBBox(rStatic.CBBMin, rStatic.CBBMax, true, true);
                            break;

                        case COLLISION_SHAPE.BoxBase:
                            cshape = BT_CSfromBBox(rStatic.Mesh.BBMin, rStatic.Mesh.BBMax, true, true);
                            break;

                        case COLLISION_SHAPE.Trimesh:
                            cshape = BT_CSfromMesh(rStatic.Mesh, true, true, true);
                            break;

                        case COLLISION_SHAPE.TrimeshConvex:
                            cshape = BT_CSfromMesh(rStatic.Mesh, true, true, true);
                            break;

                        default:
                            cshape = null;
                            break;
                    }

                    if (cshape != null)
                    {
                        var startTransform = rStatic.Transform;
                        var motionState = new DefaultMotionState(((Matrix4) startTransform).ToBullet());
                        var localInertia = Vector3.Zero;
                        rStatic.BtBody =
                            new RigidBody(new RigidBodyConstructionInfo(0.0f, motionState, cshape, localInertia.ToBullet()));
                        BtEngineDynamicsWorld.AddRigidBody(rStatic.BtBody, CollisionFilterGroups.AllFilter,
                            CollisionFilterGroups.AllFilter);
                        rStatic.BtBody.UserObject = rStatic.Self;
                    }
                }

                room.StaticMesh.Add(rStatic);
            }

#endregion

#region Sprites

            foreach (var trs in trRoom.Sprites)
            {
                var rs = new RoomSprite();
                if (trs.Texture.IsBetween(0, world.Sprites.Count, IB.aIbE))
                {
                    rs.Sprite = world.Sprites[trs.Texture];
                    rs.Position = trRoom.Vertices[trs.Vertex].Vertex.ToVector3() + room.Transform.Origin;
                }
                room.Sprites.Add(rs);
            }

#endregion

#region Sectors

            room.SectorsX = trRoom.Num_X_Sectors;
            room.SectorsY = trRoom.Num_Z_Sectors;
            room.Sectors = new List<RoomSector>();
            room.Sectors.Resize(room.SectorsX * room.SectorsY, () => new RoomSector());

            // base sectors information loading and collisional mesh creation

            // To avoid manipulating with unnecessary information, we declare simple
            // heightmap here, which will be operated with sector and floordata parsing,
            // then vertical inbetween polys will be constructed, and Bullet collisional
            // object will be created. Afterwards, this heightmap also can be used to
            // quickly detect slopes for pushable blocks and other entities that rely on
            // floor level.

            for (var i = 0; i < room.Sectors.Count; i++)
            {
                var sector = room.Sectors[i];

                // Filling base sectors information.

                sector.IndexX = (short) (i / room.SectorsY);
                sector.IndexY = (short) (i % room.SectorsY);

                sector.Position.X = room.Transform.Origin.X + (sector.IndexX + 0.5f) * TR_METERING_SECTORSIZE;
                sector.Position.Y = room.Transform.Origin.Y + (sector.IndexY + 0.5f) * TR_METERING_SECTORSIZE;
                sector.Position.Z = 0.5f * (trRoom.Y_Bottom + trRoom.Y_Top);

                sector.OwnerRoom = room;

                if (tr.GameVersion < TRGame.TR3)
                {
                    sector.BoxIndex = trRoom.Sectors[i].Box_Index;
                    sector.Material = SectorMaterial.Stone;
                }
                else
                {
                    sector.BoxIndex = (trRoom.Sectors[i].Box_Index & 0xFFF0) >> 4;
                    sector.Material = (SectorMaterial)((uint) trRoom.Sectors[i].Box_Index & 0x000F);
                }

                if (sector.BoxIndex == 0xFFFF) sector.BoxIndex = -1;

                sector.Flags = 0; // Clear sector flags

                sector.Floor = (int) -TR_METERING_STEP * trRoom.Sectors[i].Floor;
                sector.Ceiling = (int) -TR_METERING_STEP * trRoom.Sectors[i].Ceiling;
                sector.TrigIndex = trRoom.Sectors[i].FD_Index;

                // BUILDING CEILING HEIGHTMAP.

                // Penetration config is used later to build inbetween vertical collision polys.
                // If sector's penetration config is a wall, we simply build a vertical plane to
                // isolate this sector from top to bottom. Also, this allows to trick out wall
                // sectors inside another wall sectors to be ignored completely when building
                // collisional mesh.
                // Door penetration config means that we should either ignore sector collision
                // completely (classic door) or ignore one of the triangular sector parts (TR3+).

                if (sector.Ceiling == TR_METERING_WALLHEIGHT)
                {
                    sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Wall;
                }
                else if (trRoom.Sectors[i].RoomAbove != 0xFF)
                {
                    sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Ghost;
                }
                else
                {
                    sector.CeilingPenetrationConfig = TR_PENETRATION_CONFIG.Solid;
                }

                // Reset some sector parameters to avoid garbaged memory issues.

                sector.PortalToRoom = -1;
                sector.CeilingDiagonalType = TR_SECTOR_DIAGONAL_TYPE.None;
                sector.FloorDiagonalType = TR_SECTOR_DIAGONAL_TYPE.None;

                // Now, we define heightmap cells position and draft (flat) height.
                // Draft height is derived from sector's floor and ceiling values, which are
                // copied into heightmap cells Y coordinates. As result, we receive flat
                // heightmap cell, which will be operated later with floordata.

                sector.CeilingCorners[0][0] = sector.IndexX * TR_METERING_SECTORSIZE;
                sector.CeilingCorners[0][1] = sector.IndexY * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.CeilingCorners[0][2] = sector.Ceiling;

                sector.CeilingCorners[1][0] = sector.IndexX * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.CeilingCorners[1][1] = sector.IndexY * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.CeilingCorners[1][2] = sector.Ceiling;

                sector.CeilingCorners[2][0] = sector.IndexX * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.CeilingCorners[2][1] = sector.IndexY * TR_METERING_SECTORSIZE;
                sector.CeilingCorners[2][2] = sector.Ceiling;

                sector.CeilingCorners[3][0] = sector.IndexX * TR_METERING_SECTORSIZE;
                sector.CeilingCorners[3][1] = sector.IndexY * TR_METERING_SECTORSIZE;
                sector.CeilingCorners[3][2] = sector.Ceiling;


                // BUILDING FLOOR HEIGHTMAP.

                // Features same steps as for the ceiling.

                if (sector.Floor == TR_METERING_WALLHEIGHT)
                {
                    sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Wall;
                }
                else if (trRoom.Sectors[i].RoomBelow != 0xFF)
                {
                    sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Ghost;
                }
                else
                {
                    sector.FloorPenetrationConfig = TR_PENETRATION_CONFIG.Solid;
                }

                sector.FloorCorners[0][0] = sector.IndexX * TR_METERING_SECTORSIZE;
                sector.FloorCorners[0][1] = sector.IndexY * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.FloorCorners[0][2] = sector.Floor;

                sector.FloorCorners[1][0] = sector.IndexX * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.FloorCorners[1][1] = sector.IndexY * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.FloorCorners[1][2] = sector.Floor;

                sector.FloorCorners[2][0] = sector.IndexX * TR_METERING_SECTORSIZE + TR_METERING_SECTORSIZE;
                sector.FloorCorners[2][1] = sector.IndexY * TR_METERING_SECTORSIZE;
                sector.FloorCorners[2][2] = sector.Floor;

                sector.FloorCorners[3][0] = sector.IndexX * TR_METERING_SECTORSIZE;
                sector.FloorCorners[3][1] = sector.IndexY * TR_METERING_SECTORSIZE;
                sector.FloorCorners[3][2] = sector.Floor;
            }

#endregion

#region Lights

            room.Lights.Resize(trRoom.Lights.Length, () => new Light());

            for (var i = 0; i < trRoom.Lights.Length; i++)
            {
                var l = room.Lights[i];
                var tl = trRoom.Lights[i];

                l.LightType = tl.LightType;

                l.Position = tl.Position.ToVector3();

                if (l.LightType == LightType.Shadow)
                {
                    l.Colour[0] = -(tl.Color.R / 255.0f) * tl.Intensity;
                    l.Colour[1] = -(tl.Color.G / 255.0f) * tl.Intensity;
                    l.Colour[2] = -(tl.Color.B / 255.0f) * tl.Intensity;
                    l.Colour[3] = 1.0f;
                }
                else
                {
                    l.Colour[0] = tl.Color.R / 255.0f * tl.Intensity;
                    l.Colour[1] = tl.Color.G / 255.0f * tl.Intensity;
                    l.Colour[2] = tl.Color.B / 255.0f * tl.Intensity;
                    l.Colour[3] = 1.0f;
                }

                l.Inner = tl.R_Inner;
                l.Outer = tl.R_Outer;
                l.Length = tl.Length;
                l.Cutoff = tl.Cutoff;

                l.Falloff = 0.001f / l.Outer;
            }

#endregion

#region Portals

            room.Portals.Resize(trRoom.Portals.Length, () => new Portal());
            for (var i = 0; i < room.Portals.Count; i++)
            {
                var trp = trRoom.Portals[i];
                var p = room.Portals[i];
                var rDest = world.Rooms[trp.AdjoiningRoom];
                p.Vertices.Resize(4); // in original TR all portals are axis aligned rectangles
                p.DestRoom = rDest;
                p.CurrentRoom = room;
                p.Vertices = trp.Vertices.Reverse().Select(x => x.ToVector3() + room.Transform.Origin).ToList();
                p.Centre = p.Vertices.Sum() / p.Vertices.Count;
                p.GenNormal();

                // Portal position fix
                // X_MIN
                if(p.Normal.Normal.X > 0.999f && (int)p.Centre.X % 2 != 0)
                {
                    p.Move(Vector3.UnitX);
                }

                // Y_MIN
                if (p.Normal.Normal.Y > 0.999f && (int)p.Centre.Y % 2 != 0)
                {
                    p.Move(Vector3.UnitY);
                }

                // Z_MAX
                if (p.Normal.Normal.Z < -0.999f && (int)p.Centre.Z % 2 != 0)
                {
                    p.Move(-Vector3.UnitZ);
                }
            }

#endregion

#region Room borders

            room.BBMin.Z = trRoom.Y_Bottom;
            room.BBMax.Z = trRoom.Y_Top;

            room.BBMin.X = room.Transform.Origin.X + TR_METERING_SECTORSIZE;
            room.BBMin.Y = room.Transform.Origin.Y + TR_METERING_SECTORSIZE;
            room.BBMax.X = room.Transform.Origin.X + TR_METERING_SECTORSIZE * room.SectorsX - TR_METERING_SECTORSIZE;
            room.BBMax.Y = room.Transform.Origin.Y + TR_METERING_SECTORSIZE * room.SectorsY - TR_METERING_SECTORSIZE;

#endregion

#region Alternate room

            // alternate room pointer calculation if one exists.
            room.AlternateRoom = null;
            room.BaseRoom = null;

            if(trRoom.AlternateRoom.IsBetween(0, tr.Rooms.Length, IB.aIbE))
            {
                room.AlternateRoom = world.Rooms[trRoom.AlternateRoom];
            }

#endregion
        }
Example #23
0
        public static void tr_setupRoomVertices(World world, Level tr, Loader.Room tr_room, BaseMesh mesh,
            int numCorners, ushort[] vertices, ushort masked_texture, Polygon p)
        {
            p.Vertices.Resize(numCorners, () => new Vertex());

            for (var i = 0; i < numCorners; i++)
            {
                p.Vertices[i].Position = tr_room.Vertices[vertices[i]].Vertex.ToVector3();
            }
            p.FindNormal();

            for (var i = 0; i < numCorners; i++)
            {
                mesh.Vertices[vertices[i]].Normal += p.Plane.Normal;
                p.Vertices[i].Normal = p.Plane.Normal;
                p.Vertices[i].Color = TR_color_to_arr(tr_room.Vertices[vertices[i]].Color);
            }

            var tex = tr.ObjectTextures[masked_texture];
            SetAnimTexture(p, masked_texture, world);
            p.BlendMode = tex.TransparencyFlags;

            world.TextureAtlas.GetCoordinates(masked_texture, false, p);
        }
Example #24
0
        public static void TR_GenRoomProperties(World world, Level tr)
        {
            for (var i = 0; i < world.Rooms.Count; i++)
            {
                var r = world.Rooms[i];
                if(r.AlternateRoom != null)
                {
                    r.AlternateRoom.BaseRoom = r; // Refill base room pointer.
                }

                // Fill heightmap and translate floordata.
                foreach (var sector in r.Sectors)
                {
                    TR_Sector_TranslateFloorData(sector, tr);
                    Res_Sector_FixHeights(sector);
                }

                // Generate links to the near rooms.
                r.BuildNearRoomsList();
                // Generate links to the overlapped rooms.
                r.BuildOverlappedRoomsList();

                // Basic sector calculations.
                TR_Sector_Calculate(world, tr, i);
            }
        }
Example #25
0
 public static Level FromFile(string fileName)
 {
     var br = new BinaryReader(new FileStream(fileName, FileMode.Open));
     var ver = ParseVersion(br, Path.GetExtension(fileName));
     if (ver == TRGame.Unknown)
     {
         throw new ArgumentException("Couldn't detect level version", nameof(ver));
     }
     var ret = new Level(br, ver, Path.Combine(Path.GetDirectoryName(Path.GetFullPath(fileName)), "MAIN.SFX"));
     return ret;
 }
Example #26
0
        public void GenMesh(World world, uint roomID, Level tr)
        {
            var texMask = world.EngineVersion == Loader.Engine.TR4 ? TextureIndexMaskTr4 : TextureIndexMask;

            var trRoom = tr.Rooms[roomID];

            if(trRoom.Triangles.Length == 0 && trRoom.Rectangles.Length == 0)
            {
                Mesh = null;
                return;
            }

            Mesh = new BaseMesh
            {
                ID = roomID,
                TexturePageCount = world.TextureAtlas.NumAtlasPages + 1,
                UsesVertexColors = true // This is implicitly true on room meshes
            };

            Mesh.Vertices.Resize(trRoom.Vertices.Length, () => new Vertex());
            for (var i = 0; i < Mesh.Vertices.Count; i++)
            {
                Mesh.Vertices[i].Position = trRoom.Vertices[i].Vertex.ToVector3();
                Mesh.Vertices[i].Normal = Vector3.Zero; // paranoid
            }

            Mesh.FindBB();

            Mesh.Polygons.Resize(trRoom.Triangles.Length + trRoom.Rectangles.Length, () => new Polygon());
            var p = 0;

            // triangles
            for (var i = 0; i < trRoom.Triangles.Length; i++, p++)
            {
                tr_setupRoomVertices(world, tr, trRoom, Mesh, 3, trRoom.Triangles[i].Vertices,
                    (ushort)(trRoom.Triangles[i].Texture & texMask), Mesh.Polygons[p]);
                Mesh.Polygons[p].DoubleSide = (trRoom.Triangles[i].Texture & 0x8000) != 0;
            }

            // rectangles
            for (var i = 0; i < trRoom.Rectangles.Length; i++, p++)
            {
                tr_setupRoomVertices(world, tr, trRoom, Mesh, 4, trRoom.Rectangles[i].Vertices,
                    (ushort)(trRoom.Rectangles[i].Texture & texMask), Mesh.Polygons[p]);
                Mesh.Polygons[p].DoubleSide = (trRoom.Rectangles[i].Texture & 0x8000) != 0;
            }

            // let us normalise normals %)
            foreach (var v in Mesh.Vertices)
            {
                v.Normal = v.Normal.SafeNormalize();
            }

            p = 0;
            // triangles
            for (var i = 0; i < trRoom.Triangles.Length; i++, p++)
            {
                tr_copyNormals(Mesh.Polygons[p], Mesh, trRoom.Triangles[i].Vertices);
            }

            // rectangles
            for (var i = 0; i < trRoom.Rectangles.Length; i++, p++)
            {
                tr_copyNormals(Mesh.Polygons[p], Mesh, trRoom.Rectangles[i].Vertices);
            }

            Mesh.Vertices.Clear();
            Mesh.GenFaces();
            Mesh.PolySortInMesh();
        }