mirror of
https://github.com/thes3m/XNI
synced 2024-12-26 13:26:06 +01:00
442 lines
15 KiB
Objective-C
442 lines
15 KiB
Objective-C
//
|
|
// SpriteBatch.m
|
|
// XNI
|
|
//
|
|
// Created by Matej Jan on 16.9.10.
|
|
// Copyright 2010 Retronator. All rights reserved.
|
|
//
|
|
|
|
#import "Retronator.Xni.Framework.h"
|
|
#import "Retronator.Xni.Framework.Graphics.h"
|
|
#import "SpriteBatch.h"
|
|
|
|
typedef struct {
|
|
float x;
|
|
float y;
|
|
float width;
|
|
float height;
|
|
} RectangleFStruct;
|
|
|
|
@interface XniSprite : NSObject
|
|
{
|
|
@public
|
|
Texture2D *texture;
|
|
Vector2Struct position;
|
|
Vector2Struct width;
|
|
Vector2Struct height;
|
|
float layerDepth;
|
|
RectangleFStruct source;
|
|
uint color;
|
|
}
|
|
|
|
@property (nonatomic, readonly) Texture2D *texture;
|
|
@property (nonatomic, readonly) float layerDepth;
|
|
@property (nonatomic, readonly) uint textureId;
|
|
|
|
@end
|
|
|
|
@implementation XniSprite
|
|
|
|
@synthesize texture;
|
|
@synthesize layerDepth;
|
|
- (uint) textureId {
|
|
return texture.textureId;
|
|
}
|
|
|
|
@end
|
|
|
|
Matrix *identity;
|
|
|
|
NSArray *textureSort;
|
|
NSArray *frontToBackSort;
|
|
NSArray *backToFrontSort;
|
|
|
|
static inline void SpriteSetSource(XniSprite *sprite, Rectangle *source, Texture2D *texture, SpriteEffects effects) {
|
|
if (source) {
|
|
sprite->source.x = (float)source.x / texture.width;
|
|
sprite->source.y = (float)source.y / texture.height;
|
|
sprite->source.width = (float)source.width / texture.width;
|
|
sprite->source.height = (float)source.height / texture.height;
|
|
} else {
|
|
sprite->source.width = 1;
|
|
sprite->source.height = 1;
|
|
}
|
|
|
|
if (effects & SpriteEffectsFlipHorizontally) {
|
|
sprite->source.x = sprite->source.x + sprite->source.width;
|
|
sprite->source.width = -sprite->source.width;
|
|
}
|
|
|
|
if (effects & SpriteEffectsFlipVertically) {
|
|
sprite->source.y = sprite->source.y + sprite->source.height;
|
|
sprite->source.height = -sprite->source.height;
|
|
}
|
|
}
|
|
|
|
static inline void SpriteSetVertices(XniSprite *sprite, float positionX, float positionY, float originX, float originY, float scaleX, float scaleY, float rotation, float width, float height) {
|
|
float x = originX * scaleX;
|
|
float y = -originY * scaleY;
|
|
float c = cos(rotation);
|
|
float s = sin(rotation);
|
|
sprite->position.x = positionX - x * c - y * s;
|
|
sprite->position.y = positionY - x * s + y * c;
|
|
sprite->width.x = width * scaleX * c;
|
|
sprite->width.y = width * scaleX * s;
|
|
sprite->height.x = -height * scaleY * s;
|
|
sprite->height.y = height * scaleY * c;
|
|
}
|
|
|
|
static inline void SpriteSetDestinationFast(XniSprite *sprite, Rectangle *destination) {
|
|
sprite->position.x = destination.x;
|
|
sprite->position.y = destination.y;
|
|
sprite->width.x = destination.width;
|
|
sprite->height.y = destination.height;
|
|
}
|
|
|
|
static inline void SpriteSetDestination(XniSprite *sprite, Rectangle *destination, float originX, float originY, float rotation) {
|
|
|
|
SpriteSetVertices(sprite, destination.x + originX, destination.y + originY, originX, originY, 1, 1, rotation, destination.width, destination.height);
|
|
}
|
|
|
|
static inline void SpriteSetPositionFast(XniSprite *sprite, Vector2 *position, float width, float height) {
|
|
sprite->position.x = position.x;
|
|
sprite->position.y = position.y;
|
|
sprite->width.x = width;
|
|
sprite->height.y = height;
|
|
}
|
|
|
|
static inline void SpriteSetPosition(XniSprite *sprite, Vector2 *position, float originX, float originY, float scaleX, float scaleY, float rotation, float width, float height) {
|
|
SpriteSetVertices(sprite, position.x, position.y, originX, originY, scaleX, scaleY, rotation, width, height);
|
|
}
|
|
|
|
@interface SpriteBatch()
|
|
|
|
- (void) setProjection;
|
|
- (void) apply;
|
|
- (void) draw:(XniSprite*)sprite;
|
|
- (void) draw;
|
|
- (void) drawFrom:(int)startIndex to:(int)endIndex;
|
|
|
|
@end
|
|
|
|
@implementation SpriteBatch
|
|
|
|
// Recyclable vertices
|
|
static VertexPositionColorTextureStruct vertices[4];
|
|
|
|
- (id) initWithGraphicsDevice:(GraphicsDevice *)theGraphicsDevice
|
|
{
|
|
self = [super initWithGraphicsDevice:theGraphicsDevice];
|
|
if (self != nil) {
|
|
basicEffect = [[BasicEffect alloc] initWithGraphicsDevice:theGraphicsDevice];
|
|
[self setProjection];
|
|
|
|
basicEffect.textureEnabled = YES;
|
|
basicEffect.vertexColorEnabled = YES;
|
|
|
|
sprites = [[NSMutableArray alloc] init];
|
|
vertexArray = [[VertexPositionColorTextureArray alloc] initWithInitialCapacity:256];
|
|
|
|
[theGraphicsDevice.deviceReset subscribeDelegate:[Delegate delegateWithTarget:self Method:@selector(setProjection)]];
|
|
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (void) initialize {
|
|
identity = [[Matrix identity] retain];
|
|
NSSortDescriptor *textureSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"textureId" ascending:YES] autorelease];
|
|
NSSortDescriptor *depthAscendingSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"layerDepth" ascending:YES] autorelease];
|
|
NSSortDescriptor *depthDescendingSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"layerDepth" ascending:NO] autorelease];
|
|
|
|
textureSort = [[NSArray arrayWithObject:textureSortDescriptor] retain];
|
|
|
|
// For better performance, depth sorting sorts by depth first and later also by texture.
|
|
frontToBackSort = [[NSArray arrayWithObjects:depthAscendingSortDescriptor, textureSortDescriptor, nil] retain];
|
|
backToFrontSort = [[NSArray arrayWithObjects:depthDescendingSortDescriptor, textureSortDescriptor, nil] retain];
|
|
}
|
|
|
|
- (void) setProjection {
|
|
basicEffect.projection = [Matrix createOrthographicOffCenterWithLeft:0
|
|
right:self.graphicsDevice.viewport.width
|
|
bottom:self.graphicsDevice.viewport.height
|
|
top:0
|
|
zNearPlane:0
|
|
zFarPlane:-1];
|
|
}
|
|
|
|
- (void) begin {
|
|
[self beginWithSortMode:SpriteSortModeDeffered BlendState:nil SamplerState:nil DepthStencilState:nil RasterizerState:nil Effect:nil TransformMatrix:nil];
|
|
}
|
|
|
|
- (void) beginWithSortMode:(SpriteSortMode)theSortMode
|
|
BlendState:(BlendState*)theBlendState {
|
|
[self beginWithSortMode:theSortMode BlendState:theBlendState SamplerState:nil DepthStencilState:nil RasterizerState:nil Effect:nil TransformMatrix:nil];
|
|
}
|
|
|
|
- (void) beginWithSortMode:(SpriteSortMode)theSortMode
|
|
BlendState:(BlendState*)theBlendState
|
|
SamplerState:(SamplerState*)theSamplerState
|
|
DepthStencilState:(DepthStencilState*)theDepthStencilState
|
|
RasterizerState:(RasterizerState*)theRasterizerState {
|
|
[self beginWithSortMode:theSortMode BlendState:theBlendState SamplerState:theSamplerState DepthStencilState:theDepthStencilState RasterizerState:theRasterizerState Effect:nil TransformMatrix:nil];
|
|
}
|
|
|
|
- (void) beginWithSortMode:(SpriteSortMode)theSortMode
|
|
BlendState:(BlendState*)theBlendState
|
|
SamplerState:(SamplerState*)theSamplerState
|
|
DepthStencilState:(DepthStencilState*)theDepthStencilState
|
|
RasterizerState:(RasterizerState*)theRasterizerState
|
|
Effect:(Effect*)theEffect {
|
|
[self beginWithSortMode:theSortMode BlendState:theBlendState SamplerState:theSamplerState DepthStencilState:theDepthStencilState RasterizerState:theRasterizerState Effect:theEffect TransformMatrix:nil];
|
|
}
|
|
|
|
- (void) beginWithSortMode:(SpriteSortMode)theSortMode
|
|
BlendState:(BlendState*)theBlendState
|
|
SamplerState:(SamplerState*)theSamplerState
|
|
DepthStencilState:(DepthStencilState*)theDepthStencilState
|
|
RasterizerState:(RasterizerState*)theRasterizerState
|
|
Effect:(Effect*)theEffect
|
|
TransformMatrix:(Matrix*)theTransformMatrix {
|
|
|
|
if (!theBlendState) theBlendState = [BlendState alphaBlend];
|
|
if (!theSamplerState) theSamplerState = [SamplerState linearClamp];
|
|
if (!theDepthStencilState) theDepthStencilState = [DepthStencilState none];
|
|
if (!theRasterizerState) theRasterizerState = [RasterizerState cullCounterClockwise];
|
|
if (!theEffect) theEffect = basicEffect;
|
|
if (!theTransformMatrix) theTransformMatrix = [Matrix identity];
|
|
|
|
sortMode = theSortMode;
|
|
blendState = theBlendState;
|
|
depthStencilState = theDepthStencilState;
|
|
rasterizerState = theRasterizerState;
|
|
samplerState = theSamplerState;
|
|
effect = theEffect;
|
|
|
|
if ([effect isKindOfClass:[BasicEffect class]]) {
|
|
((BasicEffect*)effect).world = theTransformMatrix;
|
|
}
|
|
|
|
// Immediate mode applies the device state during begin.
|
|
if (sortMode == SpriteSortModeImmediate) {
|
|
[self apply];
|
|
}
|
|
|
|
beginCalled = YES;
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture toRectangle:(Rectangle*)destinationRectangle tintWithColor:(Color*)color {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetDestinationFast(sprite, destinationRectangle);
|
|
SpriteSetSource(sprite, nil, texture, SpriteEffectsNone);
|
|
sprite->color = color.packedValue;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture toRectangle:(Rectangle*)destinationRectangle fromRectangle:(Rectangle*)sourceRectangle tintWithColor:(Color*)color {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetDestinationFast(sprite, destinationRectangle);
|
|
SpriteSetSource(sprite, sourceRectangle, texture, SpriteEffectsNone);
|
|
sprite->color = color.packedValue;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture toRectangle:(Rectangle*)destinationRectangle fromRectangle:(Rectangle*)sourceRectangle tintWithColor:(Color*)color
|
|
rotation:(float)rotation origin:(Vector2*)origin effects:(SpriteEffects)effects layerDepth:(float)layerDepth {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetDestination(sprite, destinationRectangle, origin.x, origin.y, rotation);
|
|
SpriteSetSource(sprite, sourceRectangle, texture, effects);
|
|
sprite->color = color.packedValue;
|
|
sprite->layerDepth = layerDepth;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture to:(Vector2*)position tintWithColor:(Color*)color {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetPositionFast(sprite, position, texture.width, texture.height);
|
|
SpriteSetSource(sprite, nil, texture, SpriteEffectsNone);
|
|
sprite->color = color.packedValue;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture to:(Vector2*)position fromRectangle:(Rectangle*)sourceRectangle tintWithColor:(Color*)color {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetPositionFast(sprite, position, sourceRectangle ? sourceRectangle.width : texture.width, sourceRectangle ? sourceRectangle.height : texture.height);
|
|
SpriteSetSource(sprite, sourceRectangle, texture, SpriteEffectsNone);
|
|
sprite->color = color.packedValue;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture to:(Vector2*)position fromRectangle:(Rectangle*)sourceRectangle tintWithColor:(Color*)color
|
|
rotation:(float)rotation origin:(Vector2*)origin scaleUniform:(float)scale effects:(SpriteEffects)effects layerDepth:(float)layerDepth {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetPosition(sprite, position, origin.x, origin.y, scale, scale, rotation, sourceRectangle ? sourceRectangle.width : texture.width, sourceRectangle ? sourceRectangle.height : texture.height);
|
|
SpriteSetSource(sprite, sourceRectangle, texture, effects);
|
|
sprite->color = color.packedValue;
|
|
sprite->layerDepth = layerDepth;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(Texture2D*)texture to:(Vector2*)position fromRectangle:(Rectangle*)sourceRectangle tintWithColor:(Color*)color
|
|
rotation:(float)rotation origin:(Vector2*)origin scale:(Vector2*)scale effects:(SpriteEffects)effects layerDepth:(float)layerDepth {
|
|
XniSprite *sprite = [[[XniSprite alloc] init] autorelease];
|
|
sprite->texture = texture;
|
|
SpriteSetPosition(sprite, position, origin.x, origin.y, scale.x, scale.y, rotation, sourceRectangle ? sourceRectangle.width : texture.width, sourceRectangle ? sourceRectangle.height : texture.height);
|
|
SpriteSetSource(sprite, sourceRectangle, texture, effects);
|
|
sprite->color = color.packedValue;
|
|
sprite->layerDepth = layerDepth;
|
|
[self draw:sprite];
|
|
}
|
|
|
|
- (void) draw:(XniSprite *)sprite {
|
|
[sprites addObject:sprite];
|
|
|
|
if (sortMode == SpriteSortModeImmediate) {
|
|
[self draw];
|
|
[sprites removeAllObjects];
|
|
}
|
|
}
|
|
|
|
- (void) end {
|
|
if (!beginCalled) {
|
|
[NSException raise:@"InvalidOperationException" format:@"End was called before begin."];
|
|
}
|
|
|
|
switch (sortMode) {
|
|
case SpriteSortModeImmediate:
|
|
// We've already done all the work.
|
|
beginCalled = NO;
|
|
return;
|
|
case SpriteSortModeTexture:
|
|
[sprites sortUsingDescriptors:textureSort];
|
|
break;
|
|
case SpriteSortModeBackToFront:
|
|
[sprites sortUsingDescriptors:backToFrontSort];
|
|
break;
|
|
case SpriteSortModeFrontToBack:
|
|
[sprites sortUsingDescriptors:frontToBackSort];
|
|
break;
|
|
}
|
|
|
|
// Apply the graphics device states.
|
|
[self apply];
|
|
|
|
// Render the whole array of sprites.
|
|
[self draw];
|
|
|
|
// Clean up.
|
|
[sprites removeAllObjects];
|
|
beginCalled = NO;
|
|
}
|
|
|
|
- (void) apply {
|
|
graphicsDevice.blendState = blendState;
|
|
graphicsDevice.depthStencilState = depthStencilState;
|
|
graphicsDevice.rasterizerState = rasterizerState;
|
|
[graphicsDevice.samplerStates setItem:samplerState atIndex:0];
|
|
[[effect.currentTechnique.passes objectAtIndex:0] apply];
|
|
}
|
|
|
|
- (void) draw {
|
|
// Check how many sprites to draw.
|
|
int count = [sprites count];
|
|
if (count == 0) {
|
|
// No sprites to draw.
|
|
return;
|
|
}
|
|
|
|
// Draw until all sprites are drawn.
|
|
int startIndex = 0;
|
|
int endIndex = 0;
|
|
|
|
while (startIndex < count) {
|
|
// Get the texture for the next batch.
|
|
Texture2D *currentTexture = ((XniSprite*)[sprites objectAtIndex:startIndex]).texture;
|
|
|
|
// Try to expend the end to include all sprites with the same texture.
|
|
if (count > 1) {
|
|
while (endIndex + 1 < count && ((XniSprite*)[sprites objectAtIndex:endIndex + 1]).texture == currentTexture) {
|
|
endIndex++;
|
|
}
|
|
}
|
|
|
|
// Draw sprites from start to end.
|
|
[self drawFrom:startIndex to:endIndex];
|
|
|
|
// Start a new batch.
|
|
startIndex = endIndex + 1;
|
|
endIndex = startIndex;
|
|
}
|
|
}
|
|
|
|
- (void) drawFrom:(int)startIndex to:(int)endIndex {
|
|
|
|
// Fill the vertex array
|
|
for (int i = startIndex; i <= endIndex; i++) {
|
|
XniSprite *sprite = [sprites objectAtIndex:i];
|
|
|
|
vertices[0].position.x = sprite->position.x;
|
|
vertices[0].position.y = sprite->position.y;
|
|
vertices[0].position.z = sprite->layerDepth;
|
|
|
|
vertices[1].position.x = vertices[0].position.x + sprite->height.x;
|
|
vertices[1].position.y = vertices[0].position.y + sprite->height.y;
|
|
vertices[1].position.z = sprite->layerDepth;
|
|
|
|
vertices[2].position.x = vertices[0].position.x + sprite->width.x;
|
|
vertices[2].position.y = vertices[0].position.y + sprite->width.y;
|
|
vertices[2].position.z = sprite->layerDepth;
|
|
|
|
vertices[3].position.x = vertices[0].position.x + sprite->height.x + sprite->width.x;
|
|
vertices[3].position.y = vertices[0].position.y + sprite->height.y + sprite->width.y;
|
|
vertices[3].position.z = sprite->layerDepth;
|
|
|
|
vertices[0].texture.x = sprite->source.x;
|
|
vertices[1].texture.x = sprite->source.x;
|
|
vertices[2].texture.x = sprite->source.x + sprite->source.width;
|
|
vertices[3].texture.x = sprite->source.x + sprite->source.width;
|
|
|
|
vertices[0].texture.y = sprite->source.y;
|
|
vertices[1].texture.y = sprite->source.y + sprite->source.height;
|
|
vertices[2].texture.y = sprite->source.y;
|
|
vertices[3].texture.y = sprite->source.y + sprite->source.height;
|
|
|
|
vertices[0].color = sprite->color;
|
|
vertices[1].color = sprite->color;
|
|
vertices[2].color = sprite->color;
|
|
vertices[3].color = sprite->color;
|
|
|
|
[vertexArray addVertex:&vertices[0]];
|
|
[vertexArray addVertex:&vertices[1]];
|
|
[vertexArray addVertex:&vertices[2]];
|
|
[vertexArray addVertex:&vertices[2]];
|
|
[vertexArray addVertex:&vertices[1]];
|
|
[vertexArray addVertex:&vertices[3]];
|
|
}
|
|
|
|
[self.graphicsDevice.textures setItem:((XniSprite*)[sprites objectAtIndex:startIndex]).texture atIndex:0];
|
|
|
|
// Draw the vertex array.
|
|
int count = (endIndex - startIndex + 1) * 2;
|
|
[graphicsDevice drawUserPrimitivesOfType:PrimitiveTypeTriangleList vertices:vertexArray startingAt:0 count:count];
|
|
|
|
// Clean up.
|
|
[vertexArray clear];
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[sprites release];
|
|
[vertexArray release];
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
@end
|