UNPKG

janusweb

Version:
230 lines (203 loc) 12.8 kB
// https://xrfragment.org/#%F0%9F%93%9Clevel7%3A%20engine%20prefixes // There are cases where the 3D scene file might want to hint the // specific features to the viewer-engine (JANUSWEB, THREE.js, AFRAME, Godot e.g.). xrf_engines = function(){ const {toJanusObject,applyPrefixes,applyCleanup} = xrf_engines let cleanup = [] const map = (obj,key,realKey) => { let match = true // compatibility workaround: some 3D editors omit false booleans in export :/ // therefore we support boolean strings if( obj.userData[key] == 'false' ) obj.userData[key] = false if( obj.userData[key] == 'true' ) obj.userData[key] = true // increment/decrement let scroller = key.match(/[+]$/) if( scroller && !scene.scrollers ){ if( typeof obj.userData[key] != 'number' ) obj.userData[key] = parseFloat(obj.userData[key]) scene.scrollers = [] elation.events.add(null, 'janusweb_script_frame', function(e){ scene.scrollers.map( (f) => f(e.data) ) // e.data == delta }) } // special cases switch( key ){ case "-three-material.blending": if( obj.material ){ const modes = { 'THREE.NoBlending': THREE.NoBlending, 'THREE.NormalBlending': THREE.NormalBlending, 'THREE.AdditiveBlending': THREE.AdditiveBlending, 'THREE.SubtractiveBlending': THREE.SubtractiveBlending, 'THREE.MultiplyBlending': THREE.MultiplyBlending } setTimeout( () => { // not sure why this only works in setTimeout if( modes[ obj.userData[key] ] ) obj.material.blending = modes[ obj.userData[key] ] },10) } break; case "-three-material.sides": if( obj.material ){ const modes = { 'THREE.FrontSide': THREE.NoBlending, 'THREE.BackSide': THREE.NormalBlending, 'THREE.DoubleSide': THREE.DoubleSide } setTimeout( () => { // not sure why this only works in setTimeout if( modes[ obj.userData[key] ] ) obj.material.sides = modes[ obj.userData[key] ] },10) } break; case "-janus-use_local_asset": room.use_local_asset = obj.userData[key] room.localasset = room.createObject('object', { id: room.use_local_asset, collision_id: room.use_local_asset + '_collision', collision_scale: V(1,1,1), collision_pos: V(0,0,0), col: room.col, //fwd: room.fwd, xdir: room.xdir, ydir: room.ydir, zdir: room.zdir, shadows: true }); break; // DECLARATIVE entities case "-janus-tag": let opts = {}// rotation: '0 -180 0' } for( let i in obj.userData ){ if( !i.match(/^-janus-/) ) continue opts[ i.replace(/-janus-/,'') ] = obj.userData[i] } // create asset if( obj.userData['-janus-tag'].match(/^asset/) ){ if( opts.src && !opts.src.match(/(^\.|:\/)/) ) opts.src = room.baseurl + opts.src room.loadNewAsset( opts['tag'].replace(/^asset/,''), opts ) }else{ opts.js_id = opts.name = String(`-janus-${obj.name}_${obj.userData['-janus-tag']}`).replace(/.*janus-/,'-janus-') // create room object const jo = room.createObject( opts.tag, opts ) jo.objects['3d'].name = opts.js_id jo.visible = false // replace janusobject with nested THREE obj obj.parent.add( jo.objects['3d'] ) // fix unclickable objects (since obj might be nested in the scene-tree) if( jo.colliders ){ jo.removeCollider() jo.colliders.parent.children.push( jo.objects['3d'] ) } // we need setTimeout otherwise quaternion is not updated // https://github.com/jbaicoianu/janusweb/issues/306 setTimeout( () => { jo.orientation.copy( obj.quaternion) jo.position.copy( obj.position ) //jo.scale.copy( obj.scale ) jo.visible = true },200) // mark previously generated geo/materials by janusweb export for deletion obj.traverse ( (o) => cleanup.push(o) ) cleanup.push(obj) } break; // OBJECTS case "-janus-collision_id": const collision_id = obj.userData[key] if( collision_id != obj.name ){ console.warn(`xrfragment: ${obj.name}.collision_id can be '${obj.name}' only (for now)..skipping '${collision_id}'`) }else{ const jo = toJanusObject(obj,{noreparent:true}) jo.collidable = true jo.collision_id = collision_id jo.removeCollider(); const collider = obj.clone() collider.position.set(0,0,0) collider.rotation.set(0,0,0) collider.scale.set(1,1,1) jo.collision_trigger = true jo.setCollider('mesh',{mesh: collider}) jo.colliders.parent = obj //jo.objects.dynamics.mass = 1 //jo.objects.dynamics.addForce('static', new THREE.Vector3(0, room.gravity, 0)); //elation.events.add(jo.objects.dynamics, 'physics_collide', elation.bind(jo, jo.handleCollision)); console.log(`xrfragment: setting collision_id = ${collision_id}`) } break; default: match = false // JANUS property fallthrough if( key.match(/^-janus-/) && !key.match("-janus-tag") ){ // *TODO* more heuristics to determine scene if( obj.name == 'Scene' || obj?.parent?.name == '' || obj.userData['-janus-source']){ room[realKey] = obj.userData[key]; }else{ toJanusObject(obj)[realKey] = obj.userData[key] } match = true } // THREE fallthrough if( key.match(/^-three-/) ){ if( realKey.match('material.') && obj.material ){ // clone shared materials obj.material = obj.material.clone() if( realKey.match('material.map') ) obj.material.map = obj.material.map.clone() } const setKeyVal = (o, path, val ) => new Function('o', 'v', `o.${path} = v `)(o, val); if( scroller ){ const setKeyValIncr = (o, path, val, delta) => new Function('o', 'speed', `o.${path} += ${delta} * speed`)(o, val); try{ const myscroller = function(path,val,delta){ setKeyValIncr( this, path, val, delta) }.bind(obj, realKey.replace(/[+]$/,''), obj.userData[key]) myscroller(0.000001) // see if it triggers error scene.scrollers.push(myscroller) // otherwise add frame-function }catch(e){ console.error(`could not apply engine prefix: ${obj.name} => ${key} (tip: dont use them on <paragraph> objects)`) } }else{ try{ setKeyVal( obj, realKey, obj.userData[key] ) }catch(e){ console.error(e) } } match = true } break; } if( match ){ //console.log(`xrfragment: engine prefix '${key}:${realKey}' = '${obj.userData[key]}'`) } } room.gravity = 0 // new default unless specified otherwise let scene = elation.engine.instances.default.systems.world.scene['world-3d'] const isAsset = (obj) => String(obj.userData['-janus-tag']).match(/^asset/) const isJanusTag = (obj) => String(obj.userData['-janus-tag']) && !isAsset(obj) applyPrefixes(scene,map, (o) => isAsset(o) ) // janus requires first initialzing of assets applyPrefixes(scene,map, (o) => isJanusTag(o) ) // then regular janus tags applyPrefixes(scene,map, (o) => !isJanusTag(o) && !isAsset(o) ) // then set properties on the rest applyCleanup(cleanup) } xrf_engines.toJanusObject = function(obj,opts){ if( room.objects[ obj.name ] ) return room.objects[ obj.name ] opts = opts || {} opts.tag = opts.tag || 'object' const create = () => room.createObject( opts.tag,{ js_id: obj.name, ...opts }) let jo = create() jo.objects['3d'] = obj return jo } xrf_engines.applyPrefixes = function(scene,map,criteria){ criteria = criteria ? criteria : (obj) => true scene.traverse( (obj) => { if( criteria(obj) ){ for( let field in obj.userData ){ if( obj.userData[field] ){ try{ map(obj,field, field.replace(/^-(janus|three)-/,'') ) }catch(e){ console.error(e) } } } } }) } xrf_engines.applyCleanup = function(cleanup){ const clean = (o) => { if( o.geometry ) o.geometry.dispose() if( o.material ) o.material.dispose() o.removeFromParent() } cleanup.map(clean) } elation.events.add(null, 'room_load_complete', xrf_engines ) // future scenes if( room.loaded ) xrf_engines() // current scene