diff --git a/.circleci/config.yml b/.circleci/config.yml index 7df80e50..529c4d18 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,7 +14,7 @@ jobs: steps: - checkout - - run: + - run: name: Create firebase key file command: mkdir ~/MYR/src/keys && touch ~/MYR/src/keys/firebase.js && echo -e "export const firebaseKey = \"$FIREBASE_KEY\";\nexport default firebaseKey;" > src/keys/firebase.js # Download and cache dependencies diff --git a/README.md b/README.md index 41c3726b..6b413e43 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ The Engaging Computing Group develops new technologies to enable learners—yout ## Status [![CircleCI](https://circleci.com/gh/engaging-computing/MYR.svg?style=shield)](https://circleci.com/gh/engaging-computing/MYR) -## Change Log - 2.1.0 -> 2.1.1 -- Updates Material UI to 4.11.2 -- Add esdoc for code documentation -- Adds a dependabot configuration file so it will make usable pull requests +## Change Log - 2.1.1 -> 2.2.0 +- Adds Lighting features to MYR +- Adds Texture features to MYR +- Updates copyright in the footer to be in the form `2018 - ` ## Acknowledgments diff --git a/package-lock.json b/package-lock.json index 875683dc..09494b61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1943,16 +1943,16 @@ "requires": { "@tweenjs/tween.js": "^16.8.0", "browserify-css": "^0.8.2", - "debug": "github:ngokevin/debug#noTimestamp", + "debug": "github:ngokevin/debug#ef5f8e66d49ce8bc64c6f282c15f8b7164409e3a", "deep-assign": "^2.0.0", - "document-register-element": "github:dmarcos/document-register-element#8ccc532b7", + "document-register-element": "github:dmarcos/document-register-element#8ccc532b7f3744be954574caf3072a5fd260ca90", "envify": "^3.4.1", "load-bmfont": "^1.2.3", "object-assign": "^4.0.1", "present": "0.0.6", "promise-polyfill": "^3.1.0", "style-attr": "^1.0.2", - "three": "github:supermedium/three.js#r90fixMTLLoader", + "three": "github:supermedium/three.js#5ef2887ab3621cae54fa129a500424d6caa25b62", "three-bmfont-text": "^2.1.0", "webvr-polyfill": "^0.10.5" }, @@ -1989,7 +1989,7 @@ "resolved": "https://registry.npmjs.org/aframe-physics-system/-/aframe-physics-system-3.3.0.tgz", "integrity": "sha512-kNZ13/94pNLVMCbKXo8Si8ibcocuhy5UmSji8T+3qgP/MkacFlpdBYVth3IwcZBKzAq7jXRS5RbTs5PrpySmLw==", "requires": { - "cannon": "github:donmccurdy/cannon.js#v0.6.2-dev1", + "cannon": "github:donmccurdy/cannon.js#022e8ba53fa83abf0ad8a0e4fd08623123838a17", "three-to-cannon": "^1.3.0", "webworkify": "^1.4.0" }, @@ -10957,8 +10957,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true + "dev": true }, "acorn-globals": { "version": "1.0.9", @@ -10994,8 +10993,7 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "optional": true + "dev": true }, "cssstyle": { "version": "0.2.37", @@ -15818,8 +15816,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "optional": true + "dev": true }, "pify": { "version": "2.3.0", @@ -26920,15 +26917,13 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true + "dev": true }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, - "optional": true, "requires": { "is-extglob": "^2.1.1" } @@ -26944,8 +26939,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true + "dev": true }, "readdirp": { "version": "3.4.0", diff --git a/package.json b/package.json index cc0e372e..370e83f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "myr", - "version": "2.1.0", + "version": "2.2.0", "private": false, "engines": { "node": "12.18.2" diff --git a/src/actions/courseActions.js b/src/actions/courseActions.js index d3c68793..c52f4eb4 100644 --- a/src/actions/courseActions.js +++ b/src/actions/courseActions.js @@ -63,6 +63,7 @@ export function fetchCourse(courseId) { } dispatch(loadLesson(json.lessons[0] || "")); + dispatch(sceneActions.loadSettings(json.lessons[0].settings || {})); dispatch(render(json.lessons[0].code || "")); dispatch(updateSavedText(json.lessons[0].code || "")); dispatch(sceneActions.setNameDesc( @@ -96,6 +97,8 @@ export function loadCourse(course) { export function fetchLesson(json) { return (dispatch) => { dispatch(loadLesson(json)); + dispatch(sceneActions.resetSettings()); + dispatch(sceneActions.loadSettings(json.settings || {})); dispatch(render(json.code || "")); dispatch(updateSavedText(json.code || "")); dispatch(sceneActions.nameScene(json.name)); diff --git a/src/actions/sceneActions.js b/src/actions/sceneActions.js index 22bf463e..3a3b0391 100644 --- a/src/actions/sceneActions.js +++ b/src/actions/sceneActions.js @@ -25,6 +25,16 @@ export function loadScene(data) { export function toggleCoordSky() { return { type: types.TOGGLE_COORD_SKY }; } +export function toggleDefaultLight() { + return { type: types.TOGGLE_DEFAULT_LIGHT }; +} +export function toggleCastShadow() { + return { type: types.TOGGLE_CAST_SHADOW }; +} + +export function toggleLightIndicator(){ + return {type: types.TOGGLE_LIGHT_INDICATOR}; +} export function changeSkyColor(color) { return { type: types.CHANGE_SKY_COLOR, color }; @@ -62,8 +72,12 @@ export function loadSettings(payload) { return { type: types.LOAD_SETTINGS, payload }; } -export function changeSetting(payload) { - return { type: types.LOAD_SETTINGS, payload }; +export function changeSettings(payload) { + return { type: types.CHANGE_SETTINGS, payload }; +} + +export function resetSettings() { + return { type: types.RESET_SETTINGS }; } export function addCollectionID(payload) { @@ -95,9 +109,13 @@ export default { toggleFly, toggleFloor, loadSettings, - changeSetting, + changeSettings, + resetSettings, addCollectionID, setDesc, setNameDesc, - removeCollectionID + removeCollectionID, + toggleDefaultLight, + toggleCastShadow, + toggleLightIndicator }; \ No newline at end of file diff --git a/src/components/editor/customCompleter.js b/src/components/editor/customCompleter.js index 85c1454d..44f0a381 100644 --- a/src/components/editor/customCompleter.js +++ b/src/components/editor/customCompleter.js @@ -1,4 +1,5 @@ import myrReference from "../../myr/reference.js"; +import myrTextures from "../structural/Textures.js"; export const customCompleter = { getCompletions: function (editor, session, pos, prefix, callback) { @@ -34,10 +35,16 @@ export const customCompleter = { "static" ]; + let texture = myrTextures(); + let Texture = [...texture.TexturePack.map(obj => obj.title), + "group()" + ]; + let reference = myrReference(); let keyWords = [...reference.geometry.map(obj => obj.name + "()"), ...reference.transformations.map(obj => obj.name + "()"), ...reference.animations.map(obj => obj.name + "()"), + ...reference.lights.map(obj=>obj.name+"()"), "group()" ]; @@ -218,6 +225,15 @@ export const customCompleter = { score: 0 }; })); + + callback(null, Texture.map(function (word) { + return { + caption: word, + value: word, + meta: "texture", + score: 3 + }; + })); } }; diff --git a/src/components/layouts/TextureReference.js b/src/components/layouts/TextureReference.js new file mode 100644 index 00000000..347148ea --- /dev/null +++ b/src/components/layouts/TextureReference.js @@ -0,0 +1,35 @@ +import React from "react"; +import Header from "../structural/header/Header"; +import Footer from "../structural/Footer"; +import TextureReferencePage from "../reference/TextureReferencePage"; + +import * as layoutTypes from "../../constants/LayoutTypes.js"; + +export const TextureReference = ({ editor, editorActions, user, authActions, scene, sceneActions, projectActions, courseActions, projects, courses, match, collectionActions, collections }) => ( +
+
+
+ +
+
+
+); + +export default TextureReference; \ No newline at end of file diff --git a/src/components/reference/Reference.js b/src/components/reference/Reference.js index d810a913..869da0ef 100644 --- a/src/components/reference/Reference.js +++ b/src/components/reference/Reference.js @@ -196,6 +196,14 @@ export default class Reference extends React.Component { } value='d' /> + highlight} + label={ + +
LIGHT
+
+ } + value='e' /> {/*open_in_new} @@ -238,6 +246,10 @@ export default class Reference extends React.Component {
{this.TableEx("groups")}
} + {this.state.value === "e" && +
+ {this.TableEx("lights")} +
} : null} diff --git a/src/components/reference/ReferencePage.js b/src/components/reference/ReferencePage.js index 6d12bb50..2f33551d 100644 --- a/src/components/reference/ReferencePage.js +++ b/src/components/reference/ReferencePage.js @@ -135,6 +135,14 @@ export default class Reference extends React.Component { } value='d' /> + highlights} + label={ + +
LIGHTS
+
+ } + value='e' /> {

Key: array @@ -160,6 +168,10 @@ export default class Reference extends React.Component {

{this.TableEx("groups")}
} + {this.state.value === "e" && +
+ {this.TableEx("lights")} +
}
); } diff --git a/src/components/reference/TextureReference.js b/src/components/reference/TextureReference.js new file mode 100644 index 00000000..a58b1edd --- /dev/null +++ b/src/components/reference/TextureReference.js @@ -0,0 +1,204 @@ +import React from "react"; +import myrReference from "../../myr/textureReference"; + +import * as layoutTypes from "../../constants/LayoutTypes"; + +import { + Tabs, + Tab, + IconButton, + Drawer, + Icon, + Table, + TableBody, + TableHead, + TableRow, + TableCell, + Tooltip, + Hidden +} from "@material-ui/core"; + +const exitBtnStyle = { + //paddingbottom: 100, + position: "fixed", + top: 0, + right: 10, +}; +const newTabStyle = { + position: "fixed", + top: 0, + right: 50, +}; +export default class TextureReference extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: "a", + }; + this.tableData = myrReference(); + } + + handleChange = (event, value) => { + this.setState({ value }); + }; + + TableEx = (category) => { + + return ( + + + + Name + Description + Example + + + + {this.tableData[category].map((row, index) => ( + + {row.name} + {row.description} + {this.exampleHelper(row.example)} + + ))} + +
+ ); + }; + + render() { + const style = { + margin: 2, + padding: 0, + color: "#fff", + }; + const isDisabled = this.props.layoutType === layoutTypes.REFERENCE; + return ( +
+ {!isDisabled ? + + + + help + + + + +
+

MYR API - Reference

+ { + this.props.handleReferenceToggle(); + this.setState({ value: "a" }); + }}> + close + + + open_in_new + +
+ +
+ + category} + label={ + +
GEOMETRY
+
+ } + value='a' /> + bubble_chart} + label={ + +
TRANSFORMATIONS
+
+ } + value='b' /> + zoom_out_map} //swap_horiz control_camera category + label={ + +
ANIMATIONS
+
+ } + value='c' /> + widgets} + label={ + +
GROUPS
+
+ } + value='d' /> + {/*open_in_new} + label="OPEN IN NEW TAB" + value='n' + onClick={this.handleOpen} /> + close} + label="CLOSE" + value='x' + onClick={() => { + this.props.handleReferenceToggle(); + this.setState({ value: "a" }); + }} />*/} +
+
+ + {
+

Key: array + bool + number + string + group + data

+
} + {this.state.value === "a" && +
+ {this.TableEx("geometry")} +
} + {this.state.value === "b" && +
+ {this.TableEx("transformations")} +
} + {this.state.value === "c" && +
+ {this.TableEx("animations")} +
} + {this.state.value === "d" && +
+ {this.TableEx("groups")} +
} +
+
: null} +
+ ); + } +} diff --git a/src/components/reference/TextureReferencePage.js b/src/components/reference/TextureReferencePage.js new file mode 100644 index 00000000..5619d664 --- /dev/null +++ b/src/components/reference/TextureReferencePage.js @@ -0,0 +1,120 @@ +import React from "react"; +import myrReference from "../../myr/textureReference"; + +import TexturePack from "../structural/Textures.js"; + +import { + Tabs, + Tab, + Icon, + IconButton, + Table, + TableBody, + TableHead, + TableRow, + TableCell, + Hidden, +} from "@material-ui/core"; + +import "../../css/TextureReferencePage.css"; + +export default class TextureReference extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: "a", + }; + this.tableData = myrReference(); + } + + handleToggle = () => this.setState({ open: !this.state.open, value: "a" }); + + handleChange = (event, value) => { + this.setState({ value }); + }; + + imageHelper = (image) => { + if(image) { + let textures = TexturePack(); + let textureTitle = [...textures.TexturePack.map(obj => obj.title)]; + let textureURL = [...textures.TexturePack.map(obj => obj.url)]; + + let texture = textureURL[textureTitle.indexOf(image)]; + + return ( + texture + ); + } else { + return null; + } + } + + exampleHelper = (example) => { + if (example) { + let link = "/textureReference/" + example; + return ( + + link + + ); + } else { + return null; + } + }; + + TableEx = (category) => { + + return ( + + + + Name + + Image + Example + + + + {this.tableData[category].map((row, index) => ( + + {row.name} + + + alt + + {this.exampleHelper(row.example)} + + ))} + +
+ ); + }; + + render() { + return ( +
+ + texture} + label={ + +
TEXTURE
+
+ } + value='a' /> +
+ {this.state.value === "a" && +
+ {this.TableEx("texture")} +
} +
+ ); + } +} \ No newline at end of file diff --git a/src/components/structural/Footer.js b/src/components/structural/Footer.js index 1b684607..876b75dd 100644 --- a/src/components/structural/Footer.js +++ b/src/components/structural/Footer.js @@ -23,12 +23,12 @@ class Footer extends Component {
-
© 2020 -  +
© 2018 - {new Date().getFullYear()}  University of Massachusetts Lowell, Engaging Computing Group
-
© 2020 -
+
diff --git a/src/components/structural/Textures.js b/src/components/structural/Textures.js new file mode 100644 index 00000000..8abdd976 --- /dev/null +++ b/src/components/structural/Textures.js @@ -0,0 +1,28 @@ +let TexturePack = [ + {title:"bricks",url:"/img/textures/bricks.jpg"}, + {title:"bark",url:"/img/textures/bark.jpg"}, + {title:"checkerboard",url:"/img/textures/checkerboard.png"}, + {title:"chevron",url:"/img/textures/chevron.jpg"}, + {title:"cobblestone",url:"/img/textures/cobblestone.jpg"}, + {title:"dirt",url:"/img/textures/dirt.jpg"}, + {title:"fabric",url:"/img/textures/fabric.jpg"}, + {title:"grass",url:"/img/textures/grass.jpg"}, + {title:"lava",url:"/img/textures/lava.jpg"}, + {title:"leaves",url:"/img/textures/leaves.jpg"}, + {title:"marble",url:"/img/textures/marble.jpg"}, + {title:"metal",url:"/img/textures/metal.jpg"}, + {title:"paint",url:"/img/textures/paint.jpg"}, + {title:"rug",url:"/img/textures/rug.jpg"}, + {title:"sand",url:"/img/textures/sand.jpg"}, + {title:"stone",url:"/img/textures/stone.jpg"}, + {title:"water",url:"/img/textures/water.jpg"}, + {title:"wood",url:"/img/textures/wood.jpg"} +]; + +const texture = { + TexturePack: TexturePack, +}; + +export default function t(tex = texture) { + return tex; +} diff --git a/src/components/structural/View.js b/src/components/structural/View.js index 3e0a69e3..658117d1 100644 --- a/src/components/structural/View.js +++ b/src/components/structural/View.js @@ -59,6 +59,7 @@ class View extends Component { let el = document.getElementById("rig"); el.components["movement-controls"].velocity = new THREE.Vector3(0, 0, 0); } + } componentWillUnmount() { @@ -110,16 +111,86 @@ class View extends Component { ); } + if(ent.light){ + delete flattened.light; + let target=null,indicator=null; + if(ent.light.target){ + //Since the target will override the rotation, we want to delete the rotation attribute so it won't have an effect on the rotation of indicator. + delete flattened.rotation; + ent.light.state +="target: #lighttarget;"; + let target_position= `${ent.light.target.x || 0} ${ent.light.target.y || 0} ${ent.light.target.z || 0}`; + target = ; + } + if(this.props.sceneConfig.settings.lightIndicator){ + indicator = this.lightIndicatorHelper(ent); + } + if(this.props.sceneConfig.settings.castShadow){ + ent.light.state += this.lightShadowHelper(ent.light); + } + return [{indicator},target]; + } + let shadow; + if(this.props.sceneConfig.settings.castShadow){ + shadow = "cast:true; receive:true;"; + }else{ + shadow = "cast:false; receive:false;"; + } + if (ent.text) { delete flattened.text; // this takes care of a warning, may not be necessary return ; } if (ent.tube) { - return ; + return ; } - return ; + return ; } } + //return elements that contains necessary configuration for light indicator based on light's type and properties + lightIndicatorHelper =(ent)=>{ + + //this is a position for passing in to indicatorroation to determine the rotation of the light that use position as vector. + let position =`position:${ent.position.x || 0} ${ent.position.y || 0} ${ent.position.z || 0};`; + if(ent.light.target){ + position += `target:${ent.light.target.x || 0} ${ent.light.target.y || 0} ${ent.light.target.z || 0};`; + } + + //ambient light doesn't have an indicator + switch(ent.light.type){ + case "point": + return ; + case "spot": + let target = true; + if(!ent.light.target) { + position = ""; + target = false; + } + return ; + case "directional": + return ; + case "hemisphere": + return ; + default: + } + } + //return string that contains necessary configuration for shadow based on light's type + lightShadowHelper = (light) =>{ + let newState = ""; + //ambient and hemisphere light doesn't cast shadow + if(light.type !== "ambient" && light.type !== "hemisphere"){ + newState += "castShadow:true; shadowMapHeight:2000; shadowMapWidth:2000;"; + if(light.type === "spot"){ + newState += "shadowBias: -0.02; shadowCameraNear: 7;"; + }else if(light.type ==="directional"){ + newState += "shadowCameraNear: -40; shadowBias: -0.002; shadowCameraTop: 40; shadowCameraBottom: -40; shadowCameraLeft: -40; shadowCameraRight: 40;"; + }else if(light.type === "point"){ + newState += "shadowCameraFar: 25; shadowBias: -0.02;"; + } + }else{ + newState += "castShadow: false;"; + } + return newState; + } assetsHelper = (asset) => { return ( @@ -180,30 +251,32 @@ class View extends Component { if (this.props.sceneConfig.settings.showCoordHelper) { return ( - - - + + + + value="- X X +" + layer="type:text;layer:1;"> - + value="+ Z Z -" + layer="type:text;layer:1;"> + value=" Y + " + layer="type:text;layer:1;"> ); } else { @@ -214,12 +287,15 @@ class View extends Component { makeFloor = () => { if (this.props.sceneConfig.settings.showFloor) { return ( - ); } else { @@ -231,7 +307,7 @@ class View extends Component { /* eslint-disable */ return ( !this.state.welcomeOpen ? - + @@ -242,11 +318,25 @@ class View extends Component { + {this.props.sceneConfig.settings.defaultLight ? + + + + + : null + } + {this.props.sceneConfig.settings.lightIndicator||this.props.sceneConfig.settings.showCoordHelper ? + + + + + : null} { // create the entities Object.keys(this.props.objects).map(it => { return this.helper(this.props.objects[it]); }) } + {this.props.sceneConfig.settings.camConfig === 1 ? diff --git a/src/components/structural/header/SceneConfigMenu.js b/src/components/structural/header/SceneConfigMenu.js index 2c115c70..c9cfc549 100644 --- a/src/components/structural/header/SceneConfigMenu.js +++ b/src/components/structural/header/SceneConfigMenu.js @@ -95,6 +95,9 @@ class ConfigModal extends Component { pwProtectOpen: false, shareOpen: false, addClassOpen: false, + defaultLight: true, + castShadow: false, + spawnLightIndicator: false, email: "", sendTo: [], collectionID: "", @@ -310,6 +313,66 @@ class ConfigModal extends Component { ); }; + defaultLightToggle = () =>{ + let style = this.props.scene.settings.defaultLight ? btnStyle.on : btnStyle.off; + style = { ...btnStyle.base, ...style }; + return ( + { + this.props.handleRender(); + this.props.sceneActions.toggleDefaultLight(); + this.setState({ settingsChanged: true }); + }} > + { + this.props.scene.settings.defaultLight + ? toggle_on + : toggle_off + } + Default Light + + ); + } + castShadowToggle = () => { + let style = this.props.scene.settings.castShadow ? btnStyle.on : btnStyle.off; + style = { ...btnStyle.base, ...style }; + return ( + { + this.props.handleRender(); + this.props.sceneActions.toggleCastShadow(); + this.setState({ settingsChanged: true }); + }} > + { + this.props.scene.settings.castShadow + ? toggle_on + : toggle_off + } + Cast Shadow + + ); + } + lightIndicatorToggle = () => { + let style = this.props.scene.settings.lightIndicator ? btnStyle.on : btnStyle.off; + style = { ...btnStyle.base, ...style }; + return ( + { + this.props.handleRender(); + this.props.sceneActions.toggleLightIndicator(); + this.setState({ settingsChanged: true }); + }} > + { + this.props.scene.settings.lightIndicator + ? toggle_on + : toggle_off + } + Light Indicator + + ); + } /** * Toggles the floor on and off @@ -492,6 +555,14 @@ class ConfigModal extends Component {
+
Light Control
+
+ + +
+
+ +
Camera Control
diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js index b04d035e..614467f3 100644 --- a/src/constants/ActionTypes.js +++ b/src/constants/ActionTypes.js @@ -36,11 +36,15 @@ export const CHANGE_VIEW = "CHANGE_VIEW"; export const TOGGLE_FLY = "TOGGLE_FLY"; export const TOGGLE_FLOOR = "TOGGLE_FLOOR"; export const LOAD_SETTINGS = "LOAD_SETTINGS"; -export const CHANGE_SETTING = "CHANGE_SETTING"; +export const CHANGE_SETTINGS = "CHANGE_SETTINGS"; +export const RESET_SETTINGS = "RESET_SETTINGS"; export const ADD_CLASSROOM = "ADD_CLASSROOM"; export const REMOVE_CLASSROOM = "REMOVE_CLASSROOM"; export const SET_DESC = "SET_DESC"; export const SET_NAME_DESC = "SET_NAME_DESC"; +export const TOGGLE_DEFAULT_LIGHT = "TOGGLE_DEFAULT_LIGHT"; +export const TOGGLE_CAST_SHADOW = "TOGGLE_CAST_SHADOW"; +export const TOGGLE_LIGHT_INDICATOR = "TOGGLE_LIGHT_INDICATOR"; export const SYNC_CLASSES = "SYNC_CLASSES"; export const SYNC_CLASS = "SYNC_CLASS"; diff --git a/src/constants/LayoutTypes.js b/src/constants/LayoutTypes.js index 16e68c83..95691bbf 100644 --- a/src/constants/LayoutTypes.js +++ b/src/constants/LayoutTypes.js @@ -1,4 +1,5 @@ export const REFERENCE = "REFERENCE"; +export const TEXTURE_REFERENCE = "TEXTURE_REFERENCE"; export const IDE = "IDE"; export const CLASSROOM = "CLASSROOM"; export const GUIDED = "GUIDED"; diff --git a/src/containers/TextureReference.js b/src/containers/TextureReference.js new file mode 100644 index 00000000..d45697ac --- /dev/null +++ b/src/containers/TextureReference.js @@ -0,0 +1,39 @@ +import PropTypes from "prop-types"; +import TextureReferencePage from "../components/layouts/TextureReference.js"; +import * as Actions from "../actions"; + +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; + +// This makes sure we are getting what we think we should +TextureReferencePage.propTypes = { + editor: PropTypes.object.isRequired, + user: PropTypes.object, + scene: PropTypes.object.isRequired, +}; + +// This makes the values accessible as props +const mapStateToProps = state => ({ + editor: state.editor, + user: state.user.user, + scene: state.scene, + projects: state.project, + courses: state.courses, + collections: state.collections +}); + +// This maps dispatch actions to props +const mapDispatchToProps = dispatch => ({ + editorActions: bindActionCreators(Actions.EditorActions, dispatch), + authActions: bindActionCreators(Actions.AuthActions, dispatch), + sceneActions: bindActionCreators(Actions.SceneActions, dispatch), + projectActions: bindActionCreators(Actions.ProjectActions, dispatch), + courseActions: bindActionCreators(Actions.CourseActions, dispatch), + collectionActions: bindActionCreators(Actions.CollectionActions, dispatch) +}); + +// This does the binding to the redux store +export default connect( + mapStateToProps, + mapDispatchToProps +)(TextureReferencePage); diff --git a/src/css/TextureReferencePage.css b/src/css/TextureReferencePage.css new file mode 100644 index 00000000..f2b0a685 --- /dev/null +++ b/src/css/TextureReferencePage.css @@ -0,0 +1,46 @@ +#textureReference-page { + background: #fff; + min-height: 94vh; + width: 100% +} + +#ref-ex { + padding: 10px !important; + background: #fff !important; +} + +.ref-ex-edit div#ace-editor { + height: 69.5vh !important; +} + +#ref-ex p { + height: 12vh; + margin: 0; + overflow: auto; +} + +#ref-ex fieldset { + height: 16vh; + margin: 0; +} + +.ref-ex-btn .material-icons { + margin: 0 .3em; +} + +a.ref-ex-btn:hover { + color: #fff; +} + +.ref-ex-btn { + margin-top: 1em !important; + margin-bottom: 10px !important; + justify-content: center !important; + padding-left: 1em !important; + width: 100% !important; + text-align: center !important; +} + +#suggested-course { + padding-right: 2em; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 33fb0686..d39c89b8 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import React from "react"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import "./utils/AframeReg"; +import "./utils/AframeRegIndicator"; import "bootstrap/dist/css/bootstrap.css"; import "./css/App.css"; diff --git a/src/myr/Myr.js b/src/myr/Myr.js index 178aec5c..9a8fc443 100644 --- a/src/myr/Myr.js +++ b/src/myr/Myr.js @@ -2,6 +2,7 @@ import "aframe"; import "aframe-physics-system"; import Group from "./Group"; import CANNON from "cannon"; +import TexturePack from "../components/structural/Textures.js"; class Myr { constructor(baseEls) { @@ -13,6 +14,7 @@ class Myr { this.sceneEl = document.querySelector("a-scene"); this.cursor = { color: "red", + texture: "", transparency: 0, position: { x: 0, @@ -32,11 +34,20 @@ class Myr { radius: "1", phiLength: 360, loop: true, + textureColoring: false, 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) { @@ -81,6 +92,7 @@ class Myr { this.id = 0; this.cursor = { color: "red", + texture: "", transparency: 0, position: { x: 0, @@ -100,12 +112,21 @@ class Myr { radius: "1", phiLength: 360, loop: true, + textureColoring: false, 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 = []; @@ -116,7 +137,6 @@ class Myr { } } - /********************* TRANSFORMATIONS *********************/ /** @@ -125,6 +145,7 @@ class Myr { resetCursor = () => { this.cursor = { color: "red", + texture: "", transparency: 0, position: { x: 0, @@ -144,15 +165,72 @@ class Myr { radius: "1", phiLength: 360, loop: true, + textureColoring: false, 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 + } }; } + 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, + }; + }; + + resetAnimationCursor = () => { + this.cursor = { + ...this.cursor, + loop: true, + duration: 1000, + magnitude: { + spin: 360, + fadeOut: 0, + general: 1 + } + }; + }; + + resetLightCursor = () => { + this.cursor.light = { + intensity: 1.0, + beamAngle: 60, + diffusion: 0.0, + decay: 1, + distance: 0.0, + target: null + }; + }; + genNewId = () => { return this.counter++; }; @@ -242,7 +320,6 @@ class Myr { * @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, @@ -401,6 +478,9 @@ class Myr { case "color": this.setColor(value); break; + case "texture": + this.setTexture(value); + break; case "position": this.setPosition(value.x, value.y, value.z); break; @@ -560,6 +640,31 @@ class Myr { return this.cursor.color; } + setTexture = (texture = "") => { + 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)] !== undefined) { + this.cursor.texture = textureURL[textureTitle.indexOf(texture)]; + } + else if(urlregex.test(texture) || (texture === "")) { + this.cursor.texture = texture; + } + else { + console.error("Not a usable texture or URL."); + this.cursor.texture = ""; + } + + return this.cursor.texture; + } + + setTextureColoring = (i) => { + this.cursor.textureColoring = Boolean(i); + return this.cursor.textureColoring; + }; + setCursorAttribute = (key = "", value = "") => { if (typeof (key) !== "string" || key === "") { console.error("Error: Invalid key"); @@ -726,13 +831,14 @@ class Myr { */ box = (params) => { let base = { - geometry: "primitive: box;", id: "box" + this.genNewId(), - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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}; opacity: ${1 - this.cursor.transparency};`, }; + return this.mergeProps(base, params); } @@ -743,12 +849,12 @@ class Myr { */ circle = (params) => { let base = { - geometry: `primitive: circle; radius: ${this.cursor.radius}; theta-length: ${this.cursor.phiLength};`, id: "circ" + this.genNewId(), + geometry: `primitive: circle; radius: ${this.cursor.radius}; theta-length: ${this.cursor.phiLength};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -761,11 +867,11 @@ class Myr { cone = (params) => { let base = { id: "cone" + this.genNewId(), - geometry: `primitive: cone; radiusBottom: ${this.cursor.radius}; radiusTop: 0.1; opacity: ${1 - this.cursor.transparency};`, + geometry: `primitive: cone; radiusBottom: ${this.cursor.radius}; radiusTop: 0.1;`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double;`, + 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};`, }; return this.mergeProps(base, params); } @@ -778,11 +884,11 @@ class Myr { cylinder = (params) => { let base = { id: "cyl" + this.genNewId(), - geometry: `primitive: cylinder; radius: ${this.cursor.radius}; theta-length: ${this.cursor.phiLength}; opacity: ${1 - this.cursor.transparency};`, + geometry: `primitive: cylinder; radius: ${this.cursor.radius}; theta-length: ${this.cursor.phiLength};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double;`, + 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};`, }; return this.mergeProps(base, params); } @@ -798,9 +904,9 @@ class Myr { id: "dod" + this.genNewId(), geometry: `primitive: dodecahedron; radius: ${this.cursor.radius};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -815,9 +921,9 @@ class Myr { id: "iso" + this.genNewId(), geometry: "primitive: icosahedron;", position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -832,9 +938,9 @@ class Myr { id: "oct" + this.genNewId(), geometry: "primitive: octahedron;", position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -851,9 +957,9 @@ class Myr { radius: ".01", path: path, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double;`, + 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};`, }; return this.mergeProps(base, params); } @@ -866,11 +972,11 @@ class Myr { plane = (params) => { let base = { id: "plane" + this.genNewId(), - geometry: `primitive: plane; height: 1; width: 1; phi-length: ${this.cursor.phiLength}; opacity: ${1 - this.cursor.transparency};`, + geometry: `primitive: plane; height: 1; width: 1; phi-length: ${this.cursor.phiLength};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double;`, + 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};`, }; return this.mergeProps(base, params); } @@ -883,11 +989,11 @@ class Myr { polyhedron = (params) => { let base = { id: "poly" + this.genNewId(), - geometry: `primitive: sphere; segmentsWidth: 2; segmentsHeight: 8; phi-length: ${this.cursor.phiLength}; opacity: ${1 - this.cursor.transparency};`, + geometry: `primitive: sphere; segmentsWidth: 2; segmentsHeight: 8; phi-length: ${this.cursor.phiLength};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double;`, + 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};`, }; return this.mergeProps(base, params); } @@ -900,11 +1006,11 @@ class Myr { ring = (params) => { let base = { id: "ring" + this.genNewId(), - geometry: `primitive: ring; radiusInner: 0.5; radiusOuter: 1; theta-length: ${this.cursor.phiLength}; opacity: ${1 - this.cursor.transparency};`, + geometry: `primitive: ring; radiusInner: 0.5; radiusOuter: 1; theta-length: ${this.cursor.phiLength};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double;`, + 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};`, }; return this.mergeProps(base, params); } @@ -919,9 +1025,9 @@ class Myr { id: "sphere" + this.genNewId(), geometry: `primitive: sphere; phi-length: ${this.cursor.phiLength}`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -936,9 +1042,9 @@ class Myr { id: "tetra" + this.genNewId(), geometry: "primitive: tetrahedron;", position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -961,8 +1067,8 @@ class Myr { side: "double", color: this.cursor.color, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, + scale: this.cursor.scale, }; if (!params || typeof params === "string") { this.els[base.id] = { ...base }; @@ -982,9 +1088,9 @@ class Myr { id: "torus" + this.genNewId(), geometry: `primitive: torus; radius: ${this.cursor.radius}; radiusTubular: 0.5; arc: 360; arc: ${this.cursor.phiLength};`, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -999,11 +1105,11 @@ class Myr { id: "torKn" + this.genNewId(), geometry: "primitive: torusKnot;", position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, 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}; opacity: ${1 - this.cursor.transparency};`, }; return this.mergeProps(base, params); } @@ -1018,9 +1124,9 @@ class Myr { id: "tri" + this.genNewId(), geometry: "primitive: triangle;", position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } @@ -1037,28 +1143,169 @@ class Myr { radius: this.cursor.radius, path: path, position: this.cursor.position, - scale: this.cursor.scale, rotation: this.cursor.rotation, - material: `color: ${this.cursor.color}; side: double; opacity: ${1 - this.cursor.transparency};`, + 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};`, }; return this.mergeProps(base, params); } - /** - * Render an Aframe light with current Myr settings - * - * @param {*} params !!!DESCRIPTION NEEDED!!! - */ - light = () => { - let el = { - color: "lgt" + this.getRandomColor(), + 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, - geometry: { - primitive: "light" - }, - }; - this.els.push(el); - return el; + scale: {x:1,y:1,z:1}, + rotation: this.cursor.rotation, + }; + return this.mergeProps(base, params); + } + + 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); + } + + 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); + } + + 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); + } + + //secondColor == groundColor + 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); + } + + //beamAngle == angle + setBeamAngle=(beamAngle = 60)=>{ + if(typeof beamAngle === "number"){ + this.cursor.light.beamAngle = beamAngle; + }else{ + console.error("must pass a numeric for setBeamAngle"); + } + + } + setDecay = (decay = 0.0) =>{ + if(typeof decay === "number"){ + this.cursor.light.decay = decay; + }else{ + console.error("must pass a numeric for setDecay"); + } + } + + setDistance = (distance=0.0) =>{ + if(typeof distance === "number"){ + this.cursor.light.distance = distance; + }else{ + console.error("must pass a numeric for setDistance"); + } + } + setIntensity = (intensity = 1.0) =>{ + if(typeof intensity === "number"){ + this.cursor.light.intensity = intensity; + }else{ + console.error("must pass a numeric for setIntesity"); + } + } + + //diffusion == penumbra + setDiffusion = (diffusion = 0.0) => { + if(typeof diffusion === "number"){ + this.cursor.light.diffusion = diffusion; + }else{ + console.error("must pass a numeric for setDiffusion"); + } + } + + 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"); + } } // Prism is an alias for Polyhedron @@ -1067,7 +1314,6 @@ class Myr { // Cube is an alias for Box cube = this.box - /********************* ANIMATIONS *********************/ /** @@ -1325,6 +1571,31 @@ class Myr { 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; @@ -1349,6 +1620,33 @@ class Myr { 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; @@ -1373,6 +1671,19 @@ class Myr { 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; @@ -1400,6 +1711,19 @@ class Myr { 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; @@ -1414,14 +1738,56 @@ class Myr { return outerElId; } - /** - * Apply a colorShift animation to the Aframe element which is passed as arg - * - * @param {number} outerElId target element ID - * @param {*} color !!!DESCRIPTION NEEDED!!! - */ + colourNameToHex=(colour)=> + { + let 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; + } 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:"); + let baseCol = split[colorIndex+1]; + 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]; @@ -1444,7 +1810,7 @@ class Myr { } let anim = ` property: components.material.material.color; - from: ${(el.material.split(/\s|;/))[1]}; + from: ${(el.material.split(/\s|;/))[2]}; to: ${color}; dur: ${this.cursor.duration}; dir: alternate; @@ -1465,6 +1831,22 @@ class Myr { 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 */ diff --git a/src/myr/reference.js b/src/myr/reference.js index 3334452d..b5749ba5 100644 --- a/src/myr/reference.js +++ b/src/myr/reference.js @@ -15,6 +15,10 @@ const HtmlTooltip = withStyles(theme => ({ }, }))(Tooltip); +const handleTextureOpen = () => { + window.open(window.origin + "/textureReference"); +}; + export const stringText = (text) => { return ( The getRandomColor function returns a random color and changes the color of the cursor. If passed an array, getRandomColor will choose randomly from the given colors., example: "getRandomColor" }, + { + name: "getColor", + parameters: [], + description: returns the current color of the cursor. The color can be changed by the setColor() or getRandomColor() functions , + }, + { + name: "setTexture", + parameters: [{type: "string", name: "texture"}], + description: The setTexture function changes the texture of the cursor. The texture's normal color is displayed when setTextureColoring() is false, otherwise the texture will be affected by the current color. Textures can be applied either by using the name of one of or by inserting a valid url (e.g. "bricks" or "https://learnmyr.org/img/MYR-Logo.png"). An empty setTexture() or setTexture("") statement will remove the current texture., + example: "setTexture" + }, + { + name: "setTextureColoring", + parameters: [{type: "bool", name:"boolean"}], + description: The setTextureColoring function sets the textureColoring attribute in the cursor, turning whether colors are applied to objects with textures on and off. The default value is false., + example: "setTextureColoring" + }, + { + name: "getTexture", + parameters: [], + description: returns the current texture of the cursor. The texture can be changed by the setTexture() function., + }, { name: "setPosition", parameters: [{ type: "number", name: "x" }, { type: "number", name: "y" }, { type: "number", name: "z" }], @@ -309,6 +335,21 @@ let transformations = [ parameters: [{ type: "number", name: "z" }], description: The increaseZPos function increases the z position of the cursor, or decreases the position using negative values. This function returns the z position after it is modified. The defualt increases the position by 1., }, + { + name: "getXPos", + parameters: [], + description: returns the current x coordinate of the cursor. The x coordinate can be changed in the setPosition, setXPos, increacePosition, or increaceXPos functions. + }, + { + name: "getYPos", + parameters: [], + description: returns the current y coordinate of the cursor. The y coordinate can be changed in the setPosition, setYPos, increacePosition, or increaceYPos functions. + }, + { + name: "getZPos", + parameters: [], + description: returns the current z coordinate of the cursor. The z coordinate can be changed in the setPosition, setZPos, increacePosition, or increaceZPos functions. + }, { name: "setScale", parameters: [{ type: "number", name: "x" }, { type: "number", name: "y" }, { type: "number", name: "z" }], @@ -333,6 +374,21 @@ let transformations = [ description: The setZScale function changes the z component of the scale in the cursor. Values greater than 1 make the object larger, and values between 0 and 1 make the object smaller. The default z scale is 1., example: "setZScale" }, + { + name: "getXScale", + parameters: [], + description: returns the current x scale component of the cursor. The x scale component can be changed by the setScale and setXScale functions. + }, + { + name: "getYScale", + parameters: [], + description: returns the current y scale component of the cursor. The y scale component can be changed by the setScale and setYScale functions. + }, + { + name: "getZScale", + parameters: [], + description: returns the current z scale component of the cursor. The z scale component can be changed by the setScale and setZScale functions. + }, { name: "setRotation", parameters: [{ type: "number", name: "x" }, { type: "number", name: "y" }, { type: "number", name: "z" }], @@ -357,18 +413,43 @@ let transformations = [ description: The rollZ function changes the rotation in degrees around the z axis. The default value is 0., example: "rollZ" }, + { + name: "getXRotation", + parameters: [], + description: returns the current x rotation component of the cursor. The x rotation component can be changed by the setPosition or pitchX functions. + }, + { + name: "getYRotation", + parameters: [], + description: returns the current y rotation component of the cursor. The y rotation component can be changed by the setPosition or yawY functions. + }, + { + name: "getZRotation", + parameters: [], + description: returns the current z rotation component of the cursor. The z rotation component can be changed by the setPosition or rollZ functions. + }, { name: "setRadius", parameters: [{ type: "number", name: "radius" }], description: The setRadius function changes the radius of certain shapes. The shapes are: circle, cone, cylinder, dodecahedron, and torus. The default value is 1., example: "setRadius" }, + { + name: "getRadius", + parameters: [], + description: Returns the current radius of the cursor. The radius can be changed by the setRadius function. + }, { name: "setPhiLength", parameters: [{ type: "number", name: "phiLength" }], description: The setPhiLength function sets the phi length of the cursor in degrees. This changes the number of degrees shown of certain shapes. Interesting shapes can be achieved with values greater than 360. The default phi length is 360., example: "setPhiLength" }, + { + name: "getPhiLength", + parameters: [], + description: returns the current phi Length of the cursor. The phi Length can be changed by the setPhiLength function. + }, { name: "makeDroppable", parameters: [{ type: "string", name: "elementID" }, { type: "number", name: "mass" }], @@ -400,7 +481,12 @@ let transformations = [ name: "getCursorAttribute", parameters: [{ type: "string", name: "key" }], description: The getCursorAttribute function allows the user to get a cursor attributes set by the setCursorAttribute function or by any other cursor setter. - } + }, + { + name: "resetTransformationCursor", + parameters: [], + description: The resetTransformationCursor function resets the transformation properties of the cursor to their defaults., + }, ]; let animations = [ @@ -410,18 +496,33 @@ let animations = [ description: The setLoop function sets the loop attribute in the cursor, turning infinite looping on and off. The default value is true., example: "setLoop" }, + { + name: "getLoop", + parameters: [], + description: Returns whether the loop attribute has been set to true or false. The default value is true but can be changed by the setLoop function. + }, { name: "setDuration", parameters: [{ type: "number", name: "duration" }], description: The setDuration function sets the duration attribute of the cursor in milliseconds. The default value is 1000., example: "setDuration" }, + { + name: "getDuration", + parameters: [], + description: The getDuration function returns the current duration attribute of the cursor. The duration can be changed by the setDuration function. + }, { name: "setMagnitude", parameters: [{ type: "number", name: "magnitude" }], description: The setMagnitude function sets the magnitude attribute of the cursor in units or degrees. The default value is 1., example: "setMagnitude" }, + { + name: "getMagnitude", + parameters: [], + desription: The getMagnitude function returns the current magnitude attribute of the cursor. The magnitude can be changed by the setMagnitude function. + }, { name: "spin", parameters: [{ type: "string", name: "elementID" }], @@ -506,7 +607,11 @@ let animations = [ description: The colorShift function shifts the element from its original color to the given color. All colors valid for setColor are applicable., example: "colorShift" }, - + { + name: "resetAnimationCursor", + parameters: [], + description: The resetAnimationCursor function resets the properties of the cursor that contains animation properties to their defaults., + }, // { // name: spin(element, magnitude, loop, duration), // description: Spins the element around the y axis magnitude degrees. @@ -560,6 +665,68 @@ let animations = [ // description: Modifies transparency from 0 to magnitude (0,1] over duration milliseconds. // }, ]; +let lights = [ + { + name: "ambientLight", + parameters: [], + description: The ambientLight function makes a light that casts a light in every direction in the scene using the current cursor attributes. This function returns an {stringText("elementID")}. + }, + { + name: "directionalLight", + parameters: [], + description: The directionalLight function makes a light that casts an infinite, parallel light in a specific direction. This function returns an {stringText("elementID")}. + }, + { + name: "spotLight", + parameters: [], + description: The spotLight function makes a light that is emitted from a single point in one direction. This function returns an {stringText("elementID")}. + }, + { + name: "pointLight", + parameters: [], + description: The spotLight function makes a light that is emitted from a single point in all directions. This function returns an {stringText("elementID")}. + }, + { + name: "hemisphereLight", + parameters: [{type:"string",name:"secondColor"}], + description: The hemisphereLight function makes a light that has 2 colors on opposite sides, creating 2 distinct lighting colors. This function returns an {stringText("elementID")}. + }, + { + name: "setIntensity", + parameters: [{type:"number",name:"intensity"}], + description: The setIntensity function changes the intensity/strength of the light casting the scene. The default value is 1.0. + }, + { + name: "setBeamAngle", + parameters: [{type:"number",name:"degree"}], + description: The setBeamAngle function changes the maximum extent of a spotlight from its direction in degrees. The default value is 60. + }, + { + name: "setDecay", + parameters: [{type:"number",name:"decay"}], + description: The setDecay function changes the rate that light dims as it travels. The default value is 1.0. + }, + { + name: "setDistance", + parameters: [{type:"number",name:"distance"}], + description: The setDistance function sets the distance where the light’s intensity becomes 0. If the distance is 0, then the light does not decay with distance. The default value is 0.0. + }, + { + name: "setDiffusion", + parameters: [{type:"number",name:"diffusion"}], + description: The setDiffusion function sets the magnitude of diffusion on the edges of a spotlight . The default value is 0.0. + }, + { + name: "setLightTarget", + parameters: [{type:"number",name:"x"},{type:"number",name:"y"},{type:"number",name:"z"}], + description: The setLightTarget function sets the point where the light should be pointed to. The default values are x:0, y:0, z:0. + }, + { + name: "resetLightCursor", + parameters: [], + description: The resetLightCursor function resets the properties of the cursor that contains light properties to their defaults. + }, +]; let groups = [ { @@ -606,6 +773,7 @@ const reference = { transformations: transformations, animations: animations, groups: groups, + lights:lights, }; export default function r(ref = reference) { diff --git a/src/myr/textureReference.js b/src/myr/textureReference.js new file mode 100644 index 00000000..44399eb4 --- /dev/null +++ b/src/myr/textureReference.js @@ -0,0 +1,229 @@ +import React from "react"; +import { + Tooltip, + Typography, + withStyles +} from "@material-ui/core"; + +const HtmlTooltip = withStyles(theme => ({ + tooltip: { + maxWidth: 300, + fontSize: theme.typography.pxToRem(14), + "& b": { + fontWeight: "inherit", + }, + }, +}))(Tooltip); + +export const stringText = (text) => { + return ( + + String +

+ In computer science a string is any finite sequence of characters + (i.e., letters, numerals, symbols and punctuation marks).
+

+ {text === "elementID" && +

An elementID is a special type of string returned by geometries and groups.

} + + } + > + + {text} + +
); +}; +export const numberText = (text) => { + return ( + + Number +

+ A number is any real number, or an expression that evaluates to a real number + (e.g., -2, 3.14, 1/3). +

+ + } + > + + {text} + +
); +}; +export const boolText = (text) => { + return ( + + Bool +

+ In computer science, the bool data type has one of two possible Boolean values: true or false. +

+ + } + > + + {text} + +
); +}; + +export const arrayText = (text) => { + return ( + + Array +

+ In computer science, an array is a data structure that consists of a number of indexable elements. +

+

+ This code sets the color of the cursor to blue: +

+

+ let colors = ["blue", "green", "red"];
+ setColor(colors[0]);
+ box();
+

+ + } + > + + {text} + +
); +}; + +export const dataText = (text) => { + return ( + + Data +

+ Data can be of any valid JS datatype. This includes a strings, numbers, booleans, and objects among other datatypes.
+

+ + } + > + + {text} + +
); +}; + +let texture = [ + { + name: "bricks", + image: "bricks", + example: "bricks", + }, + { + name: "bark", + image: "bark", + example: "bark", + }, + { + name: "checkerboard", + image: "checkerboard", + example: "checkerboard", + }, + { + name: "chevron", + image: "chevron", + example: "chevron", + }, + { + name: "cobblestone", + image: "cobblestone", + example: "cobblestone", + }, + { + name: "dirt", + image: "dirt", + example: "dirt", + }, + { + name: "fabric", + image: "fabric", + example: "fabric", + }, + { + name: "grass", + image: "grass", + example: "grass", + }, + { + name: "lava", + image: "lava", + example: "lava", + }, + { + name: "leaves", + image: "leaves", + example: "leaves", + }, + { + name: "marble", + image: "marble", + example: "marble", + }, + { + name: "metal", + image: "metal", + example: "metal", + }, + { + name: "paint", + image: "paint", + example: "paint", + }, + { + name: "rug", + image: "rug", + example: "rug", + }, + { + name: "sand", + image: "sand", + example: "sand", + }, + { + name: "stone", + image: "stone", + example: "stone", + }, + { + name: "water", + image: "water", + example: "water", + }, + { + name: "wood", + image: "wood", + example: "wood", + }, + +]; + + +const textureReference = { + texture: texture, +}; + +export default function r(ref = textureReference) { + return ref; +} diff --git a/src/reducers/scene.js b/src/reducers/scene.js index 4d13cdb9..fe76d3df 100644 --- a/src/reducers/scene.js +++ b/src/reducers/scene.js @@ -9,6 +9,9 @@ export const DEF_SETTINGS = { showFloor: true, cameraPosition: "0 1.6 3", viewOnly: false, + defaultLight: true, + castShadow: false, + lightIndicator: false, collectionID: "" }; @@ -132,7 +135,7 @@ export default function scene(state = initial_state, action) { name: action.payload.name, desc: action.payload.desc }; - case types.CHANGE_SETTING: + case types.CHANGE_SETTINGS: const { param, val } = action.payload; return { ...state, @@ -141,6 +144,35 @@ export default function scene(state = initial_state, action) { [param]: val } }; + case types.RESET_SETTINGS: + return { + ...state, + settings: DEF_SETTINGS + }; + case types.TOGGLE_DEFAULT_LIGHT: + return { + ...state, + settings: { + ...state.settings, + defaultLight: !state.settings.defaultLight + } + }; + case types.TOGGLE_CAST_SHADOW: + return { + ...state, + settings: { + ...state.settings, + castShadow: !state.settings.castShadow + } + }; + case types.TOGGLE_LIGHT_INDICATOR: + return{ + ...state, + settings:{ + ...state.settings, + lightIndicator: !state.settings.lightIndicator + } + }; default: return state; } diff --git a/src/routes.js b/src/routes.js index c4482d1f..309d0dbe 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,6 +4,7 @@ import Ide from "./containers/Ide"; import Guided from "./containers/Guided"; import Collection from "./containers/Collection"; import Reference from "./containers/Reference"; +import TextureReference from "./containers/TextureReference"; import ReferenceExample from "./containers/ReferenceExample"; const router = () => { @@ -12,6 +13,7 @@ const router = () => { + diff --git a/src/tests/App.test.js b/src/tests/App.test.js index fe35ce48..ea0b3e99 100644 --- a/src/tests/App.test.js +++ b/src/tests/App.test.js @@ -69,6 +69,9 @@ const generateMockProps = () => { showFloor: true, cameraPosition: "0 1.6 3", viewOnly: false, + defaultLight: true, + castShadow: false, + lightIndicator: false, collectionID: "" }, }, @@ -319,6 +322,9 @@ describe("Scene Reducer", () => { showFloor: true, cameraPosition: "0 1.6 3", viewOnly: false, + defaultLight: true, + castShadow: false, + lightIndicator: false, collectionID: "" } }; diff --git a/src/tests/Myr.test.js b/src/tests/Myr.test.js index 80af38ab..314653a2 100644 --- a/src/tests/Myr.test.js +++ b/src/tests/Myr.test.js @@ -6,6 +6,7 @@ let colorRegEx = new RegExp("#([0-9]|[A-F]|[a-f]){6}"); const defaultCursor = { color: "red", + texture: "", transparency: 0, position: { x: 0, @@ -25,12 +26,21 @@ const defaultCursor = { radius: "1", phiLength: 360, loop: true, + textureColoring: false, 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 + } }; describe("Updates to Myr's Model", () => { @@ -40,6 +50,64 @@ describe("Updates to Myr's Model", () => { expect(myr.cursor.color).toEqual("red"); }); + it("should set the texture by using a title and getTexture() should return that title", () => { + myr.setTexture("bricks"); + expect(myr.cursor.texture).toEqual("/img/textures/bricks.jpg"); + let getTest = myr.getTexture(); + expect(getTest).toEqual("bricks"); + }); + + it("should set the texture by using a url and getTexture() should return that url", () => { + myr.setTexture("https://learnmyr.org/img/MYR-Logo.png"); + expect(myr.cursor.texture).toEqual("https://learnmyr.org/img/MYR-Logo.png"); + }); + + it("improper texture should return empty texture", () => { + myr.setTexture("asdfghjkl"); + expect(myr.cursor.texture).toEqual(""); + let getUndefinedTest = myr.getTexture(); + expect(getUndefinedTest).toEqual(""); + }); + + it("setTextureColoring(true) should allow a textured object to have a color other than white", () => { + myr.setTextureColoring(true); + myr.setColor("blue"); + myr.setTexture("bricks"); + myr.els = []; + let id = myr.box(); + let box = myr.els[id]; + + expect(myr.getColor()).toMatch("blue"); + expect(box.material).toMatch(/color: blue;/); + }); + + it("setTextureColoring(false) should not affect an untextures objects color", () => { + myr.setTextureColoring(false); + myr.setTexture(); + myr.setColor("blue"); + myr.els = []; + let id = myr.box(); + let box = myr.els[id]; + + expect(myr.getColor()).toMatch("blue"); + expect(box.material).toMatch(/color: blue;/); + }); + + it("setTextureColoring(false) should change the color of a textured object to white", () => { + myr.setTextureColoring(false); + myr.setTexture("bricks"); + myr.setColor("blue"); + myr.els = []; + let id = myr.box(); + let box = myr.els[id]; + + expect(myr.getColor()).toMatch("blue"); + expect(myr.getTexture()).toMatch("bricks"); + expect(box.material).toMatch(/color: white;/); + + myr.setTexture(); + }); + it("to SetPosition", () => { myr.setPosition(1, 2, 3); expect(myr.cursor.position).toEqual({ x: 1, y: 2, z: 3 }); @@ -80,62 +148,68 @@ describe("Updates to Myr's Model", () => { describe("Component Renders", () => { it("Box", () => { - let id = myr.box({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.box({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let box = myr.els[id]; expect(box).toBeDefined(); expect(box.geometry).toMatch(/box/); expect(box.material).toMatch(/color: blue;/); + expect(box.material).toMatch(/texture: bricks/); expect(box.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("Sphere", () => { myr.els = []; - let id = myr.sphere({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.sphere({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let sphere = myr.els[id]; expect(sphere).toBeDefined(); expect(sphere.geometry).toMatch(/sphere/); expect(sphere.material).toMatch(/color: blue;/); + expect(sphere.material).toMatch(/texture: bricks/); expect(sphere.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("Circle", () => { myr.els = []; - let id = myr.circle({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.circle({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let circle = myr.els[id]; expect(circle).toBeDefined(); expect(circle.geometry).toMatch(/circle/); expect(circle.material).toMatch(/color: blue;/); + expect(circle.material).toMatch(/texture: bricks/); expect(circle.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("Cone", () => { myr.els = []; - let id = myr.cone({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.cone({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let cone = myr.els[id]; expect(cone).toBeDefined(); expect(cone.geometry).toMatch(/cone/); expect(cone.material).toMatch(/color: blue;/); + expect(cone.material).toMatch(/texture: bricks/); expect(cone.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("Cylinder", () => { myr.els = []; - let id = myr.cylinder({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.cylinder({ material: "color: blue; texture: bricks;", position: { x: 1, y: 1, z: 1 } }); let cylinder = myr.els[id]; expect(cylinder).toBeDefined(); expect(cylinder.geometry).toMatch(/cylinder/); expect(cylinder.material).toMatch(/color: blue;/); + expect(cylinder.material).toMatch(/texture: bricks/); expect(cylinder.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("Ring", () => { myr.reset(); - let id = myr.ring(); + let id = myr.ring({ material: "color: red; texture: bricks; side: double;" }); let ring = myr.els[id]; expect(ring).toBeDefined(); expect(ring.geometry).toMatch(/ring/); expect(ring.material).toMatch(/color: red;/); + expect(ring.material).toMatch(/texture: bricks/); expect(ring.material).toMatch(/side: double;/); expect(ring.position).toEqual({ x: 0, y: 0, z: 0 }); }); @@ -143,32 +217,37 @@ describe("Component Renders", () => { it("Plane", () => { myr.reset(); myr.els = []; + myr.setTexture("bricks"); let id = myr.plane(); let plane = myr.els[id]; expect(plane).toBeDefined(); expect(plane.geometry).toMatch(/plane/); - expect(plane.material).toMatch(/color: red;/); + expect(plane.material).toMatch(/color: white;/); + expect(plane.material).toMatch("img/textures/bricks.jpg"); expect(plane.material).toMatch(/side: double;/); expect(plane.position).toEqual({ x: 0, y: 0, z: 0 }); }); it("Tetrahedron", () => { myr.reset(); - let id = myr.tetrahedron(); + let id = myr.tetrahedron({ material: "color: red; texture: bricks; side: double;" }); let tetrahedron = myr.els[id]; expect(tetrahedron).toBeDefined(); expect(tetrahedron.geometry).toMatch(/tetrahedron/); expect(tetrahedron.material).toMatch(/color: red;/); + expect(tetrahedron.material).toMatch(/texture: bricks/); expect(tetrahedron.material).toMatch(/side: double;/); expect(tetrahedron.position).toEqual({ x: 0, y: 0, z: 0 }); }); it("Triangle", () => { myr.els = []; - let id = myr.triangle({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + myr.setTextureColoring(true); + let id = myr.triangle({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let triangle = myr.els[id]; expect(triangle).toBeDefined(); expect(triangle.geometry).toMatch(/triangle/); + expect(triangle.material).toMatch(/texture: bricks/); expect(triangle.material).toMatch(/color: blue;/); expect(triangle.position).toEqual({ x: 1, y: 1, z: 1 }); }); @@ -229,23 +308,30 @@ describe("Component Renders", () => { it("Torus", () => { myr.els = []; + myr.setTextureColoring(false); myr.setColor("blue"); + myr.setTexture("bricks"); let id = myr.torus({ position: { x: 1, y: 1, z: 1 } }); let torus = myr.els[id]; expect(torus).toBeDefined(); expect(torus.geometry).toMatch(/torus/); - expect(torus.material).toMatch(/color: blue;/); + expect(torus.material).toMatch(/color: white;/); + expect(myr.getTexture()).toMatch("bricks"); expect(torus.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("torusknot", () => { myr.els = []; myr.setColor("blue"); + myr.setTexture("bricks"); + myr.setTextureColoring(true); let id = myr.torusknot({ position: { x: 1, y: 1, z: 1 } }); let torusknot = myr.els[id]; expect(torusknot).toBeDefined(); expect(torusknot.geometry).toMatch(/torus/); expect(torusknot.material).toMatch(/color: blue;/); + expect(torusknot.material).toMatch("img/textures/bricks.jpg"); + expect(myr.getTexture()).toMatch("bricks"); expect(torusknot.position).toEqual({ x: 1, y: 1, z: 1 }); }); @@ -265,31 +351,34 @@ describe("Component Renders", () => { it("dodecahedron", () => { myr.els = []; - let id = myr.dodecahedron({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.dodecahedron({ material: "color: blue; texture: bricks;", position: { x: 1, y: 1, z: 1 } }); let dodecahedron = myr.els[id]; expect(dodecahedron).toBeDefined(); expect(dodecahedron.geometry).toMatch(/dodecahedron/); expect(dodecahedron.material).toMatch(/color: blue;/); + expect(dodecahedron.material).toMatch(/texture: bricks/); expect(dodecahedron.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("icosahedron", () => { myr.els = []; - let id = myr.icosahedron({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.icosahedron({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let icosahedron = myr.els[id]; expect(icosahedron).toBeDefined(); expect(icosahedron.geometry).toMatch(/icosahedron/); expect(icosahedron.material).toMatch(/color: blue;/); + expect(icosahedron.material).toMatch(/texture: bricks/); expect(icosahedron.position).toEqual({ x: 1, y: 1, z: 1 }); }); it("octahedron", () => { myr.els = []; - let id = myr.octahedron({ material: "color: blue;", position: { x: 1, y: 1, z: 1 } }); + let id = myr.octahedron({ material: "color: blue; texture: bricks", position: { x: 1, y: 1, z: 1 } }); let octahedron = myr.els[id]; expect(octahedron).toBeDefined(); expect(octahedron.geometry).toMatch(/octahedron/); expect(octahedron.material).toMatch(/color: blue;/); + expect(octahedron.material).toMatch(/texture: bricks/); expect(octahedron.position).toEqual({ x: 1, y: 1, z: 1 }); }); }); @@ -476,6 +565,117 @@ describe("Component Animations", () => { }); +describe("MYR light functionality", () => { + it("ambientLight", ()=>{ + myr.reset(); + myr.els = []; + let id = myr.ambientLight({position: {x:1,y:1,z:1}}); + let ambientLight = myr.els[id]; + expect(ambientLight).toBeDefined(); + expect(ambientLight.light.state).toEqual(` + type: ambient; + color: red; + intensity: 1;`); + expect(ambientLight.position).toEqual({x:1,y:1,z:1}); + //light's scale sohuld not change + expect(ambientLight.scale).toEqual({x:1,y:1,z:1}); + }); + + it("directionalLight", ()=>{ + myr.els = []; + let id = myr.directionalLight({position: {x:1,y:1,z:1}}); + let directionalLight = myr.els[id]; + expect(directionalLight).toBeDefined(); + expect(directionalLight.light.state).toEqual(` + type: directional; + color: red; + intensity: 1;`); + expect(directionalLight.position).toEqual({x:1,y:1,z:1}); + expect(directionalLight.scale).toEqual({x:1,y:1,z:1}); + }); + + it("spotLight", ()=>{ + myr.els = []; + let id = myr.spotLight({position: {x:1,y:1,z:1}}); + let spotLight = myr.els[id]; + expect(spotLight).toBeDefined(); + expect(spotLight.light.state).toEqual(` + type: spot; + angle: 60; + decay: 1; + distance: 0; + intensity: 1; + penumbra: 0; + color: red;`); + expect(spotLight.position).toEqual({x:1,y:1,z:1}); + //light's scale sohuld not change + expect(spotLight.scale).toEqual({x:1,y:1,z:1}); + }); + + it("pointLight", ()=>{ + myr.els = []; + let id = myr.pointLight({position: {x:1,y:1,z:1}}); + let pointLight = myr.els[id]; + expect(pointLight).toBeDefined(); + expect(pointLight.light.state).toEqual(` + type: point; + angle: 60; + decay: 1; + distance: 0; + intensity: 1; + penumbra: 0; + color: red;`); + expect(pointLight.position).toEqual({x:1,y:1,z:1}); + //light's scale should not change + expect(pointLight.scale).toEqual({x:1,y:1,z:1}); + }); + + it("hemisphereLight", ()=>{ + myr.els = []; + let id = myr.hemisphereLight("blue",{position: {x:1,y:1,z:1}}); + let hemisphereLight = myr.els[id]; + expect(hemisphereLight).toBeDefined(); + expect(hemisphereLight.light.state).toEqual(` + type: hemisphere; + intensity: 1; + color: red; + groundColor: blue;`); + expect(hemisphereLight.position).toEqual({x:1,y:1,z:1}); + //light's scale should not change + expect(hemisphereLight.scale).toEqual({x:1,y:1,z:1}); + }); + + it("to Set Intensity", () => { + myr.setIntensity(5); + expect(myr.cursor.light.intensity).toEqual(5); + }); + + it("to Set BeamAngle", () => { + myr.setBeamAngle(30); + expect(myr.cursor.light.beamAngle).toEqual(30); + }); + + it("to Set Diffusion", () => { + myr.setDiffusion(1.25); + expect(myr.cursor.light.diffusion).toEqual(1.25); + }); + + it("to Set decay", () => { + myr.setDecay(1.87); + expect(myr.cursor.light.decay).toEqual(1.87); + }); + + it("to Set Distance", () => { + myr.setDistance(50); + expect(myr.cursor.light.distance).toEqual(50); + }); + + it("to Set Light Target", () => { + myr.setLightTarget(4,5,10); + expect(myr.cursor.light.target).toEqual({x:4, y: 5, z: 10}); + }); +}); + describe("Other Myr functionality", () => { it("Should add a model", () => { myr.reset(); @@ -492,14 +692,6 @@ describe("Other Myr functionality", () => { expect(thisEl).toHaveProperty("dynamic-body"); }); - it("Should return a light", () => { - myr.reset(); - let light = myr.light(); - expect(light).toBeTruthy(); - expect(light.color).toMatch(colorRegEx); - - }); - it("should get the right element", () => { myr.reset(); let id = myr.box(); @@ -533,6 +725,36 @@ describe("Other Myr functionality", () => { expect(myr.cursor).toEqual(defaultCursor); }); + it("should only reset cursor with transformation property", () => { + myr.setPosition(5,4,3); + myr.setMagnitude(512); + myr.setIntensity(2.15); + myr.resetTransformationCursor(); + expect(myr.cursor.position).toEqual({ x: 0, y: 0, z: 0}); + expect(myr.cursor.magnitude).toEqual({ spin: 512, fadeOut: 512, general: 512 }); + expect(myr.cursor.light.intensity).toEqual(2.15); + }); + + it("should only reset cursor with animation property", () => { + myr.setPosition(5,4,3); + myr.setMagnitude(512); + myr.setIntensity(2.15); + myr.resetAnimationCursor(); + expect(myr.cursor.position).toEqual({ x: 5, y: 4, z: 3}); + expect(myr.cursor.magnitude).toEqual({ spin: 360, fadeOut: 0, general: 1 }); + expect(myr.cursor.light.intensity).toEqual(2.15); + }); + + it("should only reset cursor with light property", () => { + myr.setPosition(5,4,3); + myr.setMagnitude(512); + myr.setIntensity(2.15); + myr.resetLightCursor(); + expect(myr.cursor.position).toEqual({ x: 5, y: 4, z: 3}); + expect(myr.cursor.magnitude).toEqual({ spin: 512, fadeOut: 512, general: 512 }); + expect(myr.cursor.light).toEqual(defaultCursor.light); + }); + it("should set the position in Myr", () => { myr.reset(); myr.setPosition(1); @@ -853,7 +1075,7 @@ describe("Other Myr functionality", () => { response = myr.getCursorAttribute("test"); expect(response).toEqual({ "test1": 1, "test2": 3 }); }); - + it("Should accept hex colors entered for setColor function", () => { myr.setColor("#ff0000"); expect(myr.cursor.color).toEqual("#ff0000"); diff --git a/src/utils/AframeReg.js b/src/utils/AframeReg.js index 8be607ab..ff7fd6c3 100644 --- a/src/utils/AframeReg.js +++ b/src/utils/AframeReg.js @@ -23,3 +23,104 @@ AFRAME.registerComponent("force-pushable", { el.body.applyImpulse(force, el.body.position); } }); + +//This set aframe entity to different layer (range from 0-31) +//all the regular MYR entities will goes to layer 0 +//And other, such as grid, light indicator will goes to layer 1 so it won't take effect of user created light +AFRAME.registerComponent("layer",{ + schema:{ + type:{ + type:"string", + default: "mesh" + }, + layer:{ + type:"number", + default: 0 + }, + }, + init: function(){ + let obj = this.el.getObject3D(this.data.type); + if(this.data.type === "group"){ + obj.children.forEach((child)=>{ + child.layers.set(this.data.layer); + }); + } else { + obj.layers.set(this.data.layer); + } + } +}); + +//Attached to the a-scene. Display aframe entities in different layer in scene every frame +AFRAME.registerComponent("scenelayer",{ + schema:{ + default:"" + }, + init: function(){ + this.renderer = this.el.renderer; + this.camera = this.el.camera; + this.scene = this.el.sceneEl.object3D; + }, + tick: function(){ + this.renderer.autoClear = true; + this.camera.layers.set(1); + this.renderer.render(this.scene, this.camera); + + this.renderer.autoClear = false; + this.camera.layers.set(0); + this.renderer.render(this.scene, this.camera); + } +}); + +//This sets the side where the shadow should be rendered +AFRAME.registerComponent("shadowcustomsetting", { + schema:{ + default:"" + }, + init: function () { + this.el.addEventListener("loaded", () => { + let obj = this.el.getObject3D("mesh"); + obj.material.shadowSide = THREE.FrontSide; + }); + }, +}); + +//This change necessary properties to entity to create a outline to light indicator +AFRAME.registerComponent("outline",{ + schema:{ + default:"" + }, + init: function(){ + let mesh = this.el.getObject3D("mesh"); + let invertColor = ~mesh.material.color.getHex(); + + mesh.material.color.set(invertColor); + mesh.material.side = THREE.BackSide; + } +}); + +//This calculate and sets the rotation of the entity based on 2 points +AFRAME.registerComponent("indicatorrotation",{ + schema:{ + position:{ + type: "vec3", + default:{x:0,y:0,z:0} + }, + target:{ + type:"vec3", + default:{x:0,y:0,z:0} + }, + }, + init: function(){ + let group = this.el.getObject3D("group"); + group.rotation.copy(this.FindRotationOf2Pts(this.data.position,this.data.target)); + }, + FindRotationOf2Pts: function(vec,vec2){ + let vector = new THREE.Vector3(vec.x,vec.y,vec.z); + let origin = new THREE.Vector3(vec2.x,vec2.y,vec2.z); + if(vector === origin){return null;} + let direction = new THREE.Vector3().subVectors(vector,origin); + let arrow = new THREE.ArrowHelper( direction.normalize(), origin,1 ); + + return arrow.rotation; + }, +}); diff --git a/src/utils/AframeRegIndicator.js b/src/utils/AframeRegIndicator.js new file mode 100644 index 00000000..c2a9dd51 --- /dev/null +++ b/src/utils/AframeRegIndicator.js @@ -0,0 +1,225 @@ +import AFRAME from "aframe"; +import * as THREE from "three"; + + +AFRAME.registerComponent("spotlightindicator",{ + schema:{ + color:{ + default:"red" + }, + target:{ + type: "boolean", + default: false + } + }, + init: function(){ + const data = this.data; + + /* define geometry */ + let cone = new THREE.CylinderGeometry(.1,.75,1,24,1,true); + let circle = new THREE.CircleGeometry(.1,24); + + if(data.target){ + circle.rotateX(Math.PI/2); + circle.translate(0,0.5,0); + }else{ + cone.rotateX(Math.PI/2); + circle.translate(0,0,0.5); + } + + let geometry = new THREE.Geometry(); + geometry.merge(circle); + geometry.merge(cone); + + /* define outline geometry */ + + let outCone = new THREE.CylinderGeometry(.2,1,1.5,24,1,true); + let outCircle = new THREE.CircleGeometry(.2,24); + + + if(data.target) { + outCircle.rotateX(-Math.PI/2); + outCircle.translate(0,0.75,0); + }else{ + outCone.rotateX(Math.PI/2); + outCircle.translate(0,0,.75); + } + + let outGeometry = new THREE.Geometry(); + outGeometry.merge(outCircle); + outGeometry.merge(outCone); + + /* define material */ + const material = new THREE.MeshBasicMaterial({color: data.color, side: THREE.DoubleSide}); + const outMaterial = CreateOutlineMaterial(material); + + /* group meshes together */ + let mesh = new AFRAME.THREE.Mesh(geometry, material); + let outlineMesh = new AFRAME.THREE.Mesh(outGeometry,outMaterial); + + let group = new AFRAME.THREE.Group(); + group.add(mesh); + group.add(outlineMesh); + + let el = this.el; + el.setObject3D("group", group); + } +}); + +AFRAME.registerComponent("pointlightindicator", { + schema:{ + color:{ + default:"red" + } + }, + init: function(){ + const data = this.data; + + /* define geometry */ + let geometry = new THREE.SphereGeometry( .25, 100, 100); + + /* define outside geometry */ + let outGeometry =new THREE.SphereGeometry( .35, 100, 100); + + /* define material */ + const material = new THREE.MeshBasicMaterial({color:data.color}); + const outMaterial = CreateOutlineMaterial(material); + + /* define and group all the mesh */ + let mesh = new THREE.Mesh(geometry, material); + let outlineMesh = new THREE.Mesh(outGeometry, outMaterial); + + let group = new AFRAME.THREE.Group(); + group.add(mesh); + group.add(outlineMesh); + + let el = this.el; + el.setObject3D("group", group); + } +}); + +AFRAME.registerComponent("directionallightindicator", { + schema:{ + color:{ + default:"red" + } + }, + init: function(){ + const data = this.data; + /* define geometry */ + let arrowHead = new THREE.ConeGeometry(.5); + let arrowPole = new THREE.CylinderGeometry(.1,.1, 2.5, 20, 4 ); + + arrowHead.translate(0,1.8,0); + arrowHead.rotateX(Math.PI); + + let geometry = new THREE.Geometry(); + geometry.merge(arrowHead); + geometry.merge(arrowPole); + + /* define outside geometry */ + let outArrowHead = new THREE.ConeGeometry(.7,1.3); + let outArrowPole = new THREE.CylinderGeometry(.2,.2, 2.8, 20, 4 ); + + let outGeometry = new THREE.Geometry(); + outArrowHead.translate(0,1.8,0); + outArrowHead.rotateX(Math.PI); + + outGeometry.merge(outArrowHead); + outGeometry.merge(outArrowPole); + + /* define material */ + let material = new THREE.MeshBasicMaterial({color:data.color}); + let outMaterial = CreateOutlineMaterial(material); + /* define and group all the meshes together */ + let mesh = new AFRAME.THREE.Mesh(geometry, material); + let outlineMesh = new AFRAME.THREE.Mesh(outGeometry,outMaterial); + + let group = new AFRAME.THREE.Group(); + group.add(mesh); + group.add(outlineMesh); + + let el = this.el; + el.setObject3D("group", group); + } +}); + +/* + Weird bug - there's circle exist in up cone and don't know why its there +*/ +AFRAME.registerComponent("hemispherelightindicator",{ + schema: { + color:{ + default:"red" + }, + secondColor:{ + default:"red" + } + }, + init: function(){ + const data = this.data; + + /* define geometry */ + let geometry = new THREE.Geometry(); + + //define parts of geometry + let head = new THREE.ConeGeometry(.5); + let pole = new THREE.CylinderGeometry(.1,.1 ,1 ,20); + head.translate(0,1.5,0); + pole.translate(0,0.5,0); + + //merge geometries + let arrowGeo = new THREE.Geometry(); + arrowGeo.merge(head); + arrowGeo.merge(pole); + + //get down matrix + let temp = new THREE.Mesh(arrowGeo); + temp.rotateX(Math.PI); + temp.updateMatrix(); + + geometry.merge(arrowGeo,new THREE.Matrix4(), 0); + geometry.merge(arrowGeo, temp.matrix, 1); + + /* define outline geometry */ + let outGeometry = new THREE.Geometry(); + + const outHead = new THREE.ConeGeometry(.7,1.3,20,12); + const outPole = new THREE.CylinderGeometry(0.2, 0.2, 1.2, 20); + outHead.translate(0, 1.5, 0); + outPole.translate(0, 0.6, 0); + + let outArrowGeo = new THREE.Geometry(); + outArrowGeo.merge(outHead); + outArrowGeo.merge(outPole); + + outGeometry.merge(outArrowGeo, new THREE.Matrix4(), 0); + outGeometry.merge(outArrowGeo, temp.matrix, 1); + + /* define material */ + const material = new THREE.MeshBasicMaterial({color: data.color}); + const secondMaterial = new THREE.MeshBasicMaterial({color:data.secondColor}); + const outMaterial = CreateOutlineMaterial(material); + const outSecondMaterial = CreateOutlineMaterial(secondMaterial); + + /* define and group all the meshes together */ + let mesh = new AFRAME.THREE.Mesh(geometry, [material, secondMaterial]); + let outlineMesh = new AFRAME.THREE.Mesh(outGeometry,[outMaterial, outSecondMaterial]); + + let group = new AFRAME.THREE.Group(); + group.add(mesh); + group.add(outlineMesh); + + let el = this.el; + el.setObject3D("group", group); + } +}); + + +/** + * @param {THREE.Material} material + */ +function CreateOutlineMaterial(material) { + let invertColor = ~material.color.getHex(); + return new THREE.MeshBasicMaterial({ color: invertColor, side: THREE.BackSide }); +} \ No newline at end of file