/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "SrCameraMovementRecognizer.h" #include "vectorization/GsSrVectorizeDevice.h" #include "vectorization/GsSrVectorizeView.h" #include #if defined(_MSC_VER) #pragma warning(disable : 26812) //disable enum class is preferred #pragma warning(disable : 26495) //disable always initialize a member variable #endif CameraMovementType::CameraMovementType() : m_type(None) { } CameraMovementType CameraMovementType::createPan(int pixelsX, int pixelsY) { CameraMovementType movement; movement.m_type = PanAlongScreen; movement.m_panData = { pixelsX, pixelsY }; return movement; } CameraMovementType CameraMovementType::createNone() { return CameraMovementType(); } CameraMovementType CameraMovementType::createAnother() { CameraMovementType movement; movement.m_type = AnotherMovement; return movement; } const CameraMovementType::PanData& CameraMovementType::panData() const { return m_panData; } bool CameraMovementType::hasSameType(const CameraMovementType& other) { return m_type == other.m_type; } CameraMovementType::operator CameraMovementType::Type() const { return m_type; } ViewportChangingType::ViewportChangingType() : m_type(None) { } ViewportChangingType ViewportChangingType::createMovement(int pixelsX, int pixelsY) { ViewportChangingType change; change.m_type = Movement; change.m_movementData = { pixelsX, pixelsY }; return change; } ViewportChangingType ViewportChangingType::createResizing() { ViewportChangingType change; change.m_type = ViewportChangingType::Resizing; return change; } ViewportChangingType ViewportChangingType::createBorderChanged() { ViewportChangingType change; change.m_type = ViewportChangingType::BorderChanged; return change; } const ViewportChangingType::MovementData& ViewportChangingType::movementData() const { return m_movementData; } ViewportChangingType ViewportChangingType::createNone() { return ViewportChangingType(); } ViewportChangingType::operator ViewportChangingType::Type() const { return m_type; } CameraState::CameraState() : position(OdGePoint3d(1.0, 1.0, 1.0)) // Camera above origin , target(OdGePoint3d::kOrigin) // Looking at origin , upVector(OdGeVector3d::kZAxis), fieldWidth(1.0), fieldHeight(1.0), projectionType(OdGsView::kParallel) { } OdSrCameraMovementRecognizer::OdSrCameraMovementRecognizer( OdGsSrVectorizerDevice* device) : m_device(device), m_lastMovementType(CameraMovementType::createAnother()) {} CameraState OdSrCameraMovementRecognizer::calcCameraState(const OdGsSrVectorizeView* pView) { CameraState state; state.position = pView->position(); state.target = pView->target(); state.upVector = pView->upVector(); state.fieldWidth = pView->fieldWidth(); state.fieldHeight = pView->fieldHeight(); state.projectionType = pView->OdGsViewImpl::isPerspective() ? OdGsView::kPerspective : OdGsView::kParallel; return state; } void OdSrCameraMovementRecognizer::postUpdate() { for (int idxView = 0; idxView < m_device->numViews(); idxView++) { OdGsSrVectorizeView* pView = static_cast(m_device->viewAt(idxView)); m_postUpdateStateStorage[idxView] = calcCameraState(pView); } } OdGeMatrix3d buildEyeMatrixInv(const OdGsView* view) { OdGeMatrix3d xEyeToWorld; OdGeVector3d yVector = view->upVector(); OdGeVector3d zVector = (view->position() - view->target()).normalize(); OdGeVector3d xVector = yVector.crossProduct(zVector); return xEyeToWorld.setCoordSystem(view->target(), xVector, yVector, zVector).inverse(); } CameraMovementType::PanData OdSrCameraMovementRecognizer::getScreenDiff(const OdGsViewImpl* view, int idxView) const { OdGePoint3d oldScreenPt = m_postUpdateStateStorage.at(idxView).position; // from previous frame OdGePoint3d newScreenPt = view->position(); OdGeMatrix3d transf = view->worldToDeviceMatrix(); // view->screenMatrix() * view->projectionMatrix() * view->viewingMatrix(); oldScreenPt.transformBy(transf); newScreenPt.transformBy(transf); return CameraMovementType::PanData{ (int)OdRound(oldScreenPt.x - newScreenPt.x), (int)OdRound(oldScreenPt.y - newScreenPt.y) }; } bool getExactViewBounds(const OdGsSrVectorizeView* pView, OdGsDCRect& extentsRectOut) { OdGsDCRect viewRect; pView->screenRectNorm(viewRect); OdGeExtents3d extents; GsViewImplHelper::getExtents(*pView, kGsMainOverlay, extents, true); if (extents == OdGeExtents3d::kInvalid) { // If extents are invalid, use the entire view boundaries extentsRectOut = viewRect; return false; } // Convert 3D extents to screen coordinates OdGePoint3d minPt = extents.minPoint(); OdGePoint3d maxPt = extents.maxPoint(); minPt.transformBy(pView->worldToDeviceMatrix()); maxPt.transformBy(pView->worldToDeviceMatrix()); OdGsDCRect extentsRect; extentsRect.m_min.x = OdTruncateToLong(minPt.x); extentsRect.m_min.y = OdTruncateToLong(minPt.y); extentsRect.m_max.x = OdTruncateToLong(maxPt.x); extentsRect.m_max.y = OdTruncateToLong(maxPt.y); extentsRect.normalize(); // Intersect the extents with the view boundaries to ensure we only narrow the region // This ignores any parts of the extents that extend beyond the view's boundaries extentsRectOut.m_min.x = std::max(extentsRect.m_min.x, viewRect.m_min.x); extentsRectOut.m_min.y = std::max(extentsRect.m_min.y, viewRect.m_min.y); extentsRectOut.m_max.x = std::min(extentsRect.m_max.x, viewRect.m_max.x); extentsRectOut.m_max.y = std::min(extentsRect.m_max.y, viewRect.m_max.y); //// Check if the resulting rect is valid (non-empty) //return (extentsRectOut.m_max.x > extentsRectOut.m_min.x && // extentsRectOut.m_max.y > extentsRectOut.m_min.y); return true; } bool OdSrCameraMovementRecognizer::isBoundsOffscreen(const OdGsSrVectorizeView* pView, int shiftX, int shiftY) const { // Get view rectangle OdGsDCRect extentsRect; if (getExactViewBounds(pView, extentsRect)) return false; // Calculate new view position after panning OdGsDCRect newExtentsRect = extentsRect; newExtentsRect.m_min.x += shiftX; newExtentsRect.m_min.y += shiftY; newExtentsRect.m_max.x += shiftX; newExtentsRect.m_max.y += shiftY; // Check if the new position would move the view completely off-screen bool completelyOffScreen = newExtentsRect.m_max.x <= extentsRect.m_min.x || newExtentsRect.m_min.x >= extentsRect.m_max.x || newExtentsRect.m_max.y <= extentsRect.m_min.y || newExtentsRect.m_min.y >= extentsRect.m_max.y; if (completelyOffScreen) { return true; } return false; } CameraMovementType OdSrCameraMovementRecognizer::recognizeMovementType(const OdGsSrVectorizeView* pView, int viewIdx) const { if (m_postUpdateStateStorage.size() == 0) return CameraMovementType::createAnother(/*initial*/); // Calculate position and direction changes const CameraState& prev = m_postUpdateStateStorage.at(viewIdx); OdGeVector3d positionDelta = pView->position() - prev.position; OdGeVector3d targetDelta = pView->target() - prev.target; OdGeVector3d prevViewDir = (prev.target - prev.position).normalize(); OdGeVector3d currentViewDir = (pView->target() - pView->position()).normalize(); // Check field size change for parallel projection zoom if (!OdEqual(pView->fieldWidth(), prev.fieldWidth) || !OdEqual(pView->fieldHeight(), prev.fieldHeight)) { return CameraMovementType::createAnother(/* zoom */); } // Check position movement along view direction for perspective zoom if (targetDelta.isZeroLength() && // Target is fixed positionDelta.isParallelTo(prevViewDir)) // Moving along view direction { return CameraMovementType::createAnother(/* zoom */); } OdGsView::Projection projectionType = pView->OdGsViewImpl::isPerspective() ? OdGsView::kPerspective : OdGsView::kParallel; // Check for movement parallel to screen plane (pan) if (positionDelta.isEqualTo(targetDelta) && pView->upVector().isEqualTo(prev.upVector) && prevViewDir.isEqualTo(currentViewDir) && projectionType == prev.projectionType) { if (pView->OdGsViewImpl::isPerspective()) return CameraMovementType::createAnother(); //pan optimization as movement of the picture is possible for the parallel projection only CameraMovementType::PanData diff = getScreenDiff(pView, viewIdx); if (diff.isZero()) return CameraMovementType::createNone(); if (isBoundsOffscreen(pView, 0, 0)) return CameraMovementType::createNone(); // if the view's content goes all the way out of the screen, it's a default type of movement // to prevent weird values of picture movement if (isBoundsOffscreen(pView, diff.pixelsX, diff.pixelsY)) return CameraMovementType::createAnother(); return CameraMovementType::createPan(diff.pixelsX, diff.pixelsY); } return CameraMovementType::createAnother(); }