public static Block CreateFromStream(Stream stream) { var reader = new BlockStreamReader(stream); if (reader.ReadLine() != "DMB_BLOCK_FILE") { throw new InvalidDataException("DMB block header is missing or incorrect."); } var segments = new Dictionary <uint, Segment>(); // We read segment children before we have all the segments, so we need to connect them in a subsequent pass var segmentConnections = new Dictionary <uint, int[]>(); while (!reader.EndOfStream) { uint segmentId = BlockCommon.ReadValue <uint>(reader, "segment"); if (segments.ContainsKey(segmentId)) { throw new InvalidDataException($"Encountered duplicate definition of segment {segmentId} at line {reader.LastLineNumber}."); } var segment = new Segment(Segment.MaxSides, Segment.MaxVertices); segments[segmentId] = segment; // Read sides for (uint sideNum = 0; sideNum < segment.Sides.Length; sideNum++) { int fileSideNum = BlockCommon.ReadValue <int>(reader, "side"); if (fileSideNum != sideNum) { throw new InvalidDataException($"Unexpected side number {fileSideNum} at line {reader.LastLineNumber}."); } var side = new Side(segment, sideNum); segment.Sides[sideNum] = side; side.BaseTextureIndex = BlockCommon.ReadPrimaryTextureIndex(reader, false); (var overlayIndex, var overlayRotation) = BlockCommon.ReadSecondaryTexture(reader, false); side.OverlayTextureIndex = overlayIndex; side.OverlayRotation = overlayRotation; for (int i = 0; i < side.Uvls.Length; i++) { side.Uvls[i] = BlockCommon.ReadUvl(reader); } } segmentConnections[segmentId] = BlockCommon.ReadSegmentChildren(reader); // Read vertices for (uint vertexNum = 0; vertexNum < Segment.MaxVertices; vertexNum++) { (var vertexLocation, var fileVertexNum) = BlockCommon.ReadVertex(reader, false); if (fileVertexNum != vertexNum) { throw new InvalidDataException($"Unexpected vertex number {fileVertexNum} at line {reader.LastLineNumber}."); } segment.Vertices[vertexNum] = new LevelVertex(vertexLocation); } segment.Light = new Fix(BlockCommon.ReadValue <int>(reader, "static_light")); segment.Function = SegFunction.None; segment.MatCenter = null; } // Now set up segment connections foreach (var connection in segmentConnections) { for (int i = 0; i < connection.Value.Length; i++) { var connectedSegmentId = connection.Value[i]; if (connectedSegmentId < 0) { continue; } segments[connection.Key].Sides[i].ConnectedSegment = segments[(uint)connectedSegmentId]; } } Block block = new Block(); foreach (Segment segment in segments.Values) { block.Segments.Add(segment); } BlockCommon.SetupVertexConnections(block); block.RemoveDuplicateVertices(); return(block); }
public static ExtendedBlock CreateFromStream(Stream stream) { var reader = new BlockStreamReader(stream); if (reader.ReadLine() != "DMB_EXT_BLOCK_FILE") { throw new InvalidDataException("DLE block header is missing or incorrect."); } ExtendedBlock block = new ExtendedBlock(); var segments = new Dictionary <uint, Segment>(); // We read segment children before we have all the segments, so we need to connect them in a subsequent pass var segmentConnections = new Dictionary <uint, int[]>(); var vertices = new Dictionary <uint, LevelVertex>(); // We also read triggers before we have all the segments var triggerTargetConnections = new Dictionary <int, List <(int segmentNum, int sideNum)> >(); while (!reader.EndOfStream) { uint segmentId = BlockCommon.ReadValue <uint>(reader, "segment"); if (segments.ContainsKey(segmentId)) { throw new InvalidDataException($"Encountered duplicate definition of segment {segmentId} at line {reader.LastLineNumber}."); } var segment = new Segment(Segment.MaxSides, Segment.MaxVertices); segments[segmentId] = segment; // Read sides for (uint sideNum = 0; sideNum < segment.Sides.Length; sideNum++) { int fileSideNum = BlockCommon.ReadValue <int>(reader, "side"); // Negative side numbers in .blx format indicate animated lights - weird, but that's how it is bool hasVariableLight = fileSideNum < 0; if (hasVariableLight) { fileSideNum = -fileSideNum; } if (fileSideNum != sideNum) { throw new InvalidDataException($"Unexpected side number {fileSideNum} at line {reader.LastLineNumber}."); } var side = new Side(segment, sideNum); segment.Sides[sideNum] = side; // Textures side.BaseTextureIndex = BlockCommon.ReadPrimaryTextureIndex(reader, true); (var overlayIndex, var overlayRotation) = BlockCommon.ReadSecondaryTexture(reader, true); side.OverlayTextureIndex = overlayIndex; side.OverlayRotation = overlayRotation; for (int i = 0; i < side.Uvls.Length; i++) { side.Uvls[i] = BlockCommon.ReadUvl(reader); } // Vertex mapping uint[] sideVertexIds = BlockCommon.ReadExtendedSideVertexIds(reader); foreach (uint sideVertexId in sideVertexIds) { // BLX_SIDE_VERTEX_ID_NONE (0xFF) = no vertex // D2X-XL uses these for non-cuboid segment support. We don't have that yet - more design work needed if (sideVertexId == BLX_SIDE_VERTEX_ID_NONE) { throw new NotSupportedException($"Found non-quadrilateral side {sideNum} at line {reader.LastLineNumber}, " + "which is currently unsupported."); } // Except for non-cuboid segments, side vertex ids appear to match Segment.SideVerts anyway, so we can ignore them } // This is what DLE does here - for future reference /*for (int j = 0; j < 4; j++) * pSide->m_vertexIdIndex[j] = ubyte(sideVertexIds[j]); * pSide->DetectShape();*/ // Animated lights if (hasVariableLight) { (uint mask, int timer, int delay) = BlockCommon.ReadExtendedVariableLight(reader); var light = new AnimatedLight(side); light.Mask = mask; light.TimeToNextTick = new Fix(timer); light.TickLength = new Fix(delay); block.AnimatedLights.Add(light); side.AnimatedLight = light; } // Walls and triggers uint wallId = BlockCommon.ReadValue <uint>(reader, "wall"); if (wallId != BLX_WALL_ID_NONE) { var wall = new Wall(side); side.Wall = wall; block.Walls.Add(wall); // .blx format includes segment/side numbers for walls - we don't need them // because we derive from context BlockCommon.ReadValue <int>(reader, "segment"); BlockCommon.ReadValue <int>(reader, "side"); wall.HitPoints = BlockCommon.ReadValue <int>(reader, "hps"); wall.Type = (WallType)BlockCommon.ReadValue <int>(reader, "type"); wall.Flags = (WallFlags)BlockCommon.ReadValue <byte>(reader, "flags"); wall.State = (WallState)BlockCommon.ReadValue <byte>(reader, "state"); wall.DoorClipNumber = (byte)BlockCommon.ReadValue <sbyte>(reader, "clip"); wall.Keys = (WallKeyFlags)BlockCommon.ReadValue <byte>(reader, "keys"); wall.CloakOpacity = BlockCommon.ReadValue <byte>(reader, "cloak"); var triggerNum = BlockCommon.ReadValue <byte>(reader, "trigger"); // 255 (0xFF) means no trigger on this wall if (triggerNum != 0xFF) { var trigger = new BlxTrigger(); wall.Trigger = trigger; block.Triggers.Add(trigger); trigger.Type = (TriggerType)BlockCommon.ReadValue <byte>(reader, "type"); trigger.Flags = BlockCommon.ReadValue <ushort>(reader, "flags"); trigger.Value = BlockCommon.ReadValue <int>(reader, "value"); trigger.Time = BlockCommon.ReadValue <int>(reader, "timer"); // Trigger targets var triggerTargets = new List <(int segmentNum, int sideNum)>(); var targetCount = BlockCommon.ReadValue <short>(reader, "count"); for (int targetNum = 0; targetNum < targetCount; targetNum++) { var targetSegmentNum = BlockCommon.ReadValue <int>(reader, "segment"); var targetSideNum = BlockCommon.ReadValue <int>(reader, "side"); triggerTargets.Add((targetSegmentNum, targetSideNum)); } if (triggerTargets.Count > 0) { triggerTargetConnections[block.Triggers.Count - 1] = triggerTargets; } } } } segmentConnections[segmentId] = BlockCommon.ReadSegmentChildren(reader); // Read vertices /*pSegment->m_nShape = 0;*/ // DLE non-cuboid - for reference for (uint vertexNum = 0; vertexNum < Segment.MaxVertices; vertexNum++) { (var vertexLocation, var fileVertexNum) = BlockCommon.ReadVertex(reader, true); // Non-cuboid segment support /*pSegment->m_info.vertexIds[i] = fileVertexNum;*/ if (fileVertexNum > BLX_MAX_VERTEX_NUM) { throw new NotSupportedException($"Found non-cuboid segment {segmentId} at line {reader.LastLineNumber}, " + "which is currently unsupported."); /*pSegment->m_nShape++; * continue;*/ } if (!vertices.ContainsKey(fileVertexNum)) { vertices[fileVertexNum] = new LevelVertex(vertexLocation); } segment.Vertices[vertexNum] = vertices[fileVertexNum]; } segment.Light = new Fix(BlockCommon.ReadValue <int>(reader, "static_light")); segment.Function = (SegFunction)BlockCommon.ReadValue <byte>(reader, "special"); var matcenNum = BlockCommon.ReadValue <sbyte>(reader, "matcen_num"); if (matcenNum != -1) { var matcen = new MatCenter(segment); segment.MatCenter = matcen; block.MatCenters.Add(matcen); } // This is an index into the fuelcen (also used for matcens) array in D1/D2. // It's relatively easy to recalculate so we don't store it. BlockCommon.ReadValue <sbyte>(reader, "value"); // Child/wall bitmasks are used internally by DLE but we don't really need them // - they can be recalculated BlockCommon.ReadValue <byte>(reader, "child_bitmask"); BlockCommon.ReadValue <byte>(reader, "wall_bitmask"); } // Now set up segment connections foreach (var connection in segmentConnections) { for (int i = 0; i < connection.Value.Length; i++) { var connectedSegmentId = connection.Value[i]; if (connectedSegmentId < 0) { continue; } segments[connection.Key].Sides[i].ConnectedSegment = segments[(uint)connectedSegmentId]; } } foreach (Segment segment in segments.Values) { block.Segments.Add(segment); } BlockCommon.SetupVertexConnections(block); // Set up trigger target connections foreach (var connection in triggerTargetConnections) { var trigger = block.Triggers[connection.Key]; for (int targetNum = 0; targetNum < connection.Value.Count; targetNum++) { var target = connection.Value[targetNum]; var targetSide = block.Segments[target.segmentNum].Sides[target.sideNum]; trigger.Targets.Add(targetSide); if (targetSide.Wall != null) { targetSide.Wall.ControllingTriggers.Add((trigger, (uint)targetNum)); } } } return(block); }