Sprite: Implement wave effect (RGSS2)

This initial implementation emulates the way RMVX splits
the sprite into "chunks" of about 8 pixels, which it then
scrolls left/right on a vertical sine wave. It even
replicates the weird behavior when wave_amp < 0, namely
"shrinking" the src_rect horizontally.

As with bush_opacity, this effect in combination with
rotation will render differently from RMVX.
This commit is contained in:
Jonas Kulla 2014-02-03 15:32:50 +01:00
parent 42b10fd2ee
commit af9039f58d
6 changed files with 346 additions and 21 deletions

View File

@ -69,6 +69,39 @@ DEF_PROP_F(Sprite, Angle)
DEF_PROP_B(Sprite, Mirror)
#ifdef RGSS2
RB_METHOD(spriteWidth)
{
RB_UNUSED_PARAM;
Sprite *s = getPrivateData<Sprite>(self);
int value;
GUARD_EXC( value = s->getWidth(); )
return rb_fix_new(value);
}
RB_METHOD(spriteHeight)
{
RB_UNUSED_PARAM;
Sprite *s = getPrivateData<Sprite>(self);
int value;
GUARD_EXC( value = s->getHeight(); )
return rb_fix_new(value);
}
DEF_PROP_I(Sprite, WaveAmp)
DEF_PROP_I(Sprite, WaveLength)
DEF_PROP_I(Sprite, WaveSpeed)
DEF_PROP_F(Sprite, WavePhase)
#endif
void
spriteBindingInit()
{
@ -98,4 +131,14 @@ spriteBindingInit()
INIT_PROP_BIND( Sprite, BlendType, "blend_type" );
INIT_PROP_BIND( Sprite, Color, "color" );
INIT_PROP_BIND( Sprite, Tone, "tone" );
#ifdef RGSS2
_rb_define_method(klass, "width", spriteWidth);
_rb_define_method(klass, "height", spriteHeight);
INIT_PROP_BIND( Sprite, WaveAmp, "wave_amp" );
INIT_PROP_BIND( Sprite, WaveLength, "wave_length" );
INIT_PROP_BIND( Sprite, WaveSpeed, "wave_speed" );
INIT_PROP_BIND( Sprite, WavePhase, "wave_phase" );
#endif
}

View File

@ -68,6 +68,35 @@ DEF_PROP_F(Sprite, Angle)
DEF_PROP_B(Sprite, Mirror)
#ifdef RGSS2
MRB_METHOD(spriteWidth)
{
Sprite *s = getPrivateData<Sprite>(mrb, self);
int value;
GUARD_EXC( value = s->getWidth(); )
return mrb_fixnum_value(value);
}
MRB_METHOD(spriteHeight)
{
Sprite *s = getPrivateData<Sprite>(mrb, self);
int value;
GUARD_EXC( value = s->getHeight(); )
return mrb_fixnum_value(value);
}
DEF_PROP_I(Sprite, WaveAmp)
DEF_PROP_I(Sprite, WaveLength)
DEF_PROP_I(Sprite, WaveSpeed)
DEF_PROP_F(Sprite, WavePhase)
#endif
void
spriteBindingInit(mrb_state *mrb)
{
@ -95,5 +124,15 @@ spriteBindingInit(mrb_state *mrb)
INIT_PROP_BIND( Sprite, Color, "color" );
INIT_PROP_BIND( Sprite, Tone, "tone" );
#ifdef RGSS2
mrb_define_method(mrb, klass, "width", spriteWidth, MRB_ARGS_NONE());
mrb_define_method(mrb, klass, "height", spriteHeight, MRB_ARGS_NONE());
INIT_PROP_BIND( Sprite, WaveAmp, "wave_amp" );
INIT_PROP_BIND( Sprite, WaveLength, "wave_length" );
INIT_PROP_BIND( Sprite, WaveSpeed, "wave_speed" );
INIT_PROP_BIND( Sprite, WavePhase, "wave_phase" );
#endif
mrb_define_method(mrb, klass, "inspect", inspectObject, MRB_ARGS_NONE());
}

View File

@ -55,7 +55,7 @@ public:
flashAlpha = flashColor.w;
}
void update()
virtual void update()
{
if (!flashing)
return;

View File

@ -34,17 +34,41 @@
typedef uint32_t index_t;
#define _GL_INDEX_TYPE GL_UNSIGNED_INT
struct ColorQuadArray
/* A small hack to get mutable QuadArray constructors */
inline void initBufferBindings(Vertex *)
{
std::vector<Vertex> vertices;
glEnableVertexAttribArray(Shader::Color);
glEnableVertexAttribArray(Shader::Position);
glEnableVertexAttribArray(Shader::TexCoord);
glVertexAttribPointer(Shader::Color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), Vertex::colorOffset());
glVertexAttribPointer(Shader::Position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), Vertex::posOffset());
glVertexAttribPointer(Shader::TexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), Vertex::texPosOffset());
}
inline void initBufferBindings(SVertex *)
{
glEnableVertexAttribArray(Shader::Position);
glEnableVertexAttribArray(Shader::TexCoord);
glVertexAttribPointer(Shader::Position, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), SVertex::posOffset());
glVertexAttribPointer(Shader::TexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), SVertex::texPosOffset());
}
template<class VertexType>
struct QuadArray
{
std::vector<VertexType> vertices;
VBO::ID vbo;
VAO::ID vao;
int quadCount;
GLsizeiptr vboSize;
ColorQuadArray()
: quadCount(0)
QuadArray()
: quadCount(0),
vboSize(-1)
{
vbo = VBO::gen();
vao = VAO::gen();
@ -53,20 +77,16 @@ struct ColorQuadArray
VBO::bind(vbo);
shState->bindQuadIBO();
glEnableVertexAttribArray(Shader::Color);
glEnableVertexAttribArray(Shader::Position);
glEnableVertexAttribArray(Shader::TexCoord);
glVertexAttribPointer(Shader::Color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), Vertex::colorOffset());
glVertexAttribPointer(Shader::Position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), Vertex::posOffset());
glVertexAttribPointer(Shader::TexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), Vertex::texPosOffset());
/* Call correct implementation here via overloading */
VertexType *dummy = 0;
initBufferBindings(dummy);
VAO::unbind();
IBO::unbind();
VBO::unbind();
}
~ColorQuadArray()
~QuadArray()
{
VBO::del(vbo);
VAO::del(vao);
@ -78,16 +98,37 @@ struct ColorQuadArray
quadCount = size;
}
void clear()
{
vertices.clear();
quadCount = 0;
}
/* This needs to be called after the final 'append()' call
* and previous to the first 'draw()' call. */
void commit()
{
VBO::bind(vbo);
VBO::uploadData(vertices.size() * sizeof(Vertex), &vertices[0], GL_DYNAMIC_DRAW);
VBO::unbind();
GLsizeiptr size = vertices.size() * sizeof(VertexType);
if (size > vboSize)
{
/* New data exceeds already allocated size.
* Reallocate VBO. */
VBO::uploadData(size, &vertices[0], GL_DYNAMIC_DRAW);
vboSize = size;
shState->ensureQuadIBO(quadCount);
}
else
{
/* New data fits in allocated size */
VBO::uploadSubData(0, size, &vertices[0]);
}
VBO::unbind();
}
void draw(size_t offset, size_t count)
{
@ -110,4 +151,7 @@ struct ColorQuadArray
}
};
typedef QuadArray<Vertex> ColorQuadArray;
typedef QuadArray<SVertex> SimpleQuadArray;
#endif // QUADARRAY_H

View File

@ -32,6 +32,9 @@
#include "transform.h"
#include "shader.h"
#include "glstate.h"
#include "quadarray.h"
#include <math.h>
#include <SDL_rect.h>
@ -63,6 +66,22 @@ struct SpritePrivate
Color *color;
Tone *tone;
#ifdef RGSS2
struct
{
int amp;
int length;
int speed;
float phase;
/* Wave effect is active (amp != 0) */
bool active;
/* qArray needs updating */
bool dirty;
SimpleQuadArray qArray;
} wave;
#endif
EtcTemps tmp;
sigc::connection prepareCon;
@ -87,6 +106,13 @@ struct SpritePrivate
prepareCon = shState->prepareDraw.connect
(sigc::mem_fun(this, &SpritePrivate::prepare));
#ifdef RGSS2
wave.amp = 0;
wave.length = 180;
wave.speed = 360;
wave.phase = 0.0;
#endif
}
~SpritePrivate()
@ -117,6 +143,10 @@ struct SpritePrivate
quad.setPosRect(IntRect(0, 0, srcRect->width, srcRect->height));
recomputeBushDepth();
#ifdef RGSS2
wave.dirty = true;
#endif
}
void updateSrcRectCon()
@ -141,6 +171,16 @@ struct SpritePrivate
if (!opacity)
return;
#ifdef RGSS2
if (wave.active)
{
/* Don't do expensive wave bounding box
* calculations */
isVisible = true;
return;
}
#endif
/* Compare sprite bounding box against the scene */
/* If sprite is zoomed/rotated, just opt out for now
@ -161,8 +201,102 @@ struct SpritePrivate
isVisible = SDL_HasIntersection(&self, &sceneRect);
}
#ifdef RGSS2
void emitWaveChunk(SVertex *&vert, float phase, int width,
float zoomY, int chunkY, int chunkLength)
{
float wavePos = phase + (chunkY / (float) wave.length) * M_PI * 2;
float chunkX = sin(wavePos) * wave.amp;
FloatRect tex(0, chunkY / zoomY, width, chunkLength / zoomY);
FloatRect pos = tex;
pos.x = chunkX;
Quad::setTexPosRect(vert, tex, pos);
vert += 4;
}
void updateWave()
{
if (!bitmap)
return;
if (wave.amp == 0)
{
wave.active = false;
return;
}
wave.active = true;
int width = srcRect->width;
int height = srcRect->height;
float zoomY = trans.getScale().y;
if (wave.amp < -(width / 2))
{
wave.qArray.resize(0);
wave.qArray.commit();
return;
}
/* RMVX does this, and I have no fucking clue why */
if (wave.amp < 0)
{
wave.qArray.resize(1);
int x = -wave.amp;
int w = width - x * 2;
FloatRect tex(x, srcRect->y, w, srcRect->height);
Quad::setTexPosRect(&wave.qArray.vertices[0], tex, tex);
wave.qArray.commit();
return;
}
/* The length of the sprite as it appears on screen */
int visibleLength = height * zoomY;
/* First chunk length (aligned to 8 pixel boundary */
int firstLength = ((int) trans.getPosition().y) % 8;
/* Amount of full 8 pixel chunks in the middle */
int chunks = (visibleLength - firstLength) / 8;
/* Final chunk length */
int lastLength = (visibleLength - firstLength) % 8;
wave.qArray.resize(!!firstLength + chunks + !!lastLength);
SVertex *vert = &wave.qArray.vertices[0];
float phase = (wave.phase * M_PI) / 180.f;
if (firstLength > 0)
emitWaveChunk(vert, phase, width, zoomY, 0, firstLength);
for (int i = 0; i < chunks; ++i)
emitWaveChunk(vert, phase, width, zoomY, firstLength + i * 8, 8);
if (lastLength > 0)
emitWaveChunk(vert, phase, width, zoomY, firstLength + chunks * 8, lastLength);
wave.qArray.commit();
}
#endif
void prepare()
{
#ifdef RGSS2
if (wave.dirty)
{
updateWave();
wave.dirty = false;
}
#endif
updateVisibility();
}
};
@ -193,14 +327,21 @@ DEF_ATTR_RD_SIMPLE(Sprite, Angle, float, p->trans.getRotation())
DEF_ATTR_RD_SIMPLE(Sprite, Mirror, bool, p->mirrored)
DEF_ATTR_RD_SIMPLE(Sprite, BushDepth, int, p->bushDepth)
DEF_ATTR_RD_SIMPLE(Sprite, BlendType, int, p->blendType)
DEF_ATTR_RD_SIMPLE(Sprite, Width, int, p->srcRect->width)
DEF_ATTR_RD_SIMPLE(Sprite, Height, int, p->srcRect->height)
DEF_ATTR_SIMPLE(Sprite, BushOpacity, int, p->bushOpacity)
DEF_ATTR_SIMPLE(Sprite, Opacity, int, p->opacity)
DEF_ATTR_SIMPLE(Sprite, Color, Color*, p->color)
DEF_ATTR_SIMPLE(Sprite, Tone, Tone*, p->tone)
#ifdef RGSS2
DEF_ATTR_RD_SIMPLE(Sprite, Width, int, p->srcRect->width)
DEF_ATTR_RD_SIMPLE(Sprite, Height, int, p->srcRect->height)
DEF_ATTR_RD_SIMPLE(Sprite, WaveAmp, int, p->wave.amp)
DEF_ATTR_RD_SIMPLE(Sprite, WaveLength, int, p->wave.length)
DEF_ATTR_RD_SIMPLE(Sprite, WaveSpeed, int, p->wave.speed)
DEF_ATTR_RD_SIMPLE(Sprite, WavePhase, float, p->wave.phase)
#endif
void Sprite::setBitmap(Bitmap *bitmap)
{
GUARD_DISPOSED
@ -218,6 +359,10 @@ void Sprite::setBitmap(Bitmap *bitmap)
*p->srcRect = bitmap->rect();
p->onSrcRectChange();
p->quad.setPosRect(p->srcRect->toFloatRect());
#ifdef RGSS2
p->wave.dirty = true;
#endif
}
void Sprite::setSrcRect(Rect *rect)
@ -252,6 +397,10 @@ void Sprite::setY(int value)
return;
p->trans.setPosition(Vec2(getX(), value));
#ifdef RGSS2
p->wave.dirty = true;
#endif
}
void Sprite::setOX(int value)
@ -293,6 +442,10 @@ void Sprite::setZoomY(float value)
p->trans.setScale(Vec2(getZoomX(), value));
p->recomputeBushDepth();
#ifdef RGSS2
p->wave.dirty = true;
#endif
}
void Sprite::setAngle(float value)
@ -346,6 +499,36 @@ void Sprite::setBlendType(int type)
}
}
#ifdef RGSS2
#define DEF_WAVE_SETTER(Name, name, type) \
void Sprite::setWave##Name(type value) \
{ \
GUARD_DISPOSED; \
if (p->wave.name == value) \
return; \
p->wave.name = value; \
p->wave.dirty = true; \
}
DEF_WAVE_SETTER(Amp, amp, int)
DEF_WAVE_SETTER(Length, length, int)
DEF_WAVE_SETTER(Speed, speed, int)
DEF_WAVE_SETTER(Phase, phase, float)
#undef DEF_WAVE_SETTER
/* Flashable */
void Sprite::update()
{
Flashable::update();
p->wave.phase += p->wave.speed / 180;
p->wave.dirty = true;
}
#endif
/* Disposable */
void Sprite::releaseResources()
{
@ -407,7 +590,14 @@ void Sprite::draw()
p->bitmap->bindTex(*base);
#ifdef RGSS2
if (p->wave.active)
p->wave.qArray.draw();
else
p->quad.draw();
#else
p->quad.draw();
#endif
glState.blendMode.pop();
}

View File

@ -41,9 +41,6 @@ public:
Sprite(Viewport *viewport = 0);
~Sprite();
int getWidth() const;
int getHeight() const;
DECL_ATTR( Bitmap, Bitmap* )
DECL_ATTR( SrcRect, Rect* )
DECL_ATTR( X, int )
@ -61,6 +58,18 @@ public:
DECL_ATTR( Color, Color* )
DECL_ATTR( Tone, Tone* )
#ifdef RGSS2
int getWidth() const;
int getHeight() const;
DECL_ATTR( WaveAmp, int )
DECL_ATTR( WaveLength, int )
DECL_ATTR( WaveSpeed, int )
DECL_ATTR( WavePhase, float )
void update();
#endif
private:
SpritePrivate *p;