How does the asset schema property work with img assets? - aframe

I am trying to pass an image as an asset property type to some other component (so that either a #selector or a url(url) can be passed) but it seems to take in the entire html component, instead of just the url.
<!DOCTYPE html>
<html>
<head>
<title>Hello, WebVR! - A-Frame</title>
<meta name='description' content='Hello, WebVR! - A-Frame'>
<script src='../../global/js/aframe-v0.8.0.min.js'></script>
<script>
AFRAME.registerComponent('some-component', {
schema: {
image: {type:'asset', default:''},
model: {type:'asset', default:''}
},
init: function() {
console.log(this.data.image); //prints out <img id="SomeImage" src="../../someDir/someFile.jpg">
console.log(this.data.model); //prints out '../../someDir/someModel.gltf'
}
});
</script>
</head>
<body>
<a-scene >
<a-assets timeout='3000'>
<!-- this works as an asset no problem -->
<a-asset-item id='SomeModel' src='../../global/assets/models/gltf/UserHead/UserHead.gltf'></a-asset-item>
<!-- this does not pass as an asset but rather an html element -->
<img id='SomeImage' src='../../global/assets/textures/equirectangular/CloudySky.jpg'>
</a-assets>
<a-entity some-component='image:#SomeImage; model:#SomeModel;'></a-entity>
</a-scene>
</body>
</html>
Thought I might look to see how A-Frame handles this in the material component can't see where does the 'src' property on material come from?
<a-entity id='skyBox'
geometry='primitive:sphere; radius:50; segments-height:6; segments-width:6;'
material='shader:flat; src:#skyMap; side:back; height:2048; width:2048'>
</a-entity>
Material component (can't see src): https://github.com/aframevr/aframe/blob/master/src/components/material.js
Thanks!
EDIT:
As per Piotr's discoveries below it looks like images are handed as a special case, as can be in the src code here with frame 0.8.0 where an image source is handed like this:
hash: function (data) {
if (data.src.tagName) {
// Since `data.src` can be an element, parse out the string if necessary for the hash.
data = utils.extendDeep({}, data);
data.src = data.src.src;
}
return JSON.stringify(data);
},
So basically if an image we the asset property will not handle image's properly and an additional step of grabbing the url from it via
data.src.src
OR
data.src.getAttribute('src');

Please correct me if i'm wrong, but i think it's not in the material schema.
I think the devil's in the component.js, which not only seems to allow you to assign a value to any given property, but also has the constructor for any component, and parses the schema.
That being said, the material does not need a src in the schema, as it seems to be a part of every component. Furthermore there are multiple parsers like the assetParse, or src-loader, checking whether an attribute is a html element, or even a video / image asset.
As for the material part, check out the dist source code, where
I think what you're looking for is:
module.exports.updateMapMaterialFromData
where you can see a-frame team uses the data.src for the material, and updates the texture with it. Just give it a ctrl+F (only 3 hits).
So when
module.exports.updateMapMaterialFromData('map', 'src', shader, data);
is called, with the given definition:
module.exports.updateMapMaterialFromData = function (materialName, dataName, shader, data) {
var el = shader.el;
var material = shader.material;
var src = data[dataName];
.......
makes src = data[src] => they make updates using a local variable src.
Also you can see the src-loader in action where the material system is registered, and when a src attribute is found out, the validateSrc function fires one of the two callbacks:
utils.srcLoader.validateSrc(src, loadImageCb, loadVideoCb);

Related

aframe glTF cube-env-map

I'm unable to see any reflexion on my gtLF model using "cube-env-map".
I'd like to get something like this :
helmet from threejs.org examples
I don't know if this is because of the .jpg files I use, or html or linked javascript scripts, or... anything else?
Here's my html :
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script><!-- Master file for aframe (== a-scene) -->
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script><!-- webcam/mobilecam -->
<script src="https://mkwy.fr/js/play-all-model-animations.js"></script><!-- animation -->
<script src="https://mkwy.fr/js/aframe-orbit-controls.min.js"></script><!-- orbit cam around target -->
<script src="https://mkwy.fr/js/aframe-extras.js"></script><!-- cub-env-map -->
</head>
<body>
<a-scene vr-mode-ui="enabled: false" embedded>
<a-assets>
<a-asset-item id="toy" src="https://mkwy.fr/assets/toyDrummerSolo.glb"></a-asset-item>
</a-assets>
<a-entity
gltf-model="#toy"
cube-env-map="path: https://mkwy.fr/assets/cube-env/; extension: jpg; reflectivity: 0.9;"
play-all-model-animations >
</a-entity>
<a-entity camera look-controls orbit-controls="target: 0 1 0; minDistance: 0.5; maxDistance: 60; initialPosition: 0 5 5"></a-entity>
</a-scene>
</body>
</html>
Many thanks in advance for your help,
(Assuming there are no console errors suggesting incorrect cubemap paths, or wrong extensions)
If your model looks like this:
And you want it to be more like this:
Then the answer lies in two factors - metalness, and roughness.
roughness determines whether the material is like a mirror (0), or completely diffuses the reflection (1).
metalness determines whether the material is metallic (1), or not (0).
You can deal with this in at least two ways:
Modify the materials in a modelling software like blender, or maya.
Modify the properties within an a-frame custom component.
A component would have to wait until the model is loaded, and change all (or some selected) model nodes. Like this:
AFRAME.registerComponent("foo", {
init: function() {
// wait until the model is loaded
this.el.addEventListener("model-loaded", e => {
// grab the mesh
let mesh = this.el.getObject3D("mesh");
mesh.traverse(node => {
// ignore nodes without materials
if (!node.material) return;
// assign the values.
node.material.metalness = 1;
node.material.roughness = 0;
})
})
}
})
You can check it out in this example.
The helmet is working with both jpg and png maps. As for arjs, if your model will be glitchy, and you'll experience z-fighting, just set the logarithmicDepthBuffer in the renderer:
<a-scene renderer="logarithmicDepthBuffer: true" embedded arjs>
Example here
.Seems to be working as expected:
Ok, I changed the settings in Blender, adding metalness, and now it works like a charm. It was confusing because in Blender you don't need to set metalness to get reflection (i.e. : a plastic bottle may have reflections). I have to play with these parameters. Many thanks!

Webfundamentals, CustomElement with shadowDOM and HTML Templates with HTML import

I have various questions about the webfundamentals implementations, i have read that a true web component must have shadowDOM for css encapsulation, customElements for the logic of the component which i really love, and HTML Temapltes and import, so im trying to do it all in a customElement component and i have encounter with many and many issues that i find really hard to debug, i will enlist them all.
Does i have to insert html template into the document to actually get it? cant i get its content from js only? and in case i have to, when i intent to replace a shadowHost content hows is it works, i mean i got the template (the link) inside the shadowRoot, my actuall problem is that when i do querySelector(link[rel="import"]).import.querySelector("template") its null after the .import function tag and when i insert that function into the document, it actually gets the template content, heres the doc.
Watching that screenshot i got 2 more questions
Should i use shadowHost.innerHTML = file.querySelector(link[rel="import"]).import.querySelector("template")
to use the tag and copy its content inside the shadowRoot element? i mean how can i implement that approach? im using Angular as first example, they use an HTML file (which im guessing its a template or slots tag) and then they add it into the component as parameters on the constructor, so how with HTMLTemplates and HTMLImport i can implement that behaviour, i have used the documented functions but it doesnt works in the final phase.
Should i keep <link rel="import"> inside the shadowRoot or inside the document.head? can i implement the template without the need of adding it into the document?
I have been trying for days to do a simple customElement with shadowDOM that works completly fine, the problem is when i try to add an external to make it more robust.
Any helps? suggestions? i can show some functions that i use on the components to have an idea.
class EgHeader extends HTMLElement {
constructor() {
super();
this.shadowHost = shadowHost.bind(this);
this.shadowStyle = shadowStyle.bind(this);
this.shadowTemplate = shadowTemplate.bind(this);
this.host = this.shadowHost();
}
connectedCallback() {
this.defaultProperties();
let importSelector = this.host.querySelector(`link[rel="import"]`);
console.log(importSelector);
// this.host.appendChild(importSelector.cloneNode(true));
this.host.innerHTML = importSelector.import.querySelector(
"template"
).content;
}
defaultProperties() {
this.getAttributeNames().forEach(key => {
console.log(key);
if (key === "css") {
return this.shadowStyle(this.getAttribute(key));
}
if (key === "template") {
return this.shadowTemplate(this.getAttribute(key));
}
});
}
}
customElements.define("eg-header", EgHeader);
function shadowHost() {
let root = this.attachShadow({
mode: "open"
});
return root;
}
function shadowStyle(stylesheet) {
let link = document.createElement("link");
link.rel = "stylesheet";
link.href = stylesheet + ".css";
this.host.appendChild(link.cloneNode(true));
return link;
}
function shadowTemplate(link) {
var template = document.createElement("link");
template.rel = "import";
template.id = `${link}-template`;
template.href = link + ".html";
document.head.appendChild(template);
this.host.appendChild(template);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="./Header.js"></script>
<script src="./index.js"></script>
</head>
<body>
<eg-header css="./Header" template="./Header">
</eg-header>
</body>
</html>
// Separated file called Header.html
<template>
<nav>This is X element</nav>
<script>
console.warn("Executed when the template is activated.");
</script>
</template>
i have read that a true web component must have shadowDOM for css encapsulation, customElements for the logic of the component which i really love, and HTML Temapltes and import
What you've read is quite outdated:
HTML Imports are deprecated so you should use another method to load templates.
Because of #1, HTML templates (aka <template> elements) are often replaced par template literals.
Templates literals can be define in Javascript. This way they can be defined in a classical Javascript file or in the ES6 module.
By the way, if you still want to use HTML Imports (not recommanded) , you'll need to use a polyfill.
<link rel="import"> should be put in the <head> element of the main document, not in the Shadow DOM.
If you want to use <template> you don't need to append it to the main document.
var template = document.createElement( 'template' )
template.innerHTML = `
<h1>Title</h1>
<div>Content</div>
`
...
this.shadowRoot.appendChild( template.content.clone( true ) )

scene api - getAframeElements

I have use floorplan conversion api to convert my 2d floorplan to 3d and get the sceneId back 71c8eef9-b44e-447f-a0d2-fd299318da56.
I want to convert it to afame component inside my aframe application.So I use getAframeElements and get two entity back and following your official sample:
const sceneEl = document.querySelector('a-scene')
io3d.scene.getAframeElements(sceneId)
.then(elements => {
// this will give us two elements
// The first is the actual scene according to the scene structure hierarchy
// The second is the camera with potential waypoints that where defined in the scene structure
// you can leverage the waypoints using our A-Frame tour component
elements.forEach((el) => {
// add elements to the scene
sceneEl.appendChild(el)
})
sceneEl.appendChild(element)
})
Then It added to aframe, but nothing happens! I got nothing.
Do I miss something?
Hard to tell what's going on there without the full HTML and JavaScript, but the full code to get that working is:
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
<script src="https://3d.io/releases/3dio-js/1.x.x/3dio.min.js"></script>
</head>
<body>
<a-scene></a-scene>
<script>
io3d.scene.getAframeElements('71c8eef9-b44e-447f-a0d2-fd299318da56').then(elems => {
document.querySelector('a-scene').appendChild(elems[0])
})
</script>
</body>
</html>
And you can see it working at https://aspiring-snowman.glitch.me/

Polymer web-component-tester failing due to variant browser viewport sizes

I'm using the Polymer/web-component-tester to run automated tests of my components.
I've run into an issue where a component test will pass if run in isolation, but fail when run using a file glob - for example:
FAILS: wct components/**/test
SUCCEEDS: wct components/btn-component/test
After a fair bit of digging, I found the reason is the change in browser behaviour: in both cases the launched browser has two iFrames side-by-side, with the right one showing the test progress, and the left showing the component. The globbed test run results in a significantly narrower left (component) iFrame.
When using polymer-gestures to simulate mouse clicks, the narrower iFrame causes issues because it can often render a horizontal scrollbar and change a component's clickability.
The following is an example of a component & test that fails as described. It renders a Cancel button a few hundred pixels to the right.
Component
<link rel="import" href="../../bower_components/polymer/polymer.html">
<polymer-element name="btn-component" attributes="name">
<template>
<style>
:host {
display: block;
width: 400px;
}
</style>
<div layout horizontal>
<span flex></span>
<div id="cancel_button" on-tap="{{cancel}}">Cancel</div>
</div>
</template>
<script>
Polymer({
ready: function() {
console.log("btn-component component ready!");
},
cancel: function(event, detail, sender) {
console.log("Cancel Btn!", event, detail, sender);
this.fire('cancel_btn', {});
}
});
</script>
</polymer-element>
Test
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>btn-component Tests</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/polymer-gestures/test/js/fake.js"></script>
<link href="../btn-component.html" rel="import">
</head>
<body>
<btn-component id="component"></btn-component>
<script>
function runAfterEvent(eventName, element, callback) {
var listener = function(event) {
element.removeEventListener(eventName, listener)
callback(event.detail);
};
element.addEventListener(eventName, listener);
}
suite('<btn-component>', function() {
var c = document.getElementById('component');
var fake = new Fake();
test('hitting cancel fires cancel event', function(done) {
runAfterEvent('cancel_btn', c, function(event) {
assert.ok(1, "the cancel_btn event should be fired");
done();
});
var cancelBtn = document.querySelectorAll("btn-component /deep/ #cancel_button")[0];
console.log(cancelBtn);
setTimeout(function() {
fake.downOnNode(cancelBtn);
fake.upOnNode(cancelBtn);
}, 1000);
});
});
</script>
The fail happens trying to click the button.
I guess there's a variety of ways to approach resolving this - including in my own tests (e.g. checking the viewport size vs the element position and scrolling right before trying to simulate a click), but starts to get quite fiddly/fragile. A reasonable option might be to add a config to wct that specifies a minimum viewport size on the component iFrame.
Perhaps I'm missing some available configuration that could help here. Is there a recommended way to handle this scenario?
A simple solution is pretty obvious. I added the following to my test's index.html
<style>
#subsuites {
width: 600px !important;
}
</style>
The css used by the wct tool sets the width at 50% and nests frames when using file globs - resulting in progressive narrowing.

Use a remote stylesheet inside a template tag (with shadow dom)

I am trying to make a semi-resuseable widget but I am running into a problem. I am trying to encapsulate a some CSS code inside a shadow root so that it does not affect the rest of the webpage but this CSS is used across multiple widgets so I am trying to include a remote stylesheet. None of the examples I have found use a remote style sheet and I was wondering if this was possible.
EX:
<template id="templateContent">
<head>
<link rel="stylesheet" href="css/generalStyle1.css">
</head>
<body>
<div class="affectedByGeneralStyle1"></div>
</body>
</template>
script to include template:
<div id="host"></div>
<script>
var importedData = (html_import_element).import.getElementById("templateContent");
var shadow = document.querySelector('#host').createShadowRoot();
var clone = document.importNode(importedData.content, true);
shadow.appendChild(clone);
</script>
I came across the same problem recently. What I ended up doing was using:
<template id="templateContent">
<style> #import "css/generalStyle.css"; </style>
</template>
Additional info: This worked just fine except that now I'm having some cache issues as Chrome does not seem to reload those resources after a hard reload.
Let add to the answer . Now direct tag is supported in shadow dom.
You can directly use
<link rel="stylesheet" href="yourcss1.css">
<link href="yourcss2.css" rel="stylesheet" type="text/css">
Check they has been update by whatwg and W3C
Useful link for using css in shadow dom.
https://w3c.github.io/webcomponents/spec/shadow/#inertness-of-html-elements-in-a-shadow-tree https://github.com/whatwg/html/commit/43c57866c2bbc20dc0deb15a721a28cbaad2140c
https://github.com/w3c/webcomponents/issues/628
Direct css link can be use in shadow dom
Thanks.
I added the stylesheet's link element directly to the shadow root this way:
let link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', 'whatever.css');
this.shadowRoot.appendChild(link);
It seems to work fine. (I called this from the constructor of the component.)
actually polymer has an internal utility to load css links, i have implemented a javascript function that is using polymer internal css processor,so if you want to add css links at runtime you can use it:
Polymer('my-element', {
ready: function () {
this.importCss("path/myfile.css");
},
importCss: function (path) {
var $shadow = $(this.shadowRoot);
var $head = $("<div></div>");
var $link = $("<link rel='stylesheet' type='text/css'>");
$link.attr("href", path);
$head.append($link);
var head = $head[0];
this.copySheetAttributes = Polymer.api.declaration.styles.copySheetAttributes;
Polymer.api.declaration.styles.convertSheetsToStyles.call(this, head);
var styles = Polymer.api.declaration.styles.findLoadableStyles(head);
if (styles.length) {
var templateUrl = this.baseURI;
Polymer.styleResolver.loadStyles(styles, templateUrl, function () {
var $style = $shadow.find("style");
if ($style.length > 0){
$shadow.find("style").append($head.find("style").html());
}else{
$shadow.append($head.html());
}
});
}
}
});
Note: this code needs jquery to run

Resources