Good way of playing video in AR with an alpha channel? - aframe

I'm trying to enable alpha channel on a video using AFrame.
I'm using "transparent" set to true but this doesn't seem to do anything.
Any ideas?
a-scene>
<a-assets timeout="30000">
<video id="lyrics" autoplay loop="true" transparent="true" src="https://cdn.glitch.com/d70cd89d-5883-430e-aa23-db6ffd90c2e4%2Fconverted.webm?v=1610981890345">
</a-assets>
<a-camera position="0 1.2 0"></a-camera>
<!-- Environment for 2D and VR viewing. It's auto-hidden in AR mode. -->
<a-entity environment="preset: forest; lighting: none; shadow: none; lightPosition: 0 2.15 0"
hide-in-ar-mode></a-entity>
<a-video src="#lyrics" width="2" height="1.375" position="0 -1 -2"></a-video>
<a-entity light="type: ambient; intensity: 0.5;"></a-entity>
<a-light type="directional"
light="castShadow: true;
shadowMapHeight: 1024;
shadowMapWidth: 1024;
shadowCameraLeft: -7;
shadowCameraRight: 5;
shadowCameraBottom: -5;
shadowCameraTop: 5;"
id="light"
target="dino"
position="-5 3 1.5"></a-light>
<!-- This shadow-receiving plane is only visible in AR mode. -->
<a-plane height="15" width="15" position="0 0 -3" rotation="-90 0 0"
shadow="receive: true"
ar-shadows="opacity: 0.3"
visible="false"></a-plane>
</a-scene>

You can specifically tell the material to use an THREE.RGBAFormat - therefore utilizing the alpha channel in the .webm. It's quite simple:
// after the entity is loaded, eg. el.addEventListener("loaded", e => {})
// grab the meshes material.
let material = this.el.getObject3D("mesh").material;
// switch the format to THREE.RGBAFormat
material.map.format = THREE.RGBAFormat;
// force an material update
material.map.needsUpdate = true;
And if all goes well, you should see the .webm being rendered correctly:
You can check it out here - works on Windows10 and mobile (android).
I'd strongly recommend troubleshooting arjs by re-creating the scene with vanilla a-frame. It's possible the issue is shared, and in my experience it's easier to debug. For example here is the same scene with the webm video.

Related

Preventing the camera entering entities in A-Frame

I have <a-scene> using A-Frame that includes many randomly placed spheres (<a-sphere>) and a camera rig. The spheres are centred at random (x,y,z) coordinates at initialisation (and are at different locations on each access to the web page). The user can move the camera with a controller or wasd keys through the scene. I want to prevent the camera from entering any of the spheres.
It seems that I should be able to do that either using a nav mesh or a physics engine. For the former, I need guidance on whether it is possible to construct a nav mesh that excludes many spheres (and just those spheres) and if so, how. With the physics engine, it seems a heavyweight way of dealing with the problem, since the spheres are static. Any advice or examples?
Finally, when the camera hitting the sphere is detected, what is the best way of actually preventing it from entering? Do I reposition the camera using javascript?
The best way is to use nav-mesh. Basically nav-mesh is a 3d plane where it has slots positioned in place of where camera movement is restricted.
If you want some simplified code for nav-mesh use this
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<!-- import the deprecated Geometry part -->
<script src="https://threejs.org/examples/js/deprecated/Geometry.js"></script>
<!-- before this tries to use its API -->
<script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras#v6.1.1/dist/aframe-extras.js"></script>
<a-scene>
<a-entity id="rig" movement-controls="constrainToNavMesh: true;" foo>
<a-entity id="camera" camera position="0 1.6 0" look-controls></a-entity>
</a-entity>
<a-plane nav-mesh rotation="-90 0 0" width="5" height="5" color="yellow"></a-plane>
<a-plane rotation="-90 0 0" width="25" height="25" color="blue" position="0 -0.01 0"></a-plane>
</a-scene>
Here instead of aframe primitive as navmesh you can use navemesh created by 3D modelling
To create a basic nav-mesh use blender software. In that create a mesh(plane) and cut slots using knife tool there. To get positioning of spheres download the 3d model in aframe as gltf import in blender and check it
navmesh example code.This example by AdaRoseCannon has endrosed all use cases in using nav-mesh
Here in AdaRoseCannon's example they have excluded holes in nav-mesh with a class name you can use this approach to cut dynamic holes .The a-plane entity here can be dynamically set too
<a-entity id="head"
camera="near:0.01;"
look-controls="pointerLockEnabled: false"
position="0 1.65 0"
wasd-controls="acceleration:20;"
simple-navmesh-constraint="navmesh:.navmesh;fall:0.5;height:1.65;exclude:.navmesh-hole;"
></a-entity>
<a-plane rotation="-90 0 0" width="1.2" height="0.6" class="navmesh-hole" visible="false"></a-plane>
Synn kindly wrote a component that did what I needed:
/*
* prevent the camera from entering a 3d entity
* coded by Synn ( https://github.com/chabloz )
*/
AFRAME.registerComponent('sphere-collider-constraint', {
schema: {
selector: {
default: '',
},
distance: {
default: 0.5,
},
},
init: function () {
this.lastPosition = new THREE.Vector3()
this.el.object3D.getWorldPosition(this.lastPosition)
this.myPos = new THREE.Vector3()
this.el.object3D.getWorldPosition(this.myPos)
this.targetPos = new THREE.Vector3()
},
tick: function () {
// if haven't moved since last tick, do nothing
this.el.object3D.getWorldPosition(this.myPos)
if (this.myPos.distanceTo(this.lastPosition) === 0) return
let didHit = false
for (const obj of document.querySelectorAll(this.data.selector)) {
obj.object3D.getWorldPosition(this.targetPos)
const distanceTo = this.myPos.distanceTo(this.targetPos)
if (distanceTo <= obj.components.geometry.data.radius + this.data.distance) {
didHit = true
break
}
}
if (didHit) {
this.el.object3D.position.copy(this.lastPosition)
this.el.object3D.parent.worldToLocal(this.el.object3D.position)
} else {
this.el.object3D.getWorldPosition(this.lastPosition)
}
},
})
Then use it in something like this:
<a-entity id="cameraRig" movement-controls="fly: true; speed: 0.7" sphere-collider-constraint="selector: .node">
<a-camera id="camera" look-controls="pointerLockEnabled: true">
</a-camera>
</a-entity>

How do I use checkpoint controls in A-Frame?

I am new to A-Frame and still trying to figure everything out! I'm currently constructing a 3D space and would like to create a guided experience for visitors by providing dots on the floor for them to click and be transported to that position. I found this code online which is perfect but I can't get it to work.
Here is the link to my project on Glitch: https://glitch.com/~museum-exhibit-demo
This is the code for my camera:
<a-entity position="1.8 -1.1 3" rotation="0 90 0" id="pov">
<a-camera universal-controls="movementControls: checkpoint" checkpoint-controls="mode: animate">
<a-entity cursor position="0 0 -1" geometry="primitive: ring; radiusInner: 0.01; radiusOuter: 0.015;" material="color: #CCC; shader: flat;"> </a-entity>
</a-camera>
</a-entity>
And this is the code for the cylinder:
<a-cylinder checkpoint radius="0.1.5" height="0.01" position="-0.164 0.111 2.363" color="#39BB82"></a-cylinder>
Can anyone spot where I'm going wrong?
UPDATE:
I just read the current source of aframe-extra and it seems that nothing is broken! In fact there was a backward-incompatible change in the new versions. Rather than the old syntax of:
universal-controls="movementControls: checkpoint;"
Now this new syntax should be used:
movement-controls="controls: checkpoint;"
But keep in mind that since version 3.2.7, the movement offset is calculated on all 3 XYZ axis and therefore the camera will move to the center of the checkpoint. If you want to preserve the height (y) then simply add the code below above line 83:
targetPosition.y = position.y;
Here is a complete working example:
<html>
<head>
<meta charset="utf-8">
<title>Checkpoint Control with AFrame 1.2.0</title>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras#v6.1.1/dist/aframe-extras.min.js"></script>
</head>
<body>
<a-scene stats>
<!-- CAMERA -->
<a-entity position="0 0 0" id="pov">
<a-camera camera="active: true; spectator: false;" look-controls="pointerLockEnabled:true" movement-controls="controls: checkpoint;" checkpoint-controls="mode: animate; animateSpeed: 10" wasd-controls="enabled: false;" position="0 1.6 22">
<a-cursor></a-cursor>
</a-camera>
</a-entity>
<!-- CHECKPOINTS -->
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 20" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 16" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 12" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 8" color="#FF0000"></a-cylinder>
</a-scene>
</body>
</html>
Information below this line is not valid anymore.
As Piotr already mentioned, the new releases of Aframe-Extra are somehow broken!
Using an older version everything will work again.
Below is a working example with Aframe-extra version 3.2.7.
Once the page is fully loaded, click on the screen to lock the cursor, then point the cursor (tiny ring) at a red circle and click.
I also noted few additional options just in case:
spectator: false (switch between 1st and 3rd person view)
pointerLockEnabled:true (hide the mouse)
mode: animate (the other option is teleport)
animateSpeed: 10 (well... adjusts the animation speed)
wasd-controls="enabled: false;" (otherwise user can move around via WASD/arrow keys)
Code:
<html>
<head>
<meta charset="utf-8">
<title>Checkpoint Control with AFrame 1.2.0</title>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras#v3.2.7/dist/aframe-extras.min.js"></script>
</head>
<body>
<a-scene stats>
<!-- CAMERA -->
<a-entity position="0 0 0" id="pov">
<a-camera camera="active: true; spectator: false;" look-controls="pointerLockEnabled:true" universal-controls="movementControls: checkpoint;" checkpoint-controls="mode: animate; animateSpeed: 10" wasd-controls="enabled: false;" position="0 1.6 22">
<a-cursor></a-cursor>
</a-camera>
</a-entity>
<!-- CHECKPOINTS -->
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 20" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 16" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 12" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 8" color="#FF0000"></a-cylinder>
</a-scene>
</body>
</html>
This won't answer the question, but should solve your problem.
You can substitute the checkpoint-controls with a simple animation system:
you click on a cylinder
you animate the camera from the current position to the cylinder
Which could be implemented like this:
// use a system to keep a global track if we are already moving
AFRAME.registerSystem('goto', {
init: function() {
this.isMoving = false
}
})
// this component will have the actual logic
AFRAME.registerComponent('goto', {
init: function() {
let camRig = document.querySelector('#rig')
// upon click - move the camera
this.el.addEventListener('click', e => {
// check if we are already moving
if (this.system.isMoving) return;
// lock other attempts to move
this.system.isMoving = true
// grab the positions
let targetPos = this.el.getAttribute("position")
let rigPos = camRig.getAttribute("position")
// set the animation attributes.
camRig.setAttribute("animation", {
"from": rigPos,
"to": AFRAME.utils.coordinates.stringify({x: targetPos.x, y: rigPos.y, z: targetPos.z}),
"dur": targetPos.distanceTo(rigPos) * 750
})
camRig.emit('go')
})
// when the animation is finished - update the "shared" variable
camRig.addEventListener('animationcomplete', e=> {
this.system.isMoving = false
})
}
})
with a setup like this:
<!-- Camera with locked movement --/>
<a-entity id="rig" animation="property: position; startEvents: go">
<a-camera look-controls wasd-controls-enabled="false"></a-camera>
<a-entity>
<!-- Cylinder node --/>
<a-cylinder goto></a-cylinder>
You can see it working in this glitch.

A-Frame using teleport-controls with shake.js events

I want to use fernandojsg's teleport controls on my A-Frame project, but the way I want to use them is with shake.js, one shake to make the teleport line appear and another one to actually teleport where you selected.
I've seen the documentations and came across the startEvents and endEvent properties, and I want to map them into the shake event... for me it sounds like I have to create a custom component to do this, but I wanted to seek help first, to see if this is possible without doing it.
So far I've made this (glitch.com/ link) but it doesn't work so far (please note that I've got some other libraries there that make use of shake, mousedown, and similar events... my plan is to activate or deactivate them depending on how the user wants to move)
<a-entity id="player" wasd-controls tap-to-walk>
<a-camera id="eyes" position="-.5 1.5 0" camera="" look-controls="" mouse-cursor="">
<a-entity id="cursor" cursor="fuse: false;"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.015; radiusOuter: 0.019"
material="color: white; shader: flat"
raycaster="far: 5; interval: 1000; objects: .clickable">
</a-entity>
<a-entity id="texto" text="value:Hola;align:center" position="0 -.3 -0.5"></a-entity>
<a-plane position="0 .7 -1" material="transparent: true; opacity: 0.5; color: #ffec04; shader:flat;" scale="1 0.2 1"></a-plane>
</a-camera>
<a-entity
teleport-controls="cameraRig: #player; teleport-origins: #eyes; startEvents:['shake','mousedown']"> </a-entity>
<a-entity id="step" sound="src: #step1"></a-entity>
</a-entity>
Thanks...
Yes you will need a some JS or a custom component to get shake.js and teleport controls to work together for two reasons:
shake.js emits its events outside the A-Frame scene on window where teleport-controls is not listening
shake.js only emits one event type, and you need to differentiate for startEvents and endEvents
But it doesn't need to be very complicated:
AFRAME.registerComponent('shake-listener', {
init: function () {
var targetEl = this.el
var count = 0
// you could also initialize the shake instance here
window.addEventListener('shake', function () {
if (++count % 2) {
targetEl.emit('shakestart')
} else {
targetEl.emit('shakeend')
}
})
}
})
Then add shake-listener to the same entity as teleport-controls="startEvents: shakestart; endEvents: shakeend"

A-Frame .obj model displaying but broken

Total beginner to a-frame here, have been through the tutorial scenes and am now setting up my first using .obj models.
Using a remote server, feel like that's important information.
I've seen questions about models not showing up but mine is displaying broken and I'm not sure where to start fixing it.
This is how it looks in windows 3D builder:
And this is how it looks in my project (backed on pink plane for contrast):
Here's the html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pokemon Stadium</title>
<link href="css/main.css" rel="stylesheet">
<script src="https://aframe.io/releases/0.4.0/aframe.min.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<!-- Scene -->
<a-scene onLoad="">
<!------------------------------------------------ Assets --------------------------------------------------------->
<a-assets>
<a-asset-item id="stadium-obj" src="assets/models/stadium/Pokemon+Stadium.obj"></a-asset-item>
<a-asset-item id="stadium-mtl" src="assets/models/stadium/Pokemon+Stadium.mtl"></a-asset-item>
<a-asset-item id="ivy-obj" src="assets/models/ivysaur/Pokemon.obj"></a-asset-item>
<a-asset-item id="ivy-mtl" src="assets/models/ivysaur/Pokemon.mtl"></a-asset-item>
</a-assets>
<!------------------------------------------------- Scene --------------------------------------------------------->
<!-- Skybox -->
<a-sky color="#279DF4"></a-sky>
<!-- Abyss -->
<a-plane scale="1000 1000" position="0 -10 0" color="#212121" rotation="-90 0 0" material="shader: flat"></a-plane>
<!-- Stadium -->
<a-entity obj-model="obj: #stadium-obj; mtl: #stadium-mtl" position="0 0 -30" scale="0.05 0.05 0.05" material="side: double"></a-entity>
<!-- Bulbasaur -->
<a-entity obj-model="obj: #ivy-obj; mtl: #ivy-mtl" position="15 10 0" scale="1 1 1" rotation="0 90 0"></a-entity>
<!-- Camera + cursor -->
<a-entity camera look-controls position="10 12 0" rotation="-23 -90 0">
<a-cursor id="cursor"
animation__click="property: scale; startEvents: click; from: 0.1 0.1 0.1; to: 1 1 1; dur: 150"
animation__fusing="property: fusing; startEvents: fusing; from: 1 1 1; to: 0.1 0.1 0.1; dur: 1500"
event-set__1="_event: mouseenter; color: springgreen"
event-set__2="_event: mouseleave; color: black"
fuse="true"
raycaster="objects: .link"></a-cursor>
</a-entity>
</a-scene>
</body>
</html>
You probably have to set the type of side of the material to THREE.DoubleSide. With A-Frame you should be able to change the type using the following DOM attribute on an AEntity: material="side: double".
If this is not the case, you should update your post with a snippet of your scene elements.
EDIT:
As shown in the image, parts of my model are rendered incorrectly. The modelloader in THREEjs reads all meshes in a model and binds them to a grouped object. To fix this, you have to set the side of the material of the meshes to DoubleSided. Luckily in A-Frame, the obj-model component emits an event when the model has loaded successfully, we add a listener for the emitted event model-loaded on the DOM element and append a callback which returns a customevent. The customevent sends a reference to the model group. Query for the AEntity, I've appended an id modelEl to mine.
<script>
(function(modelEl) {
if (!window['AFRAME'] && !modelEl) {
return;
}
modelEl.addEventListener('model-loaded', function(evt) {
var model = evt.detail.model;
traverse(model);
});
})(document.getElementById('stadium'));
function traverse(node) {
node.children.forEach(function(child) {
if (child.children) {
traverse(child);
}
updateMaterial(child['material'], THREE.DoubleSide);
});
}
function updateMaterialSide(material, side) {
if (!material) {
return;
}
if (material instanceof THREE.Material) {
material.side = side;
material.needsUpdate = true
} else if (material instanceof THREE.MultiMaterial) {
material.materials.forEach(function(childMaterial) {
updateMaterial(childMaterial, side);
});
}
}
</script>
After running the above script, my model, and some of the texture loading, has been fixed.
Something to consider would be digging into creating custom components and modify or extend the obj-model component to prevent having to query for every model that may have the same issue.
If none of this worked, I believe something might be wrong with your wavefront obj export settings.
EDIT2:
Regarding my comment, here is a result of the fixed obj file in A-Frame:
For convenience sake you can find the MTL and OBJ file here:
obj file
mtl file

A-frame: How to setup wall colliders

Can anyone tell me how to setup wall colliders? I have setup a room using OBJ files for the walls. Many thanks.
Take a look at the source code for Don McCurdy's "Walls" example:
https://sandbox.donmccurdy.com/vr/walls/
Note the addition of the physics component in the a-scene element. This is what gets the magic going.
You need to include the aframe-extras script along with aframe.
For anyone looking for a good solution nowadays, the best I found so far is to use a nav-mesh
The nav-mesh is a simple 3d object that represents the walkable area in your project.
Here is what you'll need:
To generate the nav-mesh, use the plugin https://github.com/donmccurdy/aframe-inspector-plugin-recast
To move the camera you will not use wasd-controls, but aframe-extras's movement-controls
How to
Once the plugin is added to the page, follow these steps:
I found it was better to generate without the walls, so I hide the walls first, and make sure the floor ends where the walls would be. Also, keep all objects that user should not be able to pass through in their final place.
In the browser, use ctrl + alt + i to open the inspector
In the bottom part of the inspector, you can change cellSize and cellHeight to 0.1
Export and save it
in the HTML add a new asset:
<a-asset-item id="my-nav-mesh" src="assets/navmesh.gltf"></a-asset-item>
Add a new entity that points to the nav mesh:
<a-entity gltf-model="#my-nav-mesh" nav-mesh position="0 -0.1 0" visible="false"></a-entity>
Add the movement-controls to your camera Rig, with the constrainToNavMesh: true;. Here is how mine ended up:
<a-entity id="rig" movement-controls="constrainToNavMesh: true; speed: 0.8;" position="0 0 26">
<a-entity id="camera"
camera position="0 2.6 0"
look-controls="pointerLockEnabled: true">
<a-cursor></a-cursor>
</a-entity>
Expected Result:
So, by adding the nav-mesh and using the movement-controls instead of WASD, for example, you will make your camera moveable only on the created mesh.
You can also make the mesh invisible (adding visible="false to the nav-mesh entity), and toggle its position on Z so it doesnt feel like a higher plane.
Source
I actually got this information structured from this amazing free youtube video, from Danilo Pasquariello.
https://www.youtube.com/watch?v=Y52czIft9OU
How my project is looking after doing the steps above (I just made the mesh visible again for this screenshot
kinematic-body.js is deprecated.
Don McCurdy encourages the use of teleportation
See this post too: Move camera in aframe with physics engine
aframe inspector plugin didn't work on my project.
I did that temporary
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
<script
src="https://unpkg.com/aframe-aabb-collider-component#^2.2.1/dist/aframe-aabb-collider-component.min.js"></script>
<script src="https://unpkg.com/aframe-event-set-component#3.0.3/dist/aframe-event-set-component.min.js"></script>
<script>
let isStop = false
AFRAME.registerComponent("cam", {
init: function () {
window.addEventListener('keypress', e => {
if (isStop) {
const camera = document.getElementById('camera')
if (e.key === 's' || e.key === 'a' || e.key === 'd') {
camera.setAttribute('wasd-controls-enabled', 'true')
isStop = false
}
}
})
this.el.addEventListener("hitclosest", (e) => {
console.log('ok');
isStop = true
this.el.setAttribute('wasd-controls-enabled', 'false')
})
this.el.addEventListener("hitstart", (e) => {
isStop = true
this.el.setAttribute('wasd-controls-enabled', 'false')
})
}
})
</script>
<a-scene>
<a-entity id="rig" position="0 .5 -1">
<a-camera wasd-controls-enabled="true" cam id="camera" aabb-collider="objects: .collide"
geometry="primitive: box" aabb-collider="objects: a-box">
<a-cursor></a-cursor>
</a-camera>
</a-entity>
<a-box color="blue" class="collide" width='1' height='1' position="0 1.6 -5">
</a-box>
<a-box color="red" class="collide" width='1' height='1' position="2 1.6 -5">
</a-box>
<a-box color="pink" class="collide" width='10' height='1' position="10 1.6 -5">
</a-box>
</a-scene>
Here's ammo, which is a library for aframes.
https://github.com/n5ro/aframe-physics-system/blob/master/AmmoDriver.md#ammo-shape
You could read Collision Filtering for the detailed solution.

Resources