Implement Shape rendering and Transform math for SDL2 backend
Shape rendering now works: - Shape::draw() generates triangle vertices for fill and outline - RectangleShape, CircleShape, ConvexShape provide getPointCount()/getPoint() - Shapes render with correct fill color, outline color, and outline thickness Transform class fully implemented: - translate(), rotate(), scale() modify the 3x3 affine matrix - transformPoint() applies transform to Vector2f - operator* combines transforms - getInverse() computes inverse transform Transformable::getTransform() now computes proper transform from: - position, rotation, scale, and origin RenderStates now has transform, blendMode, shader members Canvas sizing fixed for Emscripten: - EM_ASM sets canvas size after SDL window creation - SDL_GL_MakeCurrent called after canvas resize Result: RectangleShape UI elements render in correct positions! Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c5cc022aa2
commit
a702d3cab4
2 changed files with 303 additions and 26 deletions
|
|
@ -370,9 +370,16 @@ void SDL2Renderer::setProjection(float left, float right, float bottom, float to
|
|||
projectionMatrix_[15] = 1.0f;
|
||||
}
|
||||
|
||||
static int clearCount = 0;
|
||||
void SDL2Renderer::clear(float r, float g, float b, float a) {
|
||||
glClearColor(r, g, b, a);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Debug: Log first few clears to confirm render loop is running
|
||||
if (clearCount < 5) {
|
||||
std::cout << "SDL2Renderer::clear(" << r << ", " << g << ", " << b << ", " << a << ") #" << clearCount << std::endl;
|
||||
clearCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL2Renderer::drawTriangles(const float* vertices, size_t vertexCount,
|
||||
|
|
@ -461,9 +468,14 @@ void RenderWindow::create(VideoMode mode, const std::string& title, uint32_t sty
|
|||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// For Emscripten, we need to set the canvas size explicitly
|
||||
// The canvas element with id="canvas" is used by default
|
||||
// For Emscripten, tell SDL2 which canvas element to use
|
||||
// SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR = "SDL_EMSCRIPTEN_CANVAS_SELECTOR"
|
||||
SDL_SetHint("SDL_EMSCRIPTEN_CANVAS_SELECTOR", "#canvas");
|
||||
|
||||
// Set the canvas size explicitly before creating the window
|
||||
emscripten_set_canvas_element_size("#canvas", mode.width, mode.height);
|
||||
|
||||
std::cout << "Emscripten: Setting canvas to " << mode.width << "x" << mode.height << std::endl;
|
||||
#endif
|
||||
|
||||
// Create window
|
||||
|
|
@ -493,6 +505,28 @@ void RenderWindow::create(VideoMode mode, const std::string& title, uint32_t sty
|
|||
title_ = title;
|
||||
open_ = true;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Force canvas size AFTER SDL window creation (SDL may have reset it)
|
||||
emscripten_set_canvas_element_size("#canvas", mode.width, mode.height);
|
||||
|
||||
// Also set the CSS size to match
|
||||
EM_ASM({
|
||||
var canvas = document.getElementById('canvas');
|
||||
if (canvas) {
|
||||
canvas.width = $0;
|
||||
canvas.height = $1;
|
||||
canvas.style.width = $0 + 'px';
|
||||
canvas.style.height = $1 + 'px';
|
||||
console.log('EM_ASM: Set canvas to ' + $0 + 'x' + $1);
|
||||
} else {
|
||||
console.error('EM_ASM: Canvas element not found!');
|
||||
}
|
||||
}, mode.width, mode.height);
|
||||
|
||||
// Re-make context current after canvas resize
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
#endif
|
||||
|
||||
// Initialize OpenGL resources now that we have a context
|
||||
if (!SDL2Renderer::getInstance().initGL()) {
|
||||
std::cerr << "RenderWindow: Failed to initialize OpenGL resources" << std::endl;
|
||||
|
|
@ -504,6 +538,13 @@ void RenderWindow::create(VideoMode mode, const std::string& title, uint32_t sty
|
|||
|
||||
// Set up OpenGL state
|
||||
glViewport(0, 0, mode.width, mode.height);
|
||||
std::cout << "GL viewport set to " << mode.width << "x" << mode.height << std::endl;
|
||||
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
std::cerr << "GL error after viewport: " << err << std::endl;
|
||||
}
|
||||
|
||||
SDL2Renderer::getInstance().setProjection(0, mode.width, mode.height, 0);
|
||||
|
||||
// Enable blending for transparency
|
||||
|
|
@ -513,9 +554,21 @@ void RenderWindow::create(VideoMode mode, const std::string& title, uint32_t sty
|
|||
// Initial clear to a visible color to confirm GL is working
|
||||
glClearColor(0.2f, 0.3f, 0.4f, 1.0f); // Blue-gray
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
std::cerr << "GL error after clear: " << err << std::endl;
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
std::cerr << "GL error after swap: " << err << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "RenderWindow: Created " << mode.width << "x" << mode.height << " window" << std::endl;
|
||||
std::cout << "WebGL context should now show blue-gray" << std::endl;
|
||||
}
|
||||
|
||||
void RenderWindow::close() {
|
||||
|
|
@ -1118,7 +1171,125 @@ bool Font::loadFromMemory(const void* data, size_t sizeInBytes) {
|
|||
// =============================================================================
|
||||
|
||||
void Shape::draw(RenderTarget& target, RenderStates states) const {
|
||||
// TODO: Generate vertices and draw using SDL2Renderer
|
||||
size_t pointCount = getPointCount();
|
||||
if (pointCount < 3) return;
|
||||
|
||||
// Get the combined transform
|
||||
Transform combinedTransform = states.transform * getTransform();
|
||||
|
||||
// Build vertex data for fill (triangle fan from center)
|
||||
std::vector<float> vertices;
|
||||
std::vector<float> colors;
|
||||
|
||||
// Calculate center point
|
||||
Vector2f center(0, 0);
|
||||
for (size_t i = 0; i < pointCount; ++i) {
|
||||
center.x += getPoint(i).x;
|
||||
center.y += getPoint(i).y;
|
||||
}
|
||||
center.x /= pointCount;
|
||||
center.y /= pointCount;
|
||||
|
||||
// Transform center
|
||||
Vector2f transformedCenter = combinedTransform.transformPoint(center);
|
||||
|
||||
// Build triangles (fan from center)
|
||||
Color fill = getFillColor();
|
||||
float fr = fill.r / 255.0f;
|
||||
float fg = fill.g / 255.0f;
|
||||
float fb = fill.b / 255.0f;
|
||||
float fa = fill.a / 255.0f;
|
||||
|
||||
for (size_t i = 0; i < pointCount; ++i) {
|
||||
size_t next = (i + 1) % pointCount;
|
||||
|
||||
Vector2f p1 = combinedTransform.transformPoint(getPoint(i));
|
||||
Vector2f p2 = combinedTransform.transformPoint(getPoint(next));
|
||||
|
||||
// Triangle: center, p1, p2
|
||||
vertices.push_back(transformedCenter.x);
|
||||
vertices.push_back(transformedCenter.y);
|
||||
vertices.push_back(p1.x);
|
||||
vertices.push_back(p1.y);
|
||||
vertices.push_back(p2.x);
|
||||
vertices.push_back(p2.y);
|
||||
|
||||
// Colors for each vertex
|
||||
for (int v = 0; v < 3; ++v) {
|
||||
colors.push_back(fr);
|
||||
colors.push_back(fg);
|
||||
colors.push_back(fb);
|
||||
colors.push_back(fa);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw fill
|
||||
if (fill.a > 0 && !vertices.empty()) {
|
||||
SDL2Renderer::getInstance().drawTriangles(
|
||||
vertices.data(), vertices.size() / 2,
|
||||
colors.data(), nullptr, 0
|
||||
);
|
||||
}
|
||||
|
||||
// Draw outline if thickness > 0
|
||||
float outlineThickness = getOutlineThickness();
|
||||
if (outlineThickness > 0) {
|
||||
Color outline = getOutlineColor();
|
||||
if (outline.a > 0) {
|
||||
float or_ = outline.r / 255.0f;
|
||||
float og = outline.g / 255.0f;
|
||||
float ob = outline.b / 255.0f;
|
||||
float oa = outline.a / 255.0f;
|
||||
|
||||
// Build outline as quads (two triangles per edge)
|
||||
vertices.clear();
|
||||
colors.clear();
|
||||
|
||||
for (size_t i = 0; i < pointCount; ++i) {
|
||||
size_t next = (i + 1) % pointCount;
|
||||
|
||||
Vector2f p1 = combinedTransform.transformPoint(getPoint(i));
|
||||
Vector2f p2 = combinedTransform.transformPoint(getPoint(next));
|
||||
|
||||
// Calculate normal direction
|
||||
Vector2f dir(p2.x - p1.x, p2.y - p1.y);
|
||||
float len = std::sqrt(dir.x * dir.x + dir.y * dir.y);
|
||||
if (len > 0) {
|
||||
dir.x /= len;
|
||||
dir.y /= len;
|
||||
}
|
||||
Vector2f normal(-dir.y * outlineThickness, dir.x * outlineThickness);
|
||||
|
||||
// Outer points
|
||||
Vector2f p1o(p1.x + normal.x, p1.y + normal.y);
|
||||
Vector2f p2o(p2.x + normal.x, p2.y + normal.y);
|
||||
|
||||
// Two triangles for quad
|
||||
// Triangle 1: p1, p2, p1o
|
||||
vertices.push_back(p1.x); vertices.push_back(p1.y);
|
||||
vertices.push_back(p2.x); vertices.push_back(p2.y);
|
||||
vertices.push_back(p1o.x); vertices.push_back(p1o.y);
|
||||
// Triangle 2: p2, p2o, p1o
|
||||
vertices.push_back(p2.x); vertices.push_back(p2.y);
|
||||
vertices.push_back(p2o.x); vertices.push_back(p2o.y);
|
||||
vertices.push_back(p1o.x); vertices.push_back(p1o.y);
|
||||
|
||||
for (int v = 0; v < 6; ++v) {
|
||||
colors.push_back(or_);
|
||||
colors.push_back(og);
|
||||
colors.push_back(ob);
|
||||
colors.push_back(oa);
|
||||
}
|
||||
}
|
||||
|
||||
if (!vertices.empty()) {
|
||||
SDL2Renderer::getInstance().drawTriangles(
|
||||
vertices.data(), vertices.size() / 2,
|
||||
colors.data(), nullptr, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VertexArray::draw(RenderTarget& target, RenderStates states) const {
|
||||
|
|
|
|||
|
|
@ -216,29 +216,101 @@ public:
|
|||
// For now, stub implementation matching headless
|
||||
|
||||
class Transform {
|
||||
// 3x3 matrix stored as 4x4 for OpenGL compatibility
|
||||
// [ m00 m01 m02 ] [ m[0] m[4] m[12] ]
|
||||
// [ m10 m11 m12 ] -> [ m[1] m[5] m[13] ]
|
||||
// [ 0 0 1 ] [ 0 0 1 ]
|
||||
float m[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
|
||||
// 3x3 matrix stored as column-major for OpenGL
|
||||
// [ a c tx ] [ m[0] m[3] m[6] ]
|
||||
// [ b d ty ] -> [ m[1] m[4] m[7] ]
|
||||
// [ 0 0 1 ] [ m[2] m[5] m[8] ]
|
||||
float m[9] = {1,0,0, 0,1,0, 0,0,1};
|
||||
|
||||
public:
|
||||
Transform() = default;
|
||||
Transform& translate(float x, float y) { return *this; } // TODO: Implement
|
||||
|
||||
Transform& translate(float x, float y) {
|
||||
// Combine with translation matrix
|
||||
m[6] += m[0] * x + m[3] * y;
|
||||
m[7] += m[1] * x + m[4] * y;
|
||||
return *this;
|
||||
}
|
||||
Transform& translate(const Vector2f& offset) { return translate(offset.x, offset.y); }
|
||||
Transform& rotate(float angle) { return *this; } // TODO: Implement
|
||||
Transform& rotate(float angle, const Vector2f& center) { return *this; } // TODO: Implement
|
||||
Transform& scale(float factorX, float factorY) { return *this; } // TODO: Implement
|
||||
|
||||
Transform& rotate(float angle) {
|
||||
float rad = angle * 3.14159265f / 180.0f;
|
||||
float cos_a = std::cos(rad);
|
||||
float sin_a = std::sin(rad);
|
||||
|
||||
float new_m0 = m[0] * cos_a + m[3] * sin_a;
|
||||
float new_m1 = m[1] * cos_a + m[4] * sin_a;
|
||||
float new_m3 = m[0] * -sin_a + m[3] * cos_a;
|
||||
float new_m4 = m[1] * -sin_a + m[4] * cos_a;
|
||||
|
||||
m[0] = new_m0; m[1] = new_m1;
|
||||
m[3] = new_m3; m[4] = new_m4;
|
||||
return *this;
|
||||
}
|
||||
Transform& rotate(float angle, const Vector2f& center) {
|
||||
translate(center.x, center.y);
|
||||
rotate(angle);
|
||||
translate(-center.x, -center.y);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Transform& scale(float factorX, float factorY) {
|
||||
m[0] *= factorX; m[1] *= factorX;
|
||||
m[3] *= factorY; m[4] *= factorY;
|
||||
return *this;
|
||||
}
|
||||
Transform& scale(const Vector2f& factors) { return scale(factors.x, factors.y); }
|
||||
|
||||
Vector2f transformPoint(float x, float y) const { return Vector2f(x, y); } // TODO: Implement
|
||||
Vector2f transformPoint(const Vector2f& point) const { return point; }
|
||||
FloatRect transformRect(const FloatRect& rect) const { return rect; } // TODO: Implement
|
||||
Vector2f transformPoint(float x, float y) const {
|
||||
return Vector2f(m[0] * x + m[3] * y + m[6],
|
||||
m[1] * x + m[4] * y + m[7]);
|
||||
}
|
||||
Vector2f transformPoint(const Vector2f& point) const {
|
||||
return transformPoint(point.x, point.y);
|
||||
}
|
||||
|
||||
Transform getInverse() const { return Transform(); } // TODO: Implement
|
||||
FloatRect transformRect(const FloatRect& rect) const {
|
||||
// Transform all four corners and compute bounding box
|
||||
Vector2f p1 = transformPoint(rect.left, rect.top);
|
||||
Vector2f p2 = transformPoint(rect.left + rect.width, rect.top);
|
||||
Vector2f p3 = transformPoint(rect.left, rect.top + rect.height);
|
||||
Vector2f p4 = transformPoint(rect.left + rect.width, rect.top + rect.height);
|
||||
|
||||
Transform operator*(const Transform& rhs) const { return Transform(); } // TODO: Implement
|
||||
Vector2f operator*(const Vector2f& point) const { return point; }
|
||||
float minX = std::min({p1.x, p2.x, p3.x, p4.x});
|
||||
float maxX = std::max({p1.x, p2.x, p3.x, p4.x});
|
||||
float minY = std::min({p1.y, p2.y, p3.y, p4.y});
|
||||
float maxY = std::max({p1.y, p2.y, p3.y, p4.y});
|
||||
|
||||
return FloatRect(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
Transform getInverse() const {
|
||||
// Compute inverse of 3x3 affine matrix
|
||||
float det = m[0] * m[4] - m[1] * m[3];
|
||||
if (std::abs(det) < 1e-7f) return Transform();
|
||||
|
||||
float invDet = 1.0f / det;
|
||||
Transform inv;
|
||||
inv.m[0] = m[4] * invDet;
|
||||
inv.m[1] = -m[1] * invDet;
|
||||
inv.m[3] = -m[3] * invDet;
|
||||
inv.m[4] = m[0] * invDet;
|
||||
inv.m[6] = (m[3] * m[7] - m[4] * m[6]) * invDet;
|
||||
inv.m[7] = (m[1] * m[6] - m[0] * m[7]) * invDet;
|
||||
return inv;
|
||||
}
|
||||
|
||||
Transform operator*(const Transform& rhs) const {
|
||||
Transform result;
|
||||
result.m[0] = m[0] * rhs.m[0] + m[3] * rhs.m[1];
|
||||
result.m[1] = m[1] * rhs.m[0] + m[4] * rhs.m[1];
|
||||
result.m[3] = m[0] * rhs.m[3] + m[3] * rhs.m[4];
|
||||
result.m[4] = m[1] * rhs.m[3] + m[4] * rhs.m[4];
|
||||
result.m[6] = m[0] * rhs.m[6] + m[3] * rhs.m[7] + m[6];
|
||||
result.m[7] = m[1] * rhs.m[6] + m[4] * rhs.m[7] + m[7];
|
||||
return result;
|
||||
}
|
||||
Vector2f operator*(const Vector2f& point) const { return transformPoint(point); }
|
||||
|
||||
static const Transform Identity;
|
||||
|
||||
|
|
@ -331,10 +403,14 @@ class Shader;
|
|||
|
||||
class RenderStates {
|
||||
public:
|
||||
Transform transform;
|
||||
BlendMode blendMode;
|
||||
const Shader* shader = nullptr;
|
||||
|
||||
RenderStates() = default;
|
||||
RenderStates(const Transform& transform) {} // Implicit conversion from Transform
|
||||
RenderStates(const BlendMode& mode) {}
|
||||
RenderStates(const Shader* shader) {} // Implicit conversion from Shader pointer
|
||||
RenderStates(const Transform& t) : transform(t) {}
|
||||
RenderStates(const BlendMode& mode) : blendMode(mode) {}
|
||||
RenderStates(const Shader* s) : shader(s) {}
|
||||
static const RenderStates Default;
|
||||
};
|
||||
|
||||
|
|
@ -386,8 +462,18 @@ public:
|
|||
void scale(float factorX, float factorY) { scale_.x *= factorX; scale_.y *= factorY; }
|
||||
void scale(const Vector2f& factor) { scale_.x *= factor.x; scale_.y *= factor.y; }
|
||||
|
||||
Transform getTransform() const { return Transform::Identity; } // TODO: Implement
|
||||
Transform getInverseTransform() const { return Transform::Identity; } // TODO: Implement
|
||||
Transform getTransform() const {
|
||||
Transform transform;
|
||||
// Apply transformations: translate to position, rotate, scale, translate by -origin
|
||||
transform.translate(position_.x, position_.y);
|
||||
transform.rotate(rotation_);
|
||||
transform.scale(scale_.x, scale_.y);
|
||||
transform.translate(-origin_.x, -origin_.y);
|
||||
return transform;
|
||||
}
|
||||
Transform getInverseTransform() const {
|
||||
return getTransform().getInverse();
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -411,6 +497,10 @@ public:
|
|||
virtual FloatRect getLocalBounds() const { return FloatRect(); }
|
||||
virtual FloatRect getGlobalBounds() const { return FloatRect(); }
|
||||
|
||||
// Virtual methods for shape points (implemented by derived classes)
|
||||
virtual size_t getPointCount() const = 0;
|
||||
virtual Vector2f getPoint(size_t index) const = 0;
|
||||
|
||||
protected:
|
||||
void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp
|
||||
};
|
||||
|
|
@ -423,6 +513,17 @@ public:
|
|||
const Vector2f& getSize() const { return size_; }
|
||||
FloatRect getLocalBounds() const override { return FloatRect(0, 0, size_.x, size_.y); }
|
||||
FloatRect getGlobalBounds() const override { return FloatRect(position_.x, position_.y, size_.x, size_.y); }
|
||||
|
||||
size_t getPointCount() const override { return 4; }
|
||||
Vector2f getPoint(size_t index) const override {
|
||||
switch (index) {
|
||||
case 0: return Vector2f(0, 0);
|
||||
case 1: return Vector2f(size_.x, 0);
|
||||
case 2: return Vector2f(size_.x, size_.y);
|
||||
case 3: return Vector2f(0, size_.y);
|
||||
default: return Vector2f();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class CircleShape : public Shape {
|
||||
|
|
@ -433,8 +534,13 @@ public:
|
|||
void setRadius(float radius) { radius_ = radius; }
|
||||
float getRadius() const { return radius_; }
|
||||
void setPointCount(size_t count) { pointCount_ = count; }
|
||||
size_t getPointCount() const { return pointCount_; }
|
||||
size_t getPointCount() const override { return pointCount_; }
|
||||
FloatRect getLocalBounds() const override { return FloatRect(0, 0, radius_ * 2, radius_ * 2); }
|
||||
|
||||
Vector2f getPoint(size_t index) const override {
|
||||
float angle = static_cast<float>(index) / pointCount_ * 2.0f * 3.14159265f;
|
||||
return Vector2f(radius_ + radius_ * std::cos(angle), radius_ + radius_ * std::sin(angle));
|
||||
}
|
||||
};
|
||||
|
||||
class ConvexShape : public Shape {
|
||||
|
|
@ -442,9 +548,9 @@ class ConvexShape : public Shape {
|
|||
public:
|
||||
ConvexShape(size_t pointCount = 0) : points_(pointCount) {}
|
||||
void setPointCount(size_t count) { points_.resize(count); }
|
||||
size_t getPointCount() const { return points_.size(); }
|
||||
size_t getPointCount() const override { return points_.size(); }
|
||||
void setPoint(size_t index, const Vector2f& point) { if (index < points_.size()) points_[index] = point; }
|
||||
Vector2f getPoint(size_t index) const { return index < points_.size() ? points_[index] : Vector2f(); }
|
||||
Vector2f getPoint(size_t index) const override { return index < points_.size() ? points_[index] : Vector2f(); }
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue