Example #1
0
 public ShadowReceiver(Heightmap heightmap, Oblique heightmapExtendDirection)
 {
     if (heightmap == null)
     {
         throw new ArgumentNullException("heightmap");
     }
     this.heightmap = heightmap;
     this.heightmapExtendDirection = heightmapExtendDirection;
 }
Example #2
0
        public static int CalculateFlatPhysicsDepth(int physicsWidth, Oblique flatDirection)
        {
            // NOTE: This method should match calculation of flat Z-sort data
            if (flatDirection == Oblique.Straight)
            {
                return(0);
            }

            return(physicsWidth = System.Math.Min(physicsWidth, ((int)byte.MaxValue) + 1));
        }
Example #3
0
        public void SetPhysicsForCharacter()
        {
            if (Heightmap != null)
            {
                return;
            }

            // Default values for a character (based on default CharacterPhysics)
            physicsStartX = defaultCharacterStartX;
            physicsEndX   = defaultCharacterEndX;
            physicsStartZ = 0;
            physicsEndZ   = 0;
            physicsHeight = defaultCharacterHeight;
            flatDirection = Oblique.Straight;

            RegenerateDepthBounds();
        }
Example #4
0
        public static string ToFancyString(this Oblique oblique, bool straightMeansFlat = false)
        {
            switch (oblique)
            {
            case Oblique.Left:
                return("Left ↖");

            case Oblique.Straight:
                return(straightMeansFlat ? "Flat ↑" : "Straight ↑");

            case Oblique.Right:
                return("Right ↗");

            default:
                throw new ArgumentOutOfRangeException("oblique");
            }
        }
Example #5
0
        public void SetPhysicsForCarpet()
        {
            if (Heightmap != null)
            {
                return;
            }

            // Default values for a static prop
            if (HasDefaultAnimation)
            {
                var bounds = DefaultAnimation.CalculateGraphicsBounds();
                physicsStartX = bounds.X;
                physicsEndX   = bounds.X + bounds.Width;
                physicsStartZ = bounds.Y;
                physicsEndZ   = bounds.Y + bounds.Height;
                physicsHeight = 0;                // <- The magic.
                flatDirection = Oblique.Straight; // <- Arbitrary
            }

            RegenerateDepthBounds();
        }
Example #6
0
        public void SetPhysicsForFlat()
        {
            if (Heightmap != null)
            {
                return;
            }

            // Default values for a static prop
            if (HasDefaultAnimation)
            {
                var bounds = DefaultAnimation.CalculateGraphicsBounds();
                physicsStartX = bounds.X;
                physicsEndX   = bounds.X + bounds.Width;
                physicsStartZ = 0;
                physicsEndZ   = 0;
                physicsHeight = System.Math.Max(0, bounds.Y + bounds.Height); // Z-sort disregards anything below-ground
                flatDirection = Oblique.Straight;
            }


            RegenerateDepthBounds();
        }
Example #7
0
        /// <summary>Set heights from a top-surface mask where the front edge is at a particular depth</summary>
        /// <param name="maskData">The 1-bit mask representing the top surface</param>
        /// <param name="frontEdgeDepth">The depth of the front edge of pixels in the mask</param>
        /// <param name="perspective">Oblique direction that the mask projects backwards towards</param>
        public void SetFromObliqueTopMask(MaskData maskData, int frontEdgeDepth, Oblique oblique)
        {
            // Ensure that there's enough room in the heightmap to contain the maximum extents of the mask
            Rectangle outputPotentialBounds = maskData.Bounds;

            outputPotentialBounds.Y = frontEdgeDepth; // The usable Z range = [frontEdgeDepth, frontEdgeDepth + Height)
            heightmapData           = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight);

            // Note: Y axis seeks from the top downwards (from back to front)
            for (int y = maskData.EndY - 1; y >= maskData.StartY; y--)
            {
                for (int x = maskData.StartX; x < maskData.EndX; x++)
                {
                    if (maskData[x, y])
                    {
                        int height = GetHeightByWalkingObliqueForward(maskData, frontEdgeDepth, oblique, x, y);
                        int z      = y - height;

                        heightmapData[x, z] = (byte)height; // (If height overflows... at least it will be obvious)
                    }
                }
            }
        }
Example #8
0
 /// <summary>Deserialize into new object instance</summary>
 public ShadowReceiver(AnimationDeserializeContext context)
 {
     heightmap = new Heightmap(context);
     heightmapExtendDirection = context.br.ReadOblique();
 }
Example #9
0
        public AnimationSet(AnimationDeserializeContext context)
        {
            friendlyName = context.br.ReadNullableString();
            importOrigin = context.br.ReadPoint();
            behaviour    = context.br.ReadNullableString();

            if (context.br.ReadBoolean())
            {
                Heightmap = new Heightmap(context);
            }

            if (Heightmap != null)
            {
                physicsStartX = Heightmap.StartX;
                physicsEndX   = Heightmap.EndX;
                physicsStartZ = Heightmap.StartZ;
                physicsEndZ   = Heightmap.EndZ;
                // Assume that reading is faster than walking the heightmap:
                physicsHeight = context.br.ReadInt32();
                if (physicsHeight > 0)
                {
                    depthBounds = new DepthBounds(context);
                }
                flatDirection = context.br.ReadOblique(); // <- for the sake of editing, keep this value around
            }
            else
            {
                physicsStartX = context.br.ReadInt32();
                physicsEndX   = context.br.ReadInt32();
                physicsStartZ = context.br.ReadInt32();
                physicsHeight = context.br.ReadInt32();
                flatDirection = context.br.ReadOblique();

                if (physicsHeight == 0)
                {
                    physicsEndZ = context.br.ReadInt32(); // physicsEndZ gets auto-set during regen, except for carpets
                }
                RegenerateDepthBounds();                  // <- Know this is reasonably fast to generate
            }

            if (context.Version >= 38)
            {
                coplanarPriority = context.br.ReadInt32();
            }

            if (context.Version >= 36)
            {
                doAboveCheck = context.br.ReadBoolean();
            }

            if (context.br.ReadBoolean())
            {
                Ceiling = new Heightmap(context);
            }

            animations = new TagLookup <Animation>(context, () => new Animation(context));


            // Unused Animations
            {
                int count = context.br.ReadInt32();
                if (count > 0) // unusedAnimations is lazy-initialized
                {
                    unusedAnimations = new List <Animation>(count);
                    for (int i = 0; i < count; i++)
                    {
                        unusedAnimations.Add(new Animation(context));
                    }
                }
            }

            cue = context.br.ReadNullableString();


            // Shadow layers
            {
                int shadowLayerCount = context.br.ReadInt32();
                if (shadowLayerCount <= 0)
                {
                    shadowLayers = null;
                }
                else
                {
                    shadowLayers = new List <ShadowLayer>();
                    for (int i = 0; i < shadowLayerCount; i++)
                    {
                        shadowLayers.Add(new ShadowLayer(
                                             context.br.ReadInt32(),
                                             new SpriteRef(context)));
                    }

                    cachedShadowBounds = context.br.ReadBounds();
                }
            }


            //
            // FIX-UPS:
            //

            // TODO: Commenting this out and seeing if anything breaks (remove it eventually) -AR 7/7/16
            //if(physicsStartX == physicsEndX) // <- Why do we have zero width?
            //{
            //    Debug.WriteLine("WARNING: AnimationSet \"" + friendlyName + "\" has zero physics width");
            //    AutoGeneratePhysicsAndDepthBounds();
            //}
        }
Example #10
0
 public static void Write(this BinaryWriter bw, Oblique oblique)
 {
     bw.Write((sbyte)oblique);
 }
Example #11
0
        /// <summary>Create a new heightmap that extends its content (non-default heights) out in oblique directions (sutiable for creating shadow heightmaps)</summary>
        public Heightmap CreateExtendedOblique(Oblique oblique)
        {
            // Expand the region containing content in the specified oblique direction(s)
            #region ASCII Art...
            //
            // Given a heightmap that looks like this:
            //      ________
            //     | /\  /\ |
            //     |/  \/  \|
            //     |\      /|
            //     |/      \|
            //     |\  /\  /|
            //     |_\/__\/_|
            //
            // Expand it by projecting its content in the oblique direction:
            //  ________________
            // |.  | /\  /\ |   |
            // | . |/  \/  \|   |
            // |  .|\      /.   |
            // |   ./      \|.  |
            // |   |\  /\  /| . |
            // |___|_\/__\/_|__.|
            //
            #endregion

            int extendedLeft  = StartX;
            int extendedRight = EndX - 1; // <- inclusive
            for (int z = StartZ; z < EndZ; z++)
            {
                for (int x = StartX; x < EndX; x++)
                {
                    if (this[x, z] != DefaultHeight)         // Has content to extend
                    {
                        int deltaFromFront = z - StartZ;     // (positive)
                        int deltaFromBack  = z - (EndZ - 1); // (negative)

                        int frontX = x - deltaFromFront * (int)oblique;
                        int backX  = x - deltaFromBack * (int)oblique;

                        // Expand:
                        if (frontX < extendedLeft)
                        {
                            extendedLeft = frontX;
                        }
                        if (frontX > extendedRight)
                        {
                            extendedRight = frontX;
                        }
                        if (backX < extendedLeft)
                        {
                            extendedLeft = backX;
                        }
                        if (backX > extendedRight)
                        {
                            extendedRight = backX;
                        }
                    }
                }
            }

            // HACK: To allow edges to have zero values when filled, without adding expansion to the Fill methods,
            //       simply expand by one pixel here:
            // TODO: Allow the FillLeft/FillRight methods to automatically expand the size of the heightmap sideways if necessary
            //       (So that zero values on the very edge aren't lost)
            extendedLeft  -= 1;
            extendedRight += 1;


            Rectangle destinationBounds = new Rectangle(extendedLeft, StartZ, extendedRight - extendedLeft + 1, EndZ - StartZ);

            Heightmap destination = new Heightmap(DefaultHeight);
            destination.heightmapData = new Data2D <byte>(destinationBounds);

            // Copy while extending content
            for (int destinationZ = destination.StartZ; destinationZ < destination.EndZ; destinationZ++)
            {
                for (int destinationX = destination.StartX; destinationX < destination.EndX; destinationX++)
                {
                    // We could add a bunch of early-outs here (but we don't care if this is a tad slow)
                    int sourceX = destinationX, sourceZ = destinationZ;

                    // Try and extend to find content:
                    if (this[sourceX, sourceZ] == DefaultHeight)
                    {
                        // Search backwards to back of heightmap
                        while (true)
                        {
                            sourceZ += 1;
                            sourceX += (int)oblique;
                            if (sourceZ >= this.EndZ)
                            {
                                goto searchForward;
                            }
                            if (this[sourceX, sourceZ] != DefaultHeight)
                            {
                                goto done;
                            }
                        }

                        // Search forward to front of heightmap
searchForward:
                        sourceX = destinationX; sourceZ = destinationZ;
                        while (true)
                        {
                            sourceZ -= 1;
                            sourceX -= (int)oblique;
                            if (sourceZ < this.StartZ)
                            {
                                goto fail;
                            }
                            if (this[sourceX, sourceZ] != DefaultHeight)
                            {
                                goto done;
                            }
                        }

fail:
                        sourceX = destinationX; sourceZ = destinationZ;
done:
                        ;
                    }
                    destination[destinationX, destinationZ] = this[sourceX, sourceZ];
                }
            }

            return(destination);
        }
Example #12
0
        public void SetFromObliqueSide(MaskData maskData, Oblique obliqueDirection, int offset)
        {
            // Straight and Right use the same input direction (because Straight input does not make sense, but Straight output is ok)
            int inputReadDirection = 1;
            int x = maskData.StartX;

            if (obliqueDirection == Oblique.Left)
            {
                inputReadDirection = -1;
                x = maskData.EndX - 1;
            }

            int y;

            while (x >= maskData.StartX && x < maskData.EndX)
            {
                for (y = maskData.StartY; y < maskData.EndY; y++) // read bottom-to-top
                {
                    if (maskData[x, y])
                    {
                        goto foundStartPosition;
                    }
                }
                x += inputReadDirection;
            }

            // No data found!
            return;

foundStartPosition:

            // Ensure that there's enough room in the heightmap to contain the maximum extents of the processed mask...
            {
                int left, right;
                if (inputReadDirection == 1)
                {
                    left  = x;
                    right = maskData.EndX - 1;
                }
                else // reading right-to-left
                {
                    left  = maskData.StartX;
                    right = x;
                }
                // Account for offset:
                left  += Math.Min(offset, 0);
                right += Math.Max(offset, 0);
                int front = y;
                int back  = front + (right - left); // can move back one pixel for each column of input

                Rectangle outputPotentialBounds = new Rectangle(left, front, (right - left) + 1, (back - front) + 1);
                heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight);
            }


            // Convert mask to heightmap:
            int writeX = x;
            int writeZ = y;
            int baseY  = y;

            while (x >= maskData.StartX && x < maskData.EndX) // For each column to end of image
            {
                y = baseY;                                    // Count pixels from base upwards
                while (y < maskData.EndY && maskData[x, y])
                {
                    y++;
                }

                int height = y - baseY;
                if (height > 0)
                {
                    int i = 0;
                    do
                    {
                        heightmapData[writeX + i * Math.Sign(offset), writeZ] = (byte)Math.Min(byte.MaxValue, height);
                        i++;
                    } while(i < Math.Abs(offset));
                }

                // Move input:
                x += inputReadDirection;
                baseY++;

                // Move output:
                writeX += (int)obliqueDirection;
                writeZ++;
            }
        }
Example #13
0
        /// <param name="slope">Number of pixels to travel backwards before traveling in the oblique direction</param>
        public void SetFromFrontEdge(MaskData maskData, int frontEdgeDepth, int depth, Oblique obliqueDirection, int slope, int offset)
        {
            Debug.Assert(depth > 0);
            Debug.Assert(slope > 0);

            // How far do we travel on the X axis as we go backwards?
            int pixelsTraveledSideways = ((depth + slope - 1) / slope - 1) * (int)obliqueDirection;
            int outputStartX           = Math.Min(maskData.StartX, maskData.StartX + pixelsTraveledSideways);
            int outputEndX             = Math.Max(maskData.EndX, maskData.EndX + pixelsTraveledSideways);

            // Ensure that there's enough room in the heightmap to contain the maximum extents of the processed mask...
            Rectangle outputPotentialBounds = new Rectangle(outputStartX, frontEdgeDepth, outputEndX - outputStartX, depth);

            heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight);


            // Read the mask upwards to find the "lip" of the mask surface
            for (int x = maskData.StartX; x < maskData.EndX; x++)     // For each column in the image
            {
                for (int y = maskData.StartY; y < maskData.EndY; y++) // Search from bottom-to-top
                {
                    if (maskData[x, y])
                    {
                        // Found the lip at a given Y height, copy it backwards at the given pitch
                        for (int d = 0; d < depth; d++)
                        {
                            int zz = frontEdgeDepth + d;
                            int xx = x + (d / slope) * (int)obliqueDirection;

                            heightmapData[xx, zz] = (byte)(y - frontEdgeDepth + offset);
                        }

                        goto nextColumn;
                    }
                }

nextColumn:
                ;
            }
        }
Example #14
0
        public int GetHeightByWalkingObliqueForward(MaskData maskData, int frontEdgeDepth, Oblique oblique, int x, int y)
        {
            // Try to walk forward in the mask to find the front edge, from which we have a specified depth, and can calculate the height
            while (true)
            {
                // NOTE: Don't need to do special handling of "upright" sections, because our caller works top-down through the image
                //       (So these sections will be overwritten as appropriate)

                int nextX = x - (int)oblique; // Walk forward (down the mask) in the oblique direction
                int nextY = y - 1;

                if (!maskData.GetOrDefault(nextX, nextY)) // Reached the front of the mask data
                {
                    return(y - frontEdgeDepth);
                }

                x = nextX;
                y = nextY;
            }
        }
Example #15
0
        /// <summary>Get the height at a given position in the heightmap, with special shadow indexing (does not treat default height as special)</summary>
        public int GetHeightForShadow(int startX, int endX, int z, Oblique extendDirection)
        {
            Debug.Assert(heightmap.HasData); // <- NOTE: Expect our caller to skip calculation if there is no data!!

            // Transform into heightmap space:
            int transformedStartX;
            int transformedEndX;

            if (!flipX)
            {
                transformedStartX = startX - position.X;
                transformedEndX   = endX - position.X;
            }
            else
            {
                transformedStartX = -(endX - position.X - 1);
                transformedEndX   = -(startX - position.X - 1);
                extendDirection   = (Oblique)(-(int)extendDirection);
            }
            Debug.Assert(transformedStartX < transformedEndX);

            z -= position.Z;


            // Move into heightmap Z range in oblique direction
            if (z < heightmap.StartZ)
            {
                int distance = heightmap.StartZ - z; // (positive)
                distance          *= (int)extendDirection;
                transformedStartX += distance;
                transformedEndX   += distance;
                z = heightmap.StartZ;
            }
            else if (z >= heightmap.EndZ)
            {
                int distance = (heightmap.EndZ - 1) - z; // (negative)
                distance          *= (int)extendDirection;
                transformedStartX += distance;
                transformedEndX   += distance;
                z = heightmap.EndZ - 1;
            }


            // If we are outside the range, early-out:
            if (transformedEndX <= heightmap.StartX)
            {
                return(position.Y + heightmap.heightmapData[heightmap.StartX, z]); // Skips bounds check
            }
            if (transformedStartX >= (heightmap.EndX - 1))
            {
                return(position.Y + heightmap.heightmapData[heightmap.EndX - 1, z]); // Skips bounds check
            }
            // Ensure we are clipped to the range:
            if (transformedStartX < heightmap.StartX)
            {
                transformedStartX = heightmap.StartX;
            }
            if (transformedEndX > heightmap.EndX)
            {
                transformedEndX = heightmap.EndX;
            }

            int maxHeight = 0;

            for (int x = transformedStartX; x < transformedEndX; x++)
            {
                int h = heightmap.heightmapData[x, z]; // Skips bounds check
                if (h > maxHeight)
                {
                    maxHeight = h;
                }
            }

            return(position.Y + maxHeight);
        }
Example #16
0
        public AnimationSet(AnimationDeserializeContext context)
        {
            friendlyName = context.br.ReadNullableString();
            importOrigin = context.br.ReadPoint();
            behaviour    = context.br.ReadNullableString();

            if (context.br.ReadBoolean())
            {
                Heightmap = new Heightmap(context);
            }

            if (Heightmap != null)
            {
                physicsStartX = Heightmap.StartX;
                physicsEndX   = Heightmap.EndX;
                physicsStartZ = Heightmap.StartZ;
                physicsEndZ   = Heightmap.EndZ;

                // Assume that reading is faster than walking the heightmap:
                physicsHeight = context.br.ReadInt32();
                if (physicsHeight > 0)
                {
                    depthBounds = new DepthBounds(context);
                }
                flatDirection =
                    context.br.ReadOblique(); // <- for the sake of editing, keep this value around
            }
            else
            {
                physicsStartX = context.br.ReadInt32();
                physicsEndX   = context.br.ReadInt32();
                physicsStartZ = context.br.ReadInt32();
                physicsHeight = context.br.ReadInt32();
                flatDirection = context.br.ReadOblique();

                if (physicsHeight == 0)
                {
                    physicsEndZ =
                        context.br.ReadInt32(); // physicsEndZ gets auto-set during regen, except for carpets
                }
                RegenerateDepthBounds();        // <- Know this is reasonably fast to generate
            }

            if (context.Version >= 38)
            {
                coplanarPriority = context.br.ReadInt32();
            }

            if (context.Version >= 36)
            {
                doAboveCheck = context.br.ReadBoolean();
            }

            if (context.br.ReadBoolean())
            {
                Ceiling = new Heightmap(context);
            }

            animations = context.DeserializeTagLookup(() => new Animation(context));

            // Unused Animations
            {
                int count = context.br.ReadInt32();
                if (count > 0) // unusedAnimations is lazy-initialized
                {
                    unusedAnimations = new List <Animation>(count);
                    for (var i = 0; i < count; i++)
                    {
                        unusedAnimations.Add(new Animation(context));
                    }
                }
            }

            cue = context.br.ReadNullableString();

            // Shadow layers
            {
                int shadowLayerCount = context.br.ReadInt32();
                if (shadowLayerCount <= 0)
                {
                    shadowLayers = null;
                }
                else
                {
                    shadowLayers = new List <ShadowLayer>();
                    for (var i = 0; i < shadowLayerCount; i++)
                    {
                        shadowLayers.Add(new ShadowLayer(context.br.ReadInt32(), new SpriteRef(context)));
                    }

                    cachedShadowBounds = context.br.ReadBounds();
                }
            }

            // Properties:
            if (context.Version >= 40)
            {
                int count = context.br.ReadInt32();
                for (int i = 0; i < count; i++)
                {
                    properties.Add(context.br.ReadString(), context.br.ReadString());
                }
            }
        }