diff --git a/GosuImpl/Graphics/Macro.hpp b/GosuImpl/Graphics/Macro.hpp index 0a9899093..4f852b25e 100644 --- a/GosuImpl/Graphics/Macro.hpp +++ b/GosuImpl/Graphics/Macro.hpp @@ -13,11 +13,123 @@ class Gosu::Macro : public Gosu::ImageData { + typedef double Float; + Graphics& graphics; VertexArrays vertexArrays; - int givenWidth, givenHeight; + int w, h; - void realDraw(double x1, double y1, double x2, double y3) const + Transform findTransformForTarget(Float x1, Float y1, Float x2, Float y2, Float x3, Float y3, Float x4, Float y4) const + { + // Transformation logic follows a discussion on the ImageMagick mailing + // list (on which ImageMagick's perspective_transform.pl is based). + + // To draw a macro at an arbitrary position, we solve the following system: + + // 0, 0, 1, 0, 0, 0, 0, 0 | x1 + // 0, 0, 0, 0, 0, 1, 0, 0 | y1 + // w, 0, 1, 0, 0, 0, -x2w, 0 | x2 + // 0, 0, 0, w, 0, 1, -y2w, 0 | y2 + // 0, h, 1, 0, 0, 0, 0, -x3h | x3 + // 0, 0, 0, 0, h, 1, 0, -y3h | y3 + // w, h, 1, 0, 0, 0, -x4w, -x4h | x4 + // 0, 0, 0, w, h, 1, -y4w, -y4h | y4 + + // Equivalent: + + // 0, 0, 1, 0, 0, 0, 0, 0 | x1 + // 0, 0, 0, 0, 0, 1, 0, 0 | y1 + // w, 0, 0, 0, 0, 0, -x2w, 0 | x2-x1 + // 0, 0, 0, w, 0, 0, -y2w, 0 | y2-y1 + // 0, h, 0, 0, 0, 0, 0, -x3h | x3-x1 + // 0, 0, 0, 0, h, 0, 0, -y3h | y3-y1 + // 0, 0, 0, 0, 0, 0, (x2-x4)w, (x3-x4)h | x1-x2-x3+x4 + // 0, 0, 0, 0, 0, 0, (y2-y4)w, (y3-y4)h | y1-y2-y3+y4 + + // Since this matrix is relatively sparse, we unroll all three solving paths. + + static const Transform nullTransform = { 0 }; + + // Row 7 is useless + if (x2 == x4 && x3 == x4) + return nullTransform; + // Row 8 is useless + if (y2 == y3 && y3 == y4) + return nullTransform; + + Float c[8]; + + // Rows 1, 2 + c[2] = x1, c[5] = y1; + + // Use row 7 to eliminate the left cell in row 8 + // Row8 = Row8 - factor78 * Row7 + assert (x2 != x4); + Float factor78 = (y2-y4) / (x2-x4); + Float remCell8 = (y3-y4)*h - (x3-x4)*h * factor78; + Float rightSide8 = (y1-y2-y3+y4) - (x1-x2-x3+x4) * factor78; + assert (remCell8 != 0); + c[7] = rightSide8 / remCell8; + + // 0, 0, 1, 0, 0, 0, 0, 0 | x1 + // 0, 0, 0, 0, 0, 1, 0, 0 | y1 + // w, 0, 0, 0, 0, 0, -x2w, 0 | x2-x1 + // 0, 0, 0, w, 0, 0, -y2w, 0 | y2-y1 + // 0, h, 0, 0, 0, 0, 0, -x3h | x3-x1 + // 0, 0, 0, 0, h, 0, 0, -y3h | y3-y1 + // 0, 0, 0, 0, 0, 0, (x2-x4)w, (x3-x4)h | x1-x2-x3+x4 + // 0, 0, 0, 0, 0, 0, 0, remCell8 | rightSide8 + + // Use the remainding value in row 8 to eliminate the right value in row 7. + // Row7 = Row7 - factor87 * Row8 + assert (remCell8 != 0); + Float factor87 = (x3-x4)*h / remCell8; + Float remCell7 = (x2-x4)*w; + Float rightSide7 = (x1-x2-x3+x4) - rightSide8 * factor87; + assert (remCell7 != 0); + c[6] = rightSide7 / remCell7; + + // 0, 0, 1, 0, 0, 0, 0, 0 | x1 + // 0, 0, 0, 0, 0, 1, 0, 0 | y1 + // w, 0, 0, 0, 0, 0, -x2w, 0 | x2-x1 + // 0, 0, 0, w, 0, 0, -y2w, 0 | y2-y1 + // 0, h, 0, 0, 0, 0, 0, -x3h | x3-x1 + // 0, 0, 0, 0, h, 0, 0, -y3h | y3-y1 + // 0, 0, 0, 0, 0, 0, remCell7, 0 | rightSide7 + // 0, 0, 0, 0, 0, 0, 0, remCell8 | rightSide8 + + // Use the new rows 7 and 8 to calculate c0, c1, c3 & c4. + // Row3 = Row3 - factor73 * Row7 + Float factor73 = -x2*w / remCell7; + Float remCell3 = w; + Float rightSide3 = (x2-x1) - rightSide7 * factor73; + c[0] = rightSide3 / remCell3; + // Row4 = Row4 - factor74 * Row7 + Float factor74 = -y2*w / remCell7; + Float remCell4 = w; + Float rightSide4 = (y2-y1) - rightSide7 * factor74; + c[3] = rightSide4 / remCell4; + // Row5 = Row5 - factor85 * Row7 + Float factor85 = -x3*h / remCell8; + Float remCell5 = h; + Float rightSide5 = (x3-x1) - rightSide8 * factor85; + c[1] = rightSide5 / remCell5; + // Row6 = Row6 - factor86 * Row8 + Float factor86 = -y3*h / remCell8; + Float remCell6 = h; + Float rightSide6 = (y3-y1) - rightSide8 * factor86; + c[4] = rightSide6 / remCell6; + + Transform result = { + c[0], c[3], 0, c[6], + c[1], c[4], 0, c[7], + 0, 0, 1, 0, + c[2], c[5], 0, 1 + }; + return result; + } + + void drawVertexArrays(Float x1, Float y1, Float x2, Float y2, Float x3, Float y3, Float x4, Float y4) const { // TODO: Macros should not be split up because they have different transforms! This is insane. // They should be premultiplied and have the same transform by definition. Then, the transformation @@ -27,14 +139,14 @@ class Gosu::Macro : public Gosu::ImageData glEnable(GL_BLEND); glMatrixMode(GL_MODELVIEW); + Transform transform = + findTransformForTarget(x1, y1, x2, y2, x3, y3, x4, y4); + for (VertexArrays::const_iterator it = vertexArrays.begin(), end = vertexArrays.end(); it != end; ++it) { glPushMatrix(); it->renderState.apply(); - - glTranslated(x1, y1, 0); - glScaled((x2 - x1) / width(), (y3 - y1) / height(), 1); - + glMultMatrixd(&transform[0]); glInterleavedArrays(GL_T2F_C4UB_V3F, 0, &it->vertices[0]); glDrawArrays(GL_QUADS, 0, it->vertices.size()); glPopMatrix(); @@ -44,19 +156,19 @@ class Gosu::Macro : public Gosu::ImageData public: Macro(Graphics& graphics, DrawOpQueue& queue, int width, int height) - : graphics(graphics), givenWidth(width), givenHeight(height) + : graphics(graphics), w(width), h(height) { queue.compileTo(vertexArrays); } int width() const { - return givenWidth; + return w; } int height() const { - return givenHeight; + return h; } void draw(double x1, double y1, Color c1, @@ -65,11 +177,9 @@ class Gosu::Macro : public Gosu::ImageData double x4, double y4, Color c4, ZPos z, AlphaMode mode) const { - if (x1 != x3 || x2 != x4 || y1 != y2 || y3 != y4) - throw std::invalid_argument("Macros cannot be rotated yet"); if (c1 != 0xffffffff || c2 != 0xffffffff || c3 != 0xffffffff || c4 != 0xffffffff) throw std::invalid_argument("Macros cannot be tinted with colors yet"); - std::tr1::function f = std::tr1::bind(&Macro::realDraw, this, x1, y1, x2, y3); + std::tr1::function f = std::tr1::bind(&Macro::drawVertexArrays, this, x1, y1, x2, y2, x3, y3, x4, y4); graphics.scheduleGL(f, z); } diff --git a/feature_tests/record_draw_as_quad.rb b/feature_tests/record_draw_as_quad.rb new file mode 100644 index 000000000..42de3f501 --- /dev/null +++ b/feature_tests/record_draw_as_quad.rb @@ -0,0 +1,32 @@ +$LOAD_PATH << '../lib' +require 'gosu/preview' + +DEST = [[12, 295], [300, 265], [23, 42], [101, 11]] + +class FindMatrix < Gosu::Window + def initialize + super 500, 300 + + self.caption = "Macro draw_as_quad - hold to to see Image draw_as_quad" + + @image = Gosu::Image.new('/Users/jlnr/Pictures/Internetz/sadsadrobot.jpg') + @macro = record(@image.width, @image.height) { @image.draw 0, 0, 0 } + end + + def draw + Gosu::draw_quad\ + 0, 0, Gosu::Color::WHITE, + width, 0, Gosu::Color::WHITE, + 0, height, Gosu::Color::WHITE, + width, height, Gosu::Color::WHITE, 0 + + args = DEST.map { |p| [p[0], p[1], Gosu::Color::WHITE] }.flatten + [0] + if button_down? Gosu::KbTab then + @image.draw_as_quad *args + else + @macro.draw_as_quad *args + end + end +end + +FindMatrix.new.show