src/myr/Myr.js
import "aframe";
import "@engaging-computing/aframe-physics-system";
import Group from "./Group";
import CANNON from "cannon";
import TexturePack from "../components/structural/Textures.js";
import ModelPack from "../components/structural/Models.js";
class Myr {
/**
* Instantiate MYR class
* @param {object} baseEls list of the entities restore to the new MYR
*/
constructor(baseEls) {
this.counter = 0;
this.baseEls = baseEls;
this.els = [];
this.assets = [];
this.res = { els: this.els, assets: this.assets };
this.sceneEl = document.querySelector("a-scene");
this.cursor = {
color: "red",
texture: "",
transparency: 0,
position: {
x: 0,
y: 0,
z: 0
},
scale: {
x: 1,
y: 1,
z: 1
},
rotation: {
x: 0,
y: 0,
z: 0
},
radius: "1",
phiLength: 360,
loop: true,
textureColoring: false,
textureRepeatWidth: 1,
textureRepeatHeight: 1,
duration: 1000,
magnitude: {
spin: 360,
fadeOut: 0,
general: 1
},
light: {
beamAngle: 60,
decay: 1,
distance: 0.0,
intensity: 1.0,
diffusion: 0.0,
target: null
}
};
if (baseEls) {
Object.keys(this.baseEls).forEach(it => {
this.els[it] = this.baseEls[it];
});
}
}
/**
* init creates and binds the myr object to the window
*
* @param [{}] objs these are the base objects for this object
*
*/
init = () => {
// Get all the function names of the Myr(this) class
let funs = Object.keys(this).filter((p) => {
return typeof this[p] === "function";
});
// For each function bind it to the window
funs.forEach(element => {
// If a collision is detected then do not override and warn
if (Object.prototype.hasOwnProperty.call(window, element)) {
console.warn(`The ${element} of Myr is being overridden.\n` +
"If this was not intentional consider renaming the function.");
} else {
// Collision free so we can bind to window
window[element] = this[element];
}
});
}
/**
* Reset this.els to the base elements supplied to the constructor
*/
reset = () => {
// Reset base params, we might be able to merge two objects later
this.id = 0;
this.cursor = {
color: "red",
texture: "",
transparency: 0,
position: {
x: 0,
y: 0,
z: 0
},
scale: {
x: 1,
y: 1,
z: 1
},
rotation: {
x: 0,
y: 0,
z: 0
},
radius: "1",
phiLength: 360,
loop: true,
textureColoring: false,
textureRepeatWidth: 1,
textureRepeatHeight: 1,
duration: 1000,
magnitude: {
spin: 360,
fadeOut: 0,
general: 1
},
light: {
intensity: 1.0,
beamAngle: 60,
diffusion: 0.0,
decay: 1,
distance: 0.0,
target: null
}
};
// restore the base objects of the scene
this.els = [];
if (this.baseEls) {
Object.keys(this.baseEls).forEach(it => {
this.els[it] = this.baseEls[it];
});
}
}
/********************* TRANSFORMATIONS *********************/
/**
* Reset the cursor to the default
*/
resetCursor = () => {
this.cursor = {
color: "red",
texture: "",
transparency: 0,
position: {
x: 0,
y: 0,
z: 0
},
scale: {
x: 1,
y: 1,
z: 1
},
rotation: {
x: 0,
y: 0,
z: 0
},
radius: "1",
phiLength: 360,
loop: true,
textureColoring: false,
textureRepeatWidth: 1,
textureRepeatHeight: 1,
duration: 1000,
magnitude: {
spin: 360,
fadeOut: 0,
general: 1
},
light: {
intensity: 1.0,
beamAngle: 60,
diffusion: 0.0,
decay: 1,
distance: 0.0,
target: null
}
};
}
/**
* Reset the transformation properties of the cursor to the default
*/
resetTransformationCursor = () => {
this.cursor = {
...this.cursor,
color: "red",
position: {
x: 0,
y: 0,
z: 0
},
scale: {
x: 1,
y: 1,
z: 1
},
rotation: {
x: 0,
y: 0,
z: 0
},
radius: "1",
phiLength: 360,
};
};
/**
* Reset the animation properties of the cursor to the default
*/
resetAnimationCursor = () => {
this.cursor = {
...this.cursor,
loop: true,
duration: 1000,
magnitude: {
spin: 360,
fadeOut: 0,
general: 1
}
};
};
/**
* Reset the light properties of the cursor to the default
*/
resetLightCursor = () => {
this.cursor.light = {
intensity: 1.0,
beamAngle: 60,
diffusion: 0.0,
decay: 1,
distance: 0.0,
target: null
};
};
/**
* Generate unique id that is assigned to each entity
*/
genNewId = () => {
return this.counter++;
};
/**
* Sets the transparency of the cursor
*
* @param {number} transparency New transparency of the cursor (0-100)
*/
setTransparency = (transparency = 0) => {
if(typeof transparency === "number" && transparency <= 100 && transparency >= 0) {
this.cursor.transparency = transparency / 100;
} else {
console.error("setTransparency() either recieved a non numeric value or a value out of range (0-100)");
}
return this.cursor.opacity;
}
/**
* Sets the x, y, and z position of the cursor
*
* @param {number} x New x position of the cursor
* @param {number} y New y position of the cursor
* @param {number} z New z position of the cursor
*/
setPosition = (x = 0, y = 1, z = 0) => {
if (typeof x === "number" && typeof y === "number" && typeof z === "number") {
this.cursor.position = {
x: x,
y: y,
z: z
};
} else {
console.error("setPosition() must be all numeric values");
}
return { x: this.cursor.position.x, y: this.cursor.position.y, z: this.cursor.position.z };
};
/**
* Sets the x position of the cursor
*
* @param {number} x New x position of the cursor
*/
setXPos = (x = 0) => {
if (typeof x === "number") {
this.cursor.position = { ...this.cursor.position, x };
} else {
console.error("must pass a numeric for setXPos");
}
return this.cursor.position.x;
};
/**
* Sets the y position of the cursor
*
* @param {number} y New y position of the cursor
*/
setYPos = (y = 0) => {
if (typeof y === "number") {
this.cursor.position = { ...this.cursor.position, y };
} else {
console.error("must pass a numeric for setYPos");
}
return this.cursor.position.y;
};
/**
* Sets the z position of the cursor
*
* @param {number} z New z position of the cursor
*/
setZPos = (z = 0) => {
if (typeof z === "number") {
this.cursor.position = { ...this.cursor.position, z };
} else {
console.error("must pass a numeric for setZPos");
}
return this.cursor.position.z;
};
/**
* Increases the current x, y, and z position of the cursor by the given amount
*
* @param {number} x Amount to increment the x position by
* @param {number} y Amount to increment the y position by
* @param {number} z Amount to increment the z position by
*/
increasePosition = (x = 0, y = 0, z = 0) => {
if (typeof x === "number" && typeof y === "number" && typeof z === "number") {
this.cursor.position = {
...this.cursor.position,
x: this.cursor.position.x + x,
y: this.cursor.position.y + y,
z: this.cursor.position.z + z
};
} else {
console.error("increasePosition() must be all numeric values");
}
return { x: this.cursor.position.x, y: this.cursor.position.y, z: this.cursor.position.z };
}
/**
* Increases the current x position of the cursor by the given amount
*
* @param {number} x Amount to increment the x position by
*/
increaseXPos = (x = 1) => {
if (typeof x === "number") {
this.cursor.position = {
...this.cursor.position,
x: this.cursor.position.x + x,
};
} else {
console.error("must pass a numeric value for increaseXPos");
}
return this.cursor.position.x;
}
/**
* Increases the current y position of the cursor by the given amount
*
* @param {number} y Amount to increment the y position by
*/
increaseYPos = (y = 1) => {
if (typeof y === "number") {
this.cursor.position = {
...this.cursor.position,
y: this.cursor.position.y + y,
};
} else {
console.error("must pass a numeric value for increaseYPos");
}
return this.cursor.position.y;
}
/**
* Increases the current z position of the cursor by the given amount
*
* @param {number} z Amount to increment the z position by
*/
increaseZPos = (z = 1) => {
if (typeof z === "number") {
this.cursor.position = {
...this.cursor.position,
z: this.cursor.position.z + z,
};
} else {
console.error("must pass a numeric value for increaseZPos");
}
return this.cursor.position.z;
}
/**
* Sets the x, y, and z scale of the cursor
*
* @param {number} x New x scale of the cursor
* @param {number} y New y scale of the cursor
* @param {number} z New z scale of the cursor
*/
setScale = (x = 1, y = 1, z = 1) => {
if (typeof x === "number" && typeof y === "number" && typeof z === "number") {
this.cursor.scale = {
x: x,
y: y,
z: z
};
} else {
console.error("setScale() must be all numeric values");
}
return { x: this.cursor.scale.x, y: this.cursor.scale.y, z: this.cursor.scale.z };
};
/**
* Applies a given scale along the x dimension of the cursor
*
* @param {number} x New x scale of the cursor
*/
setXScale = (x) => {
if (typeof x === "number") {
this.cursor.scale = { ...this.cursor.scale, x };
} else {
console.error("must pass a numeric for setXScale");
}
return this.cursor.scale.x;
};
/**
* Applies a given scale along the y dimension of the cursor
*
* @param {number} y New y scale of the cursor
*/
setYScale = (y) => {
if (typeof y === "number") {
this.cursor.scale = { ...this.cursor.scale, y };
} else {
console.error("must pass a numeric for setYScale");
}
return this.cursor.scale.y;
};
/**
* Applies a given scale along the z dimension of the cursor
*
* @param {number} z New z scale of the cursor
*/
setZScale = (z) => {
if (typeof z === "number") {
this.cursor.scale = { ...this.cursor.scale, z };
} else {
console.error("must pass a numeric for setZScale");
}
return this.cursor.scale.z;
};
/**
* Sets the x, y, and z rotation of the cursor
*
* @param {number} x New x rotation of the cursor
* @param {number} y New y rotation of the cursor
* @param {number} z New z rotation of the cursor
*/
setRotation = (x = 0, y = 0, z = 0) => {
if (typeof x === "number" && typeof y === "number" && typeof z === "number") {
this.cursor.rotation = {
x: x,
y: y,
z: z
};
} else {
console.error("setRotation() must be all numeric values");
}
return { x: this.cursor.rotation.x, y: this.cursor.rotation.y, z: this.cursor.rotation.z };
}
/**
* Applies a given x rotation to the cursor
*
* @param {number} x New x rotation of the cursor
*/
pitchX = (x) => {
if (typeof x === "number") {
this.cursor.rotation = { ...this.cursor.rotation, x };
} else {
console.error("must pass a numeric for pitchX");
}
return this.cursor.rotation.x;
};
/**
* Applies a given y rotation to the cursor
*
* @param {number} y New y rotation of the cursor
*/
yawY = (y) => {
if (typeof y === "number") {
this.cursor.rotation = { ...this.cursor.rotation, y };
} else {
console.error("must pass a numeric for yawY");
}
return this.cursor.rotation.y;
};
/**
* Applies a given z rotation to the cursor
*
* @param {number} z New z rotation of the cursor
*/
rollZ = (z) => {
if (typeof z === "number") {
this.cursor.rotation = { ...this.cursor.rotation, z };
} else {
console.error("must pass a numeric for rollZ");
}
return this.cursor.rotation.z;
};
/**
* Sets the radius of the cursor
*
* @param {number} i New radius of the cursor
*/
setRadius = (i) => {
if (typeof i === "number") {
this.cursor.radius = String(i);
} else {
console.error("must pass a numeric for setRadius");
}
return this.cursor.radius;
};
/**
* Sets the phi length of the cursor
*
* @param {number} i New phi length of the cursor
*/
setPhiLength = (i) => {
if (typeof i === "number") {
this.cursor.phiLength = String(i);
} else {
console.error("must pass a numeric for setPhiLength");
}
return this.cursor.phiLength;
};
/**
* Sets the loop of the cursor
*
* @param {number} i New loop of the cursor
*/
setLoop = (i) => {
this.cursor.loop = Boolean(i);
return this.cursor.loop;
};
/**
* Sets the magnitude of the cursor
*
* @param {number} i New magnitude of the cursor
*/
setMagnitude = (i) => {
if (typeof i === "number") {
this.cursor.magnitude = {
spin: i,
fadeOut: i,
general: i
};
} else {
console.error("must pass a numeric for setMagnitude");
}
return this.cursor.magnitude.general;
};
/**
* Sets the duration of the cursor
*
* @param {number} i New duration of the cursor
*/
setDuration = (i) => {
if (typeof i === "number") {
this.cursor.duration = i;
} else {
console.error("must pass a numeric for setDuration");
}
return this.cursor.duration;
};
/**
* Sets the current color of the cursor. Defaults to white.
*
* @param {number} color New color of the cursor
*/
setColor = (color = "white") => {
this.cursor.color = color.toLowerCase();
return this.cursor.color;
}
/**
* Sets the texture of the cursor. Defaults is empty string.
*
* @param {string} texture Specifying the texture. This can be either texture exist in our sever or valid url to the texture
* @param {number} w Number of times to repeat the texture horizontally
* @param {number} h Number of times to repeat the texture vertically
*/
setTexture = (texture = "", w = 1, h = 1) => {
let textures = TexturePack();
let textureTitle = [...textures.TexturePack.map(obj => obj.title)];
let textureURL = [...textures.TexturePack.map(obj => obj.url)];
let urlregex = /^(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png)/;
if (textureURL[textureTitle.indexOf(texture.toLowerCase())] !== undefined) {
this.cursor.texture = textureURL[textureTitle.indexOf(texture.toLowerCase())];
}
else if(urlregex.test(texture) || (texture === "")) {
this.cursor.texture = texture;
}
else {
console.error("Not a usable texture or URL.");
throw new Error("Not a usable texture or URL.");
}
this.cursor.textureRepeatWidth = w;
this.cursor.textureRepeatHeight = h;
return this.cursor.texture;
}
/**
* Sets whether to enable to color the texture or not
*
* @param {boolean} i Enable or disable texture coloring
*/
setTextureColoring = (i) => {
this.cursor.textureColoring = Boolean(i);
return this.cursor.textureColoring;
};
/**
* Sets the existing cursor as well as creates a new custom cursor attributes to apply to entity
*
* @param {string} key Name of the attribute
* @param {any} value Values apply to attribute
*/
setCursorAttribute = (key = "", value = "") => {
if (typeof (key) !== "string" || key === "") {
console.error("Error: Invalid key");
return this.cursor;
}
switch (key.toLowerCase()) {
case "color":
this.setColor(value);
break;
case "position":
this.setPosition(value.x, value.y, value.z);
break;
case "scale":
this.setScale(value.x, value.y, value.z);
break;
case "rotation":
this.setRotation(value.x, value.y, value.z);
break;
case "radius":
this.setRadius(value);
break;
case "philength":
this.setPhiLength(value);
break;
case "loop":
this.setLoop(value);
break;
case "duration":
this.setDuration(value);
break;
case "magnitude":
this.setMagnitude(value);
break;
default:
this.cursor[key] = value;
}
return this.cursor;
}
/**
* Get the any values of the exisitng cursor attribute
*
* @param {string} key Name of the cursor attribute
* @returns Value of the cursor attribute if it exists
*/
getCursorAttribute = (key = "") => {
return this.cursor[key];
}
/**
* Returns a random valid color.
*
* @param {array} colors An array of colors to choose from. If left
* empty then random hexadecimal color are chosen.
*/
getRandomColor = (colors = null) => {
let color;
if (Array.isArray(colors) && colors.length !== 0) {
color = colors[Math.floor(Math.random() * colors.length)];
}
else {
let i, letters;
letters = "0123456789ABCDEF";
color = "#";
i = 0;
while (i < 6) {
color += letters[Math.floor(Math.random() * 16)];
i++;
}
}
return color;
}
setRandomColor = (colors = null) => {
this.cursor.color = this.getRandomColor(colors);
return this.cursor.color;
}
/**
* Allows the entity to be dropped
*
* @param {string} outerElId Id of the entity to apply the physics
* @param {number} mass Set how heavy the entity is.
*/
makeDroppable = (outerElId, mass = 2) => {
let el = this.getEl(outerElId);
let dynamicBody = `shape: auto; mass: ${mass}; angularDamping: 0.5; linearDamping: 0.5;`;
el["dynamic-body"] = dynamicBody;
return outerElId;
}
/**
* Disallows the entity to be dropped
*
* @param {string} outerElId Id of the entity to remove the properties
*/
makeUnDroppable = (outerElId) => {
let el = this.getEl(outerElId);
//Only makes an item undroppable if it is droppable but is not pushable
if (el["dynamic-body"] && (!el["force-pushable"] || el["force-pushable"] === "false")) {
el["dynamic-body"] = null;
}
return outerElId;
}
/**
* Allows the entity to be pushed
*
* @param {string} outerElId Id of the entity to apply the physics
* @param {number} mass Set how heavy the entity is.
*/
makePushable = (outerElId, mass = 2) => {
let el = this.getEl(outerElId);
let dynamicBody = `shape: auto; mass: ${mass}; angularDamping: 0.5; linearDamping: 0.5;`;
el["dynamic-body"] = dynamicBody;
el["force-pushable"] = "true";
return outerElId;
}
/**
* Disallows the entity to be pushed
*
* @param {string} outerElId Id of the entity to remove the properties
*/
makeUnPushable = (outerElId) => {
let el = this.getEl(outerElId);
if (el["force-pushable"]) {
el["dynamic-body"] = null;
el["force-pushable"] = "false";
}
return outerElId;
}
/**
* Render an Aframe box with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
box = (params) => {
let base = {
id: "box" + this.genNewId(),
geometry: "primitive: box;",
position: { ...this.cursor.position },
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe circle with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
circle = (params) => {
let base = {
id: "circ" + this.genNewId(),
geometry: `primitive: circle; radius: ${this.cursor.radius}; theta-length: ${this.cursor.phiLength};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe cone with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
cone = (params) => {
let base = {
id: "cone" + this.genNewId(),
geometry: `primitive: cone; radiusBottom: ${this.cursor.radius}; radiusTop: 0.1;`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe cylinder with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
cylinder = (params) => {
let base = {
id: "cyl" + this.genNewId(),
geometry: `primitive: cylinder; radius: ${this.cursor.radius}; theta-length: ${this.cursor.phiLength};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe dodecahedron with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
dodecahedron = (params) => {
let base = {
id: "dod" + this.genNewId(),
geometry: `primitive: dodecahedron; radius: ${this.cursor.radius};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe icosahedron with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
icosahedron = (params) => {
let base = {
id: "iso" + this.genNewId(),
geometry: "primitive: icosahedron;",
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe octahedron with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
octahedron = (params) => {
let base = {
id: "oct" + this.genNewId(),
geometry: "primitive: octahedron;",
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe line with current Myr settings
*
* @param {string} path String of positions that represents the path in following format: "x1 y1 z1, x2 y2 z2,...,xn yn zn"
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
line = (path, params) => {
let base = {
id: "line" + this.genNewId(),
tube: true,
radius: ".01",
path: path,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe plane with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
plane = (params) => {
let base = {
id: "plane" + this.genNewId(),
geometry: `primitive: plane; height: 1; width: 1; phi-length: ${this.cursor.phiLength};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe polyhedron with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
prism = (params) => {
let base = {
id: "poly" + this.genNewId(),
geometry: `primitive: sphere; segmentsWidth: 2; segmentsHeight: 8; phi-length: ${this.cursor.phiLength};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe ring with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
ring = (params) => {
let base = {
id: "ring" + this.genNewId(),
geometry: `primitive: ring; radiusInner: 0.5; radiusOuter: 1; theta-length: ${this.cursor.phiLength};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe sphere with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
sphere = (params) => {
let base = {
id: "sphere" + this.genNewId(),
geometry: `primitive: sphere; phi-length: ${this.cursor.phiLength}`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe tetrahedron with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
tetrahedron = (params) => {
let base = {
id: "tetra" + this.genNewId(),
geometry: "primitive: tetrahedron;",
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe text with current Myr settings
*
* This is a bit tricky. We need to pass text so we can decide how to render it.
* This throws a warning since text is not part of the entity system.
* Instead we pass it and then pull it off again if we see it.
*
* @param {string} text Text to display
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
text = (text, params) => {
if (typeof text !== "string") {
text = "Default";
}
let base = {
text: true,
value: text,
id: "txt" + this.genNewId(),
side: "double",
color: this.cursor.color,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
};
if (!params || typeof params === "string") {
this.els[base.id] = { ...base };
} else {
this.els[base.id] = { ...base, ...params };
}
return base.id;
}
/**
* Render an Aframe torus with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
torus = (params) => {
let base = {
id: "torus" + this.genNewId(),
geometry: `primitive: torus; radius: ${this.cursor.radius}; radiusTubular: 0.25; arc: 360; arc: ${this.cursor.phiLength};`,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe torusknot with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
torusknot = (params) => {
let base = {
id: "torKn" + this.genNewId(),
geometry: "primitive: torusKnot;",
position: this.cursor.position,
rotation: this.cursor.rotation,
p: 2,
q: 3,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe triangle with current Myr settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
triangle = (params) => {
let base = {
id: "tri" + this.genNewId(),
geometry: "primitive: triangle;",
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Render an Aframe tube with current Myr settings
*
* @param {string} path String of positions that represents the path in following format: "x1 y1 z1, x2 y2 z2,...,xn yn zn"
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
tube = (path, params) => {
let base = {
id: "tube" + this.genNewId(),
tube: true,
radius: this.cursor.radius,
path: path,
position: this.cursor.position,
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; repeat: ${this.cursor.textureRepeatWidth + " " + this.cursor.textureRepeatHeight}; opacity: ${1 - this.cursor.transparency};`,
};
return this.mergeProps(base, params);
}
/**
* Load and render a custom glTF model with current Myr settings
*
* @param {string} src Valid MYR model ID or valid glTF URL
* @param {*} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
gltfModel = (src, params) => {
let id = `gltf-model-${this.genNewId()}`;
let models = ModelPack();
let urlregex_https = /^(https:)([/|.|\w|\s|-])*\.(?:glb|gltf)/;
let urlregex_http = /^(http:)([/|.|\w|\s|-])*\.(?:glb|gltf)/;
if(models.ModelPack.has(src)) {
src = models.ModelPack.get(src).model;
} else if(!urlregex_https.test(src)) {
let error = `Unable to load model (${src}).\n`;
if(urlregex_http.test(src)) {
error += "\tHTTP URLs not supported. Please use HTTPS.";
} else {
error += "\tInvalid URL.";
}
console.error(error);
throw new Error(error);
}
// If a standard github URL, try to refactor it to the raw file URL
src = src.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/").replace("?raw=true", "");
let asset = {
id: id,
src: src,
};
let base = {
id: id,
"gltf-Model": `#${id}`,
position: { ...this.cursor.position },
rotation: this.cursor.rotation,
scale: this.cursor.scale,
material: ((this.cursor.texture === "" || this.cursor.textureColoring) ? `color: ${this.cursor.color};` : "color: white;") + `side: double; src: ${this.cursor.texture}; opacity: ${1 - this.cursor.transparency};`
};
this.assets.push(asset);
return this.mergeProps(base, params);
}
/**
* Render an Ambient light with current cursor light settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
ambientLight = (params) => {
let base = {
id: "lgt" + this.genNewId(),
light: {
state:`
type: ambient;
color: ${this.cursor.color};
intensity: ${this.cursor.light.intensity};`,
type: "ambient",
},
color: this.cursor.color,
position: this.cursor.position,
scale: {x:1,y:1,z:1},
rotation: this.cursor.rotation,
};
return this.mergeProps(base, params);
}
/**
* Render a Directional light with current cursor light settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
directionalLight = (params) => {
let base = {
id: "lgt" + this.genNewId(),
light: {
state:`
type: directional;
color: ${this.cursor.color};
intensity: ${this.cursor.light.intensity};`,
target: this.cursor.light.target,
type: "directional",
},
color: this.cursor.color,
position: this.cursor.position,
scale: {x:1,y:1,z:1},
rotation: this.cursor.rotation,
};
return this.mergeProps(base, params);
}
/**
* Render a Spot light with current cursor light settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
spotLight = (params) => {
let base = {
id: "lgt" + this.genNewId(),
light: {
state:`
type: spot;
angle: ${this.cursor.light.beamAngle};
decay: ${this.cursor.light.decay};
distance: ${this.cursor.light.distance};
intensity: ${this.cursor.light.intensity};
penumbra: ${this.cursor.light.diffusion};
color: ${this.cursor.color};`,
type: "spot",
angle: this.cursor.light.beamAngle,
target: this.cursor.light.target
},
color: this.cursor.color,
position: this.cursor.position,
scale: {x:1,y:1,z:1},
rotation:this.cursor.rotation
};
return this.mergeProps(base, params);
}
/**
* Render an point light with current cursor light settings
*
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
pointLight = (params) => {
let base = {
id: "lgt" + this.genNewId(),
light: {
state:`
type: point;
angle: ${this.cursor.light.beamAngle};
decay: ${this.cursor.light.decay};
distance: ${this.cursor.light.distance};
intensity: ${this.cursor.light.intensity};
penumbra: ${this.cursor.light.diffusion};
color: ${this.cursor.color};`,
type: "point",
},
color: this.cursor.color,
position: this.cursor.position,
scale: {x:1,y:1,z:1},
rotation: this.cursor.rotation,
};
return this.mergeProps(base, params);
}
/**
* Render an Hemisphere light with current cursor light settings
*
* @param {string} secondColor Secondary color for the hemisphere light
* @param {object} params Parameters that can overwrite the cursor attrib or add custom attrib
*/
hemisphereLight = (secondColor="red",params) => {
let base = {
id: "lgt" + this.genNewId(),
light: {
state:`
type: hemisphere;
intensity: ${this.cursor.light.intensity};
color: ${this.cursor.color};
groundColor: ${secondColor};`,
type: "hemisphere",
secondColor: secondColor
},
color: this.cursor.color,
position: this.cursor.position,
scale: {x:1,y:1,z:1},
rotation: this.cursor.rotation,
};
return this.mergeProps(base, params);
}
/**
* Set the extent of the spotlight
*
* @param {number} beamAngle Angle in degree
*/
setBeamAngle=(beamAngle = 60)=>{
if(typeof beamAngle === "number"){
this.cursor.light.beamAngle = beamAngle;
}else{
console.error("must pass a numeric for setBeamAngle");
}
}
/**
* Set the decay of the light
*
* @param {number} decay Rate of light decay
*/
setDecay = (decay = 0.0) =>{
if(typeof decay === "number"){
this.cursor.light.decay = decay;
}else{
console.error("must pass a numeric for setDecay");
}
}
/**
* Set the distance of the light dims as it travels
*
* @param {number} distance Length which light dims
*/
setDistance = (distance=0.0) =>{
if(typeof distance === "number"){
this.cursor.light.distance = distance;
}else{
console.error("must pass a numeric for setDistance");
}
}
/**
* Set the intensity of the light
*
* @param {number} intensity Number range from 0 to 10+
*/
setIntensity = (intensity = 1.0) =>{
if(typeof intensity === "number"){
this.cursor.light.intensity = intensity;
}else{
console.error("must pass a numeric for setIntesity");
}
}
/**
* Set the diffusion(penumbra) around the edge of the light
*
* @param {number} diffusion Number range from 0 to 1
*/
setDiffusion = (diffusion = 0.0) => {
if(typeof diffusion === "number"){
this.cursor.light.diffusion = diffusion;
}else{
console.error("must pass a numeric for setDiffusion");
}
}
/**
* Set the position of where the lights are facing towards
*
* @param {number} x X Position
* @param {number} y Y Position
* @param {number} z Z Position
*/
setLightTarget = (x = 0, y = 0, z = 0) => {
if(typeof x === "number" && typeof y === "number" && typeof z === "number"){
this.cursor.light.target = {
x: x,
y: y,
z: z
};
}else{
console.error("must pass a numeric for setLightTarget");
}
}
/********************* ANIMATIONS *********************/
/**
* Apply a spin animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
spin = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.spin;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: rotation;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
easing: linear;
to: ${el.rotation.x} ${el.rotation.y + magnitude} ${el.rotation.z};
`;
el.animation__spin = anim;
return outerElId;
};
/**
* Apply a spin animation on the X axis to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
* @param {string} spinType Direction of the animation
*/
spinX = (outerElId, spinType, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.spin;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: rotation;
dir: ${spinType};
dur: ${duration};
loop: ${Boolean(loop)};
easing: linear;
to: ${el.rotation.x + magnitude} ${el.rotation.y} ${el.rotation.z};
`;
el.animation__spin = anim;
return outerElId;
};
/**
* Apply a spin animation on the X axis to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
* @param {string} spinType Direction of the animation
*/
spinY = (outerElId, spinType, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.spin;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: rotation;
dir: ${spinType};
dur: ${duration};
loop: ${Boolean(loop)};
easing: linear;
to: ${el.rotation.x} ${el.rotation.y + magnitude} ${el.rotation.z};
`;
el.animation__spin = anim;
return outerElId;
};
/**
* Apply a spin animation on the Z axis to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
* @param {string} spinType Direction of the animation
*/
spinZ = (outerElId, spinType, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.spin;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: rotation;
dir: ${spinType};
dur: ${duration};
loop: ${Boolean(loop)};
easing: linear;
to: ${el.rotation.x} ${el.rotation.y} ${el.rotation.z + magnitude};
`;
el.animation__spin = anim;
return outerElId;
};
/**
* Apply a yoyo animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
yoyo = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x} ${el.position.y + magnitude} ${el.position.z};
`;
el.animation__yoyo = anim;
return outerElId;
};
/**
* Apply a sideToSide animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
sideToSide = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
property: position;
to: ${el.position.x + magnitude} ${el.position.y} ${el.position.z};
`;
el.position = { ...el.position, x: el.position.x - magnitude };
el.animation__sidetoside = anim;
return outerElId;
};
/**
* Apply a goUp animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
goUp = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x} ${el.position.y + magnitude} ${el.position.z};
`;
el.animation__goup = anim;
return outerElId;
};
/**
* Apply a goDown animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
goDown = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x} ${el.position.y - magnitude} ${el.position.z};
`;
el.animation__godown = anim;
return outerElId;
};
/**
* Apply a goLeft animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
goLeft = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x - magnitude} ${el.position.y} ${el.position.z};
`;
el.animation__goleft = anim;
return outerElId;
};
/**
* Apply a goRight animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
goRight = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x + magnitude} ${el.position.y} ${el.position.z};
`;
el.animation__goright = anim;
return outerElId;
};
/**
* Apply a goTowards animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
goTowards = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x} ${el.position.y} ${el.position.z + magnitude};
`;
el.animation__goleft = anim;
return outerElId;
};
/**
* Apply a goAway animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
goAway = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: position;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.position.x} ${el.position.y} ${el.position.z - magnitude};
`;
el.animation__goaway = anim;
return outerElId;
};
/**
* Apply a grow animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
grow = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
if(el.id.includes("lgt")){
let type = el.light.state.split(/\s|;/).filter(Boolean)[1];
let anim;
if(type === "spot"){
anim = `
property: light.angle;
from: 60;
to: ${magnitude};
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(loop)};
`;
}else if(type === "point"){
anim =`
property: light.distance;
from: 10;
to: ${magnitude};
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(loop)};
`;
}
el.animation__fadein = anim;
return outerElId;
}
let anim = `
property: scale;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.scale.x * magnitude} ${el.scale.y * magnitude} ${el.scale.z * magnitude};
`;
el.animation__grow = anim;
return outerElId;
};
/**
* Apply a shrink animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
shrink = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
if(el.id.includes("lgt")){
let type = el.light.state.split(/\s|;/).filter(Boolean)[1];
let anim;
if(type === "spot"){
anim = `
property: light.angle;
from: ${magnitude};
to: 10;
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(loop)};
`;
}else if(type === "point"){
anim =`
property: light.distance;
from:${magnitude};
to: 10;
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(loop)};
`;
}
el.animation__fadein = anim;
return outerElId;
}
let anim = `
property: scale;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
to: ${el.scale.x / magnitude} ${el.scale.y / magnitude} ${el.scale.z / magnitude};
`;
el.animation__shrink = anim;
return outerElId;
};
/**
* Apply a fadeOut animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
fadeOut = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.fadeOut;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
//if the element is light. Unlike normal object, this will alter intensity of light
if(el.id.includes("lgt")){
let anim = `
property: light.intensity;
from: 1;
to: ${magnitude};
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(loop)};
`;
el.animation__fadein = anim;
return outerElId;
}
let anim = `
property: components.material.material.opacity;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
isRawProperty: true;
from: 1;
to: ${magnitude};
`;
el.material = el.material + "; transparent: true;";
el.animation__fadeout = anim;
return outerElId;
}
/**
* Apply a fadeIn animation to the Aframe element which is passed as arg
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
fadeIn = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.general;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
//if the element is light. Unlike normal object, this will alter intensity of light
if(el.id.includes("lgt")){
let anim = `
property: light.intensity;
from: 0;
to: ${magnitude};
dur: ${duration};
dir: alternate;
loop: ${Boolean(loop)};
`;
el.animation__fadein = anim;
return outerElId;
}
let anim = `
property: components.material.material.opacity;
dir: alternate;
dur: ${duration};
loop: ${Boolean(loop)};
isRawProperty: true;
from: 0;
to: ${magnitude};
`;
el.material = el.material + "; transparent: true;";
el.animation__fadein = anim;
return outerElId;
}
/**
* Apply a colorShift animation toe the Aframe element
*
* @param {string} outerElId target element id
* @param {string} color Color the light shift it to
*/
colorShift = (outerElId, color) => {
let el = this.getEl(outerElId);
//if the element is light
if(String(el.id).includes("lgt")){
let split = el.light.state.split(/\s|;/).filter(Boolean);
let colorIndex = split.indexOf("color:")+1;
let baseCol = split[colorIndex];
if(this.colourNameToHex(baseCol)!==false){
baseCol = this.colourNameToHex(baseCol);
}
if(this.colourNameToHex(color) !== false){
color = this.colourNameToHex(color);
}
let anim = `property: light.color;
from: ${baseCol};
to: ${color};
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(this.cursor.loop)};
type: color;`;
el.animation__color = anim;
return outerElId;
}
//if the element is group
if (String(el.id).includes("grp")) {
for (let i in el.els) {
let innerEl = el.els[i];
//innerEl.material.split(/\s|;/) returns an array of strings separated by " " and ";",
//color is always its first attribute (after "color: ")
let anim = `property: components.material.material.color;
from: ${(innerEl.material.split(/\s|;/))[1]};
to: ${color};
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(this.cursor.loop)};
isRawProperty: true;
type: color;`;
innerEl.animation__color = anim;
}
return outerElId;
}
const mat = el.material.split(/\s|;/);
let anim = `property: components.material.material.color;
from: ${mat[mat.indexOf("color:")+1]};
to: ${color};
dur: ${this.cursor.duration};
dir: alternate;
loop: ${Boolean(this.cursor.loop)};
isRawProperty: true;
type: color;`;
el.animation__color = anim;
return outerElId;
}
/**
* Change the html color code to the hexadecimal
*
* @param {string} colour HTML color code
*
* @returns {string} Hexadecimal representation of the color, return false if color not found.
*/
colourNameToHex=(colour)=>
{
const colours = {"aliceblue":"#f0f8ff","antiquewhite":"#faebd7","aqua":"#00ffff","aquamarine":"#7fffd4","azure":"#f0ffff",
"beige":"#f5f5dc","bisque":"#ffe4c4","black":"#000000","blanchedalmond":"#ffebcd","blue":"#0000ff","blueviolet":"#8a2be2","brown":"#a52a2a","burlywood":"#deb887",
"cadetblue":"#5f9ea0","chartreuse":"#7fff00","chocolate":"#d2691e","coral":"#ff7f50","cornflowerblue":"#6495ed","cornsilk":"#fff8dc","crimson":"#dc143c","cyan":"#00ffff",
"darkblue":"#00008b","darkcyan":"#008b8b","darkgoldenrod":"#b8860b","darkgray":"#a9a9a9","darkgreen":"#006400","darkkhaki":"#bdb76b","darkmagenta":"#8b008b","darkolivegreen":"#556b2f",
"darkorange":"#ff8c00","darkorchid":"#9932cc","darkred":"#8b0000","darksalmon":"#e9967a","darkseagreen":"#8fbc8f","darkslateblue":"#483d8b","darkslategray":"#2f4f4f","darkturquoise":"#00ced1",
"darkviolet":"#9400d3","deeppink":"#ff1493","deepskyblue":"#00bfff","dimgray":"#696969","dodgerblue":"#1e90ff",
"firebrick":"#b22222","floralwhite":"#fffaf0","forestgreen":"#228b22","fuchsia":"#ff00ff","gainsboro":"#dcdcdc","ghostwhite":"#f8f8ff","gold":"#ffd700","goldenrod":"#daa520","gray":"#808080","green":"#008000","greenyellow":"#adff2f",
"honeydew":"#f0fff0","hotpink":"#ff69b4","indianred ":"#cd5c5c","indigo":"#4b0082","ivory":"#fffff0","khaki":"#f0e68c",
"lavender":"#e6e6fa","lavenderblush":"#fff0f5","lawngreen":"#7cfc00","lemonchiffon":"#fffacd","lightblue":"#add8e6","lightcoral":"#f08080","lightcyan":"#e0ffff","lightgoldenrodyellow":"#fafad2",
"lightgrey":"#d3d3d3","lightgreen":"#90ee90","lightpink":"#ffb6c1","lightsalmon":"#ffa07a","lightseagreen":"#20b2aa","lightskyblue":"#87cefa","lightslategray":"#778899","lightsteelblue":"#b0c4de",
"lightyellow":"#ffffe0","lime":"#00ff00","limegreen":"#32cd32","linen":"#faf0e6","magenta":"#ff00ff","maroon":"#800000","mediumaquamarine":"#66cdaa","mediumblue":"#0000cd","mediumorchid":"#ba55d3","mediumpurple":"#9370d8","mediumseagreen":"#3cb371","mediumslateblue":"#7b68ee",
"mediumspringgreen":"#00fa9a","mediumturquoise":"#48d1cc","mediumvioletred":"#c71585","midnightblue":"#191970","mintcream":"#f5fffa","mistyrose":"#ffe4e1","moccasin":"#ffe4b5","navajowhite":"#ffdead","navy":"#000080",
"oldlace":"#fdf5e6","olive":"#808000","olivedrab":"#6b8e23","orange":"#ffa500","orangered":"#ff4500","orchid":"#da70d6",
"palegoldenrod":"#eee8aa","palegreen":"#98fb98","paleturquoise":"#afeeee","palevioletred":"#d87093","papayawhip":"#ffefd5","peachpuff":"#ffdab9","peru":"#cd853f","pink":"#ffc0cb","plum":"#dda0dd","powderblue":"#b0e0e6","purple":"#800080",
"rebeccapurple":"#663399","red":"#ff0000","rosybrown":"#bc8f8f","royalblue":"#4169e1","saddlebrown":"#8b4513","salmon":"#fa8072","sandybrown":"#f4a460","seagreen":"#2e8b57","seashell":"#fff5ee","sienna":"#a0522d","silver":"#c0c0c0","skyblue":"#87ceeb","slateblue":"#6a5acd","slategray":"#708090","snow":"#fffafa","springgreen":"#00ff7f","steelblue":"#4682b4",
"tan":"#d2b48c","teal":"#008080","thistle":"#d8bfd8","tomato":"#ff6347","turquoise":"#40e0d0","violet":"#ee82ee","wheat":"#f5deb3","white":"#ffffff","whitesmoke":"#f5f5f5","yellow":"#ffff00","yellowgreen":"#9acd32"};
if (typeof colours[colour.toLowerCase()] !== "undefined"){
return colours[colour.toLowerCase()];
}
return false;
}
/********************* GETTERS *********************/
/**
* Gets the current color of the cursor
*/
getColor = () => {
return this.cursor.color;
};
/**
* Gets the current texture of the cursor
*/
getTexture = () => {
let textures = TexturePack();
let textureTitle = [...textures.TexturePack.map(obj => obj.title)];
let textureURL = [...textures.TexturePack.map(obj => obj.url)];
let returnTexture = textureTitle[textureURL.indexOf(this.cursor.texture)];
if(returnTexture === undefined){
returnTexture = this.cursor.texture;
}
return returnTexture;
};
/**
* Gets the current x position of the cursor
*/
getXPos = () => {
return this.cursor.position.x;
};
/**
* Gets the current y position of the cursor
*/
getYPos = () => {
return this.cursor.position.y;
};
/**
* Gets the current z position of the cursor
*/
getZPos = () => {
return this.cursor.position.z;
};
/**
* Gets the current x scale of the cursor
*/
getXScale = () => {
return this.cursor.scale.x;
};
/**
* Gets the current y scale of the cursor
*/
getYScale = () => {
return this.cursor.scale.y;
};
/**
* Gets the current z scale of the cursor
*/
getZScale = () => {
return this.cursor.scale.z;
};
/**
* Gets the current x rotation of the cursor
*/
getXRotation = () => {
return this.cursor.rotation.x;
};
/**
* Gets the current y rotation of the cursor
*/
getYRotation = () => {
return this.cursor.rotation.y;
};
/**
* Gets the current z rotation of the cursor
*/
getZRotation = () => {
return this.cursor.rotation.z;
};
/**
* Gets the current radius of the cursor
*/
getRadius = () => {
return this.cursor.radius;
};
/**
* Gets the current phi length of the cursor
*/
getPhiLength = () => {
return this.cursor.phiLength;
};
/**
* Gets the current lopp of the cursor
*/
getLoop = () => {
return this.cursor.loop;
};
/**
* Gets the current duration of the cursor
*/
getDuration = () => {
return this.cursor.duration;
};
/**
* Gets the current magnitude of the cursor
*/
getMagnitude = () => {
return this.cursor.magnitude.general;
};
/**
* Gets the element associated with the given element ID
*
* @param {string} outerElId target element ID
*/
getEl = (outerElId) => {
if (outerElId.entity) {
outerElId = outerElId.id;
}
return this.els[outerElId];
}
/**
* This creates an entity w shape of object and merges with supplied params
*
* @param {string} shape one of the allowed arguments to this.core()
* @param {obj} params arguments to be merged, not guarenteed to be successful
*/
mergeProps = (entity, params) => {
let id = params && params.id ? params.id : entity.id;
if (!params || typeof params === "string") {
this.els[id] = entity;
} else {
this.els[id] = { ...entity, ...params };
}
return id;
}
/**
* Return a Entity that can be used to group elements together
*/
group = () => {
this.resetCursor();
let base = {
id: "grp" + this.genNewId(),
position: { ...this.cursor.position },
rotation: this.cursor.rotation,
scale: this.cursor.scale,
};
let entity = new Group(this, base.id);
this.els[base.id] = { ...base, ...entity.entObj() };
return entity;
}
/**
* Transfer the object from MYR to the Entity. Used to add entity to the group
*
* @param {string} id Entity id to remove from list and return
*/
transfer = (id) => {
let retVal = this.els[id];
delete this.els[id];
return retVal;
}
/*
* Functions that are not in reference or used in anywhere
*/
/**
* Animate the Aframe element which is passed as arg, its same as spin but without linear easing
*
* @param {number} outerElId target element ID
* @param {number} magnitude Magnitude of the animation
* @param {boolean} loop Whether to loop the animation
* @param {number} duration How long the animation last
*/
animate = (outerElId, magnitude = null, loop = null, duration = null) => {
magnitude = magnitude !== null ? magnitude : this.cursor.magnitude.spin;
loop = loop !== null ? loop : this.cursor.loop;
duration = duration !== null ? duration : this.cursor.duration;
let el = this.getEl(outerElId);
let anim = `
property: rotation;
dir: alternate;
to: ${el.rotation.x} ${el.rotation.y + magnitude} ${el.rotation.z};
dur: ${duration};
loop: ${Boolean(loop)};
`;
el.animation = anim;
return outerElId;
};
/**
* It looks like a promise that sleep for certain ms and user can set further action after the time out.
* I don't know the original purpose but I imagine it was something like this.
* ex.
* sleep(5000).then(()=>{
* console.log("box!");
* box();
* });
* But this will not render box on the scene because it adds the box to the list after Scene is renderered
*
* @param {number} ms how long to sleep in ms
* @returns
*/
sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Looks like temporary function similar to makePushable that apply force the pass object to x,y,z direction
* Not working.
*/
push = (outerElId, x, y, z) => {
// Add an event listener
document.addEventListener("myr-view-rendered", () => {
let el = document.querySelector("#" + outerElId);
if (!el) {
return;
}
el.addEventListener("body-loaded", () => {
el.body.applyImpulse(
/* impulse */ new CANNON.Vec3(x, y, z),
/* world position */ new CANNON.Vec3().copy(el.object3D.position)
);
});
});
return outerElId;
}
/**
* Looks like a temporary function that just apply physics with constant mass
*
* @param {string} outerElId
* @returns {string} outerElId
*/
drop = (outerElId) => {
this.getEl(outerElId)["dynamic-body"] = "shape: box; mass: 5";
return outerElId;
}
/**
* Interface for setting an object's parameters in the DOM
* the idea is the setup an event listener as an almost DOM ready listener.
*
* Ex. change(box(),"material","color:green;");
*
* @param {string} outerElId target element ID
* @param {string} type what param to change
* @param {*} newParam changes
* @returns {Error} Return error if query fail to retrieve the passed Id
*/
change = (outerElId, type, newParam) => {
document.addEventListener("myr-view-rendered", () => {
try {
let el = document.querySelector("#" + outerElId);
el.setAttribute(type, newParam);
} catch (error) {
return Error("change() failed execution" +
"Ensure you are passing the proper id to the method" +
`Error msg: ${error}`);
}
});
}
/**
* Interface for setting an object's parameters in the DOM w/o the event listener dispatching the events
* It's not working/I don't know how this work"
*
* @param {string} outerElId target element ID
* @param {string} type what param to change
* @param {*} newParam changes
* @returns {Error} Return error if query fail to retrieve the passed Id
*/
syncChange = (outerElId, type, newParam) => {
try {
let el = document.querySelector("#" + outerElId);
el.setAttribute(type, newParam);
} catch (error) {
let err = Error("syncChange() failed execution\n" +
"Ensure you are passing the proper id to the method" +
`Error msg: ${error}`);
console.error(err);
return err;
}
}
/**
* Infinite loop detector that's not use in anywhere
*/
infiniteLoopDetector = (function () {
let map = {};
// define an InfiniteLoopError class
function InfiniteLoopError(msg) {
Error.call(this, msg);
this.type = "InfiniteLoopError";
}
function infiniteLoopDetector(id) {
if (id in map) {
if (Date.now() - map[id] > 200) {
delete map[id];
throw new Error("Loop runing too long!", InfiniteLoopError);
}
} else {
map[id] = Date.now();
}
}
infiniteLoopDetector.wrap = function (codeStr) {
if (typeof codeStr !== "string") {
throw new Error("Input type must be a string");
}
// this is not a strong regex, but enough to use at the time
return codeStr.replace(/for *\(.*\{|while *\(.*\{|do *\{/g, function (loopHead) {
let id = parseInt(Math.random() * Number.MAX_SAFE_INTEGER);
return `infiniteLoopDetector(${id});${loopHead}infiniteLoopDetector(${id});`;
});
};
infiniteLoopDetector.unwrap = function (codeStr) {
return codeStr.replace(/infiniteLoopDetector\([0-9]*?\);/g, "");
};
return infiniteLoopDetector;
}());
}
export default Myr;