SpaSca : open SCAffolding to SPAcially and textualy explore interfaces
https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1049 lines
44 KiB
1049 lines
44 KiB
<!DOCTYPE html>
|
|
<html>
|
|
<title>SpaSca : Spatial Scaffolding</title>
|
|
<head>
|
|
<!-- Suggestions? https://git.benetou.fr/utopiah/text-code-xr-engine/issues/ -->
|
|
<script src='dependencies/aframe.offline.min.js'></script>
|
|
<script src="dependencies/a-console.js"></script>
|
|
<script src='dependencies/aframe-troika-text.min.js'></script>
|
|
<script src='dependencies/webdav.js'></script>
|
|
<script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.1.0/dist/aframe-extras.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.2/dist/aframe-physics-system.min.js"></script>
|
|
<script src='jxr-core.js?12345'></script>
|
|
<script src='jxr-postitnote.js?13235'></script>
|
|
</head>
|
|
<body>
|
|
<script>
|
|
/* TODO :
|
|
- refactor to use emit('eventname', {eventdata:'data'}) for onreleased and onpicked rather than latest element
|
|
e.g newEl.setAttribute("onreleased", 'document.querySelector("['+generatorName+']").emit("check",{color:"'+color+'"})')
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
check for this pattern and replace by event.detail.element instead (for now e.detail.element)
|
|
might want to reconsider the el convention and do e.g let eventFromEl = e.detail.element over the eval() scope
|
|
- insure scene setup, e.g starting position and orientation of environment and main character (until now assumed unchanged)
|
|
- isolate emit('eventname', {test:0}) versus same with onreleased (which does NOT work) and same without event detail (which works)
|
|
- add audio instructions
|
|
../content/voicesBigguJulia/simon_instructions_fr.mp3
|
|
../content/voicesBigguJulia/labyrinthe_instructions_fr.mp3
|
|
../content/voicesBigguJulia/lettresprenom_instructions_fr.mp3
|
|
../content/voicesBigguJulia/tableauformes_instructions_fr.mp3
|
|
already there
|
|
../content/voicesBigguJulia/instructions.mp3
|
|
../content/voicesBigguJulia/bravojulia.mp3
|
|
../content/voicesBigguJulia/continu.mp3
|
|
../content/voicesBigguJulia/biggu-fem.mp3
|
|
|
|
example : tts --text "Regarde la couleur, ecoute le son, et pince avec ta main droite chaque cube dans le meme ordre" --model_name "tts_models/fr/mai/tacotron2-DDC" --out_path story.wav
|
|
|
|
Regarde la couleur, ecoute le son, et pince avec ta main droite chaque cube dans le meme ordre
|
|
Aide-moi a atteindre le poisson a la sortie du labyrinthe. Pour cela trouve les instructions pour me faire avancer, il y a en 4. Avec ta main gauche pince la premiere lettre et je bougerais dans cette direction !
|
|
Oops, les formes sont toutes melangees. Merci de les remettre a leur place en faisant attention que chaque fois ai la bonne couleur, comme indique par la ligne du haut et la colonne de droite. Pour deplacer les formes colorees pince les avec ta main droite.
|
|
Oh non, les lettres de ton prenom ont ete melangees. Pince chacune avec ta main droite et pose les dans le cube dans le bon ordre
|
|
|
|
- reset (as done for fishinbowl)
|
|
- 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
|
|
- philosophical experimentation, cf https://video.benetou.fr/w/9KGbaxtAEx4JLnhAkwQKC1 featuring then the trolley problem
|
|
*/
|
|
|
|
AFRAME.registerComponent('startfunctions', {
|
|
init: function(){
|
|
|
|
let untestedGames = ["checkers", "carcassone"]
|
|
addGame("voxelpaint")
|
|
addGame("simon")
|
|
addGame("physics-construct")
|
|
|
|
addGames()
|
|
}
|
|
})
|
|
|
|
//___________________________________________________________________________________________________________________________________
|
|
|
|
/*
|
|
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 addGame(gamename, visible="false"){
|
|
let newEl = document.createElement('a-entity')
|
|
newEl.id = gamename
|
|
newEl.setAttribute(gamename, "")
|
|
newEl.setAttribute("visible", visible)
|
|
newEl.classList.add( "game" )
|
|
AFRAME.scenes[0].appendChild(newEl)
|
|
}
|
|
|
|
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){
|
|
// should also work via URL, e.g hash or query parameter
|
|
Array.from( document.querySelectorAll('.game') ).map( (g,i) => g.setAttribute("visible", "false") )
|
|
document.getElementById(name).setAttribute("visible", "true")
|
|
document.querySelector("["+name+"]").emit("reset")
|
|
}
|
|
|
|
//___________________________________________________________________________________________________________________________________
|
|
/*
|
|
physics https://github.com/c-frame/aframe-physics-system and setup docs https://github.com/c-frame/aframe-physics-system/blob/master/CannonDriver.md#installation
|
|
should append to head script with src="https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.2/dist/aframe-physics-system.min.js"
|
|
|
|
<!-- Floor -->
|
|
<a-plane static-body></a-plane>
|
|
|
|
<!-- Immovable box -->
|
|
<a-box static-body position="0 0.5 -5" width="3" height="1" depth="1"></a-box>
|
|
|
|
<!-- Dynamic box -->
|
|
<a-box dynamic-body position="5 0.5 0" width="1" height="1" depth="1"></a-box>
|
|
|
|
todo
|
|
test getVoxelPoses() to hash equivalent, in order to be able to share builds
|
|
*/
|
|
AFRAME.registerComponent('physics-construct', {
|
|
init: function(){
|
|
let generatorName = this.attrName
|
|
let el = this.el
|
|
|
|
let sphereEl = document.createElement('a-sphere')
|
|
sphereEl.setAttribute('radius', '.01')
|
|
sphereEl.setAttribute('target', '')
|
|
sphereEl.setAttribute('position', '0 1 -.5')
|
|
sphereEl.setAttribute("onpicked", "window.pfb = selectedElements.at(-1).element.getAttribute('position').clone();")
|
|
sphereEl.setAttribute("onreleased", "document.querySelector('["+generatorName+"]').components['"+generatorName+"'].twoPosToBox(window.pfb, selectedElements.at(-1).element.getAttribute('position'), '"+generatorName+"');")
|
|
// really verbose... consider rebinding or more helpers in such events, facilitating access to this.el and component overall
|
|
AFRAME.scenes[0].appendChild(sphereEl)
|
|
|
|
let ballEl = document.createElement('a-sphere')
|
|
ballEl.setAttribute('radius', '.01')
|
|
ballEl.setAttribute('color', 'blue')
|
|
ballEl.setAttribute('target', '')
|
|
ballEl.setAttribute('position', '0 1.5 -0.2')
|
|
//ballEl.setAttribute('dynamic-body', '') // does not fall?
|
|
ballEl.setAttribute("onpicked", 'e.detail.element.removeAttribute("dynamic-body")') // untested
|
|
ballEl.setAttribute("onreleased", 'e.detail.element.setAttribute("dynamic-body","")')
|
|
AFRAME.scenes[0].appendChild(ballEl)
|
|
|
|
AFRAME.scenes[0].setAttribute("physics", "debug:true")
|
|
|
|
//let script = document.createElement("script")
|
|
//script.setAttribute("src", "https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.2/dist/aframe-physics-system.min.js")
|
|
//document.head.appendChild(script) // does not work, check older tricks
|
|
|
|
if (window.location.hash) {
|
|
let poses = JSON.parse(decodeURI(window.location.hash.replace("#",'')))[generatorName]
|
|
// prefixed by generatorName in order to support saving/sharing of the state of other games
|
|
poses?.map( p => {
|
|
let newEl = document.createElement('a-box')
|
|
newEl.setAttribute("scale", p.scale)
|
|
newEl.setAttribute("position", p.position)
|
|
newEl.setAttribute("rotation", p.rotation)
|
|
newEl.setAttribute("target","true")
|
|
newEl.setAttribute('static-body', '')
|
|
newEl.setAttribute("onreleased", "document.querySelector('["+generatorName+"]').emit('getVoxelPoses')")
|
|
newEl.classList.add( "voxel_" + generatorName )
|
|
newEl.classList.add( "voxel" )
|
|
newEl.classList.add( generatorName )
|
|
AFRAME.scenes[0].appendChild(newEl)
|
|
})
|
|
}
|
|
},
|
|
events: {
|
|
reset: function (evt) {
|
|
console.log(this.attrName, 'component was resetted!');
|
|
let generatorName = this.attrName
|
|
Array.from( this.el.querySelectorAll("."+generatorName) ).map( el => deleteTarget(el) )
|
|
},
|
|
check: function (evt) {
|
|
},
|
|
getVoxelPoses: function (evt){
|
|
console.log('generating poses')
|
|
let generatorName = this.attrName
|
|
let poses = []
|
|
// to clarify querySelectorAll here is done on the element and thus should no interfer with other components using the same mechanism
|
|
// Array.from( this.el.querySelectorAll(".voxel") ).map( el => {
|
|
Array.from( AFRAME.scenes[0].querySelectorAll(".voxel_" + generatorName ) ).map( el => {
|
|
poses.push( {
|
|
position: this.shortenVector3( el.getAttribute("position") ),
|
|
rotation: this.shortenVector3( el.getAttribute("rotation") ),
|
|
scale: this.shortenVector3( el.getAttribute("scale") ),
|
|
} )
|
|
})
|
|
let data = {}
|
|
data[generatorName] = poses
|
|
window.location.hash = JSON.stringify(data)
|
|
console.log('generated poses:', poses.length)
|
|
// prefixed by generatorName in order to support saving/sharing of the state of other games
|
|
},
|
|
},
|
|
shortenVector3: function ( v ){
|
|
let o = new THREE.Vector3()
|
|
o.x = v.x.toFixed(3)
|
|
o.y = v.y.toFixed(3)
|
|
o.z = v.z.toFixed(3)
|
|
return o
|
|
},
|
|
twoPosToBox(A, B, generatorName){
|
|
let center = A.clone()
|
|
center.add(B)
|
|
center.divideScalar(2)
|
|
let lengthes = A.clone()
|
|
lengthes.sub(B)
|
|
let newEl = document.createElement("a-box")
|
|
newEl.setAttribute("position", center )
|
|
newEl.setAttribute('target', '')
|
|
newEl.setAttribute('static-body', '')
|
|
newEl.classList.add( "voxel" )
|
|
newEl.classList.add( generatorName )
|
|
newEl.classList.add( "voxel_" + generatorName )
|
|
newEl.setAttribute("scale", lengthes.toArray().map( i => Math.abs(i) ).join(" ") )
|
|
newEl.setAttribute("onreleased", "document.querySelector('["+generatorName+"]').emit('getVoxelPoses')")
|
|
AFRAME.scenes[0].appendChild(newEl)
|
|
// should parent to the component element instead...
|
|
return newEl
|
|
}
|
|
})
|
|
|
|
//___________________________________________________________________________________________________________________________________
|
|
AFRAME.registerComponent('voxelpaint', {
|
|
init: function(){
|
|
let generatorName = this.attrName
|
|
let el = this.el
|
|
this.colors = ["red", "green", "blue", "yellow" ]
|
|
this.scale = 1/10
|
|
this.yOffset = 1.5
|
|
let j = 0
|
|
this.colors.map( (color, i) => {
|
|
let newEl = document.createElement('a-box')
|
|
newEl.setAttribute("scale", ""+this.scale+" "+this.scale+" "+this.scale)
|
|
newEl.setAttribute("color",color)
|
|
newEl.setAttribute("position", ""+(i%2)*this.scale+" "+(this.yOffset+j*this.scale)+" -.5")
|
|
newEl.setAttribute("target","true")
|
|
newEl.setAttribute("onpicked", "document.querySelector('["+generatorName+"]').emit('check')")
|
|
newEl.setAttribute("onreleased", "document.querySelector('["+generatorName+"]').emit('getVoxelPoses')")
|
|
newEl.classList.add( generatorName )
|
|
el.appendChild(newEl)
|
|
if (i==1) j++
|
|
})
|
|
// check if data is present, as hash or query parameter, and if so, display accordingly
|
|
if (window.location.hash) {
|
|
let poses = JSON.parse(decodeURI(window.location.hash.replace("#",'')))[generatorName]
|
|
// prefixed by generatorName in order to support saving/sharing of the state of other games
|
|
poses.map( p => {
|
|
let newEl = document.createElement('a-box')
|
|
newEl.setAttribute("scale", ""+this.scale+" "+this.scale+" "+this.scale)
|
|
newEl.setAttribute("color",p.color)
|
|
newEl.setAttribute("position", p.position)
|
|
newEl.setAttribute("rotation", p.rotation)
|
|
newEl.setAttribute("target","true")
|
|
newEl.setAttribute("onreleased", "document.querySelector('["+generatorName+"]').emit('getVoxelPoses')")
|
|
newEl.classList.add( "voxel" )
|
|
newEl.classList.add( generatorName )
|
|
el.appendChild(newEl)
|
|
})
|
|
}
|
|
},
|
|
events: {
|
|
reset: function (evt) {
|
|
console.log(this.attrName, 'component was resetted!');
|
|
let generatorName = this.attrName
|
|
Array.from( this.el.querySelectorAll(".voxel") ).map( el => el.remove() )
|
|
},
|
|
check: function (evt) {
|
|
let generatorName = this.attrName
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
let newEl = latest.cloneNode(true) // does not seem to properly clone all attributes, e.g color works but not position or scale
|
|
// ["scale", "position", "onpicked"].map( prop => console.log( prop)) //newEl.setAttribute(prop, latest.getAttribute(prop) )
|
|
newEl.setAttribute("scale", latest.getAttribute("scale") )
|
|
newEl.setAttribute("position", latest.getAttribute("position") )
|
|
newEl.setAttribute("onpicked", latest.getAttribute("onpicked") )
|
|
latest.removeAttribute("onpicked")
|
|
latest.classList.add( "voxel" )
|
|
this.el.appendChild( newEl )
|
|
// could also snap it back, e.g clear rotation, possibily use initially position
|
|
},
|
|
getVoxelPoses: function (evt){
|
|
let generatorName = this.attrName
|
|
let poses = []
|
|
Array.from( this.el.querySelectorAll(".voxel") ).map( el => {
|
|
poses.push( {
|
|
position: this.shortenVector3( el.getAttribute("position") ),
|
|
rotation: this.shortenVector3( el.getAttribute("rotation") ),
|
|
color: el.getAttribute("color")
|
|
} )
|
|
})
|
|
let data = {}
|
|
data[generatorName] = poses
|
|
window.location.hash = JSON.stringify(data)
|
|
// prefixed by generatorName in order to support saving/sharing of the state of other games
|
|
}
|
|
},
|
|
shortenVector3: function ( v ){
|
|
let o = new THREE.Vector3()
|
|
o.x = v.x.toFixed(3)
|
|
o.y = v.y.toFixed(3)
|
|
o.z = v.z.toFixed(3)
|
|
return o
|
|
}
|
|
})
|
|
|
|
|
|
//___________________________________________________________________________________________________________________________________
|
|
AFRAME.registerComponent('simon', {
|
|
init: function(){
|
|
let generatorName = this.attrName
|
|
let el = this.el
|
|
this.colors = ["red", "green", "blue", "yellow" ]
|
|
const notePrefix = '../content/notes/t'
|
|
const noteSuffix = '.mp3'
|
|
this.sequence = []
|
|
this.posInSeq = -1
|
|
this.userSeq = []
|
|
this.scale = 1/10
|
|
let j = 0
|
|
this.colors.map( (color, i) => {
|
|
let newEl = document.createElement('a-box')
|
|
newEl.setAttribute("scale", ""+this.scale+" "+this.scale+" "+this.scale)
|
|
newEl.setAttribute("color",color)
|
|
newEl.setAttribute("opacity", .5)
|
|
newEl.setAttribute("sound", "src:url("+notePrefix+(i+1)+noteSuffix+")")
|
|
newEl.setAttribute("position", ""+(i%2)*this.scale+" 1.1 "+j*this.scale)
|
|
newEl.setAttribute("target","true")
|
|
newEl.setAttribute("onreleased", 'document.querySelector("['+generatorName+']").emit("check",{color:"'+color+'"})')
|
|
newEl.classList.add( generatorName )
|
|
el.appendChild(newEl)
|
|
if (i==1) j++
|
|
})
|
|
//setTimeout( _ => { document.querySelector("["+generatorName+"]").emit('playSequence') }, 1000)
|
|
},
|
|
events: {
|
|
reset: function (evt) {
|
|
console.log(this.attrName, 'component was resetted!');
|
|
let generatorName = this.attrName
|
|
this.sequence = []
|
|
this.posInSeq = -1
|
|
this.userSeq = []
|
|
// could also reposition the boxes
|
|
clearInterval( this.interval )
|
|
setTimeout( _ => { document.querySelector("["+generatorName+"]").emit('playSequence') }, 1000)
|
|
},
|
|
check: function (evt) {
|
|
// could also snap it back, e.g clear rotation, possibily use initially position
|
|
let generatorName = this.attrName
|
|
this.userSeq.push(evt.detail.color)
|
|
let box = this.el.querySelector("a-box[color="+evt.detail.color+"]")
|
|
box.components.sound.playSound()
|
|
console.log ('seq:', this.sequence.at( this.userSeq.length-1) ,'user:', this.userSeq.at(-1) )
|
|
if (this.userSeq.at(-1) == this.sequence.at( this.userSeq.length-1) ){
|
|
console.log('same', this.sequence, this.userSeq)
|
|
if (this.userSeq.length == this.sequence.length){
|
|
console.log('entire sequence complete', this.sequence, this.userSeq)
|
|
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_win')
|
|
this.userSeq = []
|
|
document.querySelector("["+generatorName+"]").emit('playSequence') // grow sequence
|
|
} else {
|
|
console.log('partial sequence only, waiting for new input')
|
|
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_yes')
|
|
}
|
|
} else {
|
|
document.querySelector("["+generatorName+"]").emit('reset')
|
|
animateThenIdle( document.querySelector("#biggu"), 'bigguaction_no')
|
|
console.log('failed, should reset')
|
|
}
|
|
},
|
|
playSequence: function(evt) {
|
|
this.interval = setInterval( _ => {
|
|
this.posInSeq++
|
|
if (this.posInSeq == this.sequence.length){
|
|
this.sequence.push( this.colors.at( this.colors.length * Math.random() ) )
|
|
clearInterval( this.interval )
|
|
setTimeout( _ => { this.posInSeq=-1 }, 100)
|
|
// should also a timeout to start giving answers
|
|
}
|
|
let box = this.el.querySelector("a-box[color="+ this.sequence[ this.posInSeq ] + "]")
|
|
box.setAttribute("opacity", 1)
|
|
box.components.sound.playSound()
|
|
setTimeout( _ => { box.setAttribute("opacity", .5) }, 700)
|
|
}, 1000)
|
|
}
|
|
}
|
|
})
|
|
|
|
//___________________________________________________________________________________________________________________________________
|
|
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
|
|
// should show the target or a box around the letter otherwise can be tricky to grab for some letters without a top left corner
|
|
// e.g B is easy, J is hard
|
|
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){
|
|
/* // 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;")
|
|
*/
|
|
// 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 )
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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 )
|
|
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>
|
|
}
|
|
|
|
function snapToGrid(gridSize=1){ // default as 1 decimeter
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
latest.setAttribute("rotation", "0 0 0")
|
|
let pos = latest.getAttribute("position")
|
|
pos.multiplyScalar(gridSize*10).round().divideScalar(gridSize*10)
|
|
latest.setAttribute("position", pos )
|
|
}
|
|
|
|
// deeper question, making the rules themselves manipulable? JXR?
|
|
// So the result of the grammar becomes manipulable, but could you make the rules of the grammar itself visual? Even manipulable?
|
|
// could start by visualizing examples first e.g https://writer.com/wp-content/uploads/2024/03/grammar-1.webp
|
|
function snapMAB(){
|
|
// multibase arithmetic blocks aka MAB cf https://en.wikipedia.org/wiki/Base_ten_block
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
let nearby = getClosestTargetElements( latest.getAttribute('position') )
|
|
let linked = []
|
|
if (nearby.length>0){
|
|
latest.setAttribute("rotation", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("rotation") ) )
|
|
latest.setAttribute("position", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("position") ) )
|
|
latest.object3D.translateX( 1/10 )
|
|
linked.push( latest )
|
|
linked.push( nearby[0].el )
|
|
let overlap = Array.from( document.querySelectorAll(".mab") ).filter( e => e.object3D.position.distanceTo( latest.object3D.position ) < 0.01 && e!=latest )
|
|
while (overlap.length > 0 ){
|
|
latest.object3D.translateX( 1/10 )
|
|
linked.push( overlap[0] )
|
|
overlap = Array.from( document.querySelectorAll(".mab") ).filter( e => e.object3D.position.distanceTo( latest.object3D.position ) < 0.01 && e!=latest )
|
|
}
|
|
// do something special if it becomes 10, e.g become a single line, removing the "ridges"
|
|
if (linked.length > 3)
|
|
linked.map( e => Array.from( e.querySelectorAll("a-box") ).setAttribute("color", "orange") )
|
|
|
|
// also need to go backward too to see if it's the latest added
|
|
}
|
|
}
|
|
|
|
function snapRightOf(){
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
let nearby = getClosestTargetElements( latest.getAttribute('position') )
|
|
if (nearby.length>0){
|
|
latest.setAttribute("rotation", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("rotation") ) )
|
|
latest.setAttribute("position", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("position") ) )
|
|
latest.object3D.translateX( 1/10 )
|
|
// somehow... works only the 2nd time, not the 1st?!
|
|
}
|
|
}
|
|
|
|
function grammarBasedSnap(){
|
|
// verify if snappable, e.g of same type (or not)
|
|
// e.g check if both have .getAttribute('value').match(prefix) or not
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
let nearby = getClosestTargetElements( latest.getAttribute('position') )
|
|
if (nearby.length>0){
|
|
let closest = nearby[0].el
|
|
let latestTypeJXR = latest.getAttribute('value').match(prefix)
|
|
let closestTypeJXR = latest.getAttribute('value').match(prefix)
|
|
latest.setAttribute("rotation", AFRAME.utils.coordinates.stringify( closest.getAttribute("rotation") ) )
|
|
latest.setAttribute("position", AFRAME.utils.coordinates.stringify( closest.getAttribute("position") ) )
|
|
if ( latestTypeJXR && closestTypeJXR )
|
|
latest.object3D.translateX( 1/10 ) // same JXR type, snap close
|
|
else
|
|
latest.object3D.translateX( 2/10 ) // different types, snap away
|
|
// somehow... works only the 2nd time, not the 1st?!
|
|
}
|
|
}
|
|
|
|
function cloneTarget(target){
|
|
let el = target.cloneNode(true)
|
|
if (!el.id)
|
|
el.id = "clone_" + crypto.randomUUID()
|
|
else
|
|
el.id += "_clone_" + crypto.randomUUID()
|
|
AFRAME.scenes[0].appendChild(el)
|
|
}
|
|
|
|
function deleteTarget(target){
|
|
targets = targets.filter( e => e != target)
|
|
target.remove()
|
|
}
|
|
|
|
function runClosestJXR(){
|
|
// ideally this would come from event details
|
|
let latest = selectedElements[selectedElements.length-1].element
|
|
let nearby = getClosestTargetElements( latest.getAttribute('position') )
|
|
// if (nearby.length>0){ interpretJXR( nearby[0].el.getAttribute("value") ) }
|
|
nearby.map( n => interpretJXR( n.el.getAttribute("value") ) )
|
|
}
|
|
|
|
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; '>
|
|
<a href="https://git.benetou.fr/utopiah/text-code-xr-engine/issues/">
|
|
<img style='position:fixed;left:10px;' title='code repository' src='gitea_logo.svg'>
|
|
</a>
|
|
</div>
|
|
|
|
<a-scene startfunctions >
|
|
|
|
<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;"
|
|
hud camera look-controls wasd-controls position="0 1.6 0">
|
|
</a-entity>
|
|
<a-entity id="rightHand" pinchprimary hand-tracking-controls="hand: right;"></a-entity>
|
|
<a-entity id="leftHand" pinchsecondary wristattachsecondary="target: #box" hand-tracking-controls="hand: left;"></a-entity>
|
|
</a-entity>
|
|
|
|
<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 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="-.5 1.30 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
|
|
<a-troika-text anchor=left value="jxr window.location.hash = ''" target position="-.5 1.40 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-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-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-scene>
|
|
</body>
|
|
</script>
|
|
</html>
|
|
|