/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "selection_mtor_drag.h" #include "selection_.h" #include "selection_render.h" #include "selection_volume.h" #include "selection_selector.h" #include "selection_mtable_translate.h" #include "selection_mtable_brush.h" #include "selection_mtable_extrude.h" #include "selectionlib.h" #include "brush.h" #include "grid.h" inline PlaneSelectable* Instance_getPlaneSelectable( scene::Instance& instance ){ return InstanceTypeCast::cast( instance ); } class PlaneSelectableSelectPlanes : public scene::Graph::Walker { Selector& m_selector; SelectionTest& m_test; PlaneCallback m_selectedPlaneCallback; public: PlaneSelectableSelectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ) : m_selector( selector ), m_test( test ), m_selectedPlaneCallback( selectedPlaneCallback ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const override { if ( path.top().get().visible() && Instance_isSelected( instance ) ) { PlaneSelectable* planeSelectable = Instance_getPlaneSelectable( instance ); if ( planeSelectable != 0 ) { planeSelectable->selectPlanes( m_selector, m_test, m_selectedPlaneCallback ); } } return true; } }; class PlaneSelectableSelectReversedPlanes : public scene::Graph::Walker { Selector& m_selector; const SelectedPlanes& m_selectedPlanes; public: PlaneSelectableSelectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ) : m_selector( selector ), m_selectedPlanes( selectedPlanes ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const override { if ( path.top().get().visible() && Instance_isSelected( instance ) ) { PlaneSelectable* planeSelectable = Instance_getPlaneSelectable( instance ); if ( planeSelectable != 0 ) { planeSelectable->selectReversedPlanes( m_selector, m_selectedPlanes ); } } return true; } }; void Scene_forEachPlaneSelectable_selectPlanes( scene::Graph& graph, Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){ graph.traverse( PlaneSelectableSelectPlanes( selector, test, selectedPlaneCallback ) ); } void Scene_forEachPlaneSelectable_selectReversedPlanes( scene::Graph& graph, Selector& selector, const SelectedPlanes& selectedPlanes ){ graph.traverse( PlaneSelectableSelectReversedPlanes( selector, selectedPlanes ) ); } class PlaneLess { public: bool operator()( const Plane3& plane, const Plane3& other ) const { return std::tie( plane.a, plane.b, plane.c, plane.d ) < std::tie( other.a, other.b, other.c, other.d ); } }; typedef std::set PlaneSet; class SelectedPlaneSet : public SelectedPlanes { PlaneSet m_selectedPlanes; public: bool empty() const { return m_selectedPlanes.empty(); } void insert( const Plane3& plane ){ m_selectedPlanes.insert( plane ); } bool contains( const Plane3& plane ) const override { return m_selectedPlanes.contains( plane ); } typedef MemberCaller InsertCaller; }; bool Scene_forEachPlaneSelectable_selectPlanes( scene::Graph& graph, Selector& selector, SelectionTest& test ){ SelectedPlaneSet selectedPlanes; Scene_forEachPlaneSelectable_selectPlanes( graph, selector, test, SelectedPlaneSet::InsertCaller( selectedPlanes ) ); Scene_forEachPlaneSelectable_selectReversedPlanes( graph, selector, selectedPlanes ); return !selectedPlanes.empty(); } template class PlaneselectableVisibleSelectedVisitor : public SelectionSystem::Visitor { const Functor& m_functor; public: PlaneselectableVisibleSelectedVisitor( const Functor& functor ) : m_functor( functor ){ } void visit( scene::Instance& instance ) const override { PlaneSelectable* planeSelectable = Instance_getPlaneSelectable( instance ); if ( planeSelectable != 0 && instance.path().top().get().visible() ) { m_functor( *planeSelectable ); } } }; template inline const Functor& Scene_forEachVisibleSelectedPlaneselectable( const Functor& functor ){ GlobalSelectionSystem().foreachSelected( PlaneselectableVisibleSelectedVisitor( functor ) ); return functor; } PlaneSelectable::BestPlaneData Scene_forEachPlaneSelectable_bestPlane( SelectionTest& test ){ PlaneSelectable::BestPlaneData planeData; auto bestPlaneDirect = [&test, &planeData]( PlaneSelectable& planeSelectable ){ planeSelectable.bestPlaneDirect( test, planeData ); }; Scene_forEachVisibleSelectedPlaneselectable( bestPlaneDirect ); if( !planeData.valid() ){ auto bestPlaneIndirect = [&test, &planeData]( PlaneSelectable& planeSelectable ){ planeSelectable.bestPlaneIndirect( test, planeData ); }; Scene_forEachVisibleSelectedPlaneselectable( bestPlaneIndirect ); } return planeData; } bool Scene_forEachPlaneSelectable_selectPlanes2( SelectionTest& test, TranslateAxis2& translateAxis ){ const auto planeData = Scene_forEachPlaneSelectable_bestPlane( test ); if( planeData.valid() ){ const Plane3 plane = planeData.m_plane; if( planeData.direct() ){ // direct translateAxis.set0( point_on_plane( plane, test.getVolume().GetViewMatrix(), DeviceVector( 0, 0 ) ), plane ); } else{ // indirect test.BeginMesh( g_matrix4_identity ); /* may introduce some screen space offset in manipulatable to handle far-from-edge clicks perfectly; thought clicking not so far isn't too nasty, right? */ translateAxis.set0( vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( planeData.m_closestPoint, 1 ) ) ), plane ); } auto selectByPlane = [plane]( PlaneSelectable& planeSelectable ){ planeSelectable.selectByPlane( plane ); }; Scene_forEachVisibleSelectedPlaneselectable( selectByPlane ); } return planeData.valid(); } PlaneSelectable::BestPlaneData Scene_forEachSelectedBrush_bestPlane( SelectionTest& test ){ PlaneSelectable::BestPlaneData planeData; auto bestPlaneDirect = [&test, &planeData]( BrushInstance& brushInstance ){ brushInstance.bestPlaneDirect( test, planeData ); }; Scene_forEachVisibleSelectedBrush( bestPlaneDirect ); if( !planeData.valid() ){ auto bestPlaneIndirect = [&test, &planeData]( BrushInstance& brushInstance ){ brushInstance.bestPlaneIndirect( test, planeData ); }; Scene_forEachVisibleSelectedBrush( bestPlaneIndirect ); } return planeData; } PlaneSelectable::BestPlaneData Scene_forEachBrush_bestPlane( SelectionTest& test ){ if( g_SelectedFaceInstances.empty() ){ return Scene_forEachSelectedBrush_bestPlane( test ); } else{ PlaneSelectable::BestPlaneData planeData; auto bestPlaneDirect = [&test, &planeData]( BrushInstance& brushInstance ){ if( brushInstance.isSelected() || brushInstance.isSelectedComponents() ) brushInstance.bestPlaneDirect( test, planeData ); }; Scene_forEachVisibleBrush( GlobalSceneGraph(), bestPlaneDirect ); if( !planeData.valid() ){ auto bestPlaneIndirect = [&test, &planeData]( BrushInstance& brushInstance ){ if( brushInstance.isSelected() || brushInstance.isSelectedComponents() ) brushInstance.bestPlaneIndirect( test, planeData ); }; Scene_forEachVisibleBrush( GlobalSceneGraph(), bestPlaneIndirect ); } return planeData; } } bool Scene_forEachBrush_setupExtrude( SelectionTest& test, DragExtrudeFaces& extrudeFaces ){ const auto planeData = Scene_forEachBrush_bestPlane( test ); if( planeData.valid() ){ const Plane3 plane = planeData.m_plane; if( planeData.direct() ){ // direct extrudeFaces.set0( point_on_plane( plane, test.getVolume().GetViewMatrix(), DeviceVector( 0, 0 ) ), plane ); } else{ // indirect test.BeginMesh( g_matrix4_identity ); /* may introduce some screen space offset in manipulatable to handle far-from-edge clicks perfectly; thought clicking not so far isn't too nasty, right? */ extrudeFaces.set0( vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( planeData.m_closestPoint, 1 ) ) ), plane ); } extrudeFaces.m_extrudeSources.clear(); auto gatherExtrude = [plane, &extrudeFaces]( BrushInstance& brushInstance ){ if( brushInstance.isSelected() || brushInstance.isSelectedComponents() ){ bool m_pushed = false; auto gatherFaceInstances = [plane, &extrudeFaces, &brushInstance, &m_pushed]( FaceInstance& face ){ if( face.isSelected() || plane3_equal( plane, face.getFace().plane3() ) ){ if( !m_pushed ){ extrudeFaces.m_extrudeSources.emplace_back(); extrudeFaces.m_extrudeSources.back().m_brushInstance = &brushInstance; m_pushed = true; } extrudeFaces.m_extrudeSources.back().m_faces.emplace_back(); extrudeFaces.m_extrudeSources.back().m_faces.back().m_face = &face.getFace(); extrudeFaces.m_extrudeSources.back().m_faces.back().m_planepoints = face.getFace().getPlane().getPlanePoints(); } }; Brush_ForEachFaceInstance( brushInstance, gatherFaceInstances ); brushInstance.setSelectedComponents( false, SelectionSystem::eFace ); brushInstance.setSelected( false ); } }; Scene_forEachVisibleBrush( GlobalSceneGraph(), gatherExtrude ); } return planeData.valid(); } bool selection_selectVerticesOrFaceVertices( SelectionTest& test ){ { /* try to hit vertices */ DeepBestSelector deepSelector; Scene_TestSelect_Component_Selected( deepSelector, test, test.getVolume(), SelectionSystem::eVertex ); if( !deepSelector.best().empty() ){ for ( Selectable* s : deepSelector.best() ) s->setSelected( true ); return true; } } /* otherwise select vertices of brush faces, which lay on best plane */ const auto planeData = Scene_forEachSelectedBrush_bestPlane( test ); if( planeData.valid() ){ auto selectVerticesOnPlane = [plane = planeData.m_plane]( BrushInstance& brushInstance ){ brushInstance.selectVerticesOnPlane( plane ); }; Scene_forEachVisibleSelectedBrush( selectVerticesOnPlane ); } return planeData.valid(); } bool scene_insert_brush_vertices( const View& view, TranslateFreeXY_Z& freeDragXY_Z ){ SelectionVolume test( view ); ScenePointSelector selector; if( view.fill() ) Scene_forEachVisible_testselect_scene_point( view, selector, test ); else Scene_forEachVisible_testselect_scene_point_selected_brushes( view, selector, test ); test.BeginMesh( g_matrix4_identity, true ); if( selector.isSelected() ){ freeDragXY_Z.set0( vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, selector.best().depth(), 1 ) ) ) ); DoubleVector3 point = testSelected_scene_snapped_point( test, selector ); if( !view.fill() ){ point -= view.getViewDir() * GetGridSize(); } Brush::VertexModeVertices vertexModeVertices; vertexModeVertices.push_back( Brush::VertexModeVertex( point, true ) ); if( selector.face() ) vertexModeVertices.back().m_faces.push_back( selector.face() ); UndoableCommand undo( "InsertBrushVertices" ); Scene_forEachSelectedBrush( [&vertexModeVertices]( BrushInstance& brush ){ brush.insert_vertices( vertexModeVertices ); } ); return true; } else if( !view.fill() ){ //+two points freeDragXY_Z.set0( g_vector3_identity ); const AABB bounds = GlobalSelectionSystem().getBoundsSelected(); if( aabb_valid( bounds ) ){ DoubleVector3 xy = vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, 0, 1 ) ) ); vector3_snap( xy, GetSnapGridSize() ); DoubleVector3 a( xy ), b( xy ); const std::size_t max = vector3_max_abs_component_index( view.getViewDir() ); a[max] = bounds.origin[max] + bounds.extents[max]; b[max] = bounds.origin[max] - bounds.extents[max]; Brush::VertexModeVertices vertexModeVertices; vertexModeVertices.push_back( Brush::VertexModeVertex( a, true ) ); vertexModeVertices.push_back( Brush::VertexModeVertex( b, true ) ); UndoableCommand undo( "InsertBrushVertices" ); Scene_forEachSelectedBrush( [&vertexModeVertices]( BrushInstance& brush ){ brush.insert_vertices( vertexModeVertices ); } ); return true; } } return false; } template class ComponentSelectionTestableVisibleSelectedVisitor : public SelectionSystem::Visitor { const Functor& m_functor; public: ComponentSelectionTestableVisibleSelectedVisitor( const Functor& functor ) : m_functor( functor ){ } void visit( scene::Instance& instance ) const override { ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable( instance ); if ( componentSelectionTestable != 0 && instance.path().top().get().visible() ) { m_functor( *componentSelectionTestable ); } } }; template inline const Functor& Scene_forEachVisibleSelectedComponentSelectionTestable( const Functor& functor ){ GlobalSelectionSystem().foreachSelected( ComponentSelectionTestableVisibleSelectedVisitor( functor ) ); return functor; } class ResizeTranslatable : public Translatable { void translate( const Vector3& translation ) override { Scene_Translate_Component_Selected( GlobalSceneGraph(), translation ); } }; class DragManipulatorImpl final : public DragManipulator { ResizeTranslatable m_resize; TranslateFree m_freeResize; TranslateAxis2 m_axisResize; TranslateFreeXY_Z m_freeDragXY_Z; DragNewBrush m_dragNewBrush; DragExtrudeFaces m_dragExtrudeFaces; bool m_dragSelected; //drag selected primitives or components bool m_selected; //components selected temporally for drag bool m_selected2; //planeselectables in cam with alt bool m_newBrush; bool m_extrudeFaces; public: DragManipulatorImpl( Translatable& translatable, AllTransformable& transformable ) : m_resize(), m_freeResize( m_resize ), m_axisResize( m_resize ), m_freeDragXY_Z( translatable, transformable ), m_renderCircle( 2 << 3 ){ setSelected( false ); draw_circle( m_renderCircle.m_vertices.size() >> 3, 5, m_renderCircle.m_vertices.data(), RemapXYZ() ); } Manipulatable* GetManipulatable() override { if( m_newBrush ) return &m_dragNewBrush; else if( m_extrudeFaces ) return &m_dragExtrudeFaces; else if( m_selected ) return &m_freeResize; else if( m_selected2 ) return &m_axisResize; else return &m_freeDragXY_Z; } void testSelect( const View& view, const Matrix4& pivot2world ) override { SelectionPool selector; SelectionVolume test( view ); if( g_modifiers == ( c_modifierAlt | c_modifierControl ) && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive && ( GlobalSelectionSystem().countSelected() != 0 || !g_SelectedFaceInstances.empty() ) ){ // extrude m_extrudeFaces = Scene_forEachBrush_setupExtrude( test, m_dragExtrudeFaces ); } else if( GlobalSelectionSystem().countSelected() != 0 ){ if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ){ if( g_modifiers == c_modifierAlt ){ if( view.fill() ){ // alt resize m_selected2 = Scene_forEachPlaneSelectable_selectPlanes2( test, m_axisResize ); } else{ // alt vertices drag m_selected = selection_selectVerticesOrFaceVertices( test ); } } else if( g_modifiers == c_modifierNone ){ BooleanSelector booleanSelector; Scene_TestSelect_Primitive( booleanSelector, test, view ); if ( booleanSelector.isSelected() ) { /* hit a primitive */ m_dragSelected = true; /* drag a primitive */ test.BeginMesh( g_matrix4_identity, true ); m_freeDragXY_Z.set0( vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, booleanSelector.bestIntersection().depth(), 1 ) ) ) ); } else{ /* haven't hit a primitive */ m_selected = Scene_forEachPlaneSelectable_selectPlanes( GlobalSceneGraph(), selector, test ); /* select faces on planeSelectables */ } } } else if( g_modifiers == c_modifierNone ){ // components BestSelector bestSelector; Scene_TestSelect_Component_Selected( bestSelector, test, view, GlobalSelectionSystem().ComponentMode() ); /* drag components */ for ( Selectable* s : bestSelector.best() ){ if ( !s->isSelected() ) GlobalSelectionSystem().setSelectedAllComponents( false ); selector.addSelectable( SelectionIntersection( 0, 0 ), s ); m_dragSelected = true; } if( bestSelector.bestIntersection().valid() ){ test.BeginMesh( g_matrix4_identity, true ); m_freeDragXY_Z.set0( vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, bestSelector.bestIntersection().depth(), 1 ) ) ) ); } else{ if( GlobalSelectionSystem().countSelectedComponents() != 0 ){ /* drag, even if hit nothing, but got selected */ m_dragSelected = true; m_freeDragXY_Z.set0( g_vector3_identity ); } else if( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex ){ /* otherwise insert */ m_dragSelected = g_bTmpComponentMode = scene_insert_brush_vertices( view, m_freeDragXY_Z ); //hack: indicating not a tmp mode return; } } } for ( SelectableSortedSet::value_type& value : selector ) value.second->setSelected( true ); g_bTmpComponentMode = m_selected | m_selected2; } else if( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive && g_3DCreateBrushes && g_modifiers == c_modifierNone ){ m_newBrush = true; BestPointSelector bestPointSelector; Scene_TestSelect_Primitive( bestPointSelector, test, view ); Vector3 start; test.BeginMesh( g_matrix4_identity, true ); if( bestPointSelector.isSelected() ){ start = vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, bestPointSelector.best().depth(), 1 ) ) ); } else{ const Vector3 pnear = vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, -1, 1 ) ) ); const Vector3 pfar = vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, 1, 1 ) ) ); start = vector3_normalised( pfar - pnear ) * ( 256.f + GetGridSize() * sqrt( 3.0 ) ) + pnear; } vector3_snap( start, GetSnapGridSize() ); m_dragNewBrush.set0( start ); } } void setSelected( bool select ) override { m_dragSelected = select; m_selected = select; m_selected2 = select; m_newBrush = select; m_extrudeFaces = select; } bool isSelected() const override { return m_dragSelected || m_selected || m_selected2 || m_newBrush || m_extrudeFaces; } void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world ) override { if( !m_polygons.empty() ){ renderer.SetState( m_state_wire, Renderer::eWireframeOnly ); renderer.SetState( m_state_wire, Renderer::eFullMaterials ); if( m_polygons.back().size() == 1 ){ Pivot2World_viewplaneSpace( m_renderCircle.m_viewplaneSpace, matrix4_translation_for_vec3( m_polygons.back()[0] ), volume.GetModelview(), volume.GetProjection(), volume.GetViewport() ); renderer.addRenderable( m_renderCircle, m_renderCircle.m_viewplaneSpace ); } else{ renderer.addRenderable( m_renderPoly, g_matrix4_identity ); } } } void highlight( const View& view, const Matrix4& pivot2world ) override { SelectionVolume test( view ); std::vector> polygons; /* conditions structure respects one in testSelect() */ if( g_modifiers == ( c_modifierAlt | c_modifierControl ) && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive && ( GlobalSelectionSystem().countSelected() != 0 || !g_SelectedFaceInstances.empty() ) ){ // extrude if( const auto planeData = Scene_forEachBrush_bestPlane( test ); planeData.valid() ){ auto gatherPolygonsByPlane = [plane = planeData.m_plane, &polygons]( BrushInstance& brushInstance ){ if( brushInstance.isSelected() || brushInstance.isSelectedComponents() ) brushInstance.gatherPolygonsByPlane( plane, polygons, false ); }; Scene_forEachVisibleBrush( GlobalSceneGraph(), gatherPolygonsByPlane ); } } else if( GlobalSelectionSystem().countSelected() != 0 ){ if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ){ if( g_modifiers == c_modifierAlt ){ if( view.fill() ){ // alt resize if( const auto planeData = Scene_forEachPlaneSelectable_bestPlane( test ); planeData.valid() ){ auto gatherPolygonsByPlane = [plane = planeData.m_plane, &polygons]( PlaneSelectable& planeSelectable ){ planeSelectable.gatherPolygonsByPlane( plane, polygons ); }; Scene_forEachVisibleSelectedPlaneselectable( gatherPolygonsByPlane ); } } else{ // alt vertices drag SelectionIntersection intersection; const SelectionSystem::EComponentMode mode = SelectionSystem::eVertex; auto gatherComponentsHighlight = [&polygons, &intersection, &test, mode]( const ComponentSelectionTestable& componentSelectionTestable ){ componentSelectionTestable.gatherComponentsHighlight( polygons, intersection, test, mode ); }; Scene_forEachVisibleSelectedComponentSelectionTestable( gatherComponentsHighlight ); if( polygons.empty() ){ if( const auto planeData = Scene_forEachSelectedBrush_bestPlane( test ); planeData.valid() ){ auto gatherPolygonsByPlane = [plane = planeData.m_plane, &polygons]( BrushInstance& brushInstance ){ brushInstance.gatherPolygonsByPlane( plane, polygons ); }; Scene_forEachVisibleSelectedBrush( gatherPolygonsByPlane ); } } } } } else if( g_modifiers == c_modifierNone // components || g_modifiers == c_modifierShift // hack: these respect to the RadiantSelectionSystem::SelectPoint || ( g_modifiers == c_modifierControl && GlobalSelectionSystem().ComponentMode() == SelectionSystem::EComponentMode::eFace ) ){ SelectionIntersection intersection; const SelectionSystem::EComponentMode mode = GlobalSelectionSystem().ComponentMode(); auto gatherComponentsHighlight = [&polygons, &intersection, &test, mode]( const ComponentSelectionTestable& componentSelectionTestable ){ componentSelectionTestable.gatherComponentsHighlight( polygons, intersection, test, mode ); }; Scene_forEachVisibleSelectedComponentSelectionTestable( gatherComponentsHighlight ); } } if( m_polygons != polygons ){ m_polygons.swap( polygons ); SceneChangeNotify(); } } private: std::vector> m_polygons; struct RenderablePoly: public OpenGLRenderable { const std::vector>& m_polygons; RenderablePoly( const std::vector>& polygons ) : m_polygons( polygons ){ } void render( RenderStateFlags state ) const override { gl().glPolygonOffset( -2, -2 ); for( const auto& poly : m_polygons ){ gl().glVertexPointer( 3, GL_FLOAT, sizeof( m_polygons[0][0] ), poly[0].data() ); gl().glDrawArrays( GL_POLYGON, 0, GLsizei( poly.size() ) ); } gl().glPolygonOffset( -1, 1 ); // restore default } }; RenderablePoly m_renderPoly{ m_polygons }; struct RenderableCircle : public OpenGLRenderable { Array m_vertices; Matrix4 m_viewplaneSpace; RenderableCircle( std::size_t size ) : m_vertices( size ){ } void render( RenderStateFlags state ) const override { gl().glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_vertices.data()->vertex ); gl().glDrawArrays( GL_LINE_LOOP, 0, GLsizei( m_vertices.size() ) ); } }; RenderableCircle m_renderCircle; }; DragManipulator* New_DragManipulator( Translatable& translatable, AllTransformable& transformable ){ return new DragManipulatorImpl( translatable, transformable ); }