Changing drawing order of Transforms

How can I change the drawing order of transforms so that the most ‘in front’ transform is always placed before the others when I move them around?
I guess that the ‘lower’ the Translation.Y value, the ‘higher’ the Translation.Z has to be.
I looked in the API and found ‘sortbacktofront2D’ but dont’t know if this is what I need and how to use it.

Yes, SortBackToFront2D is what you need. Usually you can call it from editor,

Note that in most cases in CGE, the order of TCastleTransform doesn’t matter, e.g. it doesn’t matter for things without blending. And even with blending, it should not matter… but for now it does. The longer explanation is on Blending · castle-engine/castle-engine Wiki · GitHub . But some day, the need for SortBackToFront2D should disappear.

I don’t understand how to use this, I see no examples in code.
I have run the dragon example but the dragon is always behind the trees, even when I put it 'before it / down the screen.
(see wings).
Translation.Z of the trees are set ‘higher’ in the editor so they are always displayed in front of the dragon.

While running my code must I put a SortBackToFront2D with every Stateplay.Update?
I have a PlayerScene (transform with children scenes) and an array of NPC[I] transform (with scenes)
Do I have to put them (and how) in a list?

First you have to set correct Z positions of the scenes – the Z position determines the order (what is in front).

Then you should call SortBackToFront2D – it sorts, based on set Z values.

In the simple case, if your hierarchy under Viewport.Items is “flat”, just call Viewport.Items.SortBackToFront2D. In general case, call it at the parent transformation that needs sorting, like MyTransform.SortBackToFront2D.

When to call it? It is only necessary when you change Z values of some TCastleTransform/TCastleScene (and you use blending on them). In your case, you likely only need to call it once after setting up the location or adding all NPCs. (But if in doubt, you can call it more often, it should not hurt performance in simple cases.)

The text

It may even make sense to call this method every frame (like in every
@link(TCastleWindowBase.OnUpdate)),
if you move or otherwise change the objects (changing their bounding boxes),
or if the CameraPosition may change (note that CameraPosition is only
relevant if BlendingSort = bs3D).

… is a general description that also applies to 3D.

I improved API docs now, to make the explanation more clear:

    { Sort objects back-to-front @italic(right now)
      following one of the blending sorting algorithms.
      Only the immediate list items are reordered,
      looking at their bounding boxes (the sorting is not recursive).

      Calling this method makes sense if you use
      @url(https://github.com/castle-engine/castle-engine/wiki/Blending blending)
      and multiple partially-transparent objects may
      be visible at the same place on the screen.
      Sorting avoids artifacts when rendering.

      @unorderedList(
        @item(
          In general, you should call this method whenever the correct back-to-front order
          of objects (with respect to the current camera) changes.
        )
        @item(
          In 3D, it may make sense to call this method even every frame
          (like in every @link(TCastleWindowBase.OnUpdate)).
          Call it if you move or otherwise change the objects (changing their bounding boxes),
          or if the CameraPosition may change (note that CameraPosition is only
          relevant if BlendingSort = bs3D).
        )
        @item(
          In 2D, it is a bit simpler.
          You typically need to call this only when some objects' Z value changed
          (making this object move behind / in front of some other object),
          or when new object is added.
          You don't need to call this method when camera changes or when object's XY position
          changes, as they don't affect the order.
        )
      )

      Note that this doesn't take care of sorting the shapes
      within the scenes. For this, you should set
      @link(TCastleRenderOptions.BlendingSort Scene.RenderOptions.BlendingSort)
      to a value like bs3D, to keep it sorted.
      It is the default now, so you typically don't need to worry about it.

      See the TBlendingSort documentation for the exact specification
      of sorting algorithms. Using BlendingSort = bsNone does nothing.

      @seealso SortBackToFront2D }
    procedure SortBackToFront(const BlendingSort: TBlendingSort;
      const CameraPosition: TVector3);

    { Sort objects back-to-front @italic(right now)
      following the 2D blending sorting algorithm.
      See @link(SortBackToFront) for documentation, this method
      is only a shortcut for @code(SortBackToFront(bs2D, TVector3.Zero)).

      @seealso SortBackToFront }
    procedure SortBackToFront2D;

I still don’t get it (sorry).
All Transforms that are on screen should be sorted on their Translation.Y value.
I have a lot of walking characters around so on update or collision their drawing order has to be ‘the lower the Y the higher the Z value’.
In my 'old 'code when I was using sprites instead of Transforms I had this sort routine (with help from Eugene):

Box: array[1..30] of TPlayer;
SpriteList := TSpriteList.Create(false);
try
  if Player.Present then SpriteList.Add(Player);

  if Location.NPC_Presence then
  begin
    For Count := 1 to Location.NPC_Total do
    begin
      SpriteList.Add(Box[Count]);
     end;
 end;

function CompareSprites(constref Left, Right: TPlayer): Integer;
 begin
   Result := Sign(Right.CurrentSprite.Y - Left.CurrentSprite.Y);
 end;

  SpriteList.Sort(TSpriteComparer.Construct(@CompareSprites));

  for M in SpriteList do
  M.CurrentSprite.Draw(M.Rect);

Ah, so you want the Y coordinate (that determines the vertical position of the sprite) to also determine the front-to-back order?

In this case, just set Z coordinate to be equal to Y or -Y (depending on whether you want higher things to be closer, or further away, from camera). IOW, assuming you have a hierarchy like this:

MyViewport.Items:
- Item1 with Translation (1, 2, whatever)
- Item2 with Translation (3, 4, whatever)
- Item3 with Translation (5, 6, whatever)

do something like this

{ set all Z coordinates to be equal to -Y, leave XY coordinates unchanged }
Item1.Translation := Vector3(Item1.Translation.XY, -Item1.Translation.Y);
Item2.Translation := Vector3(Item2.Translation.XY, -Item2.Translation.Y);
Item3.Translation := Vector3(Item3.Translation.XY, -Item3.Translation.Y);
MyViewport.Items.SortBackToFront2D;

Of course, ideally you can do the first 3 lines above by some for loop:

for I := 0 to NpcsCount - 1 do
begin
  NpcTransform := Npc[I].Transform;
  NpcTransform.Translation := Vector3(NpcTransform.Translation.XY, -NpcTransform.Translation.Y);
end;
MyViewport.Items.SortBackToFront2D;

Yes, that is it!
The -Item.Translation alone does already exactly what is needed; it even works without SortBackToFront2D, but then again, this is on loading. But I can already move the Player between the NPC characters.
Next step is to scale them; guess that’s the same with the minus sign Y for the - Scale.
Keep you posted.

Thanks on behalf of the crew of Rescue Island.
:wink:

Update:
When Y > 0 their Z value becomes negative and the sprites disappear from screen.
So I guess I have to give the background scene a big negative Z value?

Update:
I succeeded in adding Scale, but I am rather bad at Mathematics.
I divided the Y scale with 500.
The scaling is too high (too rough) between the characters or compared to the height of the background scene.
(NPCinfo.Y is an integer that is read from a ‘character database’).
How can I get scaling something less over the height of the background? (I hope I express myself well)
:slight_smile:

NPCScene[NR].Translation := Vector3(NPCinfo[I].X, NPCinfo[I].Y, -NPCinfo[I].Y);
NPCScene[NR].Scale := Vector3(-NPCinfo[I].Y/500, -NPCinfo[I].Y/500, -NPCinfo[I].Y/500);
MainViewport.Items.SortBackToFront2D;

Do I understand the problem right, that once you started adding scale, it messed up the Z coordinates of your models, thus changing their order with respect to the background?

If this is the problem → then the solution is simple. You do not need to scale in Z. If you just want to make your sprite larger (but keep it at the same position in Z) then assign scale like MySprite.Scale := Vector3(100, 100, 1). This makes sprite 100x larger in XY, but keeps the Z unchanged.

No, that is not quite what I meant, well a bit.
:slight_smile:
The scaling is ‘locked’ to the Translation.Y but then it’s too ‘quick.’
As you can see in the picture the most front character is way too big and the bearded man behind the women is too small. Their scaling should be smaller.

I have now made this, that is sort of okay but I want scaling to be 1 when the
character is all way down (when bottom of the TransformY is bottom screen, about - 400) and then with every +Y it should add or decrease size with 0.003.
I think I need a formula for this.

 if Player.WalkFront = True then
 begin
    Player.Y := Player.Y - 2;
    PlayerTransform.Translation := Vector3(Player.X, Player.Y, -Player.Y);
    Player.S := Player.S + 0.003;
    PlayerTransform.Scale := Vector3(Player.S, Player.S, Player.S);
 end;    

````