You are reading the documentation for the version 2 of the ShapeDiver viewer API. Free support for this viewer version will be discontinued on June 1st, 2024.

Please refer to the version 3 documentation here. See also the migration guide from version 2 to version 3 here.

Options
All
  • Public
  • Public/Protected
  • All
Menu

Snap Module

This module is an extension to the interaction functionality that the ShapeDiver API offers. It allows to define Anchors in the local coordinate space of the object that can snap to points and lines. In this little tutorial we will explain step by step how to use this module. The public API documentation can be found here: SnapModule

Theoretical Definiton

In this theoretical part, we explain which elements are used to create the snapping possibility and which possibilities can be used to adjust it.

Snap Elements

To be able to snap an object, there is a need to define elements that the object can snap to. For now, we have defined, that one can snap to points and lines. These elements can be distinguished by an ID. To define what point is close enough to snap the object to this element, the snapRadius can be adjusted accordingly. Furthermore, just some anchor elements can be selected that snap to these element. Also, an orientation can be defined, a closer explanation on how the orientation works can be seen in the section below.

Anchor Elements

With the snap elements defined, it is now needed to define elements that can snap to them. This is done by defining anchor elements that apply to the currently dragged object. The position of these elements are defined in relation to the local coordinate system of the currently dragged object. Therefore, they stay consistent even when transformations are applied. An anchor element can be seen as adding another vertex to the object. It moves with it and keeps consistent positioning.

Orientation

An important factor is the orientation that can be provided for snap and anchor elements. The main idea is that, when the anchor snaps to a snap element, an automatic rotation can occur. This orientation consists of an up vector (default z-axis) and a directional vector (default y-axis).

The process of applying the orientational component works as follows. The object is oriented so that the anchor elemenets orientation than fits the snap elements orientation. In the following diagram this process is visualized in 2D.

Example

The snap element is the point in the middle that is pointed towards the negative x-axis. The object is the black quad with the ShapeDiver logo in it. One object anchor is positioned at the top right corner. This anchor is pointed towards the positive x-axis. As the object snaps to the point, the object is rotated so that the direction of the snap element and the anchor element are the same.

Snapping

In simple cases like in the image above the snapping is straigt forward. As soon as an object is close enough to be within the snapping radius, the anchor element will snap to the snap point. In cases with multiple anchor elements and multiple snap points, there is a strategy that is followed to ensure conistency.

The strategy consists of three steps:

  1. Calculate the distance to all snap points with the object center. If there is only one snap element where the object is within the radius and of minimal distance, the process skips ahead to step 3.
  2. If there are multiple snap elements where the object is within the radius and that have a minimal distance, the orientation is used as a factor. In this case the dot product between the direction to the object and the orientation direction of the snap object is regarded.
  3. In the last step, the snap element is now chosen and the anchor has to be determined. This can be done by snapping and rotating the object for all anchors, and chosing the anchor with the smallest distance.

This process is shown in the next images in detail. Example

In this example we have three snap points (two are on the exact same position) and two anchor elements. In the first step, in the top left image, we measure the distance from the object center to the snapping elements (step 1). As the center points are closer than the snap point in the top right and the snap radius of the top right point doesn't expand up to the object, the top right point is discarded.

The second step is shown in the top right image, here we measure the dot product to the object center and see which one is bigger. Therefore, we now have our snap point with the point that is orientated towards the negative y-axis.

The third and final step is for the selection of the anchor element. In the bottom left image we project the object for every anchor and select the one where the projected object position is closer to the original position.

The final result can be seen in the bottom right picture. As this example already makes clear, there are different ways to achieve results. It also shows the flexibility with which this snap module can be used.

Tutorial

In this very simple tutorial, we go through the necessary steps that are needed to set up the Snap Module. We will create a use case where an object can be dragged to two different points and can be orientated acoordingly. Furthermore, we create a line to snap to and explain how to handle the end points of the line.

Test Model

We start with a very simple example in which we have an asset that consists out of a cube and a cone. Without calling any API functions we get this:

Interaction Setup

Now we want to be able to interact with this example. For this, we have to update the asset via the API with an interaction group so that it can be dragged and hovered.

In a first step, we create an interaction group, with the scene API call updateInteractionGroups. Before doing that, we create the effect that should be applied once the object is being hovered or dragged. In our case we want it to be colored red in this case. In the following API call, we define hoverable and draggable to be true and define the corresponding effects. selectable is set to false as we do not want the asset to be selectable in our example.

const effect = {
    active: {
        name: 'colorHighlight',
        options: {
            color: 'red',
        },
    },
    passive: null,
};

api.scene.updateInteractionGroups([{
    id: 'example_interactionGroup',
    hoverable: true,
    hoverEffect: effect,
    draggable: true,
    dragEffect: effect,
    selectable: false,
}]);

As we have now defined and created the interaction group, we also have to assign it to the asset. This can be done with a call to the scene API function updateAsync. We first get our asset with the name (we recommend error checking here, but just left it out for readability) and add the interaction group to it. We also define the interaction mode and how the asset can be dragged. See the documentation on the scene asset for further instructions on this. In the end we call updateAsync to apply the changes to the asset in the scene. As can be seen in the example below, the asset can now be dragged.

let asset = api.scene.get({name: "DraggableObject"}, 'CommPlugin_1').data[0];
delete asset.version;
asset.interactionGroup = 'example_interactionGroup';
asset.interactionMode = 'global';
asset.dragPlaneNormal = { x: 0, y: 0, z: 1 };
api.scene.updateAsync(asset, 'CommPlugin_1');

As can be seen in the example, we can hover and drag the asset now and there is a red highlight applied when doing so.

Creation of the Snap Module

The next step is now to create the actual snap module. This can simply be done by creating a new snap module with the API as the only parameter. We also add event listeners to the events of DRAG_START and DRAG_END. The callbacks that are provided here are defined a bit further down.

const snapModule = new SDVApp.apps.snapModule(api);
snapModule.addEventListener(snapModule.EVENTTYPE.DRAG_START, _dragStart);
snapModule.addEventListener(snapModule.EVENTTYPE.DRAG_END, _dragEnd);

The Anchor Elements

Whenever we start dragging the object, there are a few things we want to define. First of all we want to define the anchors on the object that can snap to things. As we want them to be at the back-bottom positions of the model we therefore define them as follows in the call back of the DRAG_START event.

snapModule.addAnchorElement({
    id: 'anchor_0',
    position: new THREE.Vector3(asset.bbmin[0], asset.bbmax[1], 0),
});
snapModule.addAnchorElement({
    id: 'anchor_1',
    position: new THREE.Vector3(asset.bbmax[0], asset.bbmax[1], 0),
});

As we want to remove them again after the interaction has finished, we also have to do that in the DRAG_END event call back.

snapModule.removeAnchorElement('anchor_0');
snapModule.removeAnchorElement('anchor_1');

The Snap Points

In the next step, we define our first snap elements, two snap points. This is done in the DRAG_START event call back.

snapModule.addSnapPoint({
    id: 'point_0',
    snapRadius: 15,
    position: new THREE.Vector3(50, 0, 0),
});
snapModule.addSnapPoint({
    id: 'point_1',
    snapRadius: 15,
    position: new THREE.Vector3(-50, 0, 0),
});

Again, we also have to remove the points once our interaction has stopped. This is done in the DRAG_END event call back.

snapModule.removeSnapPoint('point_0');
snapModule.removeSnapPoint('point_1');

The example below shows our current results. Please note that the little sphere is an extra asset that we added to the scene just to visualize the snap element. Normally, this snap elements are not visible in the scene.

Adjusting the Orientation

In the previous example, we left out the orientation of the snap points and the anchor elements. We know do that part in this second step.

First, we have to evaluate, how we want our object to rotate once it is snapped. We defined that the object should point inwards for both snap points. This means that we define the orientation of the snap points to point towards the middle.

snapModule.addSnapPoint({
    id: 'point_0',
    snapRadius: 15,
    position: new THREE.Vector3(50, 0, 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(-1, 0, 0),
    }
});
snapModule.addSnapPoint({
    id: 'point_1',
    snapRadius: 15,
    position: new THREE.Vector3(-50, 0, 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(1, 0, 0),
    }
});

For the anchor elements that means the standard direction has to be negated.

snapModule.addAnchorElement({
    id: 'anchor_0',
    position: new THREE.Vector3(asset.bbmin[0], asset.bbmax[1], 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(0, -1, 0),
    }
});
snapModule.addAnchorElement({
    id: 'anchor_1',
    position: new THREE.Vector3(asset.bbmax[0], asset.bbmax[1], 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(0, -1, 0),
    }
});

In the following example you can see the results of this process. Our object is now pointing inwards when snapping.

Note that there are multiple ways to achieve these results:

  1. As described above.
  2. Leaving the anchor element directions the default and negating the snap point directions from the previous example.
  3. Creating a duplicate set of snap points at the positions that point toward the oposite directions. In this case the anchor elements that are targeted would have to be specified specically in the snap point definition.

The Snap Lines

In the next step, we also want to add a line to snap to. We define this line to be at the top border of the plane. Again, as with the creation of snap points and anchor elements, we call this in the DRAG_START event call back. As we want the object to be pointing towards the middle again, we negate the orientation to fit the orientation of the anchor elements.

snapModule.addSnapLine({
    id: 'line_0',
    snapRadius: 10,
    positionStart: new THREE.Vector3(50, 50, 0),
    positionEnd: new THREE.Vector3(-50, 50, 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(0, -1, 0),
    }
});

In the DRAG_END event call back we remove the line again.

snapModule.removeSnapLine('line_0');

In the following example a visual aid in form of a cylinder is added to the scene to visualize the snap line. As with the snap points, the snap line would not be visible otherwise.

The Snap Line Ends

As you can see from the previous example, the results of the snapping when coming to one of the ends is not consistent. To keep the flexibility, but also the simplicity with these lines, there have to be made some adjustements so that our object doesn't move out of the plane when snapped.

This can be achieve by adding two snap points to then ends of the line, to ensure that the object doesn't move any further. As snap points have a higher priority than lines, these points will always be targeted, as soon as you are close enough.

The points that we create in the DRAG_START event call back, have the same orientation as the line and the same positions as the start and end point. On addition that is made is the specification of anchor elements. As we only want one anchor to snap to the end points, we have to specify which anchor that should be.

snapModule.addSnapPoint({
    id: 'point_2',
    snapRadius: 15,
    position: new THREE.Vector3(50, 50, 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(0, -1, 0),
    },
    anchorElements: ['anchor_1'],
});
snapModule.addSnapPoint({
    id: 'point_3',
    snapRadius: 15,
    position: new THREE.Vector3(-50, 50, 0),
    orientation: {
        axis: new THREE.Vector3(0, 0, 1),
        direction: new THREE.Vector3(0, -1, 0),
    },
    anchorElements: ['anchor_0'],
});

Again, we also have to remove the points once our interaction has stopped. This is done in the DRAG_END event call back.

snapModule.removeSnapPoint('point_2');
snapModule.removeSnapPoint('point_3');

In the following example, we can now see the results of our little tutorial in all its glory.

As you can guess, there are many ways how to use this snap module. So be creative and enjoy!

The full public API documentation can be seen here: SnapModule

Index