Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: draw all lines in a single buffer geometry #31

Open
kumavis opened this issue May 27, 2022 · 3 comments
Open

Performance: draw all lines in a single buffer geometry #31

kumavis opened this issue May 27, 2022 · 3 comments

Comments

@kumavis
Copy link

kumavis commented May 27, 2022

Is your feature request related to a problem? Please describe.
drawing graphs with many links has poor performance on constrained devices

Describe the solution you'd like
option to render all lines as part of a single buffer geometry

Describe alternatives you've considered
not drawing the lines at all 😿

Additional context
example of many line segments in a buffer geometry

each is 1500 line segments
https://github.com/mrdoob/three.js/blob/master/examples/webgl_lines_sphere.html#L112-L128

primary challenge seems to be the expectation of 1 link matches 1 three object

@kumavis
Copy link
Author

kumavis commented May 27, 2022

heres the extracted BufferGeometry usage. simply updating vertices would not require recreating the geometry.

function createGeometry () {
  const geometry = new THREE.BufferGeometry();
  const vertices = [];
  // populate vertices
  // start
  // vertices.push( vertex.x, vertex.y, vertex.z );
  // end
  // vertices.push( vertex.x, vertex.y, vertex.z );
  
  geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
  return geometry;  
}


const material = new THREE.LineBasicMaterial({
  // color: p[ 1 ],
  // opacity: p[ 2 ]
});
const line = new THREE.LineSegments( createGeometry(), material );
scene.add( line );

// update geometry after some change
line.geometry.dispose();
line.geometry = createGeometry();

@kumavis
Copy link
Author

kumavis commented May 28, 2022

ok got it working. performance improvement is significant!

    const graph = new ThreeForceGraph()
      .graphData(packageData)
      .linkThreeObject((link) => {
        // create dummy object
        return new Object3D()
      })
      .linkPositionUpdate((linkObject, { start, end }, link) => {
        lineController.setLine(link.id, [start.x, start.y, start.z], [end.x, end.y, end.z])
        // override link position update
        return true
      })

LineController.js

import { LineSegments, LineBasicMaterial, BufferGeometry, Float32BufferAttribute } from 'three'

export class LineSegmentsController {
  constructor ({ lineCapacity = 1000, color = '#f0f0f0', opacity = 0.2 } = {}) {
    this.lineToIndex = new Map()
    this.material = new LineBasicMaterial({
      color,
      opacity,
      transparent: opacity < 1,
      depthWrite: opacity >= 1
    })
    this.geometry = new BufferGeometry()
    this.lineCapacity = 0
    this.vertices = new Float32Array(0)
    this.setLineCapacity(lineCapacity)
    this.object = new LineSegments(this.geometry, this.material)
    this.object.renderOrder = 10
  }

  setLine (id, start, end) {
    if (this.lineToIndex.has(id)) {
      const positions = this.geometry.attributes.position.array
      // existing line, update entry
      const index = this.lineToIndex.get(id)
      positions.set(start, index)
      positions.set(end, index + 3)
    } else {
      // new line, add new entry
      const lineCount = this.lineToIndex.size
      const index = lineCount * 2 * 3
      this.lineToIndex.set(id, index)
      // double line capacity if expired
      if (lineCount >= this.lineCapacity) {
        this.setLineCapacity(this.lineCapacity * 2)
      }
      const positions = this.geometry.attributes.position.array
      positions.set(start, index)
      positions.set(end, index + 3)
      this.geometry.setDrawRange(0, (lineCount + 1) * 2)
    }
    // mark for update
    this.geometry.attributes.position.needsUpdate = true
    // this.geometry.computeBoundingBox()
    // this.geometry.computeBoundingSphere()
  }

  setLineCapacity (newLineCapacity) {
    // lineCapacity * 2 point/line * 3 floats/point
    const newVertices = new Float32Array(newLineCapacity * 2 * 3)
    // only run this when its not the first time
    if (this.lineCapacity > 0) {
      const positions = this.geometry.attributes.position.array
      newVertices.set(positions)
    }
    this.vertices = newVertices
    this.geometry.setAttribute('position', new Float32BufferAttribute(this.vertices, 3))
    this.lineCapacity = newLineCapacity
  }
}

@kumavis
Copy link
Author

kumavis commented May 30, 2022

with this change alone i went 3 fps -> 15 fps on quest 2 vr headset

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant