this.sceneContainer = c}/>
83 | }
84 | initThreeJS() {
85 | this.scene = new THREE.Scene();
86 | this.scene.background = new THREE.Color( 0x000000 );
87 | this.camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 );
88 | this.scene.add(this.camera)
89 | this.renderer = new THREE.WebGLRenderer( { antialias: true } );
90 | this.renderer.setPixelRatio( window.devicePixelRatio );
91 | this.renderer.setSize( window.innerWidth, window.innerHeight );
92 | this.renderer.gammaOutput = true
93 | this.sceneContainer.appendChild( this.renderer.domElement );
94 | this.clips = []
95 | this.mixer = null
96 | this.model = null
97 |
98 | this.controls = new OrbitControls( this.camera, this.renderer.domElement );
99 | this.controls.screenSpacePanning = true;
100 |
101 | this.renderer.setAnimationLoop(this.renderThree)
102 |
103 | const light = new THREE.DirectionalLight( 0xffffff, 1.0 );
104 | light.position.set( 1, 1, 1 ).normalize();
105 | this.scene.add( light );
106 | this.scene.add(new THREE.AmbientLight(0xffffff,0.2))
107 | //console.log("rendering the asset",asset)
108 |
109 | this.swapModel(this.props.asset)
110 | }
111 |
112 | playAllClips () {
113 | this.clips.forEach((clip) => {
114 | this.mixer.clipAction(clip).reset().play();
115 | });
116 | }
117 |
118 | setClips ( clips ) {
119 | if (this.mixer) {
120 | this.mixer.stopAllAction();
121 | this.mixer.uncacheRoot(this.mixer.getRoot());
122 | this.mixer = null;
123 | }
124 |
125 | clips.forEach((clip) => {
126 | if (clip.validate()) clip.optimize();
127 | });
128 |
129 | if (!clips.length) return;
130 | this.clips = clips
131 | this.mixer = new THREE.AnimationMixer( this.model );
132 | }
133 |
134 | checkAspectRatio() {
135 | if(!this.renderer
136 | || !this.renderer.domElement
137 | || !this.sceneContainer
138 | ) return
139 | const w = this.sceneContainer.clientWidth
140 | const h = this.sceneContainer.clientHeight
141 | const aspect = w/h
142 | if(Math.abs(this.camera.aspect - aspect) > 0.001 ) {
143 | this.camera.aspect = aspect
144 | this.camera.updateProjectionMatrix()
145 | this.renderer.setSize(w-4,h-4)
146 | }
147 | }
148 |
149 | renderThree = (time) => {
150 | const dt = (time - this.prevTime) / 1000;
151 | this.checkAspectRatio()
152 |
153 | this.controls.update();
154 | this.mixer && this.mixer.update(dt);
155 |
156 | this.renderer.render( this.scene, this.camera );
157 | this.prevTime = time;
158 |
159 | }
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/src/vr/GeoAssetView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from "react"
2 | import {map, tileLayer, marker, divIcon, latLng} from "leaflet/dist/leaflet-src.js"
3 | import "leaflet/dist/leaflet.css"
4 | import {TREE_ITEM_PROVIDER} from 'react-visual-editor-framework'
5 | import {HBox} from 'appy-comps'
6 |
7 | export default class GeoAssetView extends Component {
8 |
9 |
10 | constructor(props, context) {
11 | super(props, context)
12 | this.updateLocation = (e) => {
13 | const obj = this.props.provider.accessObject(this.props.asset.id)
14 | const ll = latLng({
15 | lat: obj.latitude,
16 | lon: obj.longitude
17 | })
18 | if(this.marker) this.marker.setLatLng(ll)
19 | if(this.mymap) this.mymap.panTo(ll)
20 | }
21 | }
22 |
23 | loadCurrentLocation = () => {
24 | navigator.geolocation.getCurrentPosition((success)=>{
25 | console.log("success",success)
26 | const coords = success.coords
27 | this.props.provider.quick_setPropertyValue(this.props.asset.id,'latitude',coords.latitude)
28 | this.props.provider.quick_setPropertyValue(this.props.asset.id,'longitude',coords.longitude)
29 | },(error)=>{
30 | console.log("error",error)
31 | })
32 | }
33 |
34 | onMapClick = (e) => {
35 | this.props.provider.quick_setPropertyValue(this.props.asset.id,'latitude',e.latlng.lat)
36 | this.props.provider.quick_setPropertyValue(this.props.asset.id,'longitude',e.latlng.lng)
37 | }
38 |
39 | render() {
40 | const style = {
41 | width: '400px',
42 | height: '400px',
43 | border: '1px solid black',
44 | }
45 | this.updateLocation()
46 | return
47 |
48 |
49 |
50 |
this.div = d} style={style}>
51 |
52 | }
53 |
54 | componentDidMount() {
55 | this.mymap = map('mapid').setView([this.props.asset.longitude, this.props.asset.latitude], 18);
56 | tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
57 | maxZoom: 18,
58 | attribution: 'Map data ©
OpenStreetMap contributors, ' +
59 | '
CC-BY-SA, ' +
60 | 'Imagery ©
Mapbox',
61 | id: 'mapbox.streets'
62 | }).addTo(this.mymap);
63 | this.mymap.on('click',this.onMapClick)
64 |
65 | const customIcon = divIcon({className:'map-icon'})
66 | this.marker = marker([this.props.asset.longitude, this.props.asset.latitude],{
67 | title:'this is the geo location',
68 | icon: customIcon
69 | }).addTo(this.mymap)
70 |
71 | this.props.provider.on(TREE_ITEM_PROVIDER.PROPERTY_CHANGED, this.updateLocation)
72 | this.updateLocation()
73 | }
74 | componentWillUnmount() {
75 | this.props.provider.off(TREE_ITEM_PROVIDER.PROPERTY_CHANGED, this.updateLocation)
76 | }
77 | }
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/src/vr/ScriptEditor.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from "react"
2 | import {SelectionManager, SELECTION_MANAGER, TREE_ITEM_PROVIDER} from 'react-visual-editor-framework'
3 | import {TOTAL_OBJ_TYPES} from './Common'
4 | import AceEditor from 'react-ace'
5 | import 'brace/mode/javascript'
6 | import 'brace/theme/github'
7 | import * as ToasterManager from '../vr/ToasterManager'
8 |
9 | export default class ScriptEditor extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.refresh = () => {
13 | const id = SelectionManager.getSelection()
14 | const obj = this.props.provider.accessObject(id)
15 | if(obj.exists() && obj.type === TOTAL_OBJ_TYPES.BEHAVIOR_SCRIPT) {
16 | this.props.provider.fetchBehaviorAssetContents(id).then(text => {
17 | this.setState({id:id, text:text})
18 | })
19 | } else {
20 | }
21 | }
22 | this.state = {
23 | id:null,
24 | text:'empty',
25 | }
26 | }
27 | componentDidMount() {
28 | this.refresh()
29 | this.props.provider.on(TREE_ITEM_PROVIDER.PROPERTY_CHANGED,this.refresh)
30 | SelectionManager.on(SELECTION_MANAGER.CHANGED,this.swap)
31 | }
32 |
33 | componentWillUnmount() {
34 | this.props.provider.off(TREE_ITEM_PROVIDER.PROPERTY_CHANGED,this.refresh)
35 | SelectionManager.off(SELECTION_MANAGER.CHANGED,this.swap)
36 | }
37 | swap = () => {
38 | const obj = this.props.provider.accessObject(SelectionManager.getSelection())
39 | if(obj.exists() && obj.type === TOTAL_OBJ_TYPES.BEHAVIOR_SCRIPT) {
40 | this.refresh()
41 | }
42 | }
43 | changeText = (value) => this.setState({text:value})
44 | save = (e) => {
45 | const ann = this.editor.editor.getSession().getAnnotations()
46 | const errors = ann.filter(a => a.type === 'error')
47 | if(errors.length > 0) {
48 | ToasterManager.add("errors in behavior")
49 | } else {
50 | ToasterManager.add("saved behavior")
51 | this.props.provider.updateBehaviorAssetContents(this.state.id,this.state.text)
52 | }
53 | }
54 | render() {
55 | if(this.state.id === null) return
loading
56 | return
this.editor = ed}
66 | />
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/src/vr/Templates.js:
--------------------------------------------------------------------------------
1 | export const CUSTOM_BEHAVIOR_SCRIPT = `
2 | /*
3 | #title untitled behavior
4 | #description does something
5 | */
6 | ({
7 | // defines a target property. must be a scene
8 | properties: {
9 | scene: {
10 | type:'enum',
11 | title: 'target scene',
12 | value:null,
13 | hints: {
14 | type:'node',
15 | nodeType:'scene'
16 | }
17 | },
18 | },
19 | start: function(evt) {
20 | //called when the program starts
21 | },
22 | click: function(evt) {
23 | //called when object is clicked on
24 | //this.navigateScene(this.properties.scene)
25 | }
26 | })
27 | `
28 |
29 |
30 |
31 | export const CUSTOM_SCENE_SCRIPT = `
32 | /*
33 | #title scene script
34 | #description does something
35 | */
36 | ({
37 | // defines a target property. must be a scene
38 | properties: {
39 | scene: {
40 | type:'enum',
41 | title: 'target scene',
42 | value:null,
43 | hints: {
44 | type:'node',
45 | nodeType:'scene'
46 | }
47 | },
48 | },
49 | start: function(evt) {
50 | //called when the program starts
51 | },
52 | enter: function(evt) {
53 | //called when entering a scene
54 | },
55 | tick: function(evt) {
56 | //called on every frame
57 | },
58 | exit: function(evt) {
59 | //called when exiting a scene
60 | },
61 | stop: function(evt) {
62 | //called when the program stops
63 | },
64 | })
65 | `
--------------------------------------------------------------------------------
/src/vr/ToasterManager.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by josh on 11/29/16.
3 | */
4 | module.exports = {
5 | adds: [],
6 | add: function add(note) {
7 | this.adds.forEach(cb => cb(note))
8 | },
9 | onAdd: function onAdd(cb) {
10 | this.adds.push(cb);
11 | },
12 | offAdd: function offAdd(cb) {
13 | this.adds = this.adds.filter(n => n !== cb)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/vr/ToasterNotification.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import * as ToasterManager from './ToasterManager'
3 |
4 | export class ToasterNotification extends Component {
5 | constructor(props) {
6 | super(props)
7 | this.state = {
8 | notification:'saved',
9 | visible:false
10 | }
11 | }
12 |
13 | add = (payload) => {
14 | this.setState({notification:payload, visible:true})
15 | setTimeout(this.hide,2000)
16 | }
17 |
18 | hide = () => this.setState({visible:false})
19 |
20 | componentWillMount() {
21 | ToasterManager.onAdd(this.add)
22 | }
23 | componentWillUnmount() {
24 | ToasterManager.offAdd(this.add)
25 | }
26 |
27 | render() {
28 | return {this.state.notification}
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/vr/TranslateControl.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import TranslationArrow from './TranslationArrow'
3 |
4 | export class TranslateControl extends THREE.Group {
5 | constructor() {
6 | super()
7 | this.handles = []
8 | this.handles.push(new TranslationArrow('X', this))
9 | this.handles.push(new TranslationArrow('Y', this))
10 | this.handles.push(new TranslationArrow('Z', this))
11 | this.handles.forEach(h => this.add(h))
12 | this.visible = false
13 | }
14 |
15 | attach(target, pointer) {
16 | this.target = target
17 | this.pointer = pointer
18 | this.position.copy(target.position)
19 | this.visible = true
20 | this.handles.forEach(h => h.attach())
21 | }
22 |
23 | detach() {
24 | this.target = null
25 | this.pointer = null
26 | this.visible = false
27 | this.handles.forEach(h => h.attach())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/vr/TranslationArrow.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import {POINTER_ENTER, POINTER_EXIT, POINTER_MOVE, POINTER_PRESS, POINTER_RELEASE} from 'webxr-boilerplate'
3 |
4 |
5 |
6 | const on = (elem,type,cb) => elem.addEventListener(type,cb)
7 | const off = (elem,type,cb) => elem.removeEventListener(type,cb)
8 | const toRad = (degrees) => degrees*Math.PI/180
9 |
10 | export default class TranslationArrow extends THREE.Group {
11 | constructor(axis, control) {
12 | super()
13 | this.axis = axis
14 | this.control = control
15 | this.makePlane()
16 | this.makeArrow()
17 | this.makeInputGrabber()
18 | }
19 |
20 | makePlane() {
21 | this.plane = new THREE.Mesh(
22 | new THREE.PlaneBufferGeometry(100, 100, 100, 100),
23 | new THREE.MeshBasicMaterial({visible: true, wireframe: true, side: THREE.DoubleSide})
24 | )
25 | if (this.axis === 'Z') this.plane.rotation.y = toRad(90)
26 | this.plane.userData.draggable = true
27 | this.plane.visible = false
28 | this.add(this.plane)
29 | }
30 |
31 | makeArrow() {
32 | this.arrow = new THREE.Mesh(
33 | new THREE.CylinderBufferGeometry(0.02, 0.02, 4),
34 | new THREE.MeshLambertMaterial({color: 'yellow'})
35 | )
36 | if (this.axis === 'X') this.arrow.rotation.z = toRad(90)
37 | if (this.axis === 'Y') this.arrow.rotation.z = toRad(180)
38 | if (this.axis === 'Z') this.arrow.rotation.x = toRad(90)
39 | this.add(this.arrow)
40 | }
41 |
42 | makeInputGrabber() {
43 | this.input = new THREE.Mesh(
44 | new THREE.CylinderBufferGeometry(0.1, 0.1, 4),
45 | new THREE.MeshLambertMaterial({color: 'green', visible: false})
46 | )
47 | if (this.axis === 'X') this.input.rotation.z = toRad(90)
48 | if (this.axis === 'Y') this.input.rotation.z = toRad(180)
49 | if (this.axis === 'Z') this.input.rotation.x = toRad(90)
50 | this.input.userData.clickable = true
51 | this.add(this.input)
52 | }
53 |
54 | attach() {
55 | on(this.input, POINTER_ENTER, this.startHover)
56 | on(this.input, POINTER_EXIT, this.endHover)
57 | on(this.input, POINTER_PRESS, this.beginDrag)
58 | }
59 |
60 | detach() {
61 | off(this.input, POINTER_ENTER, this.startHover)
62 | off(this.input, POINTER_EXIT, this.endHover)
63 | off(this.input, POINTER_PRESS, this.beginDrag)
64 | }
65 |
66 | startHover = () => this.arrow.material.color.set(0xffffff)
67 | endHover = () => this.arrow.material.color.set(0xffff00)
68 |
69 | beginDrag = (e) => {
70 | this.startPoint = this.parent.position.clone()
71 | this.startPoint.copy(e.intersection.point)
72 | this.parent.target.parent.worldToLocal(this.startPoint)
73 | this.oldFilter = this.parent.pointer.intersectionFilter
74 | this.parent.pointer.intersectionFilter = (obj) => obj.userData.draggable
75 | this.startPosition = this.parent.target.position.clone()
76 | this.plane.visible = true
77 | on(this.plane, POINTER_MOVE, this.updateDrag)
78 | on(this.plane, POINTER_RELEASE, this.endDrag)
79 | }
80 | updateDrag = (e) => {
81 | this.endPoint = e.intersection.point.clone()
82 | this.parent.target.parent.worldToLocal(this.endPoint)
83 | //neutralize y and z
84 | if (this.axis === 'X') {
85 | this.endPoint.y = this.startPoint.y
86 | this.endPoint.z = this.startPoint.z
87 | }
88 | if (this.axis === 'Y') {
89 | this.endPoint.x = this.startPoint.x
90 | this.endPoint.z = this.startPoint.z
91 | }
92 | if (this.axis === 'Z') {
93 | this.endPoint.x = this.startPoint.x
94 | this.endPoint.y = this.startPoint.y
95 | }
96 | const diff = this.endPoint.clone().sub(this.startPoint)
97 | const finalPoint = this.startPosition.clone().add(diff)
98 | this.parent.target.position.copy(finalPoint)
99 | this.parent.position.copy(finalPoint)
100 | }
101 | endDrag = (e) => {
102 | off(this.plane, POINTER_MOVE, this.updateDrag)
103 | off(this.plane, POINTER_RELEASE, this.endDrag)
104 | this.parent.pointer.intersectionFilter = this.oldFilter
105 | this.plane.visible = false
106 | this.parent.dispatchEvent({
107 | type: 'change',
108 | start: this.startPosition.clone(),
109 | end: this.parent.position.clone()
110 | })
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/vr/VREditor.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin:0;
3 | padding:0;
4 | overflow: hidden;
5 | }
6 | button, .button {
7 | color: black;
8 | background-color: white;
9 | border: 1px solid black;
10 | font-size: 100%;
11 | padding: 0.25em;
12 | margin: 0.25em;
13 | text-decoration: none;
14 | display: inline-block;
15 | }
16 | button:disabled {
17 | color: darkgray;
18 | background-color: gray;
19 | }
20 | #overlay {
21 | position: fixed;
22 | font-size: 5vh;
23 | width: 100vw;
24 | height: 100vh;
25 | background-color: rgba(255,255,255,0.5);
26 | text-align: center;
27 | display: flex;
28 | flex-direction: row;
29 | justify-content: center;
30 | align-content: center;
31 | }
32 | #overlay #inner {
33 | border: 0px solid black;
34 | background-color: white;
35 | width: 80vw;
36 | /*height: 80vh;*/
37 | display: flex;
38 | flex-direction: column;
39 | align-items: center;
40 | }
41 | #overlay #errors-dialog {
42 | border: 5px solid red;
43 | background-color: #ffdddd;
44 | width: 80vw;
45 | height: 60vh;
46 | position: absolute;
47 | overflow: auto;
48 | }
49 | #overlay #errors-dialog li {
50 | border:0px solid black;
51 | text-align: left;
52 | padding: 0.5em;
53 | }
54 | #loading-indicator {
55 | display: block;
56 | }
57 |
58 | #start-button {
59 | display: none;
60 | }
61 |
--------------------------------------------------------------------------------
/src/vr/VREmbedViewApp.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 |
3 | export default class VREmbedViewApp extends Component {
4 | render() {
5 | return this is an embedded view
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/vr/console.css:
--------------------------------------------------------------------------------
1 |
2 | .console {
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | .console .message-log {
8 | overflow: scroll;
9 | flex: 1.0;
10 | margin: 0;
11 | list-style: none;
12 | padding-left: 0;
13 | }
14 |
15 | .console .message-log li {
16 | padding: 0.5rem 0.5rem;
17 | }
18 |
19 | .console .message-log li b {
20 | border-radius: 0.5rem;
21 | padding: 0.25rem 0.5rem;
22 | margin: 0.25rem;
23 | font-size: 90%;
24 | }
25 |
26 | .console .message-log li b.log {
27 | background-color: lightgoldenrodyellow;
28 | }
29 | .console .message-log li b.error {
30 | border: 1px solid lightpink;
31 | background-color: lightpink;
32 | }
33 |
34 | .console .message-log li b.location.local {
35 | background-color: aqua;
36 | }
37 | .console .message-log li b.location.remote {
38 | background-color: pink;
39 | }
40 |
41 |
42 |
43 |
44 |
45 | .map-icon {
46 | border: 5px solid red;
47 | }
48 | #mapid {
49 | z-index: 0;
50 | }
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/vr/cycle.js:
--------------------------------------------------------------------------------
1 | /*
2 | cycle.js
3 | 2018-05-15
4 |
5 | Public Domain.
6 |
7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
8 |
9 | This code should be minified before deployment.
10 | See http://javascript.crockford.com/jsmin.html
11 |
12 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
13 | NOT CONTROL.
14 | */
15 |
16 | // The file uses the WeakMap feature of ES6.
17 |
18 | /*jslint eval */
19 |
20 | /*property
21 | $ref, decycle, forEach, get, indexOf, isArray, keys, length, push,
22 | retrocycle, set, stringify, test
23 | */
24 |
25 | export function decycle(object, replacer) {
26 | "use strict";
27 |
28 | // Make a deep copy of an object or array, assuring that there is at most
29 | // one instance of each object or array in the resulting structure. The
30 | // duplicate references (which might be forming cycles) are replaced with
31 | // an object of the form
32 |
33 | // {"$ref": PATH}
34 |
35 | // where the PATH is a JSONPath string that locates the first occurance.
36 |
37 | // So,
38 |
39 | // var a = [];
40 | // a[0] = a;
41 | // return JSON.stringify(JSON.decycle(a));
42 |
43 | // produces the string '[{"$ref":"$"}]'.
44 |
45 | // If a replacer function is provided, then it will be called for each value.
46 | // A replacer function receives a value and returns a replacement value.
47 |
48 | // JSONPath is used to locate the unique object. $ indicates the top level of
49 | // the object or array. [NUMBER] or [STRING] indicates a child element or
50 | // property.
51 |
52 | var objects = new WeakMap(); // object to path mappings
53 |
54 | return (function derez(value, path) {
55 |
56 | // The derez function recurses through the object, producing the deep copy.
57 |
58 | var old_path; // The path of an earlier occurance of value
59 | var nu; // The new object or array
60 |
61 | // If a replacer function was provided, then call it to get a replacement value.
62 |
63 | if (replacer !== undefined) {
64 | value = replacer(value);
65 | }
66 |
67 | // typeof null === "object", so go on if this value is really an object but not
68 | // one of the weird builtin objects.
69 |
70 | if (
71 | typeof value === "object"
72 | && value !== null
73 | && !(value instanceof Boolean)
74 | && !(value instanceof Date)
75 | && !(value instanceof Number)
76 | && !(value instanceof RegExp)
77 | && !(value instanceof String)
78 | ) {
79 |
80 | // If the value is an object or array, look to see if we have already
81 | // encountered it. If so, return a {"$ref":PATH} object. This uses an
82 | // ES6 WeakMap.
83 |
84 | old_path = objects.get(value);
85 | if (old_path !== undefined) {
86 | return {$ref: old_path};
87 | }
88 |
89 | // Otherwise, accumulate the unique value and its path.
90 |
91 | objects.set(value, path);
92 |
93 | // If it is an array, replicate the array.
94 |
95 | if (Array.isArray(value)) {
96 | nu = [];
97 | value.forEach(function (element, i) {
98 | nu[i] = derez(element, path + "[" + i + "]");
99 | });
100 | } else {
101 |
102 | // If it is an object, replicate the object.
103 |
104 | nu = {};
105 | Object.keys(value).forEach(function (name) {
106 | nu[name] = derez(
107 | value[name],
108 | path + "[" + JSON.stringify(name) + "]"
109 | );
110 | });
111 | }
112 | return nu;
113 | }
114 | return value;
115 | }(object, "$"));
116 | }
117 |
118 |
119 |
120 | if (typeof JSON.retrocycle !== "function") {
121 | JSON.retrocycle = function retrocycle($) {
122 | "use strict";
123 |
124 | // Restore an object that was reduced by decycle. Members whose values are
125 | // objects of the form
126 | // {$ref: PATH}
127 | // are replaced with references to the value found by the PATH. This will
128 | // restore cycles. The object will be mutated.
129 |
130 | // The eval function is used to locate the values described by a PATH. The
131 | // root object is kept in a $ variable. A regular expression is used to
132 | // assure that the PATH is extremely well formed. The regexp contains nested
133 | // * quantifiers. That has been known to have extremely bad performance
134 | // problems on some browsers for very long strings. A PATH is expected to be
135 | // reasonably short. A PATH is allowed to belong to a very restricted subset of
136 | // Goessner's JSONPath.
137 |
138 | // So,
139 | // var s = '[{"$ref":"$"}]';
140 | // return JSON.retrocycle(JSON.parse(s));
141 | // produces an array containing a single element which is the array itself.
142 |
143 | var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
144 |
145 | (function rez(value) {
146 |
147 | // The rez function walks recursively through the object looking for $ref
148 | // properties. When it finds one that has a value that is a path, then it
149 | // replaces the $ref object with a reference to the value that is found by
150 | // the path.
151 |
152 | if (value && typeof value === "object") {
153 | if (Array.isArray(value)) {
154 | value.forEach(function (element, i) {
155 | if (typeof element === "object" && element !== null) {
156 | var path = element.$ref;
157 | if (typeof path === "string" && px.test(path)) {
158 | value[i] = eval(path);
159 | } else {
160 | rez(element);
161 | }
162 | }
163 | });
164 | } else {
165 | Object.keys(value).forEach(function (name) {
166 | var item = value[name];
167 | if (typeof item === "object" && item !== null) {
168 | var path = item.$ref;
169 | if (typeof path === "string" && px.test(path)) {
170 | value[name] = eval(path);
171 | } else {
172 | rez(item);
173 | }
174 | }
175 | });
176 | }
177 | }
178 | }($));
179 | return $;
180 | };
181 | }
182 |
--------------------------------------------------------------------------------
/src/vr/defs/BG360Def.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from '../../syncgraph/utils'
2 | import {NONE_ASSET, OBJ_TYPES, PROP_DEFS} from '../Common'
3 | import * as THREE from 'three'
4 | import {MeshLambertMaterial} from 'three'
5 | import {DoubleSide} from 'three'
6 |
7 | function customize(node,mat,obj) {
8 | mat.onBeforeCompile = (shader) => {
9 | //add uniform
10 | shader.uniforms.imageOffsetAngle = { value: obj.imageOffsetAngle }
11 | shader.uniforms.imageCropStartAngle = { value: obj.imageCropStartAngle }
12 | shader.uniforms.imageCropEndAngle = { value: obj.imageCropEndAngle }
13 | //prepend to the shader the defs
14 | shader.vertexShader = `
15 | uniform float imageOffsetAngle;
16 | varying float vImageOffsetAngle;
17 | uniform float imageCropStartAngle;
18 | varying float vImageCropStartAngle;
19 | uniform float imageCropEndAngle;
20 | varying float vImageCropEndAngle;
21 | ` + shader.vertexShader
22 |
23 | //copy into fragment shader
24 | shader.vertexShader = shader.vertexShader.replace('#include ',`
25 | #include
26 | vImageOffsetAngle = imageOffsetAngle;
27 | vImageCropStartAngle = imageCropStartAngle;
28 | vImageCropEndAngle = imageCropEndAngle;
29 | `)
30 |
31 | //prepend to the fragment shader the defs
32 | shader.fragmentShader = `
33 | varying float vImageOffsetAngle;
34 | varying float vImageCropStartAngle;
35 | varying float vImageCropEndAngle;
36 | `+shader.fragmentShader
37 | //use in the shader
38 | shader.fragmentShader = shader.fragmentShader.replace('#include ',`
39 | if(vUv.x < vImageCropStartAngle) discard;
40 | if(vUv.x > vImageCropEndAngle) discard;
41 | // gl_FragColor.r = vUv.x;
42 | // vec3 jv = vec3(cos(gl_FragCoord.x/50.0));
43 | // vec3 empty = vec3(0.0,0.0,0.0);//vec3(jv,jv,jv)
44 | // gl_FragColor.rgb = mix(empty,diffuseColor.rgb,jv);
45 | `)
46 | node.userData.matShader = shader
47 | }
48 | }
49 |
50 | export default class BG360Def {
51 | make(graph, scene) {
52 | if(!scene.id) throw new Error("can't create sphere w/ missing parent")
53 | return fetchGraphObject(graph,graph.createObject({
54 | type:OBJ_TYPES.bg360,
55 | title:'background',
56 | visible:true,
57 | asset:NONE_ASSET.id,
58 | transparent:false,
59 | parent:scene.id,
60 | imageOffsetAngle:0.0, //offset in UV coords, 0 to 1
61 | imageCropStartAngle:0.0, //crop left edge of image
62 | imageCropEndAngle:1.0, //crop right edge of image
63 | }))
64 | }
65 | makeNode(obj, provider) {
66 | const mat = new THREE.MeshLambertMaterial({color: 'white', side: THREE.BackSide})
67 | const node = new THREE.Mesh(new THREE.SphereBufferGeometry(20.0, 50,50),mat)
68 | this.attachAsset(node, obj, provider)
69 | customize(node,mat,obj)
70 | node.name = obj.title
71 | node.userData.clickable = true
72 | node.visible = obj.visible
73 | return node
74 | }
75 | updateProperty(node, obj, op, provider) {
76 | if (op.name === PROP_DEFS.asset.key || op.name === PROP_DEFS.transparent.key) return this.attachAsset(node, obj, provider)
77 | if(node.userData.matShader) {
78 | if (op.name === 'imageOffsetAngle') node.userData.matShader.uniforms.imageOffsetAngle.value = op.value
79 | if (op.name === 'imageCropStartAngle') node.userData.matShader.uniforms.imageCropStartAngle.value = op.value
80 | if (op.name === 'imageCropEndAngle') node.userData.matShader.uniforms.imageCropEndAngle.value = op.value
81 | } else {
82 | console.warn("BG360: no shader reference?!")
83 | }
84 | }
85 |
86 | attachAsset(node, obj, provider) {
87 | if(obj.asset === NONE_ASSET.id) {
88 | node.material = new MeshLambertMaterial({side:DoubleSide})
89 | return
90 | }
91 | provider.assetsManager.getTexture(obj.asset).then(tex => {
92 | provider.getLogger().log("loadded asset",obj.asset)
93 | provider.getLogger().log(tex)
94 | if(!tex) provider.getLogger().error("error loading asset",obj.asset)
95 | const opts = {
96 | side: DoubleSide,
97 | map:tex
98 | }
99 | if(obj.transparent) {
100 | opts.transparent = true
101 | opts.alphaTest = 0.5
102 | }
103 | node.material = new MeshLambertMaterial(opts)
104 | }).catch(err => {
105 | provider.getLogger().error('error somwhere',err.message,err)
106 | })
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/src/vr/defs/CloneDef.js:
--------------------------------------------------------------------------------
1 | import ObjectDef from './ObjectDef'
2 | import {NONE_ASSET, OBJ_TYPES} from '../Common'
3 | import {fetchGraphObject} from '../../syncgraph/utils'
4 | import * as THREE from 'three'
5 | import {MeshLambertMaterial} from 'three'
6 |
7 | let COUNTER = 0
8 |
9 |
10 | export default class CloneDef extends ObjectDef {
11 | make(graph, scene) {
12 | if(!scene.id) throw new Error("can't create cube w/ missing parent")
13 | return fetchGraphObject(graph,graph.createObject({
14 | type:OBJ_TYPES.clone,
15 | title:'clone '+COUNTER++,
16 | visible:true,
17 | tx:0, ty:0, tz:0,
18 | rx:0, ry:0, rz:0,
19 | sx:1, sy:1, sz:1,
20 | children:graph.createArray(),
21 | cloneTarget:NONE_ASSET.id,
22 | parent:scene.id
23 | }))
24 | }
25 |
26 | makeNode(obj,provider) {
27 | const node = new THREE.Group()
28 | node.name = obj.title
29 | const clicker = new THREE.Mesh(
30 | new THREE.SphereBufferGeometry(1),
31 | new MeshLambertMaterial({color: "red", transparent: true, opacity: 0.2})
32 | )
33 | clicker.material.visible = true
34 | clicker.userData.clickable = true
35 | node.userData.clicker = clicker
36 | node.add(node.userData.clicker)
37 | node.position.set(obj.tx, obj.ty, obj.tz)
38 | node.rotation.set(obj.rx, obj.ry, obj.rz)
39 | node.scale.set(obj.sx, obj.sy, obj.sz)
40 | node.visible = obj.visible
41 | node.enter = (evt, scriptManager) => {
42 | clicker.visible = false
43 | const cloneTarget = scriptManager.sgp.getGraphObjectById(obj.cloneTarget)
44 | const cloneNode = scriptManager.sgp.getThreeObject(cloneTarget)
45 | const theClone = cloneNode.clone()
46 | node.add(theClone)
47 | node.userData.theClone = theClone
48 | node.userData.cloneTarget = cloneTarget
49 |
50 | if(node.userData.theClone.enter) node.userData.theClone.enter(evt,scriptManager)
51 | node.userData.cloneTarget.find(child => scriptManager.fireSceneLifecycleEventAtChild('enter',evt,child,obj.id))
52 | }
53 | node.tick = (evt,scriptManager) => {
54 | if(node.userData.theClone.tick) node.userData.theClone.tick(evt,scriptManager)
55 | node.userData.cloneTarget.find(child => scriptManager.fireSceneLifecycleEventAtChild('tick',evt,child,obj.id))
56 | }
57 | node.exit = (evt,scriptManager) => {
58 | if(node.userData.theClone.exit) node.userData.theClone.exit(evt,scriptManager)
59 | node.userData.cloneTarget.find(child => scriptManager.fireSceneLifecycleEventAtChild('exit',evt,child,obj.id))
60 |
61 | clicker.visible = true
62 | node.remove(node.userData.theClone)
63 | node.userData.theClone = null
64 | }
65 | return node
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/vr/defs/CubeDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import ObjectDef from './ObjectDef'
3 | import {NONE_ASSET, OBJ_TYPES, PROP_DEFS} from '../Common'
4 | import {BoxGeometry, Mesh, MeshLambertMaterial} from 'three'
5 |
6 | let COUNTER = 0
7 |
8 | export default class CubeDef extends ObjectDef {
9 | make(graph, scene) {
10 | if(!scene.id) throw new Error("can't create cube w/ missing parent")
11 | return fetchGraphObject(graph,graph.createObject({
12 | type:OBJ_TYPES.cube,
13 | title:'cube '+COUNTER++,
14 | visible:true,
15 | width:0.3, height:0.3, depth:0.3,
16 | tx:0, ty:0, tz:0,
17 | rx:0, ry:0, rz:0,
18 | sx:1, sy:1, sz:1,
19 | color:'#00ff00',
20 | children:graph.createArray(),
21 | asset:NONE_ASSET.id,
22 | transparent:false,
23 | parent:scene.id
24 | }))
25 | }
26 | makeNode(obj, provider) {
27 | const node = new Mesh(
28 | new BoxGeometry(obj.width, obj.height, obj.depth),
29 | new MeshLambertMaterial({color: obj.color})
30 | )
31 | node.name = obj.title
32 | node.userData.clickable = true
33 | node.position.set(obj.tx, obj.ty, obj.tz)
34 | node.rotation.set(obj.rx,obj.ry,obj.rz)
35 | node.scale.set(obj.sx,obj.sy,obj.sz)
36 | node.visible = obj.visible
37 | this.attachAsset(node, obj, provider)
38 | return node
39 | }
40 |
41 |
42 |
43 | updateProperty(node, obj, op, provider) {
44 | if (op.name === 'width' || op.name === 'height' || op.name === 'depth') {
45 | node.geometry = new BoxGeometry(obj.width, obj.height, obj.depth)
46 | return
47 | }
48 | if (op.name === PROP_DEFS.asset.key || op.name === PROP_DEFS.transparent.key) return this.attachAsset(node, obj, provider)
49 | return super.updateProperty(node,obj,op,provider)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/vr/defs/GeoAnchorDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import {Group, Mesh, MeshLambertMaterial, SphereBufferGeometry} from 'three'
3 | import ObjectDef from './ObjectDef'
4 | import {OBJ_TYPES, REC_TYPES} from '../Common'
5 | let COUNTER = 0
6 |
7 | export default class GeoAnchorDef extends ObjectDef {
8 | make(graph, scene) {
9 | if (!scene.id) throw new Error("can't create geo anchor w/ missing parent")
10 | return fetchGraphObject(graph, graph.createObject({
11 | type: OBJ_TYPES.geoanchor,
12 | title: 'geo anchor ' + COUNTER++,
13 | visible: true,
14 | tx: 0, ty: 0, tz: 0,
15 | rx: 0, ry: 0, rz: 0,
16 | sx: 1, sy: 1, sz: 1,
17 | color: '#00ff00',
18 | children: graph.createArray(),
19 | targetGeoLocation: null,
20 | recType: REC_TYPES.SCENE_START,
21 | parent: scene.id
22 | }))
23 | }
24 | makeNode(obj) {
25 | const node = new Group()
26 | node.name = obj.title
27 | node.userData.clickable = true
28 | node.position.set(obj.tx, obj.ty, obj.tz)
29 | node.rotation.set(obj.rx,obj.ry,obj.rz)
30 | node.scale.set(obj.sx,obj.sy,obj.sz)
31 | node.visible = obj.visible
32 |
33 | const clicker = new Mesh(
34 | new SphereBufferGeometry(1),
35 | new MeshLambertMaterial({color:"red", transparent:true, opacity: 0.2})
36 | )
37 | clicker.material.visible = true
38 | clicker.userData.clickable = true
39 | node.userData.clicker = clicker
40 | node.add(clicker)
41 | node.enter = (evt, scriptManager) => {
42 | clicker.visible = false
43 | node.visible = false
44 | node.userData.info = {
45 | location:scriptManager.sgp.getGraphObjectById(obj.targetGeoLocation),
46 | recType:obj.recType,
47 | object:obj,
48 | node:node,
49 | callback:(info) => {
50 | scriptManager.fireEventAtTarget(obj, 'localized', info)
51 | node.visible = true
52 | }
53 | }
54 | scriptManager.sgp.startGeoTracker(node.userData.info)
55 | }
56 | node.exit = (evt, scriptManager) => {
57 | clicker.visible = true
58 | scriptManager.sgp.stopGeoTracker(node.userData.info)
59 | }
60 | return node
61 | }
62 | }
--------------------------------------------------------------------------------
/src/vr/defs/GroupDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import {Group} from 'three'
3 | import ObjectDef from './ObjectDef'
4 | import {OBJ_TYPES} from '../Common'
5 |
6 | let COUNTER = 0
7 |
8 | export default class GroupDef extends ObjectDef {
9 | make(graph, scene) {
10 | if(!scene.id) throw new Error("can't create cube w/ missing parent")
11 | return fetchGraphObject(graph,graph.createObject({
12 | type:OBJ_TYPES.group,
13 | title:'group '+COUNTER++,
14 | visible:true,
15 | tx:0, ty:0, tz:0,
16 | rx:0, ry:0, rz:0,
17 | sx:1, sy:1, sz:1,
18 | color:'#00ff00',
19 | children:graph.createArray(),
20 | parent:scene.id
21 | }))
22 | }
23 | makeNode(obj) {
24 | const node = new Group()
25 | node.name = obj.title
26 | node.userData.clickable = true
27 | node.position.set(obj.tx, obj.ty, obj.tz)
28 | node.rotation.set(obj.rx,obj.ry,obj.rz)
29 | node.scale.set(obj.sx,obj.sy,obj.sz)
30 | node.visible = obj.visible
31 | return node
32 | }
33 |
34 | updateProperty(node, obj, op, provider) {
35 | return super.updateProperty(node,obj,op,provider)
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/vr/defs/HudAnchorDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import {Group, Mesh, MeshLambertMaterial, SphereBufferGeometry} from 'three'
3 | import ObjectDef from './ObjectDef'
4 | import {OBJ_TYPES} from '../Common'
5 |
6 | let COUNTER = 0
7 |
8 | export class HudAnchorDef extends ObjectDef {
9 | make(graph, scene) {
10 | if(!scene.id) throw new Error("can't add hudanchor w/ missing parent")
11 | return fetchGraphObject(graph,graph.createObject({
12 | type:OBJ_TYPES.hudanchor,
13 | title:'hud anchor '+COUNTER++,
14 | visible:true,
15 | tx:0, ty:0, tz:0,
16 | rx:0, ry:0, rz:0,
17 | sx:1, sy:1, sz:1,
18 | children:graph.createArray(),
19 | parent:scene.id
20 | }))
21 | }
22 | makeNode(obj, provider) {
23 | const node = new Group()
24 | node.name = obj.title
25 | node.userData.clickable = true
26 | node.position.set(obj.tx, obj.ty, obj.tz)
27 | node.rotation.set(obj.rx,obj.ry,obj.rz)
28 | node.scale.set(obj.sx,obj.sy,obj.sz)
29 | node.visible = obj.visible
30 |
31 | const clicker = new Mesh(
32 | new SphereBufferGeometry(1),
33 | new MeshLambertMaterial({color:"red", transparent:true, opacity: 0.2})
34 | )
35 | clicker.material.visible = true
36 | clicker.userData.clickable = true
37 | node.userData.clicker = clicker
38 | node.add(clicker)
39 |
40 | node.enter = (evt,scriptManager) => {
41 | node.parent.remove(node)
42 | node.position.z = 0
43 | clicker.visible = false
44 | scriptManager.sgp.getCamera().add(node)
45 | }
46 | node.exit = (evt,scriptManager) => {
47 | scriptManager.sgp.getCamera().remove(node)
48 | clicker.visible = true
49 | const scene = scriptManager.sgp.getThreeObject(obj.parent)
50 | scene.add(node)
51 | node.position.set(obj.tx, obj.ty, obj.tz)
52 | node.rotation.set(obj.rx,obj.ry,obj.rz)
53 | node.scale.set(obj.sx,obj.sy,obj.sz)
54 | }
55 | return node
56 | }
57 |
58 | updateProperty(node, obj, op, provider) {
59 | return super.updateProperty(node,obj,op,provider)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/vr/defs/Image2DDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from '../../syncgraph/utils'
2 | import {NONE_ASSET, OBJ_TYPES, PROP_DEFS} from '../Common'
3 | import * as THREE from 'three'
4 | import ObjectDef from './ObjectDef'
5 |
6 | export default class Image2DDef extends ObjectDef {
7 | make(graph, scene) {
8 | if(!scene.id) throw new Error("can't create Image2D with missing parent")
9 | return fetchGraphObject(graph,graph.createObject({
10 | type:OBJ_TYPES.img2d,
11 | title:'image',
12 | visible:true,
13 | width:0.5,
14 | ratio:1,
15 | tx:0, ty:0, tz:0,
16 | rx:0, ry:0, rz:0,
17 | sx:1, sy:1, sz:1,
18 | asset:NONE_ASSET.id,
19 | transparent:false,
20 | parent:scene.id,
21 | }))
22 | }
23 | makeNode(obj, provider) {
24 | const node = new THREE.Mesh(
25 | new THREE.PlaneBufferGeometry(obj.width, obj.width*obj.ratio),
26 | new THREE.MeshLambertMaterial({color: 'white', side: THREE.DoubleSide})
27 | )
28 | node.name = obj.title
29 | node.userData.clickable = true
30 | // on(node,POINTER_CLICK,e =>SelectionManager.setSelection(node.userData.graphid))
31 | node.position.set(obj.tx, obj.ty, obj.tz)
32 | node.rotation.set(obj.rx,obj.ry,obj.rz)
33 | node.scale.set(obj.sx,obj.sy,obj.sz)
34 | node.visible = obj.visible
35 | this.attachAsset(node, obj, provider)
36 | return node
37 | }
38 | updateProperty(node, obj, op, provider) {
39 | if (op.name === PROP_DEFS.asset.key || op.name === PROP_DEFS.transparent.key) return this.attachAsset(node, obj, provider)
40 | if( op.name === PROP_DEFS.width.key || op.name === 'ratio') {
41 | node.geometry = new THREE.PlaneBufferGeometry(obj.width, obj.width*(1/obj.ratio))
42 | return
43 | }
44 | return super.updateProperty(node,obj,op,provider)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/vr/defs/ImageAnchorDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import {Group, Mesh, MeshLambertMaterial, SphereBufferGeometry, PlaneBufferGeometry, TextureLoader, DoubleSide} from 'three'
3 | import ObjectDef from './ObjectDef'
4 | import {OBJ_TYPES, REC_TYPES, PROP_DEFS, toRad} from '../Common'
5 |
6 | let COUNTER = 0
7 |
8 | export default class ImageAnchorDef extends ObjectDef {
9 | make(graph, scene) {
10 | if(!scene.id) throw new Error("can't imageanchor w/ missing parent")
11 | return fetchGraphObject(graph,graph.createObject({
12 | type:OBJ_TYPES.imageanchor,
13 | title:'image anchor '+COUNTER++,
14 | visible:true,
15 | reactivate: false,
16 | tx:0, ty:0, tz:0,
17 | rx:0, ry:0, rz:0,
18 | sx:1, sy:1, sz:1,
19 | color:'#00ff00',
20 | children:graph.createArray(),
21 | targetImage:null,
22 | imageRealworldWidth:1,
23 | recType:REC_TYPES.SCENE_START,
24 | parent:scene.id
25 | }))
26 | }
27 | makeNode(obj, provider) {
28 | const node = new Group()
29 | node.name = obj.title
30 | node.userData.clickable = true
31 | node.position.set(obj.tx, obj.ty, obj.tz)
32 | node.rotation.set(obj.rx,obj.ry,obj.rz)
33 | node.scale.set(obj.sx,obj.sy,obj.sz)
34 | node.visible = obj.visible
35 |
36 | const clicker = new Mesh(
37 | new SphereBufferGeometry(1),
38 | new MeshLambertMaterial({color:"red", transparent:true, opacity: 0.2})
39 | )
40 | clicker.material.visible = true
41 | clicker.userData.clickable = true
42 | node.userData.clicker = clicker
43 | node.add(clicker)
44 |
45 | const preview = new Mesh(
46 | new PlaneBufferGeometry(1,1),
47 | new MeshLambertMaterial({color:'white',transparent:true, opacity: 0.5, side: DoubleSide})
48 | )
49 | preview.rotation.x = toRad(90)
50 | node.add(preview)
51 | node.userData.preview = preview
52 | this.updateImagePreview(node,obj,provider)
53 |
54 | node.enter = (evt, scriptManager) => {
55 | clicker.visible = false
56 | node.visible = false
57 | preview.visible = false
58 | node.userData.info = {
59 | image: scriptManager.sgp.getGraphObjectById(obj.targetImage),
60 | imageRealworldWidth: obj.imageRealworldWidth,
61 | reactivate: obj.reactivate,
62 | recType: obj.recType,
63 | object: obj,
64 | node: node,
65 | callback: (info) => {
66 | scriptManager.fireEventAtTarget(obj, 'recognized', info)
67 | node.visible = true
68 | }
69 | }
70 | scriptManager.sgp.startImageRecognizer(node.userData.info)
71 | }
72 | node.exit = (evt, scriptManager) => {
73 | clicker.visible = true
74 | preview.visible = true
75 | scriptManager.sgp.stopImageRecognizer(node.userData.info)
76 | }
77 | return node
78 | }
79 |
80 | updateProperty(node, obj, op, provider) {
81 | if(op.name === PROP_DEFS.targetImage.key) {
82 | return this.updateImagePreview(node,obj,provider)
83 | }
84 | if(op.name === PROP_DEFS.imageRealworldWidth.key) {
85 | return this.updateImagePreview(node,obj,provider)
86 | }
87 | return super.updateProperty(node,obj,op,provider)
88 | }
89 |
90 | updateImagePreview(node,obj,provider) {
91 | const targetImage = provider.accessObject(obj.targetImage)
92 | if(targetImage.exists()) {
93 | const url = provider.assetsManager.getAssetURL(targetImage)
94 | provider.getLogger().log("ImageAnchor loading the asset url",url)
95 | const tex = new TextureLoader().load(url)
96 | node.userData.preview.material = new MeshLambertMaterial({color:'white',transparent:true, opacity:0.5, side:DoubleSide, map:tex})
97 | let height = (targetImage.height / targetImage.width)
98 | let width = 1
99 | node.userData.preview.geometry = new PlaneBufferGeometry(width,height)
100 | //console.log(obj)
101 | let scale = obj.imageRealworldWidth
102 | node.userData.preview.scale.set(scale,scale,scale)
103 | }
104 | if(provider.accessObject(obj.targetImage).exists()) {
105 | node.userData.preview.visible = true
106 | node.userData.clicker.visible = false
107 | } else {
108 | node.userData.preview.visible = false
109 | node.userData.clicker.visible = true
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/vr/defs/LocalAnchorDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import {Group, Mesh, MeshLambertMaterial, SphereBufferGeometry} from 'three'
3 | import ObjectDef from './ObjectDef'
4 | import {OBJ_TYPES, REC_TYPES} from '../Common'
5 |
6 | let COUNTER = 0
7 |
8 | export default class LocalAnchorDef extends ObjectDef {
9 | make(graph, scene) {
10 | if(!scene.id) throw new Error("can't localanchor w/ missing parent")
11 | return fetchGraphObject(graph,graph.createObject({
12 | type:OBJ_TYPES.localanchor,
13 | title:'localanchor '+COUNTER++,
14 | visible:true,
15 | tx:0, ty:0, tz:0,
16 | rx:0, ry:0, rz:0,
17 | sx:1, sy:1, sz:1,
18 | color:'#00ff00',
19 | children:graph.createArray(),
20 | recType:REC_TYPES.SCENE_START,
21 | parent:scene.id
22 | }))
23 | }
24 | makeNode(obj, provider) {
25 | const node = new Group()
26 | node.name = obj.title
27 | node.userData.clickable = true
28 | node.position.set(obj.tx, obj.ty, obj.tz)
29 | node.rotation.set(obj.rx,obj.ry,obj.rz)
30 | node.scale.set(obj.sx,obj.sy,obj.sz)
31 | node.visible = true
32 |
33 | const clicker = new Mesh(
34 | new SphereBufferGeometry(1),
35 | new MeshLambertMaterial({color:"green", transparent:true, opacity: 0.2})
36 | )
37 | clicker.material.visible = true
38 | clicker.userData.clickable = true
39 | node.userData.clicker = clicker
40 | node.add(clicker)
41 |
42 | node.enter = (evt, scriptManager) => {
43 | clicker.visible = false
44 | node.visible = true
45 | node.userData.info = {
46 | recType: obj.recType,
47 | object: obj,
48 | node: node,
49 | callback: (info) => {
50 | node.visible = true
51 | }
52 | }
53 | scriptManager.sgp.startLocalAnchor(node.userData.info)
54 | }
55 | node.exit = (evt, scriptManager) => {
56 | clicker.visible = true
57 | scriptManager.sgp.stopLocalAnchor(node.userData.info)
58 | }
59 | return node
60 | }
61 |
62 | updateProperty(node, obj, op, provider) {
63 | return super.updateProperty(node,obj,op,provider)
64 | }
65 |
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/vr/defs/ObjectDef.js:
--------------------------------------------------------------------------------
1 | import {NONE_ASSET} from '../Common'
2 | import {DoubleSide, MeshLambertMaterial} from 'three'
3 |
4 | export default class ObjectDef {
5 | updateProperty(node, obj, op, provider) {
6 | if (op.name === 'tx') node.position.x = parseFloat(op.value)
7 | if (op.name === 'ty') node.position.y = parseFloat(op.value)
8 | if (op.name === 'tz') node.position.z = parseFloat(op.value)
9 | if (op.name === 'rx') node.rotation.x = parseFloat(op.value)
10 | if (op.name === 'ry') node.rotation.y = parseFloat(op.value)
11 | if (op.name === 'rz') node.rotation.z = parseFloat(op.value)
12 | if (op.name === 'sx') node.scale.x = parseFloat(op.value)
13 | if (op.name === 'sy') node.scale.y = parseFloat(op.value)
14 | if (op.name === 'sz') node.scale.z = parseFloat(op.value)
15 | if (op.name === 'visible') node.visible = op.value
16 |
17 | if (op.name === 'color') {
18 | let color = op.value
19 | if(color.indexOf('#') === 0) color = color.substring(1)
20 | node.material.color.set(parseInt(color,16))
21 | }
22 |
23 | }
24 | reset(node, obj) {
25 | node.position.set(obj.tx,obj.ty,obj.tz)
26 | node.visible = obj.visible
27 | }
28 |
29 | attachAsset(node, obj, provider) {
30 | if(obj.asset === NONE_ASSET.id) {
31 | node.material = new MeshLambertMaterial({color: obj.color, side:DoubleSide})
32 | return
33 | }
34 | provider.assetsManager.getTexture(obj.asset).then(tex => {
35 | provider.getLogger().log("loadded asset",obj.asset)
36 | provider.getLogger().log(tex)
37 | if(!tex) provider.getLogger().error("error loading asset",obj.asset)
38 | const opts = {
39 | color: obj.color,
40 | side: DoubleSide,
41 | map:tex
42 | }
43 | if(obj.transparent) {
44 | opts.transparent = true
45 | opts.alphaTest = 0.5
46 | }
47 | node.material = new MeshLambertMaterial(opts)
48 | }).catch(err => {
49 | provider.getLogger().error('error somwhere',err.message,err)
50 | })
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/vr/defs/ParticlesDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import {AdditiveBlending, Color, TextureLoader, Vector3} from 'three'
3 | import ObjectDef from './ObjectDef'
4 | import {NONE_ASSET, OBJ_TYPES, PROP_DEFS} from '../Common'
5 | import GPUParticles from './GPUParticles'
6 |
7 | export const rand = (min,max) => Math.random()*(max-min) + min
8 |
9 | let COUNTER = 0
10 |
11 | export default class ParticlesDef extends ObjectDef {
12 | make(graph, scene) {
13 | if(!scene.id) throw new Error("can't create cube w/ missing parent")
14 | return fetchGraphObject(graph,graph.createObject({
15 | type:OBJ_TYPES.particles,
16 | title:'particles '+COUNTER++,
17 | visible:true,
18 | tx:0, ty:0, tz:0,
19 | rx:0, ry:0, rz:0,
20 | sx:1, sy:1, sz:1,
21 | children:graph.createArray(),
22 | pointSize:10.0,
23 | lifetime:3.0,
24 | startColor:'#ff0000',
25 | endColor:'#0000ff',
26 | parent:scene.id,
27 | texture:NONE_ASSET.id,
28 | }))
29 | }
30 | makeNode(obj, provider) {
31 | const options = {
32 | velocity: new Vector3(0,1,0),
33 | position: new Vector3(0,0,0),
34 | size:obj.pointSize,
35 | lifetime:obj.lifetime,
36 | color: new Color(1.0,0.0,1.0),
37 | endColor: new Color(0,1,1),
38 | }
39 |
40 | const node = new GPUParticles({
41 | maxParticles: 10000,
42 | position: new Vector3(0,0,0),
43 | positionRandomness: 0.0,
44 | baseVelocity: new Vector3(0.0, 0.0, -1),
45 | velocity: new Vector3(0.0, 0.0, 0.0),
46 | velocityRandomness: 1.0,
47 | acceleration: new Vector3(0,0.0,0),
48 | baseColor: new Color(1.0,1.0,0.5),
49 | color: new Color(1.0,0,0),
50 | colorRandomness: 0.5,
51 | lifetime: 3,
52 | size: 10,
53 | sizeRandomness: 1.0,
54 | blending: AdditiveBlending,
55 | onTick: (system, time) => {
56 | options.velocity.set(rand(-1,1),1,rand(-1,1))
57 | system.spawnParticle(options);
58 | }
59 | })
60 | node.tick = function(evt) {
61 | node.update(evt.time)
62 | }
63 | this.attachTexture(node,obj,provider)
64 | node.userData.options = options
65 | node.name = obj.title
66 | node.userData.clickable = false
67 | node.position.set(obj.tx, obj.ty, obj.tz)
68 | node.rotation.set(obj.rx,obj.ry,obj.rz)
69 | node.scale.set(obj.sx,obj.sy,obj.sz)
70 | node.visible = obj.visible
71 | return node
72 | }
73 |
74 | updateProperty(node, obj, op, provider) {
75 | if(op.name === PROP_DEFS.pointSize.key) return node.userData.options.size = obj.pointSize
76 | if(op.name === PROP_DEFS.lifetime.key) return node.userData.options.lifetime = obj.lifetime
77 | if(op.name === PROP_DEFS.startColor.key) return node.userData.options.color.set(obj.startColor)
78 | if(op.name === PROP_DEFS.endColor.key) return node.userData.options.endColor.set(obj.endColor)
79 | if(op.name === PROP_DEFS.texture.key) return this.attachTexture(node,obj,provider)
80 | return super.updateProperty(node,obj,op,provider)
81 | }
82 |
83 | attachTexture(node, obj, provider) {
84 | if(obj.asset === NONE_ASSET.id) {
85 | node.updateSprite(null)
86 | return
87 | }
88 | const texture = provider.accessObject(obj.texture)
89 | if(!texture.exists()) return
90 | const url = provider.assetsManager.getAssetURL(texture)
91 | provider.getLogger().log("ParticlesDef: loading the asset url",url)
92 | const tex = new TextureLoader().load(url)
93 | node.updateSprite(tex)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/vr/defs/PlaneDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import * as THREE from "three"
3 | import {NONE_ASSET, PROP_DEFS} from '../Common'
4 | import ObjectDef from './ObjectDef'
5 |
6 | export default class PlaneDef extends ObjectDef {
7 | make(graph, scene) {
8 | if(!scene.id) throw new Error("can't create plane w/ missing parent")
9 | return fetchGraphObject(graph,graph.createObject({
10 | type:'plane',
11 | title:'a plane',
12 | visible:true,
13 | width: 0.5,
14 | height: 0.5,
15 | tx:0, ty:0, tz:0,
16 | rx:0, ry:0, rz:0,
17 | sx:1, sy:1, sz:1,
18 | color:'#ffffff',
19 | children:graph.createArray(),
20 | asset:NONE_ASSET.id,
21 | transparent:false,
22 | parent:scene.id
23 | }))
24 | }
25 | makeNode(obj, provider) {
26 | const node = new THREE.Mesh(
27 | new THREE.PlaneBufferGeometry(obj.width,obj.height),
28 | new THREE.MeshLambertMaterial({color: obj.color, side: THREE.DoubleSide})
29 | )
30 | this.attachAsset(node, obj, provider)
31 | node.userData.clickable = true
32 | // on(node,POINTER_CLICK,e =>SelectionManager.setSelection(node.userData.graphid))
33 | node.position.set(obj.tx, obj.ty, obj.tz)
34 | node.rotation.set(obj.rx,obj.ry,obj.rz)
35 | node.scale.set(obj.sx,obj.sy,obj.sz)
36 | node.visible = obj.visible
37 | return node
38 | }
39 |
40 | updateProperty(node, obj, op, provider) {
41 | if (op.name === 'width') node.geometry = new THREE.PlaneBufferGeometry(op.value,obj.height)
42 | if (op.name === 'height') node.geometry = new THREE.PlaneBufferGeometry(obj.width,op.value)
43 | if (op.name === PROP_DEFS.asset.key || op.name === PROP_DEFS.transparent.key) return this.attachAsset(node, obj, provider)
44 | return super.updateProperty(node,obj,op,provider)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/vr/defs/SceneDef.js:
--------------------------------------------------------------------------------
1 | import {createGraphObjectFromObject, fetchGraphObject} from "../../syncgraph/utils";
2 | import {
3 | Group,
4 | Mesh,
5 | MeshLambertMaterial,
6 | PlaneBufferGeometry
7 | } from 'three'
8 | import {OBJ_TYPES, PROP_DEFS} from '../Common'
9 |
10 | function isAnchorType(type) {
11 | if(type === OBJ_TYPES.geoanchor) return true
12 | if(type === OBJ_TYPES.localanchor) return true
13 | if(type === OBJ_TYPES.imageanchor) return true
14 | if(type === OBJ_TYPES.hudanchor) return true
15 | return false
16 | }
17 |
18 | function generateUniqueTitle(prefix,count, root) {
19 | const title = prefix + ' ' + count
20 | const dup = root.getChildren().find(sc => sc.title === title)
21 | if(dup) {
22 | return generateUniqueTitle(prefix,count+1,root)
23 | } else {
24 | return title
25 | }
26 | }
27 |
28 | export default class SceneDef {
29 | make(graph, root) {
30 | if(!root.id) throw new Error("can't create scene w/ missing parent")
31 | let title = generateUniqueTitle('Scene ',0,root)
32 | return fetchGraphObject(graph,createGraphObjectFromObject(graph,{
33 | type:'scene',
34 | title:title,
35 | autoRecenter:true,
36 | defaultFloor: false,
37 | parent:root.id,
38 | children:graph.createArray()
39 | }))
40 | }
41 | makeNode(obj,provider) {
42 | const scene = new Group()
43 | scene.name = obj.title
44 | this.setDefaultFloor(scene,obj.defaultFloor)
45 | scene.start = () => {
46 | scene.userData.sceneAnchor = new Group()
47 | scene.userData.sceneAnchor.name = "SceneAnchor"
48 | scene.children.slice().forEach(chNode => {
49 | const chObj = provider.accessObject(chNode.userData.graphid)
50 | if(!chObj.exists()) return
51 | if(isAnchorType(chObj.type)) return
52 | if(chObj.type === OBJ_TYPES.geoanchor) return
53 | if(chObj.type === OBJ_TYPES.localanchor) return
54 | if(chObj.type === OBJ_TYPES.imageanchor) return
55 | if(chObj.type === OBJ_TYPES.hudanchor) return
56 | scene.userData.sceneAnchor.add(chNode)
57 | })
58 | scene.add(scene.userData.sceneAnchor)
59 | }
60 | scene.stop = () => {
61 | const toMove = scene.userData.sceneAnchor.children.slice()
62 | toMove.forEach(chNode => scene.add(chNode))
63 | scene.remove(scene.userData.sceneAnchor)
64 | }
65 | return scene
66 | }
67 |
68 | updateProperty(node, obj, op, provider) {
69 | console.log("update property",node.name,op.name,op.value)
70 | if(op.name === PROP_DEFS.defaultFloor.key) {
71 | console.log('setting the floor too',op.value)
72 | this.setDefaultFloor(node,op.value)
73 | }
74 | }
75 |
76 | setDefaultFloor(scene,val) {
77 | if (val === true) {
78 | const floor = new Mesh(
79 | new PlaneBufferGeometry(100, 100, 10, 10),
80 | new MeshLambertMaterial({color: 'blue'})
81 | )
82 | floor.name = 'defaultFloor'
83 | floor.rotation.x = -90 * Math.PI / 180
84 | scene.parts = {}
85 | scene.parts.floor = floor
86 | scene.add(floor)
87 | } else {
88 | if (scene.parts && scene.parts.floor) {
89 | scene.remove(scene.parts.floor)
90 | delete scene.parts.floor
91 | }
92 | }
93 | }
94 |
95 | getFloorPart(node) {
96 | if(!node) return null
97 | if(!node.parts) return null
98 | return node.parts.floor
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/vr/defs/SphereDef.js:
--------------------------------------------------------------------------------
1 | import {fetchGraphObject} from "../../syncgraph/utils"
2 | import ObjectDef from './ObjectDef'
3 | import {NONE_ASSET, OBJ_TYPES, PROP_DEFS} from '../Common'
4 | import {Mesh, MeshLambertMaterial, SphereBufferGeometry} from 'three'
5 |
6 | let COUNTER = 0
7 |
8 | export default class SphereDef extends ObjectDef {
9 | make(graph, scene) {
10 | if(!scene.id) throw new Error("can't create sphere w/ missing parent")
11 | return fetchGraphObject(graph,graph.createObject({
12 | type:OBJ_TYPES.sphere,
13 | title:'sphere '+COUNTER++,
14 | visible:true,
15 | radius: 0.3/2,
16 | tx:0, ty:0, tz:0,
17 | rx:0, ry:0, rz:0,
18 | sx:1, sy:1, sz:1,
19 | color:'#0000ff',
20 | children:graph.createArray(),
21 | asset:NONE_ASSET.id,
22 | transparent:false,
23 | parent:scene.id
24 | }))
25 | }
26 | makeNode(obj, provider) {
27 | const node = new Mesh(
28 | new SphereBufferGeometry(obj.radius),
29 | new MeshLambertMaterial({color: obj.color})
30 | )
31 | node.name = obj.title
32 | node.userData.clickable = true
33 | node.position.set(obj.tx, obj.ty, obj.tz)
34 | node.rotation.set(obj.rx,obj.ry,obj.rz)
35 | node.scale.set(obj.sx,obj.sy,obj.sz)
36 | node.visible = obj.visible
37 | this.attachAsset(node, obj, provider)
38 | return node
39 | }
40 |
41 | updateProperty(node, obj, op, provider) {
42 | if (op.name === 'radius') node.geometry = new SphereBufferGeometry(op.value)
43 | if (op.name === PROP_DEFS.asset.key || op.name === PROP_DEFS.transparent.key) return this.attachAsset(node, obj, provider)
44 | return super.updateProperty(node,obj,op,provider)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/vr/defs/TextDef.js:
--------------------------------------------------------------------------------
1 | import ObjectDef from './ObjectDef'
2 | import {fetchGraphObject} from '../../syncgraph/utils'
3 | import {OBJ_TYPES, PROP_DEFS} from '../Common'
4 | import * as THREE from 'three'
5 | import WebLayer3D from 'three-web-layer'
6 |
7 | export default class TextDef extends ObjectDef {
8 | make(graph, scene) {
9 | if(!scene.id) throw new Error("can't create sphere w/ missing parent")
10 | return fetchGraphObject(graph,graph.createObject({
11 | type:OBJ_TYPES.text,
12 | title:'some text',
13 | visible:true,
14 | text:'cool formatted text',
15 | cssStyle:
16 | `color:black;
17 | background-color:white;
18 | width: 10em;
19 | font-size: 200%;
20 | `,
21 |
22 | tx:0, ty:0, tz:0,
23 | rx:0, ry:0, rz:0,
24 | sx:1, sy:1, sz:1,
25 |
26 | children:graph.createArray(),
27 | parent:scene.id
28 | }))
29 | }
30 |
31 | makeNode(obj) {
32 | const div = document.createElement('div')
33 | div.innerHTML = obj.text
34 | div.id = `div_${obj.id}`
35 | div.classList.add('weblayer-div')
36 | if(obj.cssStyle) div.setAttribute('style',obj.cssStyle)
37 | const divLayer = new WebLayer3D(div,{
38 | pixelRatio: window.devicePixelRatio,
39 | onLayerCreate(layer) {
40 | layer.mesh.material.side = THREE.DoubleSide
41 | layer.mesh.userData.graphid = obj.id
42 | layer.mesh.userData.clickable = true
43 | }
44 | })
45 |
46 | divLayer.refresh(true)
47 | divLayer.userData.clickable = true
48 | const node = new THREE.Object3D()
49 |
50 | node.previewUpdate = function() {
51 | divLayer.update()
52 | }
53 | node.setText = function (text) {
54 | if (text !== node.userData.text) {
55 | node.userData.text = text
56 | node.userData.div.innerHTML = text
57 | //if(obj.cssStyle) node.userData.div.setAttribute('style',obj.cssStyle)
58 |
59 | // shouldn't be explicitly calling refresh
60 | //node.userData.divLayer.refresh(true)
61 | }
62 | }
63 |
64 | node.add(divLayer)
65 | node.userData.divLayer = divLayer
66 | node.userData.text = obj.text
67 | node.userData.div = div
68 | node.name = obj.title
69 | node.userData.clickable = true
70 | node.position.set(obj.tx, obj.ty, obj.tz)
71 | node.rotation.set(obj.rx,obj.ry,obj.rz)
72 | node.scale.set(obj.sx,obj.sy,obj.sz)
73 | node.visible = obj.visible
74 | this.regenerateText(node,obj)
75 | return node
76 | }
77 |
78 | updateProperty(node, obj, op, provider) {
79 | if( op.name === PROP_DEFS.text.key) this.regenerateText(node,obj)
80 | if( op.name === PROP_DEFS.cssStyle.key) this.regenerateText(node,obj)
81 | return super.updateProperty(node,obj,op,provider)
82 | }
83 |
84 |
85 | regenerateText(node, obj) {
86 | node.userData.div.innerHTML = obj.text
87 | if(obj.cssStyle) node.userData.div.setAttribute('style',obj.cssStyle)
88 | node.userData.divLayer.refresh(true)
89 | }
90 | }
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/vr/dialogs/AddAudioAssetDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager, HBox} from 'appy-comps'
3 | import {listToArray} from '../../syncgraph/utils'
4 | import {addAudioAssetFromFile, addAudioAssetFromURL} from '../AssetActions'
5 | import {Dialog, ToggleButton} from 'react-visual-editor-framework'
6 |
7 | export class AddAudioAssetDialog extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | view:'local',
12 | url:"",
13 | }
14 | this.fileInput = React.createRef();
15 | }
16 |
17 | cancel = () => {
18 | DialogManager.hide()
19 | }
20 | okay = () => {
21 | DialogManager.hide()
22 | if(this.state.view === 'remote') {
23 | console.log("adding the url",this.state.url)
24 | addAudioAssetFromURL(null,this.state.url, this.props.provider)
25 | } else {
26 | console.log("the file input is",this.fileInput)
27 | listToArray(this.fileInput.current.files).forEach(file => {
28 | addAudioAssetFromFile(file, this.props.provider)
29 | })
30 | }
31 | }
32 | switchLocal = () => this.setState({view:'local'})
33 | switchRemote = () => this.setState({view:'remote'})
34 |
35 | render() {
36 | return
50 | }
51 |
52 | selectedFile = (e) => {
53 | //console.log("selected a file", e.target)
54 | this.setState({fileInput:e.target.value})
55 | }
56 |
57 | typedURL = (e) => {
58 | this.setState({url:e.target.value})
59 | }
60 |
61 | renderSelectedPanel() {
62 | if(this.state.view === 'local') {
63 | return
64 |
67 |
68 | } else {
69 | return
70 |
73 |
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/vr/dialogs/AddGLBAssetDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {listToArray} from '../../syncgraph/utils'
4 | import {addGLBAssetFromFile} from '../AssetActions'
5 | import {Dialog} from 'react-visual-editor-framework'
6 |
7 | export class AddGLBAssetDialog extends Component {
8 | cancel = () => {
9 | DialogManager.hide()
10 | }
11 | okay = () => {
12 | DialogManager.hide()
13 | listToArray(this.fileinput.files).forEach(file => {
14 | addGLBAssetFromFile(file, this.props.provider)
15 | })
16 | }
17 |
18 | render() {
19 | return
30 | }
31 |
32 | selectedFile = (e) => {
33 | console.log("selected a file", e.target)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/vr/dialogs/AddGLTFAssetDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {listToArray} from '../../syncgraph/utils'
4 | import {Dialog} from 'react-visual-editor-framework'
5 |
6 | export class AddGLTFAssetDialog extends Component {
7 | cancel = () => {
8 | DialogManager.hide()
9 | }
10 | okay = () => {
11 | DialogManager.hide()
12 | listToArray(this.fileinput.files).forEach(file => {
13 | console.log("got the file", file)
14 | })
15 | }
16 |
17 | render() {
18 | return
32 | }
33 |
34 | selectedFile = (e) => {
35 | console.log("selected a file", e.target)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/vr/dialogs/AddImageAssetDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager, HBox} from 'appy-comps'
3 | import {listToArray} from '../../syncgraph/utils'
4 | import {addImageAssetFromFile, addImageAssetFromURL} from '../AssetActions'
5 | import {Dialog, ToggleButton} from 'react-visual-editor-framework'
6 |
7 | export class AddImageAssetDialog extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | view:'local',
12 | url:"",
13 | }
14 | this.fileInput = React.createRef();
15 | }
16 |
17 | cancel = () => {
18 | DialogManager.hide()
19 | }
20 | okay = () => {
21 | DialogManager.hide()
22 | if(this.state.view === 'remote') {
23 | console.log("adding the url",this.state.url)
24 | addImageAssetFromURL(this.state.url, this.props.provider)
25 | } else {
26 | console.log("the file input is",this.fileInput)
27 | listToArray(this.fileInput.current.files).forEach(file => {
28 | addImageAssetFromFile(file, this.props.provider)
29 | })
30 | }
31 | }
32 | switchLocal = () => this.setState({view:'local'})
33 | switchRemote = () => this.setState({view:'remote'})
34 |
35 | render() {
36 | return
50 | }
51 |
52 | selectedFile = (e) => {
53 | this.setState({fileInput:e.target.value})
54 | }
55 |
56 | typedURL = (e) => {
57 | this.setState({url:e.target.value})
58 | }
59 |
60 | renderSelectedPanel() {
61 | if(this.state.view === 'local') {
62 | return
63 |
66 |
67 | } else {
68 | return
69 |
72 |
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/vr/dialogs/BadAssetsDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {Dialog} from 'react-visual-editor-framework'
4 |
5 | export class BadAssetsDialog extends Component {
6 | dismiss = () => {
7 | DialogManager.hide()
8 | }
9 | render() {
10 | return
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/vr/dialogs/GithubAuthDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {AuthModule} from '../AuthModule'
4 | import {Dialog, getLoginURL} from 'react-visual-editor-framework'
5 |
6 | export class GithubAuthDialog extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | data:null
11 | }
12 | }
13 | componentDidMount() {
14 | console.log("opening dialog")
15 | AuthModule.getJSON(getLoginURL()).then(data => {
16 | console.log("got the data",data)
17 | this.setState({data:data})
18 | })
19 | }
20 |
21 | cancel = () => {
22 | DialogManager.hide()
23 | }
24 | okay = () => {
25 | DialogManager.hide()
26 | console.log("opening window with the data",this.state.data)
27 | AuthModule.login(this.state.data)
28 | }
29 | render() {
30 | return
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/vr/dialogs/MakeEmbedDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {Dialog} from 'react-visual-editor-framework'
4 |
5 | export class MakeEmbedDialog extends Component {
6 |
7 | okay = () => {
8 | DialogManager.hide()
9 | }
10 |
11 | generateEmbedURL() {
12 | return `http://localhost:3000/?mode=embed-view&doctype=${this.props.provider.getDocType()}&doc=${this.props.provider.getDocId()}&switcher=false`
13 | }
14 |
15 | render() {
16 | return
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/vr/dialogs/OpenAssetDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {isAudioType, isGLTFType, isImageType, isVideoType} from '../Common'
4 | import {AuthModule} from '../AuthModule'
5 | import {
6 | addAudioAssetFromURL,
7 | addGLBAssetFromURL,
8 | addImageAssetFromExpandedURL,
9 | addVideoAssetFromURL
10 | } from '../AssetActions'
11 | import {Dialog} from 'react-visual-editor-framework'
12 |
13 | export class OpenAssetDialog extends Component {
14 | constructor(props) {
15 | super(props)
16 | this.state = {
17 | assetList:[]
18 | }
19 | }
20 | refreshList = () => {
21 | this.props.provider.loadAssetList()
22 | .then((assets) => {
23 | if(this.props.filter) assets = assets.filter(this.props.filter)
24 | this.setState({assetList:assets})
25 | })
26 | }
27 | componentDidMount() {
28 | this.refreshList()
29 | }
30 | addAsset(info) {
31 | DialogManager.hide()
32 | if(isImageType(info.mimeType)) {
33 | return addImageAssetFromExpandedURL(info.id, info.url,info.mimeType, info.title, this.props.provider)
34 | }
35 | if(isAudioType(info.mimeType)) {
36 | return addAudioAssetFromURL(info.id, info.url, info.mimeType, info.title, this.props.provider)
37 | }
38 | if(isVideoType(info.mimeType)) {
39 | return addVideoAssetFromURL(info.id, info.url, info.mimeType, info.title, this.props.provider)
40 | }
41 | if(isGLTFType(info.mimeType)) {
42 | return addGLBAssetFromURL(info.id, info.url, info.mimeType, info.title, this.props.provider)
43 | }
44 | console.log("can't add this type",info.mimeType)
45 | }
46 | deleteAsset(info) {
47 | return this.props.provider.removeAssetSource(info).then((res)=>{
48 | console.log('removed?',res)
49 | this.refreshList()
50 | })
51 | }
52 |
53 | okay = () => {
54 | DialogManager.hide()
55 | }
56 | render() {
57 | return
74 | }
75 |
76 | renderDeleteButton(doc) {
77 | if(!AuthModule.supportsAssetUpload()) return ""
78 | return
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/vr/dialogs/OpenFileDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {AuthModule} from '../AuthModule'
4 | import {Dialog, toQueryString} from 'react-visual-editor-framework'
5 |
6 |
7 | export class OpenFileDialog extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | docList:[]
12 | }
13 | }
14 | refreshList = () => {
15 | this.props.provider.loadDocList().then((docs) => this.setState({docList:docs}))
16 | }
17 | componentDidMount() {
18 | this.refreshList()
19 | }
20 | generateDocUrl(doc) {
21 | const opts = Object.assign({},this.props.provider.options,{
22 | mode:'edit',
23 | doc:doc.id
24 | })
25 | delete opts.AuthModule
26 | const url = `./?${toQueryString(opts)}`
27 | return url
28 | }
29 | deleteDoc(info) {
30 | return this.props.provider.removeDoc(info).then((res)=>{
31 | console.log('removed?',res)
32 | this.refreshList()
33 | })
34 | }
35 |
36 | dismiss = () => {
37 | DialogManager.hide()
38 | }
39 | render() {
40 | return
57 | }
58 |
59 | renderDeleteButton(doc) {
60 | if(!AuthModule.supportsDocDelete()) return ""
61 | return
62 |
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/vr/dialogs/OpenScriptDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager, HBox, VBox} from 'appy-comps'
3 | import {AuthModule} from '../AuthModule'
4 | import {Dialog, Spacer} from 'react-visual-editor-framework'
5 |
6 | export class OpenScriptDialog extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | scripts:[]
11 | }
12 | }
13 | refreshList = () => {
14 | this.props.provider.loadScriptList()
15 | .then((scripts) => this.setState({scripts:scripts}))
16 | }
17 | componentDidMount() {
18 | this.refreshList()
19 | }
20 | addScript(info) {
21 | DialogManager.hide()
22 | console.log("URL is",info.url)
23 | info.url = info.name
24 | console.log("Now URL is",info.url)
25 | const beh = this.props.provider.addBehaviorAssetFromURL(info)
26 | if(this.props.onAdd) this.props.onAdd(beh)
27 | }
28 | deleteScript(info) {
29 | return this.props.provider.removeBehaviorAssetSource(info.name)
30 | .then(()=> this.refreshList())
31 | }
32 |
33 | okay = () => DialogManager.hide()
34 |
35 | render() {
36 | return
59 | }
60 |
61 | renderDeleteButton(doc) {
62 | if(!AuthModule.supportsScriptEdit()) return ""
63 | return
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/vr/dialogs/PasswordDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {AuthModule} from '../AuthModule'
4 | import {Dialog} from 'react-visual-editor-framework'
5 |
6 | export class PasswordDialog extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | field:""
11 | }
12 | }
13 | cancel = () => {
14 | DialogManager.hide()
15 | }
16 | okay = () => {
17 | DialogManager.hide()
18 | AuthModule.doPasswordLogin(this.state.field)
19 | }
20 | keyDown = (e) => {
21 | if(e.key === 'Enter') this.okay()
22 | }
23 | render() {
24 | return
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/vr/dialogs/QRDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {Dialog, QRCanvas} from 'react-visual-editor-framework'
4 |
5 | export class QRDialog extends Component {
6 |
7 | okay = () => {
8 | DialogManager.hide()
9 | }
10 |
11 | render() {
12 | let url = null
13 | if(this.props.url) url = this.props.url
14 | let wurl = url.replace("https","wxrv")
15 | wurl = wurl.replace("http","wxrv")
16 | return
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/vr/dialogs/SaveDocumentAsDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {Dialog, Spacer} from 'react-visual-editor-framework'
4 |
5 |
6 | export class SaveDocumentAsDialog extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | newTitle:"Copy of " + props.provider.getDocTitle()
11 | }
12 | }
13 | edited = (e) => {
14 | this.setState({newTitle:e.target.value})
15 | }
16 | dismiss = () => {
17 | DialogManager.hide()
18 | }
19 | saveAs = () => {
20 | DialogManager.hide()
21 | this.props.provider.duplicateDocument(this.state.newTitle)
22 | }
23 | render() {
24 | return
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/vr/dialogs/SimpleMessageDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {Dialog} from 'react-visual-editor-framework'
4 |
5 | export class SimpleMessageDialog extends Component {
6 | okay = () => {
7 | DialogManager.hide()
8 | }
9 |
10 | render() {
11 | return
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/vr/dialogs/UnsavedDocumentDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {DialogManager} from 'appy-comps'
3 | import {Dialog} from 'react-visual-editor-framework'
4 |
5 | export class UnsavedDocumentDialog extends Component {
6 | cancel = () => {
7 | DialogManager.hide()
8 | }
9 | okay = () => {
10 | DialogManager.hide()
11 | this.props.onAnyway()
12 | }
13 | render() {
14 | return
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/vr/panel2d/button2d.js:
--------------------------------------------------------------------------------
1 | import {POINTER_ENTER, POINTER_EXIT} from 'webxr-boilerplate'
2 | import Component2D from "./component2d";
3 |
4 |
5 | export default class Button2D extends Component2D {
6 | constructor() {
7 | super()
8 | this.type = 'button'
9 | this.text = 'foo'
10 | this.x = 0
11 | this.y = 0
12 | this.fsize = 20
13 | this.w = this.text.length * this.fsize
14 | this.h = 20
15 | this.normalBg = 'white'
16 | this.hoverBg = 'red'
17 | this.bg = this.normalBg
18 | this.on(POINTER_ENTER, () => {
19 | this.bg = this.hoverBg
20 | this.fire('changed', {type: 'changed', target: this})
21 | })
22 | this.on(POINTER_EXIT, () => {
23 | this.bg = this.normalBg
24 | this.fire('changed', {type: 'changed', target: this})
25 | })
26 | }
27 |
28 | draw(ctx) {
29 | ctx.font = `${this.fsize}px sans-serif`
30 | const metrics = ctx.measureText(this.text)
31 | this.w = 5 + metrics.width + 5
32 | this.h = 0 + this.fsize + 4
33 | ctx.fillStyle = this.bg
34 | ctx.fillRect(this.x, this.y, this.w, this.h)
35 | ctx.fillStyle = 'black'
36 | ctx.fillText(this.text, this.x + 3, this.y + this.fsize - 2)
37 | ctx.strokeStyle = 'black'
38 | ctx.strokeRect(this.x, this.y, this.w, this.h)
39 | }
40 |
41 | contains(pt) {
42 | if (pt.x < this.x) return false
43 | if (pt.x > this.x + this.w) return false
44 | if (pt.y < this.y) return false
45 | if (pt.y > this.y + this.h) return false
46 | return true
47 | }
48 |
49 | findAt(pt) {
50 | if (pt.x < 0) return null
51 | if (pt.x > 0 + this.w) return null
52 | if (pt.y < 0) return null
53 | if (pt.y > 0 + this.h) return null
54 | return this
55 | }
56 |
57 | set(key, value) {
58 | super.set(key, value)
59 | if (key === 'normalBg') this.bg = this.normalBg
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/vr/panel2d/component2d.js:
--------------------------------------------------------------------------------
1 | export default class Component2D {
2 | constructor() {
3 | this.listeners = {}
4 | }
5 | set(key,value) {
6 | this[key] = value
7 | this.fire('changed',{type:'changed',target:this})
8 | return this
9 | }
10 | get(key) {
11 | return this[key]
12 | }
13 | addEventListener(type,cb) {
14 | if(!this.listeners[type]) this.listeners[type] = []
15 | this.listeners[type].push(cb)
16 | return this
17 | }
18 | on(type,cb) {
19 | this.addEventListener(type,cb)
20 | return this
21 | }
22 | setAll(props) {
23 | Object.keys(props).forEach(key => this.set(key, props[key]))
24 | return this
25 | }
26 | fire(type,payload) {
27 | if(!this.listeners[type]) this.listeners[type] = []
28 | this.listeners[type].forEach(cb => cb(payload))
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/vr/panel2d/group2d.js:
--------------------------------------------------------------------------------
1 | import Component2D from "./component2d";
2 |
3 | export default class Group2D extends Component2D {
4 | constructor() {
5 | super()
6 | this.type = 'group'
7 | this.x = 0
8 | this.y = 0
9 | this.w = 100
10 | this.h = 100
11 | this.bg = 'gray'
12 | this.visible = true
13 | this.comps = []
14 | this.padding = 5
15 | this.border = 1
16 |
17 | this.layout = (comp) => {
18 | console.log("not laying out anything")
19 | }
20 | }
21 | draw(ctx) {
22 | if(!this.visible) return
23 | this.layout(this)
24 | ctx.fillStyle = this.bg
25 | ctx.fillRect(this.x,this.y,this.w,this.h)
26 | if(this.border > 0) {
27 | ctx.strokeStyle = 'black'
28 | ctx.strokeRect(this.x, this.y, this.w, this.h)
29 | }
30 | ctx.save()
31 | ctx.translate(this.x+this.padding,this.y+this.padding)
32 | this.comps.forEach(comp => comp.draw(ctx))
33 | ctx.restore()
34 | }
35 |
36 | contains(pt) {
37 | if(pt.x < this.x) return false
38 | if(pt.x > this.x + this.w) return false
39 | if(pt.y < this.y) return false
40 | if(pt.y > this.y + this.h) return false
41 | return true
42 | }
43 | findAt(pt) {
44 | if(!this.visible) return null
45 | for(let i=0; ich.set(key,value))
56 | return this
57 | }
58 |
59 | add(comp) {
60 | this.comps.push(comp)
61 | this.fire('changed',{type:'changed',target:this})
62 | return this
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/vr/panel2d/label2d.js:
--------------------------------------------------------------------------------
1 | export default class Label2D {
2 | constructor() {
3 | this.type = 'label'
4 | this.text = 'foo'
5 | this.fsize = 30
6 | this.listeners = {}
7 | this.x = 0
8 | this.y = 0
9 | this.w = this.text.length*this.fsize
10 | this.h = 20
11 | }
12 | addEventListener(type,cb) {
13 | if(!this.listeners[type]) this.listeners[type] = []
14 | this.listeners[type].push(cb)
15 | }
16 | draw(ctx) {
17 | const metrics = ctx.measureText(this.text)
18 | this.w = 5 + metrics.width + 5
19 | this.h = 2 + this.fsize + 2
20 | ctx.font = `${this.fsize}px sans-serif`
21 | ctx.fillStyle = 'black'
22 | ctx.fillText(this.text,this.x+3,this.y+this.fsize)
23 | }
24 | contains() {
25 | return false
26 | }
27 | findAt() {
28 | return null
29 | }
30 | set(key,value) {
31 | this[key] = value
32 | return this
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/vr/panel2d/panel2d.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 |
3 | import {POINTER_RELEASE, POINTER_CLICK, POINTER_ENTER, POINTER_EXIT, POINTER_PRESS, POINTER_MOVE} from 'webxr-boilerplate'
4 |
5 | // const $ = (sel) => document.querySelector(sel)
6 | const on = (elem, type, cb) => elem.addEventListener(type,cb)
7 | // const toRad = (deg) => deg * Math.PI/180
8 |
9 | export default class Panel2D extends THREE.Object3D {
10 | constructor(scene,camera) {
11 | super()
12 |
13 | if(!scene) throw new Error("cannot pass empty scene to Panel2D()")
14 | if(!camera) throw new Error("cannot pass empty camera to Panel2D()")
15 | this.type = 'panel2d'
16 | this.listeners = {}
17 | this.scene = scene
18 | this.camera = camera
19 | this.canvas = document.createElement('canvas')
20 | this.canvas.width = 256
21 | this.canvas.height = 512
22 | this.canvasTexture = new THREE.CanvasTexture(this.canvas)
23 | this.redrawHandler = (e) => this.redraw()
24 |
25 | const c = this.canvas.getContext('2d')
26 | c.fillStyle = 'red'
27 | c.fillRect(0,0,this.canvas.width,this.canvas.height)
28 |
29 | this.mesh = new THREE.Mesh(
30 | new THREE.PlaneGeometry(1,2),
31 | new THREE.MeshBasicMaterial({color:'white',map:this.canvasTexture})
32 | )
33 | this.mesh.userData.clickable = true
34 | this.comps = []
35 |
36 | this.add(this.mesh)
37 |
38 | let inside = null
39 | on(this.mesh,POINTER_MOVE,(e)=>{
40 | const uv = e.intersection.uv
41 | const fpt = new THREE.Vector2(uv.x*256, 512-uv.y*512)
42 | if(inside && !inside.contains(fpt)) {
43 | inside.fire(POINTER_EXIT)
44 | inside = null
45 | }
46 | const comp = this.findAt(fpt)
47 | // for(let i=0; i{
60 | const uv = e.intersection.uv
61 | const fpt = new THREE.Vector2(uv.x*256, 512-uv.y*512)
62 | const comp = this.findAt(fpt)
63 | if(comp) comp.fire(POINTER_CLICK)
64 | // for(let i=0; i this.header.material.color.set('yellow'))
81 | on(this.header,POINTER_EXIT, e => this.header.material.color.set('goldenrod'))
82 | on(this.header,POINTER_PRESS, e => this.startDrag())
83 | }
84 |
85 | fire(type,payload) {
86 | if(!this.listeners[type]) this.listeners[type] = []
87 | this.listeners[type].forEach(cb => cb(payload))
88 | }
89 |
90 | findAt(pt) {
91 | for(let i=0; i comp.draw(ctx))
113 | this.canvasTexture.needsUpdate = true
114 | }
115 |
116 | startDrag() {
117 | this.header.userData.clickable = false
118 | this.mesh.userData.clickable = false
119 |
120 | this.dragSphere = new THREE.Mesh(
121 | new THREE.SphereGeometry(4,32,32),
122 | new THREE.MeshLambertMaterial({
123 | color:'green',
124 | wireframe:true,
125 | side: THREE.BackSide
126 | })
127 | )
128 | this.dragSphere.userData.clickable = true
129 | this.scene.add(this.dragSphere)
130 |
131 | on(this.dragSphere,POINTER_MOVE,(e)=> this.moveDrag(e))
132 | on(this.dragSphere,POINTER_RELEASE,(e)=> this.endDrag(e))
133 |
134 | }
135 | endDrag() {
136 | this.scene.remove(this.dragSphere)
137 | this.dragSphere.userData.clickable = false
138 | this.header.userData.clickable = true
139 | this.mesh.userData.clickable = true
140 | }
141 |
142 |
143 | moveDrag(e) {
144 | this.position.copy(e.point)
145 | this.position.add(new THREE.Vector3(0,-1,0))
146 | this.lookAt(this.camera.position)
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/vr/panel2d/togglebutton2d.js:
--------------------------------------------------------------------------------
1 | import Button2D from './button2d.js'
2 |
3 | export default class ToggleButton2D extends Button2D {
4 |
5 |
6 | constructor() {
7 | super()
8 | this.selected = false
9 |
10 | this.on('click',()=>{
11 | this.set('selected',!this.get('selected'))
12 | })
13 | }
14 |
15 | set(key,value) {
16 | super.set(key,value)
17 | if(key === 'selected') {
18 | if(this.selected) {
19 | this.normalBg = 'aqua'
20 | this.bg = this.normalBg
21 | } else {
22 | this.normalBg = 'white'
23 | this.bg = this.normalBg
24 | }
25 | this.fire('selected',this)
26 | }
27 | return this
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/vr/panel2d/togglegroup2d.js:
--------------------------------------------------------------------------------
1 | import Group2D from './group2d.js'
2 | import Button2D from './button2d.js'
3 | const rowLayout = (panel)=>{
4 | let x = 0
5 | panel.comps.forEach((c)=>{
6 | c.x=x
7 | c.y=0
8 | x += c.w+panel.padding
9 | })
10 | }
11 |
12 | export default class ToggleGroup extends Group2D {
13 | constructor(values) {
14 | super()
15 | this.value = null
16 | this.set('layout',rowLayout)
17 | Object.keys(values).forEach(key =>{
18 | this.add(new Button2D().set('text',key).on('click',(sel)=> this.set('value',key)))
19 | })
20 | }
21 | set(key,value) {
22 | super.set(key,value)
23 | if(key === 'value') {
24 | this.comps.forEach(comp => {
25 | const selected = (comp.get('text') === value)
26 | comp.normalBg = selected?'aqua':'white'
27 | comp.bg = comp.normalBg
28 | })
29 | this.fire('selected',this)
30 | }
31 | return this
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/vr/setupTests.js:
--------------------------------------------------------------------------------
1 | console.log("this is setting up the tests")
--------------------------------------------------------------------------------
/src/vr/startup-sound.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/mred/a91654c26c4c418ea7ef4f441609c51578124d66/src/vr/startup-sound.m4a
--------------------------------------------------------------------------------