Reputation: 109
I'm trying to render d3 force graph in three.js, I'm using standard Line and BoxGeometry with photo texture on it. On force graph update I call draw function, which is also called on in
controls.addEventListener('change', () => {this.redraw()});
but when I move camera, some lines disappear, when I'm closer, it seems worst, besides it does not look like there is any rule, even when I'm close to graph, it look like lines to disappear are chosen at random.
There line is
And this is if I move camera a little in angle
This is from one side of the graph:
And this is when from other:
Whole code:
import {
WebGLRenderer,
Scene,
PerspectiveCamera,
Texture,
MeshBasicMaterial,
SphereGeometry,
Mesh,
Geometry,
Vector3,
LineBasicMaterial,
Line,
LineSegments,
BoxGeometry,
TextureLoader
} from 'three';
import * as three from 'three';
import { ViewModel, Link, Node } from './Model';
import { event } from 'd3-selection';
import * as selection from 'd3-selection';
import { drag } from 'd3-drag';
// Old module syntax
declare function require(name:String);
let OrbitControls = require('./../../../node_modules/three-orbit-controls/index')(three);
interface IView {
render():void;
}
class ViewNode {
public vector:Vector3;
public mesh:Mesh;
public node:Node;
}
export class Full3DView implements IView {
private canvas: Element;
private renderer: WebGLRenderer;
private scene: Scene;
private lineMaterial: LineBasicMaterial;
private camera: PerspectiveCamera;
private controls: any;
private nodes:ViewNode[] = [];
private lines:Geometry[] = [];
constructor(private model:ViewModel) {
this.canvas = document.querySelector('#view3d2');
this.model.onChange(() => {this.render()});
}
render(): void {
this.buildScene();
this.model.simulation.on('tick', () => this.redraw());
this.model.linkForce.distance(40);
this.model.collideForce.radius(30);
}
private buildScene() {
this.scene = new Scene();
this.camera = new PerspectiveCamera( 90, window.innerWidth/window.innerHeight, 1, 20000 );
this.renderer = new WebGLRenderer();
this.renderer.setSize( this.canvas.clientWidth, this.canvas.clientHeight );
this.canvas.appendChild( this.renderer.domElement );
this.controls = new OrbitControls( this.camera, this.renderer.domElement);
this.controls.addEventListener('change', () => {this.redraw()});
this.lineMaterial = new LineBasicMaterial({ color: 0xccff00, linewidth: 3});
let vectorIndex:Map<String, Vector3> = new Map();
let textureLoader = new TextureLoader();
this.model.nodes.forEach((node:Node) => {
this.buildNode(vectorIndex, textureLoader, node);
});
this.model.links.forEach((link:Link) => {
this.buildEdge(vectorIndex, link);
});
this.camera.position.z = 5000;
}
private buildNode(vectorIndex:Map<String, Vector3>, textureLoader:TextureLoader, node:Node) {
let material = new MeshBasicMaterial();
let geometry = new BoxGeometry( 30, 30, 30);
let mesh = new Mesh( geometry, material );
mesh.lookAt(this.camera.position);
this.scene.add( mesh );
mesh.position.set(node.x, node.y, 0);
mesh.rotation.x += 1;
vectorIndex.set(node.index, mesh.position);
this.nodes.push({
vector: mesh.position,
mesh: mesh,
node: node
});
textureLoader.load('/data/images/' + node.id + '.jpg', (texture:Texture) => {
material.map = texture;
material.needsUpdate = true;
});
}
private buildEdge(vectorIndex:Map<String, Vector3>, link:Link) {
let geometry = new Geometry();
geometry.vertices.push(
vectorIndex.get(link.source.index).copy(vectorIndex.get(link.source.index).setZ(0)),
vectorIndex.get(link.target.index).copy(vectorIndex.get(link.target.index).setZ(0))
);
geometry.computeLineDistances();
this.lines.push(geometry);
let line = new Line(geometry, this.lineMaterial);
this.scene.add(line);
}
private redraw() {
this.nodes.forEach((node:ViewNode) => {
node.vector.setX(node.node.x * 10);
node.vector.setY(node.node.y * 10);
node.mesh.lookAt(this.camera.position);
node.mesh.frustumCulled = false;
});
this.lines.forEach((line:Geometry) => {
line.verticesNeedUpdate = true;
});
this.renderer.render(this.scene, this.camera)
}
}
Upvotes: 0
Views: 2125
Reputation: 3857
The actual answer I was looking for is in the comment above from WestLangley.
A possible explanation for your issue is that when you update the vertices of a geometry, you should call
geometry.computeBoundingSphere()
. The renderer calls it for you on the first render call, but after that, if you modify vertices, the bounding sphere is no longer correct, and you need to update it. Alternatively, you can setmesh.frustumCulled = false
;
Upvotes: 7
Reputation: 109
I was unable to get it working using Line
object, but if I use LineSegments
and push all pairs of vertices to one Geometry
, it is working well.
So in function buildScene
I use instead of
this.lineMaterial = new LineBasicMaterial({ color: 0xccff00, linewidth: 3});
lines
this.linesGeometry = new Geometry();
this.scene.add(new LineSegments(this.linesGeometry, new LineBasicMaterial({ color: 0xccff00, linewidth: 3})));
and then content of buildEdge
is
this.linesGeometry.vertices.push(
vectorIndex.get(link.source.index).copy(vectorIndex.get(link.source.index).setZ(0)),
vectorIndex.get(link.target.index).copy(vectorIndex.get(link.target.index).setZ(0))
);
and in redraw
function I just do
this.linesGeometry.verticesNeedUpdate = true;
instead of
this.lines.forEach((line:Geometry) => {
line.verticesNeedUpdate = true;
});
Upvotes: 1