Skip to content

Adding Interactivity With Event Listeners

Christian Schlinkmann edited this page Oct 26, 2015 · 11 revisions

Introduction

This tutorial builds on The Basics of XML3D by adding some interactivity to our teapot scene through event listeners. This time around we'll cover:

  • Adding mouse event listeners to objects
  • Changing an object's color
  • Changing an object's transformation

Adding mouse event listeners

After the previous tutorial our teapot scene now looks like this:

<html>
<head>
  <script type="text/javascript" src="http://www.xml3d.org/xml3d/script/xml3d.js"></script>
  <script type="text/javascript" src="http://www.xml3d.org/xml3d/script/tools/camera.js"></script>
  <title>My very first XML3D teapot</title>
</head>
<body>
    <xml3d>
      <!-- The surface shader of our teapot: -->
      <material id="orangePhong" script="urn:xml3d:material:phong">
        <float3 name="diffuseColor">1 0.5 0</float3>
        <float name="ambientIntensity">0.5</float>
      </material>

      <!-- Some light source in our scene -->
      <light id="light1" model="urn:xml3d:light:directional" >
        <float3 name="intensity">1 1 1</float3>
      </light>

      <!-- Our viewpoint from where we see the 3D content -->
      <transform id="cameraTransform" translation="0 0 100"></transform>
      <view transform="#cameraTransform"></view>

      <!-- Teapot. The one and only. Also orange. -->
      <group material="#orangePhong" style="transform: translate3d(0px,-20px,0px)">
        <mesh src="resource/teapot.json"></mesh>
      </group>
    </xml3d>
</body>
</html>

The first thing we'll do is add a function to be triggered each time the mouse pointer is over the teapot. Just like in HTML we can do this by adding an onmouseover listener to the teapot <mesh>:

function mouseOverTeapot(event) {
   alert("Mouse over teapot!");
}
<group material="#orangePhong" style="transform: translate3d(0px,-20px,0px)">
   <mesh src="resource/teapot.json" onmouseover="mouseOverTeapot(event)"></mesh>
</group>

Note: When adding listeners as attributes like this you should always write your function call as above, with "event" as the sole argument. If you need more flexibility you could add a listener through Javascript instead:

document.getElementById("some_element").addEventListener("mouseover", myFunction);

We now have a mouseover listener bound to our teapot mesh, but it isn't doing anything useful yet. Lets change it to change the teapot's color instead.

Changing the color of an object

The easiest way to change an object's color is to first create a second material. Lets create a new green phong material:

<material id="greenPhong" model="urn:xml3d:material:phong">
    <float3 name="diffuseColor" >0.1 0.8 0.1</float3>
    <float name="ambientIntensity" >0.5</float>
</material>

The next step is to assign this green material to our teapot object whenever the mouse pointer is over it. Lets change our mouseOverTeapot function to do just that:

function mouseOverTeapot(event) {
   event.target.setAttribute("material", "#greenPhong");
}

That's all there is to it! You may notice we've set the material attribute of the <mesh> element here. Because this attribute appears deeper in the DOM hierarchy it will override the material of the parent <group> element (which is still orange).

To change back to the orange material when the mouse leaves the teapot lets add a second listener for the onmouseout event:

<group material="#orangePhong" style="transform: translate3d(0px,-20px,0px)">
   <mesh src="resource/teapot.json" onmouseover="mouseOverTeapot(event)" onmouseout="mouseOut(event)"></mesh>
</group>
function mouseOut(event) {
   event.target.setAttribute("material", "");
}

By setting the <mesh> element's material attribute back to empty string the <group>'s orange material will automatically apply again.

Highlighting our teapot

Just like in HTML, events fired by XML3D elements will bubble up through the <group> hierarchy all the way to the root <xml3d> element, unless they're canceled prematurely. This makes it easy to add, for example, a generic mouseover listener to your scene that will highlight a selected object simply by adding it to a root <group> element. event.target will always be set to the <mesh> element that generated the event, while event.currentTarget will be the element that triggered the listener.

Lets use this fact to make the teapot shrink when clicked.

Changing an object's transformation

Just for fun, we're going to add our mousedown and mouseup listeners to a new <group> element rather than the teapot's <mesh>:

<group onmousedown="mouseDown(event)" onmouseup="mouseUp(event)">
    <group material="#orangePhong" style="transform: translate3d(0px,-20px,0px)">
       <mesh src="teapot.json" onmouseover="mouseOverTeapot(event)" onmouseout="mouseOut(event)"></mesh>
    </group>
</group>

This time, instead of using a CSS transformation, lets add a <transform> element to our scene to change the teapot's scale:

<transform id="scaleSmaller" scale="0.8 0.8 0.8"></transform>

Now lets create the listener functions mouseDown and mouseUp:

function mouseDown(event) {
   event.currentTarget.setAttribute("transform", "#scaleSmaller");
}

function mouseUp(event) {
   event.currentTarget.setAttribute("transform", "");
}

As you can see the process is much the same as with our highlight material. Because transformations stack the teapot will shrink when one of its parent groups (in this case the one with the mousedown and mouseup listeners) is given a smaller scaling. To revert it back to full size we just remove the reference to the scaling <transform> in the mouseUp listener.

Scaling our teapot

Of course, we could have added this scaling directly to the teapot <mesh> element too.

That's it for this time. The whole scene should now look like:

<html>
<head>
  <script type="text/javascript" src="http://www.xml3d.org/xml3d/script/xml3d.js"></script>
  <script type="text/javascript" src="http://www.xml3d.org/xml3d/script/tools/camera.js"></script>
  <title>My very first XML3D teapot</title>
  <script type="text/javascript">
	function mouseOverTeapot(event) {
		event.target.setAttribute("material", "#greenPhong");
	}
	
	function mouseOut(event) {
		event.target.setAttribute("material", "");
	}
	
	function mouseDown(event) {
	   event.currentTarget.setAttribute("transform", "#scaleSmaller");
	}

	function mouseUp(event) {
	   event.currentTarget.setAttribute("transform", "");
	}
  </script>
</head>
<body>
    <xml3d>
      <!-- The surface shader of our teapot: -->
      <material id="orangePhong" model="urn:xml3d:material:phong">
        <float3 name="diffuseColor">1 0.5 0</float3>
        <float name="ambientIntensity">0.5</float>
      </material>
      <material id="greenPhong" model="urn:xml3d:material:phong">
        <float3 name="diffuseColor">0.1 0.8 0.1</float3>
        <float name="ambientIntensity">0.5</float>
      </material>
	  
      <!-- A transform to scale an object down to 80% of its original size -->
      <transform id="scaleSmaller" scale="0.8 0.8 0.8"></transform>

      <!-- Some light source in our scene -->
      <light id="light1" model="urn:xml3d:light:directional">
        <float3 name="intensity">1 1 1</float3>
      </light>

      <!-- Our viewpoint from where we see the 3D content -->
      <transform id="cameraTransform" translation="0 0 100"></transform>
      <view transform="#cameraTransform"></view>

      <!-- Teapot. The one and only. Also orange, and now sometimes green! -->
      <group onmousedown="mouseDown(event)" onmouseup="mouseUp(event)">
	 <group material="#orangePhong" style="transform: translate3d(0px,-20px,0px)">
		   <mesh src="teapot.json" onmouseover="mouseOverTeapot(event)" onmouseout="mouseOut(event)" </mesh>
	 </group>
      </group>
    </xml3d>
</body>
</html>