From 5406520e7075a9fd9d1a245df0dec3d356863370 Mon Sep 17 00:00:00 2001 From: xTree Date: Sat, 13 Dec 2025 15:18:31 +0100 Subject: [PATCH 1/8] fix orthogonal issue. --- .polyscope.ini | 7 +++ src/render/opengl/shaders/common.cpp | 43 +++++++++++++++++++ .../opengl/shaders/cylinder_shaders.cpp | 6 ++- src/render/opengl/shaders/sphere_shaders.cpp | 26 +++++++++-- src/render/opengl/shaders/vector_shaders.cpp | 8 ++-- 5 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 .polyscope.ini diff --git a/.polyscope.ini b/.polyscope.ini new file mode 100644 index 00000000..e13a86bd --- /dev/null +++ b/.polyscope.ini @@ -0,0 +1,7 @@ +{ + "uiScale": 1.0, + "windowHeight": 1352, + "windowPosX": 1283, + "windowPosY": 35, + "windowWidth": 2554 +} diff --git a/src/render/opengl/shaders/common.cpp b/src/render/opengl/shaders/common.cpp index fd33d2f8..86b0336c 100644 --- a/src/render/opengl/shaders/common.cpp +++ b/src/render/opengl/shaders/common.cpp @@ -179,6 +179,45 @@ vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 return eyePos.xyz / eyePos.w; } +// Detect if the projection matrix is orthographic. +// In an orthographic projection matrix, projMat[3][3] == 1.0 and projMat[2][3] == 0.0. +// In a perspective projection matrix, projMat[3][3] == 0.0 and projMat[2][3] == -1.0. +bool isOrthoProjection(mat4 projMat) { + return abs(projMat[3][3] - 1.0) < 0.001; +} + +// Build ray start and ray direction for fragment-based raycasting. +// For perspective projection: rays originate from the camera (origin) and point towards the fragment view position. +// For orthographic projection: rays are parallel, all pointing in -Z direction in view space, +// starting from the fragment's XY position. +void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, + out vec3 rayStart, out vec3 rayDir) { + + if (isOrthoProjection(projMat)) { + // Orthographic: parallel rays pointing in -Z direction + // Convert fragment screen position to NDC + vec2 ndcXY = ((2.0 * fragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1.0; + + // Unproject to view space at near plane (NDC z = -1) and far plane (NDC z = 1) + // For orthographic, we just need the XY in view space + vec4 ndcNear = vec4(ndcXY, -1.0, 1.0); + vec4 viewNear = invProjMat * ndcNear; + viewNear /= viewNear.w; + + vec4 ndcFar = vec4(ndcXY, 1.0, 1.0); + vec4 viewFar = invProjMat * ndcFar; + viewFar /= viewFar.w; + + rayStart = viewNear.xyz; + rayDir = normalize(viewFar.xyz - viewNear.xyz); + } else { + // Perspective: rays from origin through fragment position + vec3 viewPos = fragmentViewPosition(viewport, depthRange, invProjMat, fragCoord); + rayStart = vec3(0.0, 0.0, 0.0); + rayDir = viewPos; + } +} + float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint) { vec4 clipPos = projMat * vec4(viewPoint, 1.); // only actually need one element of this result, could save work float z_ndc = clipPos.z / clipPos.w; @@ -206,6 +245,10 @@ bool raySphereIntersection(vec3 rayStart, vec3 rayDir, vec3 sphereCenter, float } } +)" +// Split the raw string literal to avoid compiler string length limits +R"( + bool rayPlaneIntersection(vec3 rayStart, vec3 rayDir, vec3 planePos, vec3 planeDir, out float tHit, out vec3 pHit, out vec3 nHit) { float num = dot(planePos - rayStart, planeDir); diff --git a/src/render/opengl/shaders/cylinder_shaders.cpp b/src/render/opengl/shaders/cylinder_shaders.cpp index 601c7937..525d9d78 100644 --- a/src/render/opengl/shaders/cylinder_shaders.cpp +++ b/src/render/opengl/shaders/cylinder_shaders.cpp @@ -164,6 +164,7 @@ R"( float LARGE_FLOAT(); vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); + void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); bool rayTaperedCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, vec3 cylTip, float cylRadTail, float cylRadTip, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -173,7 +174,8 @@ R"( { // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); - vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); + vec3 rayStart, rayDir; + buildRayForFragment(u_viewport, depthRange, u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); float tipRadius = u_radius; @@ -184,7 +186,7 @@ R"( float tHit; vec3 pHit; vec3 nHit; - rayTaperedCylinderIntersection(vec3(0., 0., 0), viewRay, tailView, tipView, tailRadius, tipRadius, tHit, pHit, nHit); + rayTaperedCylinderIntersection(rayStart, rayDir, tailView, tipView, tailRadius, tipRadius, tHit, pHit, nHit); if(tHit >= LARGE_FLOAT()) { discard; } diff --git a/src/render/opengl/shaders/sphere_shaders.cpp b/src/render/opengl/shaders/sphere_shaders.cpp index f4f1eb59..9b163cae 100644 --- a/src/render/opengl/shaders/sphere_shaders.cpp +++ b/src/render/opengl/shaders/sphere_shaders.cpp @@ -73,6 +73,7 @@ R"( ${ GEOM_DECLARATIONS }$ void buildTangentBasis(vec3 unitNormal, out vec3 basisX, out vec3 basisY); + bool isOrthoProjection(mat4 projMat); void main() { @@ -82,7 +83,14 @@ R"( // Construct the 4 corners of a billboard quad, facing the camera // Quad is shifted pointRadius toward the camera, otherwise it doesn't actually necessarily // cover the full sphere due to perspective. - vec3 dirToCam = normalize(-gl_in[0].gl_Position.xyz); + vec3 dirToCam; + if (isOrthoProjection(u_projMatrix)) { + // Orthographic: camera direction is constant +Z in view space + dirToCam = vec3(0.0, 0.0, 1.0); + } else { + // Perspective: camera is at origin, direction varies per point + dirToCam = normalize(-gl_in[0].gl_Position.xyz); + } vec3 basisX; vec3 basisY; buildTangentBasis(dirToCam, basisX, basisY); @@ -142,6 +150,7 @@ R"( float LARGE_FLOAT(); vec3 lightSurfaceMat(vec3 normal, vec3 color, sampler2D t_mat_r, sampler2D t_mat_g, sampler2D t_mat_b, sampler2D t_mat_k); vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); + void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); bool raySphereIntersection(vec3 rayStart, vec3 rayDir, vec3 sphereCenter, float sphereRad, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -151,7 +160,8 @@ R"( { // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); - vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); + vec3 rayStart, rayDir; + buildRayForFragment(u_viewport, depthRange, u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); float pointRadius = u_pointRadius; ${ SPHERE_SET_POINT_RADIUS_FRAG }$ @@ -160,7 +170,7 @@ R"( float tHit; vec3 pHit; vec3 nHit; - bool hit = raySphereIntersection(vec3(0., 0., 0), viewRay, sphereCenterView, pointRadius, tHit, pHit, nHit); + bool hit = raySphereIntersection(rayStart, rayDir, sphereCenterView, pointRadius, tHit, pHit, nHit); if(tHit >= LARGE_FLOAT()) { discard; } @@ -258,6 +268,7 @@ R"( ${ GEOM_DECLARATIONS }$ void buildTangentBasis(vec3 unitNormal, out vec3 basisX, out vec3 basisY); + bool isOrthoProjection(mat4 projMat); void main() { @@ -265,7 +276,14 @@ R"( ${ SPHERE_SET_POINT_RADIUS_GEOM }$ // Construct the 4 corners of a billboard quad, facing the camera - vec3 dirToCam = normalize(-gl_in[0].gl_Position.xyz); + vec3 dirToCam; + if (isOrthoProjection(u_projMatrix)) { + // Orthographic: camera direction is constant +Z in view space + dirToCam = vec3(0.0, 0.0, 1.0); + } else { + // Perspective: camera is at origin, direction varies per point + dirToCam = normalize(-gl_in[0].gl_Position.xyz); + } vec3 basisX; vec3 basisY; buildTangentBasis(dirToCam, basisX, basisY); diff --git a/src/render/opengl/shaders/vector_shaders.cpp b/src/render/opengl/shaders/vector_shaders.cpp index 61e0f3bc..be0feb44 100644 --- a/src/render/opengl/shaders/vector_shaders.cpp +++ b/src/render/opengl/shaders/vector_shaders.cpp @@ -218,6 +218,7 @@ R"( float LARGE_FLOAT(); vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); + void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); bool rayCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, vec3 cylTip, float cylRad, out float tHit, out vec3 pHit, out vec3 nHit); bool rayConeIntersection(vec3 rayStart, vec3 rayDir, vec3 coneBase, vec3 coneTip, float coneRad, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -228,7 +229,8 @@ R"( { // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); - vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); + vec3 rayStart, rayDir; + buildRayForFragment(u_viewport, depthRange, u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); // geometric shape of hte vector float tipLengthFrac = 0.2; @@ -241,13 +243,13 @@ R"( vec3 nHit = vec3(777,777,777); vec3 cylEnd = tailView + (1. - tipLengthFrac) * (tipView - tailView); float cylRad = tipWidthFrac * adjRadius; - rayCylinderIntersection(vec3(0., 0., 0), viewRay, tailView, cylEnd, cylRad, tHit, pHit, nHit); + rayCylinderIntersection(rayStart, rayDir, tailView, cylEnd, cylRad, tHit, pHit, nHit); // Raycast to cone float tHitCone; vec3 pHitCone; vec3 nHitCone; - bool coneHit = rayConeIntersection(vec3(0., 0., 0), viewRay, cylEnd, tipView, adjRadius, tHitCone, pHitCone, nHitCone); + bool coneHit = rayConeIntersection(rayStart, rayDir, cylEnd, tipView, adjRadius, tHitCone, pHitCone, nHitCone); if(tHitCone < tHit) { tHit = tHitCone; pHit = pHitCone; From adaed5f0664bfabcc80ea1c93cea9e42592dbef8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 00:37:34 -0800 Subject: [PATCH 2/8] remove unused function --- include/polyscope/camera_view.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/polyscope/camera_view.h b/include/polyscope/camera_view.h index 61733109..743872d2 100644 --- a/include/polyscope/camera_view.h +++ b/include/polyscope/camera_view.h @@ -97,7 +97,6 @@ class CameraView : public QuantityStructure { // Rendering helpers used by quantities void setCameraViewUniforms(render::ShaderProgram& p); std::vector addCameraViewRules(std::vector initRules, bool withCameraView = true); - std::string getShaderNameForRenderMode(); // Get info related to how the frame is drawn (billboard center vector, center-to-top vector, center-to-right vector) std::tuple getFrameBillboardGeometry(); From 8d76cff18fafb58625552b2b74b881156b81618c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 00:38:40 -0800 Subject: [PATCH 3/8] move lazy properties to internal namespace --- include/polyscope/internal.h | 18 ++++++++++++ src/internal.cpp | 16 +++++++++++ src/polyscope.cpp | 56 ++++++++++++++++-------------------- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/include/polyscope/internal.h b/include/polyscope/internal.h index e57ffa82..9327931e 100644 --- a/include/polyscope/internal.h +++ b/include/polyscope/internal.h @@ -5,6 +5,9 @@ #include #include +#include "polyscope/scaled_value.h" +#include "polyscope/types.h" + namespace polyscope { @@ -36,5 +39,20 @@ extern float lastRightSideFreeY; extern float leftWindowsWidth; extern float rightWindowsWidth; +// Cached versions of lazy properties used for updates +namespace lazy { +extern TransparencyMode transparencyMode; +extern ProjectionMode projectionMode; +extern int transparencyRenderPasses; +extern int ssaaFactor; +extern float uiScale; +extern bool groundPlaneEnabled; +extern GroundPlaneMode groundPlaneMode; +extern ScaledValue groundPlaneHeightFactor; +extern int shadowBlurIters; +extern float shadowDarkness; +} // namespace lazy + + } // namespace internal } // namespace polyscope diff --git a/src/internal.cpp b/src/internal.cpp index 29e9c9f8..86aa20ce 100644 --- a/src/internal.cpp +++ b/src/internal.cpp @@ -22,5 +22,21 @@ float lastRightSideFreeY = 10; float leftWindowsWidth = -1.; float rightWindowsWidth = -1.; + +namespace lazy { + +TransparencyMode transparencyMode = TransparencyMode::None; +ProjectionMode projectionMode = ProjectionMode::Perspective; +int transparencyRenderPasses = 8; +int ssaaFactor = 1; +float uiScale = -1.; +bool groundPlaneEnabled = true; +GroundPlaneMode groundPlaneMode = GroundPlaneMode::TileReflection; +ScaledValue groundPlaneHeightFactor = 0; +int shadowBlurIters = 2; +float shadowDarkness = .4; + +} // namespace lazy + } // namespace internal } // namespace polyscope diff --git a/src/polyscope.cpp b/src/polyscope.cpp index b10dab50..fb7326a5 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -1421,19 +1421,6 @@ void refresh() { requestRedraw(); } -// Cached versions of lazy properties used for updates -namespace lazy { -TransparencyMode transparencyMode = TransparencyMode::None; -int transparencyRenderPasses = 8; -int ssaaFactor = 1; -float uiScale = -1.; -bool groundPlaneEnabled = true; -GroundPlaneMode groundPlaneMode = GroundPlaneMode::TileReflection; -ScaledValue groundPlaneHeightFactor = 0; -int shadowBlurIters = 2; -float shadowDarkness = .4; -} // namespace lazy - void processLazyProperties() { // Note: This function essentially represents lazy software design, and it's an ugly and error-prone part of the @@ -1448,45 +1435,52 @@ void processLazyProperties() { // There is a second function processLazyPropertiesOutsideOfImGui() which handles a few more that can only be set // at limited times when an ImGui frame is not active. + // projection mode + if (internal::lazy::projectionMode != view::projectionMode) { + internal::lazy::projectionMode = view::projectionMode; + view::setProjectionMode(view::projectionMode); + } + // transparency mode - if (lazy::transparencyMode != options::transparencyMode) { - lazy::transparencyMode = options::transparencyMode; + if (internal::lazy::transparencyMode != options::transparencyMode) { + internal::lazy::transparencyMode = options::transparencyMode; render::engine->setTransparencyMode(options::transparencyMode); } // transparency render passes - if (lazy::transparencyRenderPasses != options::transparencyRenderPasses) { - lazy::transparencyRenderPasses = options::transparencyRenderPasses; + if (internal::lazy::transparencyRenderPasses != options::transparencyRenderPasses) { + internal::lazy::transparencyRenderPasses = options::transparencyRenderPasses; requestRedraw(); } // ssaa - if (lazy::ssaaFactor != options::ssaaFactor) { - lazy::ssaaFactor = options::ssaaFactor; + if (internal::lazy::ssaaFactor != options::ssaaFactor) { + internal::lazy::ssaaFactor = options::ssaaFactor; render::engine->setSSAAFactor(options::ssaaFactor); } // ground plane - if (lazy::groundPlaneEnabled != options::groundPlaneEnabled || lazy::groundPlaneMode != options::groundPlaneMode) { - lazy::groundPlaneEnabled = options::groundPlaneEnabled; + if (internal::lazy::groundPlaneEnabled != options::groundPlaneEnabled || + internal::lazy::groundPlaneMode != options::groundPlaneMode) { + internal::lazy::groundPlaneEnabled = options::groundPlaneEnabled; if (!options::groundPlaneEnabled) { // if the (depecated) groundPlaneEnabled = false, set mode to None, so we only have one variable to check options::groundPlaneMode = GroundPlaneMode::None; } - lazy::groundPlaneMode = options::groundPlaneMode; + internal::lazy::groundPlaneMode = options::groundPlaneMode; requestRedraw(); } - if (lazy::groundPlaneHeightFactor.asAbsolute() != options::groundPlaneHeightFactor.asAbsolute() || - lazy::groundPlaneHeightFactor.isRelative() != options::groundPlaneHeightFactor.isRelative()) { - lazy::groundPlaneHeightFactor = options::groundPlaneHeightFactor; + if (internal::lazy::groundPlaneHeightFactor.asAbsolute() != options::groundPlaneHeightFactor.asAbsolute() || + internal::lazy::groundPlaneHeightFactor.isRelative() != options::groundPlaneHeightFactor.isRelative()) { + internal::lazy::groundPlaneHeightFactor = options::groundPlaneHeightFactor; requestRedraw(); } - if (lazy::shadowBlurIters != options::shadowBlurIters) { - lazy::shadowBlurIters = options::shadowBlurIters; + if (internal::lazy::shadowBlurIters != options::shadowBlurIters) { + internal::lazy::shadowBlurIters = options::shadowBlurIters; requestRedraw(); } - if (lazy::shadowDarkness != options::shadowDarkness) { - lazy::shadowDarkness = options::shadowDarkness; + if (internal::lazy::shadowDarkness != options::shadowDarkness) { + internal::lazy::shadowDarkness = options::shadowDarkness; requestRedraw(); } }; @@ -1495,8 +1489,8 @@ void processLazyPropertiesOutsideOfImGui() { // Like processLazyProperties, but this one handles properties which cannot be changed mid-ImGui frame // uiScale - if (lazy::uiScale != options::uiScale) { - lazy::uiScale = options::uiScale; + if (internal::lazy::uiScale != options::uiScale) { + internal::lazy::uiScale = options::uiScale; render::engine->configureImGui(); setInitialWindowWidths(); } From ee4e3cf5922296430754d2c32d7d586124610ec5 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 00:41:03 -0800 Subject: [PATCH 4/8] manage perspective vs orthographic shader raycasts with a rule at build time --- .../polyscope/render/opengl/shaders/rules.h | 2 + include/polyscope/vector_quantity.ipp | 3 +- include/polyscope/view.h | 3 ++ src/camera_view.cpp | 6 ++- src/curve_network.cpp | 11 ++-- src/point_cloud.cpp | 3 ++ src/render/mock_opengl/mock_gl_engine.cpp | 2 + src/render/opengl/gl_engine.cpp | 2 + src/render/opengl/shaders/common.cpp | 54 +++++++++---------- .../opengl/shaders/cylinder_shaders.cpp | 4 +- src/render/opengl/shaders/rules.cpp | 35 ++++++++++++ src/render/opengl/shaders/sphere_shaders.cpp | 28 +++------- .../opengl/shaders/texture_draw_shaders.cpp | 1 + src/render/opengl/shaders/vector_shaders.cpp | 4 +- src/transformation_gizmo.cpp | 2 + src/view.cpp | 29 ++++++++-- 16 files changed, 122 insertions(+), 67 deletions(-) diff --git a/include/polyscope/render/opengl/shaders/rules.h b/include/polyscope/render/opengl/shaders/rules.h index 221a678a..78979786 100644 --- a/include/polyscope/render/opengl/shaders/rules.h +++ b/include/polyscope/render/opengl/shaders/rules.h @@ -40,6 +40,8 @@ extern const ShaderReplacementRule PROJ_AND_INV_PROJ_MAT; extern const ShaderReplacementRule COMPUTE_SHADE_NORMAL_FROM_POSITION; extern const ShaderReplacementRule PREMULTIPLY_LIT_COLOR; extern const ShaderReplacementRule CULL_POS_FROM_VIEW; +extern const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE; +extern const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC; ShaderReplacementRule generateSlicePlaneRule(std::string uniquePostfix); ShaderReplacementRule generateVolumeGridSlicePlaneRule(std::string uniquePostfix); diff --git a/include/polyscope/vector_quantity.ipp b/include/polyscope/vector_quantity.ipp index e415123d..954c6e57 100644 --- a/include/polyscope/vector_quantity.ipp +++ b/include/polyscope/vector_quantity.ipp @@ -158,7 +158,8 @@ void VectorQuantity::drawVectors() { template void VectorQuantity::createProgram() { - std::vector rules = this->quantity.parent.addStructureRules({"SHADE_BASECOLOR"}); + std::vector rules = + this->quantity.parent.addStructureRules({view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR"}); if (this->quantity.parent.wantsCullPosition()) { rules.push_back("VECTOR_CULLPOS_FROM_TAIL"); } diff --git a/include/polyscope/view.h b/include/polyscope/view.h index ae4591c2..8cfca87c 100644 --- a/include/polyscope/view.h +++ b/include/polyscope/view.h @@ -85,6 +85,8 @@ CameraParameters getCameraParametersForCurrentView(); // contains all of this in // (these friendly helpers to get the same info as ^^^) glm::mat4 getCameraViewMatrix(); void setCameraViewMatrix(glm::mat4 newMat); +ProjectionMode getProjectionMode(); +void setProjectionMode(ProjectionMode newMode); glm::mat4 getCameraPerspectiveMatrix(); glm::vec3 getCameraWorldPosition(); void getCameraFrame(glm::vec3& lookDir, glm::vec3& upDir, glm::vec3& rightDir); @@ -165,6 +167,7 @@ std::tuple screenCoordsToBufferInds(glm::vec2 screenCoords); glm::ivec2 screenCoordsToBufferIndsVec(glm::vec2 screenCoords); glm::vec2 bufferIndsToScreenCoords(int xPos, int yPos); glm::vec2 bufferIndsToScreenCoords(glm::ivec2 bufferInds); +std::string getCurrentProjectionModeRaycastRule(); // == Internal helpers. Should probably not be called in user code. diff --git a/src/camera_view.cpp b/src/camera_view.cpp index e41cd416..7a3f2962 100644 --- a/src/camera_view.cpp +++ b/src/camera_view.cpp @@ -154,14 +154,16 @@ void CameraView::drawPickDelayed() { void CameraView::prepare() { { - std::vector rules = addStructureRules({"SHADE_BASECOLOR"}); + std::vector rules = + addStructureRules({view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR"}); if (wantsCullPosition()) rules.push_back("SPHERE_CULLPOS_FROM_CENTER"); rules = render::engine->addMaterialRules(material, rules); nodeProgram = render::engine->requestShader("RAYCAST_SPHERE", rules); } { - std::vector rules = addStructureRules({"SHADE_BASECOLOR"}); + std::vector rules = + addStructureRules({view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR"}); if (wantsCullPosition()) rules.push_back("CYLINDER_CULLPOS_FROM_MID"); rules = render::engine->addMaterialRules(material, rules); edgeProgram = render::engine->requestShader("RAYCAST_CYLINDER", rules); diff --git a/src/curve_network.cpp b/src/curve_network.cpp index 9a7d35d1..b3bfd800 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -221,6 +221,8 @@ void CurveNetwork::drawPickDelayed() { std::vector CurveNetwork::addCurveNetworkNodeRules(std::vector initRules) { initRules = addStructureRules(initRules); + + initRules.push_back(view::getCurrentProjectionModeRaycastRule()); if (nodeRadiusQuantityName != "" || edgeRadiusQuantityName != "") { initRules.push_back("SPHERE_VARIABLE_SIZE"); @@ -233,6 +235,8 @@ std::vector CurveNetwork::addCurveNetworkNodeRules(std::vector CurveNetwork::addCurveNetworkEdgeRules(std::vector initRules) { initRules = addStructureRules(initRules); + initRules.push_back(view::getCurrentProjectionModeRaycastRule()); + // use node radius to blend cylinder radius if (nodeRadiusQuantityName != "" || edgeRadiusQuantityName != "") { initRules.push_back("CYLINDER_VARIABLE_SIZE"); @@ -292,9 +296,10 @@ void CurveNetwork::preparePick() { size_t pickStart = pick::requestPickBufferRange(this, totalPickElements); { // Set up node picking program - nodePickProgram = - render::engine->requestShader("RAYCAST_SPHERE", addCurveNetworkNodeRules({"SPHERE_PROPAGATE_COLOR"}), - render::ShaderReplacementDefaults::Pick); + nodePickProgram = render::engine->requestShader( + "RAYCAST_SPHERE", + addCurveNetworkNodeRules({"SPHERE_PROPAGATE_COLOR"}), + render::ShaderReplacementDefaults::Pick); // Fill color buffer with packed point indices std::vector pickColors; diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index 25e24a05..be08eaa3 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -247,6 +247,9 @@ PointCloudPickResult PointCloud::interpretPickResult(const PickResult& rawResult std::vector PointCloud::addPointCloudRules(std::vector initRules, bool withPointCloud) { initRules = addStructureRules(initRules); if (withPointCloud) { + + initRules.push_back(view::getCurrentProjectionModeRaycastRule()); + if (pointRadiusQuantityName != "") { initRules.push_back("SPHERE_VARIABLE_SIZE"); } diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 3fa63568..f6609d08 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -1964,6 +1964,8 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("PREMULTIPLY_LIT_COLOR", PREMULTIPLY_LIT_COLOR); registerShaderRule("CULL_POS_FROM_VIEW", CULL_POS_FROM_VIEW); registerShaderRule("PROJ_AND_INV_PROJ_MAT", PROJ_AND_INV_PROJ_MAT); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE", BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC", BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC); // Lighting and shading things registerShaderRule("LIGHT_MATCAP", LIGHT_MATCAP); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 81f77676..779294a2 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -2500,6 +2500,8 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("PREMULTIPLY_LIT_COLOR", PREMULTIPLY_LIT_COLOR); registerShaderRule("CULL_POS_FROM_VIEW", CULL_POS_FROM_VIEW); registerShaderRule("PROJ_AND_INV_PROJ_MAT", PROJ_AND_INV_PROJ_MAT); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE", BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC", BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC); // Lighting and shading things registerShaderRule("LIGHT_MATCAP", LIGHT_MATCAP); diff --git a/src/render/opengl/shaders/common.cpp b/src/render/opengl/shaders/common.cpp index 86b0336c..7b38a339 100644 --- a/src/render/opengl/shaders/common.cpp +++ b/src/render/opengl/shaders/common.cpp @@ -179,43 +179,37 @@ vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 return eyePos.xyz / eyePos.w; } -// Detect if the projection matrix is orthographic. -// In an orthographic projection matrix, projMat[3][3] == 1.0 and projMat[2][3] == 0.0. -// In a perspective projection matrix, projMat[3][3] == 0.0 and projMat[2][3] == -1.0. -bool isOrthoProjection(mat4 projMat) { - return abs(projMat[3][3] - 1.0) < 0.001; +// Build ray start and ray direction for fragment-based raycasting. +// For perspective projection: rays originate from the camera (origin) and point towards the fragment view position. +void buildRayForFragmentPerspective(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, + out vec3 rayStart, out vec3 rayDir) { + vec3 viewPos = fragmentViewPosition(viewport, depthRange, invProjMat, fragCoord); + rayStart = vec3(0.0, 0.0, 0.0); + rayDir = normalize(viewPos); } // Build ray start and ray direction for fragment-based raycasting. -// For perspective projection: rays originate from the camera (origin) and point towards the fragment view position. // For orthographic projection: rays are parallel, all pointing in -Z direction in view space, // starting from the fragment's XY position. -void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, +void buildRayForFragmentOrthographic(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir) { - if (isOrthoProjection(projMat)) { - // Orthographic: parallel rays pointing in -Z direction - // Convert fragment screen position to NDC - vec2 ndcXY = ((2.0 * fragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1.0; - - // Unproject to view space at near plane (NDC z = -1) and far plane (NDC z = 1) - // For orthographic, we just need the XY in view space - vec4 ndcNear = vec4(ndcXY, -1.0, 1.0); - vec4 viewNear = invProjMat * ndcNear; - viewNear /= viewNear.w; - - vec4 ndcFar = vec4(ndcXY, 1.0, 1.0); - vec4 viewFar = invProjMat * ndcFar; - viewFar /= viewFar.w; - - rayStart = viewNear.xyz; - rayDir = normalize(viewFar.xyz - viewNear.xyz); - } else { - // Perspective: rays from origin through fragment position - vec3 viewPos = fragmentViewPosition(viewport, depthRange, invProjMat, fragCoord); - rayStart = vec3(0.0, 0.0, 0.0); - rayDir = viewPos; - } + // Orthographic: parallel rays pointing in -Z direction + // Convert fragment screen position to NDC + vec2 ndcXY = ((2.0 * fragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1.0; + + // Unproject to view space at near plane (NDC z = -1) and far plane (NDC z = 1) + // For orthographic, we just need the XY in view space + vec4 ndcNear = vec4(ndcXY, -1.0, 1.0); + vec4 viewNear = invProjMat * ndcNear; + viewNear /= viewNear.w; + + vec4 ndcFar = vec4(ndcXY, 1.0, 1.0); + vec4 viewFar = invProjMat * ndcFar; + viewFar /= viewFar.w; + + rayStart = viewNear.xyz; + rayDir = normalize(viewFar.xyz - viewNear.xyz); } float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint) { diff --git a/src/render/opengl/shaders/cylinder_shaders.cpp b/src/render/opengl/shaders/cylinder_shaders.cpp index 525d9d78..29b1faa1 100644 --- a/src/render/opengl/shaders/cylinder_shaders.cpp +++ b/src/render/opengl/shaders/cylinder_shaders.cpp @@ -163,8 +163,6 @@ R"( layout(location = 0) out vec4 outputF; float LARGE_FLOAT(); - vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); - void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); bool rayTaperedCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, vec3 cylTip, float cylRadTail, float cylRadTip, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -175,7 +173,7 @@ R"( // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); vec3 rayStart, rayDir; - buildRayForFragment(u_viewport, depthRange, u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + ${ BUILD_RAY_FOR_FRAGMENT }$ float tipRadius = u_radius; diff --git a/src/render/opengl/shaders/rules.cpp b/src/render/opengl/shaders/rules.cpp index b260679c..c7a19719 100644 --- a/src/render/opengl/shaders/rules.cpp +++ b/src/render/opengl/shaders/rules.cpp @@ -465,6 +465,41 @@ const ShaderReplacementRule CULL_POS_FROM_VIEW ( /* textures */ {} ); +const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE( + /* rule name */ "BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + void buildRayForFragmentPerspective(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); + )"}, + {"BUILD_RAY_FOR_FRAGMENT", R"( + buildRayForFragmentPerspective(u_viewport, vec2(gl_DepthRange.near, gl_DepthRange.far), u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + )"}, + {"GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE", R"( + vec3 dirToCam = normalize(-gl_in[0].gl_Position.xyz); + )"} + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {} +); + +const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC( + /* rule name */ "BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + void buildRayForFragmentOrthographic(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); + )"}, + {"BUILD_RAY_FOR_FRAGMENT", R"( + buildRayForFragmentOrthographic(u_viewport, vec2(gl_DepthRange.near, gl_DepthRange.far), u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + )"}, + {"GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE", R"( + vec3 dirToCam = vec3(0.0, 0.0, 1.0); + )"} + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {} +); ShaderReplacementRule generateSlicePlaneRule(std::string uniquePostfix) { diff --git a/src/render/opengl/shaders/sphere_shaders.cpp b/src/render/opengl/shaders/sphere_shaders.cpp index 9b163cae..fdf55d01 100644 --- a/src/render/opengl/shaders/sphere_shaders.cpp +++ b/src/render/opengl/shaders/sphere_shaders.cpp @@ -73,7 +73,6 @@ R"( ${ GEOM_DECLARATIONS }$ void buildTangentBasis(vec3 unitNormal, out vec3 basisX, out vec3 basisY); - bool isOrthoProjection(mat4 projMat); void main() { @@ -83,14 +82,9 @@ R"( // Construct the 4 corners of a billboard quad, facing the camera // Quad is shifted pointRadius toward the camera, otherwise it doesn't actually necessarily // cover the full sphere due to perspective. - vec3 dirToCam; - if (isOrthoProjection(u_projMatrix)) { - // Orthographic: camera direction is constant +Z in view space - dirToCam = vec3(0.0, 0.0, 1.0); - } else { - // Perspective: camera is at origin, direction varies per point - dirToCam = normalize(-gl_in[0].gl_Position.xyz); - } + // (this logic is different depending on perspective vs orthographic projection) + ${ GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE }$ + vec3 basisX; vec3 basisY; buildTangentBasis(dirToCam, basisX, basisY); @@ -149,8 +143,6 @@ R"( float LARGE_FLOAT(); vec3 lightSurfaceMat(vec3 normal, vec3 color, sampler2D t_mat_r, sampler2D t_mat_g, sampler2D t_mat_b, sampler2D t_mat_k); - vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); - void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); bool raySphereIntersection(vec3 rayStart, vec3 rayDir, vec3 sphereCenter, float sphereRad, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -161,7 +153,7 @@ R"( // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); vec3 rayStart, rayDir; - buildRayForFragment(u_viewport, depthRange, u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + ${ BUILD_RAY_FOR_FRAGMENT }$ float pointRadius = u_pointRadius; ${ SPHERE_SET_POINT_RADIUS_FRAG }$ @@ -268,7 +260,6 @@ R"( ${ GEOM_DECLARATIONS }$ void buildTangentBasis(vec3 unitNormal, out vec3 basisX, out vec3 basisY); - bool isOrthoProjection(mat4 projMat); void main() { @@ -276,14 +267,9 @@ R"( ${ SPHERE_SET_POINT_RADIUS_GEOM }$ // Construct the 4 corners of a billboard quad, facing the camera - vec3 dirToCam; - if (isOrthoProjection(u_projMatrix)) { - // Orthographic: camera direction is constant +Z in view space - dirToCam = vec3(0.0, 0.0, 1.0); - } else { - // Perspective: camera is at origin, direction varies per point - dirToCam = normalize(-gl_in[0].gl_Position.xyz); - } + // (this logic is different depending on perspective vs orthographic projection) + ${ GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE }$ + vec3 basisX; vec3 basisY; buildTangentBasis(dirToCam, basisX, basisY); diff --git a/src/render/opengl/shaders/texture_draw_shaders.cpp b/src/render/opengl/shaders/texture_draw_shaders.cpp index 7a5b66cb..507a5fe2 100644 --- a/src/render/opengl/shaders/texture_draw_shaders.cpp +++ b/src/render/opengl/shaders/texture_draw_shaders.cpp @@ -174,6 +174,7 @@ R"( // Set the depth of the fragment from the stored texture data // TODO: this a wasteful way to convert ray depth to gl_FragDepth, I am sure it can be done with much less arithmetic... figure it out // WARNING this code is duplicated in other shaders + // WARNING this almost certainly does not work right for orthographic projections vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); viewRay = normalize(viewRay); diff --git a/src/render/opengl/shaders/vector_shaders.cpp b/src/render/opengl/shaders/vector_shaders.cpp index be0feb44..98eb8b65 100644 --- a/src/render/opengl/shaders/vector_shaders.cpp +++ b/src/render/opengl/shaders/vector_shaders.cpp @@ -217,8 +217,6 @@ R"( layout(location = 0) out vec4 outputF; float LARGE_FLOAT(); - vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); - void buildRayForFragment(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); bool rayCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, vec3 cylTip, float cylRad, out float tHit, out vec3 pHit, out vec3 nHit); bool rayConeIntersection(vec3 rayStart, vec3 rayDir, vec3 coneBase, vec3 coneTip, float coneRad, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -230,7 +228,7 @@ R"( // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); vec3 rayStart, rayDir; - buildRayForFragment(u_viewport, depthRange, u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + ${ BUILD_RAY_FOR_FRAGMENT }$ // geometric shape of hte vector float tipLengthFrac = 0.2; diff --git a/src/transformation_gizmo.cpp b/src/transformation_gizmo.cpp index 25c25f88..ed66beca 100644 --- a/src/transformation_gizmo.cpp +++ b/src/transformation_gizmo.cpp @@ -50,6 +50,7 @@ void TransformationGizmo::prepare() { arrowProgram = render::engine->requestShader("RAYCAST_VECTOR", render::engine->addMaterialRules(material, { + view::getCurrentProjectionModeRaycastRule(), "VECTOR_PROPAGATE_COLOR", "TRANSFORMATION_GIZMO_VEC", "SHADE_COLOR", @@ -77,6 +78,7 @@ void TransformationGizmo::prepare() { sphereProgram = render::engine->requestShader("RAYCAST_SPHERE", render::engine->addMaterialRules(material, { + view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR", "LIGHT_MATCAP" } diff --git a/src/view.cpp b/src/view.cpp index 1ceee52e..091216f5 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -142,6 +142,20 @@ glm::vec2 bufferIndsToScreenCoords(glm::ivec2 bufferInds) { return bufferIndsToScreenCoords(bufferInds.x, bufferInds.y); } +std::string getCurrentProjectionModeRaycastRule() { + // This is related to the render engine, it returns the name of the + // shader rule which constructs rays for raycasting-based rendering. + // See rules.h + + switch (view::projectionMode) { + case ProjectionMode::Perspective: + return "BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE"; + case ProjectionMode::Orthographic: + return "BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC"; + } + return ""; +} + void processRotate(glm::vec2 startP, glm::vec2 endP) { if (startP == endP) { @@ -647,6 +661,15 @@ void setVerticalFieldOfViewDegrees(float newVal) { requestRedraw(); } +ProjectionMode getProjectionMode() { return projectionMode; } + +void setProjectionMode(ProjectionMode newMode) { + projectionMode = newMode; + internal::lazy::projectionMode = newMode; // update the lazy property right now, so we don't pay for a refresh twice + refresh(); + requestRedraw(); +} + float getVerticalFieldOfViewDegrees() { return view::fov; } float getAspectRatioWidthOverHeight() { return (float)bufferWidth / bufferHeight; } @@ -1129,13 +1152,11 @@ void buildViewGui() { std::string projectionModeStr = to_string(view::projectionMode); if (ImGui::BeginCombo("##ProjectionMode", projectionModeStr.c_str())) { if (ImGui::Selectable("Perspective", view::projectionMode == ProjectionMode::Perspective)) { - view::projectionMode = ProjectionMode::Perspective; - requestRedraw(); + setProjectionMode(ProjectionMode::Perspective); ImGui::SetItemDefaultFocus(); } if (ImGui::Selectable("Orthographic", view::projectionMode == ProjectionMode::Orthographic)) { - view::projectionMode = ProjectionMode::Orthographic; - requestRedraw(); + setProjectionMode(ProjectionMode::Orthographic); ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); From 893051721f189fc26dfa38a790ce6fe676287ecb Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 00:50:48 -0800 Subject: [PATCH 5/8] add more tests for orthographic rendering --- test/src/camera_view_test.cpp | 19 +++++++++++++++++++ test/src/curve_network_test.cpp | 15 +++++++++++++++ test/src/floating_test.cpp | 6 ++++++ test/src/point_cloud_test.cpp | 26 ++++++++++++++++++++++++++ test/src/volume_grid_test.cpp | 18 ++++++++++++++++++ test/src/volume_mesh_test.cpp | 5 +++++ 6 files changed, 89 insertions(+) diff --git a/test/src/camera_view_test.cpp b/test/src/camera_view_test.cpp index defc1749..0f0f91bd 100644 --- a/test/src/camera_view_test.cpp +++ b/test/src/camera_view_test.cpp @@ -66,6 +66,20 @@ TEST_F(PolyscopeTest, CameraViewUpdate) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, CameraViewOrthographicRendering) { + + polyscope::CameraView* cam1 = polyscope::registerCameraView( + "cam1", polyscope::CameraParameters(polyscope::CameraIntrinsics::fromFoVDegVerticalAndAspect(60, 2.), + polyscope::CameraExtrinsics::fromVectors( + glm::vec3{2., 2., 2.}, glm::vec3{-1., -1., -1.}, glm::vec3{0., 1., 0.}))); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, CameraViewPick) { polyscope::CameraView* cam1 = polyscope::registerCameraView( @@ -79,6 +93,11 @@ TEST_F(PolyscopeTest, CameraViewPick) { polyscope::show(3); + // make sure it works in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + polyscope::removeAllStructures(); } diff --git a/test/src/curve_network_test.cpp b/test/src/curve_network_test.cpp index 3225a9cf..26092ba7 100644 --- a/test/src/curve_network_test.cpp +++ b/test/src/curve_network_test.cpp @@ -37,12 +37,27 @@ TEST_F(PolyscopeTest, CurveNetworkAppearance) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, CurveNetworkOrthographicRendering) { + auto psCurve = registerCurveNetwork(); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, CurveNetworkPick) { auto psCurve = registerCurveNetwork(); // Don't bother trying to actually click on anything, but make sure this doesn't crash polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + // make sure it works in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + polyscope::removeAllStructures(); } diff --git a/test/src/floating_test.cpp b/test/src/floating_test.cpp index bee1f692..27eeee67 100644 --- a/test/src/floating_test.cpp +++ b/test/src/floating_test.cpp @@ -36,6 +36,12 @@ TEST_F(PolyscopeTest, FloatingImageTest) { polyscope::show(3); im2->setShowFullscreen(true); polyscope::show(3); + + // try orthographic rendering + // (I think there are some bugs related to raycasting and depth projection, but at least make sure it doen't crash + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); } { // ColorImageQuantity diff --git a/test/src/point_cloud_test.cpp b/test/src/point_cloud_test.cpp index 5a10027f..574a9cf6 100644 --- a/test/src/point_cloud_test.cpp +++ b/test/src/point_cloud_test.cpp @@ -62,6 +62,21 @@ TEST_F(PolyscopeTest, PointCloudAppearance) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, PointCloudSphereOrthographicRendering) { + auto psPoints = registerPointCloud(); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + + psPoints->setPointRenderMode(polyscope::PointRenderMode::Quad); + polyscope::show(3); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); +} + + TEST_F(PolyscopeTest, PointCloudPick) { auto psPoints = registerPointCloud(); @@ -70,6 +85,11 @@ TEST_F(PolyscopeTest, PointCloudPick) { psPoints->setPointRenderMode(polyscope::PointRenderMode::Quad); polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + + // make sure it works in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); polyscope::removeAllStructures(); } @@ -148,6 +168,12 @@ TEST_F(PolyscopeTest, PointCloudVector) { q1->updateData(vals); polyscope::show(3); + // make sure vectors work in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); } diff --git a/test/src/volume_grid_test.cpp b/test/src/volume_grid_test.cpp index b05a5627..0c46c697 100644 --- a/test/src/volume_grid_test.cpp +++ b/test/src/volume_grid_test.cpp @@ -26,6 +26,7 @@ TEST_F(PolyscopeTest, ShowVolumeGrid) { EXPECT_FALSE(polyscope::hasVolumeGrid("test grid")); } + TEST_F(PolyscopeTest, VolumeGridBasicOptions) { // these are node dim @@ -56,6 +57,23 @@ TEST_F(PolyscopeTest, VolumeGridBasicOptions) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, VolumeGridOrthographicRendering) { + // clang-format off + uint32_t dimX = 8; + uint32_t dimY = 10; + uint32_t dimZ = 12; + glm::vec3 bound_low{-3., -3., -3.}; + glm::vec3 bound_high{3., 3., 3.}; + + polyscope::VolumeGrid* psGrid = polyscope::registerVolumeGrid("test grid", {dimX, dimY, dimZ}, bound_low, bound_high); + + // try orthographic rendering + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + polyscope::show(3); +} + TEST_F(PolyscopeTest, VolumeGridSlicePlane) { // these are node dim diff --git a/test/src/volume_mesh_test.cpp b/test/src/volume_mesh_test.cpp index 1a81a786..f36a4e86 100644 --- a/test/src/volume_mesh_test.cpp +++ b/test/src/volume_mesh_test.cpp @@ -258,6 +258,11 @@ TEST_F(PolyscopeTest, VolumeMeshInspect) { // with a categorical quantity auto q1Cat = psVol->addVertexScalarQuantity("vals", vals, polyscope::DataType::CATEGORICAL); q1Cat->setEnabled(true); + + // try orthographic rendering + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); polyscope::show(3); // clear it out From 9327e7ae92d4b620d8be9b373637bb8072fe6de6 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 00:57:59 -0800 Subject: [PATCH 6/8] fix unrelated but with ground plane and upDir in ortho view --- src/render/ground_plane.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/ground_plane.cpp b/src/render/ground_plane.cpp index a8550e9f..c6f24981 100644 --- a/src/render/ground_plane.cpp +++ b/src/render/ground_plane.cpp @@ -252,7 +252,7 @@ void GroundPlane::draw(bool isRedraw) { } case ProjectionMode::Orthographic: { glm::vec4 lookDir = glm::vec4(0, 0, 1, 0) * viewMat; - groundPlaneProgram->setUniform("u_cameraHeight", (lookDir.y * state::lengthScale) + groundHeight); + groundPlaneProgram->setUniform("u_cameraHeight", (lookDir[iP] * state::lengthScale) + groundHeight); break; } } From b55240b7da6ca22f1c2dcc49b228b5dba2ebbd82 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 01:03:24 -0800 Subject: [PATCH 7/8] remove config file --- .polyscope.ini | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .polyscope.ini diff --git a/.polyscope.ini b/.polyscope.ini deleted file mode 100644 index e13a86bd..00000000 --- a/.polyscope.ini +++ /dev/null @@ -1,7 +0,0 @@ -{ - "uiScale": 1.0, - "windowHeight": 1352, - "windowPosX": 1283, - "windowPosY": 35, - "windowWidth": 2554 -} From 61eff5c73b20616f713b3d4b8becc35c593be72c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 14 Dec 2025 01:04:56 -0800 Subject: [PATCH 8/8] add ini to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b98dc456..d2de3dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ misc/file2c/file2cpp # Editor and OS things imgui.ini +.polyscope.ini .DS_Store .vscode *.swp