How do I wrap object around a cylinder in ThreeJS?

I’ve been trying to wrap an imported object (Collada file) around a cylinder in threejs, regardless of what the size of the imported object is, I’ve tried CurveModifier, and that doesn’t seem to work for a full 360-degree wrap. End result

This image is from blender, but basically this is what I am looking for, I’ve tried the code below, it plots the object in a circle, but I have 2 issues

  1. I can’t get the size and position to match the cylinder
  2. when I export the file, the modifiers are lost.
var width = $('#col_canvas').width();
var height = $('#col_canvas').height();
var fov = 45;
var aspect = width / height;
var near = 0.001;
var far = 100000;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var mouseX = 0;
var mouseY = 0;
var SELECTED;
var windowHalfX = width / 2;
var windowHalfY = height / 2;
var theta = 0;
var antialias = true;
var alpha = true;
var canvas = $("#canvas")[0];
var camera_init_pos = [0, 30, 0];
var camera_init_lookAt = [0, 0, 0];

var renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: antialias,
    alpha: alpha
});
renderer.setSize(width, height);

var camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(camera_init_pos[0], camera_init_pos[1], camera_init_pos[2]);
camera.lookAt(camera_init_lookAt[0], camera_init_lookAt[1], camera_init_lookAt[2]);

var scene = new THREE.Scene();

var orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
orbitControls.update();
orbitControls.addEventListener('change', render);

loadLights();

function animate() {
    requestAnimationFrame(animate);
    orbitControls.update();
    render();
}

function render() {
    renderer.render(scene, camera);
}

animate();
window.addEventListener('resize', resize);

function resize() {
    camera.left = camera.bottom * aspect;
    camera.right = camera.top * aspect;
    camera.updateProjectionMatrix();
    renderer.setSize(width, height);
    render();
}
//*****************************************************************************************
var object_names = [];
var geo_cylinder, geo_cutbox, geo_wrapper, geo_text;
var mesh_cylinder, mesh_cutbox, mesh_wrapper, mesh_text;
var url_wrapper, url_text;
var filetype_wrapper, filetype_text;
var radius, height, segments, cutbox_x, cutbox_y, cutbox_z;

$('#btn_create_new').click(function() {
    radius = parseFloat($('#txt_modal_cyl_radius').val());
    height = parseFloat($('#txt_modal_cyl_height').val());
    segments = parseFloat($('#txt_modal_cyl_segments').val());
    cutbox_x = parseFloat($('#txt_modal_cutbox_x').val());
    cutbox_y = parseFloat($('#txt_modal_cutbox_y').val());
    cutbox_z = parseFloat($('#txt_modal_cutbox_z').val());

    geo_cylinder = new THREE.CylinderGeometry(radius / 2, radius / 2, height, segments);
    geo_cylinder.rotateX(getAngle(90));
    object_names.push(geo_cylinder.name);
    mesh_cylinder = new THREE.Mesh(geo_cylinder, silver_material);
    mesh_cylinder.name = 'Cylinder';
    scene.add(mesh_cylinder);
    var cylinder_size = new THREE.Box3().setFromObject(mesh_cylinder)

    geo_cutbox = new THREE.BoxGeometry(cutbox_x, cutbox_y, cutbox_z);
    geo_cutbox.translate(0, cutbox_x * 1.114286, 0);
    object_names.push(geo_cutbox.name);
    mesh_cutbox = new THREE.Mesh(geo_cutbox, silver_material);
    mesh_cutbox.name = 'CutBox';
    scene.add(mesh_cutbox);
    loadDAEFile(url_wrapper, filetype_wrapper);
});


function loadDAEFile(url) {
    var geo_cir = new THREE.CircleGeometry(radius, 132);
    // geo_cir.rotateX(getAngle(-90));
    var points_cir = [];
    $.each(geo_cir.vertices, function(i, v) {
        if (i > 0) {
            points_cir.push(v);
        }
    });

    colladaLoader.load(url, function(dae) {
        dae.scene.traverse(function(child) {
            if (child.type == 'Mesh') {
                mesh_wrapper = child;
            }
        });
        geo_wrapper = mesh_wrapper.geometry;
        mesh_wrapper.name = 'Wrapper';
        var wrapper_size = new THREE.Box3().setFromObject(mesh_wrapper);
        let size = wrapper_size.getSize(new THREE.Vector3());
        var max_side = Math.max(size.x, size.y, size.z)
        var scale = height / max_side;
        console.log(geo_wrapper);
        geo_wrapper.scale(scale, scale, scale);

        var box = new THREE.Box3().setFromObject(mesh_wrapper);
        var center = new THREE.Vector3();
        var box_center = box.getCenter(center);
        mesh_wrapper.position.set(box_center.x * -1, box_center.y * -1, box_center.z * -1); // center the model
        // scene.add(mesh_wrapper);
        curveThis(mesh_wrapper, radius, points_cir);
    })
}

$("#file_wrapper").change(function(e) {
    file = $(this).get(0).files[0];
    url_wrapper = URL.createObjectURL(file);
    var filename = file.name;
    var extension = filename.split('.').pop();
    filetype_wrapper = extension;
    $('#sel_filetype_wrapper').val(extension);
});

$("#file_text").change(function(e) {
    file = $(this).get(0).files[0];
    url_text = URL.createObjectURL(file);
    var filename = file.name;
    var extension = filename.split('.').pop();
    filetype_text = extension;
    $('#sel_filetype_text').val(extension);
});

function curveThis(dae, circle_radius, points_cir) {
    var boundingBox = new THREE.Box3().setFromObject(dae);
    var circle_arc_length = ((3.6 * (circle_radius - (circle_radius / 20))) * Math.PI);
    var size = boundingBox.getSize();
    var scale_to_radius = (circle_arc_length / size.x) / 100;
    var scaleX = size.x * scale_to_radius * 2.5;
    var scaleY = circle_radius / size.y * 2.5;
    var scaleZ = circle_radius / size.z * 2.5;

    geometry = dae.geometry;

    geometry.scale(scaleX, scaleY, 1);


    var material = new THREE.MeshStandardMaterial({
        color: 0xff0000,
        side: THREE.DoubleSide
    });
    var cube = new THREE.Mesh(geometry, material);

    var curveHandles = [];
    var boxGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
    var boxMaterial = new THREE.MeshBasicMaterial();

    var curves = [
        points_cir
    ].map(function(curvePoints) {

        var curveVertices = curvePoints.map(function(handlePos) {

            var handle = new THREE.Mesh(boxGeometry, boxMaterial);
            handle.position.copy(handlePos);
            curveHandles.push(handle);
            scene.add(handle);
            return handle.position;

        });

        var curve = new THREE.CatmullRomCurve3(curveVertices);
        curve.curveType = 'centripetal';
        curve.closed = true;

        var points = curve.getPoints(50);
        var line = new THREE.LineLoop(
            new THREE.BufferGeometry().setFromPoints(points),
            new THREE.LineBasicMaterial({
                color: 0x00ff00
            })
        );

        scene.add(line);

        return {
            curve,
            line
        };

    });

    var numberOfInstances = 1;
    flow = new THREE.InstancedFlow(numberOfInstances, curves.length, geometry, material);

    curves.forEach(function({
        curve
    }, i) {
        flow.updateCurve(i, curve);
        scene.add(flow.object3D);
    });

    for (let i = 0; i < numberOfInstances; i++) {

        var curveIndex = i % curves.length;
        flow.setCurve(i, curveIndex);
        flow.moveIndividualAlongCurve(i, i * 1 / numberOfInstances);
        flow.object3D.setColorAt(i, new THREE.Color(0xffffff * Math.random()));
    }
}
1 Like

What image? :thinking:

2 Likes

sorry I forgot to attach image

First of all, this shrinkwrap thread seems like what you’re looking for?


If not, and assuming both objects are 3D meshes (if one is 2D, just use decals) - you can curve any “flat” shape around an arbitrary cylinder / sphere in the vertex shader - example (try modifying “Alpha” in top-right.)

As in lines 103-105 in the codepen - pretty much all you need to do is multiply the vertex position of one axis by sin / cos of a position on another axis.

Alternatively - you can also offset the vertices away from the center of the model - so that the distance between each vertex and the center, on the x/z plane, is equal to the radius of your cylinder.

But the final solution depends on how you prepare the original models - just like in Blender, there’s no single solution that’d fit all types of geometries - you need to test different modifiers / wrap methods / parameter configurations.

3 Likes

In blender, try applying the modifier before you export

image

1 Like

hi Sean, sorry I meant THREEJS, blender can do this, but I was looking for this to be done in THREEJS

Thank you, let me look into those

Hello @mjurczyk
First of thank you for your reply, the examples and/or suggestions are really for flat shapes, as you said, I was looking to wrap a 3d object around, do you know of any more idea?

@jayeomer
Some time ago I did this with instances: Detecting objects which can camera see - #10 by prisoner849
And it’s pretty much doable on js side (like what @mjurczyk described): https://codepen.io/prisoner849/full/wvjzKrb

3 Likes

Here’s an example where I wrap a TextGeometry around a cylinder

Threejs Boilerplate - JSFiddle - Code Playground

image

It uses my Bender lib.

5 Likes

Thank you so very much @seanwasere, I have been struggling for 3 weeks now, and you have saved me, I was able to find your bender.js GitHub - Sean-Bradley/Bender: Bend Geometries in Threejs that did it.
once again thank you!!!

1 Like

This is amazing work, @prisoner849
Thank you

1 Like