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);
}