/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; std::unique_ptr E3dObject::CreateObjectSpecificProperties() { return std::make_unique(*this); } E3dObject::E3dObject(SdrModel& rSdrModel) : SdrAttrObj(rSdrModel), mbTfHasChanged(true), mbIsSelected(false) { m_bIs3DObj = true; m_bClosedObj = true; } E3dObject::E3dObject(SdrModel& rSdrModel, E3dObject const & rSource) : SdrAttrObj(rSdrModel, rSource), mbTfHasChanged(true), mbIsSelected(false) { m_bIs3DObj = true; m_bClosedObj = true; // BoundVol can be copied since also the children are copied maLocalBoundVol = rSource.maLocalBoundVol; maTransformation = rSource.maTransformation; // Because the parent may have changed, definitely redefine the total // transformation next time SetTransformChanged(); // Copy selection status mbIsSelected = rSource.mbIsSelected; } E3dObject::~E3dObject() { } void E3dObject::SetSelected(bool bNew) { if(mbIsSelected != bNew) { mbIsSelected = bNew; } } // Break, default implementations bool E3dObject::IsBreakObjPossible() { return false; } std::unique_ptr E3dObject::GetBreakObj() { return nullptr; } SdrInventor E3dObject::GetObjInventor() const { return SdrInventor::E3d; } SdrObjKind E3dObject::GetObjIdentifier() const { return SdrObjKind::E3D_Object; } // Determine the capabilities of the object void E3dObject::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const { rInfo.bResizeFreeAllowed = true; rInfo.bResizePropAllowed = true; rInfo.bRotateFreeAllowed = true; rInfo.bRotate90Allowed = true; rInfo.bMirrorFreeAllowed = false; rInfo.bMirror45Allowed = false; rInfo.bMirror90Allowed = false; rInfo.bShearAllowed = false; rInfo.bEdgeRadiusAllowed = false; rInfo.bCanConvToPath = false; // no transparence for 3d objects rInfo.bTransparenceAllowed = false; // Convert 3D objects in a group of polygons: // At first not only possible, because the creation of a group of // 2D polygons would be required which need to be sorted by depth, // ie at intersections be cut relative to each other. Also the texture // coordinates were an unsolved problem. rInfo.bCanConvToPoly = false; rInfo.bCanConvToContour = false; rInfo.bCanConvToPathLineToArea = false; rInfo.bCanConvToPolyLineToArea = false; } // resize object, used from old 2d interfaces, e.g. in Move/Scale dialog (F4) void E3dObject::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) { // Movement in X, Y in the eye coordinate system E3dScene* pScene(getRootE3dSceneFromE3dObject()); if(nullptr == pScene) { return; } // transform pos from 2D world to 3D eye const sdr::contact::ViewContactOfE3dScene& rVCScene = static_cast< sdr::contact::ViewContactOfE3dScene& >(pScene->GetViewContact()); const drawinglayer::geometry::ViewInformation3D& aViewInfo3D(rVCScene.getViewInformation3D()); basegfx::B2DPoint aScaleCenter2D(static_cast(rRef.X()), static_cast(rRef.Y())); basegfx::B2DHomMatrix aInverseSceneTransform(rVCScene.getObjectTransformation()); aInverseSceneTransform.invert(); aScaleCenter2D = aInverseSceneTransform * aScaleCenter2D; basegfx::B3DPoint aScaleCenter3D(aScaleCenter2D.getX(), aScaleCenter2D.getY(), 0.5); basegfx::B3DHomMatrix aInverseViewToEye(aViewInfo3D.getDeviceToView() * aViewInfo3D.getProjection()); aInverseViewToEye.invert(); aScaleCenter3D = aInverseViewToEye * aScaleCenter3D; // Get scale factors double fScaleX(xFact); double fScaleY(yFact); // build transform basegfx::B3DHomMatrix aInverseOrientation(aViewInfo3D.getOrientation()); aInverseOrientation.invert(); basegfx::B3DHomMatrix aFullTransform(GetFullTransform()); basegfx::B3DHomMatrix aTrans(aFullTransform); aTrans *= aViewInfo3D.getOrientation(); aTrans.translate(-aScaleCenter3D.getX(), -aScaleCenter3D.getY(), -aScaleCenter3D.getZ()); aTrans.scale(fScaleX, fScaleY, 1.0); aTrans.translate(aScaleCenter3D.getX(), aScaleCenter3D.getY(), aScaleCenter3D.getZ()); aTrans *= aInverseOrientation; aFullTransform.invert(); aTrans *= aFullTransform; // Apply basegfx::B3DHomMatrix aObjTrans(GetTransform()); aObjTrans *= aTrans; E3DModifySceneSnapRectUpdater aUpdater(this); SetTransform(aObjTrans); } // Move object in 2D is needed when using cursor keys void E3dObject::NbcMove(const Size& rSize) { // Movement in X, Y in the eye coordinate system E3dScene* pScene(getRootE3dSceneFromE3dObject()); if(nullptr == pScene) { return; } //Dimensions of the scene in 3D and 2D for comparison tools::Rectangle aRect = pScene->GetSnapRect(); basegfx::B3DHomMatrix aInvDispTransform; E3dScene* pParent(getParentE3dSceneFromE3dObject()); if(nullptr != pParent) { aInvDispTransform = pParent->GetFullTransform(); aInvDispTransform.invert(); } // BoundVolume from 3d world to 3d eye const sdr::contact::ViewContactOfE3dScene& rVCScene = static_cast< sdr::contact::ViewContactOfE3dScene& >(pScene->GetViewContact()); const drawinglayer::geometry::ViewInformation3D& aViewInfo3D(rVCScene.getViewInformation3D()); basegfx::B3DRange aEyeVol(pScene->GetBoundVolume()); aEyeVol.transform(aViewInfo3D.getOrientation()); if ((aRect.GetWidth() == 0) || (aRect.GetHeight() == 0)) throw o3tl::divide_by_zero(); // build relative movement vector in eye coordinates basegfx::B3DPoint aMove( static_cast(rSize.Width()) * aEyeVol.getWidth() / static_cast(aRect.GetWidth()), static_cast(-rSize.Height()) * aEyeVol.getHeight() / static_cast(aRect.GetHeight()), 0.0); basegfx::B3DPoint aPos(0.0, 0.0, 0.0); // movement vector to local coordinates of objects' parent basegfx::B3DHomMatrix aInverseOrientation(aViewInfo3D.getOrientation()); aInverseOrientation.invert(); basegfx::B3DHomMatrix aCompleteTrans(aInvDispTransform * aInverseOrientation); aMove = aCompleteTrans * aMove; aPos = aCompleteTrans * aPos; // build transformation and apply basegfx::B3DHomMatrix aTranslate; aTranslate.translate(aMove.getX() - aPos.getX(), aMove.getY() - aPos.getY(), aMove.getZ() - aPos.getZ()); E3DModifySceneSnapRectUpdater aUpdater(pScene); SetTransform(aTranslate * GetTransform()); } void E3dObject::RecalcSnapRect() { maSnapRect = tools::Rectangle(); } // Inform parent of changes in the structure (eg by transformation), in this // process the object in which the change has occurred is returned. void E3dObject::StructureChanged() { E3dScene* pParent(getParentE3dSceneFromE3dObject()); if(nullptr != pParent) { pParent->InvalidateBoundVolume(); pParent->StructureChanged(); } } E3dScene* E3dObject::getParentE3dSceneFromE3dObject() const { return dynamic_cast< E3dScene* >(getParentSdrObjectFromSdrObject()); } // Determine the top-level scene object E3dScene* E3dObject::getRootE3dSceneFromE3dObject() const { E3dScene* pParent(getParentE3dSceneFromE3dObject()); if(nullptr != pParent) { return pParent->getRootE3dSceneFromE3dObject(); } return nullptr; } // Calculate enclosed volume, including all child objects basegfx::B3DRange E3dObject::RecalcBoundVolume() const { basegfx::B3DRange aRetval; if (utl::ConfigManager::IsFuzzing()) // skip slow path for fuzzing return aRetval; const sdr::contact::ViewContactOfE3d* pVCOfE3D = dynamic_cast< const sdr::contact::ViewContactOfE3d* >(&GetViewContact()); if(pVCOfE3D) { // BoundVolume is without 3D object transformation, use correct sequence const drawinglayer::primitive3d::Primitive3DContainer& xLocalSequence(pVCOfE3D->getVIP3DSWithoutObjectTransform()); if(!xLocalSequence.empty()) { const uno::Sequence< beans::PropertyValue > aEmptyParameters; const drawinglayer::geometry::ViewInformation3D aLocalViewInformation3D(aEmptyParameters); aRetval = xLocalSequence.getB3DRange(aLocalViewInformation3D); } } return aRetval; } // Get enclosed volume and possibly recalculate it const basegfx::B3DRange& E3dObject::GetBoundVolume() const { if(maLocalBoundVol.isEmpty()) { const_cast< E3dObject* >(this)->maLocalBoundVol = RecalcBoundVolume(); } return maLocalBoundVol; } void E3dObject::InvalidateBoundVolume() { maLocalBoundVol.reset(); } // Pass on the changes in transformation to all child objects void E3dObject::SetTransformChanged() { InvalidateBoundVolume(); mbTfHasChanged = true; } // Define the hierarchical transformation over all Parents, store in // maFullTransform and return them const basegfx::B3DHomMatrix& E3dObject::GetFullTransform() const { if(mbTfHasChanged) { basegfx::B3DHomMatrix aNewFullTransformation(maTransformation); E3dScene* pParent(getParentE3dSceneFromE3dObject()); if(nullptr != pParent) { aNewFullTransformation = pParent->GetFullTransform() * aNewFullTransformation; } const_cast< E3dObject* >(this)->maFullTransform = aNewFullTransformation; const_cast< E3dObject* >(this)->mbTfHasChanged = false; } return maFullTransform; } void E3dObject::NbcSetTransform(const basegfx::B3DHomMatrix& rMatrix) { if(maTransformation != rMatrix) { maTransformation = rMatrix; SetTransformChanged(); StructureChanged(); } } // Set transformation matrix with repaint broadcast void E3dObject::SetTransform(const basegfx::B3DHomMatrix& rMatrix) { if(rMatrix != maTransformation) { NbcSetTransform(rMatrix); SetChanged(); BroadcastObjectChange(); if (m_pUserCall != nullptr) m_pUserCall->Changed(*this, SdrUserCallType::Resize, tools::Rectangle()); } } basegfx::B3DPolyPolygon E3dObject::CreateWireframe() const { const basegfx::B3DRange aBoundVolume(GetBoundVolume()); return basegfx::utils::createCubePolyPolygonFromB3DRange(aBoundVolume); } // Get the name of the object (singular) OUString E3dObject::TakeObjNameSingul() const { OUString sName = SvxResId(STR_ObjNameSingulObj3d); OUString aName(GetName()); if (!aName.isEmpty()) { sName += " '" + aName + "'"; } return sName; } // Get the name of the object (plural) OUString E3dObject::TakeObjNamePlural() const { return SvxResId(STR_ObjNamePluralObj3d); } E3dObject* E3dObject::CloneSdrObject(SdrModel& rTargetModel) const { return new E3dObject(rTargetModel, *this); } std::unique_ptr E3dObject::NewGeoData() const { return std::make_unique(); } void E3dObject::SaveGeoData(SdrObjGeoData& rGeo) const { SdrAttrObj::SaveGeoData (rGeo); static_cast(rGeo).maLocalBoundVol = maLocalBoundVol; static_cast(rGeo).maTransformation = maTransformation; } void E3dObject::RestoreGeoData(const SdrObjGeoData& rGeo) { maLocalBoundVol = static_cast(rGeo).maLocalBoundVol; E3DModifySceneSnapRectUpdater aUpdater(this); NbcSetTransform(static_cast(rGeo).maTransformation); SdrAttrObj::RestoreGeoData (rGeo); } // 2D-rotation of a 3D-body, normally this is done by the scene itself. // This is however a correct implementation, because everything that has // happened is a rotation around the axis perpendicular to the screen and that // is regardless of how the scene has been rotated up until now. void E3dObject::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) { // So currently the gluepoints are defined relative to the scene aOutRect. // Before turning the gluepoints are defined relative to the page. They // take no part in the rotation of the scene. To ensure this, there is the // SetGlueReallyAbsolute(sal_True); double fAngleInRad = toRadians(nAngle); basegfx::B3DHomMatrix aRotateZ; aRotateZ.rotate(0.0, 0.0, fAngleInRad); NbcSetTransform(aRotateZ * GetTransform()); SetBoundAndSnapRectsDirty(); // This forces a recalculation of all BoundRects NbcRotateGluePoints(rRef,nAngle,sn,cs); // Rotate the gluepoints (who still // have coordinates relative to the // original page) SetGlueReallyAbsolute(false); // from now they are again relative to BoundRect (that is defined as aOutRect) } std::unique_ptr E3dCompoundObject::CreateObjectSpecificProperties() { return std::make_unique(*this); } E3dCompoundObject::E3dCompoundObject(SdrModel& rSdrModel) : E3dObject(rSdrModel) { } E3dCompoundObject::E3dCompoundObject(SdrModel& rSdrModel, E3dCompoundObject const & rSource) : E3dObject(rSdrModel, rSource) { } E3dCompoundObject::~E3dCompoundObject () { } basegfx::B2DPolyPolygon E3dCompoundObject::TakeXorPoly() const { basegfx::B2DPolyPolygon aRetval; const uno::Sequence< beans::PropertyValue > aEmptyParameters; drawinglayer::geometry::ViewInformation3D aViewInfo3D(aEmptyParameters); E3dScene* pRootScene = fillViewInformation3DForCompoundObject(aViewInfo3D, *this); if(pRootScene) { const sdr::contact::ViewContactOfE3dScene& rVCScene = static_cast< sdr::contact::ViewContactOfE3dScene& >(pRootScene->GetViewContact()); const basegfx::B3DPolyPolygon aCubePolyPolygon(CreateWireframe()); aRetval = basegfx::utils::createB2DPolyPolygonFromB3DPolyPolygon(aCubePolyPolygon, aViewInfo3D.getObjectToView() * GetTransform()); aRetval.transform(rVCScene.getObjectTransformation()); } return aRetval; } sal_uInt32 E3dCompoundObject::GetHdlCount() const { // 8 corners + 1 E3dVolumeMarker (= Wireframe representation) return 9; } void E3dCompoundObject::AddToHdlList(SdrHdlList& rHdlList) const { const uno::Sequence< beans::PropertyValue > aEmptyParameters; drawinglayer::geometry::ViewInformation3D aViewInfo3D(aEmptyParameters); E3dScene* pRootScene = fillViewInformation3DForCompoundObject(aViewInfo3D, *this); if(pRootScene) { const basegfx::B3DRange aBoundVolume(GetBoundVolume()); if(!aBoundVolume.isEmpty()) { const sdr::contact::ViewContactOfE3dScene& rVCScene = static_cast< sdr::contact::ViewContactOfE3dScene& >(pRootScene->GetViewContact()); for(sal_uInt32 a(0); a < 8; a++) { basegfx::B3DPoint aPos3D; switch(a) { case 0 : aPos3D.setX(aBoundVolume.getMinX()); aPos3D.setY(aBoundVolume.getMinY()); aPos3D.setZ(aBoundVolume.getMinZ()); break; case 1 : aPos3D.setX(aBoundVolume.getMinX()); aPos3D.setY(aBoundVolume.getMinY()); aPos3D.setZ(aBoundVolume.getMaxZ()); break; case 2 : aPos3D.setX(aBoundVolume.getMinX()); aPos3D.setY(aBoundVolume.getMaxY()); aPos3D.setZ(aBoundVolume.getMinZ()); break; case 3 : aPos3D.setX(aBoundVolume.getMinX()); aPos3D.setY(aBoundVolume.getMaxY()); aPos3D.setZ(aBoundVolume.getMaxZ()); break; case 4 : aPos3D.setX(aBoundVolume.getMaxX()); aPos3D.setY(aBoundVolume.getMinY()); aPos3D.setZ(aBoundVolume.getMinZ()); break; case 5 : aPos3D.setX(aBoundVolume.getMaxX()); aPos3D.setY(aBoundVolume.getMinY()); aPos3D.setZ(aBoundVolume.getMaxZ()); break; case 6 : aPos3D.setX(aBoundVolume.getMaxX()); aPos3D.setY(aBoundVolume.getMaxY()); aPos3D.setZ(aBoundVolume.getMinZ()); break; case 7 : aPos3D.setX(aBoundVolume.getMaxX()); aPos3D.setY(aBoundVolume.getMaxY()); aPos3D.setZ(aBoundVolume.getMaxZ()); break; } // to 3d view coor aPos3D *= aViewInfo3D.getObjectToView() * GetTransform(); // create 2d relative scene basegfx::B2DPoint aPos2D(aPos3D.getX(), aPos3D.getY()); // to 2d world coor aPos2D *= rVCScene.getObjectTransformation(); rHdlList.AddHdl(std::make_unique(Point(basegfx::fround(aPos2D.getX()), basegfx::fround(aPos2D.getY())), SdrHdlKind::BezierWeight)); } } } const basegfx::B2DPolyPolygon aPolyPolygon(TakeXorPoly()); if(aPolyPolygon.count()) { rHdlList.AddHdl(std::make_unique(aPolyPolygon)); } } SdrObjKind E3dCompoundObject::GetObjIdentifier() const { return SdrObjKind::E3D_CompoundObject; } void E3dCompoundObject::RecalcSnapRect() { const uno::Sequence< beans::PropertyValue > aEmptyParameters; drawinglayer::geometry::ViewInformation3D aViewInfo3D(aEmptyParameters); E3dScene* pRootScene = fillViewInformation3DForCompoundObject(aViewInfo3D, *this); maSnapRect = tools::Rectangle(); if(!pRootScene) return; // get VC of 3D candidate const sdr::contact::ViewContactOfE3d* pVCOfE3D = dynamic_cast< const sdr::contact::ViewContactOfE3d* >(&GetViewContact()); if(!pVCOfE3D) return; // get 3D primitive sequence const drawinglayer::primitive3d::Primitive3DContainer xLocalSequence(pVCOfE3D->getViewIndependentPrimitive3DContainer()); if(xLocalSequence.empty()) return; // get BoundVolume basegfx::B3DRange aBoundVolume(xLocalSequence.getB3DRange(aViewInfo3D)); // transform bound volume to relative scene coordinates aBoundVolume.transform(aViewInfo3D.getObjectToView()); // build 2d relative scene range basegfx::B2DRange aSnapRange( aBoundVolume.getMinX(), aBoundVolume.getMinY(), aBoundVolume.getMaxX(), aBoundVolume.getMaxY()); // transform to 2D world coordinates const sdr::contact::ViewContactOfE3dScene& rVCScene = static_cast< sdr::contact::ViewContactOfE3dScene& >(pRootScene->GetViewContact()); aSnapRange.transform(rVCScene.getObjectTransformation()); // snap to integer maSnapRect = tools::Rectangle( sal_Int32(floor(aSnapRange.getMinX())), sal_Int32(floor(aSnapRange.getMinY())), sal_Int32(ceil(aSnapRange.getMaxX())), sal_Int32(ceil(aSnapRange.getMaxY()))); } E3dCompoundObject* E3dCompoundObject::CloneSdrObject(SdrModel& rTargetModel) const { return new E3dCompoundObject(rTargetModel, *this); } // convert given basegfx::B3DPolyPolygon to screen coor basegfx::B2DPolyPolygon E3dCompoundObject::TransformToScreenCoor(const basegfx::B3DPolyPolygon& rCandidate) const { const uno::Sequence< beans::PropertyValue > aEmptyParameters; drawinglayer::geometry::ViewInformation3D aViewInfo3D(aEmptyParameters); E3dScene* pRootScene = fillViewInformation3DForCompoundObject(aViewInfo3D, *this); basegfx::B2DPolyPolygon aRetval; if(pRootScene) { aRetval = basegfx::utils::createB2DPolyPolygonFromB3DPolyPolygon(rCandidate, aViewInfo3D.getObjectToView() * GetTransform()); const sdr::contact::ViewContactOfE3dScene& rVCScene = static_cast< sdr::contact::ViewContactOfE3dScene& >(pRootScene->GetViewContact()); aRetval.transform(rVCScene.getObjectTransformation()); } return aRetval; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */