PositionToUpPlane

I may, most likely, be overcomplicating this / missing something - soz in advance if so…

I’ve got a scene that is a plane with Y = 0 - it’s the “ground”

I want to find out what the X + Z are when the user clicks on the screen for my ground plane (no hit or co-ordinates)

We’ve got PositionToWorldPlane, I guess that’s almost what I need apart from using YPlane rather than ZPlane.

PositionToUpPlane would tell me where a ray from camera intersects YPlane giving me (X, YPlane, Z) - YPlane = 0 being the most interesting of course.

The ground plane may have a transform (most likely) but I know those so applying the inverse transform to (X, YPlane, Z) should now tell me where on the grid, if at all, my mouse click was.

You can easily create your own PositionToUpPlane :slight_smile: The PositionToWorldPlane is just a handy shortcut, underneath we just make a collision betwen ray <-> plane, and we have a rich API for making various collisions :slight_smile: The PositionToWorldPlace is implemented like this:

function TCastleViewport.PositionToWorldPlane(const Position: TVector2;
  const ScreenCoordinates: Boolean;
  const PlaneZ: Single; out PlanePosition: TVector3): Boolean;
var
  R: TFloatRectangle;
  ScreenPosition: TVector2;
  RayOrigin, RayDirection: TVector3;
begin
  R := RenderRect;

  if ScreenCoordinates then
    ScreenPosition := Position
  else
    ScreenPosition := Position * UIScale + R.LeftBottom;

  Camera.CustomRay(R, ScreenPosition, FProjection, RayOrigin, RayDirection);

  Result := TrySimplePlaneRayIntersection(PlanePosition, 2, PlaneZ,
    RayOrigin, RayDirection);
end;

You can change it to implement your own method, that takes additional TCastleViewport instance as a parameter. I think everything accessed there is public, except FProjection – just use Projection for it right now. (It is deprecated, but I will undeprecate it soon as I see it has valid uses :slight_smile: )

You want to tweak the TrySimplePlaneRayIntersection call at the end. Above it says “2, PlaneZ”, which means that a plane has constant Z, equal to PlaneZ argument. You will want to change it to “1, PlaneY”, where 1 means that Y coordinate of the plane is constant. See TrySimplePlaneRayIntersection docs.

To perform a more generic ray <-> plane collision test, we also have TryPlaneRayIntersection routine.

Knowing X + Z for a given Y works nicely

I ended up with this…

function PositionToUpPlane(const Viewport: TCastleViewport;
  const Position: TVector2; const ScreenCoordinates: Boolean;
  const PlaneY: Single; out PlanePosition: TVector3): Boolean;
var
  R: TFloatRectangle;
  ScreenPosition: TVector2;
  RayOrigin, RayDirection: TVector3;
begin
  R := Viewport.RenderRect;

  if ScreenCoordinates then
    ScreenPosition := Position
  else
    ScreenPosition := Position * Viewport.UIScale + R.LeftBottom;

  Viewport.Camera.CustomRay(R, ScreenPosition, Viewport.Projection, RayOrigin, RayDirection);

  Result := TrySimplePlaneRayIntersection(PlanePosition, 1, PlaneY,
    RayOrigin, RayDirection);
end;

I get the depreciation warning of course…

It works really well so far - I’ll go and try break it now, rotating the scene will screw things up nicely I’m sure…:slight_smile:

As a side-effect of another task, I added to CGE TCastleViewport.PositionToRay that allows to implement such methods easier. The above can be now simplified to:

function PositionToUpPlane(const Viewport: TCastleViewport;
  const Position: TVector2; const ScreenCoordinates: Boolean;
  const PlaneY: Single; out PlanePosition: TVector3): Boolean;
var
  RayOrigin, RayDirection: TVector3;
begin
  Viewport.Camera.PositionToRay(Position, ScreenCoordinates, RayOrigin, RayDirection);
  Result := TrySimplePlaneRayIntersection(PlanePosition, 1, PlaneY,
    RayOrigin, RayDirection);
end;

IOW, PositionToRay under the hood does Camera.CustomRay with proper arguments.