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:
embeddedarjs 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>
