Files
2025-09-18 17:55:52 -04:00

153 lines
5.7 KiB
C++

#include "GfxBase/ViewportBillboarder.h"
#include "V8DataModel/Filters.h"
#include "V8DataModel/Camera.h"
#include "V8World/World.h"
#include "V8World/ContactManager.h"
namespace RBX {
ViewportBillboarder::ViewportBillboarder()
:guiScreenSize(NULL)
,alwaysOnTop(false)
,screenOffset2D(Vector2::zero())
{}
ViewportBillboarder::ViewportBillboarder(
const Vector3& partExtentRelativeOffset,
const Vector3& partStudsOffset,
const Vector2& billboardSizeRelativeOffset,
const UDim2& billboardSize,
const Vector2* guiScreenSize)
:partExtentRelativeOffset(partExtentRelativeOffset)
,partStudsOffset(partStudsOffset)
,billboardSizeRelativeOffset(billboardSizeRelativeOffset)
,billboardSize(billboardSize)
,guiScreenSize(guiScreenSize)
,alwaysOnTop(false)
,screenOffset2D(Vector2::zero())
{}
Vector2 ViewportBillboarder::getScreenOffset(const Rect2D& parentviewport, const RBX::Camera& camera, const CoordinateFrame& desiredModelView)
{
const CoordinateFrame& cameraFrame = camera.coordinateFrame();
Vector3 projectedTranslation = camera.project((cameraFrame * desiredModelView).translation);
return Math::roundVector2(projectedTranslation.xy());
}
void ViewportBillboarder::update(const Rect2D& parentviewport, const Camera& camera, Vector3 partSize, CoordinateFrame partCFrame)
{
Vector3 halfSize = partSize/2;
Extents cameraSpaceExtents;
// calculate extents in camera space
for(int a = 0; a < 8; a++)
{
// go through all permutations (use bits of counter)
Vector3 extent((a & 4) ? halfSize.x : -halfSize.x,
(a & 2) ? halfSize.y : -halfSize.y,
(a & 1) ? halfSize.z : -halfSize.z);
Vector3 pextent = camera.coordinateFrame().pointToObjectSpace(partCFrame.pointToWorldSpace(extent));
cameraSpaceExtents.expandToContain(pextent);
}
Vector3 relativeOffsetInCameraSpace = (partExtentRelativeOffset + Vector3::one()) * 0.5f * cameraSpaceExtents.size() + cameraSpaceExtents.min();
Vector3 billboardCenterInCameraSpace = relativeOffsetInCameraSpace + partStudsOffset;
Vector3 billboardCenterInWorldSpace = camera.coordinateFrame().pointToWorldSpace(billboardCenterInCameraSpace);
Vector3 billboardCenterInProjSpace = camera.project(billboardCenterInWorldSpace);
if(billboardCenterInProjSpace.z > 0 && billboardCenterInProjSpace.z < 1000)
{
visibleAndValid = true;
}
else
{
visibleAndValid = false;
return;
}
float pixelsPerStud = billboardCenterInProjSpace.z;
Vector2 studsPerPixel(1.f/ pixelsPerStud, 1.f/ pixelsPerStud); // undo the perspective effect of finding extents in screenspace.
// -1 : to UI coordinates (0,0 upper left, )
Vector2 billboardSizeInStuds = (billboardSize * (Vector2::one()*pixelsPerStud)) * studsPerPixel;
if(guiScreenSize)
viewport = Rect2D(*guiScreenSize);
else
viewport = Rect2D(billboardSizeInStuds * pixelsPerStud);
Vector2 UIToCameraSpaceScaler(billboardSizeInStuds/ viewport.wh());
billboardCenterInCameraSpace += Vector3(billboardSizeRelativeOffset * billboardSizeInStuds, 0);
CoordinateFrame desiredModelView;
desiredModelView.translation = billboardCenterInCameraSpace - Vector3(billboardSizeInStuds.x * 0.5f, billboardSizeInStuds.y * -0.5f, 0);
desiredModelView.rotation.set(UIToCameraSpaceScaler.x, 0,0,0,UIToCameraSpaceScaler.y, 0,0,0, 1);
if (alwaysOnTop)
screenOffset2D = getScreenOffset(parentviewport, camera, desiredModelView);
cframe = camera.coordinateFrame() * desiredModelView;
}
bool ViewportBillboarder::hitTest(const Vector2int16& mousePosition, const Vector2int16& windowSize,
RBX::Workspace* workspace,
Vector2& billboardMousePosition)
{
const RBX::Camera& camera = *(workspace->getConstCamera());
Vector3 x0y0Screen, x1y1Screen;
if (alwaysOnTop)
{
x0y0Screen = Vector3(screenOffset2D, 0);
x1y1Screen = Vector3(screenOffset2D + viewport.wh(), 0);
}
else
{
Vector3 x0y0World = cframe.pointToWorldSpace(Vector3(viewport.x0(), -viewport.y0(),0));
Vector3 x1y1World = cframe.pointToWorldSpace(Vector3(viewport.x1(), -viewport.y1(),0));
x0y0Screen = camera.project(x0y0World);
x1y1Screen = camera.project(x1y1World);
}
Extents extents;
extents.expandToContain(Vector3(x0y0Screen.xy(),0));
extents.expandToContain(Vector3(x1y1Screen.xy(),0));
if(extents.contains(Vector3(mousePosition.x, mousePosition.y, 0)))
{
billboardMousePosition = Vector2((mousePosition.x - x0y0Screen.x)/(x1y1Screen.x - x0y0Screen.x)*viewport.width(), (mousePosition.y - x0y0Screen.y)/(x1y1Screen.y - x0y0Screen.y)*viewport.height());
if (alwaysOnTop) {
return true;
}
ContactManager& contactManager = *workspace->getWorld()->getContactManager();
RbxRay unitRay = camera.worldRay(mousePosition.x, mousePosition.y);
RbxRay searchRay = RbxRay::fromOriginAndDirection(unitRay.origin(), unitRay.direction() * 2048);
Vector3 partHitPointWorld;
FilterInvisibleNonColliding invisibleNonCollidingObjectsFilter; // invisible & non-colliding parts don't block mouse-clicks
if(contactManager.getHit( searchRay, NULL, &invisibleNonCollidingObjectsFilter, partHitPointWorld) == NULL)
{
//We didn't hit anything
return true;
}
Vector3 hitPointWorld = cframe.pointToWorldSpace(Vector3(billboardMousePosition.x, billboardMousePosition.y, 0));
Vector3 hitPointScreen = camera.project(hitPointWorld);
Vector3 partHitPointScreen = camera.project(partHitPointWorld);
if(partHitPointScreen.z < hitPointScreen.z)
{
return true;
}
return false;
}
return false;
}
}