2.1 Hello Cube Interactions

This topic covers the basics of user input with MagicScript Components in your Magic Leap app. In it you'll add another component, a Toggle, to the Hello Cube example and make the MagicScript cube rotate when the toggle is on.

Toggle Rotate MagicScript Cube

Pre-requisites

Before starting this tutorial, you should complete Hello, Cube! MagicScript.

Add Rotation

The three-dimensional rotation properties in MagicScript Components are represented as quaternions, which are compact, simple, and convenient for axis-angle representations in homogeneous coordinates. Simply, quaternions are determined from the angle about a unit axis and are represented as [x, y, z, w]. See Quaternions and spatial rotation for more information.

Euler angles are an easy way to specify rotations around specific axes and are commonly referred to as yaw (around z-axis), pitch (around y-axis), and roll (around x-axis). In this example, we recommend using Euler angles to create the rotation angles and then convert them to unit quaternions to update the rotation property on the Model component. A conversion function is provided for you in the steps below. See Conversion between quaternions and Euler angles for more information. Important: The coordinate system in MagicScript is y down and +z forward.

Now that you have the basics of rotation in three-dimensions, let's make a cube rotate around the y-axis.

  1. In the helloCube/src/app.js file from the Hello, Cube! example, add a rotation quaternion variable for the Cube to the app state and initialize it with [0, 0 ,0 ,0] in the constructor. A constructor is a special function that creates and initializes an instance of the class. With React, the constructor is the only place where you should assign this.state directly for a component. For updating the app state anywhere else, call this.setState, which calls the render function for you and updates the app.
constructor (props) {
super(props);
this.state = {
rotation: [0, 0, 0, 0]
};
  1. Add the local rotation property to the Cube tag and connect it to the app state's rotation, this.state.rotation.
<Model
modelPath={require("../../assets/MagicScriptCube.glb")}
localRotation={this.state.rotation}>
</Model>
  1. Create a method to make an object rotate around its y-axis using the current timestamp from Date.now() as the angle for rotation.
updateRotation = () => {
/**
* Rotates the cube.
*/
let pitch = Date.now() / 1000;
let roll = 0;
let yaw = 0;
let newRotation = this.eulerToQuaternion(pitch, roll, yaw);
this.setState({ rotation: newRotation });
}
eulerToQuaternion(pitch, roll, yaw) {
/**
* Converts Euler angles to unit quaternions.
* @see [Wikipedia]{@link https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles}
*
* @param {Number} pitch - y
* @param {Number} roll - x
* @param {Number} yaw - z
* @returns {Number Array} unit quaternion represented as [x, y, z, w]
*/
let cy = Math.cos(yaw * 0.5);
let sy = Math.sin(yaw * 0.5);
let cr = Math.cos(roll * 0.5);
let sr = Math.sin(roll * 0.5);
let cp = Math.cos(pitch * 0.5);
let sp = Math.sin(pitch * 0.5);
let w = cy * cr * cp + sy * sr * sp;
let x = cy * sr * cp - sy * cr * sp;
let y = cy * cr * sp + sy * sr * cp;
let z = sy * cr * cp - cy * sr * sp;
return [ x, y, z, w];
}
  1. Set up a timer to update the cube's rotation every 10 milliseconds. The timer starts when the component is rendered for the first time (mounted in React terms), and the timer is cleaned up when the component is removed (unmounted).
constructor (props) {
super(props);
this.state = {
rotation: [0, 0, 0, 0]
};
this.interval = 10; // Milliseconds to elapse between update calls
}
componentDidMount() {
/**
* Called after the component is mounted. Starts the update intervals.
*/
this.updateRotation();
this.handler = setInterval(this.updateRotation, this.interval);
}
componentWillUnmount() {
/**
* Stops the update intervals, after the component is unmounted.
*/
clearInterval(this.handler);
}
  1. Build and run the app to verify that the cube starts rotating on app start. Connect a Magic Leap device to your computer via USB. Open a command-line session from The Lab. In the command line session, navigate to the helloCube app project folder and enter: magic-script build -i. Launch the Hello, Cube! app on the device. To view the app using the Simulator without a device, see Zero Iteration MagicScript.

Add Toggle Component

Since MagicScript Components are built on Lumin Runtime and UIKit, user interactions with the Control work inherently with the user interface elements provided by MagicScript Components. Simply declaring the component tag creates a user interface element that responds to the Control. To add a response to the user's actions, connect functions to the component's events.

Let's add a Toggle component to the app and see how interactions work in MagicScript Components by making the cube rotate only when the toggle is on.

  1. Import the Toggle component from magic-script-components by updating the import statement at the top of the app.js file to be:
import { View, Model, Toggle } from 'magic-script-components';
  1. Add the Toggle tag to the render method. Verify the Toggle element appears below the cube after building and running the app. Use the Control to turn the toggle on and off.
render() {
/**
* Creates and renders the MagicScript Component elements for the UI.
*/
return (
<View name="main-view">
<Model
modelPath={require("../../assets/MagicScriptCube.glb")}
localRotation={this.state.rotation}>
</Model>
<Toggle
localPosition={[0, -0.25, 0]}>
Animate
</Toggle>
</View>
);
}
  1. Add an animate boolean to the app state to track whether the cube should be currently animating or not.
constructor (props) {
super(props);
this.state = {
animate: false,
rotation: [0, 0, 0, 0]
};
this.interval = 10; // Milliseconds to elapse between update calls
}
  1. Add a check in updateRotation so the cube only rotates if animate is true.
updateRotation = () => {
/**
* Rotates the cube, if animation is toggled on by the user.
*/
const { animate } = this.state;
if (animate) {
let pitch = Date.now() / 1000;
let roll = 0;
let yaw = 0;
let newRotation = this.eulerToQuaternion(pitch, roll, yaw);
this.setState({ rotation: newRotation });
}
}
  1. Add a method to toggle the animate boolean to provide a reaction to user input and connect it to the OnToggleChanged property in the Toggle component.
toggleAnimation = () => {
/**
* Inverts the animation state.
*/
this.setState({animate: !this.state.animate});
}
render() {
/**
* Creates and renders the MagicScript Component elements for the UI.
*/
return (
<View name="main-view">
<Model
modelPath={require("../../assets/MagicScriptCube.glb")}
localRotation={this.state.rotation}>
</Model>
<Toggle
localPosition={[0, -0.25, 0]}
onToggleChanged={this.toggleAnimation}>
Animate
</Toggle>
</View>
);
}
  1. Build and run the app. Verify the cube rotates when you turn the toggle on and stops rotating when the toggle is off.

Complete App

The complete app.js file looks like the following:

import React from 'react';
import { View, Model, Toggle } from 'magic-script-components';
export default class MyApp extends React.Component {
constructor (props) {
super(props);
this.state = {
animate: false,
rotation: [0, 0, 0, 0]
};
this.interval = 10; // Milliseconds to elapse between update calls
}
componentDidMount() {
/**
* Called after the component is mounted. Starts the update intervals.
*/
this.updateRotation();
this.handler = setInterval(this.updateRotation, this.interval);
}
componentWillUnmount() {
/**
* Stops the update intervals, after the component is unmounted.
*/
clearInterval(this.handler);
}
eulerToQuaternion(pitch, roll, yaw) {
/**
* Converts Euler angles to unit quaternions.
* @see [Wikipedia]{@link https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles}
*
* @param {Number} pitch - y
* @param {Number} roll - x
* @param {Number} yaw - z
* @returns {Number Array} unit quaternion represented as [x, y, z, w]
*/
let cy = Math.cos(yaw * 0.5);
let sy = Math.sin(yaw * 0.5);
let cr = Math.cos(roll * 0.5);
let sr = Math.sin(roll * 0.5);
let cp = Math.cos(pitch * 0.5);
let sp = Math.sin(pitch * 0.5);
let w = cy * cr * cp + sy * sr * sp;
let x = cy * sr * cp - sy * cr * sp;
let y = cy * cr * sp + sy * sr * cp;
let z = sy * cr * cp - cy * sr * sp;
return [ x, y, z, w];
}
updateRotation = () => {
/**
* Rotates the cube, if animation is toggled on by the user.
*/
const { animate } = this.state;
if (animate) {
let pitch = Date.now() / 1000;
let roll = 0;
let yaw = 0;
let newRotation = this.eulerToQuaternion(pitch, roll, yaw);
this.setState({ rotation: newRotation });
}
}
toggleAnimation = () => {
/**
* Inverts the animation state.
*/
this.setState({animate: !this.state.animate});
}
render() {
/**
* Creates and renders the MagicScript Component elements for the UI.
*/
return (
<View name="main-view">
<Model
modelPath={require("../../assets/MagicScriptCube.glb")}
localRotation={this.state.rotation}>
</Model>
<Toggle
localPosition={[0, -0.25, 0]}
onToggleChanged={this.toggleAnimation}>
Animate
</Toggle>
</View>
);
}
}