====================
Using multiple views
====================

HOOPS Web Viewer 2024.6.0 and above supports multiple views on a single Web Viewer canvas. Common use cases include front, side, and top views, although you are able to set whatever camera your application requires. The number of views is only limited by the capability of the execution environment. 

These views can have their own camera, draw mode, lights, and more. In fact, anything that is modified using a call on the ``View`` class will be unique to that view. All these views share a single ``Model`` (as well as several other viewer concepts), so any change to the model will be reflected in all views. For example, if you're viewing a model with two views and move a part, it will move in all views.

There is a new type of key, the ``ViewKey`` which is an identifier unique to a view. The initial view has a key of ``0``. You're able to add and delete views as needed using functions added to the ``WebViewer`` class. When creating a new view, you need only supply a ``HTMLElement`` or a string containing the ID of the ``HTMLElement`` to set up a new 3D view. Each view is only drawn when it needs to be, so if you're rotating a camera in one view, the other views will not be drawn.

Once you have created your new view, it will have its own operator stack which allows you to customize interaction.

.. admonition:: Important

	A user can only create additional views after the ``ModelStructureReady`` callback has been triggered. Additionally, the initial view cannot be deleted.
	

Let's imagine we have a page with two ``HTMLElement`` containers with IDs of "viewer1" and "viewer2". To create a new view, simply call ``WebViewer.AddView`` with the ID of the container:

.. code-block:: javascript

	<div id="viewer1" class="hwv-view">Initial view</div>
	<div id="viewer2" class="hwv-view">New view</div>

	<script type="module">
	import * as Communicator from "./hoops-web-viewer.mjs";
	
	// initial view
	const hwv = new Communicator.WebViewer({
		container: "viewer1",
		enginePath: ".",
		endpointUri: "my_model.scs"
	});
	hwv.start();

	hwv.setCallbacks({
		modelStructureReady: () => {
			// new view
			hwv.addView({
				container: "viewer2"
			});
		}
	});
	</script>
	
The ``class="hwv-view"`` is not mandatory - you can use your own styling and layout. In the screenshot below, notice the multiple cameras provide different views, but a selection in one view also selects the same geometry in the second view.

.. image:: images/multiple-view.png

To remove a view, use ``WebViewer.removeView``. 

A more complete example which shows views being created in response to events can be found at *INSTALL_DIR/web_viewer/examples/multi_view.html* and its related script *INSTALL_DIR/web_viewer/examples/scripts/examples/multi_view.js*. This example uses classes to encapsulate the variables and logic, which is recommended for production applications.


API changes related to this feature
===================================

**Callbacks**

Several callbacks now have an additional ``ViewKey`` parameter if they happen in a particular view, for example, camera changes. This has been added as the last parameter, so it shouldn't affect existing callbacks if the user is not interested in multi-view. 

**Camera**

Projection matrices are partially based on the dimensions of the canvas, so an additional, optional ``View`` parameter has been added to functions that get these matrices. This only affects the default view if it isn't provided.

**Events**

Since input events can now potentially happen in a canvas that could differ from the default, they now take a ``ViewKey`` argument to their constructors. This also applies to keyboard input since we might want to know what view has focus when we press a key.

**Model**

The ``Model`` class is largely unaffected. However, CAD views and CAD configurations need to take place in a specific view. So a parameter has been added. 

**Selection**

Since there are multiple cameras now, we need to know which view we're picking from when we do a screen-based selection. Several selection functions have been changed this way, with an optional ``View`` parameter at the end. This defaults to the default view if not specified.

**Snapshots**

``SnapshotConfig`` has a optional parameter for a ``ViewKey``. This is also able to be set at the end of the constructor.

**View**

Several managers have been moved from the ``WebViewer`` to the ``View`` class since they are more suited to individual views than they are the global viewer. These are ``OverlayManager``, ``FloorplanManager``, and ``OperatorManager``. Each view now has its own operator stack, managed by the ``OperatorManager``. Any functions called on the view affect only the view in the canvas they own. So setting lights in one view should do nothing for other views, for example.

**WebViewer**

As above, the viewer no longer *directly* manages the ``OverlayManager``, ``FloorplanManager``, or the ``OperatorManager``. These symbols still exist in the ``WebViewer`` for compatibility, they simply point to the default view's version of these objects. The ``WebViewer`` also still has a ``view`` field which is simply the default view. There are also new functions to ``addView``, ``removeView``, and ``getView``, and a new field ``views`` which is an array of views.

**CallbackMap**

The following have an additional `ViewKey` parameter at the end:
+ ``camera``
+ ``frameDrawn``
+ ``overlayViewportSet``
+ ``transitionBegin``
+ ``transitionEnd``
+ ``viewOrientation``

**Camera**

The following have an additional optional ``View`` parameter at the end:
+ ``getProjectionMatrix``
+ ``getFullMatrix``

**Event.InputEvent**

A constructor and a getter have been added:
+ ``constructor(viewKey: ViewKey)``
+ ``get viewKey(): ViewKey``

Additionally, the following child types are affected by this. They now take a ``ViewKey`` as the last argument to their constructor:

+ ``KeyInputEvent``
+ ``MouseInputEvent``
+ ``MouseWheelInputEvent``
+ ``TouchInputEvent``

**Model**

+ ``activateCadView`` takes an ``AbstractView`` as its first parameter
+ ``activateDefaultCadView`` takes an ``AbstractView`` as its first parameter
+ ``activateCadConfiguration`` takes an ``AbstractView`` as its first parameter
+ ``activateDefaultCadConfiguration`` takes an ``AbstractView`` as its first parameter

**SelectionManager**

The following take an optional ``View`` as their last parameter:

+ ``selectFromPoint``
+ ``selectAllFromPoint``
+ ``selectFromRay``
+ ``selectAllFromRay``
+ ``beginScreenSelectByArea``
+ ``beginRayDrillSelection``

**SnapshotConfig**

Now has a public ``ViewKey`` field and in addition the view key can be specified at construction as the last optional parameter.

**View**

The ``View`` class now has a ``OperatorManager``, ``FloorplanManager``, and ``OverlayManager`` taken from the ``WebViewer``. The ``WebViewer`` version of these symbols simply point to the managers for view ``0``.

**WebViewer**

``view`` is now a ``get`` which returns the default ``View``. The ``OverlayManager``, ``FloorplanManager``, or the ``OperatorManager`` are all now just getters for those managers on the default view. New functions have been added to manage views:  ``addView``, ``removeView``, and ``getView``. ``addView`` must be supplied an ``HTMLElement`` or ``string`` with the ID of an ``HTMLElement`` where the new view will be situated. ``removeView`` takes a ``ViewKey`` and removes the associated view. ``getView`` takes a ``ViewKey`` and returns the ``View`` it belongs to.

**Legacy Viewer**

The legacy viewer (the pre-ESM viewer) shares an engine with the ESM viewer and thus the engine is aware of the concept of multi-views even though there should be no actual API changes. Everything that worked before should still work, any behavior that has changed due to multi-canvas is a bug.


Limitations
===========

* Cutting planes work, but if there are several views, capping will only be drawn in one of them after a cutting plane drag. An update to a view will be drawn with capping intact.
* Some effects such as simple shadow and bloom might behave poorly on certain systems if your canvases are of different sizes.
* Certain selections such as sphere or convex polyhedron selections will operate on the visibility state of the default view.