Textured Ring Implementation

In response to a request here I implemented a textured ring class which I thought might be useful to others so here we go:

original thread

download
TexturedRing.zip (9.2 KB)

image
this must be a power-of-two texture to support GL_REPEAT

result

TexturedRingTest.h - useage

//almaxmasuma@gmail.com - 2015/08/11
#include "cocos2d.h"
#include "TexturedRing.h"

cocos2d::Scene* createTexturedRingScene()
{
    auto director = cocos2d::Director::getInstance();
    auto center = director->getVisibleOrigin() + director->getVisibleSize() * 0.5f;
    auto scene = cocos2d::Scene::create();
    {
        auto ring = TexturedRing::create(30, 32, "ring_test.png");
        ring->setPosition(center);
        scene->addChild(ring);
    }
    {
        auto ring = TexturedRing::create(60, 32, "ring_test.png");
        ring->setStrokeScale(0.2f);
        ring->setPosition(center);
        scene->addChild(ring);
    }
    {
        auto ring = TexturedRing::create(100, 32, "ring_test.png");
        ring->setPosition(center);
        scene->addChild(ring);
    }
    {
        auto ring = TexturedRing::create(160, 32, "ring_test.png");
        ring->setStrokeScale(0.5f);
        ring->setIsTextureRepeated(false);
        ring->setPosition(center);
        scene->addChild(ring);
    }
    return scene;
}

TexturedRing.h

 //almaxmasuma@gmail.com - 2015/08/11
#pragma once

#include "cocos2d.h"

class TexturedRing : public cocos2d::Node, public cocos2d::TextureProtocol
{
public:
    virtual ~TexturedRing();
    
    //static constructors
    static TexturedRing* create(float radius, unsigned int segmentCount, const std::string& imagePath);
    static TexturedRing* create(float innerRadius, unsigned int segmentCount, cocos2d::Texture2D* texture);
    
    //--Node--
    void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags) override;
    
    //--TextureProtocol--
    virtual cocos2d::Texture2D* getTexture() const override;
    virtual void setTexture(cocos2d::Texture2D *texture) override;
    
    //--BlendProtocol--
    virtual void setBlendFunc(const cocos2d::BlendFunc &blendFunc) override;
    virtual const cocos2d::BlendFunc& getBlendFunc() const override;
    
    //--helper methods--
    bool buildGeometry(float radius, unsigned int segmentCount);
    
    //--configuration--
    //scales the thickness of line and repeat rate
    CC_PROPERTY_PASS_BY_REF(float, m_strokeScale, StrokeScale);
    //true by default. The texture is repeated around the ring. Textures must be PoT for GL_REPEAT
    CC_PROPERTY_PASS_BY_REF(bool, m_isTextureRepeated, IsTextureRepeated);
    
CC_CONSTRUCTOR_ACCESS:
    TexturedRing();
    bool init(float radius, unsigned int segmentCount, const std::string& imagePath);
    bool initWithTexture(float radius, unsigned int segmentCount, cocos2d::Texture2D* texture);
    
    //texture
    cocos2d::Texture2D* m_texture;
    cocos2d::BlendFunc m_blendFunc;
    
    //geometry
    float m_radius;
    unsigned int m_segmentCount;
    std::vector<cocos2d::Vec2> m_vertices;
    std::vector<cocos2d::Tex2F> m_texCoords;
    
    //rendering
    void onDraw(const cocos2d::Mat4 &transform, uint32_t flags);
    cocos2d::CustomCommand _customCommand;
    
private:
    CC_DISALLOW_COPY_AND_ASSIGN(TexturedRing);
};

TexturedRing.cpp

//almaxmasuma@gmail.com - 2015/08/11
#include "TexturedRing.h"

USING_NS_CC;

TexturedRing::TexturedRing()
: m_texture(nullptr)
, m_radius(0)
, m_segmentCount(0)
, m_strokeScale(1)
, m_isTextureRepeated(true)
{
    
}

TexturedRing::~TexturedRing()
{
    CC_SAFE_RELEASE_NULL(m_texture);
}

TexturedRing* TexturedRing::create(float radius, unsigned int segmentCount, const std::string& imagePath)
{
    auto instance = new (std::nothrow) TexturedRing();
    if(instance && instance->init(radius, segmentCount, imagePath))
    {
        return instance;
    }
    CC_SAFE_DELETE(instance);
    return nullptr;
}

TexturedRing* TexturedRing::create(float radius, unsigned int segmentCount, Texture2D* texture)
{
    auto instance = new (std::nothrow) TexturedRing();
    if(instance && instance->initWithTexture(radius, segmentCount, texture))
    {
        return instance;
    }
    CC_SAFE_DELETE(instance);
    return nullptr;
}

bool TexturedRing::init(float radius, unsigned int segmentCount, const std::string& imagePath)
{
    auto textureCache = Director::getInstance()->getTextureCache();
    auto texture = textureCache->addImage(imagePath);
    return initWithTexture(radius, segmentCount, texture);
}

bool TexturedRing::initWithTexture(float radius, unsigned int segmentCount, Texture2D* texture)
{
    if(texture == nullptr)
    {
        CCLOGERROR("[TexturedRing] Invalid texture");
        return false;
    }
    
    setAnchorPoint(Vec2(0.5f,0.5f));
    setTexture(texture);
    if(buildGeometry(radius, segmentCount) == false)
    {
        return false;
    }
    
    // configure texture rendering
    if (! texture || ! texture->hasPremultipliedAlpha())
    {
        m_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
        setOpacityModifyRGB(false);
    }
    else
    {
        m_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
        setOpacityModifyRGB(true);
    }
    
    //set default shader
    setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE));
    
    return true;
}

bool TexturedRing::buildGeometry(float radius, unsigned int segmentCount)
{
    if(radius <= 0)
    {
        CCLOGERROR("[TexturedRing] Invalid radius: (%f)", radius);
        return false;
    }
    
    if(segmentCount < 4)
    {
        CCLOGERROR("[TexturedRing] Invalid segment count: (%d), must be 4 or more", segmentCount);
        return false;
    }
    
    auto textureSize = m_texture->getContentSize() * m_strokeScale;
    
    //calculate radii
    auto strokeHalfWidth = textureSize.height * 0.5f;
    auto innerRadius = radius - strokeHalfWidth;
    auto outerRadius = radius + strokeHalfWidth;
    
    //calulate best fitting texture repeat count
    auto circumference = 2.0f*M_PI*radius;
    auto textureRepeatCount = 1;
    if(m_isTextureRepeated)
    {
        textureRepeatCount = MAX(1.0f, roundf((circumference / textureSize.width)));
    }
    
    //build buffers interleaved between inner and outer circles
    size_t vertCount = (segmentCount+1) * 2;
    m_vertices.resize(vertCount);
    m_texCoords.resize(vertCount);
    for(size_t i = 0; i < segmentCount + 1; i++)
    {
        size_t idx = i * 2;
        float tVal = (float)i / segmentCount;
        float angleRadians = 2.0f*M_PI*tVal;
        Vec2 dir = Vec2::forAngle(angleRadians);
        float u = tVal * textureRepeatCount;
        
        //inner
        m_texCoords[idx].u = u;
        m_texCoords[idx].v = 0;
        m_vertices[idx].x = outerRadius + dir.x * innerRadius;
        m_vertices[idx].y = outerRadius + dir.y * innerRadius;
        
        //outer
        m_texCoords[idx+1].u = u;
        m_texCoords[idx+1].v = 1;
        m_vertices[idx+1].x = outerRadius + dir.x * outerRadius;
        m_vertices[idx+1].y = outerRadius + dir.y * outerRadius;
    }
    
    setContentSize(Size(outerRadius*2,outerRadius*2));
    
    //cache configuration
    m_radius = radius;
    m_segmentCount = segmentCount;
    
    return true;
}

Texture2D* TexturedRing::getTexture() const
{
    return m_texture;
}

void TexturedRing::setTexture(Texture2D *texture)
{
    CC_SAFE_RELEASE_NULL(m_texture);
    if(texture != nullptr)
    {
        if(m_isTextureRepeated)
        {
            Texture2D::TexParams textureParams;
            textureParams.minFilter = GL_LINEAR;
            textureParams.magFilter = GL_LINEAR;
            textureParams.wrapS = GL_REPEAT; //default to horizontal wrapping
            textureParams.wrapT = GL_CLAMP_TO_EDGE;
            texture->setTexParameters(textureParams);
        }
        m_texture = texture;
        m_texture->retain();
        //rebuild the geometry to match new texture
        if(m_radius > 0 && m_segmentCount > 0)
        {
            buildGeometry(m_radius, m_segmentCount);
        }
    }
}

void TexturedRing::setBlendFunc(const BlendFunc &blendFunc)
{
    m_blendFunc = blendFunc;
}

const BlendFunc& TexturedRing::getBlendFunc() const
{
    return m_blendFunc;
}

void TexturedRing::setStrokeScale(const float &scale)
{
    if(scale != m_strokeScale)
    {
        m_strokeScale = scale;
        buildGeometry(m_radius, m_segmentCount);
    }
}

const float& TexturedRing::getStrokeScale() const
{
    return m_isTextureRepeated;
}

void TexturedRing::setIsTextureRepeated(const bool &isTextureRepeated)
{
    if(isTextureRepeated != m_isTextureRepeated)
    {
        m_isTextureRepeated = isTextureRepeated;
        setTexture(m_texture);
    }
}

const bool& TexturedRing::getIsTextureRepeated() const
{
    return m_isTextureRepeated;
}

void TexturedRing::onDraw(const Mat4 &transform, uint32_t flags)
{
    if(m_texture == nullptr || m_vertices.empty())
    {
        return;
    }
    
    getGLProgram()->use();
    getGLProgram()->setUniformsForBuiltins(transform);
    
    GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD);
    GL::blendFunc( m_blendFunc.src, m_blendFunc.dst );
    
    GL::bindTexture2D( m_texture->getName() );
    
#ifdef EMSCRIPTEN
    setGLBufferData(m_vertices, (sizeof(Vec2) * m_vertices.size()), 0);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, 0);
    
    setGLBufferData(_texCoords, (sizeof(Tex2F) * m_texCoords.size()), 1);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, 0);
#else
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, m_vertices.data());
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, m_texCoords.data());
#endif // EMSCRIPTEN
    
    glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)m_vertices.size());
    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, m_vertices.size());
}

void TexturedRing::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    _customCommand.init(_globalZOrder, transform, flags);
    _customCommand.func = CC_CALLBACK_0(TexturedRing::onDraw, this, transform, flags);
    renderer->addCommand(&_customCommand);
}
2 Likes

Updated to include a configuration to control whether the texture should repeat

1 Like

Hi, I cannot download the attached file. And the images are missing ! Can you re-upload this files ?

Thanks you !