Skip to content

Instantly share code, notes, and snippets.

@andijakl
Last active January 13, 2021 23:29
Show Gist options
  • Save andijakl/2226770fd3e7da6d2ac0b4016f19a597 to your computer and use it in GitHub Desktop.
Save andijakl/2226770fd3e7da6d2ac0b4016f19a597 to your computer and use it in GitHub Desktop.
Complete AnchorPositioning.js script that creates an AR anchor in Amazon Sumerian, based on the upcoming scripting API.
// Import Sumerian scripts
import * as s from 'module://sumerian-common/api';
import * as si from 'module://sumerian-common/internal';
export default class AnchorPositioning extends s.Action {
static get PROPERTIES() {
return {
// Optional property to set the owner of the AR anchor
anchorEntity: {
// Property should be a reference to another entity in the scene
type: s.type.Entity,
// Description for the Amazon Sumerian web UI
description: 'Attach Anchor to this Entity'
}
};
}
start(ctx) {
// Setting up global communication
// -------------------------------------------------------------------
// Create / establish reference to global placeMode setting variable.
// This should be accessible by all components in the scene, so that
// others can for example pause their animations while the user is
// re-positioning the object.
// All variables created with this are stored in this object instance
// and are therefore persisted and accessible from other functions in
// this class.
this.placeMode = ctx.world.value('placeMode');
// Initially, set the place mode to false.
this.placeMode.set(false);
// Get a reference to the WorldPlaced event.
// This is also a global (world) event. This script will emit the
// event once the user finished placing the scene contents.
this.worldPlacedEvent = ctx.world.event('WorldPlaced');
// Check entity that will get the anchor component
// -------------------------------------------------------------------
// Allow providing a specific entity in Sumerian where
// the anchor should be located in the scene hierarchy.
if (!this.anchorEntity) {
// Fallback: if the developer didn't set an entity in the editor,
// use this object (self).
console.log("no anchor entity provided - use self");
this.anchorEntity = ctx.entity;
}
// World reference
// -------------------------------------------------------------------
// The world is essentially the loaded scene. It allows interacting
// with the scene contents and reading / setting several properties
// of it.
const publicWorld = this.anchorEntity.world;
// Same as before, we actually need the more powerful internal world
// class, not the "normal" one.
this.internalWorld = si.World.forPublicWorld(publicWorld);
// Amazon Sumerian AR System
// -------------------------------------------------------------------
// Retrieve the ArSystem of Amazon Sumerian. Note that this is only
// available if running on a platform that supports AR.
// So, you can't really test your code on the PC.
this.arSystem = this.internalWorld.getSystem('ArSystem');
if (!this.arSystem) {
// No AR system is found - cancel further initialization.
// This usually happens on the computer so far. For testing,
// you can remove the return statement and continue execution
// to get a bit farther.
console.log("No AR system found");
return;
}
console.log("Initializing AR anchoring");
// Attach AR anchor component to entity
// -------------------------------------------------------------------
// First, create the anchor component that is actually attached
// to the entity.
const arAnchorComponent = new si.ArAnchorComponent();
// To work with this component, we also need the internal entity
// that extends the 'normal' entity and provides additional functionality.
this.internalAnchorEntity = si.Entity.forPublicEntity(this.anchorEntity);
// The internal entity allows us to add an additional component
// to its definition - in this case, the ArAnchorComponent we
// just created.
this.internalAnchorEntity.setComponent(arAnchorComponent);
// Listeners and Callbacks
// -------------------------------------------------------------------
// First, create a variable that stores an arrow function to the
// performHitTest method of this class. This method takes care of
// starting the hit test in the native AR system.
// We need this as an arrow function to have access to "this" in
// the class scope. Creating this function in a variable allows
// to remove the event listener once the scene closes.
var performHitTestCallback = (evt) => this.performHitTest(evt);
// Add an event listener for touch events anywhere on the screen.
// Note: the MouseUpAction of Sumerian doesn't provide the coordinates
// -> use the HTML DOM event directly. This is accessible through
// the Renderer.
this.internalWorld.sumerianRunner.renderer.domElement.addEventListener('touchend', performHitTestCallback);
// Phones use touch events. To also develop / test on a PC (or on
// a mobile device with a mouse attached), you can also register for
// mouse events.
//this.internalWorld.sumerianRunner.renderer.domElement.addEventListener('mouseup', performHitTestCallback);
// Listen for the world event "StartPlaceWorld". This will be sent
// by another component in the scene when the placement mode should
// be active. When that event is received, this script sets placeMode to true,
// which actually allows this script to react to touch events.
this.startPlaceWorldEvent = ctx.world.event('StartPlaceWorld');
this.startPlaceWorldEvent.listen(ctx, data => this.placeMode.set(true));
// Cleanup. When this script stops, also remove the event listeners.
// This is especially important here, as Sumerian doesn't always reliably
// clean up all the runtime-registrations when you repeatedly start the scene.
ctx.onStop(
() => {
console.log("Removing touch event listener.");
this.internalWorld.sumerianRunner.renderer.domElement.removeEventListener('touchend', performHitTestCallback);
// For testing on the PC with a mouse
//this.internalWorld.sumerianRunner.renderer.domElement.removeEventListener('mouseup', performHitTestCallback);
}
);
}
// Touch handler.
// As the first step, performs a hit test at the currrent screen location.
performHitTest(evt) {
// First, check if this script is currently in an active place mode.
// That is triggered when another component in the scene sent the
// StartPlaceWorld event.
if (!this.placeMode.get()) {
// Currently not place mode - don't proceed!
console.log("Not in place mode - not proceeding");
return;
}
console.log("Perform hit test: " + JSON.stringify(evt));
var pixelRatio = this.internalWorld.sumerianRunner.renderer.devicePixelRatio;
// Touch
var touchX;
var touchY;
if (evt.changedTouches) {
touchX = evt.changedTouches[0].pageX * pixelRatio;
touchY = evt.changedTouches[0].pageY * pixelRatio;
} else {
// Mouse
touchX = evt.clientX * pixelRatio;
touchY = evt.clientY * pixelRatio;
}
var normalizedX = touchX / this.internalWorld.sumerianRunner.renderer.viewportWidth;
var normalizedY = touchY / this.internalWorld.sumerianRunner.renderer.viewportHeight;
if (this.arSystem) {
console.log("AR System found: calling hit test callback...")
this.arSystem.hitTest(normalizedX, normalizedY, (anchorTransform) => this.hitTestCallback(anchorTransform));
} else {
// No AR mode - exit place mode right away
console.log("No AR system - exiting place mode.")
this.placeMode.set(false);
this.worldPlacedEvent.emit();
}
}
// Hit test callback. If the hit test was successful (i.e., detected a
// point in the real world), registers an anchor with that point.
hitTestCallback(anchorTransform) {
if (anchorTransform) {
console.log("Transform from hit test: " + anchorTransform + ". Registering anchor...");
this.arSystem.registerAnchor(anchorTransform, (anchorId) => this.registerAnchorCallback(anchorId));
} else {
console.log("No transform received from hit test");
}
}
// Anchor registration callback. Sets the anchor ID of the entity's
// ArAnchorComponent. The engine's ArSystem will automatically update
// the world position and orientation of entities with a valid anchor ID.
registerAnchorCallback(anchorId) {
if (anchorId) {
this.internalAnchorEntity.getComponent('ArAnchorComponent').anchorId = anchorId;
console.log("Registered AR anchor: " + anchorId);
this.placeMode.set(false);
this.worldPlacedEvent.emit();
console.log("Quit place mode and sent WorldPlaced event");
} else {
console.log("No Anchor ID received");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment