public static Wad2 ImportFromFile(string fileName, bool withSounds, IDialogHandler progressReporter, bool allowTRNGDecryption = false) { if (fileName.EndsWith(".wad2", StringComparison.InvariantCultureIgnoreCase)) { return(Wad2Loader.LoadFromFile(fileName, withSounds)); } else if (fileName.EndsWith(".wad", StringComparison.InvariantCultureIgnoreCase) || fileName.EndsWith(".was", StringComparison.InvariantCultureIgnoreCase) || fileName.EndsWith(".sam", StringComparison.InvariantCultureIgnoreCase) || fileName.EndsWith(".sfx", StringComparison.InvariantCultureIgnoreCase) || fileName.EndsWith(".swd", StringComparison.InvariantCultureIgnoreCase)) { if (!fileName.EndsWith(".wad", StringComparison.InvariantCultureIgnoreCase)) { fileName = Path.ChangeExtension(fileName, "wad"); } var oldWad = new Tr4Wad.Tr4Wad(); oldWad.LoadWad(fileName); return(Tr4WadOperations.ConvertTr4Wad(oldWad, progressReporter)); } else { var originalLevel = new TrLevel(); originalLevel.LoadLevel(fileName, allowTRNGDecryption); return(TrLevelOperations.ConvertTrLevel(originalLevel)); } }
private static TextureArea ConvertColoredFaceToTexture(Wad2 wad, TrLevel oldLevel, ushort paletteIndex, bool isTriangle) { ColorC color; var sixteenBitIndex = paletteIndex >> 8; if (oldLevel.Palette16.Count > 0 && oldLevel.Palette16.Count == oldLevel.Palette8.Count && sixteenBitIndex < oldLevel.Palette16.Count) { var trColor = oldLevel.Palette16[sixteenBitIndex]; color = new ColorC(trColor.Red, trColor.Green, trColor.Blue, 255); } else { var trColor = oldLevel.Palette8[paletteIndex & 0xFF]; color = new ColorC((byte)(trColor.Red * 4), (byte)(trColor.Green * 4), (byte)(trColor.Blue * 4), 255); } const int dummyTextureSize = 4; var image = ImageC.CreateNew(dummyTextureSize, dummyTextureSize); image.Fill(color); TextureArea textureArea = new TextureArea(); textureArea.Texture = new WadTexture(image); textureArea.TexCoord0 = new Vector2(0, 0); textureArea.TexCoord1 = new Vector2(dummyTextureSize, 0); textureArea.TexCoord2 = new Vector2(dummyTextureSize, dummyTextureSize); textureArea.TexCoord3 = new Vector2(0, dummyTextureSize); textureArea.BlendMode = BlendMode.Normal; textureArea.DoubleSided = false; return(textureArea); }
public static WadStatic ConvertTrLevelStaticMeshToWadStatic(Wad2 wad, TrLevel oldLevel, int staticIndex, TextureArea[] objectTextures) { tr_staticmesh oldStatic = oldLevel.StaticMeshes[staticIndex]; var newStatic = new WadStatic(new WadStaticId(oldStatic.ObjectID)); // First setup collisional and visibility bounding boxes newStatic.CollisionBox = new BoundingBox(new Vector3(oldStatic.CollisionBox.X1, -oldStatic.CollisionBox.Y1, oldStatic.CollisionBox.Z1), new Vector3(oldStatic.CollisionBox.X2, -oldStatic.CollisionBox.Y2, oldStatic.CollisionBox.Z2)); newStatic.VisibilityBox = new BoundingBox(new Vector3(oldStatic.VisibilityBox.X1, -oldStatic.VisibilityBox.Y1, oldStatic.VisibilityBox.Z1), new Vector3(oldStatic.VisibilityBox.X2, -oldStatic.VisibilityBox.Y2, oldStatic.VisibilityBox.Z2)); // Then import the mesh. If it was already added, the mesh will not be added to the dictionary. newStatic.Mesh = ConvertTrLevelMeshToWadMesh(wad, oldLevel, oldLevel.GetMeshFromPointer(oldStatic.Mesh), objectTextures); return(newStatic); }
public static Wad2 ConvertTrLevel(TrLevel oldLevel) { var wad = new Wad2() { GameVersion = oldLevel.Version, SoundSystem = SoundSystem.Xml }; logger.Info("Converting TR level to WAD2"); // Convert textures TextureArea[] objectTextures = ConvertTrLevelTexturesToWadTexture(oldLevel); logger.Info("Texture conversion complete."); // Then convert moveables and static meshes // Meshes will be converted inside each model for (int i = 0; i < oldLevel.Moveables.Count; i++) { WadMoveable moveable = ConvertTrLevelMoveableToWadMoveable(wad, oldLevel, i, objectTextures); wad.Moveables.Add(moveable.Id, moveable); } logger.Info("Moveable conversion complete."); for (int i = 0; i < oldLevel.StaticMeshes.Count; i++) { WadStatic @static = ConvertTrLevelStaticMeshToWadStatic(wad, oldLevel, i, objectTextures); wad.Statics.Add(@static.Id, @static); } logger.Info("Static mesh conversion complete."); // Convert sprites ConvertTrLevelSprites(wad, oldLevel); logger.Info("Sprite conversion complete."); return(wad); }
private static void ConvertTrLevelSprites(Wad2 wad, TrLevel oldLevel) { ImageC tiles = ImageC.FromByteArray(oldLevel.TextureMap32, 256, oldLevel.TextureMap32.Length / 1024); foreach (var oldSequence in oldLevel.SpriteSequences) { int lengthOfSequence = -oldSequence.NegativeLength; int startIndex = oldSequence.Offset; var newSequence = new WadSpriteSequence(new WadSpriteSequenceId((uint)oldSequence.ObjectID)); for (int i = startIndex; i < startIndex + lengthOfSequence; i++) { tr_sprite_texture oldSpriteTexture = oldLevel.SpriteTextures[i]; int spriteX, spriteY, spriteWidth, spriteHeight; int x1 = oldSpriteTexture.LeftSide; int x2 = oldSpriteTexture.RightSide; int y1 = oldSpriteTexture.TopSide; int y2 = oldSpriteTexture.BottomSide; if (oldLevel.Version == TRVersion.Game.TR1 || oldLevel.Version == TRVersion.Game.TR2 || oldLevel.Version == TRVersion.Game.TR3) { spriteX = oldSpriteTexture.X; spriteY = oldSpriteTexture.Y; spriteWidth = ((oldSpriteTexture.Width - 255) / 256 + 1); spriteHeight = ((oldSpriteTexture.Height - 255) / 256 + 1); } else { spriteX = oldSpriteTexture.LeftSide; spriteY = oldSpriteTexture.TopSide; spriteWidth = ((oldSpriteTexture.Width / 256) + 1); spriteHeight = ((oldSpriteTexture.Height / 256) + 1); } // Add current sprite to the sequence var spriteImage = ImageC.CreateNew(spriteWidth, spriteHeight); spriteImage.CopyFrom(0, 0, tiles, spriteX, spriteY + oldSpriteTexture.Tile * 256, spriteWidth, spriteHeight); RectangleInt2 rect = new RectangleInt2(x1, y1, x2, y2); newSequence.Sprites.Add(new WadSprite { Texture = new WadTexture(spriteImage), Alignment = rect }); } wad.SpriteSequences.Add(newSequence.Id, newSequence); } }
private static TextureArea[] ConvertTrLevelTexturesToWadTexture(TrLevel oldLevel) { var objectTextures = new TextureArea[oldLevel.ObjectTextures.Count]; ImageC tiles = ImageC.FromByteArray(oldLevel.TextureMap32, 256, oldLevel.TextureMap32.Length / 1024); tiles.ReplaceColor(new ColorC(255, 0, 255, 255), new ColorC(0, 0, 0, 0)); // for (int i = 0; i < oldLevel.ObjectTextures.Count; ++i) Parallel.For(0, oldLevel.ObjectTextures.Count, i => { var oldTexture = oldLevel.ObjectTextures[i]; int textureTileIndex = oldTexture.TileAndFlags & 0x7fff; bool isTriangle = (oldTexture.TileAndFlags & 0x8000) != 0; // Exists only in TR4+ if (oldLevel.Version == TRVersion.Game.TR1 || oldLevel.Version == TRVersion.Game.TR2 || oldLevel.Version == TRVersion.Game.TR3) { isTriangle = (oldTexture.Vertices[3].X == 0) && (oldTexture.Vertices[3].Y == 0); } // Calculate UV coordinates... Vector2[] coords = new Vector2[isTriangle ? 3 : 4]; for (int j = 0; j < coords.Length; ++j) { coords[j] = new Vector2(oldTexture.Vertices[j].X, oldTexture.Vertices[j].Y) * (1.0f / 256.0f); } // Find the corners of the texture Vector2 min = coords[0], max = coords[0]; for (int j = 1; j < coords.Length; ++j) { min = Vector2.Min(min, coords[j]); max = Vector2.Max(max, coords[j]); } VectorInt2 start = VectorInt2.FromFloor(min); VectorInt2 end = VectorInt2.FromCeiling(max); start = VectorInt2.Min(VectorInt2.Max(start, new VectorInt2()), new VectorInt2(256, 256)); end = VectorInt2.Min(VectorInt2.Max(end, new VectorInt2()), new VectorInt2(256, 256)); // Create image ImageC image = ImageC.CreateNew(end.X - start.X, end.Y - start.Y); image.CopyFrom(0, 0, tiles, start.X, start.Y + textureTileIndex * 256, end.X - start.X, end.Y - start.Y); // Replace black with transparent color //image.ReplaceColor(new ColorC(0, 0, 0, 255), new ColorC(0, 0, 0, 0)); WadTexture texture = new WadTexture(image); // Create texture area TextureArea textureArea = new TextureArea(); textureArea.DoubleSided = false; textureArea.BlendMode = (BlendMode)(oldTexture.Attributes); textureArea.TexCoord0 = coords[0] - start; textureArea.TexCoord1 = coords[1] - start; textureArea.TexCoord2 = coords[2] - start; textureArea.TexCoord3 = isTriangle ? textureArea.TexCoord2 : (coords[3] - start); textureArea.Texture = texture; objectTextures[i] = textureArea; }); return(objectTextures); }
public static WadMoveable ConvertTrLevelMoveableToWadMoveable(Wad2 wad, TrLevel oldLevel, int moveableIndex, TextureArea[] objectTextures) { Console.WriteLine("Converting Moveable " + moveableIndex); var oldMoveable = oldLevel.Moveables[moveableIndex]; WadMoveable newMoveable = new WadMoveable(new WadMoveableId(oldMoveable.ObjectID)); // First a list of meshes for this moveable is built var oldMeshes = new List <tr_mesh>(); for (int j = 0; j < oldMoveable.NumMeshes; j++) { oldMeshes.Add(oldLevel.Meshes[(int)oldLevel.RealPointers[(int)(oldMoveable.StartingMesh + j)]]); } // Convert the WadMesh var newMeshes = new List <WadMesh>(); foreach (var oldMesh in oldMeshes) { newMeshes.Add(ConvertTrLevelMeshToWadMesh(wad, oldLevel, oldMesh, objectTextures)); } // Build the skeleton var root = new WadBone(); root.Name = "bone_0_root"; root.Parent = null; root.Translation = Vector3.Zero; root.Mesh = newMeshes[0]; newMoveable.Bones.Add(root); for (int j = 0; j < oldMoveable.NumMeshes - 1; j++) { var bone = new WadBone(); bone.Name = "bone_" + (j + 1).ToString(); bone.Parent = null; bone.Translation = Vector3.Zero; bone.Mesh = newMeshes[j + 1]; newMoveable.Bones.Add(bone); } for (int mi = 0; mi < (oldMeshes.Count - 1); mi++) { int j = mi + 1; var opcode = (WadLinkOpcode)oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4)]; int linkX = oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 1]; int linkY = -oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 2]; int linkZ = oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 3]; newMoveable.Bones[j].OpCode = opcode; newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ); } // Convert animations int numAnimations = 0; int nextMoveable = oldLevel.GetNextMoveableWithAnimations(moveableIndex); if (nextMoveable == -1) { numAnimations = oldLevel.Animations.Count - oldMoveable.Animation; } else { numAnimations = oldLevel.Moveables[nextMoveable].Animation - oldMoveable.Animation; } var frameBases = new Dictionary <WadAnimation, ushort>(); for (int j = 0; j < numAnimations; j++) { if (oldMoveable.Animation == -1) { break; } WadAnimation newAnimation = new WadAnimation(); var oldAnimation = oldLevel.Animations[j + oldMoveable.Animation]; newAnimation.FrameRate = oldAnimation.FrameRate; newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.Animation); newAnimation.NextFrame = oldAnimation.NextFrame; newAnimation.StateId = oldAnimation.StateID; newAnimation.EndFrame = (ushort)(oldAnimation.FrameEnd - oldAnimation.FrameStart); newAnimation.Name = TrCatalog.GetAnimationName(oldLevel.Version, oldMoveable.ObjectID, (uint)j); for (int k = 0; k < oldAnimation.NumStateChanges; k++) { WadStateChange sc = new WadStateChange(); var wadSc = oldLevel.StateChanges[(int)oldAnimation.StateChangeOffset + k]; sc.StateId = wadSc.StateID; for (int n = 0; n < wadSc.NumAnimDispatches; n++) { WadAnimDispatch ad = new WadAnimDispatch(); var wadAd = oldLevel.AnimDispatches[(int)wadSc.AnimDispatch + n]; ad.InFrame = (ushort)(wadAd.Low - oldAnimation.FrameStart); ad.OutFrame = (ushort)(wadAd.High - oldAnimation.FrameStart); ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.Animation) % numAnimations); ad.NextFrame = (ushort)wadAd.NextFrame; sc.Dispatches.Add(ad); } newAnimation.StateChanges.Add(sc); } if (oldAnimation.NumAnimCommands < oldLevel.AnimCommands.Count) { int lastCommand = oldAnimation.AnimCommand; for (int k = 0; k < oldAnimation.NumAnimCommands; k++) { short commandType = oldLevel.AnimCommands[lastCommand + 0]; WadAnimCommand command = new WadAnimCommand { Type = (WadAnimCommandType)commandType }; switch (commandType) { case 1: command.Parameter1 = (short)oldLevel.AnimCommands[lastCommand + 1]; command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; command.Parameter3 = (short)oldLevel.AnimCommands[lastCommand + 3]; lastCommand += 4; break; case 2: command.Parameter1 = (short)oldLevel.AnimCommands[lastCommand + 1]; command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; lastCommand += 3; break; case 3: lastCommand += 1; break; case 4: lastCommand += 1; break; case 5: command.Parameter1 = (short)(oldLevel.AnimCommands[lastCommand + 1] - oldAnimation.FrameStart); command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; lastCommand += 3; break; case 6: command.Parameter1 = (short)(oldLevel.AnimCommands[lastCommand + 1] - oldAnimation.FrameStart); command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; lastCommand += 3; break; default: // Ignore invalid anim commands (see for example karnak.wad) logger.Warn("Unknown anim command " + commandType); goto ExitForLoop; } newAnimation.AnimCommands.Add(command); } ExitForLoop: ; } int frames = (int)oldAnimation.FrameOffset / 2; uint numFrames; if (j + oldMoveable.Animation == oldLevel.Animations.Count - 1) { if (oldAnimation.FrameSize == 0) { numFrames = oldLevel.Version == TRVersion.Game.TR1 ? (uint)((newAnimation.EndFrame + 1) / newAnimation.FrameRate) : 0; } else { numFrames = ((uint)(2 * oldLevel.Frames.Count) - oldAnimation.FrameOffset) / (uint)(2 * oldAnimation.FrameSize); } } else { if (oldAnimation.FrameSize == 0) { numFrames = oldLevel.Version == TRVersion.Game.TR1 ? (uint)((newAnimation.EndFrame + 1) / newAnimation.FrameRate) : 0; } else { numFrames = (oldLevel.Animations[oldMoveable.Animation + j + 1].FrameOffset - oldAnimation.FrameOffset) / (uint)(2 * oldAnimation.FrameSize); } } for (int f = 0; f < numFrames; f++) { WadKeyFrame frame = new WadKeyFrame(); int startOfFrame = frames; frame.BoundingBox = new BoundingBox(new Vector3(oldLevel.Frames[frames], -oldLevel.Frames[frames + 2], oldLevel.Frames[frames + 4]), new Vector3(oldLevel.Frames[frames + 1], -oldLevel.Frames[frames + 3], oldLevel.Frames[frames + 5])); frames += 6; frame.Offset = new Vector3(oldLevel.Frames[frames], (short)(-oldLevel.Frames[frames + 1]), oldLevel.Frames[frames + 2]); frames += 3; // TR1 has also the number of angles to follow if (oldLevel.Version == TRVersion.Game.TR1) { frames++; } for (int n = 0; n < oldMoveable.NumMeshes; n++) { frame.Angles.Add( WadKeyFrameRotation.FromTrAngle(ref frames, oldLevel.Frames, oldLevel.Version == TRVersion.Game.TR1, oldLevel.Version == TRVersion.Game.TR4 || oldLevel.Version == TRVersion.Game.TR5)); } if ((frames - startOfFrame) < oldAnimation.FrameSize) { frames += ((int)oldAnimation.FrameSize - (frames - startOfFrame)); } newAnimation.KeyFrames.Add(frame); } frameBases.Add(newAnimation, oldAnimation.FrameStart); // New velocities float acceleration = oldAnimation.Accel / 65536.0f; newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f; float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f; newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f; if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0) { newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; } newMoveable.Animations.Add(newAnimation); } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; if (animation.KeyFrames.Count == 0) { animation.EndFrame = 0; } // HACK: this fixes some invalid NextAnimations values animation.NextAnimation %= (ushort)newMoveable.Animations.Count; newMoveable.Animations[i] = animation; } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; // HACK: this fixes some invalid NextFrame values if (frameBases[newMoveable.Animations[animation.NextAnimation]] != 0) { animation.NextFrame %= frameBases[newMoveable.Animations[animation.NextAnimation]]; } foreach (var stateChange in animation.StateChanges) { for (int J = 0; J < stateChange.Dispatches.Count; ++J) { WadAnimDispatch animDispatch = stateChange.Dispatches[J]; if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]] != 0) { ushort newFrame = (ushort)(animDispatch.NextFrame % frameBases[newMoveable.Animations[animDispatch.NextAnimation]]); // In some cases dispatches have invalid NextFrame. // From tests it seems that's ok to delete the dispatch or put the NextFrame equal to zero. if (newFrame > newMoveable.Animations[animDispatch.NextAnimation].EndFrame) { newFrame = 0; } animDispatch.NextFrame = newFrame; } stateChange.Dispatches[J] = animDispatch; } } newMoveable.Animations[i] = animation; } return(newMoveable); }
private static WadMesh ConvertTrLevelMeshToWadMesh(Wad2 wad, TrLevel oldLevel, tr_mesh oldMesh, TextureArea[] objectTextures) { WadMesh mesh = new WadMesh(); mesh.Name = "Mesh_" + oldLevel.Meshes.IndexOf(oldMesh); // Add positions foreach (var oldVertex in oldMesh.Vertices) { mesh.VerticesPositions.Add(new Vector3(oldVertex.X, -oldVertex.Y, oldVertex.Z)); } // Create the bounding areas mesh.BoundingSphere = new BoundingSphere(new Vector3(oldMesh.Center.X, oldMesh.Center.Y, oldMesh.Center.Z), oldMesh.Radius); mesh.BoundingBox = mesh.CalculateBoundingBox(); // Add normals foreach (var oldNormal in oldMesh.Normals) { mesh.VerticesNormals.Add(new Vector3(oldNormal.X, -oldNormal.Y, oldNormal.Z)); } // Add shades foreach (var oldShade in oldMesh.Lights) { mesh.VerticesShades.Add(oldShade); } // Add polygons foreach (var oldPoly in oldMesh.TexturedQuads) { TextureArea textureArea = objectTextures[oldPoly.Texture & 0x7fff]; textureArea.DoubleSided = (oldPoly.Texture & 0x8000) != 0; WadPolygon poly; poly.Shape = WadPolygonShape.Quad; poly.Index0 = oldPoly.Index0; poly.Index1 = oldPoly.Index1; poly.Index2 = oldPoly.Index2; poly.Index3 = oldPoly.Index3; poly.ShineStrength = (byte)((oldPoly.LightingEffect & 0x7c) >> 2); poly.Texture = textureArea; mesh.Polys.Add(poly); } foreach (var oldPoly in oldMesh.TexturedTriangles) { TextureArea textureArea = objectTextures[oldPoly.Texture & 0x7fff]; textureArea.DoubleSided = (oldPoly.Texture & 0x8000) != 0; WadPolygon poly = new WadPolygon(); poly.Shape = WadPolygonShape.Triangle; poly.Index0 = oldPoly.Index0; poly.Index1 = oldPoly.Index1; poly.Index2 = oldPoly.Index2; poly.Index3 = 0; poly.ShineStrength = (byte)((oldPoly.LightingEffect & 0x7c) >> 2); poly.Texture = textureArea; mesh.Polys.Add(poly); } foreach (var oldPoly in oldMesh.ColoredRectangles) { WadPolygon poly = new WadPolygon(); poly.Shape = WadPolygonShape.Quad; poly.Index0 = oldPoly.Index0; poly.Index1 = oldPoly.Index1; poly.Index2 = oldPoly.Index2; poly.Index3 = oldPoly.Index3; poly.Texture = ConvertColoredFaceToTexture(wad, oldLevel, oldPoly.Texture, false); poly.ShineStrength = 0; mesh.Polys.Add(poly); } foreach (var oldPoly in oldMesh.ColoredTriangles) { WadPolygon poly; poly.Shape = WadPolygonShape.Triangle; poly.Index0 = oldPoly.Index0; poly.Index1 = oldPoly.Index1; poly.Index2 = oldPoly.Index2; poly.Index3 = 0; poly.Texture = ConvertColoredFaceToTexture(wad, oldLevel, oldPoly.Texture, true); poly.ShineStrength = 0; mesh.Polys.Add(poly); } // Usually only for static meshes if (mesh.VerticesNormals.Count == 0) { mesh.CalculateNormals(); } return(mesh); }