Convenient way to determine exact point under mouse

Thanks. So, if I need to change picture in TCastleTransform sometimes, can I load to memory all images after start game. And, after that, do aproximally this:
Current_Object-TCastleTransform := Preloaded_Image-TCastleTransform;
Is it faster then change URL?

At now it works like this:

  1. Player change location, for example, he came to the town from the forest.
  2. Next all TCastleTransform objects get them new files (.png or 3D in the future, maybe…), location refreshed like scene in the theater, but with new decorations (for now it happen literally in one moment, but I don’t use many different objects to render yet).
  3. And, after scene constructed, when player interact with this area, all TCastleTransform remain unchanged until player change global location again. In my plan, player will didn’t change location every few seconds, but will explore every scene some time. So, I can afford short loading of scene, i think (if it will be necessary)…

If the scene URLs only change when switching locations, then it’s likely not an issue.

And the images have a cache anyway. So the cost of changing URL, if the same image is loaded elsewhere already, is small. And you can “warmup” cache with your predicted scene contents using Customize look by CastleSettings.xml | Manual | Castle Game Engine .

So you probably don’t need to do anything :slight_smile: based on what you describe.

Though to make things ultra-fast (to have really zero cost of switching)… you can reuse the same image reference multiple times (following " 10. Multiple instances of the same scene" on Writing code to modify scenes and transformations | Manual | Castle Game Engine ). So you could do something like this at initialization:

MyImage[1] := TCastleImageTransform.Create(...);
MyImage[1].Url := 'castle-data:/1.png';

MyImage[2] := TCastleImageTransform.Create(...);
MyImage[2].Url := 'castle-data:/2.png';

....

And then reuse the MyImage[...] instances for tiles:

for X := 0 to ...
  for Y := 0 to ...
    NewTileImage := MyImage[TileType[X, Y]];
    if (TileTransformation[X, Y].Count = 0) or 
       (TileTransformation[X, Y][0] <> NewTileImage) then
    begin
      TileTransformation[X, Y].Clear;
      TileTransformation[X, Y].Add(NewTileImage);
    end;
1 Like

I just tried this example and for testing I replaced the left url knight.gltf with a starling xml pointing to a png but it is not shown, only the position arrows?

I’d have to see what exactly you did to help :slight_smile: Can you send the testcase?

Here it is.
So I replaced the left knight with a starling file. It looks as if the png file occupies the whole viewport now but is not visible.

shader_effects.zip (63.4 MB)

Ir works OK, clicking on “Randomize Color Effect (Left Knight)” works exactly as it should for me.

You don’t initially see the sprite sheet, because it is big, and you really see just one big dark pixel of it. The sprite sheet default size is quite big, which usually makes sense in 2D. In 3D you either want to move camera away from it, or make image smaller.

  • So simply move away from it. At runtime, just use mouse scroll wheel to move away (there’s an “Examine” navigation component in that example, which makes mouse wheel do that).

  • Or adjust Scale on image to e.g. 0.001, to bring your sprite sheet to a size that is similar to 3D knight.

I played with both options to confirm they are fine. “Randomize Color Effect (Left Knight)” works OK either way.



Thanks, got it working now. :grinning:
Using this example and changing color when transform is under mouse (instead button clicking), how can I reset the colors to the original sprite colors of the image when the mouse is not over the image anymore?

In this simple case, just send color to white (RGB 1,1,1), so

EffectColorField.Send(WhiteRGB);
// equivalent: EffectColorField.Send(Vector3(1, 1, 1));

The shader will keep running but essentially doing nothing. This is such a simple operation that it should be OK :slight_smile:

Alternatively you could set Enabled to true or false on the TEffectNode instance. It is created in CreateColorEffect(const Scene: TCastleScene);, you’d have to save it to a field like ColorEffect: TEffectNode and then later do ColorEffect.Enabled := true / false.

Thanks.
I now have this for testing but there is no lighting up when mouse is over the character (scene1). I also don’t see it in the design. What do I do wrong here?

Rescue_Island.zip (70.6 MB)

@michalis

So a reminder? Here it is. :slight_smile:

Thanks for the reproduction!

Testing:

  • Your model is correctly “lighten up”. So ViewMain.LightupColor correctly executes.

  • But then, it is never brought back to the default lighting. Because you compare Viewport.TransformUnderMouse <> nil, so whenever mouse is over anything (not necessarily your character), the model is lit up.

So you never see any change, because the EffectColorField.Send(Vector3(1, 1, 1)); from TViewMain.Update never executes. The color is always (1.3,1.3,1.3).

As a simple test to confirm this, I propose to change NewColor in TViewMain.LightupColor implementation to something drastically different, like saturated red, NewColor := Vector3(1, 0, 0);. When you do this, you will notice that character turns red immediately when you move the mouse, and just stays red all the time.

Solution: to make a “highlight”, what you want is to compare Viewport.TransformUnderMouse with your character, so

if Viewport.TransformUnderMouse <> nil then

if Viewport.TransformUnderMouse = Scene1 then

P.S. (Notes not directly related to your problem, but just mentioning in case they are useful):

  • Note that the Sender in procedure TViewMain.LightupColor(Sender: TObject) is not used. At least in this testcase – I realize that you probably cut it down before sending to me, so maybe you want to ignore this note, I just mention it in case it will be helpful.

  • You don’t need to run it as ViewMain.LightupColor(Scene1); (through the instance variable), you can just call it as LightupColor(...); when you’re already within another method TViewMain.Update.

All in all, in this simple testcase, I would implement TViewMain.Update in more obvious way, without LightupColor helper:

procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
  inherited;

  if Viewport.TransformUnderMouse <> nil then
    Label1.Caption := Viewport.TransformUnderMouse.Name
  else
    Label1.Caption := ' ';

  if Viewport.TransformUnderMouse = Scene1 then
    EffectColorField.Send(Vector3(1.3, 1.3, 1.3))
  else
    EffectColorField.Send(Vector3(1, 1, 1));
end;

Note that I completely separated setting Label1.Caption from setting EffectColorField. They are independent, need different conditions.

This (implement ray collision example that avoids transparent objects) is still a TODO.

Other than that, I think I managed to catch up with all pending questions / tasks on forum today :slight_smile:

Many thanks, I now got it working when using the CGE editor.

Now I want to add the effect on every scene created in code but it does not work.
I have (heavily stripped down type of TAvatar):
`

type
  TAvatar = class

  private
   type TPersonalia = class
    
     FirstName: string;
     end;
   var Personalia: TPersonalia;

   Transform: TCastleTransform;
   face_west, face_east, face_north, face_south: TCastleScene: TCastleScene;

   LightUp: TCastleDirectionalLight;     


constructor TAvatar.Create(firstname: string);
begin

  Personalia := TPersonalia.Create;

  Personalia.FirstName := firstname;

  Transform := TCastleTransform.Create(Transform);

  face_west := TCastleScene.Create(face_west);
  Transform.Add(face_west);
  face_east := TCastleScene.Create(face_east);
  Transform.Add(face_east);
  face_north := TCastleScene.Create(face_north);
  Transform.Add(face_north);
  face_south := TCastleScene.Create(face_south);
  Transform.Add(face_south);
 
  LightUp := TCastleDirectionalLight.Create(LightUp);
  LightUp.Exists:= true;
  LightUp.Intensity:= 3;
  LightUp.RenderLayer:= rlParent;

  face_west.Add(LightUp);
  face_east.Add(LightUp);
  face_north.Add(LightUp);
  face_southt.Add(LightUp);

  ViewMain.Viewport.Items.Add(Transform); 

(in Start section:)

Player := TAvatar.Create('jack'); 
CreateColorEffect(player.face_west);   

When I add other scenes
CreateColorEffect(player.face_east);
CreateColorEffect(player.face_north);

only the last one gives the effect.

I’d have to see the code where you update the effect to tell what’s wrong.

If you have something similar to the previous code you posted, note:

  1. You need to store different “EffectColorField” for each scene you may want to highlight.

  2. In “TViewMain.Update” you need to turn off the old highlight, turn on the new highlight.

You could e.g. use behaviors Behaviors | Manual | Castle Game Engine to manage this information, e.g. declare

type
  THighlightBehavior = class(TCastleBehavior)
    EffectColorField: ..;
  end;

Then when adding the effect to SomeScene, add also the behavior:

EffectColorField := ...; // create it as before

HighlightBehavior := THighlightBehavior.Create(...);
HighlightBehavior.EffectColorField := EffectColorField;
SomeScene.AddBehavior(HighlightBehavior);

In Update, track the old and new highlighted objects:

type
  TViewXxx = class
  private
    CurrentHighlight: THighlightBehavior;
  end;

...Update(...);
var
  NewHighlight: THighlightBehavior;
begin
  ... // whatever you had previously

  if Viewport.TransformUnderMouse <> nil then
  begin
     NewHighlight := Viewport.TransformUnderMouse.FindBehavior(THighlightBehavior) as THighlightBehavior;
    if NewHighlight <> CurrentHighlight then
   begin
     if CurrentHighlight <> nil then
       CurrentHighlight.Send(Vector3(1, 1, 1));
     if NewHighlight <> nil then
       NewHighlight.Send(Vector3(1.3, 1.3, 1.3));
     CurrentHighlight := NewHighlight;
   end;
  end;
end;

Thanks.

I get a few errors:

If you can see what is wrong here, otherwise I think I have to upload another testcase.
:slight_smile:

And I don’t know if it is possible but instead of changing colors for all 8 different scenes that are part of one Transform I would like to change the parent color (of the upperTransform).

  1. HighLight.EffectColorField – I meant HighLightBehavior.EffectColorField

  2. CurrentHighlight.Send – I meant CurrentHighlight.EffectColorField.Send, NewHighlight.Send – I meant NewHighlight.EffectColorField.Send.

It seems you missed the logic behind my changes (otherwise you’d be able to fix these yourself :slight_smile: ). If anything is unclear in how this works, please ask. The idea of behaviors is explained, with examples, on Behaviors | Manual | Castle Game Engine .

For now, you cannot apply effects on parent TCastleTransform to affect all children of TCastleScene. You have to apply the effect on each TCastleScene.

Yes, I admit that this is confusing. I have tried it again but cannot get it to work.
Would you please take a look at the attachment? Thanks. :slight_smile:

light-test.zip (34.4 MB)

  1. The declaration

    HighLightBehavior: TCastleBehavior;
    

    should be changed to

    HighLightBehavior: THighLightBehavior;
    

    (otherwise you get compilation error at “HighlightBehavior.EffectColorField := ...” because the class TCastleBehavior doesn’t contain the EffectColorField field).

  2. Actually HighLightBehavior: THighLightBehavior; should not be declared in TViewMain, it should be local, only declared and used when we create it.

  3. Then the code compiles but crashes – because you try to use the EffectColorField: TSFVec3f; declared in class TViewMain. This has to be removed, EffectColorField should only be inside THighLightBehavior.

    This required a bit of rework – you need to have 1 THighLightBehavior instance for each of your scenes.

    So you cannot have

    HighlightBehavior := THighlightBehavior.Create(HighlightBehavior);
    HighlightBehavior.EffectColorField := EffectColorField;
    
    Scene1.AddBehavior(HighlightBehavior);
    Scene2.AddBehavior(HighlightBehavior);
    

    See how I remade the logic to create the effect, and THighlightBehavior instance, and attach it to scene in procedure AddColorEffect(const Scene: TCastleScene).

  4. Then we can remove pieces of old code.

    • TViewMain.LightupColor is no longer necessary.

    • These lines are no longer necessary, we handle highlight in more generic way using behaviors:

  if Viewport.TransformUnderMouse = Scene1 then
    EffectColorField.Send(Vector3(1.3, 1.3, 1.3))
  else
    EffectColorField.Send(Vector3(1, 1, 1));


  if Viewport.TransformUnderMouse = Scene2 then
    EffectColorField.Send(Vector3(1.3, 1.3, 1.3))
else
    EffectColorField.Send(Vector3(1, 1, 1));

Please see

– this is my minimal modification to your example, and it compiles and works nicely – any of 2 characters get highlighted when mouse is over them :slight_smile:

Do compare my version with your version to see all changes I did :slight_smile: If you don’t have your file in a version control, you can compare files e.g. using http://meldmerge.org/ .

This was a tough one for me so many thanks for sorting this out!

Then I tried to put procedure AddColorEffect in a separate one, out of the Start procedure because my character scenes are created in code procedures during game play and not at start. (it was just in the testcase).
I got an error on ‘Self’ with HighLightBehavior := THighlightBehavior.Create(Self); (Self not found).
When I changed it to HighLightBehavior := THighlightBehavior.Create(HighLightBehavior ); I got a note but the code compiled then crashed. (obviously because HighlightBehavior was not created).
Then I changed it in procedure TViewMain.AddColorEffect and then it worked. :slight_smile:
So as procedure AddColorEffect without putting it in TViewMain did not work, I don’t know why, it probably could not find the other references to HighlightBehavior because they were part of TViewMain?

I am happy this works now, thanks again!

1 Like

When you create a component (any class descending from Pascal TComponent, this includes a lot of CGE classes or classes derived from them, like TCastleBehavior and THighlightBehavior) then you need to provide “owner” instance as the first parameter.

SomeInstance := TSomeClass.Create(SomeOwner);

The SomeOwner will be responsible for freeing the SomeInstance automatically, so that freeing SomeOwner will also free SomeInstance.

The article Modern Object Pascal Introduction for Programmers | Castle Game Engine may be helpful, from the paragraph

To avoid the need to explicitly free the instance, one can also use the TComponent feature of “ownership”

What should be the owner? There are really many possible options.

  • You can pass Self, if you are within the method of another component. If you are within a method of some view (like TViewMain) then Self means the current view instance and is equivalent to the global variable ViewMain.

  • If you are not implementing a view method (but e.g. a simple procedure), you could use global variable ViewMain. Or pass the owner as a special parameter of the procedure, e.g.

    procedure MyProcedureToCreateSomething(const SomeOwner: TComponent);
    begin
      SomeInstance := TSomeClass.Create(SomeOwner);
    end;
    

    and use it like MyProcedureToCreateSomething(Self) from another method. See Modern Object Pascal Introduction for Programmers | Castle Game Engine for a (very terse, I admit) description of Self.

  • In a view, you can also use FreeAtStop as the owner, to make something freed when the view stops.

  • Note that you’re not forced to provide an owner. You can always use just nil. But be sure to free the SomeInstance yourself then (by e.g. FreeAndNil(SomeInstance) at some point) otherwise you will have a memory leak.

    SomeInstance := TSomeClass.Create(nil); // Owner is nil, we will free it manually
    try
     SomeInstance.DoSomething;
    finally
      FreeAndNil(SomeInstance);
    end;