Convenient way to determine exact point under mouse

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;