Combining text shapes in Three.js without losing their distinct material colors

Currently experimenting with merging text geometries in Three.js (r84) and looking to achieve this using multiMaterial while preserving individual colors for each text object. Check out the live demo here: https://jsfiddle.net/5oydk6nL/

Appreciate any insights or guidance.

var $win = $( window ) ,
    $container = $( '#webGL-container' ) ,

    scene , camera , cameraTarget , renderer ,
    pointLight , hex ,
    stats , rendererStats ,

    typeface = 'https://cdn.rawgit.com/redwavedesign/ca97268140e8a51633595cd34bb77f16/raw/46ae61687ac8e7e3af01ee2c983580f2b0b0809f/bebas_regular.json';



/* text objects */

var a = {
    text: 'a' ,
    color: 'red' ,
    x: -90
}

var b = {
    text: 'b' ,
    color: 'blue' ,
    x: -60
}

var c = {
    text: 'c' ,
    color: 'green' ,
    x: -30
}

var d = {
    text: 'd' ,
    color: 'yellow' ,
    x: 0
}

var e = {
    text: 'e' ,
    color: 'purple' ,
    x: 30
}

var f = {
    text: 'f' ,
    color: 'orange' ,
    x: 60
}

var g = {
    text: 'g' ,
    color: 'aqua' ,
    x: 90
}




// array containing all text objects
var letters = [ a , b , c , d , e , f , g ];




function decimalToHex( d ) {

    var hex = Number( d ).toString( 16 );
    hex = "000000".substr( 0, 6 - hex.length ) + hex;
    return hex.toUpperCase();

}




function initialize() {


    /* Create scene, camera, and renderer */
    scene = new THREE.Scene();
    camera =  new THREE.PerspectiveCamera( 40 , window.innerWidth / window.innerHeight , .1 , 1500 );
    renderer = new THREE.WebGLRenderer({ antialias: true });

    /* Setup renderer */
    renderer.setClearColor( '#ffffff' );
    renderer.setSize( window.innerWidth , window.innerHeight );
    renderer.shadowMap.enabled = true;
    renderer.shadowMapSoft = true;

    /* Setup camera */
    camera.position.x = 0;
    camera.position.y = 0;
    camera.position.z = 500;

    cameraTarget = new THREE.Vector3( 0 , 0 , 0 );

    /* Lights */
    pointLight = new THREE.PointLight( 0xffffff, 2 );
    pointLight.position.set( 20 , -300 , 200 );
    scene.add( pointLight );

    pointLight.color.setStyle( '#EBEBEB' );
    hex = decimalToHex( pointLight.color.getHex() );


    // Load each text object from the 'letters' array
    $.each( letters , function( index , letter ) {


        var fontLoader = new THREE.FontLoader();


        // Load font
        fontLoader.load( typeface , function ( font ) {


            var geometry = new THREE.TextGeometry( letter.text , {

                    font: font,
                    height: 8 ,
                    size: 28 ,
                    curveSegments: 4 ,
                    bevelThickness: 1,
                    bevelSize: 1.5 ,
                    bevelSegments: 3 ,
                    bevelEnabled: true ,
                    material: 0,
                    extrudeMaterial: 1

            });


            var material = new THREE.MultiMaterial( [
                new THREE.MeshPhongMaterial( { color: letter.color , shading: THREE.FlatShading } ), // front
                new THREE.MeshPhongMaterial( { color: letter.color , shading: THREE.SmoothShading } ) // side
            ] );


            var mesh = new THREE.Mesh( geometry , material );


            mesh.position.set( letter.x , 0 , 0 );


            // Add text object to scene
            scene.add( mesh );


        });


    // End of each loop
    });


    // Append the rendered element to the page
    $container.append( renderer.domElement );


}




function animateScene() {

    camera.lookAt( cameraTarget );

    renderer.clear();

    renderer.render( scene , camera );

}



function startAnimation() {

    // Initialize Three.js stats utility
    stats.begin();

    requestAnimationFrame( startAnimation );

    animateScene();

    // Update Threex stats plugin
    rendererStats.update( renderer );

    // Test Three.js stats functionality
    stats.end();

}



// Performance monitoring setup

rendererStats = new THREEx.RendererStats();
rendererStats.domElement.style.position = 'absolute'
rendererStats.domElement.style.left = '0px'
rendererStats.domElement.style.bottom = '0px'
document.body.appendChild( rendererStats.domElement );

stats = new Stats();
    stats.showPanel( 0 );
    document.body.appendChild( stats.domElement );
    document.body.appendChild( stats.dom );



// Initialization
initialize();

// Start animation
startAnimation();

Answer №1

Check out the updated fiddle here.

Several steps were required to update the code:

1) Create a multimaterial and assign material for each individual letter:

var material = new THREE.MeshPhongMaterial({
    color: letter.color,
    shading: THREE.FlatShading
});

multiMaterial.materials.push(material);

2) Assign a material index to all faces of every letter:

for (var i = 0, il = geometry.faces.length; i < il; i++) {
    geometry.faces[i].materialIndex = index
}

3) Translate the geometry along the x-axis using a transformation matrix:

geometry.applyMatrix(
    matrix.makeTranslation(letter.x, 0, 0)
);

4) Combine the geometries of each letter into one single geometry:

mergedGeometry.merge(geometry);

Now you have a single merged geometry:

https://i.sstatic.net/ewjnX.png


Note: Avoid using jQuery in your fiddle in the future, as this additional library is unnecessary for your example. For more information, refer to these guidelines..

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

Can a before hook ever run after a test in any situation, Mocha?

My before hook runs after the initial test and at the conclusion of the second test. Here is the code for my before hook: before(function () { insightFacade.addDataset("courses", content) .then(function (result: InsightResponse) { ...

Filter an array containing objects within objects and use the reduce method to calculate the total value

Here is an array of objects that I'm working with: const data = [ { "order_id":38795, "order_type":"Music", "date":"2021-08-14", "name":"Concert ...

Here is a unique rewrite:"Adjusting the prop of a Material UI Button component depending on screen size breakpoints can be achieved by utilizing

While using the Material UI Button component, I encountered an issue with conditionally determining the variant of the button based on screen size. Specifically, I want the variant to be 'outlined' on medium and larger screens, and no variant at ...

What steps can be taken to determine the cause of an abrupt closure of a websocket client?

My browser game utilizes the ws module. However, there seems to be an issue where connections from distant computers randomly close while playing the game. The screen freezes when this happens, but interestingly, I do not encounter this problem when testin ...

Utilize a button in React as a way to simultaneously submit a form and redirect with an href

I've created a form where users can input their information and click on a send button to submit it (see code below, button at the end). The form works fine, but when a user clicks on send, the input disappears which might give the impression that the ...

Creating dimension in simple shapes like Ring or Cylinder with A-frame

Can a 2D primitive, such as a Ring or Cylinder, be given thickness or depth? I am working on creating a 3D door using primitives and I would like to add dimension to either a ring with depth or a cylinder with a thicker mesh. If this can be achieved at t ...

In order to use the serve command, it is necessary to run it within an Angular project. However, if a project definition cannot be located

An error occurred while running the ng serve command: C:\Mysystem\Programs\myfwms>ng serve The serve command needs to be executed within an Angular project, but a project definition could not be found. I encounter this error when ...

Using an onclick function to increment and decrement values

Can anyone help me figure out how to reverse a function onclick? Here is the function: var theTotal = 0; $('button').click(function(){ theTotal = Number(theTotal) + Number($(this).val()); $('.total').text(theTotal); }); ...

How can transitions be activated in Bootstrap 4 carousel?

Having an issue with my Bootstrap 4 carousel where I need to move 4 slides at a time. I have tried using the code below: $(this).carousel(4); However, the problem is that the carousel only jumps to that index without showing any transitions. Using $(this) ...

Tips for activating a function when the sidebar menu is opened in Ionic 2

How can I trigger a function when the SideMenu is open in Ionic 2? I came across this link here for Ionic-1, but I'm wondering how to accomplish this in Ionic 2. Any help would be appreciated. Thanks! ...

Transform a JavaScript object into the appropriate REST query format using the Meteor.call() function

In my attempt to make an API call to a Wordpress REST API, I came across different ways of doing so. The console version of the call looks like this: http://dev.thomastraum.com/wp-json/posts?type=tt_works&filter[work_categories]=all&filter[posts_p ...

Passing a string from JavaScript to ASP.NET using AJAX

Having trouble passing data from JavaScript to ASP.cs using AJAX. The process doesn't seem to be working as expected. I am trying to pass a string with the results of a listbox but encountering issues. What could be causing this problem? Thank you. ...

The JavaScript function fails to give back the parameter values

After creating a function in the script tag within the head section that takes two parameters, a and b, to return the product of a multiplied by b, I encountered an issue. When calling the function with the values 3 and 4, nothing was displayed. To troubl ...

Instructions for extracting and storing values from a JSON response into an array

Utilizing react-select async feature to fetch options from an input provided via an API. The JSON response contains a list with a "FullName" field, which I aim to extract and store in an array to be used as options. Within the JSON structure, there is a l ...

Access your Angular 5 application as a static webpage directly in your browser, no server required!

I need to run an Angular 5 application in a browser by manually opening the index.html file without the use of a server. My client does not have access to any servers and simply wants me to provide a package (dist folder) for them to double-click on the in ...

Using jQuery AJAX for JavaScript redirection

I have been working on a JQuery AJAX response, and need help redirecting to another view. Here is the code snippet: $.ajax({ url: "/Home/PersistSelections", type: 'post', contentType: "application/json; ...

Frozen Blanket: Modifying Particle Velocity

I need assistance adjusting the particle speed in this snowfall animation script. I'm having trouble locating the specific values that control the "Falling Speed." The current speed of the falling particles is too fast, and here is most of the code sn ...

What's the best way to adjust the width of the <Input> component in Reactstrap?

How can I adjust the width of an input element in Reactstrap to be smaller? I've attempted to set the bsSize to small without success <InputGroup> <Input type="text" name="searchTxt" value={props.searchText ...

Acquire the URL using Angular within a local environment

I am currently working on a basic Angular project where I have a JSON file containing some data. [{ "name": "Little Collins", "area": "Bronx", "city": "New York", "coverImage": "https://images.unsplash.com/photo-1576808597967-93bd9aaa6bae?ixlib=rb-1.2.1&a ...

Dealing with an empty request.FILES using FileUploadParser in Django Rest Framework and Angular file upload technique

Uploading files in an angular view can sometimes be tricky, especially when using templates. The code snippet below shows how to upload multiple and single files using Angular File Upload library. Multiple <input type="file" name="file" nv-file-select= ...