Steps for making a Three.js 3D line collection with specified width and thickness

Is there a way to create a Three.js 3D line series with customizable width and thickness?

Although the Three.js line object does have a linewidth attribute, it is not universally supported across all browsers and platforms in WebGL.

Here is how you can set the linewidth in Three.js:

    var material = new THREE.LineBasicMaterial({
        color: 0xff0000,
        linewidth: 5
    });

The previous Three.js ribbon object, which had width, has recently been eliminated.

When it comes to the Three.js tube object, it can generate 3D extrusions, but the lines do not go through the control points due to their Bezier-based nature.

Does anyone know of a method to draw a line series (such as polylines or plotlines) in Three.js with user-defined 'bulk' like width, thickness, or radius?

This question might be a rephrasing of the following question: Extruding a graph in three.js.

If there isn't an existing solution, I am open to collaborating on creating a simple function to address this question.

However, it would be great if someone could point out an already established method...

One possible solution proposed by WestLangley is to have the polyline maintain a constant pixel width, similar to what the Three.js canvas renderer currently offers.

Check out a comparison of the two renderers here:

Canvas and WebGL Lines Compared via GitHub Pages

Canvas and WebGL Lines Compared via jsFiddle

An ideal solution would allow for specifying linewidth and achieving consistent results across both renderers.

Furthermore, there are alternative approaches to 3D lines that involve realistic physical constructs, complete with shadows and interactivity. These avenues also warrant exploration.

Here are demos showcasing lines composed of multiple meshes on GitHub Pages:

Sphere and Cylinder Polylines

An intricate solution where each joint consists of a full sphere.

Cubes Polylines

It's likely that creating these as seamless single meshes will present complex challenges. In the meantime, here is a link to a partial visualization of wide, tall 3D lines:

3D Box Line on jsFiddle

The aim is to simplify the coding process "for dummies." As a result, creating a 3D line should be as straightforward and familiar as adding a sphere or cube. Geometry + material = mesh > scene. The geometry should be efficient in terms of vertices and faces creation.

The lines should possess both width and height, with the Y direction always pointing upward. The demo showcases this, although it does not tackle mitering corners seamlessly...

Answer №1

After a thorough analysis, I have developed a potential solution that seems to meet the majority of your specified criteria:

http://codepen.io/smithjones/pen/BFRel?editors=001

The idea behind this solution is quite straightforward: display any given geometry in "wireframe mode" and then apply a full-screen GLSL shader to add thickness to the wireframe lines.

This shader was influenced by the blur shaders found in the ThreeJS distribution, which essentially replicate the image multiple times along the horizontal and vertical axes. I automated this process and allowed users to define the number of copies, ensuring each copy was offset by one pixel.

While my demonstration utilized a 3D cube mesh (with an orthographic camera), converting it to a polyline should be relatively simple.

The crux of this solution lies in the custom shader (specifically the fragment shader section):

    uniform sampler2D tDiffuse;
    uniform int edgeWidth;
    uniform int diagOffset;
    uniform float totalWidth;
    uniform float totalHeight;
    const int MAX_LINE_WIDTH = 30; // Necessary due to GLSL limitations on for loops
    varying vec2 vUv;

    void main() {
        int offset = int( floor(float(edgeWidth) / float(2) + 0.5) );
        vec4 color = vec4( 0.0, 0.0, 0.0, 0.0);

        // Horizontal line copies first
        for (int i = 0; i < MAX_LINE_WIDTH; i++) {
            float uvFactor = (float(1) / totalWidth);
            float newUvX = vUv.x + float(i - offset) * uvFactor;
            float newUvY = vUv.y + (float(i - offset) * float(diagOffset) ) * uvFactor;  
            color = max(color, texture2D( tDiffuse, vec2( newUvX,  newUvY  ) ));    
            if(i == edgeWidth) break; 
        }

        // Vertical line copies
        for (int i = 0; i < MAX_LINE_WIDTH; i++) {
            float uvFactor = (float(1) / totalHeight);
            float newUvX = vUv.x + (float(i - offset) * float(-diagOffset) ) * uvFactor; 
            float newUvY = vUv.y + float(i - offset) * uvFactor;
            color = max(color, texture2D( tDiffuse, vec2( newUvX, newUvY ) ));  
            if(i == edgeWidth) break;
        }

        gl_FragColor = color;
    }

Advantages:

  • No additional geometry required beyond the line vertices
  • User-defined line thickness
  • Full-screen shader is generally gentle on the GPU
  • Can be fully integrated within the WebGL canvas

Disadvantages:

  • Line thickness is precise on horizontal and vertical edges but slightly off on diagonal edges due to the algorithm used. This is a limitation of the solution, although it is minimally noticeable for low line thicknesses and complex geometries.
  • Gaps may appear at joints for thicker lines. Testing the Codepen demo will illustrate this issue. I attempted to address this by introducing a second "diagonal pass," but it became complex and may only affect higher line thicknesses or extreme angles. Exploring the original source could shed light on this potential solution.
  • Since a full-screen filter is employed, the WebGL context is restricted to displaying objects of this thickness. Displaying various line widths would necessitate additional rendering passes.

Answer №2

If you're looking for a possible solution, consider working with your 3D points and utilizing the THREE.Vector3.project method to determine screen-space coordinates. From there, you can easily utilize canvas, along with its lineTo and moveTo functions, to create your desired visuals. The canvas 2D context supports varying line thickness, providing flexibility in your design.

var w = renderer.domElement.innerWidth;
var h = renderer.domElement.innerHeight;
vector.project(camera);
context2d.lineWidth = 3;
var x = (vector.x+1)*(w/2);
var y = h - (vector.y+1)*(h/2);
context2d.lineTo(x,y);

Keep in mind that you may need to use a separate canvas, or layer, above your WebGL rendering canvas to achieve the desired effect.

For scenarios with minimal camera changes, you can consider constructing lines using polygons and adjusting their vertex positions based on camera transformations. This approach is especially effective with orthographic cameras, as only rotations would necessitate vertex position adjustments.

Another technique involves disabling canvas clearing and drawing your lines multiple times with offsets within a circle or box. Once completed, you can re-enable clearing. While this method may require additional draw operations, it offers scalability in your design.

It's worth noting that the limitations with line thickness are partly due to how ANGLE operates in browsers like Chrome and Firefox, as it emulates OpenGL through DirectX. ANGLE developers mention that the WebGL specification mandates support for line thickness only up to 1, thus they do not view it as a bug and have no plans to address it. However, line thickness functionality should work on non-Windows operating systems where ANGLE is not utilized.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to specifically call the last promise in a loop?

I'm new to promises and could use some help. I have a situation where promises are resolving randomly, how can I ensure that the last promise in the loop is resolved after the full loop executes? For example, if this.selectedValues has 4 values, som ...

My method for updating form input properties involves switching the disable attribute from "false" to "true" and vice versa

I have a form that uses Ajax to submit data. Once the user submits the form, the text is updated to indicate that the data was sent successfully, and then the form is displayed with the fields filled out. I want to display the form but prevent users from r ...

Is it acceptable for Single Page Web Apps to have multiple requests at startup?

I've been dedicated to developing a Single Page Web App (SPA) recently. The frontend is built with BackboneJS/Marionette, while the backend is powered by Java Spring :(. However, I've noticed that the application's start time could be sluggi ...

Addressing the issue of empty ngRepeat loops

Utilizing ngRepeat to generate table rows: <tr ng-repeat="User in ReportModel.report" on-finish-render> <td><span>{{User.name}}</span></td> </tr> An on-finish-render directive triggers an event upon completion of t ...

Developing elements in React Native based on JSON data dynamically

Hello everyone, I'm brand new to this forum and just starting out with React Native. I was wondering if someone could help me by providing a code snippet to create form elements (such as an image and a toggle switch) based on JSON data. Here is what ...

What steps should I take to ensure the privacy of this React Layout?

To ensure only authenticated users can access the layout component, we need to implement a check. const router = createBrowserRouter([ { path: "/", element: <Layout />, children: [ { path: "/", ...

What could be causing the span class data to not be retrieved by the getText method

Looking to retrieve the value of a span class called 'spacer-right big project-card-measure-secondary-info' <span class="spacer-right big project-card-measure-secondary-info">1</span> snippet of code: browser.waitForEl ...

jQuery Show/Hide Not Working Properly

I'm attempting to showcase one Tweet at a time and have it rotate through different tweets. I'm facing an issue where it works correctly on one type of page, but not on the other. Could this be due to a CSS precedence rule overriding the function ...

Encountered an issue when trying to establish a connection to the MySQL database on Openshift using a

I am currently running a Node.js app with Express that is deployed on OpenShift. I have set up databases using the PHPMyAdmin 4.0 cartridge. Although I can establish a connection to the database, anytime I run a query, it throws an ECONNREFUSED error. Whe ...

Is there a way to manipulate the appearance of a scroller using JavaScript?

I'm intrigued by how fellow front-end developers are able to customize the scrollbar shape on a webpage to enhance its appearance. Can anyone guide me on using JavaScript to accomplish this? ...

Leveraging the Power of jsPDF in Your Google Apps Scripts

Is it feasible to integrate jsPDF into a Google Apps Script project? I am attempting to create documents using templated HTML, but certain CSS styles like background-color do not translate to PDF when using the getAs() function. Given the limitations of ...

What is causing the error "unable to access property 'map' of undefined" in my code?

Currently working on React + Redux and trying to implement a likes counter. However, encountering an error 'cannot read property 'map' of undefined'. Here is the code snippet for my Reducer: const initialState = { comments: [], ...

Wrapping an anchor tag with a div in Codeigniter

Can a div tag be used inside an anchor function? I have a div with the following CSS: #first{ opacity:0; } Now, I want to include it in my anchor element. Here is the code snippet: <?php if(is_array($databuku)){ echo '<ol>&l ...

"Encountering issues with react-swipable-views when attempting to use it

I'm currently working on implementing react-swipable-views into my React application, but I encountered a specific error message: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite component ...

Disallow the use of punctuation marks such as dots, plus signs, minus signs, and the letter 'e' when entering

Is there a way to restrict the input of a dot, plus sign, minus sign, or letter 'e' when using semantic-ui-react library? I have searched for solutions but have not found any that work. Can someone provide assistance with this issue? ...

What causes the scrollbar to not extend to the bottom when connected to another scrollbar via a JavaScript equation?

I have been working on a code that involves multiple scrollbars that are all linked together. When you move one scrollbar, the other two will move proportionally. However, due to differences in width, the scroller doesn't always reach the end of the s ...

When making a PUT request with Fetch, the Cache-Control header is altered in Firefox but remains unchanged in

In the process of generating presigned URLs for uploading to S3, it is necessary to set the Cache-Control header value to be public, max-age=31536000, immutable. The fetch operation is executed using the following code: fetch( uploadUrl, ...

Steps for activating chromedriver logging using the selenium webdriver

Is there a way to activate the chromedriver's extensive logging features from within the selenium webdriver? Despite discovering the appropriate methods loggingTo and enableVerboseLogging, I am struggling to apply them correctly: require('chrom ...

Is it possible to extract all parameters, including nested or within arrays, from a Swagger File using Node.js?

Looking for a method to extract and interpret data such as parameters from a Swagger file. Specifically, I am working with the Petstore Swagger API ( ). The definitions within the file contain references to other components, like parameters. One example ...

The data retrieved using Ionic 3 Angular Fire's payload.val() seems to be returning

Is it possible to determine whether a profile is filled in or empty when using Firebase Database for storing profile information? Currently, I am utilizing profile.payload.val(), but it seems to return null in both cases instead of true when the profile is ...