@ -7,97 +7,542 @@
< script src = "dependencies/a-console.js" > < / script >
< script src = 'dependencies/aframe-troika-text.min.js' > < / script >
< script src = 'dependencies/webdav.js' > < / script >
< script src = 'jxr-core.js?123' > < / script >
< script src = "https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.1.0/dist/aframe-extras.min.js" > < / script >
< script src = 'jxr-core.js?1234567' > < / script >
< script src = 'jxr-postitnote.js?13235' > < / script >
< / head >
< body >
< script >
var forceXaxis
// setInterval( _ => console.log(forceXaxis), 1000)
var translatingTargets = false
var clearRot
function toggleTranslateTargets(){
translatingTargets = !translatingTargets
let scene = AFRAME.scenes[0].object3D
if (translatingTargets){
let anchor = new THREE.Object3D()
let latest = selectedElements[selectedElements.length-1].element
latest.object3D.add( anchor )
// also inherits rotation, could try cancel it as the opposite of latest rotation
// might be easier to copy the position only every few ms instead
anchor.position.sub( latest.object3D.position )
//targets.map( t => anchor.attach(t.object3D) )
// should attach all BUT the current moving entity!
Array.from(document.querySelectorAll('.mab')).map( t => anchor.attach(t.object3D) )
// they don't move... despite
} else {
clearInterval( clearRot )
Array.from(document.querySelectorAll('.mab')).map( t => scene.attach(t.object3D) )
//targets.map( t => scene.attach(t.object3D) )
// could delete anchor, cleaner
/* TODO :
- reset (as done for fishinbowl)
- add audio instructions
- better menu (e.g target with onreleased)
- fix maze/mazemap mismatch (causing emit() error on init)
- game ideas
- art deco / art nouveau facade as puzzle mixed pieces
*/
AFRAME.registerComponent('startfunctions', {
init: function(){
addGames()
// example of adding a game programmatically
let newEl = document.createElement('a-entity')
//let gamename = "checkers"
let gamename = "carcassone"
newEl.id = gamename
newEl.setAttribute(gamename, "")
newEl.classList.add( "game" )
AFRAME.scenes[0].appendChild(newEl)
// move with waddle example
let biggu = document.querySelector("#biggu")
biggu.setAttribute("animation__translation", "property: position; to: 0 0 0.5; dur: 10000;")
biggu.setAttribute("animation__waddle", "property: rotation; from: 0 -20 -10; to: 0 20 10; dur: 1000; loop:true; easing: linear; dir:alternate;")
}
})
//___________________________________________________________________________________________________________________________________
/*
game manager component
parent entity where each game itself is another child entity
menu
show/hide each game
bookmark
filter on e.g age range, last played, not completed
has listener to unify animation and audio
e.g yes/win or try again
but also lets custom content be presented, e.g custom audio instructions
*/
function addGames(){
const imgPath = "../content/games/previews/"
const imgExtension = ".jpg"
// show/hide should be enough (target should only work when shown iirc)
Array.from( document.querySelectorAll('.game') ).map( (g,i) => {
let n = addNewNote("jxr showOnlyThisGame('"+g.id+"')")
AFRAME.scenes[0].appendChild(n)
setTimeout( _ => {
let newEl = document.createElement("a-image")
newEl.setAttribute("src", imgPath+g.id+imgExtension)
//newEl.setAttribute("position", "-1 0 0")
newEl.setAttribute("target", "true") // now works despite relative position... but weird
newEl.setAttribute("onreleased", "showOnlyThisGame('"+g.id+"')")
n.appendChild( newEl )
n.object3D.position.y+=i/10
// n.setAttribute("annotation", "content:...")
// e.g to add French, would need to add specific data e.g full name, translation with language name e.g FR, etc
}, 500 )
})
// also need to add reset state!
// could add a reset event listener on each component
}
function showOnlyThisGame(name){
Array.from( document.querySelectorAll('.game') ).map( (g,i) => g.setAttribute("visible", "false") )
document.getElementById(name).setAttribute("visible", "true")
document.querySelector("["+name+"]").emit("reset")
}
//___________________________________________________________________________________________________________________________________
AFRAME.registerComponent('carcassone', {
init: function(){
// written vertically then joined, corners, bridges, crosses
let tiles = [ "00100 01100 11011 00110 00100", "00100 00100 10101 00100 00100", "00100 00100 11111 00100 00100", ]
this.colors = ['red', 'green', 'blue', 'yellow']
let generatorName = this.attrName
let el = this.el
let deckOfTiles = []
for (let i=0; i< 4 ; i + + )
deckOfTiles.push( tiles[2] )
for (let i=0; i< 3 ; i + + )
this.colors.map( (c,i) => {
let t = tiles[1].replace('10101','10'+(i+2)+'01') // put in the center, easier, but could be a random one bridge, center vertical
deckOfTiles.push( t )
})
let colorMixes = []
for (let i=1; i< 4 ; i + + )
colorMixes.push( [0,i] )
for (let i=2; i< 4 ; i + + )
colorMixes.push( [1,i] )
colorMixes.push( [2,3] )
for (let i=0; i< 2 ; i + + )
colorMixes.map( cs => {
let t = tiles[0].replace('11011','1'+(cs[0]+2)+'0'+(cs[1]+2)+'1')
deckOfTiles.push( t )
})
for (let i=0; i< 2 ; i + + )
this.colors.map( (c,i) => {
let t = tiles[0].replace('01100','01'+(i+2)+'00')
deckOfTiles.push( t )
})
// TODO add the item per color, should try to make minimalist fishes, e.g cone for tail the flatten sphere for body
// test to generate tiles
let stepSize = 1/2
deckOfTiles.map( (tile,n) => {
let t = this.tileFromData( tile )
t.setAttribute("position", "0 0 "+(n*stepSize))
el.appendChild( t )
})
},
tileFromData: function(tileData){
let generatorName = this.attrName
let tileEl = document.createElement("a-entity")
tileData.split(" ").filter(l=>l.length>0).map( (line,i) => {
let whatever = [...line.trim()].map( (c,j) =>{
let newEl = document.createElement("a-box")
newEl.setAttribute("scale", ".1 .1 .1")
let color
let pieceColor
switch (Number(c)){
case 0:
color="blue"
newEl.setAttribute("height", 2)
break;
case 1:
color="white"
break;
// could do Number(c) to be able to check if >1 as fish on tile (with potential a random rotation)
case 2:
case 3:
case 4:
case 5:
color="white"
pieceColor = this.colors[Number(c)-2]
break;
}
if (pieceColor){
let pieceEl = document.createElement('a-cylinder')
pieceEl.setAttribute("radius", .4)
pieceEl.setAttribute("height", .1)
pieceEl.setAttribute("color", pieceColor)
pieceEl.setAttribute("position", "0 1 0")
pieceEl.classList.add( generatorName )
newEl.appendChild(pieceEl)
}
newEl.setAttribute("color", color)
newEl.setAttribute("position", ""+j/10+" 0 "+i/10)
tileEl.appendChild(newEl)
})
})
return tileEl
},
events: {
reset: function (evt) {
console.log(this.attrName, 'component was resetted!');
},
check: function (evt) {
let generatorName = this.attrName
}
}
})
//___________________________________________________________________________________________________________________________________
AFRAME.registerComponent('checkers', {
init: function(){
let generatorName = this.attrName
let el = this.el
let color = "white"
this.scale = 1/10
for (let j=0;j< 8 ; j + + ) {
for (let i=0;i< 8 ; i + + ) {
let newEl = document.createElement('a-box')
newEl.setAttribute("scale", ""+this.scale+" "+this.scale/10+" "+this.scale)
color=="white"?color="black":color="white"
newEl.setAttribute("color",color)
newEl.setAttribute("position", ""+i*this.scale+" 0.1 "+j*this.scale)
newEl.classList.add( generatorName )
el.appendChild(newEl)
if (j< 2 ) {
let pieceEl = document.createElement('a-cylinder')
pieceEl.setAttribute("radius", .04)
pieceEl.setAttribute("height", .1)
pieceEl.setAttribute("target", "true")
pieceEl.setAttribute("color","#555555")
pieceEl.setAttribute("position", ""+i*this.scale+" 0.1 "+j*this.scale)
pieceEl.classList.add( generatorName )
el.appendChild(pieceEl)
}
if (j>=6){
let pieceEl = document.createElement('a-cylinder')
pieceEl.setAttribute("radius", .04)
pieceEl.setAttribute("height", .1)
pieceEl.setAttribute("target", "true")
pieceEl.setAttribute("color","#EEEEEE")
pieceEl.setAttribute("position", ""+i*this.scale+" 0.1 "+j*this.scale)
pieceEl.classList.add( generatorName )
el.appendChild(pieceEl)
}
}
color=="white"?color="black":color="white"
}
},
events: {
reset: function (evt) {
console.log(this.attrName, 'component was resetted!');
},
check: function (evt) {
let generatorName = this.attrName
}
}
})
//___________________________________________________________________________________________________________________________________
// model component so far, single setup and single check
AFRAME.registerComponent('fishinbowl', {
init: function(){
let generatorName = this.attrName
let el = this.el
this.correctlyPlacedFishes = 0
this.maxFishes = 5
this.xOffset = -.1
this.yOffset = .5
this.zOffset = -.1
this.scale = 1/1
for (let i=0;i< this.maxFishes ; i + + ) {
let newEl = document.createElement('a-gltf-model')
newEl.setAttribute("onreleased", 'document.querySelector("['+generatorName+']").emit("check")')
// this is THE key part, namely that when we pinch, move then release that target, it checks the state of the component
newEl.setAttribute("target","true")
newEl.setAttribute("scale",".001 .001 .001")
newEl.setAttribute("src","../content/winterset/Fish.glb")
newEl.setAttribute("position", ""+(Math.random()+this.xOffset)+" "+(Math.random()*this.scale+this.yOffset)+" "+(-Math.random()*this.scale+this.zOffset))
newEl.classList.add( generatorName )
el.appendChild(newEl)
}
},
events: {
reset: function (evt) {
console.log(this.attrName, 'component was resetted!');
this.correctlyPlacedFishes = 0
Array.from( document.querySelectorAll('.'+this.attrName) ).map( (e,i) => {
e.setAttribute("position", ""+(Math.random()+this.xOffset)+" "+(Math.random()*this.scale+this.yOffset)+" "+(-Math.random()*this.scale+this.zOffset))
})
},
check: function (evt) {
let generatorName = this.attrName
//used via onrelease="..."
if (!selectedElements || selectedElements.length < 1 ) {
console.warn(generatorName, 'check failed, should be called after entity moves, e.g onreleased="..."')
return // should only happen after something has been moved
}
let latest = selectedElements[selectedElements.length-1].element
let target = document.getElementById(generatorName+"_target")
let posA = new THREE.Vector3();
let posB = new THREE.Vector3();
latest.object3D.getWorldPosition( posA )
target.object3D.getWorldPosition( posB )
if ( posA.distanceTo( posB ) < .2 ) {
++this.correctlyPlacedFishes
console.log( this.correctlyPlacedFishes )
// forcing immovable
latest.removeAttribute("target") // doesn't unmount the component iirc, not sure it works though!
targets = targets.filter( e => e != target)
if ( this.correctlyPlacedFishes < 3 ) {
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_yes')
document.getElementById("biggucontinu").play()
}
if ( this.correctlyPlacedFishes == 3 ){
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_win')
document.getElementById("biggubravojulia").play()
}
}
}
}
})
//___________________________________________________________________________________________________________________________________
let correctlyPlacedLetters = 0
AFRAME.registerComponent('letterstoword', {
init: function(){
correctlyPlacedLetters = 0
let generatorName = this.attrName
let word = "JULIA" // assumes 1 letter per word, should index position instead
const scale = 1/3.5
const xOffset = -.5
const yOffset = .5
const zOffset = -.1
let el = this.el
let whatever = [...word].map( (c,i) =>{
let newEl = document.createElement('a-text')
newEl.setAttribute("target", "")
newEl.setAttribute("value", c)
newEl.setAttribute("scale", ".5 .5 .5")
newEl.setAttribute("onreleased", "lettersCheckDistanceToDedicatedTargetSpot('"+generatorName+"')")
newEl.setAttribute("position", ""+(Math.random()+xOffset)+" "+(Math.random()*scale+yOffset)+" "+(-Math.random()*scale+zOffset))
newEl.classList.add( generatorName )
el.appendChild(newEl)
let targetEl = document.createElement('a-box')
targetEl.setAttribute("position", ""+(xOffset+i*scale)+" "+(yOffset)+" "+zOffset)
targetEl.id = generatorName+"_"+c
targetEl.setAttribute("scale", ".05 .05 .05")
targetEl.setAttribute("opacity", ".5")
el.appendChild(targetEl)
})
}
})
function lettersCheckDistanceToDedicatedTargetSpot(generatorName){
//used via onrelease="..."
let latest = selectedElements[selectedElements.length-1].element
let target = document.getElementById(generatorName+"_"+latest.getAttribute("value"))
// should also be params, getting complicated...
let posA = new THREE.Vector3();
let posB = new THREE.Vector3();
latest.object3D.getWorldPosition( posA )
target.object3D.getWorldPosition( posB )
if ( posA.distanceTo( posB ) < .2 ) {
latest.setAttribute("color", "green")
++correctlyPlacedLetters
console.log( correctlyPlacedLetters )
// forcing immovable
latest.removeAttribute("target") // doesn't unmount the component iirc, not sure it works though!
targets = targets.filter( e => e != target)
if ( correctlyPlacedLetters < 5 ) {
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_yes')
document.getElementById("biggucontinu").play()
}
if ( correctlyPlacedLetters == 5 ) {
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_win')
document.getElementById("biggubravojulia").play()
}
}
}
//___________________________________________________________________________________________________________________________________
let correctlyPlacedPrimitives = 0
AFRAME.registerComponent('table2entries', {
init: function(){
correctlyPlacedPrimitives = 0
let generatorName = this.attrName
// generate grid and models tool, with target positions to check against
const colors = ["red", "green", "blue"]
const primitive = ["box", "sphere", "cylinder"]
const scale = 1/3.5
const xOffset = .1
const yOffset = .2
const zOffset = -.6
let el = this.el
colors.map( (c,j) => {
let cel = document.createElement('a-plane')
cel.setAttribute("color", c)
cel.setAttribute("position", ""+(xOffset+colors.length*scale)+" "+(yOffset+j*scale)+" "+zOffset)
cel.setAttribute("scale", ".1 .1 .1")
el.appendChild(cel)
})
primitive.map( (p,i) => {
let pel = document.createElement('a-'+p)
pel.setAttribute("position", ""+(xOffset+i*scale)+" "+(yOffset+primitive.length*scale)+" "+zOffset)
pel.setAttribute("scale", ".05 .05 .05")
if (p=="box") pel.setAttribute("scale", ".1 .1 .1")
el.appendChild(pel)
colors.map( (c,j) => {
let newEl = document.createElement('a-'+p)
newEl.setAttribute("target", "")
newEl.setAttribute("color", c)
newEl.setAttribute("scale", ".05 .05 .05")
newEl.setAttribute("onreleased", "checkDistanceToDedicatedTargetSpot('"+generatorName+"')")
if (p=="box") newEl.setAttribute("scale", ".1 .1 .1")
newEl.setAttribute("position", ""+Math.random()+" "+Math.random()+" "+(Math.random()+zOffset))
newEl.classList.add( generatorName )
el.appendChild(newEl)
let targetEl = document.createElement('a-box')
targetEl.setAttribute("position", ""+(xOffset+i*scale)+" "+(yOffset+j*scale)+" "+zOffset)
targetEl.id = generatorName+"_"+p+"_"+c
targetEl.setAttribute("scale", ".05 .05 .05")
targetEl.setAttribute("opacity", ".5")
el.appendChild(targetEl)
})
})
}
})
function checkDistanceToDedicatedTargetSpot(generatorName){
//used via onrelease="..."
let latest = selectedElements[selectedElements.length-1].element
let target = document.getElementById(generatorName+"_"+latest.localName.split('-')[1]+"_"+latest.getAttribute("color"))
// should also be params, getting complicated...
let posA = new THREE.Vector3();
let posB = new THREE.Vector3();
latest.object3D.getWorldPosition( posA )
target.object3D.getWorldPosition( posB )
let idCheck = generatorName+"_"+latest.localName.split('-')[1]+"_"+latest.getAttribute("color")
// should also be params, getting complicated...
console.log (idCheck, posA.distanceTo( posB ), posA.distanceTo( posB ) < .2 )
if ( posA.distanceTo( posB ) < .2 ) {
latest.setAttribute("wireframe", true)
++correctlyPlacedPrimitives
console.log( correctlyPlacedPrimitives )
// forcing immovable
latest.removeAttribute("target") // doesn't unmount the component iirc, not sure it works though!
targets = targets.filter( e => e != target)
if ( correctlyPlacedPrimitives < 9 ) {
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_yes')
document.getElementById("biggucontinu").play()
}
if ( correctlyPlacedPrimitives == 9 ) {
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_win')
document.getElementById("biggubravojulia").play()
}
}
}
//___________________________________________________________________________________________________________________________________
// should moved to e.g src='jxr-game-maze.js'
AFRAME.registerComponent('mazemap', {
init: function(){
let el = this.el
this.data.split("\n").filter(l=>l.length>0).map( (line,i) => {
let whatever = [...line].map( (c,j) =>{
let newEl = document.createElement("a-box")
let color
switch (c){
case "1":
color="blue"
newEl.setAttribute("height", 2)
break;
case "0":
color="white"
newEl.setAttribute("material", "metalness:.2") // no big difference
break;
case "S":
color="grey"
break;
case "E":
color="grey"
newEl.id = "mazeend"
break;
}
newEl.setAttribute("color", color)
newEl.setAttribute("position", ""+j+" 0 "+i)
el.appendChild(newEl)
})
})
}
})
// could also get from parameter URL e.g mazemap=S1111,00001,10111,10001,1110E as suggested by Leon
function forbiddenSpots(){
// should only be done once
return Array.from( document.querySelectorAll("#maze>a-entity>a-box[color=blue]") )
.map( el => { let pos = new THREE.Vector3(); el.object3D.getWorldPosition(pos); return pos})
}
function overForbiddenSpot(selectorA="#biggu", distanceThreshold=.2){
let posA = new THREE.Vector3();
document.querySelector(selectorA).object3D.getWorldPosition( posA )
let over = false
forbiddenSpots().map( posB => {
if ( posA.distanceTo( posB ) < distanceThreshold )
over = true
})
return over
}
function moveBigguForward(step=.2){
// could also first if within maze boundaries
document.querySelector("#biggu").object3D.translateZ(step)
if (overForbiddenSpot())
setTimeout( _ => document.querySelector("#biggu").object3D.translateZ(-step), 500 )
}
function moveBigguBackward(step=-.2){
// could also first if within maze boundaries
document.querySelector("#biggu").object3D.translateZ(step)
if (overForbiddenSpot())
setTimeout( _ => document.querySelector("#biggu").object3D.translateZ(-step), 500 )
}
function moveBigguRight(step=.2){
// could also first if within maze boundaries
document.querySelector("#biggu").object3D.translateX(step)
if (overForbiddenSpot())
setTimeout( _ => document.querySelector("#biggu").object3D.translateX(-step), 500 )
}
function moveBigguLeft(step=-.2){
// could also first if within maze boundaries
document.querySelector("#biggu").object3D.translateX(step)
if (overForbiddenSpot())
setTimeout( _ => document.querySelector("#biggu").object3D.translateX(-step), 500 )
}
function checkWinCondition(selectorA="#biggu", selectorB="#mazeend", distanceThreshold=.2){
let posA = new THREE.Vector3();
let posB = new THREE.Vector3();
document.querySelector(selectorA).object3D.getWorldPosition( posA )
document.querySelector(selectorB).object3D.getWorldPosition( posB )
if ( posA.distanceTo( posB ) < distanceThreshold ) {
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_win')
document.getElementById("biggubravojulia").play()
}
return ( posA.distanceTo( posB ) < distanceThreshold )
}
var attachToPlayer = false
function toggleAttachToSelf(){
attachToPlayer = !attachToPlayer
attachToPlayer ? parent=document.querySelector("#player") : parent=AFRAME.scenes[0]
targets.map( t => parent.object3D.attach(t.object3D) )
function animateThenIdle(mainCharacter, animationName, timeScale='1'){
mainCharacter.setAttribute('animation-mixer', "clip:"+animationName+";loop:once; timeScale:"+timeScale)
mainCharacter.addEventListener('animation-finished', _ => {
mainCharacter.setAttribute('animation-mixer', "clip:bigguaction_idle; loop:true;")
})
// could return the animation duration or an event when done
}
function checkIntersection(latest, nearby){
// end src='jxr-game-maze.js'
//___________________________________________________________________________________________________________________________________
function doesThisContainThat(latest, nearby){
//let latest = selectedElements[selectedElements.length-1].element
//let nearby = getClosestTargetElements( latest.getAttribute('position') )
// https://threejs.org/docs/?q=box#api/en/math/Box3.containsBox
// https://threejs.org/docs/?q=box#api/en/math/Box3.expandByObject
let a = new THREE.Box3().expandByObject( latest.object3D ) // consider mesh.geometry.computeBoundingBox() first
let b = new THREE.Box3().expandByObject( nearby.object3D )
console.log(a,b, a.containsBox(b))
// testable as checkIntersection( document.querySelector("[color='yellow']"), document.querySelector("[color='purple']") )
return a.containsBox(b )
// testable as doesThisContainThat ( document.querySelector("[color='yellow']"), document.querySelector("[color='purple']") )
// < a-box scale = ".1 .1 .1" position = ".5 .8 -.3" color = "purple" > < / a-box >
// < a-box scale = ".2 .2 .2" position = ".5 .8 -.3" color = "yellow" > < / a-box >
}
setTimeout( _ => {
let newPostIt = addNewNoteAsPostItNote("jxr console.log(222);", "0 1.2 -.5")
.setAttribute("onreleased", "grammarBasedSnap()")
let otherPostIt = addNewNoteAsPostItNote("jxr console.log(111);", "0 1.4 -.5")
.setAttribute("onreleased", "grammarBasedSnap()")
let postIt = addNewNoteAsPostItNote("hi this is a post-it note.", "0 1.6 -.5")
.setAttribute("onreleased", "runClosestJXR(); grammarBasedSnap()") // dunno how to share the event context back here...
// .setAttribute("onreleased", "snapNext()") // does NOT support multiple instances for now
// see https://aframe.io/docs/1.5.0/core/component.html#multiple
// maybe bind could help
//let cloneMe = addNewNote('jxr clone me from corner', '0 0 .1', '1 1 1', 'cmd')
// should rebind parent...
//setTimeout( _ => { _ => cloneMe.object3D.parent = postIt.object3D }, 1000 )
// should try object3D.attach() instead
//.addEventListener('loaded',
// entityIndexes( document.querySelector("[color='blue']").object3D.children[0] )
}, 1000 )
// e.g document.querySelector("[color='blue']").object3D.children[0]
function entityIndexes(mesh){ // needs a mesh with a geometry, not a group
// could also traverse
let gp = mesh.geometry.attributes.position;
let wPos = [];
for(let i = 0;i < gp.count ; i + + ) {
let p = new THREE.Vector3().fromBufferAttribute(gp, i); // set p from `position`
mesh.localToWorld(p); // p has wordl coords
wPos.push(p);
}
// many are duplicates, i.e a a cube will return 24 indexes (4 per 6 faces), not 8
//let l = [...new Set(wPos)].length; console.log( l )
[...new Set(wPos)].map( p => addNewNote("x", p))
console.log( [...new Set(wPos)].length )
// seems to add the duplicates again
// try to "de-dup" via .distanceTo() below a threshold instead
}
function snapToGrid(gridSize=1){ // default as 1 decimeter
let latest = selectedElements[selectedElements.length-1].element
latest.setAttribute("rotation", "0 0 0")
@ -186,32 +631,14 @@ function runClosestJXR(){
nearby.map( n => interpretJXR( n.el.getAttribute("value") ) )
}
function notesFromArray(data, generatorName="", field="title", offset=1, step=1/10, depth=-.5 ){
data.slice(0,maxItemsFromSources).map( (n,i) => {
addNewNote( n[field], "0 "+(offset+i*step)+" "+depth, ".1 .1 .1", null, generatorName )
.setAttribute("onreleased","spreadItemsFromCollection('getcsljson', 1.5)")
})
}
function spreadItemsFromCollection( generatorName, offset=1, step=1/10, depth=-.5 ){
getArrayFromClass(generatorName).sort((a,b)=>a.getAttribute('position').y-b.getAttribute('position').y).map( (n,i) => {
n.setAttribute('position', "0 "+(offset+i*step)+" "+depth)
n.setAttribute('rotation', "0 0 0") // could also be based on the average of all items, the first item, last one, etc
// see also snap-on-pinchended component
})
let items = getArrayFromClass(generatorName).sort((b,a)=>a.getAttribute('position').y-b.getAttribute('position').y).map( n => n.getAttribute('value') )
shareLiveEvent('modified list', items)
}
let page = "Wiki.VirtualRealityInterface";
// should do then only once graph loaded instead, should emit event
let pageFromParam = AFRAME.utils.getUrlParameter('page')
if (pageFromParam) page = pageFromParam
setTimeout( _ => {
Array.from( document.querySelectorAll("[value='"+page+"']") ).map( n =>
n.setAttribute("onreleased", "console.log('dropped, should toggle display children,"+n.id+"')"));
Array.from( document.querySelectorAll("[value='"+page+"']>a-sphere") ).map( n => n.setAttribute("color", "purple"))
}, 5000)
AFRAME.registerComponent('idleafterload', {
events: {
'model-loaded': function (evt) {
console.log('This entity was loaded!');
this.el.setAttribute('animation-mixer', "clip:bigguaction_idle; loop:true;")
}
}
});
< / script >
< div style = 'position:fixed;z-index:1; top: 0%; left: 0%; border-bottom: 70px solid transparent; border-left: 70px solid #eee; ' >
@ -221,8 +648,27 @@ setTimeout( _ => {
< / div >
< a-scene startfunctions >
< a-gltf-model hide-on-enter-ar = "" id = "environment" src = "../content/CubeRoom.glb" rotation = "0 -90 0" position = "0 0 1" scale = "" > < / a-gltf-model >
<!-- Cube Room by Anonymous [CC - BY] via Poly Pizza -->
< a-assets >
< audio id = "biggucestmoi" src = "../content/voicesBigguJulia/biggu-fem.mp3" > < / audio >
< audio id = "biggubravojulia" src = "../content/voicesBigguJulia/bravojulia.mp3" > < / audio >
< audio id = "biggucontinu" src = "../content/voicesBigguJulia/continu.mp3" > < / audio >
< audio id = "bigguinstructions" src = "../content/voicesBigguJulia/instructions.mp3" > < / audio >
< template id = "avatar-template" > < / template >
< template id = "left-hand-default-template" >
< a-entity networked-hand-controls = "hand:left" > < / a-entity >
< / template >
< template id = "right-hand-default-template" >
< a-entity networked-hand-controls = "hand:right" > < / a-entity >
< / template >
< / a-assets >
< a-gltf-model id = "environment" hide-on-enter-ar = "" src = "../content/winterset/WinterIsland.glb" rotation = "0 20 0" position = "2 -4.5 -3" > < / a-gltf-model >
< a-gltf-model src = "../content/winterset/Crystal_iPoly3D.glb" position = "-0.4 -0.2 -3" scale = "0.1 0.1 0.1" > < / a-gltf-model >
< a-gltf-model idleafterload id = "biggu" src = "../content/winterset/SK_Biggu_v029_optimized.glb" position = "0 0 -1" >
<!-- <a - sound src="#bigguinstructions"></a - sound> -->
< / a-gltf-model >
< a-entity id = "rig" >
< a-entity id = "player" networked = "template:#avatar-template;attachTemplateToLocal:false;"
@ -232,60 +678,49 @@ setTimeout( _ => {
< a-entity id = "leftHand" pinchsecondary wristattachsecondary = "target: #box" hand-tracking-controls = "hand: left;" > < / a-entity >
< / a-entity >
< a-box pressable start-on-press id = "box" scale = "0.05 0.05 0.05" color = "pink" > < / a-box >
< a-troika-text value = "SpaSca : Spatial Scaffolding" anchor = "left" outline-width = "5%" font = "../content/ChakraPetch-Regular.ttf" position = "-3 5 -2"
scale="3 3 3" rotation="80 0 0" troika-text="outlineWidth: 0.01; strokeColor: #ffffff" material="flatShading: true; blending: additive; emissive: #c061cb">< / a-troika-text >
< a-sky hide-on-enter-ar color = "lightgray" > < / a-sky >
< a-troika-text anchor = left target value = "instructions : \n--right pinch to move\n--left pinch to execute" position = "0 0.65 -0.2" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr onNextPrimaryPinch(deleteTarget)" target position = " -0.3 1.50 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr onNextPrimaryPinch(cloneTarget)" target position = " -0.3 1.60 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left target value = "instructions : \n--right pinch to move\n--left pinch to execute" position = "0 1.65 -0.2" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr location.reload()" target position = " -0.3 1.30 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr location.reload()" target position = "-1 1.30 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "forceXaxis toggling"
onreleased="console.log('run on released');forceXaxis=!forceXaxis"
onpicked="console.log('run on picked');forceXaxis=!forceXaxis"
target position=" -0.3 1.45 0" rotation="0 40 0" scale="0.1 0.1 0.1">< / a-troika-text >
< a-console position = "2 2 0" rotation = "0 -45 0" font-size = "34" height = 1 skip-intro = true > < / a-console >
< a-troika-text anchor = left value = "translate targets"
onreleased="toggleTranslateTargets()"
onpicked="toggleTranslateTargets()"
target position=" 1 1.45 -.2" rotation="0 -40 0" scale="0.1 0.1 0.1">< / a-troika-text >
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
< a-entity visible = "false" id = "maze" class = "game" onstart = "" wincondition = "checkWinCondition()" losecondition = "" advice = "" onmistake = "" >
< a-entity scale = "0.2 0.2 0.2" position = "0 -.1 -1" mazemap = "
S1111
00001
10111
10001
1110E">
< / a-entity >
< a-gltf-model class = "bigguEndPoint" scale = "0.002 0.002 0.002" position = ".9 0.1 -.2" gltf-model = "../content/winterset/Fish.glb" > < / a-gltf-model >
<!-- bad offset... so leaving the end position as part of the maze itself -->
< a-troika-text anchor = left value = "jxr moveBigguForward(); checkWinCondition()" target position = "-0.3 .60 -.3" rotation = "90 0 0" annotation = "content: BIGGU DEVANT" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr moveBigguBackward(); checkWinCondition()" target position = "-0.3 .40 -.3" rotation = "90 0 0" annotation = "content: BIGGU DERRIERE" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr moveBigguRight(); checkWinCondition()" target position = "-0.1 .50 -.3" rotation = "90 0 0" annotation = "content: BIGGU DROITE" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr moveBigguLeft(); checkWinCondition()" target position = "-0.5 .50 -.3" rotation = "90 0 0" annotation = "content: BIGGU GAUCHE" scale = "0.1 0.1 0.1" > < / a-troika-text >
< / a-entity >
<!-- restart? location.reload() for now -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
< a-entity visible = "false" id = "table2entries" class = "game" onstart = "" wincondition = "" losecondition = "" advice = "" onmistake = "" table2entries > < / a-entity >
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
< a-entity visible = "false" id = "letterstoword" class = "game" onstart = "" wincondition = "" losecondition = "" advice = "" onmistake = "" letterstoword > < / a-entity >
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
< a-entity visible = "false" id = "fishinbowl" class = "game" onstart = "" wincondition = "" losecondition = "" advice = "" onmistake = "" fishinbowl >
< a-gltf-model id = "fishinbowl_target" src = "../content/winterset/FruitBowl.glb" position = "0.00055 -0.01343 -0.4778" scale = "0.1 0.1 0.1" > < / a-gltf-model >
< / a-entity >
< a-troika-text anchor = left value = "jxr setTimeout( _ => toggleAttachToSelf(), 1000); toggleAttachToSelf()" target position = " -0.3 1.25 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-console position = "2 2 0" rotation = "0 -45 0" font-size = "34" height = 1 skip-intro = true > < / a-console >
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
< a-box id = "box" visible = "false" > < / a-box >
<!-- bug if #box missing, so hiding for now -->
<!-- bug in start - on - press after XR init, as mentioned there -->
< a-box scale = ".07 .07 .07" class = "mab" target position = ".3 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".07 .07 .07" class = "mab" target position = ".1 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".07 .07 .07" class = "mab" target position = ".5 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".07 .07 .07" class = "mab" target position = "-.5 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".1 .1 .1" target position = ".5 1.6 -.3" color = "blue" onreleased = "snapToGrid()"
annotation="content:could also show/hide grid with gridHelper on pinch started and hide on release"
>< / a-box >
< a-box scale = ".1 .1 .1" target position = ".5 1.8 -.3" color = "blue" onreleased = "snapToGrid()" > < / a-box >
< a-box scale = ".1 .1 .1" position = ".5 .8 -.3" color = "purple" > < / a-box >
< a-box scale = ".2 .2 .2" position = ".5 .8 -.3" color = "yellow" > < / a-box >
< / a-scene >
< / body >
< / script >