Jean-Yves Didier

setup of original component repository

Showing 56 changed files with 3904 additions and 0 deletions
import ARCS from '../build/arcs.js';
var Animator;
/**
* @class Animator
* @classdesc A component that request new frames for animation.
* This component is useful when you want to create animations in the
* context of a web browser.
*/
Animator = ARCS.Component.create(
function() {
var paused = false;
var self=this;
var tick = function () {
if (paused === false) {
requestAnimationFrame(tick);
self.emit("onAnimationFrame");
}
}
/**
* Starts requesting frames for animation. As soon as it is started,
* the signal <b>onAnimationFrame</b> is periodically triggered.
* @slot
* @emits onAnimationFrame
* @function Animator#start
*/
this.start = function () {
paused = false;
tick();
};
/**
* Stops requesting frames for animation.
* @slot
* @function Animator#stop
*/
this.stop = function () {
paused = true;
};
},
['start','stop'],
'onAnimationFrame'
);
/**
* Signals that an animation frame is ready.
* @signal
* @function Animator#onAnimationFrame
*/
export default {Animator: Animator};
import ARCS from '../build/arcs.js';
import CV from '../deps/cv/index.js';
import AR from '../deps/aruco/index.js';
var ARUCODetector;
/**
* @class ARUCODetector
* @classdesc Component that detects ARUCO markers in images
* This component encapsulate the {@link https://github.com/jcmellado/js-aruco|js-aruco} library.
*/
ARUCODetector = ARCS.Component.create(
function() {
var detector ;
/*1 Instanciate here the detector */
detector = new AR.Detector();
/**
* Detects ARUCO markers in the given image.
* If markers are detected, this slot triggers the signal <b>onMarkers</b>.
* @param image {obj} the image in which markers should be detected
* @emits onMarkers
* @function ARUCODetector#detect
* @slot
*/
this.detect = function (image) {
/*1 recover markers from image
* then send they will be sent through onMarkers event
*/
var markers = detector.detect(image);
this.emit("onMarkers",markers);
};
/**
* Signal that is emitted when markers are detected in an image.
* @function ARUCODetector#onMarkers
* @param markers {Marker[]} Array of detected markers.
* @signal
*/
/**
* @typedef {Object} Marker
* @property {number} id - marker id
* @property {Object} pose - computed pose for the marker
* @property {number[]} pose.rotation - rotation matrix (3x3)
* @property {number[]} pose.position - translation (3 components)
*/
},
'detect',
['onMarkers']
);
export default {ARUCODetector: ARUCODetector};
This diff is collapsed. Click to expand it.
/* ugly hack in order to display data in web page instead of console */
import ARCS from '../build/arcs.js';
let Console;
/**
* @class Console
* @classdesc Redirects console messages to a given HTML element in the page.
* @param id {string} id of the HTML element in which console messages will be added.
*/
Console = ARCS.Component.create(
function (id) {
if (id === undefined) {
return ;
}
if (typeof document === "undefined") return;
var output = document.getElementById(id);
if (output) {
window.console = {
timeRef: new Date().getTime(),
output : output,
display: function(color,args) {
var s = document.createElement("span");
s.style.color=color;
var elapsed = (new Date().getTime() - this.timeRef);
s.innerHTML = '<span class="marker">' + (elapsed/1000).toFixed(3) + '</span> ' + Array.prototype.join.call(args, ' ');
output.appendChild(s);
output.appendChild(document.createElement("br"));
},
log: function () {
this.display('green',arguments);
},
error: function () {
this.display('red',arguments);
},
warn: function () {
this.display('orange',arguments);
}
};
}
}
);
export default { Console: Console};
//export default {};
import ARCS from '../build/arcs.js';
let Filter;
/**
* SlotConfig
* @typedef {object} SlotConfig
* @property slot {string} slot name.
* @property func {string} a string enclosing javascript code acting as the body
* of the slot function. <b>self</b> can be used inside of it in order to emit
* signals.
*/
/**
* @class Filter
* @classdesc Creates a filter in between a signal and a slot
*
* The idea is to create a dynamic component with a slot list and signals
* so that data is adapted, on the fly, by the component.
* In a way, it is similar to filters in pipe|filter architectures.
*
* @param config {Object} configuration object to initialize filter
* @param config.signals {Array.string} the list signals potentially emitted by
* the filter.
* @param config.slots {Array.SlotConfig} the list of slots declared for this
* component.
*/
Filter = ARCS.Component.create(
/** @lends Filter.prototype */
function(config) {
let self = this;
let _config = config || {};
self.signals = self.signals || {};
//self.signal(self.signals);
if (_config.signals) {
_config.signals.forEach(e => {
self.signals[e] = [];
});
}
if (_config.slots) {
_config.slots.forEach(e => {
if (!e.slot || !e.func) return;
let slotName = e.slot;
let slotFunc = new Function('self',`return ${e.func}`)(self);
self.slots.push(slotName);
self[slotName] = slotFunc;
});
}
},
[],
[]
);
export default {Filter: Filter};
import ARCS from '../build/arcs.js';
let FullscreenManager ;
FullscreenManager = ARCS.Component.create(
function() {
},
[],
[]
);
export default { FullscreenManager: FullscreenManager };
import ARCS from '../build/arcs.js';
/**
* @class GeoLocator
* @classdesc Uses the location provider of the browser to
* determine the geolocation of the device.
* This component relies on the
* {@link http://www.w3.org/TR/geolocation-API/|Geolocation API}.
*/
let GeoLocator = ARCS.Component.create(function() {
var self = this;
var geolocator = navigator.geolocation ;
var watcher;
var timeout = 1000;
var enableHighAccuracy = false;
var handlePosition = function(position) {
self.emit("sendPosition",position.coords.longitude,position.coords.latitude);
self.emit("sendCoordinates", position);
};
var handleError = function(positionError) {
switch(positionError.code) {
case positionError.TIMEOUT:
console.error("Timeout while geolocalizing", positionError.message);
break;
case positionError.PERMISSION_DENIED:
console.error("You must authorize geolocation", positionError.message);
break;
case positionError.POSITION_UNAVAILABLE:
console.error("Position is unavailable", positionError.message);
break;
}
self.emit("geolocationDisabled");
};
/**
* Set the timeout (in milliseconds) between the call of
* {@link GeoLocator#getPosition|getPosition} or
* {@link GeoLocator#watchPosition|watchPosition} and
* the sending of signal {@link GeoLocator#sendPosition|sendPosition}.
* @param t {number} timeout duration in milliseconds
* @function GeoLocator#setTimeout
* @slot
*/
this.setTimeout = function(t) {
timeout = t;
};
this.enableHighAccuracy = function() {
enableHighAccuracy = true;
};
this.disableHighAccuracy = function() {
enableHighAccuracy = false;
};
/**
* Retrieves the geolocation of the device. Use this function for a
* one shot retrieval. If it succeeds, signals {@link GeoLocator#sendPosition|sendPosition}
* and {@link GeoLocator#sendCoordinates|sendCoordinates} are triggered.
* Otherwise, signal {@link GeoLocator#geolocationDisabled|geolocationDisabled} is triggered.
* @emits sendPosition
* @emits sendCoordinates
* @emits geolocationDisabled
* @function GeoLocator#getPosition
* @slot
*/
this.getPosition = function() {
if (geolocator) {
geolocator.getCurrentPosition(handlePosition,handleError,
{ timeout: timeout, enableHighAccuracy : enableHighAccuracy} );
} else {
this.emit("geolocationDisabled");
}
};
/**
* Retrieves the geolocation of the device. Use this function if you
* want to continuously obtain the geolocation of the device.
* If it succeeds, signals {@link GeoLocator#sendPosition|sendPosition}
* and {@link GeoLocator#sendCoordinates|sendCoordinates} are triggered.
* Otherwise, signal {@link GeoLocator#geolocationDisabled|geoLocationDisabled} is triggered.
* You can use {@link clearWatch} to stop watching the position.
* @emits sendPosition
* @emits sendCoordinates
* @emits geolocationDisabled
* @function GeoLocator#watchPosition
* @slot
*/
this.watchPosition= function() {
if (geolocator) {
if (watcher) {
geolocator.clearWatch(watcher);
}
watcher = geolocator.watchPosition(handlePosition, handleError,
{ timeout: timeout, enableHighAccuracy : enableHighAccuracy} );
} else {
this.emit("geolocationDisabled");
}
};
/**
* Ends geolocation queries.
* @see {@link GeoLocator#watchPosition|watchPosition}
* @function GeoLocator#clearWatch
* @slot
*/
this.clearWatch = function() {
if (geolocator) {
if (watcher) {
geolocator.clearWatch(watcher);
}
}
};
/**
* Sends the position of the device (longitude and latitude)
* @param lon {number} longitude of the geolocation
* @param lat {number} latitude of the geolocation
* @function GeoLocator#sendPosition
* @signal
*/
/**
* Sends the coordinates of the device using the coordinates object
* proposed in the specification of the geolocation API.
* @param coords {object} coordinates of the device.
* @function GeoLocator#sendCoordinates
* @signal
*/
/**
* Notifies that the geolocation service is disabled
* @function GeoLocator#geolocationDisabled
* @signal
*/
},
[
"getPosition","watchPosition","clearWatch","setTimeout",
"enableHighAccuracy", "disableHighAccuracy"
], // liste slots
["sendPosition","sendCoordinates","geolocationDisabled"] // liste de signaux
);
export default { GeoLocator:GeoLocator};
import ARCS from '../build/arcs.js';
var GPSInertialPose = new ARCS.Component.create(
function() {
var id = 0;
var pose = {
position : [ 0, 0, 0],
rotation : [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]
};
var cos = Math.cos;
var sin = Math.sin;
var meterToDegree = 0.00001;
this.setId = function(identifier) {
id = identifier;
};
this.setPosition = function(longitude, latitude) {
pose.position = [ longitude, latitude, 1.2*meterToDegree ];
this.emit('sendPose', [{ id: id, pose : pose}]);
};
// this function computes the rotation matrix according to
// https://w3c.github.io/deviceorientation/spec-source-orientation.html
this.setOrientation = function(orientation) {
var alpha = orientation.alpha;
var beta = orientation.beta;
var gamma = orientation.gamma;
pose.rotation = [[
cos(alpha)*cos(gamma) - sin(alpha)*sin(beta)*sin(gamma),
-cos(beta)*sin(alpha),
cos(gamma)*sin(alpha)*sin(beta) + cos(alpha)*sin(gamma)
], [
cos(gamma)*sin(alpha) + cos(alpha)*sin(beta)*sin(gamma),
cos(alpha)*cos(beta),
sin(alpha)*sin(gamma) - cos(alpha)*cos(gamma)*sin(beta)
], [
-cos(beta)*sin(gamma),
sin(beta),
cos(beta)*cos(gamma)
]
];
this.emit('sendPose', [{ id: id, pose : pose}]);
};
},
['setPosition','setOrientation', 'setId'],
['sendPose']
);
export default { GPSInertialPose: GPSInertialPose };
import ARCS from '../build/arcs.js';
let Inertial = ARCS.Component.create( function() {
var self = this;
var screenOrientation = false;
// here, we should correct orientation
var handleOrientation = function (event) {
if (screenOrientation) {
var orientation = screen.msOrientation
|| screen.mozOrientation || screen.orientation;
event.alpha -= (orientation)?orientation.angle:0;
}
self.emit("sendOrientation",event);
};
var handleMotion = function(event) {
self.emit("sendAcceleration",event.acceleration);
self.emit("sendAccelerationIncludingGravity", event.accelerationIncludingGravity);
self.emit("sendRotationRate",event.rotationRate);
};
this.start = function() {
if (window.DeviceOrientationEvent) {
console.log("Device orientation capability detected");
window.addEventListener("deviceorientation", handleOrientation, false);
} else {
console.log("[Inertial]","no device orientation API");
}
if (window.DeviceMotionEvent) {
console.log("Device motion capability detected");
window.addEventListener("devicemotion", handleMotion, true);
} else {
console.log("[Inertial]","no device motion API");
}
};
this.setScreenOrientation = function(flag) {
screenOrientation = flag;
};
},
["start","setScreenOrientation"],
["sendOrientation","sendAcceleration","sendAccelerationIncludingGravity", "sendRotationRate"]
);
export default { Inertial : Inertial };
import ARCS from '../build/arcs.js';
var Logger;
var indexedDB = window.indexedDB;
// two modes: websockets or indexeddb
// the idea is to add a timestamp as well as a timestamp for the start of
// the component.
// we need to structure indexeddb, the key is the timestamp.
// a simple websocket server may write down data in offline json file
// for a start.
// it might be great to be able to analyse and draw some graphs using the
// tools and libraries used by Mathieu (Mouttapa).
// see flot charting library (based on canvas) or epoch (based on svg).
// configuration: DB, name, table?, websocket, url?,
// the goal of this function is to transform the object into a structured
// data compliant with the logger
var toStructuredData = function( obj) {
var i, p, res;
if (obj instanceof Function) {
return undefined;
}
if (obj instanceof Array) {
res = [];
for (i= 0; i<obj.length; i++) {
res.push(toStructuredData(obj[i]));
}
return res;
}
if (obj instanceof Object) {
res = {};
if (obj instanceof Event) {
for(p in obj) {
if (! Event.prototype.hasOwnProperty(p)) {
res[p] = toStructuredData(obj[p]);
}
}
} else {
for (p in obj) {
res[p] = toStructuredData(obj[p]);
}
}
return res;
}
return obj;
};
Logger = ARCS.Component.create(
function(obj) {
var i;
var self = this;
var dbconfig;
var server;
var db;
var ws;
var dbversion;
var request;
var openDatabase;
if (typeof obj !== "object") {
console.error("[Logger] Wrong object structure at initialisation");
return ;
}
/**********************************************************************
* Creation of data needed for database
*********************************************************************/
if (obj.dbconfig != undefined) {
dbconfig = obj.dbconfig;
dbconfig.name = dbconfig.name || "ARCS" ;
dbconfig.table = dbconfig.table || "logtable";
dbconfig.clear = dbconfig.clear || false;
if (indexedDB === undefined) {
console.error("[Logger] IndexedDB extension not available");
return;
}
var openDatabase = function ( version ) {
var request = indexedDB.open(dbconfig.name, version);
request.onerror = function(event) {
console.error("[Logger] Could not open database " + dbconfig.name);
};
request.onsuccess = function(event) {
db = request.result;
// let's check if the table is available.
if (!db.objectStoreNames.contains(dbconfig.table)) {
// otherwise, we should trigger an update
dbversion = db.version;
db.close();
openDatabase(dbversion+1);
} else {
if (dbconfig.clear) {
var request2 = db.transaction([dbconfig.table],"readwrite").objectStore(dbconfig.table).clear();
request2.onsuccess = function(event) {
self.emit("onDatabaseReady");
};
request2.onerror = function(event) {
console.error("[Logger] Could not clear table " + dbconfig.table);
};
} else {
self.emit("onDatabaseReady");
}
}
};
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore(dbconfig.table, { autoIncrement : true});
objectStore.createIndex("timestamp", "timestamp", {unique : false});
objectStore.createIndex("event", "event", { unique: false});
};
};
// the line below should be called later
//openDatabase();
}
if (obj.slots != undefined) {
var arr = obj.slots;
for (i=0; i< arr.length; i++) {
if (typeof arr[i] === "string") {
this.slots.push(arr[i]);
//TokenSender.prototype.slots.push(arr[i]);
this[arr[i]] = function( s ) {
return function() {
// we keep the "event" name i.e. the slot name
// and we construct an object with the surrounding data
var data = [], i, p, tmpobj ;
for (i = 0; i < arguments.length ; i++) {
data.push(toStructuredData(arguments[i]));
}
var obj = { timestamp : new Date().valueOf() , event: s, data: data };
if (db !== undefined) {
try {
var request = db.transaction([dbconfig.table], "readwrite").objectStore(dbconfig.table).add(obj);
request.onerror = function (event) {
console.error("[Logger] could not write data in table " + dbconfig.table);
};
} catch (e) {
console.error("[Logger] problem pushing value in database ("+ e + "): " + JSON.stringify(obj));
}
}
if (ws !== undefined) {
if (ws.readyState === WebSocket.OPEN) {
try {
ws.send(JSON.stringify(obj));
} catch (e) {
console.error("[Logger] problem pushing value through socket");
}
}
}
};
} (arr[i]);
}
}
}
// few things should be located inside an init slot
// this is mainly the preparation of the websocket client
// and the preparation of the database.
// the idea is to start a little bit later events that could be sent
this.initialise = function() {
if (openDatabase !== undefined) {
openDatabase();
}
if (obj.server != undefined) {
// here we will create a connection to a websocket server
if (obj.server === "") {
obj.server = 'ws://' + window.location.hostname + ':8080';
}
ws = new WebSocket(obj.server);
ws.onopen = function() {
// may be we should put notifications
self.emit("onSocketReady");
};
ws.onerror = function() {
console.error("[Logger] Trouble opening web socket: "+ obj.server);
};
}
};
},
["initialise"],
[ "onDatabaseReady","onSocketReady"]
);
export default { Logger: Logger };
/**
* Example of component declarations inside a module.
* You may look at the definitions of the following components:
* {@link Loop}
* @file
*/
import ARCS from '../build/arcs.js';
/**
* @class Loop
* @classdesc loop component creation using a compact style.
* This component iterates for a given number of times
*/
var Loop = ARCS.Component.create(
function () {
/**
* Sets the number of times the component should iterate.
* It starts the iterations. At each iteration, a signal newIteration is
* emitted, then, at the end of the iterations, a signal sendToken is
* eventually triggered.
* @param n {numeric} number of iterations
* @function Loop#setIterations
* @slot
* @emits newIteration
* @emits sendToken
*/
this.setIterations = function (n) {
var i;
for (i = 0; i < n; i++) {
console.log("Loop : emitting ", i);
this.emit("newIteration", i);
}
this.emit("endLoop");
};
/** @function Loop#newIteration
* @signal
* @param n {number} current iteration number.
*/
/** @function Loop#sendToken
* @signal
* @param s {string} token to emit.
*/
},
"setIterations", //slotList
["endLoop", "newIteration"] // signalList
);
/**
* @class DisplayInt
* @classdesc displayInt component creation using a variation with defined slots
* in the constructor (a slot is a function). DisplayInt will display an integer
* received on its display slot.
*/
var DisplayInt = function () {
/**
* @param n {numeric} number to display
* @function DisplayInt#display
* @slot
*/
this.display = function (n) {
console.log(" DisplayInt : " + n);
};
};
ARCS.Component.create(DisplayInt);
DisplayInt.slot("display");
/**
* @class Sum
* @classdec Sum is a component summing integers passed to its slot "add"
* and the result is sent back by signal "sum".
* This component is declared in two different phases: declaration of the
* constructor and declaration of the slot "add".
*/
var Sum = function () {
this.total = 0;
};
ARCS.Component.create(Sum);
/**
* This slot adds its parameter to its internal sum and send it back by using
* the signal "sum".
* @param n {integer} add n to the internal sum of the component.
* @function Sum#add
* @slot
*/
Sum.slot("add", function (n) {
this.total = this.total + n;
this.emit("sum", this.total); //console.log(" Total : " + this.total);
});
Sum.signal("sum");
// the anonymous function must return the components in one object:
// keys are factory names, value are actual constructors modified by
// ARCS.Component.create
export default {Loop: Loop, DisplayInt: DisplayInt, Sum: Sum};
import ARCS from '../build/arcs.js';
import POS from '../deps/pose/square_pose.js';
var MarkerLocator;
MarkerLocator = ARCS.Component.create(
function () {
var square_pose = new POS.SquareFiducial();
this.setFocalLength = function (focalLength) {
square_pose.setFocalLength(focalLength);
};
this.setModelSize = function (modelSize) {
square_pose.setModelSize(modelSize);
};
this.setIntrinsics = function (intrinsics) {
square_pose.setMatrix(intrinsics);
};
this.setImageSource = function (id) {
var imageSource = document.getElementById(id);
if (id === undefined) {
return;
}
var imageSourceStyle = window.getComputedStyle(imageSource);
square_pose.setImageSize(parseInt(imageSourceStyle.width),parseInt( imageSourceStyle.height));
};
this.locateMarkers = function (markers) {
var k, pose;
for (k=0; k < markers.length; k++) {
corners = markers[k].corners;
markers[k].pose = square_pose.pose(corners);
}
this.emit("onLocatedMarkers",markers);
};
},
['locateMarkers','setFocalLength','setModelSize','setImageSource'],
['onLocatedMarkers']
);
export default { MarkerLocator: MarkerLocator };
import ARCS from '../build/arcs.js';
import * as THREE from '../deps/three.js/index.js';
var ObjectTransform ;
/**
* @class ObjectTransform
* @classdesc Apply transformations to objects
*/
ObjectTransform = ARCS.Component.create(
function() {
var objRoot;
var refMat;
var id = -1;
/**
* Sets the object of interest on which we would like to apply transforms.
* @param obj {object} a Three.js Object3D
* @function ObjectTransform#setObject
*/
this.setObject = function (obj) {
objRoot = new THREE.Object3D();
obj.parent.add(objRoot);
obj.parent.remove(obj);
objRoot.add(obj);
var box = new THREE.Box3;
box.setFromObject(obj);
var s = box.size();
var scale = MAX3(s.x, s.y, s.z);
console.log(scale);
obj.add(new THREE.AxisHelper(scale / 2));
};
var MAX3 = function (a,b,c) {
if ( a >= b ) {
if ( a >= c) {
return a;
} else {
return c;
}
} else {
if (b >= c) {
return b;
} else {
return c;
}
}
};
// right now, we make something compatible with aruco markers
// it may evolve in the future
/**
* Takes an array of markers and then applies transformations
* to the referenced object.
* @function ObjectTransform#setTransform
* @param arr {Marker[]} an array of detected markers.
*/
this.setTransform = function ( arr ) {
/*2 set here the transformation we should apply on objRoot
* Each marker has 3 major properties :
* - id is the marker id;
* - pose.rotation gives its orientation using a rotation matrix
* and is a 3x3 array
* - pose.position gives its position with respect to the camera
* and is a vector with 3 components.
*/
if (objRoot === undefined) { return ; }
var i ;
for ( i = 0; i < arr.length; i++) {
if ( arr[i].id === id ) {
// insert your code here.
//<--
var rotation = arr[i].pose.rotation;
var translation = arr[i].pose.position;
/*var matrix = new THREE.Matrix4(
rotation[0][0], rotation[0][1], rotation[0][2], translation[0],
rotation[1][0], rotation[1][1], rotation[1][2], translation[1],
rotation[2][0], rotation[2][1], rotation[2][2], translation[2],
0 , 0, 0, 1);
var r = new THREE.Euler();
r.setFromRotationMatrix(matrix);
objRoot.rotation.x = r.x;
objRoot.rotation.y = r.y;
objRoot.rotation.z = r.z;
objRoot.position.x = translation[0];
objRoot.position.y = translation[1];
objRoot.position.z = translation[2];
objRoot.scale.x = 1;
objRoot.scale.y = 1;
objRoot.scale.z = 1;*/
objRoot.rotation.x = -Math.asin(-rotation[1][2]);
objRoot.rotation.y = -Math.atan2(rotation[0][2], rotation[2][2]);
objRoot.rotation.z = Math.atan2(rotation[1][0], rotation[1][1]);
objRoot.position.x = translation[0];
objRoot.position.y = translation[1];
objRoot.position.z = -translation[2];
//-->
}
}
};
this.setId = function (i) {
id = i;
};
},
['setObject', 'setTransform', 'setId'],
[]
);
export default { ObjectTransform : ObjectTransform };
import ARCS from '../build/arcs.js';
import * as THREE from '../deps/three.js/index.js';
import { OBJLoader } from '../deps/objloader/objloader.js';
import { MTLLoader } from '../deps/mtlloader/mtlloader.js';
//import { DDSLoader } from '../deps/ddsloader/ddsloader.js';
var internalOBJLoader;
internalOBJLoader = ARCS.Component.create(
function() {
var self = this;
var innerObject;
var onLoadWrapper = function(obj) {
innerObject = obj;
console.log("[OBJLoader] object has %d components", obj.children.length);
//console.log(obj);
self.emit("onLoad", obj);
};
var onLoadJSON = function(geom, mat) {
innerObject = new THREE.Mesh(geom, new THREE.MeshFaceMaterial(mat));
self.emit("onLoad", innerObject);
};
var progress = function ( xhr ) {
console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
};
var error = function ( xhr ) {
console.log( 'An error happened' );
};
this.load = function(objURL, mtlURL) {
var loader;
// we may use a string tokenizer to determine file types
// then all would be in the same loading slot.
//console.log("loading objects", objURL, mtlURL);
var manager = new THREE.LoadingManager();
//manager.addHandler( /\.dds$/i, new DDSLoader() );
if (mtlURL === undefined) {
//console.log("using loader");
loader = new OBJLoader(manager);
loader.load(objURL, onLoadWrapper, progress, error);
} else {
//console.log("using mtl loader");
loader = new MTLLoader(manager);
loader.load(mtlURL, function(materials) {
materials.preload();
console.log(materials);
new OBJLoader(manager)
.setMaterials(materials)
.load(objURL, onLoadWrapper, progress, error);
}, progress, error);
}
};
this.loadJSON = function(jsonURL) {
var loader;
//console.log("loading objects", jsonURL);
loader = new THREE.ObjectLoader();
loader.load(jsonURL, onLoadJSON); //, progress, error);
};
var MAX3 = function (a,b,c) {
if ( a >= b ) {
if ( a >= c) {
return a;
} else {
return c;
}
} else {
if (b >= c) {
return b;
} else {
return c;
}
}
};
this.unitize = function() {
if (innerObject === undefined) { return ; }
var box = new THREE.Box3();
box.setFromObject(innerObject);
var s = box.size();
var c = box.center();
var scale = 1 / MAX3(s.x, s.y, s.z);
innerObject.scale.x = innerObject.scale.y = innerObject.scale.z = scale;
};
this.resize = function(s) {
this.unitize();
if (innerObject === undefined) { return ; }
innerObject.scale.x *= s;
innerObject.scale.y *= s;
innerObject.scale.z *= s;
};
},
["load","unitize", "resize"],
["onLoad"]
);
export default { OBJLoader: internalOBJLoader};
import ARCS from '../build/arcs.js';
let QRCodeDetector;
QRCodeDetector = ARCS.Component.create(
function() {
let self = this;
let makeFromJSQR = function(code) {
return {
id: code.data,
corners: [
code.location.topLeftCorner,
code.location.topRightCorner,
code.location.bottomRightCorner,
code.location.bottomLeftCorner
]
};
};
let makeFromBarcodeReader = function(code) {
return {
id: code.rawValue,
corners: code.cornerPoints
};
};
if (window.BarcodeDetector) {
let detector = new BarcodeDetector();
this.detect= async function(imageData) {
let codes = await detector.detect(imageData);
for(const code of codes) {
if (code.format === 'qr_code') {
console.log(makeFromBarcodeReader(code));
this.emit('onQRCode', makeFromBarcodeReader(code));
}
}
};
} else {
let busy = false;
let dataCache = null;
const worker = new Worker('../../deps/jsqr/worker.js');
worker.addEventListener("message", ((data) => {
busy = false;
dataCache = data.data;
}));
this.detect= function(imageData) {
if (!busy) {
busy = true;
worker.postMessage(imageData);
}
if (dataCache !== null) {
console.log("qrdata", makeFromJSQR(dataCache));
this.emit('onQRCode', makeFromJSQR(dataCache));
}
};
}
},
['detect'],
['onQRCode']
);
export default {QRCodeDetector: QRCodeDetector};
import ARCS from '../build/arcs.js';
let UIMapper ;
/**
* UIMapperSlot
* An item describing a slot for an UIMapper component
* @typedef {Object} UIMapperSlot
* @property param {string} a string representing a parameter. It can reach
* subcomponents of an object by using the notation X.subcomp1.subcomp2 ...
* where X value is the position index of the parameter
* @property field {string} a string representing a reference to an HTML element
* and its subcomponents in the same way as the param property. However, the X
* component relates to a CSS selector (without CSS classes)
* @property format {string} a string representing code for a callback function.
* The callback should only expect one parameter, which is the value given by
* the parameter param
* @property value a value that should be injected by the slot to the HTML field.
* It can be used instead of param.
*/
/**
* UIMapperSignal
* An item describing a signal for an UIMapper component
* @typedef {Object} UIMapperSignal
* @property selector {string} a CSS selector to target a DOM element
* @property event {string} the event of the DOM element that should trigger
* the signal.
*/
/**
* @class UIMapper
* @classdesc Component that maps slot input to an HTML element
* UIMapper is using metaprogramming, that is to say the configuration object
* will determine what are the needed slots for the component instance as well
* as the way they will tie to any HTML element.
* @param obj {object} configuration object to initialize the component.
* @param obj.slots {object} an object where keys are slot names and descriptions
* objects of type UIMapperSlot
* @param obj.signals {object} an object where keys are signal names and
* descriptions objects of type UIMapperSlot
*/
UIMapper = ARCS.Component.create(
function(obj) {
let self = this;
// returns a map function
const mapFunction = function(param, field, format, val) {
let params = param.split('.');
let func = null;
if (format !== undefined) {
func = new Function('self', `return ${format}`)(self);
}
let fields = field.split('.');
let eltField = document.querySelector(fields[0]);
for(let i=1; i < fields.length-1; i++) {
eltField = eltField[fields[i]];
}
return function() {
let value ;
if (val === undefined) {
value = arguments[params[0]];
for (let i=1; i < params.length; i++) {
value = value[params[i]];
}
} else {
value = val;
}
eltField[fields[fields.length-1]] = (func)?((typeof func === 'function')?func(value):func):value;
}
};
obj = obj || {};
if (obj.slots) {
for(let p in obj.slots) {
let slotName = p;
let slotFunction = function(pobj) {
return function() {
pobj.map( elt => {
mapFunction(elt.param, elt.field,elt.format, elt.value).apply(null, arguments);
});
};
}
if (self.slots.indexOf(slotName) < 0) {
self.slots.push(slotName);
self[slotName] = slotFunction(obj.slots[p]);
}
};
}
self.signals = self.signals || {};
let osignals = obj.signals;
if (osignals) {
for (let s in osignals) {
let signalName = s;
let signalDescr = osignals[s];
let domElt;
switch(signalDescr.selector) {
case "document":
domElt = document;
break;
case "window":
domElt = window;
break;
default:
domElt = document.querySelector(signalDescr.selector);
}
if (domElt) {
domElt.addEventListener(signalDescr.event, self => {
return e => self.emit(s, e)
});
}
self.signals[s] = [];
}
}
}, [], []
);
export default {UIMapper : UIMapper};
import ARCS from '../build/arcs.js';
/* LiveSource browser compatibility :
* - Chrome: 53+
* - Edge: 12+
* - Firefox: 42+
* - Safari: 11+
*/
var LiveSource, VideoSource;
/**
* @class LiveSource
* @classdesc Starts the acquisition of a live video stream provided by a webcam
* @param config {object} configuration object to initialize the component.
* @param [config.width=320] {number} - preferred image width in pixels.
* @param [config.height=240] {number} - preferred image height in pixels.
* @param [config.facingMode] {string} - camera facingMode as listed in [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode}
* @param [config.video] {HTMLVideoElement} - css selector for the video element (default: newly created shadow video element)
* @param [config.canvas] {HTMLCanvasElement} - css selector for the canvas element (default: newly created shadow canvas element)
*/
LiveSource = ARCS.Component.create(
/** @lends LiveSource.prototype */
function (config) {
let context, canvas, video, imageData;
let _config = config ||{};
let defaultWidth = _config.width || 320;
let defaultHeight = _config.height || 240;
let facingMode = _config.facingMode || null;
if (_config.video) video = document.querySelector(_config.video);
if (_config.canvas) canvas = document.querySelector(_config.canvas);
video = video|| document.createElement("video");
canvas = canvas || document.createElement("canvas");
canvas.width = video.width = defaultWidth;
canvas.height = video.height = defaultHeight;
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
let shadow = document.body;
if (!_config.video) {
video.style.display = "none";
shadow.appendChild(video);
}
if (!_config.canvas) {
canvas.style.display = "none";
shadow.appendChild(canvas);
}
context = canvas.getContext("2d");
var self = this;
var handleMediaStream = function(stream) {
video.srcObject = stream;
stream.addEventListener('ended', e => console.log('video ended', e));
video.load();
video.onloadedmetadata = e => video.play();
video.onerror = e => console.log('video error', e);
/*video.videoWidth=defaultWidth;
video.videoHeight=defaultHeight;*/
self.emit("onReady");
};
var errorMediaStream = function(error) {
console.error("Cannot initialize video component: " + error.code + ' ' + error.name + ' ' + error.message);
};
/**
* starts the video stream
* @slot
* @function LiveSource#start
* @emits onReady
*/
this.start = function() {
if (video.srcObject && video.srcObject.active) {
this.stop();
}
/*navigator.mediaDevices.enumerateDevices().
then((devices) => {
devices.forEach(function(device) {
console.log(device.kind + ": " + device.label +
" id = " + device.deviceId);
});
});*/
navigator.mediaDevices.getUserMedia({video: {facingMode: _config.facingMode, width: defaultWidth, height: defaultHeight}})
//navigator.mediaDevices.getUserMedia({video: {facingMode: _config.facingMode}})
.then(handleMediaStream)
.catch(errorMediaStream);
};
this.stop = function() {
video.srcObject.getTracks().forEach(track => track.stop);
};
this.continue = function() {
if (video) {
video.play();
}
};
/**
* captures a frame from the video stream.
* Emits the signal <b>onImage</b> when the frame is captured
* @slot
* @function LiveSource#grabFrame
* @emits onImage
*/
this.grabFrame = function () {
if (!video.srcObject)
return;
if (video.paused)
video.play();
if (video.paused || video.ended) {
console.log('paused', video.paused, 'ended', video.ended);
return;
}
if( video.srcObject.ended) {
console.log('stream has been ended');
return;
}
if (video.readyState === video.HAVE_ENOUGH_DATA) {
//console.log('grab :', video.readyState);
context.drawImage(video, 0, 0, canvas.width, canvas.height);
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
this.emit("onImage",imageData);
}
};
},
['start','stop','grabFrame'],
['onReady','onImage']
);
/**
* emitted when live video stream has been started
* @function LiveSource#onReady
* @signal
*/
/**
* emitted when an image from the video stream is ready
* @function LiveSource#onImage
* @param image {ImageData}
* @signal
*/
/**
* @class VideoSource
* @classdesc Uses a video element from the browser to process images.
*/
VideoSource = ARCS.Component.create(
/** @lends VideoSource.prototype */
function() {
let context, canvas, video;
let _config = config ||{};
let defaultWidth = _config.width || 320;
let defaultHeight = _config.height || 240;
if (!_config.video) video = document.querySelector(_config.video);
if (!_config.canvas) {
canvas = document.querySelector(_config.canvas);
canvas.style.width = defaultWidth + "px";
canvas.style.height = defaultHeight + "px";
}
video = video|| document.createElement("video");
canvas = canvas || document.createElement("canvas");
canvas.width = video.width = defaultWidth;
canvas.height = video.height = defaultHeight;
let shadow = document.body;
if (_config.video) {
video.style.display = "none";
shadow.appendChild(video);
}
if (_config.canvas) {
canvas.style.display = "none";
shadow.appendChild(canvas);
}
context = canvas.getContext("2d");
let self = this;
/**
* starts the video stream
* @slot
* @function VideoSource#start
* @emits onReady
*/
this.start = function() {
if (video.paused || video.ended) {
video.addEventListener('play', function() {
self.emit("onReady");
});
} else {
self.emit("onReady");
}
};
/**
* captures a frame from the video stream.
* Emits the signal <b>onImage</b> when the frame is captured
* @slot
* @function VideoSource#grabFrame
* @emits onImage
*/
this.grabFrame = function () {
if ( context === undefined || canvas === undefined || video === undefined)
createWidgets();
if (video.paused || video.ended) {
console.log('paused', video.paused, 'ended', video.ended);
return;
}
console.log('grab');
context.drawImage(video, 0, 0, canvas.width, canvas.height);
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
this.emit("onImage",imageData);
};
},
['grabFrame', 'start'],
['onReady', 'onImage']
);
/**
* emitted when video stream has been started
* @function VideoSource#onReady
* @signal
*/
/**
* emitted when an image from the video stream is ready
* @function VideoSource#onImage
* @param image {ImageData}
* @signal
*/
export default {LiveSource: LiveSource, VideoSource: VideoSource};
import ARCS from '../build/arcs.js';
import * as THREE from '../deps/three.js/index.js';
import {ARButton} from '../deps/three.js/ARButton.js';
var XRViewer;
/**
* @class XRViewer
* @classdesc Simple compositing viewer for augmented reality using WebXR
* @param [config] {object} configuration object for the viewer
* @param [config.rootElement="document.body"] {string} element on which to attach renderer
* @param [config.sessionConfig] {object} an object to configure an immersive
* session as formulated in WebXR specification.
*/
XRViewer = ARCS.Component.create(
/** @lends XRViewer.prototype */
function (config) {
let renderer, scene, camera;
let sceneId;
let container;
let self = this;
let defaultStyle;
let _config = config || {};
container = (_config.rootElement !== undefined)?
document.querySelector(_config.rootElement):document.body;
container = container || document.body;
let rootOverlay = container;
if (_config.sessionConfig && _config.sessionConfig.domOverlay
&& _config.sessionConfig.domOverlay && _config.sessionConfig.domOverlay.root) {
rootOverlay = document.querySelector(_config.sessionConfig.domOverlay.root) || rootOverlay;
_config.sessionConfig.domOverlay.root = rootOverlay; // || document.body;
}
let defaultDisplay = window.getComputedStyle(rootOverlay).getPropertyValue("display");
let firstFrame = true;
// scenegraph initializations
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
//renderer.setClearColor(0x000000, 1);
renderer.setSize(container.width, container.height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.xr.enabled = true;
renderer.xr.cameraAutoUpdate = false;
container.appendChild(renderer.domElement);
rootOverlay.appendChild(ARButton.createButton(renderer,_config.sessionConfig));
var theta = 45;
camera = new THREE.PerspectiveCamera(theta, container.width / container.height, 0.01, 10);
var _light = new THREE.DirectionalLight(0xffffff);
_light.position.set(0,5,5);
scene.add(_light);
/*renderer.domElement.addEventListener('webglcontextlost', (event) => {
console.log(event);
});*/
/**
* Adds new objects to the current 3D scene
* @param scene {object} 3D object as defined by Three.js to add to the scene.
* @slot
* @function XRViewer#addScene
*/
this.addScene = function (subScene) {
scene.add(subScene);
};
/**
* Removes an object from the current 3D scene
* @param scene {object} 3D object as defined by Three.js to remove from the scene.
* @slot
* @function XRViewer#removeScene
*/
this.removeScene = function (subScene) {
scene.remove(subScene);
};
/**
* Sets the pose
* @param pos {object} a position with x, y and z coordinates
* @param rot {object} unused so far
* @slot
* @function XRViewer#setPose
*/
this.setPose = function(pos, rot) {
let session = renderer.xr.getSession();
let p = camera.position;
let q = new THREE.Quaternion();
q.setFromRotationMatrix(camera.matrixWorld);
session.requestReferenceSpace(renderer.xr.getReferenceSpace()).then(
(space) => {
let xrSpace = space;
xrSpace = xrSpace.getOffsetReferenceSpace(
new XRRigidTransform(
{ x: p.x, y: p.y, z: p.z, w: 1 },
{ x: q.x, y: q.y, z: q.z, w: q.w}
)
);
// we will also have to modify the orientation
// because offset position is not enough
xrSpace = xrSpace.getOffsetReferenceSpace(
new XRRigidTransform(
{ x: pos.x, y: pos.y, z:pos.z, w:1},
{ x: 0, y:0, z:0, w:1}
)
);
}
);
};
/**
* Starts the animation loop.
* Each time an animation frame is obtained, preRender and postRender
* signals are triggered
* @slot
* @function XRViewer#start
* @triggers preRender
* @triggers postRender
*/
this.start = function() {
renderer.setAnimationLoop(render);
};
let render = function (time, frame) {
if (frame) {
renderer.xr.updateCamera(camera);
if (firstFrame) {
renderer.xr.getSession().addEventListener('end',() => {
firstFrame = true;
rootOverlay.style.display = defaultDisplay;
self.emit('onEnded');
});
firstFrame = false;
self.emit('onStarted');
}
//console.log(JSON.stringify(renderer.xr.getCamera().position));
let pose = frame.getViewerPose(renderer.xr.getReferenceSpace());
//console.log(pose);
/*for (let xrView of pose.views) {
if (xrView.camera) {
console.log("I can see a camera");
} */
/*if (pose) {
console.log(pose.views[0].projectionMatrix);
}*/
self.emit("onRender",time,renderer.xr.getCamera(),frame);
}
renderer.render(scene, camera);
}
},
/** @lends XRViewer.slots */
['addScene','removeScene','start','setPose'],
['onRender','onEnded', 'onStarted']
);
/**
* emitted when a frame is obtained after rendering of the scene.
* @function XRViewer#onRender
* @signal
* @param time {number} time elapsed since the beginning of the loop in seconds
* @param camera {THREE.Camera} the camera object
* @param frame {XRFrame} a frame as setup by the WebXR loop
*/
export default {XRViewer: XRViewer};
importScripts('./jsQR.js');
self.addEventListener("message", e => {
const obj = e.data;
const qrData = jsQR(obj.data, obj.width, obj.height);
self.postMessage(qrData);
});
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
var POS = POS || {};
POS.SquareFiducial = function(modelSize, focalLength, width, height){
this.modelSize = modelSize || 70;
this.focalLength = focalLength || 600;
this.u0 = width/2.0;
this.v0 = height/2.0;
this.au = this.focalLength;
this.av = this.focalLength;
this.setModelSize = function (modelSize) {
this.modelSize = modelSize;
};
this.setFocalLength = function (focal) {
this.focalLength = focal;
this.au = focal;
this.av = focal;
};
this.setMatrix = function (mat) {
this.au = mat[0];
this.av = mat[4];
this.u0 = mat[2];
this.v0 = mat[5];
};
this.setImageSize = function (width, height) {
this.u0 = width/2;
this.v0 = height/2;
};
};
POS.SquareFiducial.prototype.pose = function(imagePoints){
var u0 = this.u0;
var v0 = this.v0;
var au = this.au;
var av = this.av;
var ua = imagePoints[0].x ;
var va = imagePoints[0].y ;
var ub = imagePoints[1].x ;
var vb = imagePoints[1].y ;
var uc = imagePoints[2].x ;
var vc = imagePoints[2].y ;
var ud = imagePoints[3].x ;
var vd = imagePoints[3].y ;
var detm = uc*vd - vc*ud + ud*vb - ub*vd + ub*vc - uc*vb;
var ZA = 1;
var ZB = (ua * (vc - vd) + va * (ud - uc) + (uc*vd - ud*vc))/detm ;
var ZC = (ua * (vb - vd) + va * (ud - ub) - (ud*vb - ub*vd))/detm ;
var ZD = (ua * (vb - vc) + va * (uc - ub) + (ub*vc - uc*vb))/detm ;
var r1 = ZC/ZA ;
var r2 = ZD/ZB ;
var tmp11 = (r1*(ua - u0)-(uc -u0))/au ;
var tmp12 = (r1*(va - v0)-(vc -v0))/av ;
var tmp21 = (r2*(ud - u0)-(ub -u0))/au ;
var tmp22 = (r2*(vd - v0)-(vb -v0))/av ;
var ZpA = -this.modelSize / Math.sqrt((r1-1)*(r1-1) + tmp11*tmp11 + tmp12*tmp12) ;
var ZpB = -this.modelSize / Math.sqrt((r2-1)*(r2-1) + tmp21*tmp21 + tmp22*tmp22) ;
var ZpC = r1 * ZpA;
var ZpD = r2 * ZpB;
var XA = ZpA/au *(ua-u0);
var XB = ZpB/au *(ub-u0);
var XC = ZpC/au *(uc-u0);
var XD = ZpD/au *(ud-u0);
var YA = ZpA/av *(va-v0);
var YB = ZpB/av *(vb-v0);
var YC = ZpC/av *(vc-v0);
var YD = ZpD/av *(vd-v0);
var position = [];
position[0] = -(XA + XB + XC + XD)/4.0;
position[1] = (YA + YB + YC + YD)/4.0;
position[2] = (ZpA + ZpB + ZpC + ZpD)/4.0;
var AC = [ - XC + XA, YC - YA, ZpC - ZpA ];
var DB = [ - XB + XD, YB - YD, ZpB - ZpD ];
var AB = [ XB - XA, YB - YA, ZpB - ZpA ];
var DA = [ XA - XD, YA - YD, ZpA - ZpD ];
var sV = this.sumVectors(AC,DB);
var dV = this.diffVectors(DB,AC);
var r1Star = this.scalVector(sV, 1.0/this.normVector(sV));
var r2Star = this.scalVector(dV, 1.0/this.normVector(dV));
var r3Star = this.crossProduct(r1Star,r2Star);
var rotation = [ [ r1Star[0], r2Star[0], r3Star[0] ],
[ r1Star[1], r2Star[1], r3Star[1] ],
[ r1Star[2], r2Star[2], r3Star[2] ]];
//console.log(rotation);
return new POS.SimplePose(position, rotation);
};
POS.SquareFiducial.prototype.sumVectors = function(a,b) {
return [ a[0]+b[0], a[1]+b[1], a[2]+b[2] ];
};
POS.SquareFiducial.prototype.diffVectors = function (a,b) {
return [ a[0]-b[0], a[1]-b[1], a[2]-b[2] ];
};
POS.SquareFiducial.prototype.scalVector = function(a,s) {
return [ a[0]*s, a[1]*s, a[2]*s ];
};
POS.SquareFiducial.prototype.normVector = function (a) {
return Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);
};
POS.SquareFiducial.prototype.crossProduct = function(a,b) {
return [ a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0]
];
};
POS.SimplePose = function(pos, rot) {
this.position = pos;
this.rotation = rot;
};
export default POS;
/*
Copyright (c) 2012 Juan Mellado
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
References:
- "Numerical Recipes in C - Second Edition"
http://www.nr.com/
*/
var SVD = SVD || {};
SVD.svdcmp = function(a, m, n, w, v){
var flag, i, its, j, jj, k, l, nm,
anorm = 0.0, c, f, g = 0.0, h, s, scale = 0.0, x, y, z, rv1 = [];
//Householder reduction to bidiagonal form
for (i = 0; i < n; ++ i){
l = i + 1;
rv1[i] = scale * g;
g = s = scale = 0.0;
if (i < m){
for (k = i; k < m; ++ k){
scale += Math.abs( a[k][i] );
}
if (0.0 !== scale){
for (k = i; k < m; ++ k){
a[k][i] /= scale;
s += a[k][i] * a[k][i];
}
f = a[i][i];
g = -SVD.sign( Math.sqrt(s), f );
h = f * g - s;
a[i][i] = f - g;
for (j = l; j < n; ++ j){
for (s = 0.0, k = i; k < m; ++ k){
s += a[k][i] * a[k][j];
}
f = s / h;
for (k = i; k < m; ++ k){
a[k][j] += f * a[k][i];
}
}
for (k = i; k < m; ++ k){
a[k][i] *= scale;
}
}
}
w[i] = scale * g;
g = s = scale = 0.0;
if ( (i < m) && (i !== n - 1) ){
for (k = l; k < n; ++ k){
scale += Math.abs( a[i][k] );
}
if (0.0 !== scale){
for (k = l; k < n; ++ k){
a[i][k] /= scale;
s += a[i][k] * a[i][k];
}
f = a[i][l];
g = -SVD.sign( Math.sqrt(s), f );
h = f * g - s;
a[i][l] = f - g;
for (k = l; k < n; ++ k){
rv1[k] = a[i][k] / h;
}
for (j = l; j < m; ++ j){
for (s = 0.0, k = l; k < n; ++ k){
s += a[j][k] * a[i][k];
}
for (k = l; k < n; ++ k){
a[j][k] += s * rv1[k];
}
}
for (k = l; k < n; ++ k){
a[i][k] *= scale;
}
}
}
anorm = Math.max(anorm, ( Math.abs( w[i] ) + Math.abs( rv1[i] ) ) );
}
//Acumulation of right-hand transformation
for (i = n - 1; i >= 0; -- i){
if (i < n - 1){
if (0.0 !== g){
for (j = l; j < n; ++ j){
v[j][i] = ( a[i][j] / a[i][l] ) / g;
}
for (j = l; j < n; ++ j){
for (s = 0.0, k = l; k < n; ++ k){
s += a[i][k] * v[k][j];
}
for (k = l; k < n; ++ k){
v[k][j] += s * v[k][i];
}
}
}
for (j = l; j < n; ++ j){
v[i][j] = v[j][i] = 0.0;
}
}
v[i][i] = 1.0;
g = rv1[i];
l = i;
}
//Acumulation of left-hand transformation
for (i = Math.min(n, m) - 1; i >= 0; -- i){
l = i + 1;
g = w[i];
for (j = l; j < n; ++ j){
a[i][j] = 0.0;
}
if (0.0 !== g){
g = 1.0 / g;
for (j = l; j < n; ++ j){
for (s = 0.0, k = l; k < m; ++ k){
s += a[k][i] * a[k][j];
}
f = (s / a[i][i]) * g;
for (k = i; k < m; ++ k){
a[k][j] += f * a[k][i];
}
}
for (j = i; j < m; ++ j){
a[j][i] *= g;
}
}else{
for (j = i; j < m; ++ j){
a[j][i] = 0.0;
}
}
++ a[i][i];
}
//Diagonalization of the bidiagonal form
for (k = n - 1; k >= 0; -- k){
for (its = 1; its <= 30; ++ its){
flag = true;
for (l = k; l >= 0; -- l){
nm = l - 1;
if ( Math.abs( rv1[l] ) + anorm === anorm ){
flag = false;
break;
}
if ( Math.abs( w[nm] ) + anorm === anorm ){
break;
}
}
if (flag){
c = 0.0;
s = 1.0;
for (i = l; i <= k; ++ i){
f = s * rv1[i];
if ( Math.abs(f) + anorm === anorm ){
break;
}
g = w[i];
h = SVD.pythag(f, g);
w[i] = h;
h = 1.0 / h;
c = g * h;
s = -f * h;
for (j = 1; j <= m; ++ j){
y = a[j][nm];
z = a[j][i];
a[j][nm] = y * c + z * s;
a[j][i] = z * c - y * s;
}
}
}
//Convergence
z = w[k];
if (l === k){
if (z < 0.0){
w[k] = -z;
for (j = 0; j < n; ++ j){
v[j][k] = -v[j][k];
}
}
break;
}
if (30 === its){
return false;
}
//Shift from bottom 2-by-2 minor
x = w[l];
nm = k - 1;
y = w[nm];
g = rv1[nm];
h = rv1[k];
f = ( (y - z) * (y + z) + (g - h) * (g + h) ) / (2.0 * h * y);
g = SVD.pythag( f, 1.0 );
f = ( (x - z) * (x + z) + h * ( (y / (f + SVD.sign(g, f) ) ) - h) ) / x;
//Next QR transformation
c = s = 1.0;
for (j = l; j <= nm; ++ j){
i = j + 1;
g = rv1[i];
y = w[i];
h = s * g;
g = c * g;
z = SVD.pythag(f, h);
rv1[j] = z;
c = f / z;
s = h / z;
f = x * c + g * s;
g = g * c - x * s;
h = y * s;
y *= c;
for (jj = 0; jj < n; ++ jj){
x = v[jj][j];
z = v[jj][i];
v[jj][j] = x * c + z * s;
v[jj][i] = z * c - x * s;
}
z = SVD.pythag(f, h);
w[j] = z;
if (0.0 !== z){
z = 1.0 / z;
c = f * z;
s = h * z;
}
f = c * g + s * y;
x = c * y - s * g;
for (jj = 0; jj < m; ++ jj){
y = a[jj][j];
z = a[jj][i];
a[jj][j] = y * c + z * s;
a[jj][i] = z * c - y * s;
}
}
rv1[l] = 0.0;
rv1[k] = f;
w[k] = x;
}
}
return true;
};
SVD.pythag = function(a, b){
var at = Math.abs(a), bt = Math.abs(b), ct;
if (at > bt){
ct = bt / at;
return at * Math.sqrt(1.0 + ct * ct);
}
if (0.0 === bt){
return 0.0;
}
ct = at / bt;
return bt * Math.sqrt(1.0 + ct * ct);
};
SVD.sign = function(a, b){
return b >= 0.0? Math.abs(a): -Math.abs(a);
};
export default SVD;
This diff is collapsed. Click to expand it.
import * as THREE from './index.js';
let FrustumCamera = function ( left, right, bottom, top, near, far ) {
THREE.Camera.call( this );
this.type = 'FrustumCamera';
this.left = left !== undefined ? left : -1;
this.right = right !== undefined ? right : 1;
this.bottom = bottom !== undefined ? bottom : -1;
this.top = top !== undefined ? top : 1;
this.near = near !== undefined ? near : 0.1;
this.far = far !== undefined ? far : 2000;
this.updateProjectionMatrix();
};
FrustumCamera.prototype = Object.create( THREE.Camera.prototype );
FrustumCamera.prototype.constructor = FrustumCamera;
FrustumCamera.prototype.updateProjectionMatrix = function () {
this.projectionMatrix.makeFrustum(
this.left, this.right, this.bottom, this.top, this.near, this.far
);
};
FrustumCamera.prototype.clone = function () {
var camera = new FrustumCamera();
THREE.Camera.prototype.clone.call( this, camera );
camera.left = this.left;
camera.right = this.right;
camera.top = this.top;
camera.bottom = this.bottom;
camera.near = this.near;
camera.far = this.far;
camera.projectionMatrix.copy( this.projectionMatrix );
return camera;
};
export default FrustumCamera;
This is the javascript version of one of our previous framework named ARCS for
Augmented Reality Component System. Many of the concepts are those
developped for the original engine and can be read in
[this page](http://arcs.ibisc.univ-evry.fr/).
The javascript ARCS engine is intended to run on both server side in a
[Node.js](http://nodejs.org/) environment or inside a web browser.
This diff is collapsed. Click to expand it.
(function() {
var counter = 0;
var numbered;
var source = document.getElementsByClassName('prettyprint source');
if (source && source[0]) {
source = source[0].getElementsByTagName('code')[0];
numbered = source.innerHTML.split('\n');
numbered = numbered.map(function(item) {
counter++;
return '<span id="line' + counter + '" class="line"></span>' + item;
});
source.innerHTML = numbered.join('\n');
}
})();
This diff is collapsed. Click to expand it.
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
This diff is collapsed. Click to expand it.
@font-face {
font-family: 'Open Sans';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Regular-webfont.eot');
src:
local('Open Sans'),
local('OpenSans'),
url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
}
@font-face {
font-family: 'Open Sans Light';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Light-webfont.eot');
src:
local('Open Sans Light'),
local('OpenSans Light'),
url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
}
html
{
overflow: auto;
background-color: #fff;
font-size: 14px;
}
body
{
font-family: 'Open Sans', sans-serif;
line-height: 1.5;
color: #4d4e53;
background-color: white;
}
a, a:visited, a:active {
color: #0095dd;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header
{
display: block;
padding: 0px 4px;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0;
}
#main {
float: left;
width: 70%;
}
article dl {
margin-bottom: 40px;
}
section
{
display: block;
background-color: #fff;
padding: 12px 24px;
border-bottom: 1px solid #ccc;
margin-right: 30px;
}
.variation {
display: none;
}
.signature-attributes {
font-size: 60%;
color: #aaa;
font-style: italic;
font-weight: lighter;
}
nav
{
display: block;
float: right;
margin-top: 28px;
width: 30%;
box-sizing: border-box;
border-left: 1px solid #ccc;
padding-left: 16px;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a, nav ul a:visited, nav ul a:active {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
line-height: 18px;
color: #4D4E53;
}
nav h3 {
margin-top: 12px;
}
nav li {
margin-top: 6px;
}
footer {
display: block;
padding: 6px;
margin-top: 12px;
font-style: italic;
font-size: 90%;
}
h1, h2, h3, h4 {
font-weight: 200;
margin: 0;
}
h1
{
font-family: 'Open Sans Light', sans-serif;
font-size: 48px;
letter-spacing: -2px;
margin: 12px 24px 20px;
}
h2, h3
{
font-size: 30px;
font-weight: 700;
letter-spacing: -1px;
margin-bottom: 12px;
}
h4
{
font-size: 18px;
letter-spacing: -0.33px;
margin-bottom: 12px;
color: #4d4e53;
}
h5, .container-overview .subsection-title
{
font-size: 120%;
font-weight: bold;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6
{
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
.ancestors { color: #999; }
.ancestors a
{
color: #999 !important;
text-decoration: none;
}
.clear
{
clear: both;
}
.important
{
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px;
}
.type-signature {
color: #aaa;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
color: #0095dd;
}
.details { margin-top: 14px; border-left: 2px solid #DDD; }
.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
.details dd { margin-left: 70px; }
.details ul { margin: 0; }
.details ul { list-style-type: none; }
.details li { margin-left: 30px; padding-top: 6px; }
.details pre.prettyprint { margin: 0 }
.details .object-value { padding-top: 0; }
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption
{
font-style: italic;
font-size: 107%;
margin: 0;
}
.prettyprint
{
border: 1px solid #ddd;
width: 80%;
overflow: auto;
}
.prettyprint.source {
width: inherit;
}
.prettyprint code
{
font-size: 100%;
line-height: 18px;
display: block;
padding: 4px 12px;
margin: 0;
background-color: #fff;
color: #4D4E53;
}
.prettyprint code span.line
{
display: inline-block;
}
.prettyprint.linenums
{
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol
{
padding-left: 0;
}
.prettyprint.linenums li
{
border-left: 3px #ddd solid;
}
.prettyprint.linenums li.selected,
.prettyprint.linenums li.selected *
{
background-color: lightyellow;
}
.prettyprint.linenums li *
{
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.params, .props
{
border-spacing: 0;
border: 0;
border-collapse: collapse;
}
.params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td, .params th, .props td, .props th
{
border: 1px solid #ddd;
margin: 0px;
text-align: left;
vertical-align: top;
padding: 4px 6px;
display: table-cell;
}
.params thead tr, .props thead tr
{
background-color: #ddd;
font-weight: bold;
}
.params .params thead tr, .props .props thead tr
{
background-color: #fff;
font-weight: bold;
}
.params th, .props th { border-right: 1px solid #aaa; }
.params thead .last, .props thead .last { border-right: 1px solid #ddd; }
.params td.description > p:first-child,
.props td.description > p:first-child
{
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child,
.props td.description > p:last-child
{
margin-bottom: 0;
padding-bottom: 0;
}
.disabled {
color: #454545;
}
/* JSDoc prettify.js theme */
/* plain text */
.pln {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* string content */
.str {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a keyword */
.kwd {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a comment */
.com {
font-weight: normal;
font-style: italic;
}
/* a type name */
.typ {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a literal value */
.lit {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* punctuation */
.pun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp open bracket */
.opn {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp close bracket */
.clo {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a markup tag name */
.tag {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute name */
.atn {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute value */
.atv {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a declaration */
.dec {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a variable name */
.var {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a function name */
.fun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}
/* Tomorrow Theme */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* Pretty printing styles. Used with prettify.js. */
/* SPAN elements with the classes below are added by prettyprint. */
/* plain text */
.pln {
color: #4d4d4c; }
@media screen {
/* string content */
.str {
color: #718c00; }
/* a keyword */
.kwd {
color: #8959a8; }
/* a comment */
.com {
color: #8e908c; }
/* a type name */
.typ {
color: #4271ae; }
/* a literal value */
.lit {
color: #f5871f; }
/* punctuation */
.pun {
color: #4d4d4c; }
/* lisp open bracket */
.opn {
color: #4d4d4c; }
/* lisp close bracket */
.clo {
color: #4d4d4c; }
/* a markup tag name */
.tag {
color: #c82829; }
/* a markup attribute name */
.atn {
color: #f5871f; }
/* a markup attribute value */
.atv {
color: #3e999f; }
/* a declaration */
.dec {
color: #f5871f; }
/* a variable name */
.var {
color: #c82829; }
/* a function name */
.fun {
color: #4271ae; } }
/* Use higher contrast and text-weight for printable form. */
@media print, projection {
.str {
color: #060; }
.kwd {
color: #006;
font-weight: bold; }
.com {
color: #600;
font-style: italic; }
.typ {
color: #404;
font-weight: bold; }
.lit {
color: #044; }
.pun, .opn, .clo {
color: #440; }
.tag {
color: #006;
font-weight: bold; }
.atn {
color: #404; }
.atv {
color: #060; } }
/* Style */
/*
pre.prettyprint {
background: white;
font-family: Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #ccc;
padding: 10px; }
*/
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0; }
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */ }
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */ }
<?js
var data = obj;
var self = this;
?>
<?js if (data.augments && data.augments.length) { ?>
<ul><?js data.augments.forEach(function(a) { ?>
<li><?js= self.linkto(a, a) ?></li>
<?js }) ?></ul>
<?js } ?>
<?js
var self = this;
var isGlobalPage;
docs.forEach(function(doc, i) {
?>
<?js
// we only need to check this once
if (typeof isGlobalPage === 'undefined') {
isGlobalPage = (doc.kind === 'globalobj');
}
?>
<?js if (doc.kind === 'mainpage' || (doc.kind === 'package')) { ?>
<?js= self.partial('mainpage.tmpl', doc) ?>
<?js } else if (doc.kind === 'source') { ?>
<?js= self.partial('source.tmpl', doc) ?>
<?js } else { ?>
<section>
<header>
<?js if (!doc.longname || doc.kind !== 'module') { ?>
<h2><?js if (doc.ancestors && doc.ancestors.length) { ?>
<span class="ancestors"><?js= doc.ancestors.join('') ?></span>
<?js } ?>
<?js= doc.name ?>
<?js if (doc.variation) { ?>
<sup class="variation"><?js= doc.variation ?></sup>
<?js } ?></h2>
<?js if (doc.classdesc) { ?>
<div class="class-description"><?js= doc.classdesc ?></div>
<?js } ?>
<?js } else if (doc.kind === 'module' && doc.modules) { ?>
<?js doc.modules.forEach(function(module) { ?>
<?js if (module.classdesc) { ?>
<div class="class-description"><?js= module.classdesc ?></div>
<?js } ?>
<?js }) ?>
<?js } ?>
</header>
<article>
<div class="container-overview">
<?js if (doc.kind === 'module' && doc.modules) { ?>
<?js if (doc.description) { ?>
<div class="description"><?js= doc.description ?></div>
<?js } ?>
<?js doc.modules.forEach(function(module) { ?>
<?js= self.partial('method.tmpl', module) ?>
<?js }) ?>
<?js } else if (doc.kind === 'class') { ?>
<?js= self.partial('method.tmpl', doc) ?>
<?js } else { ?>
<?js if (doc.description) { ?>
<div class="description"><?js= doc.description ?></div>
<?js } ?>
<?js= self.partial('details.tmpl', doc) ?>
<?js if (doc.examples && doc.examples.length) { ?>
<h3>Example<?js= doc.examples.length > 1? 's':'' ?></h3>
<?js= self.partial('examples.tmpl', doc.examples) ?>
<?js } ?>
<?js } ?>
</div>
<?js if (doc.augments && doc.augments.length) { ?>
<h3 class="subsection-title">Extends</h3>
<?js= self.partial('augments.tmpl', doc) ?>
<?js } ?>
<?js if (doc.requires && doc.requires.length) { ?>
<h3 class="subsection-title">Requires</h3>
<ul><?js doc.requires.forEach(function(r) { ?>
<li><?js= self.linkto(r, r) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js
var classes = self.find({kind: 'class', memberof: doc.longname});
if (!isGlobalPage && classes && classes.length) {
?>
<h3 class="subsection-title">Classes</h3>
<dl><?js classes.forEach(function(c) { ?>
<dt><?js= self.linkto(c.longname, c.name) ?></dt>
<dd><?js if (c.summary) { ?><?js= c.summary ?><?js } ?></dd>
<?js }); ?></dl>
<?js } ?>
<?js
var mixins = self.find({kind: 'mixin', memberof: doc.longname});
if (!isGlobalPage && mixins && mixins.length) {
?>
<h3 class="subsection-title">Mixins</h3>
<dl><?js mixins.forEach(function(m) { ?>
<dt><?js= self.linkto(m.longname, m.name) ?></dt>
<dd><?js if (m.summary) { ?><?js= m.summary ?><?js } ?></dd>
<?js }); ?></dl>
<?js } ?>
<?js
var namespaces = self.find({kind: 'namespace', memberof: doc.longname});
if (!isGlobalPage && namespaces && namespaces.length) {
?>
<h3 class="subsection-title">Namespaces</h3>
<dl><?js namespaces.forEach(function(n) { ?>
<dt><?js= self.linkto(n.longname, n.name) ?></dt>
<dd><?js if (n.summary) { ?><?js= n.summary ?><?js } ?></dd>
<?js }); ?></dl>
<?js } ?>
<?js
var members = self.find({kind: 'member', memberof: isGlobalPage ? {isUndefined: true} : doc.longname});
// symbols that are assigned to module.exports are not globals, even though they're not a memberof anything
if (isGlobalPage && members && members.length && members.forEach) {
members = members.filter(function(m) {
return m.longname && m.longname.indexOf('module:') !== 0;
});
}
if (members && members.length && members.forEach) {
?>
<h3 class="subsection-title">Members</h3>
<?js members.forEach(function(p) { ?>
<?js= self.partial('members.tmpl', p) ?>
<?js }); ?>
<?js } ?>
<?js
var methods = self.find({kind: 'function', slot: false, signal: false, memberof: isGlobalPage ? {isUndefined: true} : doc.longname});
if (methods && methods.length && methods.forEach) {
?>
<h3 class="subsection-title">Methods</h3>
<dl><?js methods.forEach(function(m) {?>
<?js= self.partial('method.tmpl', m) ?>
<?js }); ?></dl>
<?js } ?>
<?js
var slots = self.find({slot: true, memberof: title === 'Global' ? {isUndefined: true} : doc.longname});
if (slots && slots.length && slots.forEach) {
?>
<h3 class="subsection-title">Slots</h3>
<dl><?js slots.forEach(function(m) { ?>
<?js= self.partial('method.tmpl', m) ?>
<?js }); ?></dl>
<?js } ?>
<?js
var signals = self.find({signal: true, memberof: title === 'Global' ? {isUndefined: true} : doc.longname});
if (signals && signals.length && signals.forEach) {
?>
<h3 class="subsection-title">Signals</h3>
<dl><?js signals.forEach(function(m) { ?>
<?js= self.partial('method.tmpl', m) ?>
<?js }); ?></dl>
<?js } ?>
<?js
var typedefs = self.find({kind: 'typedef', memberof: isGlobalPage ? {isUndefined: true} : doc.longname});
if (typedefs && typedefs.length && typedefs.forEach) {
?>
<h3 class="subsection-title">Type Definitions</h3>
<?js typedefs.forEach(function(e) {
if (e.signature) {
?>
<?js= self.partial('method.tmpl', e) ?>
<?js
}
else {
?>
<?js= self.partial('members.tmpl', e) ?>
<?js
}
}); ?>
<?js } ?>
<?js
var events = self.find({kind: 'event', memberof: isGlobalPage ? {isUndefined: true} : doc.longname});
if (events && events.length && events.forEach) {
?>
<h3 class="subsection-title">Events</h3>
<?js events.forEach(function(e) { ?>
<?js= self.partial('method.tmpl', e) ?>
<?js }); ?>
<?js } ?>
</article>
</section>
<?js } ?>
<?js }); ?>
<?js
var data = obj;
var self = this;
var defaultObjectClass = '';
// Check if the default value is an object or array; if so, apply code highlighting
if (data.defaultvalue && (data.defaultvaluetype === 'object' || data.defaultvaluetype === 'array')) {
data.defaultvalue = "<pre class=\"prettyprint\"><code>" + data.defaultvalue + "</code></pre>";
defaultObjectClass = ' class="object-value"';
}
?>
<?js
var properties = data.properties;
if (properties && properties.length && properties.forEach) {
?>
<h5 class="subsection-title">Properties:</h5>
<?js= this.partial('properties.tmpl', data) ?>
<?js } ?>
<dl class="details">
<?js if (data.version) {?>
<dt class="tag-version">Version:</dt>
<dd class="tag-version"><ul class="dummy"><li><?js= version ?></li></ul></dd>
<?js } ?>
<?js if (data.since) {?>
<dt class="tag-since">Since:</dt>
<dd class="tag-since"><ul class="dummy"><li><?js= since ?></li></ul></dd>
<?js } ?>
<?js if (data.inherited && data.inherits && !data.overrides) { ?>
<dt class="inherited-from">Inherited From:</dt>
<dd class="inherited-from"><ul class="dummy"><li>
<?js= this.linkto(data.inherits, this.htmlsafe(data.inherits)) ?>
</li></ul></dd>
<?js } ?>
<?js if (data.overrides) { ?>
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<?js= this.linkto(data.overrides, this.htmlsafe(data.overrides)) ?>
</li></ul></dd>
<?js } ?>
<?js if (data.implementations && data.implementations.length) { ?>
<dt class="implementations">Implementations:</dt>
<dd class="implementations"><ul>
<?js data.implementations.forEach(function(impl) { ?>
<li><?js= self.linkto(impl, self.htmlsafe(impl)) ?></li>
<?js }); ?>
</ul></dd>
<?js } ?>
<?js if (data.implements && data.implements.length) { ?>
<dt class="implements">Implements:</dt>
<dd class="implements"><ul>
<?js data.implements.forEach(function(impl) { ?>
<li><?js= self.linkto(impl, self.htmlsafe(impl)) ?></li>
<?js }); ?>
</ul></dd>
<?js } ?>
<?js if (data.mixes && data.mixes.length) { ?>
<dt class="mixes">Mixes In:</dt>
<dd class="mixes"><ul>
<?js data.mixes.forEach(function(a) { ?>
<li><?js= self.linkto(a, a) ?></li>
<?js }); ?>
</ul></dd>
<?js } ?>
<?js if (data.deprecated) { ?>
<dt class="important tag-deprecated">Deprecated:</dt><?js
if (data.deprecated === true) { ?><dd class="yes-def tag-deprecated"><ul class="dummy"><li>Yes</li></ul></dd><?js }
else { ?><dd><ul class="dummy"><li><?js= data.deprecated ?></li></ul></dd><?js }
?>
<?js } ?>
<?js if (data.author && author.length) {?>
<dt class="tag-author">Author:</dt>
<dd class="tag-author">
<ul><?js author.forEach(function(a) { ?>
<li><?js= self.resolveAuthorLinks(a) ?></li>
<?js }); ?></ul>
</dd>
<?js } ?>
<?js if (data.copyright) {?>
<dt class="tag-copyright">Copyright:</dt>
<dd class="tag-copyright"><ul class="dummy"><li><?js= copyright ?></li></ul></dd>
<?js } ?>
<?js if (data.license) {?>
<dt class="tag-license">License:</dt>
<dd class="tag-license"><ul class="dummy"><li><?js= license ?></li></ul></dd>
<?js } ?>
<?js if (data.defaultvalue) {?>
<dt class="tag-default">Default Value:</dt>
<dd class="tag-default"><ul class="dummy">
<li<?js= defaultObjectClass ?>><?js= data.defaultvalue ?></li>
</ul></dd>
<?js } ?>
<?js if (data.meta && self.outputSourceFiles) {?>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<?js= self.linkto(meta.shortpath) ?>, <?js= self.linkto(meta.shortpath, 'line ' + meta.lineno, null, 'line' + meta.lineno) ?>
</li></ul></dd>
<?js } ?>
<?js if (data.tutorials && tutorials.length) {?>
<dt class="tag-tutorial">Tutorials:</dt>
<dd class="tag-tutorial">
<ul><?js tutorials.forEach(function(t) { ?>
<li><?js= self.tutoriallink(t) ?></li>
<?js }); ?></ul>
</dd>
<?js } ?>
<?js if (data.see && see.length) {?>
<dt class="tag-see">See:</dt>
<dd class="tag-see">
<ul><?js see.forEach(function(s) { ?>
<li><?js= self.linkto(s) ?></li>
<?js }); ?></ul>
</dd>
<?js } ?>
<?js if (data.todo && todo.length) {?>
<dt class="tag-todo">To Do:</dt>
<dd class="tag-todo">
<ul><?js todo.forEach(function(t) { ?>
<li><?js= t ?></li>
<?js }); ?></ul>
</dd>
<?js } ?>
</dl>
<?js var data = obj; ?>
<pre><code><?js= data ?></code></pre>
<?js
var data = obj;
var self = this;
data.forEach(function(example) {
if (example.caption) {
?>
<p class="code-caption"><?js= example.caption ?></p>
<?js } ?>
<pre class="prettyprint"><code><?js= self.htmlsafe(example.code) ?></code></pre>
<?js
});
?>
\ No newline at end of file
<?js
var data = obj;
?>
<?js if (data.description && data.type && data.type.names) { ?>
<dl>
<dt>
<div class="param-desc">
<?js= data.description ?>
</div>
</dt>
<dd></dd>
<dt>
<dl>
<dt>
Type
</dt>
<dd>
<?js= this.partial('type.tmpl', data.type.names) ?>
</dd>
</dl>
</dt>
<dd></dd>
</dl>
<?js } else { ?>
<div class="param-desc">
<?js if (data.description) { ?>
<?js= data.description ?>
<?js } else if (data.type && data.type.names) { ?>
<?js= this.partial('type.tmpl', data.type.names) ?>
<?js } ?>
</div>
<?js } ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ARCS: <?js= title ?></title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<header>
<a href="index.html" style="font-size: 200%; text-decoration: none; font-weight: bold; color: #6F6FFF;"><img src="../arcs_logo.png" alt="ARCS logo">.js</a> <span style="font-size: 250%; margin-left:2em; font-weight: bold; color: #6F6FBB;">Augmented Reality Component System</span>
</header>
<div id="main">
<h1 class="page-title"><?js= title ?></h1>
<?js= content ?>
</div>
<nav>
<?js= this.nav ?>
</nav>
<br clear="both">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc <?js= env.version.number ?></a> on <?js= (new Date()) ?>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>
<?js
var data = obj;
var self = this;
?>
<?js if (data.kind === 'package') { ?>
<h3><?js= data.name ?> <?js= data.version ?></h3>
<?js } ?>
<?js if (data.readme) { ?>
<section>
<article><?js= data.readme ?></article>
</section>
<?js } ?>
<?js
var data = obj;
var self = this;
?>
<h4 class="name" id="<?js= id ?>"><?js= data.attribs + name + (data.signature ? data.signature : '') ?></h4>
<?js if (data.summary) { ?>
<p class="summary"><?js= summary ?></p>
<?js } ?>
<?js if (data.description) { ?>
<div class="description">
<?js= data.description ?>
</div>
<?js } ?>
<?js if (data.type && data.type.names) {?>
<h5>Type:</h5>
<ul>
<li>
<?js= self.partial('type.tmpl', data.type.names) ?>
</li>
</ul>
<?js } ?>
<?js= this.partial('details.tmpl', data) ?>
<?js if (data.fires && fires.length) { ?>
<h5>Fires:</h5>
<ul><?js fires.forEach(function(f) { ?>
<li><?js= self.linkto(f) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js if (data.examples && examples.length) { ?>
<h5>Example<?js= examples.length > 1? 's':'' ?></h5>
<?js= this.partial('examples.tmpl', examples) ?>
<?js } ?>
<?js
var data = obj;
var self = this;
?>
<?js if (data.kind !== 'module') { ?>
<?js if (data.kind === 'class' && data.classdesc) { ?>
<h2>Constructor</h2>
<?js } ?>
<h4 class="name" id="<?js= id ?>"><?js= data.attribs + (kind === 'class' ? 'new ' : '') +
name + (data.signature || '') ?></h4>
<?js if (data.summary) { ?>
<p class="summary"><?js= summary ?></p>
<?js } ?>
<?js } ?>
<?js if (data.kind !== 'module' && data.description) { ?>
<div class="description">
<?js= data.description ?>
</div>
<?js } ?>
<?js if (data.augments && data.alias && data.alias.indexOf('module:') === 0) { ?>
<h5>Extends:</h5>
<?js= self.partial('augments.tmpl', data) ?>
<?js } ?>
<?js if (kind === 'event' && data.type && data.type.names) {?>
<h5>Type:</h5>
<ul>
<li>
<?js= self.partial('type.tmpl', data.type.names) ?>
</li>
</ul>
<?js } ?>
<?js if (data['this']) { ?>
<h5>This:</h5>
<ul><li><?js= this.linkto(data['this'], data['this']) ?></li></ul>
<?js } ?>
<?js if (data.params && params.length) { ?>
<h5>Parameters:</h5>
<?js= this.partial('params.tmpl', params) ?>
<?js } ?>
<?js= this.partial('details.tmpl', data) ?>
<?js if (data.kind !== 'module' && data.requires && data.requires.length) { ?>
<h5>Requires:</h5>
<ul><?js data.requires.forEach(function(r) { ?>
<li><?js= self.linkto(r) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js if (data.fires && fires.length) { ?>
<h5>Fires:</h5>
<ul><?js fires.forEach(function(f) { ?>
<li><?js= self.linkto(f) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js if (data.listens && listens.length) { ?>
<h5>Listens to Events:</h5>
<ul><?js listens.forEach(function(f) { ?>
<li><?js= self.linkto(f) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js if (data.listeners && listeners.length) { ?>
<h5>Listeners of This Event:</h5>
<ul><?js listeners.forEach(function(f) { ?>
<li><?js= self.linkto(f) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js if (data.emits && emits.length) { ?>
<h5>Emits signals:</h5>
<ul><?js emits.forEach(function(f) { ?>
<li><?js= self.linkto(data.memberof+"#"+f,f) ?></li>
<?js }); ?></ul>
<?js } ?>
<?js if (data.exceptions && exceptions.length) { ?>
<h5>Throws:</h5>
<?js if (exceptions.length > 1) { ?><ul><?js
exceptions.forEach(function(r) { ?>
<li><?js= self.partial('exceptions.tmpl', r) ?></li>
<?js });
?></ul><?js } else {
exceptions.forEach(function(r) { ?>
<?js= self.partial('exceptions.tmpl', r) ?>
<?js });
} } ?>
<?js if (data.returns && returns.length) { ?>
<h5>Returns:</h5>
<?js if (returns.length > 1) { ?><ul><?js
returns.forEach(function(r) { ?>
<li><?js= self.partial('returns.tmpl', r) ?></li>
<?js });
?></ul><?js } else {
returns.forEach(function(r) { ?>
<?js= self.partial('returns.tmpl', r) ?>
<?js });
} } ?>
<?js if (data.examples && examples.length) { ?>
<h5>Example<?js= examples.length > 1? 's':'' ?></h5>
<?js= this.partial('examples.tmpl', examples) ?>
<?js } ?>
<?js
var params = obj;
/* sort subparams under their parent params (like opts.classname) */
var parentParam = null;
params.forEach(function(param, i) {
var paramRegExp;
if (!param) {
return;
}
if (parentParam && parentParam.name && param.name) {
paramRegExp = new RegExp('^(?:' + parentParam.name + '(?:\\[\\])*)\\.(.+)$');
if ( paramRegExp.test(param.name) ) {
param.name = RegExp.$1;
parentParam.subparams = parentParam.subparams || [];
parentParam.subparams.push(param);
params[i] = null;
}
else {
parentParam = param;
}
}
else {
parentParam = param;
}
});
/* determine if we need extra columns, "attributes" and "default" */
params.hasAttributes = false;
params.hasDefault = false;
params.hasName = false;
params.forEach(function(param) {
if (!param) { return; }
if (param.optional || param.nullable || param.variable) {
params.hasAttributes = true;
}
if (param.name) {
params.hasName = true;
}
if (typeof param.defaultvalue !== 'undefined') {
params.hasDefault = true;
}
});
?>
<table class="params">
<thead>
<tr>
<?js if (params.hasName) {?>
<th>Name</th>
<?js } ?>
<th>Type</th>
<?js if (params.hasAttributes) {?>
<th>Attributes</th>
<?js } ?>
<?js if (params.hasDefault) {?>
<th>Default</th>
<?js } ?>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<?js
var self = this;
params.forEach(function(param) {
if (!param) { return; }
?>
<tr>
<?js if (params.hasName) {?>
<td class="name"><code><?js= param.name ?></code></td>
<?js } ?>
<td class="type">
<?js if (param.type && param.type.names) {?>
<?js= self.partial('type.tmpl', param.type.names) ?>
<?js } ?>
</td>
<?js if (params.hasAttributes) {?>
<td class="attributes">
<?js if (param.optional) { ?>
&lt;optional><br>
<?js } ?>
<?js if (param.nullable) { ?>
&lt;nullable><br>
<?js } ?>
<?js if (param.variable) { ?>
&lt;repeatable><br>
<?js } ?>
</td>
<?js } ?>
<?js if (params.hasDefault) {?>
<td class="default">
<?js if (typeof param.defaultvalue !== 'undefined') { ?>
<?js= self.htmlsafe(param.defaultvalue) ?>
<?js } ?>
</td>
<?js } ?>
<td class="description last"><?js= param.description ?><?js if (param.subparams) { ?>
<h6>Properties</h6>
<?js= self.partial('params.tmpl', param.subparams) ?>
<?js } ?></td>
</tr>
<?js }); ?>
</tbody>
</table>
<?js
var data = obj;
var props = data.subprops || data.properties;
/* sort subprops under their parent props (like opts.classname) */
var parentProp = null;
props.forEach(function(prop, i) {
if (!prop) { return; }
if ( parentProp && prop.name && prop.name.indexOf(parentProp.name + '.') === 0 ) {
prop.name = prop.name.substr(parentProp.name.length+1);
parentProp.subprops = parentProp.subprops || [];
parentProp.subprops.push(prop);
props[i] = null;
}
else {
parentProp = prop;
}
});
/* determine if we need extra columns, "attributes" and "default" */
props.hasAttributes = false;
props.hasDefault = false;
props.hasName = false;
props.forEach(function(prop) {
if (!prop) { return; }
if (prop.optional || prop.nullable) {
props.hasAttributes = true;
}
if (prop.name) {
props.hasName = true;
}
if (typeof prop.defaultvalue !== 'undefined' && !data.isEnum) {
props.hasDefault = true;
}
});
?>
<table class="props">
<thead>
<tr>
<?js if (props.hasName) {?>
<th>Name</th>
<?js } ?>
<th>Type</th>
<?js if (props.hasAttributes) {?>
<th>Attributes</th>
<?js } ?>
<?js if (props.hasDefault) {?>
<th>Default</th>
<?js } ?>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<?js
var self = this;
props.forEach(function(prop) {
if (!prop) { return; }
?>
<tr>
<?js if (props.hasName) {?>
<td class="name"><code><?js= prop.name ?></code></td>
<?js } ?>
<td class="type">
<?js if (prop.type && prop.type.names) {?>
<?js= self.partial('type.tmpl', prop.type.names) ?>
<?js } ?>
</td>
<?js if (props.hasAttributes) {?>
<td class="attributes">
<?js if (prop.optional) { ?>
&lt;optional><br>
<?js } ?>
<?js if (prop.nullable) { ?>
&lt;nullable><br>
<?js } ?>
</td>
<?js } ?>
<?js if (props.hasDefault) {?>
<td class="default">
<?js if (typeof prop.defaultvalue !== 'undefined') { ?>
<?js= self.htmlsafe(prop.defaultvalue) ?>
<?js } ?>
</td>
<?js } ?>
<td class="description last"><?js= prop.description ?><?js if (prop.subprops) { ?>
<h6>Properties</h6><?js= self.partial('properties.tmpl', prop) ?>
<?js } ?></td>
</tr>
<?js }); ?>
</tbody>
</table>
<?js
var data = obj || {};
if (data.description) {
?>
<div class="param-desc">
<?js= description ?>
</div>
<?js } ?>
<?js if (data.type && data.type.names) {?>
<dl>
<dt>
Type
</dt>
<dd>
<?js= this.partial('type.tmpl', data.type.names) ?>
</dd>
</dl>
<?js } ?>
\ No newline at end of file
<?js
var data = obj;
?>
<section>
<article>
<pre class="prettyprint source linenums"><code><?js= data.code ?></code></pre>
</article>
</section>
\ No newline at end of file
<section>
<header>
<?js if (children.length > 0) { ?>
<ul><?js
var self = this;
children.forEach(function(t) { ?>
<li><?js= self.tutoriallink(t.name) ?></li>
<?js }); ?></ul>
<?js } ?>
<h2><?js= header ?></h2>
</header>
<article>
<?js= content ?>
</article>
</section>
<?js
var data = obj;
var self = this;
data.forEach(function(name, i) { ?>
<span class="param-type"><?js= self.linkto(name, self.htmlsafe(name)) ?></span>
<?js if (i < data.length-1) { ?>|<?js } ?>
<?js }); ?>
\ No newline at end of file
{
"plugins": ["sigslot.js"],
"source" : {
"include" : [ "../components", "Readme.md", "../deps/surf/surf.js", "../deps/pose/posit_gen.js" ]
},
"opts" : {
"template": "arcs",
"destination": "components"
}
}
exports.defineTags = function(dictionary) {
dictionary.defineTag('signal', {
onTagged: function(doclet, tag) {
doclet.signal = true;
},
mustNotHaveValue : true,
mustNotHaveDescription: true
});
dictionary.defineTag('slot', {
onTagged: function(doclet, tag) {
doclet.slot = true;
},
mustNotHaveValue : true,
mustNotHaveDescription: true
});
dictionary.defineTag('emits', {
onTagged: function(doclet, tag) {
doclet.emits = doclet.emits || [];
doclet.emits.push(tag.value);
},
mustHaveValue: true
});
};
exports.handlers = {
newDoclet : function(e) {
var doclet = e.doclet;
if (doclet.kind === 'function') {
doclet.signal = doclet.signal || false;
doclet.slot = doclet.slot || false;
}
}
};
\ No newline at end of file
{
"name": "arcsjs-components",
"version": "0.9.1",
"description": "Components for Augmented Reality Component System in web browser and node environment",
"homepage": "http://arcs.ibisc.fr",
"repository": {
"type": "git",
"url": "https://forge.ibisc.univ-evry.fr/arcs/arcs.js-components.git"
},
"main": "build/arcs.js",
"keywords": [
"Augmented Reality"
],
"eslintConfig": {
"env": {
"browser": true,
"es2021": true
},
"parserOptions": {
"sourceType": "module"
},
"rules": {
"func-style": "off"
}
},
"author": "Jean-Yves Didier",
"license": "GPL-3.0-or-later",
"dependencies": {
"js-aruco": ">=0.1",
"three": ">=0.131",
"tracking": ">=1.1.3",
"webpack": "^5.51.1",
"jsqr": "^1.4.0",
},
"devDependencies": {
"copy-webpack-plugin": "^9.0.1",
"jsdoc-webpack-plugin": "^0.3.0",
"val-loader": "^4.0.0"
},
"scripts": {
"doc": "webpack --config-name doc"
}
}
const path = require('path');
const jsdoc = require('jsdoc-webpack-plugin');
const copy = require('copy-webpack-plugin');
module.exports = [
{
name: "doc",
mode: "development",
entry: {},
plugins: [
new jsdoc({
conf: 'docs/components.conf.json',
destination: 'docs/components',
template: 'docs/arcs',
}),
]
},
{
name: "deps",
mode: "production",
entry: {},
plugins: [
new copy({
patterns: [
{ from: 'node_modules/jsqr/dist/jsQR.js', to: '../deps/jsqr/jsQR.js'},
{ from: 'node_modules/tracking/build/tracking.js', to: '../deps/tracking/tracking.js'},
{ from: 'node_modules/three/build/three.module.js', to: '../deps/three.js/index.js'},
{
from: 'node_modules/three/examples/jsm/loaders/OBJLoader.js',
to: '../deps/objloader/index.js',
transform: function(content, path) {
return Buffer.from(content.toString().replace(
'../../../build/three.module.js',
'../three.js/index.js'
));
}
},
{
from: 'node_modules/three/examples/jsm/loaders/MTLLoader.js',
to: '../deps/mtlloader/index.js',
transform: function(content, path) {
return Buffer.from(content.toString().replace(
'../../../build/three.module.js',
'../three.js/index.js'
));
}
},
{
from: 'node_modules/three/examples/jsm/loaders/DDSLoader.js',
to: '../deps/ddsloader/index.js',
transform: function(content, path) {
return Buffer.from(content.toString().replace(
'../../../build/three.module.js',
'../three.js/index.js'
));
}
},
{
from: 'node_modules/three/examples/jsm/webxr/ARButton.js',
to: '../deps/three.js/ARButton.js',
transform: function(content, path) {
return Buffer.from(content.toString().replace(
'../../../build/three.module.js',
'./index.js'
));
}
},
{
from: 'node_modules/js-aruco/src/aruco.js',
to: '../deps/aruco/index.js',
transform: function(content, path) {
return Buffer.from(content.toString()
.replace(
"var CV = require('./cv');",
"import CV from '../cv/index.js';"
)
.replace(
"module.exports = AR;",
"export default AR;"
)
);
}
},
{
from: 'node_modules/js-aruco/src/cv.js',
to: '../deps/cv/index.js',
transform: function(content, path) {
return Buffer.from(content.toString()
.replace(
"module.exports = CV;",
"export default CV;"
)
);
}
},
// TODO put here also files for dep directory.
// can also prepend content and replace strings using the transform property!
]
}),
]
}
];