Script when the camera is close to an object

Hello everyone !

I’m looking for a way to activate a script when the camera is close to an object. I tested with getCameraPosition functions, but there is not enough documentation. Can you give some information ?

Cedric

Hi @Cedric_atlantis

It is possibile, by using part of the API that has not been made official yet:
You could get your object data by using this function:

viewer.findNodesOfType('OBJECT TYPE NAME'))

OBJECT TYPE NAME refers to object name in editor:
Please use only object that are meshes directly (indicated by circle left to the name in the list) and not nodes that group meshes together (indicated by arrow left to the name in the list. Only by using meshes directly you can access their position.

Here is an example script that will:

  • Look for mesh named ‘Group61’,
  • Read mesh bounding box center and store to a variable
  • Calculate mesh center distance to camera on each frame.
<script>
  var viewer = WALK.getViewer();

  let meshCenter = null

  function checkProximity() {
    const cameraPosition = viewer.getCameraPosition();
    const distance = cameraPosition.distanceTo(meshCenter);
  
    if (distance < 2) {
      // do something
    }

    requestAnimationFrame(checkProximity);
  }

  function onSceneLoadComplete() {
    const nodes = viewer.findNodesOfType('Group61').pop();
    const mesh = nodes.mesh;

    meshCenter = mesh.geometry.boundingSphere.center;

    requestAnimationFrame(checkProximity);
  }
  viewer.onSceneLoadComplete(onSceneLoadComplete);
</script>

Best

Thank you for that ! In your example, is there an activation of a script or an extension?

You could activate your script at the do something part in the if block.
However, this would fire the script each frame the camera is below certain distance.

We could add additional condition to check whether we should activate te script. We could reset the condition once we move away from the object.

The updated version of the script could look like this.
In the example I’m activating popup extension.

<script>
  const viewer = WALK.getViewer();

  const popupContent =
    '<div style="font-size: 1.5em">' +
    '  <span style="font-size: 1.5em">Welcome</span>' +
    "  <hr/>" +
    "  Hello Visitor!" +
    "</div>";

  let meshCenter = null;
  let cameraWithinDistance = false;

  function showHtmlPopup() {
    viewer.openPopup(popupContent, {
      centerHorizontally: true,
      centerVertically: true,
    });
  }

  function checkProximity() {
    const cameraPosition = viewer.getCameraPosition();
    const distance = cameraPosition.distanceTo(meshCenter);

    if (distance < 2 && !cameraWithinDistance) {
      showHtmlPopup()
      cameraWithinDistance = true;
    }

    if (distance >= 2) {
      cameraWithinDistance = false;
    }

    requestAnimationFrame(checkProximity);
  }

  function onSceneLoadComplete() {
    const nodes = viewer.findNodesOfType("Group61").pop();
    const mesh = nodes.mesh;

    meshCenter = mesh.geometry.boundingSphere.center;

    requestAnimationFrame(checkProximity);
  }
  viewer.onSceneLoadComplete(onSceneLoadComplete);
</script>

in the top ! I’m testing this tonight

Good morning ! I just added this script in my body-end by modifying the NAME OF THE OBJECT TYPE but nothing happens…

An idea ?

Moreover, is it possible to activate an extension created from shapespark as a switch object?

THANKS !

Is there anything other than “meshCenter”?

Hi @Cedric_atlantis

This script should work, Bute there is one caveat that I did not anticipate. NAME OF THE OBJECT TYPE might refer to multiple objects. In this instance the script will return the last one of the found objects and we don’t have a way to determine which one this would be. We don’t have a way to filter objects by name in editor unfortunately so there is no reliable way to tell if there are multiple objects with the same name. Sorry for the confusion.

There are two solutions to this issue:

  • Use a mesh with unique name and unique geometry that will represent the trigger in your scene.
  • Provide a point coordinates to use instead of mesh center point.

To lookup a point coordinates you can position camera in Viewer tab, and create new WALK view. The camera orientation will yield it’'s current position. (you can also click on “set current” to update coordinates)
image

To use a point coordinate in the script we can drop the mesh lookup nad pass the points parameters directly via new THREE.Vector3(X, Y, Z) object:

Here is updated script:

<script>
  const viewer = WALK.getViewer();

  const popupContent =
    '<div style="font-size: 1.5em">' +
    '  <span style="font-size: 1.5em">Welcome</span>' +
    "  <hr/>" +
    "  Hello Visitor!" +
    "</div>";

  let meshCenter = new THREE.Vector3(1.383, -3.652, 0.744);
  let cameraWithinDistance = false;

  function showHtmlPopup() {
    viewer.openPopup(popupContent, {
      centerHorizontally: true,
      centerVertically: true,
    });
  }

  function checkProximity() {
    const cameraPosition = viewer.getCameraPosition();
    const distance = cameraPosition.distanceTo(meshCenter);

    if (distance < 2 && !cameraWithinDistance) {
      showHtmlPopup()
      cameraWithinDistance = true;
    }

    if (distance >= 2) {
      cameraWithinDistance = false;
    }

    requestAnimationFrame(checkProximity);
  }

  function onSceneLoadComplete() {
    requestAnimationFrame(checkProximity);
  }

  viewer.onSceneLoadComplete(onSceneLoadComplete);
</script>

Let me know if this will work!

1 Like

@Cedric_atlantis

About the second part of your question:

It is possible to activate an extensions created from Shapespark editor.
You can get the extension by using viewer.getExtension('type', 'name') and fire it up with trigger() function.

image
In case of ‘switch objects’ the function should like this:

const extension = viewer.getExtension('SwitchObjects', '1');
extension.trigger();

if you would like to switch objects based on proximity you can modify the script I’ve provided:
This script will trigger switchObjects extension if camera is closer than 2 meters to the point specified and if the camera will move away the switchObjects extension is called again.

<script>
  const viewer = WALK.getViewer();

  let triggerPoint = new THREE.Vector3(1.383, -3.652, 0.744);
  let extension = null;
  let cameraWithinDistance = false;

  function showHtmlPopup() {
    viewer.openPopup(popupContent, {
      centerHorizontally: true,
      centerVertically: true,
    });
  }

  function triggerExtension() {
    extension.trigger()
  }

  function checkProximity() {
    const cameraPosition = viewer.getCameraPosition();
    const distance = cameraPosition.distanceTo(triggerPoint);

    if (distance < 2 && !cameraWithinDistance) {
      triggerExtension()
      cameraWithinDistance = true;
    }

    if (distance >= 2 && cameraWithinDistance) {
      triggerExtension()
      cameraWithinDistance = false;
    }

    requestAnimationFrame(checkProximity);
  }

  function onSceneLoadComplete() {
    extension = viewer.getExtension('SwitchObjects', '1');
    requestAnimationFrame(checkProximity);
  }

  viewer.onSceneLoadComplete(onSceneLoadComplete);
</script>

@heung_aile

Please look at my explanation how to use point instead of mesh in examples I’ve provided.

hello and thank you for your response!

My items are unique items so I shouldn’t have a problem. I’m going to try with the triangulation data you gave me because I placed this script in my body-end but it doesn’t work.
I did a test in a scene where I only put one cube

There is no camera orientation setting in the Viewer tab.

So I found the view position in the “scene.json” file and used it.

image

let meshCenter = new THREE.Vector3(-0.04766218254864165, -13.19379844758102, 1.7559728311134728);
let cameraWithinDistance = false;

function checkProximity() {
const cameraPosition = viewer.getCameraPosition();
const distance = cameraPosition.distanceTo(meshCenter);
viewer.setMaterialEditable(“SCREEN”);
var videoTexture = viewer.findMaterial(“SCREEN”).baseColorTexture;
if (distance < 2 && !cameraWithinDistance) {
if (videoTexture.isPlaying) {
videoTexture.pause();
} else {
videoTexture.play();
}

  cameraWithinDistance = true;

  viewer.requestFrame();
  return true;
}

if (distance >= 2) {
  cameraWithinDistance = false;
  if (videoTexture.isPlaying) {
    videoTexture.pause();
  } else {
    videoTexture.play();
  }
  viewer.requestFrame();
  return true;
}

requestAnimationFrame(checkProximity);

}

function onSceneLoadComplete() {
requestAnimationFrame(checkProximity);
}

viewer.onSceneLoadComplete(onSceneLoadComplete);

The function to work only works once, I want to play it when the video stops, and I want it to stop when it’s playing

Can we get them to react every time they go to the area?This feature reacts only once when the user moves

Hi @heung_aile

I’m not sure if this is main issue, but it seems to me that in your code the second if-block is missing cameraWithinDistance check.

    if (distance < 2 && !cameraWithinDistance) {
      triggerExtension()
      cameraWithinDistance = true;
    }

    if (distance >= 2 && cameraWithinDistance) {
      triggerExtension()
      cameraWithinDistance = false;
    }

This part of the code will trigger function once each time when camera moves within distance specified and when it leaves the distance specified. Without the second argument (&& cameraWithinDistance) the function declared in that block will be called continuously.

if (distance < 2 && !cameraWithinDistance) {
      triggerExtension()
      cameraWithinDistance = true;
    }

    if (distance >= 2) {
      triggerExtension()
      cameraWithinDistance = false;
    }

it’s right??

I want to add various functions depending on the position.

let meshCenter = new THREE.Vector3(-11.740804869432607, -23.442055358814674, 2.0012483651156687);
  let triggerPoint = new THREE.Vector3(-0.17461756312701143, -12.563396708711661, 2.0012483651156687);
  let extension = null;
  let extension2 = null;
  let cameraWithinDistance = false;

  function triggerExtension() {
    extension.trigger();
  }
  function triggerExtension2() {
    extension2.trigger();
  }

  function checkProximity() {
    const cameraPosition = viewer.getCameraPosition();
    const distance = cameraPosition.distanceTo(meshCenter);
    const distance2 = cameraPosition.distanceTo(triggerPoint);

    if (distance < 2 && !cameraWithinDistance) {
      
      triggerExtension2();

      cameraWithinDistance = true;
    }
    
    
    if (distance2 < 2 && !cameraWithinDistance) {
      triggerExtension();
      cameraWithinDistance = true;
    }

    if (distance2 >= 2 && cameraWithinDistance) {
      triggerExtension();
      cameraWithinDistance = false;
    }
    

    requestAnimationFrame(checkProximity);
  }

  function onSceneLoadComplete() {
    extension = viewer.getExtension("VideoTextureControl", "1");
    extension2 = viewer.getExtension("HtmlLabel", "1");

    requestAnimationFrame(checkProximity);
  }

  viewer.onSceneLoadComplete(onSceneLoadComplete);

To use multiple functions you would need to duplicate both variables ‘distance’ and ‘cameraWithinDistance’.

for example:

  • distance1 and distance2
  • cameraWithinDistance1 and cameraWithinDistance2

so basically you would duplicate your code for each function that needs to be called:

   if (distance1 < 2 && !cameraWithinDistance1) {
      triggerExtension1()
      cameraWithinDistance1 = true;
    }

    if (distance1 >= 2 && cameraWithinDistance1) {
      triggerExtension1()
      cameraWithinDistance1 = false;
    }

   if (distance2 < 2 && !cameraWithinDistance2) {
      triggerExtension2()
      cameraWithinDistance2 = true;
    }

    if (distance2 >= 2 && cameraWithinDistance2) {
      triggerExtension2()
      cameraWithinDistance2 = false;
    }

Best!

1 Like

It’s working well , thx :slight_smile:

1 Like

Good morning !

Both scripts work, one to activate an extension, the other to open a popup when approaching an object.

I tried to combine the two so that when I approach “Cube 1”, it activates an extension, without going through the coordinates of a point, but I can’t do it…

Can you help me ?

<script>
  const viewer = WALK.getViewer();

  let extension = null;
  let cameraWithinDistance = false;

  function triggerExtension() {
    if (extension) {
      extension.activate();
    }
  }

  function checkProximity() {
    const cameraPosition = viewer.getCameraPosition();
    const nodes = viewer.findNodesOfType('Cube');
    if (nodes.length > 0) {
      const mesh = nodes[0].mesh;
      const distance = cameraPosition.distanceTo(mesh.position);

      if (distance < 2 && !cameraWithinDistance) {
        cameraWithinDistance = true;
        triggerExtension();
      }

      if (distance >= 2 && cameraWithinDistance) {
        cameraWithinDistance = false;
      }
    }

    requestAnimationFrame(checkProximity);
  }

  function onSceneLoadComplete() {
    extension = viewer.getExtension('SwitchObjects', '1');
    requestAnimationFrame(checkProximity);
  }

  viewer.onSceneLoadComplete(onSceneLoadComplete);
</script>