Skip to content

Implement collision detection (touching sprite, point) #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libscratchcpp
Submodule libscratchcpp updated 111 files
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ qt_add_qml_module(scratchcpp-render
textbubbleshape.h
textbubblepainter.cpp
textbubblepainter.h
cputexturemanager.cpp
cputexturemanager.h
blocks/penextension.cpp
blocks/penextension.h
blocks/penblocks.cpp
Expand Down
47 changes: 47 additions & 0 deletions src/ProjectPlayer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,53 @@ ProjectScene {
}
}

// Uncomment to display sprite bounding boxes (for debugging)
/*Rectangle {
function translateX(x) {
// Translates Scratch X-coordinate to the scene coordinate system
return root.stageScale * (root.stageWidth / 2 + x)
}

function translateY(y) {
// Translates Scratch Y-coordinate to the scene coordinate system
return root.stageScale * (root.stageHeight / 2 - y)
}

id: boundRect
color: "transparent"
border.color: "red"
border.width: 3
visible: targetItem.visible

function updatePosition() {
let bounds = targetItem.getQmlBounds();
boundRect.x = translateX(bounds.left);
boundRect.y = translateY(bounds.top);
width = bounds.width * root.stageScale;
height = -bounds.height * root.stageScale;
}

Connections {
target: targetItem

function onXChanged() { boundRect.updatePosition() }
function onYChanged() { boundRect.updatePosition() }
function onRotationChanged() { boundRect.updatePosition() }
function onWidthChanged() { boundRect.updatePosition() }
function onHeightChanged() { boundRect.updatePosition() }
function onScaleChanged() { boundRect.updatePosition() }
}

Connections {
property Scale transform: Scale {}
target: transform

function onXScaleChanged() { boundRect.updatePosition() }

Component.onCompleted: transform = targetItem.transform[0]
}
}*/

Loader {
readonly property alias model: targetItem.spriteModel
active: model ? model.bubbleText !== "" : false
Expand Down
118 changes: 118 additions & 0 deletions src/cputexturemanager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "cputexturemanager.h"
#include "texture.h"

using namespace scratchcpprender;

CpuTextureManager::CpuTextureManager()
{
}

CpuTextureManager::~CpuTextureManager()
{
for (const auto &[handle, data] : m_textureData)
delete[] data;
}

GLubyte *CpuTextureManager::getTextureData(const Texture &texture)
{
if (!texture.isValid())
return nullptr;

const GLuint handle = texture.handle();
auto it = m_textureData.find(handle);

if (it == m_textureData.cend()) {
if (addTexture(texture))
return m_textureData[handle];
else
return nullptr;
} else
return it->second;
}

const std::vector<QPoint> &CpuTextureManager::getTextureConvexHullPoints(const Texture &texture)
{
static const std::vector<QPoint> empty;

if (!texture.isValid())
return empty;

const GLuint handle = texture.handle();
auto it = m_convexHullPoints.find(handle);

if (it == m_convexHullPoints.cend()) {
if (addTexture(texture))
return m_convexHullPoints[handle];
else
return empty;
} else
return it->second;
}

bool CpuTextureManager::addTexture(const Texture &texture)
{
if (!texture.isValid())
return false;

const GLuint handle = texture.handle();
const int width = texture.width();
const int height = texture.height();

QOpenGLFunctions glF;
glF.initializeOpenGLFunctions();

// Create a FBO for the texture
unsigned int fbo;
glF.glGenFramebuffers(1, &fbo);
glF.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glF.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, handle, 0);

if (glF.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
qWarning() << "error: framebuffer incomplete (CpuTextureManager)";
glF.glDeleteFramebuffers(1, &fbo);
return false;
}

// Read pixels
GLubyte *pixels = new GLubyte[width * height * 4]; // 4 channels (RGBA)
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

// Flip vertically
int rowSize = width * 4;
GLubyte *tempRow = new GLubyte[rowSize];

for (size_t i = 0; i < height / 2; ++i) {
size_t topRowIndex = i * rowSize;
size_t bottomRowIndex = (height - 1 - i) * rowSize;

// Swap rows
memcpy(tempRow, &pixels[topRowIndex], rowSize);
memcpy(&pixels[topRowIndex], &pixels[bottomRowIndex], rowSize);
memcpy(&pixels[bottomRowIndex], tempRow, rowSize);
}

delete[] tempRow;

m_textureData[handle] = pixels;
m_convexHullPoints[handle] = {};
std::vector<QPoint> &hullPoints = m_convexHullPoints[handle];

// Get convex hull points
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = (y * width + x) * 4; // 4 channels (RGBA)

// Check alpha channel
if (pixels[index + 3] > 0)
hullPoints.push_back(QPoint(x, y));
}
}

// Cleanup
glF.glBindFramebuffer(GL_FRAMEBUFFER, 0);
glF.glDeleteFramebuffers(1, &fbo);

return true;
}
30 changes: 30 additions & 0 deletions src/cputexturemanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <QPoint>
#include <QtOpenGL>
#include <unordered_map>

namespace scratchcpprender
{

class Texture;

class CpuTextureManager
{
public:
CpuTextureManager();
~CpuTextureManager();

GLubyte *getTextureData(const Texture &texture);
const std::vector<QPoint> &getTextureConvexHullPoints(const Texture &texture);

private:
bool addTexture(const Texture &texture);

std::unordered_map<GLuint, GLubyte *> m_textureData;
std::unordered_map<GLuint, std::vector<QPoint>> m_convexHullPoints;
};

} // namespace scratchcpprender
9 changes: 9 additions & 0 deletions src/internal/TextBubble.qml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ TextBubbleShape {
function onScaleChanged() { positionBubble() }
}

Connections {
property Scale transform: Scale {}
target: transform

function onXScaleChanged() { positionBubble() }

Component.onCompleted: transform = root.target.transform[0]
}

Text {
id: bubbleText
anchors.left: parent.left
Expand Down
9 changes: 6 additions & 3 deletions src/irenderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Texture;
class IRenderedTarget : public QNanoQuickItem
{
public:
IRenderedTarget(QNanoQuickItem *parent = nullptr) :
IRenderedTarget(QQuickItem *parent = nullptr) :
QNanoQuickItem(parent)
{
}
Expand Down Expand Up @@ -83,8 +83,11 @@ class IRenderedTarget : public QNanoQuickItem
virtual void setGraphicEffect(ShaderManager::Effect effect, double value) = 0;
virtual void clearGraphicEffects() = 0;

virtual void updateHullPoints(QOpenGLFramebufferObject *fbo) = 0;
virtual const std::vector<QPointF> &hullPoints() const = 0;
virtual const std::vector<QPoint> &hullPoints() const = 0;

virtual bool containsScratchPoint(double x, double y) const = 0;

virtual bool touchingClones(const std::vector<libscratchcpp::Sprite *> &clones) const = 0;
};

} // namespace scratchcpprender
23 changes: 7 additions & 16 deletions src/projectloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ using namespace libscratchcpp;
ProjectLoader::ProjectLoader(QObject *parent) :
QObject(parent)
{
m_project.setDownloadProgressCallback([this](unsigned int finished, unsigned int all) {
m_project.downloadProgressChanged().connect([this](unsigned int finished, unsigned int all) {
if (finished != m_downloadedAssets) {
m_downloadedAssets = finished;
emit downloadedAssetsChanged();
Expand Down Expand Up @@ -208,12 +208,8 @@ void ProjectLoader::stop()

void ProjectLoader::answerQuestion(const QString &answer)
{
if (m_engine) {
auto f = m_engine->questionAnswered();

if (f)
f(answer.toStdString());
}
if (m_engine)
m_engine->questionAnswered()(answer.toStdString());
}

void ProjectLoader::timerEvent(QTimerEvent *event)
Expand Down Expand Up @@ -255,16 +251,11 @@ void ProjectLoader::load()
m_engine->setCloneLimit(m_cloneLimit);
m_engine->setSpriteFencingEnabled(m_spriteFencing);

auto redrawHandler = std::bind(&ProjectLoader::redraw, this);
m_engine->setRedrawHandler(std::function<void()>(redrawHandler));

auto addMonitorHandler = std::bind(&ProjectLoader::addMonitor, this, std::placeholders::_1);
m_engine->setAddMonitorHandler(std::function<void(Monitor *)>(addMonitorHandler));

auto removeMonitorHandler = std::bind(&ProjectLoader::removeMonitor, this, std::placeholders::_1, std::placeholders::_2);
m_engine->setRemoveMonitorHandler(std::function<void(Monitor *, IMonitorHandler *)>(removeMonitorHandler));
m_engine->aboutToRender().connect(&ProjectLoader::redraw, this);
m_engine->monitorAdded().connect(&ProjectLoader::addMonitor, this);
m_engine->monitorRemoved().connect(&ProjectLoader::removeMonitor, this);

m_engine->setQuestionAsked([this](const std::string &question) { emit questionAsked(QString::fromStdString(question)); });
m_engine->questionAsked().connect([this](const std::string &question) { emit questionAsked(QString::fromStdString(question)); });

// Load targets
const auto &targets = m_engine->targets();
Expand Down
Loading