3d-force-graph-vr icon indicating copy to clipboard operation
3d-force-graph-vr copied to clipboard

How to make the nodes look at the camera at all times without calling refresh ?

Open slechanii opened this issue 2 years ago • 4 comments

Hi, i'm trying to make all my nodes (3D meshes) look at the camera position at all times, I am getting the camera position and making the mesh look at its position whenever its rendered the problem is that it only gets executed when nodeThreeObject is called, is there a way for me to make the nodes look at the camera at all times despite not getting rerendered ?

EDIT : After running some tests I found out that while I'm getting a camera, the vector is always (0,0,0) (even atfer moving the camera and triggering a rerender), here is the code I'm using to get the camera and print the position vector inside the nodeThreeObject :

  let cameraEl = document.querySelector("a-entity[camera]");
    if (!cameraEl) {
      cameraEl = document.querySelector("a-camera");
    }
    let camera = cameraEl.components.camera.camera;
    console.log(camera.position) // Always 0,0,0

Thanks.

slechanii avatar Jun 02 '22 13:06 slechanii

@slechanii have you considered using Sprites? The very definition of a Sprite is a (planar) object that always faces the camera POV, which sounds like what you're seeking.

vasturiano avatar Jun 05 '22 19:06 vasturiano

@slechanii have you considered using Sprites? The very definition of a Sprite is a (planar) object that always faces the camera POV, which sounds like what you're seeking.

Thanks for answering quickly!

I was indeed using sprites before and it did the job well, the problem is that from I what I saw, using sprites makes it impossible to use hover / onclick events due to a bug with aframe (gathered from past issues and having problems making it work, might have been fixed ?)

If it hasn't been fixed it means that I can't use sprites as I need hover & onclick on nodes for what i'm trying to achieve.

If sprites are not usable do you have an idea on how I could make the nodes always look at the camera ?

Currently I'm facing 2 issues :

  1. I can get a camera (explained in my 1st post) but this camera's position vector is always stuck at (0,0,0) no matter what I do strangely (am I doing it wrong ?)
  2. Even If I were to get the camera and the position vector right, the NodeThreeObject function seems to only be called on rerenders, so I would only be able to make the nodes look at the camera when a rerender is called

Thanks for your help!

slechanii avatar Jun 07 '22 10:06 slechanii

@slechanii the reason behind the Sprite issue in Aframe is mentioned here: https://github.com/vasturiano/3d-force-graph-vr/issues/31#issuecomment-1043570188

That issue eventually originates in the Aframe module, so there's not a whole lot that can be done to work around it.

Similarly, I'm also not sure how easy it would be to implement Sprite-like behaviour without using actual Sprite objects.

vasturiano avatar Jun 10 '22 17:06 vasturiano

I also encountered this problem and wrote a component to deal with it. Essentially what this does is to wrap each node in an A-Frame <a-sphere>, which makes dealing with them much more tractable. The code below also attaches a <a-text> entity to each sphere, and makes them always look towards the camera.

/* helper for vasturiano/3d-force-graph-vr
	*			draw a sphere around each force graph node, to make it easy to point to them with the ray caster,
	* 			and attach a text label (which rotates to always face the camera)
	* after the graph has been created, use something like
	*			fgEl.setAttribute('spherize', {})
	* to create the spheres
	*/
AFRAME.registerComponent('spherize', {
	schema: {},
	dependencies: ['forcegraph'],
	init: function () {
		// spheres are cached here and re-used
		this.spheres = new Map()
	},
	tick: function (time, timeDelta) {
		document
			.querySelector('[forcegraph]')
			.components.forcegraph.forceGraph.children.forEach((child) => {
				if (child.type == 'Mesh' && child.__data.id) {
					let sphereEl = this.spheres.get(child.__data.id)
					if (sphereEl) {
						// reuse existing sphere and label, but change its position
						sphereEl.object3D.position.copy(child.position)
					} else {
						sphereEl = document.createElement('a-sphere')
						sphereEl.classList.add('node')
						sphereEl.id = child.__data.id
						this.spheres.set(child.__data.id, sphereEl)
						sphereEl.setAttribute('position', child.position)
						let radius = child.geometry.parameters.radius + 0.1
						sphereEl.setAttribute('radius', radius)
						let color = child.__data.color || 'white'
						let compColor = lightOrDark(standardize_color(color)) == 'light' ? 'black' : 'white'
						sphereEl.setAttribute('color', color)
						this.el.appendChild(sphereEl)

						let label = document.createElement('a-entity')
						label.setAttribute('text', {
							value: splitText(child.__data.label, 9),
							color: compColor,
							width: 5 * radius,
							align: 'center',
						})
						sphereEl.setAttribute('look-at', '#cameraRig')
						label.setAttribute('position', {x: 0, y: 0, z: radius})
						sphereEl.appendChild(label)
					}
				}
			})
	},
})

(the line let compColor = lightOrDark(standardize_color(color)) == 'light' ? 'black' : 'white' sets compColor to white or black depending on which gives a better contrast with the colour of the sphere)

micrology avatar Jun 11 '22 11:06 micrology