#ifndef TEXTURE_PAINTER_CLASS #define TEXTURE_PAINTER_CLASS #include #include #include "texture.h" // The brush behind the texture drawer. Edits a Texture's pixels and uploads them. class TexturePainter { public: glm::vec3 color = glm::vec3(0.0f); int brushSize = 8; // radius in pixels of a 256 wide texture, so it looks the same on any size // uv runs 0-1 from the top left of the image. Joins up with the last call until EndStroke. void Stroke(Texture& texture, const glm::vec2& uv) { const glm::vec2 point(uv.x * texture.width, (1.0f - uv.y) * texture.height); const float radius = glm::max(1.0f, brushSize * texture.width / 256.0f); if (!stroking) { Stamp(texture, point, radius); } else { // Fill the gap, the mouse can cross a lot of pixels in one frame const int steps = (int)std::ceil(glm::length(point - lastPoint) / (radius * 0.5f)); for (int i = 1; i <= steps; i++) Stamp(texture, glm::mix(lastPoint, point, (float)i / (float)steps), radius); } stroking = true; lastPoint = point; texture.Upload(); } void EndStroke() { stroking = false; } void Clear(Texture& texture) { texture.pixels.assign(texture.pixels.size(), 255); texture.Upload(); } private: bool stroking = false; glm::vec2 lastPoint = glm::vec2(0.0f); void Stamp(Texture& texture, const glm::vec2& center, float radius) { const int minX = glm::max((int)(center.x - radius), 0); const int maxX = glm::min((int)(center.x + radius), texture.width - 1); const int minY = glm::max((int)(center.y - radius), 0); const int maxY = glm::min((int)(center.y + radius), texture.height - 1); for (int y = minY; y <= maxY; y++) { for (int x = minX; x <= maxX; x++) { const glm::vec2 offset = glm::vec2(x + 0.5f, y + 0.5f) - center; if (glm::dot(offset, offset) > radius * radius) continue; unsigned char* pixel = &texture.pixels[((size_t)y * texture.width + x) * 4]; pixel[0] = (unsigned char)(color.r * 255.0f); pixel[1] = (unsigned char)(color.g * 255.0f); pixel[2] = (unsigned char)(color.b * 255.0f); pixel[3] = 255; } } } }; #endif