Viewport Refactoring

From K-3D

Jump to: navigation, search

Overview

Currently the main panel used for interaction with objects is the Viewport, and apart from this there are a number of other panels used for various editing of the document in general. While the existing panels all (apart from maybe the pipeline graph) are fundamentally different from the Viewport from UI point of view, there will in the future be a need for panels that will share some of the basic UI concepts with the Viewport. For example, the currently developed UV editor will have similar selection and navigation, and I envision an animation track editor that also could share some concepts. This proposal is an approach to providing a unified interface for these viewport panels, primarily from an end user perspective, to achieve a unified UI for the core interaction of different panels.

The three different basic areas of interaction that this proposal covers is navigation, selection and transformation. While there probably are a lot of other tools and things that can theoretically be shared, these are the core things I think make most sense in unifying, since they're (at least in my experience) by far the most used elements of interaction, and something you assume "just will work" in every part of the UI where it feels applicable.

Proposal

My proposal is to break out a common interface to be implemented by all panels where it's applicable. This interface would cover the three areas above. (There is a code example at the bottom of this page showing the current state of the interface.)

The interface can be used by navigation models and tools to interact with the view without necessarily "knowing what they're doing". The viewport itself works in something I refer to as "Viewport space", which in the regular viewport coincides with standard 3D X/Y/Z space, while in the UV editor it's U/V texture space, and in for example an animation track editor it could be time/value. The interface deals with widget coordinates, since the screen area is primarily what the UI knows about. (However, it also provides methods to convert to and from viewport space.)

View interaction is handled through a number of fairly specific view manipulations (track, dolly, zoom, orbit, rotate...). Of these many only make sense in a 3D view, and could be left empty by a 2D implementation. It still, in combination with the navigation model, provides a unified way of interacting throughout all panels. If middle mouse button tracks the view in one viewport type, it does in all, etc. While it doesn't actually provide much shared code for the viewport implementations, it provides a shared framework to make it happen.

As far as selection is concerned, it's fairly straight forward. For a given point or area in the viewport, selection records can be returned.

The interaction with transform tools is not fully fleshed out yet, but there is a fairly specific plan on how to solve this. Currently the transform tools (move, rotate and scale) are already somewhat generic, since they work through a "transform target" interface. Different types of selected components have different implementations of the target interface, and can respond differently to the tool input. While these targets are currently implemented as part of the tools themselves, I propose to move these implementations into the viewports, and in the viewport interface provide methods to query targets. This allows different viewports to provide quite different functionality as response to the tool input. Using the previous examples, the 3D viewport would move things in 3D space, the UV editor would move things in texture space, and an animation editor would move keys in time. All look the same from the tool side (all are transforms in some N-D space), and the major difference is the location & method of modifying data, which is a property the viewport knows about and can provide functionality for.

Course of action

  • Design an interface
    • Navigation (DONE)
    • Selection (DONE)
    • Transformation (in progress)
  • Let the 3D Viewport implement this interface (DONE for the interface so far)
  • Let everything that previously used the 3D Viewport directly work though the interface instead (almost done for all applicable places)
  • Let the UV editor implement the interface
    • Navigation (DONE)
    • Selection (DONE)
    • Transformation (in progress; manipulators are shown but no interaction yet)
  • Possibly implement for other panels like pipeline graph

Potential problems & improvements

  • The design is possibly over-generalizing by encouraging to use an interface for new panels that maybe doesn't quite fit
  • The design is possibly over-specific, making new panels hard to fit in
    • ...but on the other hand, this is why it only covers the most basic and fundamental ways of interaction, which is somewhat of a common denominator
  • For more elaborate panels to fit in, like advanced use cases of the pipeline viewer, selection might need to be extended to cover more meta-concepts (like selection of a node property, or similar). I'm on thin ice here now, it might not be needed or might already be possible. :)

Current interface

This is what the current proposal of the interface looks like. (Mostly just methods broken out of the Viewport implementation.)

class iviewport
{
public:

	/// Returns all points contained in the given rectangle in widget coordinates
	virtual k3d::selection::records get_selectable_points(const k3d::rectangle& SelectionRegion, bool Backfacing) { return k3d::selection::records(); }
	/// Returns all lines that intersect the given rectangle in widget coordinates
	virtual k3d::selection::records get_selectable_lines(const k3d::rectangle& SelectionRegion, bool Backfacing) { return k3d::selection::records(); }
	/// Returns all faces that intersect the given rectangle in widget coordinates
	virtual k3d::selection::records get_selectable_faces(const k3d::rectangle& SelectionRegion, bool Backfacing) { return k3d::selection::records(); }
	/// Returns all nodes that intersect the given rectangle in widget coordinates
	virtual k3d::selection::records get_selectable_nodes(const k3d::rectangle& SelectionRegion) { return k3d::selection::records(); }
	/// Returns all objects (points, lines, faces, or nodes, depending on selection mode) that intersect the given rectangle in widget coordinates
	virtual k3d::selection::records get_selectable_objects(const k3d::rectangle& SelectionRegion, bool Backfacing) { return k3d::selection::records(); }

	/// Returns the closest point at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_point(const k3d::point2& Coordinates, bool Backfacing)
	{ 
		k3d::selection::records records;
		return pick_point(Coordinates, records, Backfacing);
	}
	/// Returns the closest line at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_line(const k3d::point2& Coordinates, bool Backfacing)
	{ 
		k3d::selection::records records;
		return pick_line(Coordinates, records, Backfacing);
	}
	/// Returns the closest face at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_face(const k3d::point2& Coordinates, bool Backfacing)
	{ 
		k3d::selection::records records;
		return pick_face(Coordinates, records, Backfacing);
	}
	/// Returns the closest node at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_node(const k3d::point2& Coordinates)
	{ 
		k3d::selection::records records;
		return pick_node(Coordinates, records);
	}
	/// Returns the closest object (point, line, face, or node, depending on selection mode) at the given coordinates (may return an empty record)
	virtual k3d::selection::record pick_object(const k3d::point2& Coordinates, bool Backfacing)
	{ 
		k3d::selection::records records;
		return pick_object(Coordinates, records, Backfacing);
	}

	/// Returns the closest point at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_point(const k3d::point2& Coordinates, k3d::selection::records& Records, bool Backfacing) { return k3d::selection::record(); }
	/// Returns the closest line at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_line(const k3d::point2& Coordinates, k3d::selection::records& Records, bool Backfacing) { return k3d::selection::record(); }
	/// Returns the closest face at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_face(const k3d::point2& Coordinates, k3d::selection::records& Records, bool Backfacing) { return k3d::selection::record(); }
	/// Returns the closest node at the given widget coordinates (may return an empty record)
	virtual k3d::selection::record pick_node(const k3d::point2& Coordinates, k3d::selection::records& Records) { return k3d::selection::record(); }
	/// Returns the closest object (point, line, face, or node, depending on selection mode) at the given coordinates (may return an empty record)
	virtual k3d::selection::record pick_object(const k3d::point2& Coordinates, k3d::selection::records& Records, bool Backfacing) { return k3d::selection::record(); }

	/// Converts widget coordinates to normalized camera coordinates - note: results may be outside the range [-0, 1] because the viewport and camera aspect ratios may not match
	virtual const k3d::point2 widget_to_ndc(const k3d::point2& WidgetCoords) = 0;
	/// Converts normalized camera coordinates to widget coordinates
	virtual const k3d::point2 ndc_to_widget(const k3d::point2& NDC) = 0;
	/// Converts widget coordinates to a line in viewport space coordinates
	virtual const k3d::line3 unproject(const k3d::point2& WidgetCoords) = 0;
	/// Projects a point in viewport space coordinates into screen space, returning the 2D widget coordinates
	virtual const k3d::point2 project(const k3d::point3& WorldCoords) = 0;

	/// Translate the view in the view plane
	virtual void view_track(const k3d::vector2& Delta) {}
	/// Dolly the view
	virtual void view_dolly(double Delta) {}
	/// Zoom the view
	virtual void view_zoom(double Delta) {}
	/// Orbit the view around the target point of the viewport (if any)
	virtual void view_orbit(const k3d::vector2& Delta) {}
	/// Pan and tilt the view
	virtual void view_pan_tilt(const k3d::vector2& Delta) {}
	/// Roll the view
	virtual void view_roll(double Delta) {}
	/// Centers the given node in the viewport by changing the view orientation
	virtual void view_aim_node(k3d::inode* Node) {}
	/// Centers the current selection in the viewport by changing the view orientation
	virtual void view_aim_selection(document_state& DocumentState) {}
	/// Makes sure the current selection fits within the viewport by changing the view position
	virtual void view_frame_selection(document_state& DocumentState) {}

	/// Returns the viewport view matrix
	virtual const k3d::matrix4 get_view_matrix() = 0;

	/// Store the current view in a command_arguments
	virtual void store_view(command_arguments& arguments) {}
	/// Restore the current view from a command_arguments
	virtual void restore_view(const command_arguments& arguments) {}
};