I want to be able to move in my VR world by looking around and holding down the cardboard button to move. I have tried for 2 hours and couldn't figure it out. I really don't want to use teleportation as my solution.
I'd throw this in an aframe component, and use the three.js API:
In the init check whether the mouse is up or down.
In the tick find out the rotation as a world matrix using extractRotation(mesh.matrix), apply it to a forward vector using direction.applyMatrix4(matrix), and add it to the current camera position.
AFRAME.registerComponent("foo", {
init: function() {
this.mouseDown = false
this.el.addEventListener("mousedown", (e) => {
this.mouseDown = true
})
this.el.addEventListener("mouseup", (e) => {
this.mouseDown = false
})
},
tick: function() {
if (this.mouseDown) {
let pos = this.el.getAttribute("position")
let mesh = this.el.object3D
var matrix = new THREE.Matrix4();
var direction = new THREE.Vector3(0, 0, -0.1);
matrix.extractRotation(mesh.matrix);
direction.applyMatrix4(matrix)
direction.add(new THREE.Vector3(pos.x, pos.y, pos.z))
this.el.setAttribute("position", direction)
}
}
})
Working fiddle here.
Related
This may have been asked numerous times, but I can't get a clear "newbie" plan of action.
Building Aframe experiences to showcase some interiors for numerous client presentations—this will be show on a desktop browser only, and I need to be able to control pan/rotate/turn-around camera movement with left and right arrow keys instead of relying on the mouse, as many clients have found this cumbersome. I just need to control this like an old first-person shooter with four arrow buttons.
Is there a simple way to do this? I've seen various permutations of this question but no simple solution so far. Thanks!
A simple keyboard input look component:
AFRAME.registerComponent('kbd-look-controls', {
schema: {
speed: {type: 'number', default: 2}
},
init: function () {
this.bindFunctions();
this.addEventListeners();
this.keyPressed = {
'ArrowUp': false,
'ArrowDown': false,
'ArrowLeft': false,
'ArrowRight': false
}
},
remove: function () {
this.removeEventListeners();
},
tick: function(time, delta) {
var data = this.data;
var object3D = this.el.object3D;
const angleDelta = 0.01 * data.speed * (delta / 16);
if (this.keyPressed['ArrowUp']) {
object3D.rotation.x = object3D.rotation.x + angleDelta;
}
if (this.keyPressed['ArrowDown']) {
object3D.rotation.x = object3D.rotation.x - angleDelta;
}
if (this.keyPressed['ArrowLeft']) {
object3D.rotation.y = object3D.rotation.y + angleDelta;
}
if (this.keyPressed['ArrowRight']) {
object3D.rotation.y = object3D.rotation.y - angleDelta;
}
},
bindFunctions() {
this.onKeyUp = this.onKeyUp.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
},
addEventListeners() {
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('keyup', this.onKeyUp);
},
removeEventListeners() {
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
},
onKeyUp: function (evt) {
this.keyPressed[evt.code] = false;
},
onKeyDown: function (evt) {
this.keyPressed[evt.code] = true;
}
})
Sample usage:
<a-entity camera kbd-look-controls="speed: 2.5" position="0 1 0"></a-entity>
This is one approach to getting to achieve your functionality.
Using wasd-controls component as well can be undesirable, since the wasd-controls also listens to the arrow keys.
Doesn't work with the look-controls component since it's also adjusting the rotation.
I'm making a virtual tour using AFrame, with a <a-sky> for the 360° images, some <a-circle> for hotspots, and <a-text> below circles for indications.
My goal is to make texts always parallel to the screen. I already try the aframe-look-at-component on the camera, but it's not what I was looking for because they face a point instead of facing the screen.
So my next idea was to create an invisible cursor, and copy his rotation the the texts, but I'm not sure of this because I don't know if the cursor update his rotation or if it's only base on the cam rotation.
Anyway the main source of this problem was I don't know how to change the rotation of my text after creation, I tried mytext.object3D.rotation, mytext.setAttribute('rotation', newRotation), and also object3D.lookAt(), but either it didn't matter, or it wasn't what I was looking for.
What is the best way to achieve this ?
Here my hotspot component (which create the texts based on some props):
AFRAME.registerPrimitive('a-hotspot', {
defaultComponents: {
hotspot: {}
},
mappings: {
for: 'hotspot.for',
to: 'hotspot.to',
legend: 'hotspot.legend',
'legend-pos': 'hotspot.legend-pos',
'legend-rot': 'hotspot.legend-rot'
}
});
AFRAME.registerComponent('hotspot', {
schema: {
for: { type: 'string' },
to: { type: 'string' },
legend: { type: 'string' },
'legend-pos': { type: 'vec3', default: {x: 0, y: -0.5, z:0}},
'legend-rot': { type: 'number', default: 0 },
positioning: { type: 'boolean', default: false }
},
init: function () {
this.shiftIsPress = false
window.addEventListener('keydown', this.handleShiftDown.bind(this))
window.addEventListener('keyup', this.handleShiftUp.bind(this))
this.tour = document.querySelector('a-tour');
if (this.data.legend)
this.addText();
this.el.addEventListener('click', this.handleClick.bind(this));
},
// Creating the text, based on hotspots props
addText: function () {
var hotspot = this.el,
position = new THREE.Vector3(hotspot.object3D.position.x, hotspot.object3D.position.y, hotspot.object3D.position.z),
text = document.createElement('a-text'),
loadedScene = document.querySelector('a-tour').getAttribute('loadedScene')
position.x += this.data['legend-pos'].x
position.y += this.data['legend-pos'].y
position.z += this.data['legend-pos'].z
console.log(this.data['legend-rot'])
// Set text attributes
text.id = `text_${this.data.for}_to_${this.data.to}`
text.setAttribute('position', position)
text.setAttribute('color', '#BE0F34')
text.setAttribute('align', 'center')
text.setAttribute('value', this.data.legend)
text.setAttribute('for', this.data.for)
if (loadedScene && loadedScene !== this.data.for) text.setAttribute('visible', false)
// Insert text after hotspot
hotspot.parentNode.insertBefore(text, hotspot.nextSibling)
},
// This part is supposed to edit the rotation
// to always fit to my idea
tick: function () {
if (this.el.getAttribute('visible')) {
var cursorRotation = document.querySelector('a-cursor').object3D.getWorldRotation()
//document.querySelector(`#text_${this.data.for}_to_${this.data.to}`).object3D.lookAt(cursorRotation)
this.updateRotation(`#text_${this.data.for}_to_${this.data.to}`)
}
},
// This parts manage the click event.
// When shift is pressed while clicking on hotspot, it enable another component
// to stick a hotspot to the camera for help me to place it on the scene
// otherwise, it change the 360° image and enbable/disable hotspots.
handleShiftDown: function (e) {
if (e.keyCode === 16) this.shiftIsPress = true
},
handleShiftUp: function (e) {
if (e.keyCode === 16) this.shiftIsPress = false
},
handleClick: function (e) {
var target = 'target: #' + this.el.id
var tour = this.tour.components['tour']
if (this.shiftIsPress)
tour.el.setAttribute('hotspot-helper', target)
else
tour.loadSceneId(this.data.to, true);
}
});
I really don't know what to do..
EDIT: I found a part solution:
If I had geometry to my text (and material with alphaTest: 1 for hide it), setAttribute('rotation') work, and I base it on camera rotation. The problem is that after that, the camera is locked, don't understand why ^^
var cursorRotation = document.querySelector('a-camera').object3D.rotation
document.querySelector(`#text_${this.data.for}_to_${this.data.to}`).setAttribute('rotation', cursorRotation)
Thanks,
Navalex
I finally found the solution !
Instead of document.querySelector('a-camera').object3D.rotation, I used document.querySelector('a-camera').getAttribute('rotation') and it's work nice !
Be sure to check out the example here: https://stemkoski.github.io/A-Frame-Examples/sprites.html
The 'box' sign is always visible to user
I'm pretty new to paperjs. My animation is working for this I use the following javascript:
view.onFrame = function () {
drawYellowBlock();
}
The function drawYellowBlock draws a yellow block but this animated. When the animation is done I want to stop view.onFrame because I have the feeling it is unnecessary to keep it running while nothing is happening any more. Then when a button is clicked I should be able to activate the onFrame again.
Is this possible and necessary?
So I want my draw function to be something like this:
var scale = 0;
function drawYellowBlock() {
scale = scale + 0.1
//animate block
if(scale < = 1){
//make block grow
}
else{
//stop onFrame
}
$('button').click(function(){
scale = 0;
//start onFrame and Animation
});
You can simply set up a flag used in onFrame method to check if you should animate or not.
Here is a sketch demonstrating the solution.
// Draw the item with a small initial scale.
var item = new Path.Rectangle({
from: view.center - 100,
to: view.center + 100,
fillColor: 'orange',
applyMatrix: false
});
item.scaling = 0.1;
// Draw instructions.
new PointText({
content: 'Press space to start animation',
point: view.center + [0, -80],
justification: 'center'
});
// Create a flag that we will use to know wether we should animate or not.
var animating = false;
// On space key pressed...
function onKeyDown(event) {
if (event.key === 'space') {
// ...start animation.
animating = true;
}
}
// On frame...
function onFrame() {
// ...if animation has started...
if (animating) {
// ...scale up the item.
item.scaling += 0.05;
// When item is totally scaled up...
if (item.scaling.x >= 1) {
// ...stop animation.
animating = false;
}
}
}
You can do something like this
function draw () {
drawYellowBlock();
view.onFrame = undefined
}
view.onFrame = draw
function onclickHandler(){
view.onFrame = draw
}
just remove the function reference from the onFrame handler once its done and append the function back once the button is clicked
I write JS app where I draw a lot of polylines using array of points, but in avery point I have some additional properties in this point (GPS data, speed etc).
I want to show these additional props onmouseover or onmouseclick event.
I have two ways:
use the standard polylines and event handler. But in this case I can't to determine additional properties for start point of this polyline cause I can't to save these props in polyline properties. There is one solution - save in array additional properties and try to find them by LatLng of first point of the polyline, but it's too slow I guess..
extend polyline and save additional properties in new Object, but I can't to extend mouse events :(
To extend polyline I use this code:
function myPolyline(prop, opts){
this.prop = prop;
this.Polyline = new google.maps.Polyline(opts);
}
myPolyline.prototype.setMap = function(map) {
return this.Polyline.setMap(map);
}
myPolyline.prototype.getPath = function() {
return this.Polyline.getPath();
}
myPolyline.prototype.addListener= function(prop) {
return this.Polyline.addListener();
}
myPolyline.prototype.getProp= function() {
return this.prop;
}
myPolyline.prototype.setProp= function(prop) {
return this.prop = prop;
}
and create new object in for loop (i - index of current point in array of points) like that:
var polyline_opts = {
path: line_points,
strokeColor: color,
geodesic: true,
strokeOpacity: 0.5,
strokeWeight: 4,
icons: [
{
icon: lineSymbol,
offset: '25px',
repeat: '50px'
}
],
map: map
};
var add_prop = {
id: i,
device_id: device_id
};
...
devices_properties[device_id].tracks[(i-1)] = new myPolyline(add_prop, polyline_opts);
Where:
line_points - array of points (just two points),
i - current point index
devices_properties[device_id].tracks - array of extended polylines (with add properties) by my device_id index
After that I set event handler like that:
var tmp = devices_properties[device_id].tracks[(i-1)];
google.maps.event.addListener(tmp.Polyline, 'click', function(e) {
...
console.log(tmp.prop.id);
...
}
But in this case I always get the same id in console..
When I use
google.maps.event.addListener(devices_properties[device_id].tracks[(i-1)].Polyline, 'click', function(e) {
...
console.log(???); // How to get parent of polyline fired the event?
...
}
I don't know how to get parent of polyline fired the event?
I answer my own question - It's done, I've just have some troubles with using "for" instead "$.each" :)
Before I use:
for ( i = 1; i < devices_properties[device_id].history_points.length; i++ ) {
...
create myPolyline
...
}
and it's doesn't work - created one event handle.
After:
$.each(devices_properties[device_id].history_points, function(i, tmp){
...
create myPolyline ()
...
}
and it works - create a lot of event handlers.
To handle event I use this:
google.maps.event.addListener(c_polyline.Polyline, 'mouseover', function(e) {
var prop = c_polyline.getProp();
...
console.log(prop.id, prop.device_id);
}
I am experimenting with Meteor and KineticJS. What I'm trying to do is to create a shape, and move it towards a mouse click on the stage. The position should be saved to the mongoDB so that all connected clients can be updated.
I haven't gotten far yet, but this is what I've got. I basically need a way to do two things:
How can I make a shape move towards the mouse click on the stage
and stop when it gets there?
Is there a better way of checking
the current position of a shape, other than the gameLoop that I
tried below? It works, but it feels wrong.
Thank you!
//client.js code
var player = null;
var playerAnim = null;
Template.container.rendered = function () {
var myCanvas = document.getElementById('myCanvas');
var stage = new Kinetic.Stage({
container: myCanvas,
width: 1024,
height: 1024
});
var layer = new Kinetic.Layer();
// add the layer to the stage
stage.add(layer);
// add click listener for the stage
$(stage.getContent()).on('click', function(evt) {
//stage was clicked
//Find the player shape in the database
Players.find().forEach(function (dbShape) {
player = new Kinetic.RegularPolygon(dbShape);
// setup an animation to move the player to the right
playerAnim = new Kinetic.Animation(function(frame) {
var velocity = 50;
var dist = velocity * (frame.timeDiff / 1000);
player.move(dist, 0);
Players.update(dbShape._id, {$set: {x: player.attrs.x, y: player.attrs.y}});
}, layer);
playerAnim.start();
layer.add(player);
layer.draw();
});
});
//make a gameLoop to check the position and stop the animation
Meteor.setInterval(function(gameLoop){
if(player.attrs.x > 500){
playerAnim.stop();
}
}, 500);
Meteor.autosubscribe(function () {
// clear the canvas
if (layer) {
layer.removeChildren();
layer.clear();
}
// populate the canvas with the Shapes collection
Players.find().forEach(function (dbShape) {
var player = new Kinetic.RegularPolygon(dbShape);
layer.add(player);
layer.draw();
});
});
}
I would use a tween to do this. Grab your object, get mouse position, and on mousedown or click create a Tween for your node to that mouse position.
layer.on('mousedown', function() {
var mousePos = stage.getMousePosition();
var tween = new Kinetic.Tween({
node: rect,
duration: 1,
x: mousePos.x,
y: mousePos.y,
opacity: 1,
strokeWidth: 6,
scaleX: 1.5
}).play();
});
JSFiddle
Note: To make the layer clickable, we need to add a transparent rectangle with the size of the width and height of the stage to the layer. See in the jsfiddle the Kinetic.Rect I made named var bg
Would putting the check inside the animation work for you?
playerAnim = new Kinetic.Animation(function(frame) {
var velocity = 50;
var dist = velocity * (frame.timeDiff / 1000);
player.move(dist, 0);
Players.update(dbShape._id, {$set: {x: player.attrs.x, y: player.attrs.y}});
if(player.getX() > 500){
this.stop();
}
}, layer);