camutils: add support for Builder pattern.

This makes the upcoming Java bindings simpler and improves parity with
existing API conventions.

Note that camutils does not depend on filament and therefore cannot use
BuilderBase.

Note that the Manipulator Builder state is public which breaks with
convention but makes implementation simple and allows C++ clients to
continue using modern initializer syntax if they wish.
This commit is contained in:
Philip Rideout
2020-01-14 15:26:22 -08:00
parent c5fa6fe7f2
commit 2854c0627d
4 changed files with 150 additions and 55 deletions
+49 -35
View File
@@ -43,9 +43,9 @@ enum class Fov { VERTICAL, HORIZONTAL };
* CameraManipulator* manip;
*
* void init() {
* manip = CameraManipulator::create(camutils::Mode::ORBIT, {
* .viewport = {1024, 768}
* });
* manip = CameraManipulator::Builder()
* .viewport(1024, 768)
* .build(camutils::Mode::ORBIT);
* }
*
* void onMouseDown(int x, int y) {
@@ -84,41 +84,55 @@ public:
/** Optional raycasting function to enable perspective-correct panning. */
typedef bool (*RayCallback)(const vec3& origin, const vec3& dir, FLOAT* t, void* userdata);
/**
* User-controlled configuration that is never changed by the manipulator.
*
* This is a Builder expressed as a POD structure. Many of the fields fall back to reasonable
* default values if they are zero-filled.
*/
/** Builder state, direct access is allowed but Builder methods are preferred. **/
struct Config {
int viewport[2]; //! Width and height of the viewing area
vec3 targetPosition; //! World-space position of interest, defaults to (0,0,0)
vec3 upVector; //! Orientation for the home position, defaults to (0,1,0)
FLOAT zoomSpeed; //! Multiplied with scroll delta, defaults to 0.01
// Orbit mode properties
vec3 orbitHomePosition; //! Initial eye position in world space, defaults to (0,0,1)
vec2 orbitSpeed; //! Multiplied with viewport delta, defaults to 0.01
// Map mode properties
Fov fovDirection; //! The FOV axis that's held constant when the viewport changes
FLOAT fovDegrees; //! The full FOV (not the half-angle)
FLOAT farPlane; //! The distance to the far plane
vec2 mapExtent; //! The ground plane size used to compute the home position
FLOAT mapMinDistance; //! Constrains the zoom-in level
// Raycast properties
vec4 groundPlane; //! Plane equation used as a raycast fallback
RayCallback raycastCallback; //! Raycast function for accurate grab-and-pan
void* raycastUserdata; //! Callback userdata for closures
int viewport[2];
vec3 targetPosition;
vec3 upVector;
FLOAT zoomSpeed;
vec3 orbitHomePosition;
vec2 orbitSpeed;
Fov fovDirection;
FLOAT fovDegrees;
FLOAT farPlane;
vec2 mapExtent;
FLOAT mapMinDistance;
vec4 groundPlane;
RayCallback raycastCallback;
void* raycastUserdata;
};
/**
* Creates a new camera manipulator, either ORBIT or MAP.
*
* Clients can simply use "delete" to destroy the manipulator.
*/
static Manipulator* create(Mode mode, const Config& props);
struct Builder {
// Common properties
Builder& viewport(int width, int height); //! Width and height of the viewing area
Builder& targetPosition(FLOAT x, FLOAT y, FLOAT z); //! World-space position of interest, defaults to (0,0,0)
Builder& upVector(FLOAT x, FLOAT y, FLOAT z); //! Orientation for the home position, defaults to (0,1,0)
Builder& zoomSpeed(FLOAT val); //! Multiplied with scroll delta, defaults to 0.01
// Orbit mode properties
Builder& orbitHomePosition(FLOAT x, FLOAT y, FLOAT z); //! Initial eye position in world space, defaults to (0,0,1)
Builder& orbitSpeed(FLOAT x, FLOAT y); //! Multiplied with viewport delta, defaults to 0.01
// Map mode properties
Builder& fovDirection(Fov fov); //! The axis that's held constant when viewport changes
Builder& fovDegrees(FLOAT degrees); //! The full FOV (not the half-angle)
Builder& farPlane(FLOAT distance); //! The distance to the far plane
Builder& mapExtent(FLOAT worldWidth, FLOAT worldHeight); //! The ground size for computing home position
Builder& mapMinDistance(FLOAT mindist); //! Constrains the zoom-in level
// Raycast properties
Builder& groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d); //! Plane equation used as a raycast fallback
Builder& raycastCallback(RayCallback cb, void* userdata); //! Raycast function for accurate grab-and-pan
/**
* Creates a new camera manipulator, either ORBIT or MAP.
*
* Clients can simply use "delete" to destroy the manipulator.
*/
Manipulator* build(Mode mode);
Config details;
};
virtual ~Manipulator() = default;
+85 -4
View File
@@ -26,12 +26,93 @@ using namespace filament::math;
namespace filament {
namespace camutils {
template <typename FLOAT> Manipulator<FLOAT>*
Manipulator<FLOAT>::create(Mode mode, const Manipulator<FLOAT>::Config& props) {
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::viewport(int width, int height) {
details.viewport[0] = width;
details.viewport[1] = height;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::targetPosition(FLOAT x, FLOAT y, FLOAT z) {
details.targetPosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::upVector(FLOAT x, FLOAT y, FLOAT z) {
details.upVector = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::zoomSpeed(FLOAT val) {
details.zoomSpeed = val;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::orbitHomePosition(FLOAT x, FLOAT y, FLOAT z) {
details.orbitHomePosition = {x, y, z};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::orbitSpeed(FLOAT x, FLOAT y) {
details.orbitSpeed = {x, y};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::fovDirection(Fov fov) {
details.fovDirection = fov;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::fovDegrees(FLOAT degrees) {
details.fovDegrees = degrees;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::farPlane(FLOAT distance) {
details.farPlane = distance;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::mapExtent(FLOAT worldWidth, FLOAT worldHeight) {
details.mapExtent = {worldWidth, worldHeight};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::mapMinDistance(FLOAT mindist) {
details.mapMinDistance = mindist;
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d) {
details.groundPlane = {a, b, c, d};
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::raycastCallback(RayCallback cb, void* userdata) {
details.raycastCallback = cb;
details.raycastUserdata = userdata;
return *this;
}
template <typename FLOAT>
Manipulator<FLOAT>* Manipulator<FLOAT>::Builder::build(Mode mode) {
if (mode == Mode::MAP) {
return new MapManipulator<FLOAT>(mode, props);
return new MapManipulator<FLOAT>(mode, details);
}
return new OrbitManipulator<FLOAT>(mode, props);
return new OrbitManipulator<FLOAT>(mode, details);
}
template <typename FLOAT>
+14 -14
View File
@@ -39,14 +39,14 @@ TEST_F(CamUtilsTest, Orbit) {
float3 eye, targetPosition, up;
CamManipulator* orbit = CamManipulator::create(camutils::Mode::ORBIT, {
.viewport = {256, 256},
.targetPosition = {0, 0, 0},
.upVector = {0, 1, 0},
.zoomSpeed = 0.01,
.orbitHomePosition = {0, 0, 4},
.orbitSpeed = {1, 1},
});
CamManipulator* orbit = CamManipulator::Builder()
.viewport(256, 256)
.targetPosition(0, 0, 0)
.upVector(0, 1, 0)
.zoomSpeed(0.01)
.orbitHomePosition(0, 0, 4)
.orbitSpeed(1, 1)
.build(camutils::Mode::ORBIT);
orbit->getLookAt(&eye, &targetPosition, &up);
EXPECT_VEC_EQ(eye, 0, 0, 4);
@@ -69,12 +69,12 @@ TEST_F(CamUtilsTest, Map) {
float3 eye, targetPosition, up;
CamManipulator* map = CamManipulator::create(camutils::Mode::MAP, {
.viewport = {256, 256},
.targetPosition = {0, 0, 0},
.zoomSpeed = 0.01,
.orbitHomePosition = {0, 0, 4},
});
CamManipulator* map = CamManipulator::Builder()
.viewport(256, 256)
.targetPosition(0, 0, 0)
.zoomSpeed(0.01)
.orbitHomePosition(0, 0, 4)
.build(camutils::Mode::MAP);
delete map;
}
+2 -2
View File
@@ -522,8 +522,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
// set-up the camera manipulators
camutils::Mode mode = camutils::Mode::ORBIT;
mMainCameraMan = CameraManipulator::create(mode, { .targetPosition = {0, 0, -4} });
mDebugCameraMan = CameraManipulator::create(mode, { .targetPosition = {0, 0, -4} });
mMainCameraMan = CameraManipulator::Builder().targetPosition(0, 0, -4).build(mode);
mDebugCameraMan = CameraManipulator::Builder().targetPosition(0, 0, -4).build(mode);
mMainView->setCamera(mMainCamera);
mMainView->setCameraManipulator(mMainCameraMan);