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
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 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 )
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…
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.