Unheilig
Unheilig

Reputation: 16302

iOS: transforming a view into cylindrical shape

With Quartz 2D we can transform our views on the x, yand z axis.

In some cases we could even make them look 3D by changing the values of the matrixes.

I was wondering if it could be possible to transform a view into a cylinder shape like in the following picture?

enter image description here

Please ignore the top part of the cylinder. I am more curious to know whether it would be possible warping an UIView around like the side of the cylinder as in the image.

Is that possible only making use of Quartz 2D, layers and transformations (not OpenGL)? If not, is it possible to at least draw it in CGContext to make a view appear like so?

Upvotes: 5

Views: 3016

Answers (4)

Graham Perks
Graham Perks

Reputation: 23398

I realize this goes beyond Quartz2D... You could try adding SceneKit.

  • Obtain the view's image via UIGraphicsBeginImageContext(), view.layer.renderInContext(), CGBitmapContextCreateImage().
  • Create a SCNMaterial with the diffuse property set to the image of your view
  • Create an SCNCylinder and apply the material to it.
  • Add the cylinder to an SCNScene.
  • Create an SCNView and set its scene.
  • Add the SCNView to your view hierarchy.

Upvotes: 2

Mrunal
Mrunal

Reputation: 14128

Reference : Using OpenGL ES 2.0 with iOS, how do I draw a cylinder between two points?

I have also used the same code for one of my project:

Check this one where it is mentioned to draw cone shape; it's dated but after adapting the algorithm, it works.

See code below for solution. Self represents the mesh and contains the vertices, indices, and such.

- (instancetype)initWithOriginRadius:(CGFloat)originRadius
                   atOriginPoint:(GLKVector3)originPoint
                    andEndRadius:(CGFloat)endRadius
                      atEndPoint:(GLKVector3)endPoint
                   withPrecision:(NSInteger)precision
                        andColor:(GLKVector4)color
{
self = [super init];

if (self) {
    // normal pointing from origin point to end point
    GLKVector3 normal = GLKVector3Make(originPoint.x - endPoint.x,
                                       originPoint.y - endPoint.y,
                                       originPoint.z - endPoint.z);

    // create two perpendicular vectors - perp and q
    GLKVector3 perp = normal;
    if (normal.x == 0 && normal.z == 0) {
        perp.x += 1;
    } else {
        perp.y += 1;
    }

    // cross product
    GLKVector3 q = GLKVector3CrossProduct(perp, normal);
    perp = GLKVector3CrossProduct(normal, q);

    // normalize vectors
    perp = GLKVector3Normalize(perp);
    q = GLKVector3Normalize(q);

    // calculate vertices
    CGFloat twoPi = 2 * PI;        
    NSInteger index = 0;
    for (NSInteger i = 0; i < precision + 1; i++) {
        CGFloat theta = ((CGFloat) i) / precision * twoPi; // go around circle and get points

        // normals
        normal.x = cosf(theta) * perp.x + sinf(theta) * q.x;
        normal.y = cosf(theta) * perp.y + sinf(theta) * q.y;
        normal.z = cosf(theta) * perp.z + sinf(theta) * q.z;

        AGLKMeshVertex meshVertex;
        AGLKMeshVertexDynamic colorVertex;

        // top vertex
        meshVertex.position.x = endPoint.x + endRadius * normal.x;
        meshVertex.position.y = endPoint.y + endRadius * normal.y;
        meshVertex.position.z = endPoint.z + endRadius * normal.z;
        meshVertex.normal = normal;
        meshVertex.originalColor = color;

        // append vertex
        [self appendVertex:meshVertex];

        // append color vertex
        colorVertex.colors = color;
        [self appendColorVertex:colorVertex];

        // append index
        [self appendIndex:index++];

        // bottom vertex
        meshVertex.position.x = originPoint.x + originRadius * normal.x;
        meshVertex.position.y = originPoint.y + originRadius * normal.y;
        meshVertex.position.z = originPoint.z + originRadius * normal.z;
        meshVertex.normal = normal;
        meshVertex.originalColor = color;

        // append vertex
        [self appendVertex:meshVertex];

        // append color vertex
        [self appendColorVertex:colorVertex];

        // append index
        [self appendIndex:index++];
    }

    // draw command
    [self appendCommand:GL_TRIANGLE_STRIP firstIndex:0 numberOfIndices:self.numberOfIndices materialName:@""];
}

return self;
}

Upvotes: 1

Mark Bessey
Mark Bessey

Reputation: 19782

You definitely can't do this with a transform. What you could do is create your UIView off-screen, get the context for the view, get an image from that, and then map the image to a new image, using a non-linear mapping.

So:

  1. Create an image context with UIGraphicsBeginImageContext()
  2. Render the view there, with view.layer.renderInContext()
  3. Get an image of the result with CGBitmapContextCreateImage()
  4. Write a mapping function that takes the x/y screen coordinates and maps them to coordinates on the cylinder.
  5. Create a new image the size of the screen view, and call the mapping function to copy pixels from the source to the destination.
  6. Draw the destination bitmap to the screen.

None of these steps is particularly-difficult, and you might come up with various ways to simplify. For example, you can just render strips of the original view, offsetting the Y coordinate based on the coordinates of a circle, if you are okay with not doing perspective transformations.

If you want the view to actually be interactive, then you'd need to do the transform in the opposite direction when handling touch events.

Upvotes: 4

David R&#246;nnqvist
David R&#246;nnqvist

Reputation: 56635

No you can't bend a view using a transform.

The transform can only manipulate the four corners of the view so no matter what you do it will still be a plane.

Upvotes: 3

Related Questions