Clipping of Surfaces During Dynamic Draw with Gumball

Hello,

I am working on a C++ plugin that allows a user to manipulate surfaces in real time utilizing a polyhedral mesh. I am attempting to have the user manipulate a Gumball object to help them move vertices on the mesh. The issue I am having is that the surfaces that I generate with the dynamic draw method end up clipped at certain angles. My guess is this has something to do with the clipping planes, but I am not sure what that would be. The following image shows the surface with no clipping:

From a different angle, the same surface looks like this:

Here is the relevant code that performs the drawing:

VertexGumballGetXForm.hpp

class VertexGumballGetXForm : public CRhinoGetXform
{
public:
	VertexGumballGetXForm(MeshSurfaces* a_MeshSurfaces, const CRhinoGetObject& a_VertObjects, int a_NumThreads);
	~VertexGumballGetXForm() = default;

	CRhinoGet::result moveGumball();
	void updateMeshSurfaces();

	// m_GumballDC Wrapper Methods
	void Disable();
	void Enable(unsigned int a_DocSerialNumber);
	void EnableGumballDraw(bool a_Enable);
	const ON_Xform PreTransform();
	void setBaseGumball();

	void updateGumball();

	// Overrides
	BOOL CalculateTransform(CRhinoViewport& vp, const ON_3dPoint& pt, ON_Xform& xform);
	void OnMouseDown(CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const ON_2iPoint* p);
	void OnMouseMove(CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const ON_2iPoint* p);
	void DynamicDraw(CRhinoDisplayPipeline& dp, const ON_3dPoint& pt);

private:
	// Initial data
	const CRhinoCommandContext&			  m_Context;

	MeshSurfaces*						  m_MeshSurfaces;
	MeshType							  m_Mesh;
	ON_Mesh								  m_OnMesh; //TODO:	 This probably is not needed anymore

	std::vector<ON_3dPoint>				  m_InitialVertPositions;
	std::vector<int>					  m_ModifiedVertIndices;
	std::vector<VertexHandle>			  m_ModifiedVertHandles;
	std::vector<struct Handle>			  m_ModifiedNurbsHandles;

	// Used for dynamic draw updates
	bool								  m_Draw;
	bool								  m_Hidden;

	ON_Mesh								  m_UpdateMesh;
	ON_Mesh								  m_StaticMesh;
	std::vector<ON_3dPoint>				  m_UpdatedVertPositions;
	ON_3dVector							  m_TranslationDirection;
	std::vector<NurbsSurfaceToHandlePair> m_UpdatedNurbs;

	NurbsSurfaceToHandlePairBuilder*	  m_NurbsSurfaceToHandlePairBuilder;

	CRhinoGumballDisplayConduit*          m_GumballDC;
	CRhinoGumball*						  m_Gumball;

	void initializeVertData(const CRhinoGetObject& a_VertObjects);
	void initializeUpdateMesh();
	void initializeGumballDC(const CRhinoGetObject& a_VertObjects);
	void initializeGumball(const CRhinoGetObject& a_VertObjects);
	void updateUpdatedNurbs();
	void initializeModifiedNurbsHandles();

	void hide();
	void hideSurfacePatches();
	void hideMeshObject();
	void show();
	void showSurfacePatches();
	void showMeshObject();
};

VertexGumballGetXForm.cpp

VertexGumballGetXForm::VertexGumballGetXForm(MeshSurfaces* a_MeshSurfaces, const CRhinoGetObject& a_VertObjects, int a_NumThreads) :
	m_MeshSurfaces(a_MeshSurfaces),
	m_Mesh(MeshType(*a_MeshSurfaces->getMesh())),
	m_OnMesh(ON_Mesh(*a_MeshSurfaces->getOnMesh())),
	m_Context(a_MeshSurfaces->getContext()),
	m_NurbsSurfaceToHandlePairBuilder(new NurbsSurfaceToHandlePairBuilder(a_NumThreads)),
	m_Draw(false),
	m_Hidden(false),
	m_TranslationDirection(ON_3dVector(0, 0, 0))
{
	initializeVertData(a_VertObjects);
	initializeGumball(a_VertObjects);
	initializeGumballDC(a_VertObjects);
	updateUpdatedNurbs();	//TODO: Find better way to get Handles?
	initializeModifiedNurbsHandles();
	//SetBasePoint(m_InitialVertPositions.at(0));	  // Sets base point to first vertex (Note: at least one vertex will be selected)
	//DrawLineFromPoint(m_InitialVertPositions.at(0), true);
	this->AppendObjects(a_VertObjects);
}

/*
 *  Input: A CRhinoGetObject containing data for vertices selected by user
 *
 *	Initializes m_ModifiedVertIndices, m_ModifiedVertHandles, m_InitialVertPositions,
 *  and m_UpdatedVertPositions
 */
void VertexGumballGetXForm::initializeVertData(const CRhinoGetObject& a_VertObjects)
{
	int t_NumVerts = a_VertObjects.ObjectCount();
	for (int i = 0; i < t_NumVerts; ++i)
	{
		// Grab vertex index
		CRhinoObjRef t_VertRef = a_VertObjects.Object(i);
		int			 t_VertIndex = t_VertRef.GeometryComponentIndex().m_index;
		m_ModifiedVertIndices.push_back(t_VertIndex);

		// Grab vertex handle
		m_ModifiedVertHandles.push_back(VertexHandle(m_ModifiedVertIndices.at(i)));

		// Grab initial vertex position
		ON_3dPoint t_VertPosition = m_MeshSurfaces->getOnMesh()->m_dV[t_VertIndex];
		m_InitialVertPositions.push_back(t_VertPosition);
		m_UpdatedVertPositions.push_back(t_VertPosition);
	}
}

void VertexGumballGetXForm::initializeGumballDC(const CRhinoGetObject& a_VertObjects)
{
	m_GumballDC = new CRhinoGumballDisplayConduit();
}

void VertexGumballGetXForm::initializeGumball(const CRhinoGetObject& a_VertObjects)
{
	ON_BoundingBox t_BBox = getBoundingBox(a_VertObjects);
	m_Gumball = new CRhinoGumball();
	m_Gumball->SetFromBoundingBox(t_BBox);
			 
	// Turn off rotation
	m_Gumball->m_appearance.m_bEnableXRotate = false;
	m_Gumball->m_appearance.m_bEnableYRotate = false;
	m_Gumball->m_appearance.m_bEnableZRotate = false;
			 
	// Turn off scale
	m_Gumball->m_appearance.m_bEnableXScale = false;
	m_Gumball->m_appearance.m_bEnableYScale = false;
	m_Gumball->m_appearance.m_bEnableZScale = false;
}

/*
 *	Updates the collection of ON_NurbsSurface to new positions
 *  based on changes made in the mesh
 */
void VertexGumballGetXForm::updateUpdatedNurbs()
{
	// Calculate new points
	m_UpdatedVertPositions = m_InitialVertPositions;
	for (size_t i = 0; i < m_UpdatedVertPositions.size(); ++i)
	{
		ON_3dPoint t_NewPt = m_UpdatedVertPositions.at(i);
		t_NewPt.x = t_NewPt.x + m_TranslationDirection.x;
		t_NewPt.y = t_NewPt.y + m_TranslationDirection.y;
		t_NewPt.z = t_NewPt.z + m_TranslationDirection.z;
		m_UpdatedVertPositions.at(i) = t_NewPt;
	}

	//OnMesh
	for (size_t i = 0; i < m_ModifiedVertIndices.size(); ++i)
	{
		int t_VertIndex = m_ModifiedVertIndices.at(i);
		ON_3dPoint t_NewVert = m_UpdatedVertPositions.at(i);
		m_OnMesh.SetVertex(t_VertIndex, t_NewVert);
	}

	//Need to invalidate things so they may be recalculated
	m_OnMesh.InvalidateVertexBoundingBox();
	m_OnMesh.InvalidateVertexNormalBoundingBox();
	m_OnMesh.InvalidateCurvatureStats();
	m_OnMesh.m_FN.SetCount(0);
	m_OnMesh.m_N.SetCount(0);
	m_OnMesh.ComputeFaceNormals();
	m_OnMesh.ComputeVertexNormals();
	m_OnMesh.SetClosed(-1);

	//Mesh
	for (size_t i = 0; i < m_UpdatedVertPositions.size(); ++i)
	{
		// New vertex position
		OpenMesh::Vec3d t_NewVert((m_UpdatedVertPositions.at(i).x),
			(m_UpdatedVertPositions.at(i).y),
			(m_UpdatedVertPositions.at(i).z));


		m_Mesh.set_point(m_ModifiedVertHandles.at(i), t_NewVert);
	}

	m_UpdatedNurbs = m_NurbsSurfaceToHandlePairBuilder->buildNurbsSurfaceToHandlePairs(m_Mesh, m_ModifiedVertHandles);
}

void VertexGumballGetXForm::initializeModifiedNurbsHandles()
{
	std::set<struct Handle> t_Handles;
	for (NurbsSurfaceToHandlePair t_Pair : m_UpdatedNurbs)
	{
		t_Handles.emplace(t_Pair.m_Handle);
	}

	m_ModifiedNurbsHandles = std::vector<struct Handle>(t_Handles.begin(), t_Handles.end());
}

CRhinoGet::result VertexGumballGetXForm::moveGumball()
{
	if (m_GumballDC == 0)
		return CRhinoGet::cancel;

	if (!m_GumballDC->PreTransform().IsIdentity())
	{
		m_bHaveXform = TRUE;
		m_xform = m_GumballDC->PreTransform();
	}

	SetBasePoint(m_GumballDC->BaseGumball().m_frame.m_plane.Origin(), false);

	const_cast<CRhinoXformObjectList&>(ObjectList()).EnableDisplayFeedback(true);
	if (!m_xform.IsIdentity())
		const_cast<CRhinoXformObjectList&>(ObjectList()).UpdateDisplayFeedbackXform(m_xform);

	SetGetPointCursor(RhinoApp().m_default_cursor);
	CRhinoGet::result t_Result = GetPoint(0, true);

	const_cast<CRhinoXformObjectList&>(ObjectList()).EnableDisplayFeedback(false);

	return t_Result;
}

/*
 *  Called after the user confirms the new locations of the selected vertices.
 */
void VertexGumballGetXForm::updateMeshSurfaces()
{
	if (m_Hidden)
	{
		show();
	}
	m_MeshSurfaces->update(m_UpdatedNurbs, m_Mesh, m_ModifiedVertHandles, m_OnMesh);
}

void VertexGumballGetXForm::show()
{
	showSurfacePatches();
	showMeshObject();
	m_Hidden = false;
}

void VertexGumballGetXForm::showSurfacePatches()
{
	m_MeshSurfaces->showSurfacePatches(m_ModifiedNurbsHandles);
}

void VertexGumballGetXForm::showMeshObject()
{
	m_MeshSurfaces->showMeshObject();
}

void VertexGumballGetXForm::Disable()
{
	m_GumballDC->Disable();
}

void VertexGumballGetXForm::Enable(unsigned int a_DocSerialNumber)
{
	m_GumballDC->Enable(a_DocSerialNumber);
}

void VertexGumballGetXForm::EnableGumballDraw(bool a_Enable)
{
	m_GumballDC->EnableGumballDraw(a_Enable);
}

const ON_Xform VertexGumballGetXForm::PreTransform()
{
	return m_GumballDC->PreTransform();
}

void VertexGumballGetXForm::setBaseGumball()
{
	m_GumballDC->SetBaseGumball(*m_Gumball);
}

void VertexGumballGetXForm::updateGumball()
{
	if (!m_GumballDC->m_drag_settings.m_bRelocateGumball)
	{
		ON_Xform xform = m_GumballDC->TotalTransform();
		m_GumballDC->SetPreTransform(xform);
	}

	// update location of base gumball
	CRhinoGumballFrame t_GumballDCFrame = m_GumballDC->Gumball().m_frame;
	CRhinoGumballFrame t_GumballFrame = m_Gumball->m_frame;
	t_GumballFrame.m_plane = t_GumballDCFrame.m_plane;
	t_GumballFrame.m_scale_grip_distance = t_GumballDCFrame.m_scale_grip_distance;
	m_Gumball->m_frame = t_GumballFrame;
}

/*
 *  Input:  CRhinoViewport: Rhino Viewport
 *			ON_3dPoint: The 3d location of the mouse pointer
 *			ON_Xform: The resulting Xform is stored here
 *
 *	Output: BOOL: TRUE for Xform != IdentityTransform
 */
BOOL VertexGumballGetXForm::CalculateTransform(CRhinoViewport& vp, const ON_3dPoint& pt, ON_Xform& xform)
{
	if (m_GumballDC == 0)
		return FALSE;

	if (m_GumballDC->m_drag_settings.m_bRelocateGumball)
		xform = m_GumballDC->PreTransform();
	else
		xform = m_GumballDC->TotalTransform();

	m_TranslationDirection = ON_3dVector(xform.m_xform[0][3], xform.m_xform[1][3], xform.m_xform[2][3]);

	return xform.IsValid() ? TRUE : FALSE;

	//m_TranslationDirection = pt - m_basepoint;
	//if (m_TranslationDirection.IsTiny())
	//	xform = ON_Xform::IdentityTransformation;
	//else
	//	xform = ON_Xform::TranslationTransformation(m_TranslationDirection);
	//return (xform.IsValid()) ? TRUE : FALSE;
}

void VertexGumballGetXForm::OnMouseDown(CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const ON_2iPoint* p)
{
	if (p == 0 || m_GumballDC == 0 || m_GumballDC->m_pick_result.m_gumball_mode != gb_mode_nothing)
		return;

	m_GumballDC->m_pick_result.SetToDefaultPickResult();

	CRhinoPickContext t_PickContext;
	t_PickContext.m_view = vp.ParentView();
	t_PickContext.m_pick_style = CRhinoPickContext::point_pick;
	if (vp.SetClippingRegionTransformation(p->x, p->y, t_PickContext.m_pick_region))
	{
		vp.VP().GetFrustumLine(p->x, p->y, t_PickContext.m_pick_line);
		t_PickContext.UpdateClippingPlanes();
		m_GumballDC->PickGumball(t_PickContext, this);
	}
}

/*
 *	Create Nurbs Surfaces each time the mouse pointer is moved
 */
void VertexGumballGetXForm::OnMouseMove(CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const ON_2iPoint* p)
{
	if (p == 0 || m_GumballDC == 0 || m_GumballDC->m_pick_result.m_gumball_mode == gb_mode_nothing)
		return;

	m_GumballDC->CheckShiftAndControlKeys();

	ON_Line t_WorldLine;
	if (!vp.VP().GetFrustumLine(p->x, p->y, t_WorldLine))
		t_WorldLine = ON_Line(ON_UNSET_POINT, ON_UNSET_POINT);

	bool t_Success = m_GumballDC->UpdateGumball(pt, t_WorldLine);
	if (t_Success)
	{
		ON_Xform t_Xform;
		m_Draw = false;

		if (CalculateTransform(vp, pt, t_Xform))
		{
			updateUpdatedNurbs();
			m_Draw = true;
		}
		CRhinoGetXform::OnMouseMove(vp, flags, pt, p);
	}
}

/*
 *	Draw the Mesh and Nurbs Surfaces (with Zebras) to the viewport
 */
void VertexGumballGetXForm::DynamicDraw(CRhinoDisplayPipeline& dp, const ON_3dPoint& pt)
{
	if (m_Draw)
	{
		if (!m_Hidden)
		{
			hide();
		}

		//TODO: Draw only modified portion of Mesh
		// Draw Mesh
		dp.DrawMesh(m_OnMesh, true, false, nullptr);

		// Draw Surfaces
		const CRhinoZebraAnalysisSettings& t_ZebraSettings = RhinoApp().AppSettings().ZebraAnalysisSettings();
		const ON_MeshParameters& t_MeshParameters = m_Context.Document()->Properties().AnalysisMeshSettings();
		const ON_Color& t_Color = RhinoApp().AppSettings().SelectedObjectColor();

		for (NurbsSurfaceToHandlePair t_Current : m_UpdatedNurbs)
		{
			ON_Brep* t_Brep = t_Current.m_NurbsSurface.BrepForm();
			dp.DrawZebraPreview(t_Brep, t_ZebraSettings, t_MeshParameters, t_Color, nullptr);
			delete t_Brep;
		}

	//	const CDisplayPipelineAttributes* attrs = dp.DisplayAttrs();
	//	CDisplayPipelineMaterial dpm = *attrs->m_pMaterial;
	//	dp.SetupDisplayMaterial(dpm, m_Context.m_doc);
	//	dp.SetupDisplayMaterial(dpm, &m_doc, nullptr);

	//	//int wireframe_density = 5;
	//	for (size_t i = 0; i < patches.size(); i++)
	//	{
	//		ON_Brep* b = patches.at(i).BrepForm();
	//		dp.DrawShadedBrep(b, &dpm, nullptr);
	//		delete b;
	//	}
	}

	CRhinoGetXform::DynamicDraw(dp, pt);
}

/*
 *  Hides the Document Surfaces and Mesh before the user moves the vertices.
 */
void VertexGumballGetXForm::hide()
{
	//TODO: hide unaffected patches (except for first ring around)?
	hideSurfacePatches();
	hideMeshObject();
	m_Hidden = true;
}

void VertexGumballGetXForm::hideSurfacePatches()
{
	m_MeshSurfaces->hideSurfacePatches(m_ModifiedNurbsHandles);
}

void VertexGumballGetXForm::hideMeshObject()
{
	m_MeshSurfaces->hideMeshObject(); 
}

In the above code, I made use of the example code offered by Rhino on using CRhinoGetXform for dynamic draw as well as the ones for Gumball display.

I appreciate any and all help in trying to solve this issue.

It looks like your dynamic manipulation takes the objects that are being drawn outside the bounds of the display conduit. Mabye CRhinoGetXform::SetDynamicDrawBounds(const ON_BoundingBox& bbox) can help if you call it with a large enough bounding box?

Hello Menno,

I tried your solution, but I am still running into the same issue. Here is where I create the large bounding box and attempt to use it in CRhinoGetXform::SetDynamicDrawBounds(const ON_BoundingBox& bbox):

CRhinoCommand::result move_vertices(MeshSurfaces* a_MeshSurfaces, const CRhinoCommandContext& a_Context)
{
	//Grab mesh vertex to move
	CRhinoGetObject t_GetVerts;
	t_GetVerts.SetCommandPrompt(L"Select Mesh Vertex/Vertices to Move");
	t_GetVerts.SetGeometryFilter(CRhinoObject::meshvertex_filter);
	t_GetVerts.GetObjects(1, 0);
	if (t_GetVerts.CommandResult() != CRhinoCommand::success)
		return t_GetVerts.CommandResult();

    //Here is the BoundingBox I create
	ON_BoundingBox t_BBox(ON_3dPoint(-1000, -1000, -1000), ON_3dPoint(1000,1000,1000));
	if (!t_BBox.IsValid())
		return CRhinoCommand::failure;

	int t_NumThreads = 1;
	VertexGumballGetXForm t_VG(a_MeshSurfaces, t_GetVerts, t_NumThreads);
	t_VG.SetDynamicDrawBounds(t_BBox);   //Call to set BoundingBox

	while (true)
	{
		t_VG.setBaseGumball();
		t_VG.EnableGumballDraw(true);
		t_VG.Enable(a_Context.Document()->RuntimeSerialNumber());
		a_Context.Document()->Redraw();

		if (t_VG.PreTransform().IsIdentity())
		{
			t_VG.SetCommandPrompt(L"Drag gumball");
			t_VG.AcceptNothing(FALSE);
		}
		else
		{
			t_VG.SetCommandPrompt(L"Drag gumball. Press Enter when done");
			t_VG.AcceptNothing(TRUE);
		}

		t_VG.moveGumball();
		t_VG.EnableGumballDraw(false);
		t_VG.Disable();

		if (t_VG.CommandResult() != CRhinoCommand::success)
			break;

		CRhinoGet::result t_Result = t_VG.Result();
		if (t_Result == CRhinoGet::point)
		{
			t_VG.updateGumball();
			continue;
		}

		break;
	}

	t_VG.EnableGumballDraw(false);
	t_VG.Disable();

	if (!t_VG.PreTransform().IsIdentity())
	{
		t_VG.updateMeshSurfaces();
	}

	a_Context.Document()->Redraw();
	return CRhinoCommand::success;
}

Let me know what you think.

Here is a video that may give a better sense of the problem (after suggested fix):

Sorry, I’m all out of options. My best advice is to look for functions or member variables on e.g. display conduit or displaypipeline object that sets clipping planes or a bounding box.