Aframe dynamic importing of gltf file - aframe

So I have a simply gltf file and i would like to import it with a-frame during runtime.
I already tried to convert the gltf string to a URL and load it, however this gives a components:gltf-model:warn Cannot read properties of undefined (reading '0') warning. This is the code i tried:
<a-scene>
<a-entity id="entity" position="0 2 -5"></a-entity>
</a-scene>
<script>
const gltfString = `{"cacessors":[{"bufferView":0,"byteOffset":0,"count":3,"componentType":5126,"extras":{},"type":"VEC3","min":[-0.5,-0.5,0.0],"max":[0.5,0.5,0.0]},{"bufferView":0,"byteOffset":12,"count":3,"componentType":5126,"extras":{},"type":"VEC3"}],"asset":{"extras":{},"version":"2.0"},"buffers":[{"byteLength":72,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAAAAAACAPwAAAAAAAAAAAAAAvwAAAL8AAAAAAAAAAAAAgD8AAAAAAAAAPwAAAL8AAAAAAAAAAAAAAAAAAIA/","extras":{}}],"bufferViews":[{"buffer":0,"byteLength":72,"byteStride":24,"target":34962,"extras":{}}],"extras":{},"meshes":[{"extras":{},"primitives":[{"attributes":{"POSITION":0,"COLOR_0":1},"extras":{}}]}],"nodes":[{"extras":{},"mesh":0}],"scenes":[{"extras":{},"nodes":[0]}]}`;
const gltfBlob = new Blob([gltfString]); // convert string to blob
const gltfUrl = URL.createObjectURL(gltfBlob, {type: "text/plain"}); // blob to URL
document.addEventListener('DOMContentLoaded', function (event) {
const entity = document.querySelector('#entity');
console.log(entity) // blob:http://localhost:5500/4efe2c0d-5568-4928-8820-2e686b5b0c2a
bruhEntity.setAttribute('gltf-model', `url(${gltfUrl})`);
});
</script>
What would be the way to achieve this?
This is my glitch project

There was a typo in the string, here's a simple way to find out whats wrong:
Save the buffer in a file test.gltf.
Throw it into the gltf-viewer
Find out there is a typo (from the error this.json.accessors is undefined)
change cacessors to accessors
voila:
const gltfString = `{"accessors":[{"bufferView":0,"byteOffset":0,"count":3,"componentType":5126,"extras":{},"type":"VEC3","min":[-0.5,-0.5,0.0],"max":[0.5,0.5,0.0]},{"bufferView":0,"byteOffset":12,"count":3,"componentType":5126,"extras":{},"type":"VEC3"}],"asset":{"extras":{},"version":"2.0"},"buffers":[{"byteLength":72,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAAAAAACAPwAAAAAAAAAAAAAAvwAAAL8AAAAAAAAAAAAAgD8AAAAAAAAAPwAAAL8AAAAAAAAAAAAAAAAAAIA/","extras":{}}],"bufferViews":[{"buffer":0,"byteLength":72,"byteStride":24,"target":34962,"extras":{}}],"extras":{},"meshes":[{"extras":{},"primitives":[{"attributes":{"POSITION":0,"COLOR_0":1},"extras":{}}]}],"nodes":[{"extras":{},"mesh":0}],"scenes":[{"extras":{},"nodes":[0]}]}`;
const gltfBlob = new Blob([gltfString], {
type: "text/plain"
}); // convert string to blob
const gltfUrl = URL.createObjectURL(gltfBlob, ); // blob to URL
const entity = document.querySelector('#entity');
entity.setAttribute('gltf-model', `url(${gltfUrl})`);
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<a-scene renderer="colorManagement: true">
<a-light position="0 2 -1.9" intensity=10 type="point"></a-light>
<a-entity id="entity" position="0 2 -2"></a-entity>
</a-scene>

Related

Find attached child objects of a-frame/ a-marker

I have a small A-frame based program to render text on a marker. When user click on the marker, I would like to scale the text, so it will be more visible. I can capture the click-event on the marker but how can I get it's child entities to scale?
My code can be found in https://codepen.io/asatrash/pen/rNLMgpa
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
</head>
<body>
<script>
//Multi Markers WebAR-AR.js and Aframe - Playing the Archive - Connected Environment CASA-UCL
//Global Variable
var markersURLArray=[];
var markersNameArray=[];
var allow_clicks = false;
AFRAME.registerComponent('markers_start',{
init:function(){
console.log('Add markers to the scene');
var sceneEl = document.querySelector('a-scene');
//list of the markers
for(var i=1; i<6; i++)`enter code here`
{
var url="https://raw.githubusercontent.com/asatrash/ARInWeb/main/resources/markers/pattern-Individual_Blocks-3.patt";
markersURLArray.push(url);
markersNameArray.push('Marker_'+i);
//console.log(url);
}
for(var k=0; k<5; k++)
{
var markerEl = document.createElement('a-marker');
markerEl.setAttribute('type','pattern');
markerEl.setAttribute('url',markersURLArray[k]);
markerEl.setAttribute('id',markersNameArray[k]);
markerEl.setAttribute('registerevents','');
sceneEl.appendChild(markerEl);
var boxEl = document.createElement('a-box');
boxEl.setAttribute('id','box');
boxEl.setAttribute('depth','0.001');
boxEl.setAttribute('width','6');
boxEl.setAttribute('height','4');
boxEl.setAttribute('opacity','0.25');
boxEl.setAttribute('position','0 -2 -4');
boxEl.setAttribute('rotation','0 0 0');
boxEl.setAttribute('material', {color: '#000000'});
markerEl.appendChild(boxEl);
var textEl = document.createElement('a-text');
textEl.setAttribute('id','text');
textEl.setAttribute('text',{color: '#ffff00', align: 'left', value:'This is a very log text /b \n line which might or might not wrap', width: '2' });
//textEl.setAttribute('position', '-1.4 1.5 0');
textEl.setAttribute('position', '-2.5 1.5 0')
textEl.setAttribute('scale', '2 2 0');
boxEl.appendChild(textEl);
}
}
});
//Detect marker found and lost
AFRAME.registerComponent('registerevents', {
init: function () {
const marker = this.el;
marker.addEventListener("markerFound", ()=> {
var markerId = marker.id;
console.log('Marker Found: ', markerId);
allow_clicks = true;
});
marker.addEventListener("markerLost",() =>{
var markerId = marker.id;
console.log('Marker Lost: ', markerId);
allow_clicks = false;
});
marker.addEventListener('click', (evt) => {
if (allow_clicks){
console.log("CLICKED!!!" + marker.id);
//I want to scale the box and the text attached to the marker jere
}
});
},
});
</script>
<a-scene markers_start vr-mode-ui="enabled: false" color-space="sRGB" renderer="gammaOutput: true"
embedded arjs='debugUIEnabled: false; sourceType: webcam; patternRatio: 0.85; trackingMethod: best;'>
<a-entity id='userCamera' camera look-controls >
<a-cursor> </a-cursor>
</a-entity>
</a-scene>
</body>
</html>
Did bit more testing and found out the following.
The 'click' event doesn't work as I expected on the marker. It is registered to the whole scene and it get invoked irrespective of the place I click. Which is not what I want. I am looking at using intersection Points to get it working.
However the getting access to the child can be easily done if you put unique ids. We can use the normal document.getElementById("boxelementId"); and change the attributes using the setAttribute method.
Ex: To change the text
var markerTextEl = document.getElementById(markerTextxId);
markerTextEl.setAttribute("value", "Current Temperature is: " + data);
I used above inside the 'markerFound' event to show different/current value when user point to a particular marker.

Add link to GLTF object in aframe

Is it possible to add a link to an GLTF 3d object (which is triggered with a marker?)
I've tried the usual 'a-link' method, the onClick method, even applying an id and using jQuery - all without luck - any help would be appreciated.
<a-scene embedded arjs>
<a-marker id="dragon" preset="custom" type="pattern" url="pattern-dragonfly.patt">
<a-entity animation-mixer="clip: *;" scale="1.5 1.5 1.5" gltf-model-next="src: url(dragon.gltf);"></a-entity>
</a-marker>
<a-entity camera></a-entity>
</a-scene>
To make this work, you need to create a cursor with a raycaster, and a custom component for the gltf.
<a-entity id="mouseCursor" cursor="rayOrigin: mouse" raycaster="objects: .clickable"></a-entity>
<a-entity id="tree" gltf-model="#gltftree" scale="5 5 5" treeman class="clickable" ></a-entity>
Inside the custom component, first you traverse the gltf and store references to the models that you want to be interactive, like this
init: function(){
let el = this.el;
let self = this;
self.trees = [];
el.addEventListener("model-loaded", e =>{
let tree3D = el.getObject3D('mesh');
if (!tree3D){return;}
console.log('tree3D', tree3D);
tree3D.traverse(function(node){
if (node.isMesh){
console.log(node);
self.trees.push(node);
node.material = new THREE.MeshStandardMaterial({color: 0x33aa00});
}
});
});
Then you make event listeners that detect intersection events, and save which object has been intersected, and highlight it, so users know it is live, like this
el.addEventListener('raycaster-intersected', e =>{
self.raycaster = e.detail.el;
let intersection = self.raycaster.components.raycaster.getIntersection(el);
console.log('click', intersection.object.name, self.mouseOverObject,
intersection.object.name != self.mouseOverObject );
if (self.mouseOverObject != intersection.object.name){
intersection.object.material.emissive = new THREE.Color(0xFFFF00);
intersection.object.material.emissiveIntensity = 0.5;
} else {
intersection.object.material.emissive = new THREE.Color(0x000000);
intersection.object.material.emissiveIntensity = 0.0;
}
self.mouseOverObject = intersection.object.name;
});
el.addEventListener('raycaster-intersected-cleared', e =>{
self.trees.forEach(function(tree){
tree.material.emissive = new THREE.Color(0x000000);
tree.material.emissiveIntensity = 0.0;
});
self.mouseOverObject = null;
});
Finally add a click listener that operate the hyperlink, like this
el.addEventListener('click', function(){
console.log(self.mouseOverObject);
if(self.mouseOverObject === "Trunk_A"){
console.log('link');
let url = 'https://supermedium.com/supercraft/';
let win = window.open(url, '_blank');
win.focus();
}
});
glitch here
Click the trunk to activate the hyperlink.

Aframe - draw in a-sphere or videosphere or a-sky

How can make a function like in this sample:
https://codepen.io/knee-cola/pen/OpmYyp.
I am reciceving this error:
Uncaught TypeError: Cannot read property 'setFromCamera' of null.
This is my code:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width">
<title>EXPERIMENT ONLY</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="aframe.js"></script>
<script type="text/javascript">
$(document).ready(function() {
document.addEventListener('mousemove', onMouseMove, false);
});
function onMouseMove(ev) {
var sceneEl = document.querySelector('a-scene');
ev.preventDefault();
ev.cancelBubble = true;
var app = this;
var threePoint = _findIntersection(sceneEl, ev.clientX, ev.clientY);
}
function _findIntersection(app, clientX, clientY) {
var raycasterEl = AFRAME.scenes[0].querySelector('[raycaster]');
var sphereEl = document.querySelector('#bilog');
var cameraEl = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// converting click coordinates from document-space to Three space
var mouseRay = new THREE.Vector2();
mouseRay.x = ( clientX / window.innerWidth ) * 2 - 1;
mouseRay.y = - ( clientY / window.innerHeight ) * 2 + 1;
// intersecting mouse ray with all the objects in the scene
raycasterEl.setFromCamera(mouseRay, cameraEl);
var intersects = raycasterEl.intersectObjects( sphereEl );
if(intersects.length===0) {
return(null);
}
return(intersects[0].point);
}
</script>
</head>
<body>
<a-scene>
<a-assets>
<canvas id="my-canvas" crossorigin="anonymous"></canvas>
</a-assets>
<a-sphere id="bilog" radius="1" material="src: #my-canvas; side:front" draw-canvas="my-canvas" position="0 0 0"></a-sphere>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>
I have tried very hard but still no success. I don't know three.js and have a little knowledge in Aframe. Please help me. Thank you.
You have to wait until the scene has loaded:
document.querySelector(‘a-scene’).addEventListener(‘loaded’, function() { ... your code ... });
There’s no entity with a raycaster entity in your scene so document.querySelector(‘[raycaster]’) will return null
You also need to call the function on the component, not the entity:
raycasterEl.components.raycaster.setFromCamera(...)
There might be other problems. I recommend reading through the A-Frame docs to get you acquainted with the scene and components APIs

adding object dynamically in live a-frame camera view

I am trying to place an image in a-frame camera view. please share an example.
A quick way to do this is to add an invisible "marker" as a child of the camera, and use its position as the spawn point when adding an object.
HTML
<a-scene>
<a-camera>
<a-entity id="marker" position="0 0 -5"></a-entity>
</a-camera>
<a-cylinder id="floor" height="0.1" radius="10" color="green"></a-cylinder>
</a-scene>
JS
var sceneEl = document.querySelector('a-scene');
var markerEl = document.querySelector('#marker');
// Add boxe when spacebar is pressed.
document.addEventListener('keyup', function (e) {
if (e.keyCode !== 32) return;
var newEl = document.createElement('a-box');
newEl.setAttribute('color', 'red');
sceneEl.appendChild(newEl);
var position = markerEl.object3D.getWorldPosition();
position.y = 0.5;
newEl.setAttribute('position', position);
});
Codepen: https://codepen.io/donmccurdy/pen/QOOXbK?editors=1010

Using ambientOcclusionMap with tiled textures in A-frame

Does anyone have an example of applying ambientOcclusionMap to an OBJ model?
To save on texture size I'd like to make tiled materials using the material component and bake the shadows into the ambientOcclusionMap separately. However, when I apply the AO map and adjust the intensity, the whole material gets darker - no visible shadow map. What am I missing? Any ideas?
<a-entity obj-model="obj:#brick_walls-obj" material="src: #brick_walls_tiled_T; repeat: 10 10; ambientOcclusionMap: #brick_walls_shadow; ambientOcclusionMapIntensity: 0.5"></a-entity>
For example - http://codepen.io/MannyMeadows/pen/KWZWvY
The material component doesn't tend to work with custom models. What you can do, though, is create a component that makes whatever changes you need directly to the THREE.Material object.
AFRAME.registerComponent('material-map', {
schema: {
map: {default: ''},
uri: {default: ''}
},
init: function () {
this.textureLoader = new THREE.TextureLoader();
this.modify();
this.el.addEventListener('model-loaded', this.modify.bind(this));
},
modify: function () {
const textureLoader = this.textureLoader;
const mesh = this.el.getObject3D('mesh');
const data = this.data;
if (!mesh) return;
mesh.traverse(function(node) {
if (node.material) {
switch (data.map) {
case 'emissiveMap':
node.material.emissive = node.material.color.clone();
node.material.emissiveMap = textureLoader.load(data.uri);
break;
default:
node.material[data.map] = textureLoader.load(data.uri);
}
node.material.needsUpdate = true;
}
});
}
});
Usage:
<a-entity obj-model="obj: foo.obj; mtl: foo.mtl"
material-map="map: aoMap; uri: foo_ao.png">
</a-entity>
NOTE: Your original code also looks correct to me, so I'm not 100% sure why it isn't working... This is something I've done in similar cases, though, so maybe it will work.

Resources