Suprcode / Zircon

Legend of Mir 3 - Official Public Zircon Source
336 stars 255 forks source link

Weird Double Drawing Of PlayerObject Ontop Of Everything #74

Open FluffyDuckyEU opened 1 year ago

FluffyDuckyEU commented 1 year ago

I don't understand this line, it seems like a hack and it is having an impact on the wing effects.

https://github.com/Suprcode/mir3-zircon/blob/3a890d540ed4fa4a9b90f0089d2c3cadf2ef05e4/Client/Scenes/Views/MapControl.cs#L405

Removing it has a consequence, so it's not so simple, i assume this is the reason this was added.

With This Line UnnecessaryDraw

Without This Line UnnecessaryDraw2

FluffyDuckyEU commented 1 year ago

I feel like this should be handled as part of PlayerObject Draw() method.

https://github.com/Suprcode/mir3-zircon/blob/3a890d540ed4fa4a9b90f0089d2c3cadf2ef05e4/Client/Models/PlayerObject.cs#L801

Imo maybe I should just reimplement that whole Draw method while i'm touching it :|

Suprcode commented 1 year ago

Yes double drawing has always been a requirement to see yourself behind objects.

I don't think you lose the colour when you hover if that's what you meant. Just a side effect of the draw blend / opacity making the image brighter and losing colour I think.

I'm not sure how you'd plan to get around needing to double draw. The simplest fix would be to just double draw the wings too - I can't think of any other way you can resolve it in a single draw.

FluffyDuckyEU commented 1 year ago

It seems like a waste to process twice, i was hoping to be able to determine whether the character is hidden whilst processing in the PlayerObject.Draw but because the map object spans over multiple cells whilst only actually being assigned to one cell that the player isn't likely on makes it not possible like you said :/

Suprcode commented 1 year ago

To be honest you probably can work it out. You'd have to look at the current X coord, and all Y coordinates directly below the player (plus maybe +/- 1 X due to overflowing image to out cells). Then you'd need to look at all 3 layer images to see if the cell height reaches the player.

It'll certainly be an expensive calculation to constantly do, so you'd need to ensure its cached - which might use abit of memory after a certain period.

Suprcode commented 1 year ago

Thinking about it, you're really only going to need to calculate 3 cells below you - each time you move. So maybe not that expensive and worth looking in to. Certainly would be better than drawing opacity over the player constantly

FluffyDuckyEU commented 1 year ago

I thought some map objects were quite tall, let me see if I can investigate that as a separate thing.

Suprcode commented 1 year ago

They are, but there's only 3 cells below you. Any objects lower than that won't get drawn, even if they're more than 3 cells tall. I think.

FluffyDuckyEU commented 1 year ago

Here is the same example. This particular object is 1x13 size.

image

FluffyDuckyEU commented 1 year ago

Do you think it's still possible to be able to calculate a top image that is up to maybe 10 cells away from your character?

Sorry for the horrible pink lines, i don't know where i found this program but it does the job for viewing the map😅

image

FluffyDuckyEU commented 1 year ago

The more I think about it, the more I think it's not possible, how would you also handle a character half under shelter and half not? The double draw handles this, but would you end up with the draw fully oqaue not opaque at all if you had this situation with manually worked out solution?

image

Suprcode commented 1 year ago

You'd have to just double draw as soon as the feet/player cell is in the area. But it still means 99% of the time you're not double drawing.

Maybe write the function to search for 10 coords below you and see how the performance is. Since you only need to do it once every time you move, it'll probably be fine

FluffyDuckyEU commented 1 year ago

I may be thinking about this too simply, but using the cell positions I don't think is enough to work.

image

Maybe you have something in mind that i don't, when you say:

Then you'd need to look at all 3 layer images to see if the cell height reaches the player.

How would I know if the map object image overflow lands on top of the player fully or not? The only way I can see this being achieved is if you check whether the images intersect, not by cells but by pixel maybe?

Suprcode commented 1 year ago

The images only ever go as high as they have atleast 1 pixel in them. As you can see from below the pink cells show the bottom of each image (where its physically placed).

image

So all you need to do is get the height of the image, divide is by the constant cell height - that'll give you the cells tall an image is.

then as you loop down your Y coord - you check if the player coord is greater than the Y coord minus the image height. If it is, you know the player is over it.

player Y = 100

checking Y coord = 105 105's image height = 5

if (100 > 105 - 5) Player is behind image.

image height will always be minimum of 1. So in most cases for a single cell image

if (100 > 101 - 1) player not behind

(you'll need to do this on front and middle layers)

Should be as simple as that. Obviously you're not going to be able to know if just a foot is behind the image, but you're still massively reducing when you have to double draw the player.

Suprcode commented 1 year ago

I've given this a quick go, and although it does kind of work - it certainly not perfect.

public bool IsBehindObject()
        {
            if (User == null) return false;

            var location = User.CurrentLocation;

            IsBehind = false;

            for (int offset = 0; offset < 20; offset++)
            {
                var y = location.Y + offset;

                var cell = Cells[location.X, y];

                if (cell == null) continue;

                LibraryFile file;
                MirLibrary library;

                if (Libraries.KROrder.TryGetValue(cell.MiddleFile, out file) && file != LibraryFile.Tilesc && CEnvir.LibraryList.TryGetValue(file, out library))
                {
                    int index = cell.MiddleImage - 1;

                    Size s = library.GetSize(index);

                    var cellsTall = s.Height / CellHeight;

                    if (location.Y > (location.Y + offset - cellsTall))
                    {
                        IsBehind = true;
                        break;
                    }
                }

                if (Libraries.KROrder.TryGetValue(cell.FrontFile, out file) && file != LibraryFile.Tilesc && CEnvir.LibraryList.TryGetValue(file, out library))
                {
                    int index = cell.FrontImage - 1;

                    Size s = library.GetSize(index);

                    var cellsTall = s.Height / CellHeight;

                    if (location.Y > (location.Y + offset - cellsTall))
                    {
                        IsBehind = true;
                        break;
                    }
                }
            }

            System.Diagnostics.Debug.WriteLine(IsBehind ? "true" : "false");

            return IsBehind;
        }

Then add an "IsBehind" variable to map control

call this line inside LocationChanged()

GameScene.Game.MapControl.IsBehindObject();

and finally wrap drawPlayer around the check


if (IsBehind)
            {
                MapObject.User.DrawPlayer(false);
            }

It would probably be much better just doing this check when the map gets drawn itself - and it doesn't always look to work, often i find myself double drawing when i'm not behind anything.

Ideally checking if there is a visible pixel on the image would be much better - but i can only think performance on that would be an issue.

FluffyDuckyEU commented 1 year ago

I didn't have much time to look myself but i was thinking more along this train of thought, but it didn't work.. maybe i missed something simple.

image

FluffyDuckyEU commented 1 year ago

Ah it does kind of work if I change this code a bit.

                        if (!playerOverlap)
                        {
                            if (s.Width == 0 || s.Height == 0) continue;

                            Rectangle MidImage = new Rectangle(drawX, drawY, s.Width, s.Height);
                            bool result = !Rectangle.Intersect(MidImage, playerRectangle).IsEmpty;
                            playerOverlap = result;
                        }

It still has issues though.

DDB

Maybe easier to see without the huge armour wings. DDB1

Suprcode commented 1 year ago

That sounds like a much better approach

i think you'll need to cache it though so it only rechecks once per player move.

Also, the player dimensions are alot more than just body - if you look in DrawBody theres an l/t/r/b value which gets used to create the shadows - maybe you can store this as a rectangle to reuse it for intersecting?

josh-74 commented 2 months ago

I've done some work on this and I've made the player's Opacity to = 0F; when it's detected behind an object, even with mouse hover it's completely invisible, although Objects of other types are visible upon mouse hover which can be fixed... Not sure what FluffyDuckEU was completely wanting though?

Edit; if it's the wings which are causing this effect, then id need item name so i can check too... can't find anything in the database regarding wings which can be equipped.

https://github.com/user-attachments/assets/a9faf0fa-1581-4c5f-82a0-7f34bc96ea23

Suprcode commented 2 months ago

It wants to be the opposite of that. It should only draw once when you're not behind an object - and draw twice when you are (as you should see yourself transparently when behind objects).

Currently it draws twice all the time, which is the issue needing to be resolved.

josh-74 commented 2 months ago

Interesting, if the areas on the map where this is happening can be pinpointed that'd be great, on my end it's not appearing for me at all, my character consistently stays at half opacity when under any roofs or big entrances.

I've also ensured the code draws the player if the opacity condition is met, which also feels 100x more smoother now when running under any roof tops.

Suprcode commented 2 months ago

the issue isn't when behind an object, its all the other time when you're not.

the player is being drawn twice, all the time. Once at full opacity, and once at half opacity.

The half opacity drawing should only be done when stood behind an object. If not, it should not be drawing the opacity body at all.