Line2 objects appear jagged when making curves

I need to implement curves that look like the following:

I was provided the SVGs for these lines, which I imported into Blender and exported as a JSON file with the positions of each vertex. The data I got was in the following format:

{
    "points": [
        {
            "x": 3.1263577938079834,
            "y": 0.11644318699836731,
            "z": 4.950274467468262
        },
        {
            "x": 3.105274200439453,
            "y": 0.09999111294746399,
            "z": 4.950274467468262
        },
       // a lot of points
}

Each line has 95 vertices.

Then, in Three I created lines as follows:

import lineData from "linedata.json";

const vertices = [];
const colors = [];
const pts = lineData.points;

for (let i = 0; i < pts.length; i++) {
    const { x, y, z } = pts[i];
    vertices.push(x, y, z);
    // here I use a helper function that interpolates between colors
    // based on the position of the index of the vertex
    // returns 3 values between 0 and 1
    colors.push(...getColor(i / pts.length));
}

const geometry = new LineGeometry();
geometry.setPositions(vertices);
geometry.setColors(colors);

const material = new LineMaterial({
    color: 0xffffff,
    lineWidth: 1,
    resolution: new THREE.Vector2(window.innerWidth, window.innerHeight),
    vertexColors: true,
});

const line = new Line2(geometry, material);

scene.add(line);

The result is:

I tried adding more geometry by increasing the curve resolution in Blender before converting to a mesh, but didn’t help at all.

I tried other approaches, like Line, Tube and MeshLine, but still can’t get the result I need.

What am I doing wrong and/or is there a better way of doing this type of thin thread-like object?

Should I just render the SVG? I strongly prefer having the curve as an actual Threejs object so I can manipulate its material and other properties.

Do you have renderer.antialias set to true on the renderer already?

Yes, I do. Also pixel ratio.

const renderer = new THREE.WebGLRenderer({ antiAlias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
// canvas is 100% width and 100vh, not worrying about mobile just now
renderer.setSize(window.innerWidth, window.innerHeight);

can you share the original SVGs

Sure thing!

<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1678.49 3472.37">
  <defs>
    <style>
      .cls-1 {
        fill: none;
        stroke: url(#radial-gradient);
        stroke-miterlimit: 10;
      }
    </style>
    <radialGradient id="radial-gradient" cx="839.25" cy="1736.18" fx="839.25" fy="1736.18" r="1363.57" gradientTransform="translate(1121.7 2986.19) rotate(-151.82) scale(1 .56)" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff"/>
      <stop offset="1" stop-color="#fff"/>
    </radialGradient>
  </defs>
  <g id="Layer_1-2" data-name="Layer 1">
    <path class="cls-1" d="M1678.19.4c-17.21,12.91-173.14,116.38-322.65,146.27-278.69,55.71-464.61,395.78-550.65,604.01-63.51,153.71-207.83,460.67-129.06,716.69,34.42,111.85,151.37,199.78,210.8,311.04,77.44,144.97,95.48,416.74,98.42,502.84,5.01,146.59,33.86,313.24-45.19,472.42s-564.4,461.6-685.63,533.45c-116.15,68.83-215.1,124.76-253.82,184.99"/>
  </g>
</svg>

I decided to skip Blender and try creating the shape directly in Three. Which yielded a much better result:

Here’s the code based on the docs and the tiger example:

const loader = new SVGLoader();

const svg = await loader.loadAsync("/line.svg");
const svgPath = svg.paths[0]; // it's a single path
svgPath.userData.style.strokeWidth = 1; // balancing scale/width

const material = new THREE.MeshBasicMaterial({
    color: svgPath.color,
    side: THREE.DoubleSide,
    depthWrite: false,
});

const geometry = new SVGLoader.pointsToStroke(
    svgPath.subPaths[0].getPoints(), // it's a single subpath
    svgPath.userData.style,
);

const mesh = new THREE.Mesh(geometry, material);
mesh.scale.setScalar(0.0065); // it's really huge
mesh.scale.y *= -1; // correct Y axis

const baseGroup = new THREE.Group();
baseGroup.add(mesh);

scene.add(baseGroup);

// hack to center the curve
const boundingBox = new THREE.Box3();
const boundingBoxSize = new THREE.Vector3();
boundingBox.setFromObject(baseGroup).getSize(boundingBoxSize);

baseGroup.position.x -= boundingBoxSize.x / 2;
1 Like

heres a html version of your line

3 Likes

Thank you, Sean. That is a very nice effect. Astonishingly simple to implement at that.

Going SVG was really the way. I only have the SVG of a single thread so I ended up scaling it randomly and moving slightly to give the impression that there are different models just as a placeholder.

Will report back when I have the final setup,

Thanks, everyone.