To complete this workshop you will need:
Hardware:
Languages used:
This workshop will show you how to:
Create a new index.html in VSCode. AR.Js is built on the top AFrame, and like AFrame, its Hello World scene requires just few lines of HTML to run.
Since version 3.0.0, AR.js has two different sources: one using NFT Image tracking, and a second one using Marker Based Tracking. The latter one is used in this workshop.
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
Add to the
the components:
embedded
arjs
with the properties: debugUIEnabled: false;
sourceType: webcam;
trackingMethod: best;
vr-mode-ui="enabled: false;
to remove the VR buttonrenderer
of the scene, with the properties: logarithmicDepthBuffer: true;
physicallyCorrectLights: true;
Add a camera entity
<a-entity camera> </a-entity>
and the
<a-marker preset="hiro"> </a-marker>
AR.js has two embedded markers: Hiro, the image can be download here, and Kanji, the image can be download here.
When AR.js detects the marker, the entities within
are set to visible.
<a-marker preset="hiro">
<a-box position='0 0 0' material='color: blue;'></a-box>
</a-marker>
Here the complete index.html for the Hello WebAR scene
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
</head>
<body style='margin : 0px; overflow: hidden;'>
<a-scene
embedded
arjs='debugUIEnabled: false; sourceType: webcam; trackingMethod: best;'
vr-mode-ui="enabled: false;"
renderer="
logarithmicDepthBuffer: true;
physicallyCorrectLights: true;"
>
<a-marker preset="hiro">
<a-box position='0 0.5 0' material='color: blue;'></a-box>
</a-marker>
<a-entity camera></a-entity>
</a-scene>
</body>
</html>
The renderer
component in
is used to control the final appearance of the AR cube: e.g. without physically correct lighting...
...and with physically correct lighting
The online Marker Training tool is used to easily create a custom marker for AR.js.
ar.js
component of the
, therefore, multiple markers in the same AR experience need to use the same ratio to be correctly detectedPATT
file (the pattern recognised by the source camera), and the original image with the black border (the actual marker used to trigger the AR experience)preset="hiro"
to type="pattern"
and add the location of the .patt
fileFrom:
<a-marker preset="hiro">
<a-box position='0 0.5 0' material='color: blue;'></a-box>
</a-marker>
To:
<a-marker id="marker1" type="pattern" url="resources/pattern/pattern-1.patt">
<a-box position='0 0 0' material='color: blue;'></a-box>
</a-marker>
<a-marker id="marker2" type="pattern" url="resources/pattern/pattern-2.patt">
<a-box position='0 0 0' material='color: red;'></a-box>
</a-marker>
arjs
the patternRatio
(e.g. 85% pattern ratio, 15% black border => 0.85).<a-scene
embedded
arjs='debugUIEnabled: false; sourceType: webcam; trackingMethod: best; patternRatio: 0.85;'
vr-mode-ui="enabled: false;"
renderer="
logarithmicDepthBuffer: true;
physicallyCorrectLights: true;"
>
The markers can be used to display any kind of AFrame entity, as well as external assets.
<assets>
<a-asset-item
id="windbody"
src="resources/models/windmeterBody.glb"
response-type="arraybuffer" >
</a-asset-item>
<a-asset-item
id="windneedle"
src="resources/models/windmeterNeedle.glb"
response-type="arraybuffer" >
</a-asset-item>
</assets>
<a-marker id="marker1" type="pattern" url="resources/pattern/pattern-1.patt">
<a-entity id="windMeter" scale="5 5 5" position="0 0 0" rotation="0 0 0">
<a-entity
id="WBody"
gltf-model="#windbody"
scale="1 1 1"
position="0 0 0"
rotation="0 0 0">
</a-entity>
<a-entity id="WNeedleContainer" position="0 0.1875 0">
<a-entity
id="WNeedle"
gltf-model="#windneedle"
scale="1 1 1"
position="0 -0.1875 0"
rotation="0 0 0">
</a-entity>
</a-entity>
</a-entity>
</a-marker>
Using standard touch events it is possible to interact with the AR digital model. Hammer.js is a library that can be used to add gestures on any WebApp.
<script src="resources/js/hammer.min.js"></script>
A custom AFrame component is used to listen to pan
and pinch
events.
rotatescale.js
, set the name of the component and create the init
functionAFRAME.registerComponent("rotatescale",{
init:function() { } )
init:function
create an Hammer
element var element = document.querySelector('body');
var hammerEl = new Hammer(element);
the component is applied to the
to control the
in it.
this.marker = document.querySelector('a-marker')
var model = this.marker.querySelector('a-entity');
pan
event is built in the library as a default event. The pinch
event has to be declaredvar pinch = new Hammer.Pinch(); // Pinch is not default
hammerEl.add(pinch); // add it to the Manager instance
pan
functionhammerEl.on('pan', (ev) => {
let rotation = model.getAttribute("rotation")
switch(ev.direction) {
case 2: //DIRECTION_LEFT from https://hammerjs.github.io/api/
rotation.y = rotation.y - 4
break;
case 4: //DIRECTION_RIGHT from https://hammerjs.github.io/api/
rotation.y = rotation.y + 4
break;
case 8: //DIRECTION_UP from https://hammerjs.github.io/api/
rotation.x = rotation.x - 4
break;
case 16: //DIRECTION_DOWN from https://hammerjs.github.io/api/
rotation.x = rotation.x + 4
break;
default:
break;
}
model.setAttribute("rotation", rotation)
});
pinch
functionhammerEl.on("pinch", (ev) => {
let scale = {x:ev.scale/1, y:ev.scale/1, z:ev.scale/1}
model.setAttribute("scale", scale);
});
the complete rotatescale.js
looks like
AFRAME.registerComponent("rotatescale",{
init:function() {
var element = document.querySelector('body');
var hammerEl = new Hammer(element);
this.marker = document.querySelector('a-marker') //the component rotatescale need to be apply on the a-marker tag
var model = this.marker.querySelector('a-entity');
var pinch = new Hammer.Pinch(); // Pinch is not by default in the recognisers
hammerEl.add(pinch); // add it to the Manager instance
hammerEl.on('pan', (ev) => {
let rotation = model.getAttribute("rotation")
switch(ev.direction) {
case 2: //DIRECTION_LEFT from https://hammerjs.github.io/api/
rotation.y = rotation.y - 2
break;
case 4: //DIRECTION_RIGHT from https://hammerjs.github.io/api/
rotation.y = rotation.y + 2
break;
case 8: //DIRECTION_UP from https://hammerjs.github.io/api/
rotation.x = rotation.x - 2
break;
case 16: //DIRECTION_DOWN from https://hammerjs.github.io/api/
rotation.x = rotation.x + 2
break;
default:
break;
}
model.setAttribute("rotation", rotation)
});
hammerEl.on("pinch", (ev) => {
let scale = {x:ev.scale/1, y:ev.scale/1, z:ev.scale/1}
model.setAttribute("scale", scale);
});
}
});
rotatescale.js
to the HTML document<script src="resources/js/rotatescale.js"></script>
rotatescale
to the
<a-marker rotatescale id="marker1" type="pattern" url="resources/pattern/pattern-1.patt">
The Paho JavaScript Client is used to receive the data from the MQTT Broker.
<script src="resources/js/paho-mqtt-min.js"></script>
A custom AFrame component is used to read the data from the MQTT Broker
mqtt.js
and set the name of the component and create the init
functionAFRAME.registerComponent('mqttreader', {
init:function (){
console.log('MQTT Reader');
In this example the MQTT data is used to control, in real time, the pointer of the digital wind meter.
const sceneEl = document.querySelector('a-scene');
const windNeedle=sceneEl.querySelector('#WNeedleContainer');
let mqtt;
let reconnectedTimeout=5000;
let hostname="ADDRESS.OF.THE.MQTT.BROKER";
let port=PORT;
MQTTconnect()
, onConnect
and onFailure
function onConnect()
{
console.log("Connected...");
mqtt.subscribe("TOPIC/TO/SUBSCRIBE");
}
function MQTTconnect()
{
console.log("Connected to "+hostname+": "+ port);
//works but in ws not wss
mqtt=new Paho.MQTT.Client(hostname,port,"clientjs");
let options={
timeout:3,
onSuccess:onConnect,
onFailure:onFailure,
};
mqtt.onMessageArrived=onMessageArrived;
mqtt.connect(options);
}
function onFailure(message)
{
console.log("Connection to "+hostname+" failed");
setTimeout(MQTTconnect,reconnectedTimeout);
}
the function onMessageArrived
is used to control the rotation of the pointer on the body of the wind meter. In this case the playloadString
is the MPH value that need to be transform to a rotation value
function onMessageArrived(msg)
{
out_msg="msg is "+msg.destinationName+" -- "+msg.payloadString+"<br>";
console.log(out_msg);
let valueWRot=(((msg.payloadString*6)*Math.PI)/180);
console.log(valueWRot);
windNeedle.object3D.rotation.z = -valueWRot;
}
MQTTconnect();
}
})
mqttreader
to the
of the wind meter<a-entity mqttreader
id="WBody"
gltf-model="#windbody"
scale="1 1 1"
position="0 0 0"
rotation="0 0 0">
</a-entity>