/* 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.h" #include "debugging/debugging.h" #include #include #include #include "windowobserver.h" #include "iundo.h" #include "ientity.h" #include "cullable.h" #include "renderable.h" #include "selectable.h" #include "editable.h" #include "math/frustum.h" #include "signal/signal.h" #include "generic/object.h" #include "selectionlib.h" #include "render.h" #include "view.h" #include "renderer.h" #include "stream/stringstream.h" #include "eclasslib.h" #include "generic/bitfield.h" #include "generic/static.h" #include "pivot.h" #include "stringio.h" #include "container/container.h" #include "grid.h" TextOutputStream& ostream_write(TextOutputStream& t, const Vector4& v) { return t << "[ " << v.x() << " " << v.y() << " " << v.z() << " " << v.w() << " ]"; } TextOutputStream& ostream_write(TextOutputStream& t, const Matrix4& m) { return t << "[ " << m.x() << " " << m.y() << " " << m.z() << " " << m.t() << " ]"; } struct Pivot2World { Matrix4 m_worldSpace; Matrix4 m_viewpointSpace; Matrix4 m_viewplaneSpace; Vector3 m_axis_screen; void update(const Matrix4& pivot2world, const Matrix4& modelview, const Matrix4& projection, const Matrix4& viewport) { Pivot2World_worldSpace(m_worldSpace, pivot2world, modelview, projection, viewport); Pivot2World_viewpointSpace(m_viewpointSpace, m_axis_screen, pivot2world, modelview, projection, viewport); Pivot2World_viewplaneSpace(m_viewplaneSpace, pivot2world, modelview, projection, viewport); } }; void point_for_device_point(Vector3& point, const Matrix4& device2object, const float x, const float y, const float z) { // transform from normalised device coords to object coords point = vector4_projected(matrix4_transformed_vector4(device2object, Vector4(x, y, z, 1))); } void ray_for_device_point(Ray& ray, const Matrix4& device2object, const float x, const float y) { // point at x, y, zNear point_for_device_point(ray.origin, device2object, x, y, -1); // point at x, y, zFar point_for_device_point(ray.direction, device2object, x, y, 1); // construct ray vector3_subtract(ray.direction, ray.origin); vector3_normalise(ray.direction); } bool sphere_intersect_ray(const Vector3& origin, float radius, const Ray& ray, Vector3& intersection) { intersection = vector3_subtracted(origin, ray.origin); const double a = vector3_dot(intersection, ray.direction); const double d = radius * radius - (vector3_dot(intersection, intersection) - a * a); if(d > 0) { intersection = vector3_added(ray.origin, vector3_scaled(ray.direction, a - sqrt(d))); return true; } else { intersection = vector3_added( ray.origin, vector3_scaled(ray.direction, a)); return false; } } void ray_intersect_ray(const Ray& ray, const Ray& other, Vector3& intersection) { intersection = vector3_subtracted(ray.origin, other.origin); //float a = 1;//vector3_dot(ray.direction, ray.direction); // always >= 0 double dot = vector3_dot(ray.direction, other.direction); //float c = 1;//vector3_dot(other.direction, other.direction); // always >= 0 double d = vector3_dot(ray.direction, intersection); double e = vector3_dot(other.direction, intersection); double D = 1 - dot*dot;//a*c - dot*dot; // always >= 0 if (D < 0.000001) { // the lines are almost parallel intersection = vector3_added(other.origin, vector3_scaled(other.direction, e)); } else { intersection = vector3_added(other.origin, vector3_scaled(other.direction, (e - dot*d) / D)); } } const Vector3 g_origin(0, 0, 0); const float g_radius = 64; void point_on_sphere(Vector3& point, const Matrix4& device2object, const float x, const float y) { Ray ray; ray_for_device_point(ray, device2object, x, y); sphere_intersect_ray(g_origin, g_radius, ray, point); } void point_on_axis(Vector3& point, const Vector3& axis, const Matrix4& device2object, const float x, const float y) { Ray ray; ray_for_device_point(ray, device2object, x, y); ray_intersect_ray(ray, Ray(Vector3(0, 0, 0), axis), point); } void point_on_plane(Vector3& point, const Matrix4& device2object, const float x, const float y) { Matrix4 object2device(matrix4_full_inverse(device2object)); point = vector4_projected(matrix4_transformed_vector4(device2object, Vector4(x, y, object2device[14] / object2device[15], 1))); } //! a and b are unit vectors .. returns angle in radians inline float angle_between(const Vector3& a, const Vector3& b) { return static_cast(2.0 * atan2( vector3_length(vector3_subtracted(a, b)), vector3_length(vector3_added(a, b)) )); } #if defined(_DEBUG) class test_quat { public: test_quat(const Vector3& from, const Vector3& to) { Vector4 quaternion(quaternion_for_unit_vectors(from, to)); Matrix4 matrix(matrix4_rotation_for_quaternion(quaternion_multiplied_by_quaternion(quaternion, c_quaternion_identity))); } private: }; static test_quat bleh(g_vector3_axis_x, g_vector3_axis_y); #endif //! axis is a unit vector inline void constrain_to_axis(Vector3& vec, const Vector3& axis) { vec = vector3_normalised(vector3_added(vec, vector3_scaled(axis, -vector3_dot(vec, axis)))); } //! a and b are unit vectors .. a and b must be orthogonal to axis .. returns angle in radians float angle_for_axis(const Vector3& a, const Vector3& b, const Vector3& axis) { if(vector3_dot(axis, vector3_cross(a, b)) > 0.0) return angle_between(a, b); else return -angle_between(a, b); } float distance_for_axis(const Vector3& a, const Vector3& b, const Vector3& axis) { return static_cast(vector3_dot(b, axis) - vector3_dot(a, axis)); } class Manipulatable { public: virtual void Construct(const Matrix4& device2manip, const float x, const float y) = 0; virtual void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) = 0; }; void transform_local2object(Matrix4& object, const Matrix4& local, const Matrix4& local2object) { object = matrix4_multiplied_by_matrix4( matrix4_multiplied_by_matrix4(local2object, local), matrix4_full_inverse(local2object) ); } class Rotatable { public: virtual void rotate(const Quaternion& rotation) = 0; }; class RotateFree : public Manipulatable { Vector3 m_start; Rotatable& m_rotatable; public: RotateFree(Rotatable& rotatable) : m_rotatable(rotatable) { } void Construct(const Matrix4& device2manip, const float x, const float y) { point_on_sphere(m_start, device2manip, x, y); vector3_normalise(m_start); } void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) { Vector3 current; point_on_sphere(current, device2manip, x, y); vector3_normalise(current); m_rotatable.rotate(quaternion_for_unit_vectors(m_start, current)); } }; class RotateAxis : public Manipulatable { Vector3 m_axis; Vector3 m_start; Rotatable& m_rotatable; public: RotateAxis(Rotatable& rotatable) : m_rotatable(rotatable) { } void Construct(const Matrix4& device2manip, const float x, const float y) { point_on_sphere(m_start, device2manip, x, y); constrain_to_axis(m_start, m_axis); } /// \brief Converts current position to a normalised vector orthogonal to axis. void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) { Vector3 current; point_on_sphere(current, device2manip, x, y); constrain_to_axis(current, m_axis); m_rotatable.rotate(quaternion_for_axisangle(m_axis, angle_for_axis(m_start, current, m_axis))); } void SetAxis(const Vector3& axis) { m_axis = axis; } }; void translation_local2object(Vector3& object, const Vector3& local, const Matrix4& local2object) { object = matrix4_get_translation_vec3( matrix4_multiplied_by_matrix4( matrix4_translated_by_vec3(local2object, local), matrix4_full_inverse(local2object) ) ); } class Translatable { public: virtual void translate(const Vector3& translation) = 0; }; class TranslateAxis : public Manipulatable { Vector3 m_start; Vector3 m_axis; Translatable& m_translatable; public: TranslateAxis(Translatable& translatable) : m_translatable(translatable) { } void Construct(const Matrix4& device2manip, const float x, const float y) { point_on_axis(m_start, m_axis, device2manip, x, y); } void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) { Vector3 current; point_on_axis(current, m_axis, device2manip, x, y); current = vector3_scaled(m_axis, distance_for_axis(m_start, current, m_axis)); translation_local2object(current, current, manip2object); vector3_snap(current, GetSnapGridSize()); m_translatable.translate(current); } void SetAxis(const Vector3& axis) { m_axis = axis; } }; class TranslateFree : public Manipulatable { private: Vector3 m_start; Translatable& m_translatable; public: TranslateFree(Translatable& translatable) : m_translatable(translatable) { } void Construct(const Matrix4& device2manip, const float x, const float y) { point_on_plane(m_start, device2manip, x, y); } void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) { Vector3 current; point_on_plane(current, device2manip, x, y); current = vector3_subtracted(current, m_start); translation_local2object(current, current, manip2object); vector3_snap(current, GetSnapGridSize()); m_translatable.translate(current); } }; class Scalable { public: virtual void scale(const Vector3& scaling) = 0; }; class ScaleAxis : public Manipulatable { private: Vector3 m_start; Vector3 m_axis; Scalable& m_scalable; public: ScaleAxis(Scalable& scalable) : m_scalable(scalable) { } void Construct(const Matrix4& device2manip, const float x, const float y) { point_on_axis(m_start, m_axis, device2manip, x, y); } void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) { Vector3 current; point_on_axis(current, m_axis, device2manip, x, y); Vector3 delta = vector3_subtracted(current, m_start); translation_local2object(delta, delta, manip2object); vector3_snap(delta, GetSnapGridSize()); Vector3 start(vector3_snapped(m_start, GetSnapGridSize())); Vector3 scale( start[0] == 0 ? 1 : 1 + delta[0] / start[0], start[1] == 0 ? 1 : 1 + delta[1] / start[1], start[2] == 0 ? 1 : 1 + delta[2] / start[2] ); m_scalable.scale(scale); } void SetAxis(const Vector3& axis) { m_axis = axis; } }; class ScaleFree : public Manipulatable { private: Vector3 m_start; Scalable& m_scalable; public: ScaleFree(Scalable& scalable) : m_scalable(scalable) { } void Construct(const Matrix4& device2manip, const float x, const float y) { point_on_plane(m_start, device2manip, x, y); } void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) { Vector3 current; point_on_plane(current, device2manip, x, y); Vector3 delta = vector3_subtracted(current, m_start); translation_local2object(delta, delta, manip2object); vector3_snap(delta, GetSnapGridSize()); Vector3 start(vector3_snapped(m_start, GetSnapGridSize())); Vector3 scale( start[0] == 0 ? 1 : 1 + delta[0] / start[0], start[1] == 0 ? 1 : 1 + delta[1] / start[1], start[2] == 0 ? 1 : 1 + delta[2] / start[2] ); m_scalable.scale(scale); } }; class RenderableClippedPrimitive : public OpenGLRenderable { struct primitive_t { PointVertex m_points[9]; std::size_t m_count; }; Matrix4 m_inverse; std::vector m_primitives; public: Matrix4 m_world; void render(RenderStateFlags state) const { for(std::size_t i=0; i P[1])) // an upward crossing || (((*prev)[1] > P[1]) && ((*cur)[1] <= P[1]))) { // a downward crossing // compute the actual edge-ray intersect x-coordinate float vt = (float)(P[1] - (*prev)[1]) / ((*cur)[1] - (*prev)[1]); if (P[0] < (*prev)[0] + vt * ((*cur)[0] - (*prev)[0])) // P[0] < intersect { ++crossings; // a valid crossing of y=P[1] right of P[0] } } } return (crossings & 0x1) != 0; // 0 if even (out), and 1 if odd (in) } inline double triangle_signed_area_XY(const Vector3& p0, const Vector3& p1, const Vector3& p2) { return ((p1[0] - p0[0]) * (p2[1] - p0[1])) - ((p2[0] - p0[0]) * (p1[1] - p0[1])); } enum clipcull_t { eClipCullNone, eClipCullCW, eClipCullCCW, }; inline SelectionIntersection select_point_from_clipped(Vector4& clipped) { return SelectionIntersection(clipped[2] / clipped[3], static_cast(vector3_length_squared(Vector3(clipped[0] / clipped[3], clipped[1] / clipped[3], 0)))); } void BestPoint(std::size_t count, Vector4 clipped[9], SelectionIntersection& best, clipcull_t cull) { Vector3 normalised[9]; { for(std::size_t i=0; i 2) { double signed_area = triangle_signed_area_XY(normalised[0], normalised[1], normalised[2]); if((cull == eClipCullCW && signed_area > 0) || (cull == eClipCullCCW && signed_area < 0)) return; } if(count == 2) { Segment3D segment(normalised[0], normalised[1]); Point3D point = segment_closest_point_to_point(segment, Vector3(0, 0, 0)); assign_if_closer(best, SelectionIntersection(point.z(), 0)); } else if(count > 2 && !point_test_polygon_2d(Vector3(0, 0, 0), normalised, normalised + count)) { point_iterator_t end = normalised + count; for(point_iterator_t previous = end-1, current = normalised; current != end; previous = current, ++current) { Segment3D segment(*previous, *current); Point3D point = segment_closest_point_to_point(segment, Vector3(0, 0, 0)); float depth = point.z(); point.z() = 0; float distance = static_cast(vector3_length_squared(point)); assign_if_closer(best, SelectionIntersection(depth, distance)); } } else if(count > 2) { assign_if_closer( best, SelectionIntersection( static_cast(ray_distance_to_plane( Ray(Vector3(0, 0, 0), Vector3(0, 0, 1)), plane3_for_points(normalised[0], normalised[1], normalised[2]) )), 0 ) ); } #if defined(DEBUG_SELECTION) if(count >= 2) g_render_clipped.insert(clipped, count); #endif } void LineStrip_BestPoint(const Matrix4& local2view, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best) { Vector4 clipped[2]; for(std::size_t i = 0; (i + 1) < size; ++i) { const std::size_t count = matrix4_clip_line(local2view, vertex3f_to_vector3(vertices[i].vertex), vertex3f_to_vector3(vertices[i + 1].vertex), clipped); BestPoint(count, clipped, best, eClipCullNone); } } void LineLoop_BestPoint(const Matrix4& local2view, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best) { Vector4 clipped[2]; for(std::size_t i = 0; i < size; ++i) { const std::size_t count = matrix4_clip_line(local2view, vertex3f_to_vector3(vertices[i].vertex), vertex3f_to_vector3(vertices[(i+1)%size].vertex), clipped); BestPoint(count, clipped, best, eClipCullNone); } } void Line_BestPoint(const Matrix4& local2view, const PointVertex vertices[2], SelectionIntersection& best) { Vector4 clipped[2]; const std::size_t count = matrix4_clip_line(local2view, vertex3f_to_vector3(vertices[0].vertex), vertex3f_to_vector3(vertices[1].vertex), clipped); BestPoint(count, clipped, best, eClipCullNone); } void Circle_BestPoint(const Matrix4& local2view, clipcull_t cull, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best) { Vector4 clipped[9]; for(std::size_t i=0; i((*x).vertex), reinterpret_cast((*y).vertex), reinterpret_cast((*z).vertex), clipped ), clipped, best, cull ); } } typedef std::multimap SelectableSortedSet; class SelectionPool : public Selector { SelectableSortedSet m_pool; SelectionIntersection m_intersection; Selectable* m_selectable; public: void pushSelectable(Selectable& selectable) { m_intersection = SelectionIntersection(); m_selectable = &selectable; } void popSelectable() { addSelectable(m_intersection, m_selectable); m_intersection = SelectionIntersection(); } void addIntersection(const SelectionIntersection& intersection) { assign_if_closer(m_intersection, intersection); } void addSelectable(const SelectionIntersection& intersection, Selectable* selectable) { if(intersection.valid()) { m_pool.insert(SelectableSortedSet::value_type(intersection, selectable)); } } typedef SelectableSortedSet::iterator iterator; iterator begin() { return m_pool.begin(); } iterator end() { return m_pool.end(); } bool failed() { return m_pool.empty(); } }; const Colour4b g_colour_sphere(0, 0, 0, 255); const Colour4b g_colour_screen(0, 255, 255, 255); const Colour4b g_colour_selected(255, 255, 0, 255); inline const Colour4b& colourSelected(const Colour4b& colour, bool selected) { return (selected) ? g_colour_selected : colour; } template inline void draw_semicircle(const std::size_t segments, const float radius, PointVertex* vertices, remap_policy remap) { const double increment = c_pi / double(segments << 2); std::size_t count = 0; float x = radius; float y = 0; remap_policy::set(vertices[segments << 2].vertex, -radius, 0, 0); while(count < segments) { PointVertex* i = vertices + count; PointVertex* j = vertices + ((segments << 1) - (count + 1)); PointVertex* k = i + (segments << 1); PointVertex* l = j + (segments << 1); #if 0 PointVertex* m = i + (segments << 2); PointVertex* n = j + (segments << 2); PointVertex* o = k + (segments << 2); PointVertex* p = l + (segments << 2); #endif remap_policy::set(i->vertex, x,-y, 0); remap_policy::set(k->vertex,-y,-x, 0); #if 0 remap_policy::set(m->vertex,-x, y, 0); remap_policy::set(o->vertex, y, x, 0); #endif ++count; { const double theta = increment * count; x = static_cast(radius * cos(theta)); y = static_cast(radius * sin(theta)); } remap_policy::set(j->vertex, y,-x, 0); remap_policy::set(l->vertex,-x,-y, 0); #if 0 remap_policy::set(n->vertex,-y, x, 0); remap_policy::set(p->vertex, x, y, 0); #endif } } class Manipulator { public: virtual Manipulatable* GetManipulatable() = 0; virtual void testSelect(const View& view, const Matrix4& pivot2world) { } virtual void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) { } virtual void setSelected(bool select) = 0; virtual bool isSelected() const = 0; }; inline Vector3 normalised_safe(const Vector3& self) { if(vector3_equal(self, g_vector3_identity)) { return g_vector3_identity; } return vector3_normalised(self); } class RotateManipulator : public Manipulator { struct RenderableCircle : public OpenGLRenderable { Array m_vertices; RenderableCircle(std::size_t size) : m_vertices(size) { } void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_vertices.data()->colour); glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_vertices.data()->vertex); glDrawArrays(GL_LINE_LOOP, 0, GLsizei(m_vertices.size())); } void setColour(const Colour4b& colour) { for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) { (*i).colour = colour; } } }; struct RenderableSemiCircle : public OpenGLRenderable { Array m_vertices; RenderableSemiCircle(std::size_t size) : m_vertices(size) { } void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_vertices.data()->colour); glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_vertices.data()->vertex); glDrawArrays(GL_LINE_STRIP, 0, GLsizei(m_vertices.size())); } void setColour(const Colour4b& colour) { for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) { (*i).colour = colour; } } }; RotateFree m_free; RotateAxis m_axis; Vector3 m_axis_screen; RenderableSemiCircle m_circle_x; RenderableSemiCircle m_circle_y; RenderableSemiCircle m_circle_z; RenderableCircle m_circle_screen; RenderableCircle m_circle_sphere; SelectableBool m_selectable_x; SelectableBool m_selectable_y; SelectableBool m_selectable_z; SelectableBool m_selectable_screen; SelectableBool m_selectable_sphere; Pivot2World m_pivot; Matrix4 m_local2world_x; Matrix4 m_local2world_y; Matrix4 m_local2world_z; bool m_circle_x_visible; bool m_circle_y_visible; bool m_circle_z_visible; public: static Shader* m_state_outer; RotateManipulator(Rotatable& rotatable, std::size_t segments, float radius) : m_free(rotatable), m_axis(rotatable), m_circle_x((segments << 2) + 1), m_circle_y((segments << 2) + 1), m_circle_z((segments << 2) + 1), m_circle_screen(segments<<3), m_circle_sphere(segments<<3) { draw_semicircle(segments, radius, m_circle_x.m_vertices.data(), RemapYZX()); draw_semicircle(segments, radius, m_circle_y.m_vertices.data(), RemapZXY()); draw_semicircle(segments, radius, m_circle_z.m_vertices.data(), RemapXYZ()); draw_circle(segments, radius * 1.15f, m_circle_screen.m_vertices.data(), RemapXYZ()); draw_circle(segments, radius, m_circle_sphere.m_vertices.data(), RemapXYZ()); } void UpdateColours() { m_circle_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); m_circle_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); m_circle_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); m_circle_screen.setColour(colourSelected(g_colour_screen, m_selectable_screen.isSelected())); m_circle_sphere.setColour(colourSelected(g_colour_sphere, false)); } void updateCircleTransforms() { Vector3 localViewpoint(matrix4_transformed_direction(matrix4_transposed(m_pivot.m_worldSpace), vector4_to_vector3(m_pivot.m_viewpointSpace.z()))); m_circle_x_visible = !vector3_equal_epsilon(g_vector3_axis_x, localViewpoint, 1e-6f); if(m_circle_x_visible) { m_local2world_x = g_matrix4_identity; vector4_to_vector3(m_local2world_x.y()) = normalised_safe( vector3_cross(g_vector3_axis_x, localViewpoint) ); vector4_to_vector3(m_local2world_x.z()) = normalised_safe( vector3_cross(vector4_to_vector3(m_local2world_x.x()), vector4_to_vector3(m_local2world_x.y())) ); matrix4_premultiply_by_matrix4(m_local2world_x, m_pivot.m_worldSpace); } m_circle_y_visible = !vector3_equal_epsilon(g_vector3_axis_y, localViewpoint, 1e-6f); if(m_circle_y_visible) { m_local2world_y = g_matrix4_identity; vector4_to_vector3(m_local2world_y.z()) = normalised_safe( vector3_cross(g_vector3_axis_y, localViewpoint) ); vector4_to_vector3(m_local2world_y.x()) = normalised_safe( vector3_cross(vector4_to_vector3(m_local2world_y.y()), vector4_to_vector3(m_local2world_y.z())) ); matrix4_premultiply_by_matrix4(m_local2world_y, m_pivot.m_worldSpace); } m_circle_z_visible = !vector3_equal_epsilon(g_vector3_axis_z, localViewpoint, 1e-6f); if(m_circle_z_visible) { m_local2world_z = g_matrix4_identity; vector4_to_vector3(m_local2world_z.x()) = normalised_safe( vector3_cross(g_vector3_axis_z, localViewpoint) ); vector4_to_vector3(m_local2world_z.y()) = normalised_safe( vector3_cross(vector4_to_vector3(m_local2world_z.z()), vector4_to_vector3(m_local2world_z.x())) ); matrix4_premultiply_by_matrix4(m_local2world_z, m_pivot.m_worldSpace); } } void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) { m_pivot.update(pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); updateCircleTransforms(); // temp hack UpdateColours(); renderer.SetState(m_state_outer, Renderer::eWireframeOnly); renderer.SetState(m_state_outer, Renderer::eFullMaterials); renderer.addRenderable(m_circle_screen, m_pivot.m_viewpointSpace); renderer.addRenderable(m_circle_sphere, m_pivot.m_viewpointSpace); if(m_circle_x_visible) { renderer.addRenderable(m_circle_x, m_local2world_x); } if(m_circle_y_visible) { renderer.addRenderable(m_circle_y, m_local2world_y); } if(m_circle_z_visible) { renderer.addRenderable(m_circle_z, m_local2world_z); } } void testSelect(const View& view, const Matrix4& pivot2world) { m_pivot.update(pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport()); updateCircleTransforms(); SelectionPool selector; { { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_local2world_x)); #if defined(DEBUG_SELECTION) g_render_clipped.construct(view.GetViewMatrix()); #endif SelectionIntersection best; LineStrip_BestPoint(local2view, m_circle_x.m_vertices.data(), m_circle_x.m_vertices.size(), best); selector.addSelectable(best, &m_selectable_x); } { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_local2world_y)); #if defined(DEBUG_SELECTION) g_render_clipped.construct(view.GetViewMatrix()); #endif SelectionIntersection best; LineStrip_BestPoint(local2view, m_circle_y.m_vertices.data(), m_circle_y.m_vertices.size(), best); selector.addSelectable(best, &m_selectable_y); } { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_local2world_z)); #if defined(DEBUG_SELECTION) g_render_clipped.construct(view.GetViewMatrix()); #endif SelectionIntersection best; LineStrip_BestPoint(local2view, m_circle_z.m_vertices.data(), m_circle_z.m_vertices.size(), best); selector.addSelectable(best, &m_selectable_z); } } { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_viewpointSpace)); { SelectionIntersection best; LineLoop_BestPoint(local2view, m_circle_screen.m_vertices.data(), m_circle_screen.m_vertices.size(), best); selector.addSelectable(best, &m_selectable_screen); } { SelectionIntersection best; Circle_BestPoint(local2view, eClipCullCW, m_circle_sphere.m_vertices.data(), m_circle_sphere.m_vertices.size(), best); selector.addSelectable(best, &m_selectable_sphere); } } m_axis_screen = m_pivot.m_axis_screen; if(!selector.failed()) { (*selector.begin()).second->setSelected(true); } } Manipulatable* GetManipulatable() { if(m_selectable_x.isSelected()) { m_axis.SetAxis(g_vector3_axis_x); return &m_axis; } else if(m_selectable_y.isSelected()) { m_axis.SetAxis(g_vector3_axis_y); return &m_axis; } else if(m_selectable_z.isSelected()) { m_axis.SetAxis(g_vector3_axis_z); return &m_axis; } else if(m_selectable_screen.isSelected()) { m_axis.SetAxis(m_axis_screen); return &m_axis; } else return &m_free; } void setSelected(bool select) { m_selectable_x.setSelected(select); m_selectable_y.setSelected(select); m_selectable_z.setSelected(select); m_selectable_screen.setSelected(select); } bool isSelected() const { return m_selectable_x.isSelected() | m_selectable_y.isSelected() | m_selectable_z.isSelected() | m_selectable_screen.isSelected() | m_selectable_sphere.isSelected(); } }; Shader* RotateManipulator::m_state_outer; const float arrowhead_length = 16; const float arrowhead_radius = 4; inline void draw_arrowline(const float length, PointVertex* line, const std::size_t axis) { (*line++).vertex = vertex3f_identity; (*line).vertex = vertex3f_identity; vertex3f_to_array((*line).vertex)[axis] = length - arrowhead_length; } template inline void draw_arrowhead(const std::size_t segments, const float length, FlatShadedVertex* vertices, VertexRemap, NormalRemap) { std::size_t head_tris = (segments << 3); const double head_segment = c_2pi / head_tris; for(std::size_t i = 0; i < head_tris; ++i) { { FlatShadedVertex& point = vertices[i*6+0]; VertexRemap::x(point.vertex) = length - arrowhead_length; VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos(i * head_segment)); VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin(i * head_segment)); NormalRemap::x(point.normal) = arrowhead_radius / arrowhead_length; NormalRemap::y(point.normal) = static_cast(cos(i * head_segment)); NormalRemap::z(point.normal) = static_cast(sin(i * head_segment)); } { FlatShadedVertex& point = vertices[i*6+1]; VertexRemap::x(point.vertex) = length; VertexRemap::y(point.vertex) = 0; VertexRemap::z(point.vertex) = 0; NormalRemap::x(point.normal) = arrowhead_radius / arrowhead_length; NormalRemap::y(point.normal) = static_cast(cos((i + 0.5) * head_segment)); NormalRemap::z(point.normal) = static_cast(sin((i + 0.5) * head_segment)); } { FlatShadedVertex& point = vertices[i*6+2]; VertexRemap::x(point.vertex) = length - arrowhead_length; VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos((i+1) * head_segment)); VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin((i+1) * head_segment)); NormalRemap::x(point.normal) = arrowhead_radius / arrowhead_length; NormalRemap::y(point.normal) = static_cast(cos((i+1) * head_segment)); NormalRemap::z(point.normal) = static_cast(sin((i+1) * head_segment)); } { FlatShadedVertex& point = vertices[i*6+3]; VertexRemap::x(point.vertex) = length - arrowhead_length; VertexRemap::y(point.vertex) = 0; VertexRemap::z(point.vertex) = 0; NormalRemap::x(point.normal) = -1; NormalRemap::y(point.normal) = 0; NormalRemap::z(point.normal) = 0; } { FlatShadedVertex& point = vertices[i*6+4]; VertexRemap::x(point.vertex) = length - arrowhead_length; VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos(i * head_segment)); VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin(i * head_segment)); NormalRemap::x(point.normal) = -1; NormalRemap::y(point.normal) = 0; NormalRemap::z(point.normal) = 0; } { FlatShadedVertex& point = vertices[i*6+5]; VertexRemap::x(point.vertex) = length - arrowhead_length; VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos((i+1) * head_segment)); VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin((i+1) * head_segment)); NormalRemap::x(point.normal) = -1; NormalRemap::y(point.normal) = 0; NormalRemap::z(point.normal) = 0; } } } template class TripleRemapXYZ { public: static float& x(Triple& triple) { return triple.x(); } static float& y(Triple& triple) { return triple.y(); } static float& z(Triple& triple) { return triple.z(); } }; template class TripleRemapYZX { public: static float& x(Triple& triple) { return triple.y(); } static float& y(Triple& triple) { return triple.z(); } static float& z(Triple& triple) { return triple.x(); } }; template class TripleRemapZXY { public: static float& x(Triple& triple) { return triple.z(); } static float& y(Triple& triple) { return triple.x(); } static float& z(Triple& triple) { return triple.y(); } }; void vector3_print(const Vector3& v) { globalOutputStream() << "( " << v.x() << " " << v.y() << " " << v.z() << " )"; } class TranslateManipulator : public Manipulator { struct RenderableArrowLine : public OpenGLRenderable { PointVertex m_line[2]; RenderableArrowLine() { } void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_line[0].colour); glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_line[0].vertex); glDrawArrays(GL_LINES, 0, 2); } void setColour(const Colour4b& colour) { m_line[0].colour = colour; m_line[1].colour = colour; } }; struct RenderableArrowHead : public OpenGLRenderable { Array m_vertices; RenderableArrowHead(std::size_t size) : m_vertices(size) { } void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(FlatShadedVertex), &m_vertices.data()->colour); glVertexPointer(3, GL_FLOAT, sizeof(FlatShadedVertex), &m_vertices.data()->vertex); glNormalPointer(GL_FLOAT, sizeof(FlatShadedVertex), &m_vertices.data()->normal); glDrawArrays(GL_TRIANGLES, 0, GLsizei(m_vertices.size())); } void setColour(const Colour4b& colour) { for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) { (*i).colour = colour; } } }; struct RenderableQuad : public OpenGLRenderable { PointVertex m_quad[4]; void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_quad[0].colour); glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_quad[0].vertex); glDrawArrays(GL_LINE_LOOP, 0, 4); } void setColour(const Colour4b& colour) { m_quad[0].colour = colour; m_quad[1].colour = colour; m_quad[2].colour = colour; m_quad[3].colour = colour; } }; TranslateFree m_free; TranslateAxis m_axis; RenderableArrowLine m_arrow_x; RenderableArrowLine m_arrow_y; RenderableArrowLine m_arrow_z; RenderableArrowHead m_arrow_head_x; RenderableArrowHead m_arrow_head_y; RenderableArrowHead m_arrow_head_z; RenderableQuad m_quad_screen; SelectableBool m_selectable_x; SelectableBool m_selectable_y; SelectableBool m_selectable_z; SelectableBool m_selectable_screen; Pivot2World m_pivot; public: static Shader* m_state_wire; static Shader* m_state_fill; TranslateManipulator(Translatable& translatable, std::size_t segments, float length) : m_free(translatable), m_axis(translatable), m_arrow_head_x(3 * 2 * (segments << 3)), m_arrow_head_y(3 * 2 * (segments << 3)), m_arrow_head_z(3 * 2 * (segments << 3)) { draw_arrowline(length, m_arrow_x.m_line, 0); draw_arrowhead(segments, length, m_arrow_head_x.m_vertices.data(), TripleRemapXYZ(), TripleRemapXYZ()); draw_arrowline(length, m_arrow_y.m_line, 1); draw_arrowhead(segments, length, m_arrow_head_y.m_vertices.data(), TripleRemapYZX(), TripleRemapYZX()); draw_arrowline(length, m_arrow_z.m_line, 2); draw_arrowhead(segments, length, m_arrow_head_z.m_vertices.data(), TripleRemapZXY(), TripleRemapZXY()); draw_quad(16, m_quad_screen.m_quad); } void UpdateColours() { m_arrow_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); m_arrow_head_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); m_arrow_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); m_arrow_head_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); m_arrow_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); m_arrow_head_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); m_quad_screen.setColour(colourSelected(g_colour_screen, m_selectable_screen.isSelected())); } bool manipulator_show_axis(const Pivot2World& pivot, const Vector3& axis) { return fabs(vector3_dot(pivot.m_axis_screen, axis)) < 0.95; } void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) { m_pivot.update(pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); // temp hack UpdateColours(); Vector3 x = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.x())); bool show_x = manipulator_show_axis(m_pivot, x); Vector3 y = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.y())); bool show_y = manipulator_show_axis(m_pivot, y); Vector3 z = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.z())); bool show_z = manipulator_show_axis(m_pivot, z); renderer.SetState(m_state_wire, Renderer::eWireframeOnly); renderer.SetState(m_state_wire, Renderer::eFullMaterials); if(show_x) { renderer.addRenderable(m_arrow_x, m_pivot.m_worldSpace); } if(show_y) { renderer.addRenderable(m_arrow_y, m_pivot.m_worldSpace); } if(show_z) { renderer.addRenderable(m_arrow_z, m_pivot.m_worldSpace); } renderer.addRenderable(m_quad_screen, m_pivot.m_viewplaneSpace); renderer.SetState(m_state_fill, Renderer::eWireframeOnly); renderer.SetState(m_state_fill, Renderer::eFullMaterials); if(show_x) { renderer.addRenderable(m_arrow_head_x, m_pivot.m_worldSpace); } if(show_y) { renderer.addRenderable(m_arrow_head_y, m_pivot.m_worldSpace); } if(show_z) { renderer.addRenderable(m_arrow_head_z, m_pivot.m_worldSpace); } } void testSelect(const View& view, const Matrix4& pivot2world) { m_pivot.update(pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport()); SelectionPool selector; Vector3 x = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.x())); bool show_x = manipulator_show_axis(m_pivot, x); Vector3 y = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.y())); bool show_y = manipulator_show_axis(m_pivot, y); Vector3 z = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.z())); bool show_z = manipulator_show_axis(m_pivot, z); { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_viewpointSpace)); { SelectionIntersection best; Quad_BestPoint(local2view, eClipCullCW, m_quad_screen.m_quad, best); if(best.valid()) { best = SelectionIntersection(0, 0); selector.addSelectable(best, &m_selectable_screen); } } } { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_worldSpace)); #if defined(DEBUG_SELECTION) g_render_clipped.construct(view.GetViewMatrix()); #endif if(show_x) { SelectionIntersection best; Line_BestPoint(local2view, m_arrow_x.m_line, best); Triangles_BestPoint(local2view, eClipCullCW, m_arrow_head_x.m_vertices.begin(), m_arrow_head_x.m_vertices.end(), best); selector.addSelectable(best, &m_selectable_x); } if(show_y) { SelectionIntersection best; Line_BestPoint(local2view, m_arrow_y.m_line, best); Triangles_BestPoint(local2view, eClipCullCW, m_arrow_head_y.m_vertices.begin(), m_arrow_head_y.m_vertices.end(), best); selector.addSelectable(best, &m_selectable_y); } if(show_z) { SelectionIntersection best; Line_BestPoint(local2view, m_arrow_z.m_line, best); Triangles_BestPoint(local2view, eClipCullCW, m_arrow_head_z.m_vertices.begin(), m_arrow_head_z.m_vertices.end(), best); selector.addSelectable(best, &m_selectable_z); } } if(!selector.failed()) { (*selector.begin()).second->setSelected(true); } } Manipulatable* GetManipulatable() { if(m_selectable_x.isSelected()) { m_axis.SetAxis(g_vector3_axis_x); return &m_axis; } else if(m_selectable_y.isSelected()) { m_axis.SetAxis(g_vector3_axis_y); return &m_axis; } else if(m_selectable_z.isSelected()) { m_axis.SetAxis(g_vector3_axis_z); return &m_axis; } else { return &m_free; } } void setSelected(bool select) { m_selectable_x.setSelected(select); m_selectable_y.setSelected(select); m_selectable_z.setSelected(select); m_selectable_screen.setSelected(select); } bool isSelected() const { return m_selectable_x.isSelected() | m_selectable_y.isSelected() | m_selectable_z.isSelected() | m_selectable_screen.isSelected(); } }; Shader* TranslateManipulator::m_state_wire; Shader* TranslateManipulator::m_state_fill; class ScaleManipulator : public Manipulator { struct RenderableArrow : public OpenGLRenderable { PointVertex m_line[2]; void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_line[0].colour); glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_line[0].vertex); glDrawArrays(GL_LINES, 0, 2); } void setColour(const Colour4b& colour) { m_line[0].colour = colour; m_line[1].colour = colour; } }; struct RenderableQuad : public OpenGLRenderable { PointVertex m_quad[4]; void render(RenderStateFlags state) const { glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_quad[0].colour); glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_quad[0].vertex); glDrawArrays(GL_QUADS, 0, 4); } void setColour(const Colour4b& colour) { m_quad[0].colour = colour; m_quad[1].colour = colour; m_quad[2].colour = colour; m_quad[3].colour = colour; } }; ScaleFree m_free; ScaleAxis m_axis; RenderableArrow m_arrow_x; RenderableArrow m_arrow_y; RenderableArrow m_arrow_z; RenderableQuad m_quad_screen; SelectableBool m_selectable_x; SelectableBool m_selectable_y; SelectableBool m_selectable_z; SelectableBool m_selectable_screen; Pivot2World m_pivot; public: ScaleManipulator(Scalable& scalable, std::size_t segments, float length) : m_free(scalable), m_axis(scalable) { draw_arrowline(length, m_arrow_x.m_line, 0); draw_arrowline(length, m_arrow_y.m_line, 1); draw_arrowline(length, m_arrow_z.m_line, 2); draw_quad(16, m_quad_screen.m_quad); } Pivot2World& getPivot() { return m_pivot; } void UpdateColours() { m_arrow_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); m_arrow_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); m_arrow_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); m_quad_screen.setColour(colourSelected(g_colour_screen, m_selectable_screen.isSelected())); } void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) { m_pivot.update(pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); // temp hack UpdateColours(); renderer.addRenderable(m_arrow_x, m_pivot.m_worldSpace); renderer.addRenderable(m_arrow_y, m_pivot.m_worldSpace); renderer.addRenderable(m_arrow_z, m_pivot.m_worldSpace); renderer.addRenderable(m_quad_screen, m_pivot.m_viewpointSpace); } void testSelect(const View& view, const Matrix4& pivot2world) { m_pivot.update(pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport()); SelectionPool selector; { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_worldSpace)); #if defined(DEBUG_SELECTION) g_render_clipped.construct(view.GetViewMatrix()); #endif { SelectionIntersection best; Line_BestPoint(local2view, m_arrow_x.m_line, best); selector.addSelectable(best, &m_selectable_x); } { SelectionIntersection best; Line_BestPoint(local2view, m_arrow_y.m_line, best); selector.addSelectable(best, &m_selectable_y); } { SelectionIntersection best; Line_BestPoint(local2view, m_arrow_z.m_line, best); selector.addSelectable(best, &m_selectable_z); } } { Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_viewpointSpace)); { SelectionIntersection best; Quad_BestPoint(local2view, eClipCullCW, m_quad_screen.m_quad, best); selector.addSelectable(best, &m_selectable_screen); } } if(!selector.failed()) { (*selector.begin()).second->setSelected(true); } } Manipulatable* GetManipulatable() { if(m_selectable_x.isSelected()) { m_axis.SetAxis(g_vector3_axis_x); return &m_axis; } else if(m_selectable_y.isSelected()) { m_axis.SetAxis(g_vector3_axis_y); return &m_axis; } else if(m_selectable_z.isSelected()) { m_axis.SetAxis(g_vector3_axis_z); return &m_axis; } else return &m_free; } void setSelected(bool select) { m_selectable_x.setSelected(select); m_selectable_y.setSelected(select); m_selectable_z.setSelected(select); m_selectable_screen.setSelected(select); } bool isSelected() const { return m_selectable_x.isSelected() | m_selectable_y.isSelected() | m_selectable_z.isSelected() | m_selectable_screen.isSelected(); } }; 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 { if(path.top().get().visible()) { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && selectable->isSelected()) { 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 { if(path.top().get().visible()) { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && selectable->isSelected()) { 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 { if(plane.a < other.a) { return true; } if(other.a < plane.a) { return false; } if(plane.b < other.b) { return true; } if(other.b < plane.b) { return false; } if(plane.c < other.c) { return true; } if(other.c < plane.c) { return false; } if(plane.d < other.d) { return true; } if(other.d < plane.d) { return false; } return false; } }; typedef std::set PlaneSet; inline void PlaneSet_insert(PlaneSet& self, const Plane3& plane) { self.insert(plane); } inline bool PlaneSet_contains(const PlaneSet& self, const Plane3& plane) { return self.find(plane) != self.end(); } class SelectedPlaneSet : public SelectedPlanes { PlaneSet m_selectedPlanes; public: bool empty() const { return m_selectedPlanes.empty(); } void insert(const Plane3& plane) { PlaneSet_insert(m_selectedPlanes, plane); } bool contains(const Plane3& plane) const { return PlaneSet_contains(m_selectedPlanes, plane); } typedef MemberCaller1 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(); } void Scene_Translate_Component_Selected(scene::Graph& graph, const Vector3& translation); void Scene_Translate_Selected(scene::Graph& graph, const Vector3& translation); void Scene_TestSelect_Primitive(Selector& selector, SelectionTest& test, const VolumeTest& volume); void Scene_TestSelect_Component(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode); void Scene_TestSelect_Component_Selected(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode); void Scene_SelectAll_Component(bool select, SelectionSystem::EComponentMode componentMode); class ResizeTranslatable : public Translatable { void translate(const Vector3& translation) { Scene_Translate_Component_Selected(GlobalSceneGraph(), translation); } }; class DragTranslatable : public Translatable { void translate(const Vector3& translation) { if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) { Scene_Translate_Component_Selected(GlobalSceneGraph(), translation); } else { Scene_Translate_Selected(GlobalSceneGraph(), translation); } } }; class SelectionVolume : public SelectionTest { Matrix4 m_local2view; const View& m_view; clipcull_t m_cull; Vector3 m_near; Vector3 m_far; public: SelectionVolume(const View& view) : m_view(view) { } const VolumeTest& getVolume() const { return m_view; } const Vector3& getNear() const { return m_near; } const Vector3& getFar() const { return m_far; } void BeginMesh(const Matrix4& localToWorld, bool twoSided) { m_local2view = matrix4_multiplied_by_matrix4(m_view.GetViewMatrix(), localToWorld); // Cull back-facing polygons based on winding being clockwise or counter-clockwise. // Don't cull if the view is wireframe and the polygons are two-sided. m_cull = twoSided && !m_view.fill() ? eClipCullNone : (matrix4_handedness(localToWorld) == MATRIX4_RIGHTHANDED) ? eClipCullCW : eClipCullCCW; { Matrix4 screen2world(matrix4_full_inverse(m_local2view)); m_near = vector4_projected( matrix4_transformed_vector4( screen2world, Vector4(0, 0, -1, 1) ) ); m_far = vector4_projected( matrix4_transformed_vector4( screen2world, Vector4(0, 0, 1, 1) ) ); } #if defined(DEBUG_SELECTION) g_render_clipped.construct(m_view.GetViewMatrix()); #endif } void TestPoint(const Vector3& point, SelectionIntersection& best) { Vector4 clipped; if(matrix4_clip_point(m_local2view, point, clipped) == c_CLIP_PASS) { best = select_point_from_clipped(clipped); } } void TestPolygon(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) { Vector4 clipped[9]; for(std::size_t i=0; i+2(vertices[0]), reinterpret_cast(vertices[i+1]), reinterpret_cast(vertices[i+2]), clipped ), clipped, best, m_cull ); } } void TestLineLoop(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) { if(count == 0) return; Vector4 clipped[9]; for(VertexPointer::iterator i = vertices.begin(), end = i + count, prev = i + (count-1); i != end; prev = i, ++i) { BestPoint( matrix4_clip_line( m_local2view, reinterpret_cast((*prev)), reinterpret_cast((*i)), clipped ), clipped, best, m_cull ); } } void TestLineStrip(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) { if(count == 0) return; Vector4 clipped[9]; for(VertexPointer::iterator i = vertices.begin(), end = i + count, next = i + 1; next != end; i = next, ++next) { BestPoint( matrix4_clip_line( m_local2view, reinterpret_cast((*i)), reinterpret_cast((*next)), clipped ), clipped, best, m_cull ); } } void TestLines(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) { if(count == 0) return; Vector4 clipped[9]; for(VertexPointer::iterator i = vertices.begin(), end = i + count; i != end; i += 2) { BestPoint( matrix4_clip_line( m_local2view, reinterpret_cast((*i)), reinterpret_cast((*(i+1))), clipped ), clipped, best, m_cull ); } } void TestTriangles(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) { Vector4 clipped[9]; for(IndexPointer::iterator i(indices.begin()); i != indices.end(); i += 3) { BestPoint( matrix4_clip_triangle( m_local2view, reinterpret_cast(vertices[*i]), reinterpret_cast(vertices[*(i+1)]), reinterpret_cast(vertices[*(i+2)]), clipped ), clipped, best, m_cull ); } } void TestQuads(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) { Vector4 clipped[9]; for(IndexPointer::iterator i(indices.begin()); i != indices.end(); i += 4) { BestPoint( matrix4_clip_triangle( m_local2view, reinterpret_cast(vertices[*i]), reinterpret_cast(vertices[*(i+1)]), reinterpret_cast(vertices[*(i+3)]), clipped ), clipped, best, m_cull ); BestPoint( matrix4_clip_triangle( m_local2view, reinterpret_cast(vertices[*(i+1)]), reinterpret_cast(vertices[*(i+2)]), reinterpret_cast(vertices[*(i+3)]), clipped ), clipped, best, m_cull ); } } void TestQuadStrip(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) { Vector4 clipped[9]; for(IndexPointer::iterator i(indices.begin()); i+2 != indices.end(); i += 2) { BestPoint( matrix4_clip_triangle( m_local2view, reinterpret_cast(vertices[*i]), reinterpret_cast(vertices[*(i+1)]), reinterpret_cast(vertices[*(i+2)]), clipped ), clipped, best, m_cull ); BestPoint( matrix4_clip_triangle( m_local2view, reinterpret_cast(vertices[*(i+2)]), reinterpret_cast(vertices[*(i+1)]), reinterpret_cast(vertices[*(i+3)]), clipped ), clipped, best, m_cull ); } } }; class SelectionCounter { public: typedef const Selectable& first_argument_type; SelectionCounter(const SelectionChangeCallback& onchanged) : m_count(0), m_onchanged(onchanged) { } void operator()(const Selectable& selectable) { if(selectable.isSelected()) { ++m_count; } else { ASSERT_MESSAGE(m_count != 0, "selection counter underflow"); --m_count; } m_onchanged(selectable); } bool empty() const { return m_count == 0; } std::size_t size() const { return m_count; } private: std::size_t m_count; SelectionChangeCallback m_onchanged; }; inline void ConstructSelectionTest(View& view, const rect_t selection_box) { view.EnableScissor(selection_box.min[0], selection_box.max[0], selection_box.min[1], selection_box.max[1]); } inline const rect_t SelectionBoxForPoint(const float device_point[2], const float device_epsilon[2]) { rect_t selection_box; selection_box.min[0] = device_point[0] - device_epsilon[0]; selection_box.min[1] = device_point[1] - device_epsilon[1]; selection_box.max[0] = device_point[0] + device_epsilon[0]; selection_box.max[1] = device_point[1] + device_epsilon[1]; return selection_box; } inline const rect_t SelectionBoxForArea(const float device_point[2], const float device_delta[2]) { rect_t selection_box; selection_box.min[0] = (device_delta[0] < 0) ? (device_point[0] + device_delta[0]) : (device_point[0]); selection_box.min[1] = (device_delta[1] < 0) ? (device_point[1] + device_delta[1]) : (device_point[1]); selection_box.max[0] = (device_delta[0] > 0) ? (device_point[0] + device_delta[0]) : (device_point[0]); selection_box.max[1] = (device_delta[1] > 0) ? (device_point[1] + device_delta[1]) : (device_point[1]); return selection_box; } Quaternion construct_local_rotation(const Quaternion& world, const Quaternion& localToWorld) { return quaternion_normalised(quaternion_multiplied_by_quaternion( quaternion_normalised(quaternion_multiplied_by_quaternion( quaternion_inverse(localToWorld), world )), localToWorld )); } inline void matrix4_assign_rotation(Matrix4& matrix, const Matrix4& other) { matrix[0] = other[0]; matrix[1] = other[1]; matrix[2] = other[2]; matrix[4] = other[4]; matrix[5] = other[5]; matrix[6] = other[6]; matrix[8] = other[8]; matrix[9] = other[9]; matrix[10] = other[10]; } void matrix4_assign_rotation_for_pivot(Matrix4& matrix, scene::Instance& instance) { Editable* editable = Node_getEditable(instance.path().top()); if(editable != 0) { matrix4_assign_rotation(matrix, matrix4_multiplied_by_matrix4(instance.localToWorld(), editable->getLocalPivot())); } else { matrix4_assign_rotation(matrix, instance.localToWorld()); } } inline bool Instance_isSelectedComponents(scene::Instance& instance) { ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); return componentSelectionTestable != 0 && componentSelectionTestable->isSelectedComponents(); } class TranslateSelected : public SelectionSystem::Visitor { const Vector3& m_translate; public: TranslateSelected(const Vector3& translate) : m_translate(translate) { } void visit(scene::Instance& instance) const { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { transform->setType(TRANSFORM_PRIMITIVE); transform->setTranslation(m_translate); } } }; void Scene_Translate_Selected(scene::Graph& graph, const Vector3& translation) { if(GlobalSelectionSystem().countSelected() != 0) { GlobalSelectionSystem().foreachSelected(TranslateSelected(translation)); } } Vector3 get_local_pivot(const Vector3& world_pivot, const Matrix4& localToWorld) { return Vector3( matrix4_transformed_point( matrix4_full_inverse(localToWorld), world_pivot ) ); } void translation_for_pivoted_matrix_transform(Vector3& parent_translation, const Matrix4& local_transform, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent) { // we need a translation inside the parent system to move the origin of this object to the right place // mathematically, it must fulfill: // // local_translation local_transform local_pivot = local_pivot // local_translation = local_pivot - local_transform local_pivot // // or maybe? // local_transform local_translation local_pivot = local_pivot // local_translation local_pivot = local_transform^-1 local_pivot // local_translation + local_pivot = local_transform^-1 local_pivot // local_translation = local_transform^-1 local_pivot - local_pivot Vector3 local_pivot(get_local_pivot(world_pivot, localToWorld)); Vector3 local_translation( vector3_subtracted( local_pivot, matrix4_transformed_point( local_transform, local_pivot ) /* matrix4_transformed_point( matrix4_full_inverse(local_transform), local_pivot ), local_pivot */ ) ); translation_local2object(parent_translation, local_translation, localToParent); /* // verify it! globalOutputStream() << "World pivot is at " << world_pivot << "\n"; globalOutputStream() << "Local pivot is at " << local_pivot << "\n"; globalOutputStream() << "Transformation " << local_transform << " moves it to: " << matrix4_transformed_point(local_transform, local_pivot) << "\n"; globalOutputStream() << "Must move by " << local_translation << " in the local system" << "\n"; globalOutputStream() << "Must move by " << parent_translation << " in the parent system" << "\n"; */ } void translation_for_pivoted_rotation(Vector3& parent_translation, const Quaternion& local_rotation, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent) { translation_for_pivoted_matrix_transform(parent_translation, matrix4_rotation_for_quaternion_quantised(local_rotation), world_pivot, localToWorld, localToParent); } void translation_for_pivoted_scale(Vector3& parent_translation, const Vector3& world_scale, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent) { Matrix4 local_transform( matrix4_multiplied_by_matrix4( matrix4_full_inverse(localToWorld), matrix4_multiplied_by_matrix4( matrix4_scale_for_vec3(world_scale), localToWorld ) ) ); local_transform.tx() = local_transform.ty() = local_transform.tz() = 0; // cancel translation parts translation_for_pivoted_matrix_transform(parent_translation, local_transform, world_pivot, localToWorld, localToParent); } class rotate_selected : public SelectionSystem::Visitor { const Quaternion& m_rotate; const Vector3& m_world_pivot; public: rotate_selected(const Quaternion& rotation, const Vector3& world_pivot) : m_rotate(rotation), m_world_pivot(world_pivot) { } void visit(scene::Instance& instance) const { TransformNode* transformNode = Node_getTransformNode(instance.path().top()); if(transformNode != 0) { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { transform->setType(TRANSFORM_PRIMITIVE); transform->setScale(c_scale_identity); transform->setTranslation(c_translation_identity); transform->setType(TRANSFORM_PRIMITIVE); transform->setRotation(m_rotate); { Editable* editable = Node_getEditable(instance.path().top()); const Matrix4& localPivot = editable != 0 ? editable->getLocalPivot() : g_matrix4_identity; Vector3 parent_translation; translation_for_pivoted_rotation( parent_translation, m_rotate, m_world_pivot, matrix4_multiplied_by_matrix4(instance.localToWorld(), localPivot), matrix4_multiplied_by_matrix4(transformNode->localToParent(), localPivot) ); transform->setTranslation(parent_translation); } } } } }; void Scene_Rotate_Selected(scene::Graph& graph, const Quaternion& rotation, const Vector3& world_pivot) { if(GlobalSelectionSystem().countSelected() != 0) { GlobalSelectionSystem().foreachSelected(rotate_selected(rotation, world_pivot)); } } class scale_selected : public SelectionSystem::Visitor { const Vector3& m_scale; const Vector3& m_world_pivot; public: scale_selected(const Vector3& scaling, const Vector3& world_pivot) : m_scale(scaling), m_world_pivot(world_pivot) { } void visit(scene::Instance& instance) const { TransformNode* transformNode = Node_getTransformNode(instance.path().top()); if(transformNode != 0) { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { transform->setType(TRANSFORM_PRIMITIVE); transform->setScale(c_scale_identity); transform->setTranslation(c_translation_identity); transform->setType(TRANSFORM_PRIMITIVE); transform->setScale(m_scale); { Editable* editable = Node_getEditable(instance.path().top()); const Matrix4& localPivot = editable != 0 ? editable->getLocalPivot() : g_matrix4_identity; Vector3 parent_translation; translation_for_pivoted_scale( parent_translation, m_scale, m_world_pivot, matrix4_multiplied_by_matrix4(instance.localToWorld(), localPivot), matrix4_multiplied_by_matrix4(transformNode->localToParent(), localPivot) ); transform->setTranslation(parent_translation); } } } } }; void Scene_Scale_Selected(scene::Graph& graph, const Vector3& scaling, const Vector3& world_pivot) { if(GlobalSelectionSystem().countSelected() != 0) { GlobalSelectionSystem().foreachSelected(scale_selected(scaling, world_pivot)); } } class translate_component_selected : public SelectionSystem::Visitor { const Vector3& m_translate; public: translate_component_selected(const Vector3& translate) : m_translate(translate) { } void visit(scene::Instance& instance) const { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { transform->setType(TRANSFORM_COMPONENT); transform->setTranslation(m_translate); } } }; void Scene_Translate_Component_Selected(scene::Graph& graph, const Vector3& translation) { if(GlobalSelectionSystem().countSelected() != 0) { GlobalSelectionSystem().foreachSelectedComponent(translate_component_selected(translation)); } } class rotate_component_selected : public SelectionSystem::Visitor { const Quaternion& m_rotate; const Vector3& m_world_pivot; public: rotate_component_selected(const Quaternion& rotation, const Vector3& world_pivot) : m_rotate(rotation), m_world_pivot(world_pivot) { } void visit(scene::Instance& instance) const { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { Vector3 parent_translation; translation_for_pivoted_rotation(parent_translation, m_rotate, m_world_pivot, instance.localToWorld(), Node_getTransformNode(instance.path().top())->localToParent()); transform->setType(TRANSFORM_COMPONENT); transform->setRotation(m_rotate); transform->setTranslation(parent_translation); } } }; void Scene_Rotate_Component_Selected(scene::Graph& graph, const Quaternion& rotation, const Vector3& world_pivot) { if(GlobalSelectionSystem().countSelectedComponents() != 0) { GlobalSelectionSystem().foreachSelectedComponent(rotate_component_selected(rotation, world_pivot)); } } class scale_component_selected : public SelectionSystem::Visitor { const Vector3& m_scale; const Vector3& m_world_pivot; public: scale_component_selected(const Vector3& scaling, const Vector3& world_pivot) : m_scale(scaling), m_world_pivot(world_pivot) { } void visit(scene::Instance& instance) const { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { Vector3 parent_translation; translation_for_pivoted_scale(parent_translation, m_scale, m_world_pivot, instance.localToWorld(), Node_getTransformNode(instance.path().top())->localToParent()); transform->setType(TRANSFORM_COMPONENT); transform->setScale(m_scale); transform->setTranslation(parent_translation); } } }; void Scene_Scale_Component_Selected(scene::Graph& graph, const Vector3& scaling, const Vector3& world_pivot) { if(GlobalSelectionSystem().countSelectedComponents() != 0) { GlobalSelectionSystem().foreachSelectedComponent(scale_component_selected(scaling, world_pivot)); } } class BooleanSelector : public Selector { bool m_selected; SelectionIntersection m_intersection; Selectable* m_selectable; public: BooleanSelector() : m_selected(false) { } void pushSelectable(Selectable& selectable) { m_intersection = SelectionIntersection(); m_selectable = &selectable; } void popSelectable() { if(m_intersection.valid()) { m_selected = true; } m_intersection = SelectionIntersection(); } void addIntersection(const SelectionIntersection& intersection) { if(m_selectable->isSelected()) { assign_if_closer(m_intersection, intersection); } } bool isSelected() { return m_selected; } }; class BestSelector : public Selector { SelectionIntersection m_intersection; Selectable* m_selectable; SelectionIntersection m_bestIntersection; std::list m_bestSelectable; public: BestSelector() : m_bestIntersection(SelectionIntersection()), m_bestSelectable(0) { } void pushSelectable(Selectable& selectable) { m_intersection = SelectionIntersection(); m_selectable = &selectable; } void popSelectable() { if(m_intersection.equalEpsilon(m_bestIntersection, 0.25f, 0.001f)) { m_bestSelectable.push_back(m_selectable); m_bestIntersection = m_intersection; } else if(m_intersection < m_bestIntersection) { m_bestSelectable.clear(); m_bestSelectable.push_back(m_selectable); m_bestIntersection = m_intersection; } m_intersection = SelectionIntersection(); } void addIntersection(const SelectionIntersection& intersection) { assign_if_closer(m_intersection, intersection); } std::list& best() { return m_bestSelectable; } }; class DragManipulator : public Manipulator { TranslateFree m_freeResize; TranslateFree m_freeDrag; ResizeTranslatable m_resize; DragTranslatable m_drag; SelectableBool m_dragSelectable; public: bool m_selected; DragManipulator() : m_freeResize(m_resize), m_freeDrag(m_drag), m_selected(false) { } Manipulatable* GetManipulatable() { return m_dragSelectable.isSelected() ? &m_freeDrag : &m_freeResize; } void testSelect(const View& view, const Matrix4& pivot2world) { SelectionPool selector; SelectionVolume test(view); if(GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) { BooleanSelector booleanSelector; Scene_TestSelect_Primitive(booleanSelector, test, view); if(booleanSelector.isSelected()) { selector.addSelectable(SelectionIntersection(0, 0), &m_dragSelectable); m_selected = false; } else { m_selected = Scene_forEachPlaneSelectable_selectPlanes(GlobalSceneGraph(), selector, test); } } else { BestSelector bestSelector; Scene_TestSelect_Component_Selected(bestSelector, test, view, GlobalSelectionSystem().ComponentMode()); for(std::list::iterator i = bestSelector.best().begin(); i != bestSelector.best().end(); ++i) { if(!(*i)->isSelected()) { GlobalSelectionSystem().setSelectedAllComponents(false); } m_selected = false; selector.addSelectable(SelectionIntersection(0, 0), (*i)); m_dragSelectable.setSelected(true); } } for(SelectionPool::iterator i = selector.begin(); i != selector.end(); ++i) { (*i).second->setSelected(true); } } void setSelected(bool select) { m_selected = select; m_dragSelectable.setSelected(select); } bool isSelected() const { return m_selected || m_dragSelectable.isSelected(); } }; class ClipManipulator : public Manipulator { public: Manipulatable* GetManipulatable() { ERROR_MESSAGE("clipper is not manipulatable"); return 0; } void setSelected(bool select) { } bool isSelected() const { return false; } }; class select_all : public scene::Graph::Walker { bool m_select; public: select_all(bool select) : m_select(select) { } bool pre(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0) { selectable->setSelected(m_select); } return true; } }; class select_all_component : public scene::Graph::Walker { bool m_select; SelectionSystem::EComponentMode m_mode; public: select_all_component(bool select, SelectionSystem::EComponentMode mode) : m_select(select), m_mode(mode) { } bool pre(const scene::Path& path, scene::Instance& instance) const { ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); if(componentSelectionTestable) { componentSelectionTestable->setSelectedComponents(m_select, m_mode); } return true; } }; void Scene_SelectAll_Component(bool select, SelectionSystem::EComponentMode componentMode) { GlobalSceneGraph().traverse(select_all_component(select, componentMode)); } // RadiantSelectionSystem class RadiantSelectionSystem : public SelectionSystem, public Translatable, public Rotatable, public Scalable, public Renderable { mutable Matrix4 m_pivot2world; Matrix4 m_pivot2world_start; Matrix4 m_manip2pivot_start; Translation m_translation; Rotation m_rotation; Scale m_scale; public: static Shader* m_state; private: EManipulatorMode m_manipulator_mode; Manipulator* m_manipulator; // state bool m_undo_begun; EMode m_mode; EComponentMode m_componentmode; SelectionCounter m_count_primitive; SelectionCounter m_count_component; TranslateManipulator m_translate_manipulator; RotateManipulator m_rotate_manipulator; ScaleManipulator m_scale_manipulator; DragManipulator m_drag_manipulator; ClipManipulator m_clip_manipulator; typedef SelectionList selection_t; selection_t m_selection; selection_t m_component_selection; Signal1 m_selectionChanged_callbacks; void ConstructPivot() const; mutable bool m_pivotChanged; bool m_pivot_moving; void Scene_TestSelect(Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode); bool nothingSelected() const { return (Mode() == eComponent && m_count_component.empty()) || (Mode() == ePrimitive && m_count_primitive.empty()); } public: enum EModifier { eManipulator, eToggle, eReplace, eCycle, }; RadiantSelectionSystem() : m_undo_begun(false), m_mode(ePrimitive), m_componentmode(eDefault), m_count_primitive(SelectionChangedCaller(*this)), m_count_component(SelectionChangedCaller(*this)), m_translate_manipulator(*this, 2, 64), m_rotate_manipulator(*this, 8, 64), m_scale_manipulator(*this, 0, 64), m_pivotChanged(false), m_pivot_moving(false) { SetManipulatorMode(eTranslate); pivotChanged(); addSelectionChangeCallback(PivotChangedSelectionCaller(*this)); AddGridChangeCallback(PivotChangedCaller(*this)); } void pivotChanged() const { m_pivotChanged = true; SceneChangeNotify(); } typedef ConstMemberCaller PivotChangedCaller; void pivotChangedSelection(const Selectable& selectable) { pivotChanged(); } typedef MemberCaller1 PivotChangedSelectionCaller; void SetMode(EMode mode) { if(m_mode != mode) { m_mode = mode; pivotChanged(); } } EMode Mode() const { return m_mode; } void SetComponentMode(EComponentMode mode) { m_componentmode = mode; } EComponentMode ComponentMode() const { return m_componentmode; } void SetManipulatorMode(EManipulatorMode mode) { m_manipulator_mode = mode; switch(m_manipulator_mode) { case eTranslate: m_manipulator = &m_translate_manipulator; break; case eRotate: m_manipulator = &m_rotate_manipulator; break; case eScale: m_manipulator = &m_scale_manipulator; break; case eDrag: m_manipulator = &m_drag_manipulator; break; case eClip: m_manipulator = &m_clip_manipulator; break; } pivotChanged(); } EManipulatorMode ManipulatorMode() const { return m_manipulator_mode; } SelectionChangeCallback getObserver(EMode mode) { if(mode == ePrimitive) { return makeCallback1(m_count_primitive); } else { return makeCallback1(m_count_component); } } std::size_t countSelected() const { return m_count_primitive.size(); } std::size_t countSelectedComponents() const { return m_count_component.size(); } void onSelectedChanged(scene::Instance& instance, const Selectable& selectable) { if(selectable.isSelected()) { m_selection.append(instance); } else { m_selection.erase(instance); } ASSERT_MESSAGE(m_selection.size() == m_count_primitive.size(), "selection-tracking error"); } void onComponentSelection(scene::Instance& instance, const Selectable& selectable) { if(selectable.isSelected()) { m_component_selection.append(instance); } else { m_component_selection.erase(instance); } ASSERT_MESSAGE(m_component_selection.size() == m_count_component.size(), "selection-tracking error"); } scene::Instance& ultimateSelected() const { ASSERT_MESSAGE(m_selection.size() > 0, "no instance selected"); return m_selection.back(); } scene::Instance& penultimateSelected() const { ASSERT_MESSAGE(m_selection.size() > 1, "only one instance selected"); return *(*(--(--m_selection.end()))); } void setSelectedAll(bool selected) { GlobalSceneGraph().traverse(select_all(selected)); m_manipulator->setSelected(selected); } void setSelectedAllComponents(bool selected) { Scene_SelectAll_Component(selected, SelectionSystem::eVertex); Scene_SelectAll_Component(selected, SelectionSystem::eEdge); Scene_SelectAll_Component(selected, SelectionSystem::eFace); m_manipulator->setSelected(selected); } void foreachSelected(const Visitor& visitor) const { selection_t::const_iterator i = m_selection.begin(); while(i != m_selection.end()) { visitor.visit(*(*(i++))); } } void foreachSelectedComponent(const Visitor& visitor) const { selection_t::const_iterator i = m_component_selection.begin(); while(i != m_component_selection.end()) { visitor.visit(*(*(i++))); } } void addSelectionChangeCallback(const SelectionChangeHandler& handler) { m_selectionChanged_callbacks.connectLast(handler); } void selectionChanged(const Selectable& selectable) { m_selectionChanged_callbacks(selectable); } typedef MemberCaller1 SelectionChangedCaller; void startMove() { m_pivot2world_start = GetPivot2World(); } bool SelectManipulator(const View& view, const float device_point[2], const float device_epsilon[2]) { if(!nothingSelected() || (ManipulatorMode() == eDrag && Mode() == eComponent)) { #if defined (DEBUG_SELECTION) g_render_clipped.destroy(); #endif m_manipulator->setSelected(false); if(!nothingSelected() || (ManipulatorMode() == eDrag && Mode() == eComponent)) { View scissored(view); ConstructSelectionTest(scissored, SelectionBoxForPoint(device_point, device_epsilon)); m_manipulator->testSelect(scissored, GetPivot2World()); } startMove(); m_pivot_moving = m_manipulator->isSelected(); if(m_pivot_moving) { Pivot2World pivot; pivot.update(GetPivot2World(), view.GetModelview(), view.GetProjection(), view.GetViewport()); m_manip2pivot_start = matrix4_multiplied_by_matrix4(matrix4_full_inverse(m_pivot2world_start), pivot.m_worldSpace); Matrix4 device2manip; ConstructDevice2Manip(device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport()); m_manipulator->GetManipulatable()->Construct(device2manip, device_point[0], device_point[1]); m_undo_begun = false; } SceneChangeNotify(); } return m_pivot_moving; } void deselectAll() { if(Mode() == eComponent) { setSelectedAllComponents(false); } else { setSelectedAll(false); } } void SelectPoint(const View& view, const float device_point[2], const float device_epsilon[2], RadiantSelectionSystem::EModifier modifier, bool face) { ASSERT_MESSAGE(fabs(device_point[0]) <= 1.0f && fabs(device_point[1]) <= 1.0f, "point-selection error"); if(modifier == eReplace) { if(face) { setSelectedAllComponents(false); } else { deselectAll(); } } #if defined (DEBUG_SELECTION) g_render_clipped.destroy(); #endif { View scissored(view); ConstructSelectionTest(scissored, SelectionBoxForPoint(device_point, device_epsilon)); SelectionVolume volume(scissored); SelectionPool selector; if(face) { Scene_TestSelect_Component(selector, volume, scissored, eFace); } else { Scene_TestSelect(selector, volume, scissored, Mode(), ComponentMode()); } if(!selector.failed()) { switch(modifier) { case RadiantSelectionSystem::eToggle: { SelectableSortedSet::iterator best = selector.begin(); // toggle selection of the object with least depth if((*best).second->isSelected()) (*best).second->setSelected(false); else (*best).second->setSelected(true); } break; // if cycle mode not enabled, enable it case RadiantSelectionSystem::eReplace: { // select closest (*selector.begin()).second->setSelected(true); } break; // select the next object in the list from the one already selected case RadiantSelectionSystem::eCycle: { SelectionPool::iterator i = selector.begin(); while(i != selector.end()) { if((*i).second->isSelected()) { (*i).second->setSelected(false); ++i; if(i != selector.end()) { i->second->setSelected(true); } else { selector.begin()->second->setSelected(true); } break; } ++i; } } break; default: break; } } } } void SelectArea(const View& view, const float device_point[2], const float device_delta[2], RadiantSelectionSystem::EModifier modifier, bool face) { if(modifier == eReplace) { if(face) { setSelectedAllComponents(false); } else { deselectAll(); } } #if defined (DEBUG_SELECTION) g_render_clipped.destroy(); #endif { View scissored(view); ConstructSelectionTest(scissored, SelectionBoxForArea(device_point, device_delta)); SelectionVolume volume(scissored); SelectionPool pool; if(face) { Scene_TestSelect_Component(pool, volume, scissored, eFace); } else { Scene_TestSelect(pool, volume, scissored, Mode(), ComponentMode()); } for(SelectionPool::iterator i = pool.begin(); i != pool.end(); ++i) { (*i).second->setSelected(!(modifier == RadiantSelectionSystem::eToggle && (*i).second->isSelected())); } } } void translate(const Vector3& translation) { if(!nothingSelected()) { //ASSERT_MESSAGE(!m_pivotChanged, "pivot is invalid"); m_translation = translation; m_pivot2world = m_pivot2world_start; matrix4_translate_by_vec3(m_pivot2world, translation); if(Mode() == eComponent) { Scene_Translate_Component_Selected(GlobalSceneGraph(), m_translation); } else { Scene_Translate_Selected(GlobalSceneGraph(), m_translation); } SceneChangeNotify(); } } void outputTranslation(TextOutputStream& ostream) { ostream << " -xyz " << m_translation.x() << " " << m_translation.y() << " " << m_translation.z(); } void rotate(const Quaternion& rotation) { if(!nothingSelected()) { //ASSERT_MESSAGE(!m_pivotChanged, "pivot is invalid"); m_rotation = rotation; if(Mode() == eComponent) { Scene_Rotate_Component_Selected(GlobalSceneGraph(), m_rotation, vector4_to_vector3(m_pivot2world.t())); matrix4_assign_rotation_for_pivot(m_pivot2world, m_component_selection.back()); } else { Scene_Rotate_Selected(GlobalSceneGraph(), m_rotation, vector4_to_vector3(m_pivot2world.t())); matrix4_assign_rotation_for_pivot(m_pivot2world, m_selection.back()); } SceneChangeNotify(); } } void outputRotation(TextOutputStream& ostream) { ostream << " -eulerXYZ " << m_rotation.x() << " " << m_rotation.y() << " " << m_rotation.z(); } void scale(const Vector3& scaling) { if(!nothingSelected()) { m_scale = scaling; if(Mode() == eComponent) { Scene_Scale_Component_Selected(GlobalSceneGraph(), m_scale, vector4_to_vector3(m_pivot2world.t())); } else { Scene_Scale_Selected(GlobalSceneGraph(), m_scale, vector4_to_vector3(m_pivot2world.t())); } SceneChangeNotify(); } } void outputScale(TextOutputStream& ostream) { ostream << " -scale " << m_scale.x() << " " << m_scale.y() << " " << m_scale.z(); } void rotateSelected(const Quaternion& rotation) { startMove(); rotate(rotation); freezeTransforms(); } void translateSelected(const Vector3& translation) { startMove(); translate(translation); freezeTransforms(); } void scaleSelected(const Vector3& scaling) { startMove(); scale(scaling); freezeTransforms(); } void MoveSelected(const View& view, const float device_point[2]) { if(m_manipulator->isSelected()) { if(!m_undo_begun) { m_undo_begun = true; GlobalUndoSystem().start(); } Matrix4 device2manip; ConstructDevice2Manip(device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport()); m_manipulator->GetManipulatable()->Transform(m_manip2pivot_start, device2manip, device_point[0], device_point[1]); } } /// \todo Support view-dependent nudge. void NudgeManipulator(const Vector3& nudge, const Vector3& view) { if(ManipulatorMode() == eTranslate || ManipulatorMode() == eDrag) { translateSelected(nudge); } } void endMove(); void freezeTransforms(); void renderSolid(Renderer& renderer, const VolumeTest& volume) const; void renderWireframe(Renderer& renderer, const VolumeTest& volume) const { renderSolid(renderer, volume); } const Matrix4& GetPivot2World() const { ConstructPivot(); return m_pivot2world; } static void constructStatic() { m_state = GlobalShaderCache().capture("$POINT"); #if defined(DEBUG_SELECTION) g_state_clipped = GlobalShaderCache().capture("$DEBUG_CLIPPED"); #endif TranslateManipulator::m_state_wire = GlobalShaderCache().capture("$WIRE_OVERLAY"); TranslateManipulator::m_state_fill = GlobalShaderCache().capture("$FLATSHADE_OVERLAY"); RotateManipulator::m_state_outer = GlobalShaderCache().capture("$WIRE_OVERLAY"); } static void destroyStatic() { #if defined(DEBUG_SELECTION) GlobalShaderCache().release("$DEBUG_CLIPPED"); #endif GlobalShaderCache().release("$WIRE_OVERLAY"); GlobalShaderCache().release("$FLATSHADE_OVERLAY"); GlobalShaderCache().release("$WIRE_OVERLAY"); GlobalShaderCache().release("$POINT"); } }; Shader* RadiantSelectionSystem::m_state = 0; namespace { RadiantSelectionSystem* g_RadiantSelectionSystem; inline RadiantSelectionSystem& getSelectionSystem() { return *g_RadiantSelectionSystem; } } class testselect_entity_visible : public scene::Graph::Walker { Selector& m_selector; SelectionTest& m_test; public: testselect_entity_visible(Selector& selector, SelectionTest& test) : m_selector(selector), m_test(test) { } bool pre(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && Node_isEntity(path.top())) { m_selector.pushSelectable(*selectable); } SelectionTestable* selectionTestable = Instance_getSelectionTestable(instance); if(selectionTestable) { selectionTestable->testSelect(m_selector, m_test); } return true; } void post(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && Node_isEntity(path.top())) { m_selector.popSelectable(); } } }; class testselect_primitive_visible : public scene::Graph::Walker { Selector& m_selector; SelectionTest& m_test; public: testselect_primitive_visible(Selector& selector, SelectionTest& test) : m_selector(selector), m_test(test) { } bool pre(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0) { m_selector.pushSelectable(*selectable); } SelectionTestable* selectionTestable = Instance_getSelectionTestable(instance); if(selectionTestable) { selectionTestable->testSelect(m_selector, m_test); } return true; } void post(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0) { m_selector.popSelectable(); } } }; class testselect_component_visible : public scene::Graph::Walker { Selector& m_selector; SelectionTest& m_test; SelectionSystem::EComponentMode m_mode; public: testselect_component_visible(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode) : m_selector(selector), m_test(test), m_mode(mode) { } bool pre(const scene::Path& path, scene::Instance& instance) const { ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); if(componentSelectionTestable) { componentSelectionTestable->testSelectComponents(m_selector, m_test, m_mode); } return true; } }; class testselect_component_visible_selected : public scene::Graph::Walker { Selector& m_selector; SelectionTest& m_test; SelectionSystem::EComponentMode m_mode; public: testselect_component_visible_selected(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode) : m_selector(selector), m_test(test), m_mode(mode) { } bool pre(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && selectable->isSelected()) { ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); if(componentSelectionTestable) { componentSelectionTestable->testSelectComponents(m_selector, m_test, m_mode); } } return true; } }; void Scene_TestSelect_Primitive(Selector& selector, SelectionTest& test, const VolumeTest& volume) { Scene_forEachVisible(GlobalSceneGraph(), volume, testselect_primitive_visible(selector, test)); } void Scene_TestSelect_Component_Selected(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode) { Scene_forEachVisible(GlobalSceneGraph(), volume, testselect_component_visible_selected(selector, test, componentMode)); } void Scene_TestSelect_Component(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode) { Scene_forEachVisible(GlobalSceneGraph(), volume, testselect_component_visible(selector, test, componentMode)); } void RadiantSelectionSystem::Scene_TestSelect(Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode) { switch(mode) { case eEntity: { Scene_forEachVisible(GlobalSceneGraph(), view, testselect_entity_visible(selector, test)); } break; case ePrimitive: Scene_TestSelect_Primitive(selector, test, view); break; case eComponent: Scene_TestSelect_Component_Selected(selector, test, view, componentMode); break; } } class FreezeTransforms : public scene::Graph::Walker { public: bool pre(const scene::Path& path, scene::Instance& instance) const { TransformNode* transformNode = Node_getTransformNode(path.top()); if(transformNode != 0) { Transformable* transform = Instance_getTransformable(instance); if(transform != 0) { transform->freezeTransform(); } } return true; } }; void RadiantSelectionSystem::freezeTransforms() { GlobalSceneGraph().traverse(FreezeTransforms()); } void RadiantSelectionSystem::endMove() { freezeTransforms(); if(Mode() == ePrimitive) { if(ManipulatorMode() == eDrag) { Scene_SelectAll_Component(false, SelectionSystem::eFace); } } m_pivot_moving = false; pivotChanged(); SceneChangeNotify(); if(m_undo_begun) { StringOutputStream command; if(ManipulatorMode() == eTranslate) { command << "translateTool"; outputTranslation(command); } else if(ManipulatorMode() == eRotate) { command << "rotateTool"; outputRotation(command); } else if(ManipulatorMode() == eScale) { command << "scaleTool"; outputScale(command); } else if(ManipulatorMode() == eDrag) { command << "dragTool"; } GlobalUndoSystem().finish(command.c_str()); } } inline AABB Instance_getPivotBounds(scene::Instance& instance) { Entity* entity = Node_getEntity(instance.path().top()); if(entity != 0 && (entity->getEntityClass().fixedsize || !node_is_group(instance.path().top()))) { Editable* editable = Node_getEditable(instance.path().top()); if(editable != 0) { return AABB(vector4_to_vector3(matrix4_multiplied_by_matrix4(instance.localToWorld(), editable->getLocalPivot()).t()), Vector3(0, 0, 0)); } else { return AABB(vector4_to_vector3(instance.localToWorld().t()), Vector3(0, 0, 0)); } } return instance.worldAABB(); } class bounds_selected : public scene::Graph::Walker { AABB& m_bounds; public: bounds_selected(AABB& bounds) : m_bounds(bounds) { m_bounds = AABB(); } bool pre(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && selectable->isSelected()) { aabb_extend_by_aabb_safe(m_bounds, Instance_getPivotBounds(instance)); } return true; } }; class bounds_selected_component : public scene::Graph::Walker { AABB& m_bounds; public: bounds_selected_component(AABB& bounds) : m_bounds(bounds) { m_bounds = AABB(); } bool pre(const scene::Path& path, scene::Instance& instance) const { Selectable* selectable = Instance_getSelectable(instance); if(selectable != 0 && selectable->isSelected()) { ComponentEditable* componentEditable = Instance_getComponentEditable(instance); if(componentEditable) { aabb_extend_by_aabb_safe(m_bounds, aabb_for_oriented_aabb_safe(componentEditable->getSelectedComponentsBounds(), instance.localToWorld())); } } return true; } }; void Scene_BoundsSelected(scene::Graph& graph, AABB& bounds) { graph.traverse(bounds_selected(bounds)); } void Scene_BoundsSelectedComponent(scene::Graph& graph, AABB& bounds) { graph.traverse(bounds_selected_component(bounds)); } #if 0 inline void pivot_for_node(Matrix4& pivot, scene::Node& node, scene::Instance& instance) { ComponentEditable* componentEditable = Instance_getComponentEditable(instance); if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && componentEditable != 0) { pivot = matrix4_translation_for_vec3(componentEditable->getSelectedComponentsBounds().origin); } else { Bounded* bounded = Instance_getBounded(instance); if(bounded != 0) { pivot = matrix4_translation_for_vec3(bounded->localAABB().origin); } else { pivot = g_matrix4_identity; } } } #endif void RadiantSelectionSystem::ConstructPivot() const { if(!m_pivotChanged || m_pivot_moving) return; m_pivotChanged = false; Vector3 m_object_pivot; if(!nothingSelected()) { { AABB bounds; if(Mode() == eComponent) { Scene_BoundsSelectedComponent(GlobalSceneGraph(), bounds); } else { Scene_BoundsSelected(GlobalSceneGraph(), bounds); } m_object_pivot = bounds.origin; } vector3_snap(m_object_pivot, GetSnapGridSize()); m_pivot2world = matrix4_translation_for_vec3(m_object_pivot); switch(m_manipulator_mode) { case eTranslate: break; case eRotate: if(Mode() == eComponent) { matrix4_assign_rotation_for_pivot(m_pivot2world, m_component_selection.back()); } else { matrix4_assign_rotation_for_pivot(m_pivot2world, m_selection.back()); } break; case eScale: if(Mode() == eComponent) { matrix4_assign_rotation_for_pivot(m_pivot2world, m_component_selection.back()); } else { matrix4_assign_rotation_for_pivot(m_pivot2world, m_selection.back()); } break; default: break; } } } void RadiantSelectionSystem::renderSolid(Renderer& renderer, const VolumeTest& volume) const { //if(view->TestPoint(m_object_pivot)) if(!nothingSelected()) { renderer.Highlight(Renderer::ePrimitive, false); renderer.Highlight(Renderer::eFace, false); renderer.SetState(m_state, Renderer::eWireframeOnly); renderer.SetState(m_state, Renderer::eFullMaterials); m_manipulator->render(renderer, volume, GetPivot2World()); } #if defined(DEBUG_SELECTION) renderer.SetState(g_state_clipped, Renderer::eWireframeOnly); renderer.SetState(g_state_clipped, Renderer::eFullMaterials); renderer.addRenderable(g_render_clipped, g_render_clipped.m_world); #endif } void SelectionSystem_OnBoundsChanged() { getSelectionSystem().pivotChanged(); } SignalHandlerId SelectionSystem_boundsChanged; void SelectionSystem_Construct() { RadiantSelectionSystem::constructStatic(); g_RadiantSelectionSystem = new RadiantSelectionSystem; SelectionSystem_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(FreeCaller()); GlobalShaderCache().attachRenderable(getSelectionSystem()); } void SelectionSystem_Destroy() { GlobalShaderCache().detachRenderable(getSelectionSystem()); GlobalSceneGraph().removeBoundsChangedCallback(SelectionSystem_boundsChanged); delete g_RadiantSelectionSystem; RadiantSelectionSystem::destroyStatic(); } inline float screen_normalised(float pos, std::size_t size) { return ((2.0f * pos) / size) - 1.0f; } typedef Vector2 DeviceVector; inline DeviceVector window_to_normalised_device(WindowVector window, std::size_t width, std::size_t height) { return DeviceVector(screen_normalised(window.x(), width), screen_normalised(height - 1 - window.y(), height)); } inline float device_constrained(float pos) { return std::min(1.0f, std::max(-1.0f, pos)); } inline DeviceVector device_constrained(DeviceVector device) { return DeviceVector(device_constrained(device.x()), device_constrained(device.y())); } inline float window_constrained(float pos, std::size_t origin, std::size_t size) { return std::min(static_cast(origin + size), std::max(static_cast(origin), pos)); } inline WindowVector window_constrained(WindowVector window, std::size_t x, std::size_t y, std::size_t width, std::size_t height) { return WindowVector(window_constrained(window.x(), x, width), window_constrained(window.y(), y, height)); } typedef Callback1 MouseEventCallback; Single g_mouseMovedCallback; Single g_mouseUpCallback; #if 1 const ButtonIdentifier c_button_select = c_buttonLeft; const ModifierFlags c_modifier_manipulator = c_modifierNone; const ModifierFlags c_modifier_toggle = c_modifierShift; const ModifierFlags c_modifier_replace = c_modifierShift | c_modifierAlt; const ModifierFlags c_modifier_face = c_modifierControl; #else const ButtonIdentifier c_button_select = c_buttonLeft; const ModifierFlags c_modifier_manipulator = c_modifierNone; const ModifierFlags c_modifier_toggle = c_modifierControl; const ModifierFlags c_modifier_replace = c_modifierNone; const ModifierFlags c_modifier_face = c_modifierShift; #endif const ModifierFlags c_modifier_toggle_face = c_modifier_toggle | c_modifier_face; const ModifierFlags c_modifier_replace_face = c_modifier_replace | c_modifier_face; const ButtonIdentifier c_button_texture = c_buttonMiddle; const ModifierFlags c_modifier_apply_texture1 = c_modifierControl | c_modifierShift; const ModifierFlags c_modifier_apply_texture2 = c_modifierControl; const ModifierFlags c_modifier_apply_texture3 = c_modifierShift; const ModifierFlags c_modifier_copy_texture = c_modifierNone; class Selector_ { RadiantSelectionSystem::EModifier modifier_for_state(ModifierFlags state) { if(state == c_modifier_toggle || state == c_modifier_toggle_face) { return RadiantSelectionSystem::eToggle; } if(state == c_modifier_replace || state == c_modifier_replace_face) { return RadiantSelectionSystem::eReplace; } return RadiantSelectionSystem::eManipulator; } rect_t getDeviceArea() const { DeviceVector delta(m_current - m_start); if(selecting() && fabs(delta.x()) > m_epsilon.x() && fabs(delta.y()) > m_epsilon.y()) { return SelectionBoxForArea(&m_start[0], &delta[0]); } else { rect_t default_area = { { 0, 0, }, { 0, 0, }, }; return default_area; } } public: DeviceVector m_start; DeviceVector m_current; DeviceVector m_epsilon; std::size_t m_unmoved_replaces; ModifierFlags m_state; const View* m_view; RectangleCallback m_window_update; Selector_() : m_start(0.0f, 0.0f), m_current(0.0f, 0.0f), m_unmoved_replaces(0), m_state(c_modifierNone) { } void draw_area() { m_window_update(getDeviceArea()); } void testSelect(DeviceVector position) { RadiantSelectionSystem::EModifier modifier = modifier_for_state(m_state); if(modifier != RadiantSelectionSystem::eManipulator) { DeviceVector delta(position - m_start); if(fabs(delta.x()) > m_epsilon.x() && fabs(delta.y()) > m_epsilon.y()) { DeviceVector delta(position - m_start); getSelectionSystem().SelectArea(*m_view, &m_start[0], &delta[0], modifier, (m_state & c_modifier_face) != c_modifierNone); } else { if(modifier == RadiantSelectionSystem::eReplace && m_unmoved_replaces++ > 0) { modifier = RadiantSelectionSystem::eCycle; } getSelectionSystem().SelectPoint(*m_view, &position[0], &m_epsilon[0], modifier, (m_state & c_modifier_face) != c_modifierNone); } } m_start = m_current = DeviceVector(0.0f, 0.0f); draw_area(); } bool selecting() const { return m_state != c_modifier_manipulator; } void setState(ModifierFlags state) { bool was_selecting = selecting(); m_state = state; if(was_selecting ^ selecting()) { draw_area(); } } ModifierFlags getState() const { return m_state; } void modifierEnable(ModifierFlags type) { setState(bitfield_enable(getState(), type)); } void modifierDisable(ModifierFlags type) { setState(bitfield_disable(getState(), type)); } void mouseDown(DeviceVector position) { m_start = m_current = device_constrained(position); } void mouseMoved(DeviceVector position) { m_current = device_constrained(position); draw_area(); } typedef MemberCaller1 MouseMovedCaller; void mouseUp(DeviceVector position) { testSelect(device_constrained(position)); g_mouseMovedCallback.clear(); g_mouseUpCallback.clear(); } typedef MemberCaller1 MouseUpCaller; }; class Manipulator_ { public: DeviceVector m_epsilon; const View* m_view; bool mouseDown(DeviceVector position) { return getSelectionSystem().SelectManipulator(*m_view, &position[0], &m_epsilon[0]); } void mouseMoved(DeviceVector position) { getSelectionSystem().MoveSelected(*m_view, &position[0]); } typedef MemberCaller1 MouseMovedCaller; void mouseUp(DeviceVector position) { getSelectionSystem().endMove(); g_mouseMovedCallback.clear(); g_mouseUpCallback.clear(); } typedef MemberCaller1 MouseUpCaller; }; void Scene_copyClosestTexture(SelectionTest& test); void Scene_applyClosestTexture(SelectionTest& test); class RadiantWindowObserver : public SelectionSystemWindowObserver { enum { SELECT_EPSILON = 8, }; int m_width; int m_height; bool m_mouse_down; public: Selector_ m_selector; Manipulator_ m_manipulator; RadiantWindowObserver() : m_mouse_down(false) { } void release() { delete this; } void setView(const View& view) { m_selector.m_view = &view; m_manipulator.m_view = &view; } void setRectangleDrawCallback(const RectangleCallback& callback) { m_selector.m_window_update = callback; } void onSizeChanged(int width, int height) { m_width = width; m_height = height; DeviceVector epsilon(SELECT_EPSILON / static_cast(m_width), SELECT_EPSILON / static_cast(m_height)); m_selector.m_epsilon = m_manipulator.m_epsilon = epsilon; } void onMouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) { if(button == c_button_select) { m_mouse_down = true; DeviceVector devicePosition(window_to_normalised_device(position, m_width, m_height)); if(modifiers == c_modifier_manipulator && m_manipulator.mouseDown(devicePosition)) { g_mouseMovedCallback.insert(MouseEventCallback(Manipulator_::MouseMovedCaller(m_manipulator))); g_mouseUpCallback.insert(MouseEventCallback(Manipulator_::MouseUpCaller(m_manipulator))); } else { m_selector.mouseDown(devicePosition); g_mouseMovedCallback.insert(MouseEventCallback(Selector_::MouseMovedCaller(m_selector))); g_mouseUpCallback.insert(MouseEventCallback(Selector_::MouseUpCaller(m_selector))); } } else if(button == c_button_texture) { DeviceVector devicePosition(device_constrained(window_to_normalised_device(position, m_width, m_height))); View scissored(*m_selector.m_view); ConstructSelectionTest(scissored, SelectionBoxForPoint(&devicePosition[0], &m_selector.m_epsilon[0])); SelectionVolume volume(scissored); if(modifiers == c_modifier_apply_texture1 || modifiers == c_modifier_apply_texture2 || modifiers == c_modifier_apply_texture3) { Scene_applyClosestTexture(volume); } else if(modifiers == c_modifier_copy_texture) { Scene_copyClosestTexture(volume); } } } void onMouseMotion(const WindowVector& position, ModifierFlags modifiers) { m_selector.m_unmoved_replaces = 0; if(m_mouse_down && !g_mouseMovedCallback.empty()) { g_mouseMovedCallback.get()(window_to_normalised_device(position, m_width, m_height)); } } void onMouseUp(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) { if(button == c_button_select && !g_mouseUpCallback.empty()) { m_mouse_down = false; g_mouseUpCallback.get()(window_to_normalised_device(position, m_width, m_height)); } } void onModifierDown(ModifierFlags type) { m_selector.modifierEnable(type); } void onModifierUp(ModifierFlags type) { m_selector.modifierDisable(type); } }; SelectionSystemWindowObserver* NewWindowObserver() { return new RadiantWindowObserver; } #include "modulesystem/singletonmodule.h" #include "modulesystem/moduleregistry.h" class SelectionDependencies : public GlobalSceneGraphModuleRef, public GlobalShaderCacheModuleRef, public GlobalOpenGLModuleRef { }; class SelectionAPI : public TypeSystemRef { SelectionSystem* m_selection; public: typedef SelectionSystem Type; STRING_CONSTANT(Name, "*"); SelectionAPI() { SelectionSystem_Construct(); m_selection = &getSelectionSystem(); } ~SelectionAPI() { SelectionSystem_Destroy(); } SelectionSystem* getTable() { return m_selection; } }; typedef SingletonModule SelectionModule; typedef Static StaticSelectionModule; StaticRegisterModule staticRegisterSelection(StaticSelectionModule::instance());