ggmendez
ggmendez

Reputation: 621

Non-radial texture mapping over a ring geometry in WebGL using Three.js

I am trying to simulate image deformation effects using textures over 2D geomtries using the ThreeJS library. I want to apply a texture image over a hollow circle (basically, a ring built by the THREE.RingGeometry function) and obtain the results shown at this image:

Texture image and desired results

Following I show the results I am obtaining in my scene both for the solid ring and its wireframed version:

Texture applied over the ring and its corresponding wireframed version

The problem is that, as you see, the texture is been applied in a radial way, from the center of the ring to the outside. However, what I really need is to apply the texture image on a concentric circle way, as shown in the first image of this question.

The idea is to produce a deformed version of the original texture over a ring shape. I would like to know how this effect can be programmatically achieved through Three.js in such a way that the destination shape can be any arbitrary 2D geometry .

Following, there is the relevant code I am using to draw my scene:

var texture = THREE.ImageUtils.loadTexture('./images/texture.png');

var wireRing = new THREE.Mesh(new THREE.RingGeometry(10, 20, 50, 5, 0, Math.PI * 2), new THREE.MeshBasicMaterial({map: texture, wireframe: true}));
wireRing.position.set(-25, 50, 0);
scene.add(wireRing);

var ring = new THREE.Mesh(new THREE.RingGeometry(10, 20, 50, 5, 0, Math.PI * 2), new THREE.MeshBasicMaterial({map: texture}));
ring.position.set(25, 50, 0);
scene.add(ring);

Upvotes: 2

Views: 2741

Answers (2)

Z. Bagley
Z. Bagley

Reputation: 9260

For anyone using a more recent version three.js I was able to create a TypeScript class that updates the uv correctly. The main change from the standard RingGeomtry is updating the uv definition from a const and then utilizing uv = new THREE.Vector2(j / phiSegments, i / thetaSegments); which has changed since the previously correct answer provided.

planet-ring-geomtry.ts

import * as THREE from 'three';

export class PlanetRingGeomtry extends THREE.BufferGeometry {
  parameters: {
    innerRadius: number;
    outerRadius: number;
    thetaSegments: number;
    phiSegments: number;
    thetaStart: number;
    thetaLength: number;
  };

  constructor(
    innerRadius = 0.5,
    outerRadius = 1,
    thetaSegments = 32,
    phiSegments = 1,
    thetaStart = 0,
    thetaLength = Math.PI * 2
  ) {
    super();

    this.parameters = {
      innerRadius: innerRadius,
      outerRadius: outerRadius,
      thetaSegments: thetaSegments,
      phiSegments: phiSegments,
      thetaStart: thetaStart,
      thetaLength: thetaLength,
    };

    thetaSegments = Math.max(3, thetaSegments);
    phiSegments = Math.max(1, phiSegments);

    // buffers

    const indices = [];
    const vertices = [];
    const normals = [];
    const uvs = [];

    // some helper variables

    let radius = innerRadius;
    const radiusStep = (outerRadius - innerRadius) / phiSegments;
    const vertex = new THREE.Vector3();
    let uv = new THREE.Vector2();

    // generate vertices, normals and uvs

    for (let j = 0; j <= phiSegments; j++) {
      for (let i = 0; i <= thetaSegments; i++) {
        // values are generate from the inside of the ring to the outside

        const segment = thetaStart + (i / thetaSegments) * thetaLength;

        // vertex

        vertex.x = radius * Math.cos(segment);
        vertex.y = radius * Math.sin(segment);

        vertices.push(vertex.x, vertex.y, vertex.z);

        // normal

        normals.push(0, 0, 1);

        // uv
        uv = new THREE.Vector2(j / phiSegments, i / thetaSegments);

        uvs.push(uv.x, uv.y);
      }

      // increase the radius for next row of vertices

      radius += radiusStep;
    }

    // indices

    for (let j = 0; j < phiSegments; j++) {
      const thetaSegmentLevel = j * (thetaSegments + 1);

      for (let i = 0; i < thetaSegments; i++) {
        const segment = i + thetaSegmentLevel;

        const a = segment;
        const b = segment + thetaSegments + 1;
        const c = segment + thetaSegments + 2;
        const d = segment + 1;

        // faces

        indices.push(a, b, d);
        indices.push(b, c, d);
      }
    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute(
      'position',
      new THREE.Float32BufferAttribute(vertices, 3)
    );
    this.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
  }
}

Previous release definition for RingGeomtry r67: https://github.com/mrdoob/three.js/blob/fa4e4ae77fe5a1f9506a91160cc7a9f4e61518fc/src/extras/geometries/RingGeometry.js

Updated based on RingGeomtry r166: https://github.com/mrdoob/three.js/blob/1845f6cd0525b5c73b9da33c40e198c360af29f1/src/geometries/RingGeometry.js

Visual example of application of texture for Saturn's rings:

map color map texture data

alphaMap alphaMap texture data

relevant code

    var thetaSegments = 42;

    var geometry = new PlanetRingGeomtry(
      innerRadius,
      outerRadius,
      thetaSegments
    );
    geometry.rotateX(Math.PI / 2);

    var mapTexture = this.textureLoader.load(map);
    mapTexture.minFilter = THREE.NearestFilter;

    var colorMapTexture = this.textureLoader.load(colorMap);
    colorMapTexture.minFilter = THREE.NearestFilter;

    var material = new THREE.MeshLambertMaterial({
      map: colorMapTexture,
      alphaMap: mapTexture,
      transparent: true,
      opacity: 0.98,
      side: THREE.DoubleSide,
    });

    var ring = new THREE.Mesh(geometry, material);
    ring.position.set(0, 0, 0);

result rendered planet with rings

Upvotes: 3

WestLangley
WestLangley

Reputation: 104793

You just need to change the UV mapping in RingGeometry like so:

uvs.push( new THREE.Vector2( o / thetaSegments, i / phiSegments ) );

Also, if you want to rotate the texture around the ring, you instantiate the RingGeometry by varying the thetaStart parameter:

var geometry = new THREE.RingGeometry( 10, 20, 50, 5, thetaStart, Math.PI * 2 );

three.js r.67

Upvotes: 3

Related Questions