Lines and MeshLines Jitter in big scene however CylinderGeometry does not, why?

Hello, Threejs community,

I have a blast design CAD like application that I am migrating to three.js, the original intent was to draw the blast holes as varying symbols (Cross, Circle, Diamond, etc.) however when the scene is using real-world geo-coordinates (478700.000, 6772400.000, 400.000) and drawing with Lines or MeshLines the render is jittery. I assume this is due to the float precision and the camera distance as all forums are saying. However if I draw the shapes with CylinderGeometry the render is smooth and there are no jitters.

Jittery Render occurs with Line or MeshLine
LineMeshJittery

Smooth Render Occurs with any Mesh (Cylinder, Torus, etc)
cylindersmooth

createLine.js has used THREE.Line and THREE.MeshLine with out success to remediate the jittery render.

//createLine.js
import * as THREE from "three";
import { BufferGeometry, Vector3, Vector2, Color } from "three";
import { MeshLine, MeshLineMaterial } from "three.meshline";

export function createLine(start, end, color, lineWidth, dashArray, dashOffset, dashRatio, opacity, sizeAttenuation) {
	const material = new MeshLineMaterial({
		map: null,
		useMap: false,
		color: new Color(color),
		opacity: opacity,
		resolution: new Vector2(window.innerWidth, window.innerHeight),
		lineWidth: lineWidth,
		dashArray: dashArray,
		dashOffset: dashOffset,
		dashRatio: dashRatio,
		opacity: opacity,
		sizeAttenuation: sizeAttenuation
	});
	const points = [];
	points.push(new Vector3(start.x, start.y, start.z));
	points.push(new Vector3(end.x, end.y, end.z));

	const line = new MeshLine();
	const geometry = new BufferGeometry().setFromPoints(points);
	line.setGeometry(geometry);

	const mesh = new THREE.Mesh(line, material);

	return mesh;
}

createCylinder.js is used to build a 3D object each cylinder is used to replace a line segment.

//createCylinder.js
import { MeshBasicMaterial, MeshPhongMaterial, CylinderGeometry, Mesh } from "three";
import { Vector3 } from "three";
import { params } from "../createScene";

export function createCylinder(color, materialType, startVector, endVector, diameter, radialSegments) {
	diameter = diameter || 500;
	diameter = diameter / 1000;
	let material;
	if (materialType === "basic") {
		material = new MeshBasicMaterial({ color });
	} else if (materialType === "phong") {
		material = new MeshPhongMaterial({
			color: color,
			flatShading: true
		});
	}

	const height = startVector.distanceTo(endVector);
	const direction = endVector.clone().sub(startVector).normalize();
	const position = startVector.clone().add(endVector).multiplyScalar(0.5);

	const geometry = new CylinderGeometry(diameter / 2, diameter / 2, height, radialSegments);
	const cylinder = new Mesh(geometry, material);

	//add a rotation matrix with the fulcrum at the startVector and the endVector being the base of the cylinder
	cylinder.position.copy(position);
	cylinder.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), direction);
	if (params.debugComments) {
		console.log("createCylinder > UUID:" + cylinder.uuid + " X: " + position.x + " Y: " + position.y + " Z: " + position.z);
	}
	return cylinder;
}

This wouldn’t be an issue if all I wanted to view were blast holes in a blast design.

However, I will need to use a lot of lines in the future. Imported from DXFs and also those created in the application.

I’d like to know if it has to do with how I am dealing with the coordinates.
Should I use MeshLine differently?

I certainly don’t want to be creating a line from individual cylinders
I don’t want to origin shift if possible. I tried an origin shift (and unless was done before importing the file) the jitter is still there in the render for lines.

I certainly don’t have an issue with the meshes for blast holes however for DXF line geometry, I’d like to draw lines, rectangles ect.

Thank you in advance
Brent.

Yeah! That’s definitely the culprit. You need to scale down your vertices (Most 3D apps have some transform functionalities). You can scale down the geometry with something like geometry.scale(0.1,0.1,0.1), then reset your camera position, near, far, accordingly.

Also give Line2/Fat lines a try, for some beefy lines.

1 Like

The scale option seems a good option. I’ll give it an attempt and see if it suits over the next few days.

I understand that you’ve provided a good answer extremely quickly, although while I have someone engaged I have a few more questions.

I’d still be interested if you know why the CylinderGeometry is smooth and the Line are not, with same coordinates. Also another option I was thinking of was using Float64Array. To avoid any transformations, or will this not work due to the underlying rendering? ( gl.LINE_STRIP )

For context the vast majority of the world (in this field) works in meter units. The largest UTM grid is 700km(700,000m) by 8200km(8,200,000) but the coords can be from 0 to 10,000,000.0000m. Four decimal places is more than sufficient for the accuracy for this application (for positioning, maybe not for hole scale stuff).

I was considering the overhead of float64, I haven’t found a lot on this topic, I did find this pull request discussing the float64 storage of Matrix4 elements issue https://github.com/mrdoob/three.js/pull/10702. However the scaling option sounds good for most things. It seems reasonable to convert all imported coordinates to kilometers.

For example (please tell me if I am wrong):

  • If I use a meter scale (6,777,342.4678 m),
    – the value is not as precise and the precision will degrade (as I have seen).
  • If I use a kilometer scale float32 will maintain good precision
    • 0.00165km is (equivalent to 165mm - which is a common hole diameter)
      • Original: 0.00165
      • Float32: 0.0016499999910593033
      • Difference: 8.94e-12
      • Float64: 0.00165
      • Difference: 0.0

The smallest effective hole is 25mm (realistically)

  • Float32 Representation:
    • Float32 Value: 0.00025
    • Difference from Original: 1.19e-11
  • Float64 Representation:
    • Float64 Value: 0.00025
    • Difference from Original: 0.0

Is there a link or advice on how to deal with the extremes of my scale of information?
Do you think I could use float64 throughout the application to avoid the jitters and imprecision? Or will three.js force it to float32?
Thanks again.

I’d still be interested if you know why the CylinderGeometry is smooth and the Line are not, with same coordinates.

You’re storing huge values in vertex attributes which means they will be transformed on the gpu where float 32 operations at most are used. With the cylinders you’re positioning them on the CPU with the transformation matrix where the large values are multiplied out when transforming into camera space and float 64 operations are used.

Also another option I was thinking of was using Float64Array. To avoid any transformations, or will this not work due to the underlying rendering? ( gl.LINE_STRIP )

WebGL does not support float 64 attributes

4 Likes

Thanks to everyone for the explanation.

Nothing seems to work as I’d like with keeping the coordinate space in its original precision so I am back to an origin shift for now. A rough implementation at this stage, but one I will fix in the future.

Just going to have to store the centroid and then subtract and add from the data set.
Oh well :frowning:

export function handleFileUpload(event, canvas) {
	const file = event.target.files[0];
	if (!file) {
		return;
	}
	const reader = new FileReader();

	let points = [];

	reader.onload = function(event) {
		const data = event.target.result;

		if (!file.name.toLowerCase().endsWith(".csv")) {
			return;
		}

		points = parseCSV(data);
		const { x, y, z } = getCentroid(points);
		let colour = 0xffffff;
		if (data.split("\n")[0].split(",").length === 4) {
			for (const point of points) {
				if (params.debugComments) {
					console.log("fileUpload/handleFileUpload/drawDummys: " + point.pointID + " X: " + point.startXLocation + " Y: " + point.startYLocation + " Z: " + point.startZLocation);
				}
				point.startXLocation = point.startXLocation - x;
				point.startYLocation = point.startYLocation - y;
				point.startZLocation = point.startZLocation - z;

				drawDummys(canvas.scene, colour, point);
			}
		} else if (data.split("\n")[0].split(",").length === 7) {
			for (const point of points) {
				if (params.debugComments) {
					console.log("fileUpload/handleFileUpload/draw " + point.pointID + " X: " + point.startXLocation + " Y: " + point.startYLocation + " Z: " + point.startZLocation);
				}
				point.startXLocation = point.startXLocation - x;
				point.startYLocation = point.startYLocation - y;
				point.startZLocation = point.startZLocation - z;
				point.endXLocation = point.endXLocation - x;
				point.endYLocation = point.endYLocation - y;
				point.endZLocation = point.endZLocation - z;

				drawHoles(canvas.scene, colour, point, 1000, 1);
			}
		}

		canvas.camera.position.set(0, 0, 0 + 100);
		canvas.camera.lookAt(0, 0, 0);
		controls.target.set(0, 0, 0);

		if (params.debugComments) {
			console.log(controls.target);
		}
		canvas.camera.updateMatrixWorld();
	};

	reader.readAsText(file);
}

Recentering / shifting the geometry origin is the only real option.