How to judge the mouse on TDrawableImage

I use TDrawableImage to draw by myself, Everything is good at present

I met a problem, How to judge the mouse on TDrawableImage

1、examples/viewport_and_scenes/detect_scene_hit/
This example not use TDrawableImage to draw

2、With TCastleTiledMap, you can use Map property and PositionToTile method to detect on which exactly tile we clicked.
My TDrawableImage shape is irregular,PositionToTile ,It seems that I can’t achieve my goal

I previously used Zengl to judge the mouse on ImageTex by function glReadPixels (core function)

glReadnPixels return pixel data from the frame buffer,speed is fast

If you want a direct equivalent to glReadPixels, you can just use Window.Container.SaveScreen that will give you back TRGBImage. Use one of these overloads:

function SaveScreen: TRGBImage; overload;
function SaveScreen(const SaveRect: TRectangle): TRGBImage; overload; virtual;
function SaveScreen(const SaveRect: TFloatRectangle): TRGBImage; overload;

Underneath they indeed use just glReadPixels, there’s nothing more fancy there:)

The TRGBImage is an image in regular memory, you can just read it’s pixel values, like MyImage.Pixels[X, Y, 0], this returns TCastleColor.

Notes:

  1. It’s not efficient to process many pixels one-by-one like this (iterating over array TRGBImage.Pixels is much more efficient) but I guess you just want to query a single pixel under mouse, in fact you probably just want to grab this single pixel value to 1x1 image.

  2. Window.Container.SaveScreen will flush and redraw internally. This could be avoided by using SaveScreen_NoFlush, it’s a global routine:

    function SaveScreen_NoFlush(
      const Rect: TRectangle; const ReadBuffer: TColorBuffer): TRGBImage;
    

    You can use it with ReadBuffer set to cbBack. But then you have to do it right after drawing the map but before the “buffers swap” to read from back buffer reliably, as reading from front buffer is never reliable (and after swap, back buffer contents are undefined). (This isn’t related to CGE, it’s how glReadPixels works, so it is surely the same as in ZenGL.)

    So you can call SaveScreen_NoFlush like that only during Render effectively, right after drawing the map but before the whole Render of everything finished.

  3. You say glReadPixels works fast for you, I just have to make a disclaimer:

    Using glReadPixels or Window.Container.SaveScreen or SaveScreen_NoFlush too often is not in general an efficient solution. It means we read data from GPU to regular memory, which in general is something one should not do, transfer of data from the GPU to regular memory is never super fast (and it requires GPU to finish drawing, to write all it knows to the color buffer, while you synchronously wait). Though I guess it may be just fast enough in some cases.

    This is one of advantages of higher-level solutions, like examples/viewport_and_scenes/detect_scene_hit/ using TCastleScene, where you can query for collisions using rays, with or without physics engine. That is also why PositionToTile just calculates the position using math. But then, indeed it will not be able to query for irregular images, on CGE side we don’t analyze e.g. alpha of images.

The example:Door shape is irregular, When mouse move on the door,can trigger event, so I need to determine whether the mouse is on the door picture

In tiledmap 2DGames, this is a very common design needs, such as the mouse moves to char、bookcase、Irregular entrance、switch on the wall etc.

If use Map property and PositionToTile method detect on which exactly tile(not door irregular shape, only standard size tile) we clicked. Can’t accurately determine whether the mouse is on the door .

If use examples/viewport_and_scenes/detect_scene_hit , It seems not suitable for tiledmap

Is there any other way?

Admittedly not right now. But I can foresee how to implement a “proper” system for the future for TCastleTiledMap (that knows about shapes). We can then do a specialized raycast, looking at 2D geometry of the shapes. So it would detect whole rendered shapes (even when they are larger than map tiles). To avoid detecting shapes where pixels are transparent, It could resolve the point to a specific point in texture atlas, query the alpha of it, and look at lower layer if it is transparent.

Admittedly this is not ready yet and would be some work to add.

If you want to go with solution of grabbing the pixel under the mouse, and you know it performs well for you, go ahead and use it now. And I’m adding another TODO for TCastleTiledMap for future :slight_smile:

I thought about it recently, CGE still can’t do it

I can describe the details as much as possible, please forgive me for poor English

Tiledmap map only needs to judge some objects, such as the mouse moves to char、door、bookcase、Irregular entrance、switch on the wall etc

So I traversed all object element,check if the mouse is above them.

First, check whether the mouse is in the area where the graphic rectangle is located,.This one is very fast, because only the coordinates need to be compared. Can exclude most of the elements.

For the situation of the mouse in the image rectangular area, get this image Texture Data( in ZenGL) , check whether the mouse points X,Y has pixel data

In fact, I only take a little data(only one image),and it is not obtained from the map,get this image texture. If it is transparent, explain that the mouse is not there.

SaveScreen get data from a map after rendering, not one image,This method can’t do it

If have TDrawableImage.SaveScreen ,Maybe can do , It seems impossible

The following is the code in ZenGL, judging whether the mouse is on the picture


99

So the ZenGL is doing something close to what I mentioned in “But I can foresee how to implement a “proper” system for the future for TCastleTiledMap (that knows about shapes)…” in my earlier post. They analyze the original image, since that has the alpha channel.

I have added a GitHub issue Tiled maps: detect the tile indicated by mouse, taking into account texture tiles with any shape, and with any alpha channel · Issue #448 · castle-engine/castle-engine · GitHub to deal with it in CGE.

If you want to implement it with your custom system, you want to look at original image’s data. TDrawableImage has a reference to Image: TEncodedImage, you can use it to obtain the original image data. If it’s not using GPU compression, you can just cast it to e.g. TCastleImage and query pixels there, like

function AlphaOfPixel(const X, Y: Integer): Single;
begin
  Result := (MyDrawableImage.Image as TCastleImage).Colors[X, Y, 0].W;
end;

The rendered image indeed has everything “flattened”.

To be clear, I did point to SaveScreen adding a disclaimer that it’s not perfect, because you asked for analogy to glReadPixels at the beginning of this thread :slight_smile:

Unless ZenGL is using glReadPixels for more things, maybe they use it even to get original image data? If that is so: For CGE, this is in TDrawableImage.Image, there’s no need for glReadPixels.

( I can also imagine a solution that would still use SaveScreen – you’d need to make a temporary render of everything, setting all textures to a solid color, specific to given tile, but still with original alpha. Though I would recommend to pursue a solution without this involved hack .)

Thanks michalis, AlphaOfPixel can do it,I have tested and passed(I will test the performance in the next step). My current problems seem to be solved, I will continue to explore CGE.

This also shows that a complete game example is the importance of the engine.

Simple examples cannot reveal the functions that need to be used in actual use.

I agree about the examples – we need to have a “simple Diablo game” example, this would explore features needed by games of this type. In the past, I tested Tiled support on “examples/tiled/strategy_game_demo/”, but that’s too simple use-case, it didn’t require various features that “Diablo” would.

I apologize for the banality of my observation which I am sure derives from my difficulty in understanding but I’m wondering: why not simply implement in the engine the possibility of creating trivial hotspots by drawing them around a door, a bed, a wall?
“The example:Door shape is irregular, When mouse move on the door,can trigger event, so I need to determine whether the mouse is on the door picture”.
Isn’t that the purpose of a hotspot in a game engine?
I had a lot of fun with different engines such as Wintermute, AGS, Point & Click, Visionaire, Unity, and a common point in all of them was the presence of hotspots, fundamental in point and click games. Couldn’t one envisage including them in the CGE?

Indeed you could set up manually “hotspots” like this (this is already possible using CGE to some extent, see below) but it would be additional work, esp. for a big map, to keep such hotspots synchronized with the actual Tiled map.

So the point of related GitHub issue ( Tiled maps: detect the tile indicated by mouse, taking into account texture tiles with any shape, and with any alpha channel · Issue #448 · castle-engine/castle-engine · GitHub ) is to detect the tile image under mouse – using just the information we already have about the Tiled map (including alpha channel of the tiles).

As for hotspots: You can already detect scene, shape, material etc. under the mouse cursor, see example “examples/viewport_and_scenes/detect_scene_hit/”. So you can use any TCastleTransform descendant as a “hotspot” this way, just set Visible=false on it. You could set up hotspots:

Has the problem been resolved now?

Considering that this is a frequently used feature in 2D Tiledmap games, can increase the priority?

In other engines, I can use irregular images like doors as a special object for mouse collision detection. At the current 2D level of cge, I cannot find a reasonable detection approach (tiledmap of a large number of sub images)

If possible, it would be best to have a 2D example

Well, doing that for TDrawableImage is pretty trivial:

pixel-detection.zip (19.4 KB)

The core of the detection is in Motion method:

  X := (Event.Position.X - DestRect.Left) / DestRect.Width;
  Y := (Event.Position.Y - DestRect.Bottom) / DestRect.Height;
  if (X >= 0) and (Y >= 0) and (X < 1) and (Y < 1) then
  begin
    ImgX := Round(SrcRect.Width * X - SrcRect.Left);
    ImgY := Round(SrcRect.Width * Y - SrcRect.Left);
    if (ImgX >= 0) and (ImgY >= 0) and (ImgX < Image.Width) and (ImgY < Image.Height) then
    begin
      if (Image.Image as TRGBAlphaImage).PixelPtr(ImgX, ImgY)^.W > 10 then
        LabelFps.Caption := 'OVER PIXEL'
      else
        LabelFps.Caption := 'OUT OF PIXEL'
    end else
      LabelFps.Caption := 'Mouse is within the screen rectangle but outside of the image rectangle, the results are ambiguous. However, even here we can try to play with Image.RepeatS and Image.RepeatT';
  end else
    LabelFps.Caption := 'OUT OF IMAGE'; 

The question is how “scalable” this algorithm is :slight_smile: E.g. a TDrawableImage not necessarily contains a CPU Image. Also if used for something “bigger” like pixel-perfect collision detection (next natural step of this feature) it quickly results in poor performance as requires “random reading of memory”. I’ve seen an idea for solution through custom “mipmaps”-like downscales of the image to speedup collision detection, but in turn it eats up memory :slight_smile:

So, I guess the question is “what exactly this feature should do in all possible usecases”.

99_副本

For example, for non rectangular doors, when the mouse moves over the image of the door, the mouse becomes a small hand. Clicking on it can detect a collision with the door, triggering door related events

Similar to a treasure chest, I don’t want the mouse to become a small hand when there is still a distance from the treasure chest. The effect I want is that the mouse only changes when it is on the picture of the treasure chest. Similar to the switches on the wall