Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//
// SXBezierCurve.h
// Klee
//
// Created by Alessandro Maroso on 04/11/14.
// Copyright (c) 2014 waresme. All rights reserved.
//
// For informations about bezier curves and how to configure them with control points: http://pomax.github.io/bezierinfo/
//
#import <Sparrow/Sparrow.h>
@class SPBaseEffect;
@class SPVertexData;
typedef enum
{
SXBezierCurveTypeLinear,
SXBezierCurveTypeQuadratic,
SXBezierCurveTypeCubic
} SXBezierCurveType;
@interface SXBezierCurve : SPDisplayObject
{
SPBaseEffect *_baseEffect;
SPVertexData *_vertexData;
uint _vertexBufferName;
BOOL _updateRequired;
}
/// Bezier curve type
@property (nonatomic, assign) SXBezierCurveType curveType;
/// Starting point
@property (nonatomic, strong) SPPoint *startPoint;
/// Ending point
@property (nonatomic, strong) SPPoint *endPoint;
/// First control point
@property (nonatomic, strong) SPPoint *controlPoint1;
/// Second control point
@property (nonatomic, strong) SPPoint *controlPoint2;
/// Color of the line
@property (nonatomic, assign) uint color;
/// Thickness of the line in pixels
@property (nonatomic, assign) float thickness;
/// Number of segments that are composing the cruved line (default = 100)
@property (nonatomic, assign) uint segmentsNumber;
/// The first and last control points are always the end points of the curve; however, the intermediate control points (if any) generally do not lie on the curve. Default cubic.
- (SXBezierCurve *)initWithStartPoint:(SPPoint*)start endPoint:(SPPoint*)end controlPoint1:(SPPoint*)c1 controlPoint2:(SPPoint*)c2;
/// The point at t of the Bézier curve (x component)
- (float)bezierX:(float)t;
/// The point at t of the Bézier curve (y component)
- (float)bezierY:(float)t;
/// The point at t of the Bézier curve
- (SPPoint*)bezier:(float)t;
/// The point at t of the derivative of the Bézier curve with respect to t (x component)
- (float)bezierTangentX:(float)t;
/// The point at t of the derivative of the Bézier curve with respect to t (y component)
- (float)bezierTangentY:(float)t;
/// The point at t of the derivative of the Bézier curve with respect to t (x component)
- (SPPoint*)bezierTangent:(float)t;
@end
//
// SXBezierCurve.m
// Klee
//
// Created by Alessandro Maroso on 04/11/14.
// Copyright (c) 2014 waresme. All rights reserved.
//
// A Bezier curve is separable, that means that you can compute it one coordinate at a time (first x, then y, then z...).
//
#import "SXBezierCurve.h"
@implementation SXBezierCurve
- (SXBezierCurve *)initWithStartPoint:(SPPoint*)start endPoint:(SPPoint*)end controlPoint1:(SPPoint*)c1 controlPoint2:(SPPoint*)c2
{
if (self = [super init]) {
self.curveType = SXBezierCurveTypeCubic;
self.startPoint = start;
self.endPoint = end;
self.controlPoint1 = c1;
self.controlPoint2 = c2;
self.segmentsNumber = 100;
self.thickness = 1;
_baseEffect = [[SPBaseEffect alloc] init];
_vertexData = [[SPVertexData alloc] initWithSize:self.segmentsNumber premultipliedAlpha:YES];
for (int i = 0 ; i < _vertexData.numVertices; i++)
_vertexData.vertices[i].color = SPVertexColorMakeWithColorAndAlpha(SPColorRed, 1.0f);
[self setupVertices];
_updateRequired = YES;
}
return self;
}
#pragma mark - OpenGL methods
- (void)update
{
[self setupVertices];
[self syncBuffers];
_updateRequired = NO;
}
/// Setup vertex data to bind to opengl
- (void)setupVertices
{
// Set up vertex data positions basing on bezier function
for (int i = 0; i < _vertexData.numVertices; i++) {
[_vertexData setPositionWithX:[self bezierX:(float)i/(float)_vertexData.numVertices] y:[self bezierY:(float)i/(float)_vertexData.numVertices] atIndex:i];
}
}
- (float)bezierX:(float)t
{
float nt = 1.0f - t;
if (self.curveType == SXBezierCurveTypeCubic)
return self.startPoint.x * nt * nt * nt + 3.0 * self.controlPoint1.x * nt * nt * t + 3.0 * self.controlPoint2.x * nt * t * t + self.endPoint.x * t * t * t;
else if (self.curveType == SXBezierCurveTypeQuadratic)
return self.startPoint.x * nt * nt + 2.0 * self.controlPoint1.x * nt * t + self.endPoint.x * t * t;
else
return self.startPoint.x * nt + self.endPoint.x * t;
}
- (float)bezierY:(float)t
{
float nt = 1.0f - t;
if (self.curveType == SXBezierCurveTypeCubic)
return self.startPoint.y * nt * nt * nt + 3.0 * self.controlPoint1.y * nt * nt * t + 3.0 * self.controlPoint2.y * nt * t * t + self.endPoint.y * t * t * t;
else if (self.curveType == SXBezierCurveTypeQuadratic)
return self.startPoint.y * nt * nt + 2.0 * self.controlPoint1.y * nt * t + self.endPoint.y * t * t;
else
return self.startPoint.y * nt + self.endPoint.y * t;
}
- (SPPoint*)bezier:(float)t
{
return [SPPoint pointWithX:[self bezierX:t] y:[self bezierY:t]];
}
- (float)bezierTangentX:(float)t
{
float nt = 1.0f - t;
return -3.0 * self.startPoint.x * nt * nt + 3.0 * self.controlPoint1.x * (1.0 - 4.0 * t + 3.0 * t * t) + 3.0 * self.controlPoint2.x * (2.0 * t - 3.0 * t * t) + 3.0 * self.endPoint.x * t * t;
}
- (float)bezierTangentY:(float)t
{
float nt = 1.0f - t;
return -3.0 * self.startPoint.y * nt * nt + 3.0 * self.controlPoint1.y * (1.0 - 4.0 * t + 3.0 * t * t) + 3.0 * self.controlPoint2.y * (2.0 * t - 3.0 * t * t) + 3.0 * self.endPoint.y * t * t;
}
- (SPPoint*)bezierTangent:(float)t
{
return [SPPoint pointWithX:[self bezierTangentX:t] y:[self bezierTangentY:t]];
}
- (void)syncBuffers
{
if (!_vertexBufferName) {
[self createBuffers];
}
// Bind the buffer object array to the GL_ARRAY_BUFFER target buffer
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferName);
// Send the line data over to the target buffer in GPU RAM
glBufferData(GL_ARRAY_BUFFER, // the target buffer
sizeof(SPVertex) * _vertexData.numVertices, // the number of bytes to put into the buffer
_vertexData.vertices, // a pointer to the data being copied
GL_STATIC_DRAW); // the usage pattern of the data
}
- (void)createBuffers {
// First, we destory the existing buffers.
[self destroyBuffers];
// Have OpenGL generate a (1) buffer name and store it in the buffer object array (_vertexBufferName)
glGenBuffers(1, &_vertexBufferName);
_updateRequired = YES;
}
- (void)destroyBuffers {
if (_vertexBufferName) {
glDeleteBuffers(1, &_vertexBufferName);
_vertexBufferName = 0;
}
}
/// Renders the display object with the help of a support object.
- (void)render:(SPRenderSupport *)support
{
if (_updateRequired)
[self update];
[support finishQuadBatch]; // finish previously batched quads
[support addDrawCalls:1]; // update stats display
_baseEffect.mvpMatrix = support.mvpMatrix;
_baseEffect.alpha = support.alpha;
[_baseEffect prepareToDraw];
[SPBlendMode applyBlendFactorsForBlendMode:support.blendMode
premultipliedAlpha:_vertexData.premultipliedAlpha];
int attribPosition = _baseEffect.attribPosition;
int attribColor = _baseEffect.attribColor;
glEnableVertexAttribArray(attribPosition);
glEnableVertexAttribArray(attribColor);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferName);
glVertexAttribPointer(attribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(SPVertex), (void *)(offsetof(SPVertex, position)));
glVertexAttribPointer(attribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(SPVertex), (void *)(offsetof(SPVertex, color)));
glLineWidth(self.thickness);
glDrawArrays(GL_LINE_STRIP, 0, _vertexData.numVertices);
}
#pragma mark - Setters/Getters
- (uint)color {
return self.color;
}
- (void)setColor:(uint)color {
[_vertexData setColor:color];
_updateRequired = YES;
}
- (void)setStartPoint:(SPPoint *)startPoint
{
_startPoint = startPoint;
_updateRequired = YES;
}
- (void)setEndPoint:(SPPoint *)endPoint
{
_endPoint = endPoint;
_updateRequired = YES;
}
- (void)setControlPoint1:(SPPoint *)controlPoint1
{
_controlPoint1 = controlPoint1;
_updateRequired = YES;
}
- (void)setControlPoint2:(SPPoint *)controlPoint2
{
_controlPoint2 = controlPoint2;
_updateRequired = YES;
}
#pragma mark - Object methods
- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetSpace
{
if (_updateRequired)
[self update];
if (!targetSpace)
targetSpace = self;
SPMatrix *matrix = [self transformationMatrixToSpace:targetSpace];
return [_vertexData boundsAfterTransformation:matrix];
}
@end
//
// SXTexturedBezierCurve.h
// Klee
//
// Created by Alessandro Maroso on 10/11/14.
// Copyright (c) 2014 waresme. All rights reserved.
//
#import <Sparrow/Sparrow.h>
#import "SXBezierCurve.h"
@interface SXTexturedBezierCurve : SXBezierCurve
{
SPTexture *_texture;
uint _indexBufferName;
ushort *_indexData;
}
- (SXTexturedBezierCurve *)initWithStartPoint:(SPPoint*)start endPoint:(SPPoint*)end controlPoint1:(SPPoint*)c1 controlPoint2:(SPPoint*)c2 texture:(SPTexture*)texture;
@property (nonatomic, strong) SPTexture *texture;
@end
//
// SXTexturedBezierCurve.m
// Klee
//
// Created by Alessandro Maroso on 10/11/14.
// Copyright (c) 2014 waresme. All rights reserved.
//
#import "SXTexturedBezierCurve.h"
@implementation SXTexturedBezierCurve
- (SXTexturedBezierCurve *)initWithStartPoint:(SPPoint*)start endPoint:(SPPoint*)end controlPoint1:(SPPoint*)c1 controlPoint2:(SPPoint*)c2 texture:(SPTexture*)texture
{
if (self = [super init])
{
self.curveType = SXBezierCurveTypeCubic;
self.startPoint = start;
self.endPoint = end;
self.controlPoint1 = c1;
self.controlPoint2 = c2;
self.segmentsNumber = 100;
self.thickness = 10;
self.texture = texture;
self.texture.repeat = YES;
_baseEffect = [[SPBaseEffect alloc] init];
[self setupVertices];
_updateRequired = YES;
}
return self;
}
#pragma mark - OpenGL methods
- (void)update
{
[self setupVertices];
[self syncBuffers];
_updateRequired = NO;
}
/// Setup vertex data to bind to opengl
- (void)setupVertices
{
// Create vertex data object
_vertexData = [[SPVertexData alloc] initWithSize:self.segmentsNumber*2+2
premultipliedAlpha:YES];
for (int i = 0 ; i < _vertexData.numVertices; i++)
_vertexData.vertices[i].color = SPVertexColorMakeWithColorAndAlpha(SPColorWhite, 1.0f);
// Set up vertex data positions basing on bezier function at each point
SPPoint *bezierPoint, *previousBezierPoint, *perpDirection;
float currentLength = 0;
for (int i = 0; i <= self.segmentsNumber; i++)
{
// Calculate bezier point
if (bezierPoint)
previousBezierPoint = bezierPoint;
bezierPoint = [self bezier:(float)i/(float)self.segmentsNumber];
if (previousBezierPoint)
currentLength += [bezierPoint subtractPoint:previousBezierPoint].length;
// Find tangent at that point, get perpendicular vector, normalize it and scale it by thickness factor
perpDirection = [[[[self bezierTangent:(float)i/(float)self.segmentsNumber] perpendicular] normalize] scaleBy:self.thickness];
// Set vertices positions
[_vertexData setPosition:[bezierPoint subtractPoint:perpDirection]
atIndex:i*2];
[_vertexData setPosition:[bezierPoint addPoint:perpDirection]
atIndex:i*2+1];
// One texture for all the line
// Set up the texture coordinates of each vertex. Texture coordinates are in the range [0, 1].
[_vertexData setTexCoordsWithX:currentLength/self.texture.width
y:0
atIndex:i*2+1];
[_vertexData setTexCoordsWithX:currentLength/self.texture.width
y:1
atIndex:i*2];
}
[_texture adjustVertexData:_vertexData atIndex:0 numVertices:_vertexData.numVertices];
// Set up Index data for triangle strip drawing
int numIndices = self.segmentsNumber * 2 + 2;
if (!_indexData)
_indexData = malloc(sizeof(ushort) * numIndices);
else
_indexData = realloc(_indexData, sizeof(ushort) * numIndices);
// Vertices positions
// 1---3---5
// | \ | \ | (...)
// 0---2---4
for (int i = 0; i <= self.segmentsNumber; i++)
{
_indexData[i*2] = i*2;
_indexData[i*2+1] = i*2+1;
}
}
- (void)destroyBuffers
{
if (_vertexBufferName)
{
glDeleteBuffers(1, &_vertexBufferName);
_vertexBufferName = 0;
}
if (_indexBufferName)
{
glDeleteBuffers(1, &_indexBufferName);
_indexBufferName = 0;
}
}
- (void)createBuffers
{
// First, we destory the existing buffers.
[self destroyBuffers];
// Have OpenGL generate buffer names and store them in the buffer object array
glGenBuffers(1, &_vertexBufferName);
glGenBuffers(1, &_indexBufferName);
if (!_vertexBufferName || !_indexBufferName)
[NSException raise:SP_EXC_OPERATION_FAILED format:@"could not create vertex buffers"];
}
- (void)syncBuffers
{
if (!_vertexBufferName || !_indexBufferName)
[self createBuffers];
// Bind the buffer object array to the GL_ARRAY_BUFFER target buffer
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferName);
// Send the data over to the target buffer in GPU RAM
glBufferData(GL_ARRAY_BUFFER, sizeof(SPVertex) * _vertexData.numVertices, _vertexData.vertices, GL_DYNAMIC_DRAW);
int numIndices = self.segmentsNumber * 2 + 2;
// Bind the buffer object array to the GL_ELEMENT_ARRAY_BUFFER target buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferName);
// Send the data over to the target buffer in GPU RAM
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ushort) * numIndices, _indexData, GL_DYNAMIC_DRAW);
}
/// Renders the display object with the help of a support object.
- (void)render:(SPRenderSupport *)support
{
if (_updateRequired)
[self update];
[support finishQuadBatch]; // finish previously batched quads
[support addDrawCalls:1]; // update stats display
_baseEffect.mvpMatrix = support.mvpMatrix;
_baseEffect.alpha = support.alpha;
_baseEffect.texture = _texture;
[_baseEffect prepareToDraw];
[SPBlendMode applyBlendFactorsForBlendMode:support.blendMode
premultipliedAlpha:_vertexData.premultipliedAlpha];
// Vertex Shader program input attributes (create names-indices)
int attribPosition = _baseEffect.attribPosition;
int attribColor = _baseEffect.attribColor;
int attribTexCoords = _baseEffect.attribTexCoords;
glEnableVertexAttribArray(attribPosition);
glEnableVertexAttribArray(attribColor);
glEnableVertexAttribArray(attribTexCoords);
// Bind this object buffers object array to the opengl vertex and index buffers
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferName);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferName);
// Specify the location and data format of the arrays of vertex shader program input attributes to use when rendering
glVertexAttribPointer(attribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(SPVertex), (void *)(offsetof(SPVertex, position)));
glVertexAttribPointer(attribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(SPVertex), (void *)(offsetof(SPVertex, color)));
if (self.texture)
glVertexAttribPointer(attribTexCoords, 2, GL_FLOAT, GL_FALSE, sizeof(SPVertex), (void *)(offsetof(SPVertex, texCoords)));
int numIndices = self.segmentsNumber * 2 + 2;
glDrawElements(GL_TRIANGLE_STRIP, numIndices, GL_UNSIGNED_SHORT, 0);
}
#pragma mark - Setters/Getters
- (void)setTexture:(SPTexture *)value
{
if (value == nil)
{
[NSException raise:SP_EXC_INVALID_OPERATION format:@"texture cannot be nil!"];
}
else if (value != _texture)
{
_texture = value;
_texture.repeat = YES;
_updateRequired = YES;
[_vertexData setPremultipliedAlpha:_texture.premultipliedAlpha updateVertices:YES];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.