McRogueFace/src/3d/Viewport3D.h

272 lines
9.9 KiB
C
Raw Normal View History

2026-02-04 13:33:14 -05:00
// Viewport3D.h - 3D rendering viewport for McRogueFace
// A UIDrawable that renders a 3D scene to an FBO and displays it
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "UIDrawable.h"
#include "UIBase.h"
#include "PyDrawable.h"
#include "Math3D.h"
#include "Camera3D.h"
#include <memory>
namespace mcrf {
// Forward declarations
class Viewport3D;
class Shader3D;
} // namespace mcrf
// Python object struct
typedef struct {
PyObject_HEAD
std::shared_ptr<mcrf::Viewport3D> data;
PyObject* weakreflist;
} PyViewport3DObject;
namespace mcrf {
// =============================================================================
// Viewport3D - 3D rendering viewport as a UIDrawable
// Renders 3D content to an FBO, then blits to screen at display size
// =============================================================================
class Viewport3D : public UIDrawable {
public:
Viewport3D();
Viewport3D(float x, float y, float width, float height);
~Viewport3D();
// UIDrawable interface
void render(sf::Vector2f offset, sf::RenderTarget& target) override final;
PyObjectsEnum derived_type() override final;
UIDrawable* click_at(sf::Vector2f point) override final;
sf::FloatRect get_bounds() const override;
void move(float dx, float dy) override;
void resize(float w, float h) override;
// Size (screen display size)
void setSize(float width, float height);
float getWidth() const { return size_.x; }
float getHeight() const { return size_.y; }
// Internal resolution (PS1 style: render at low res, upscale)
void setInternalResolution(int width, int height);
int getInternalWidth() const { return internalWidth_; }
int getInternalHeight() const { return internalHeight_; }
// Camera access
Camera3D& getCamera() { return camera_; }
const Camera3D& getCamera() const { return camera_; }
// Camera convenience methods (exposed to Python)
void setCameraPosition(const vec3& pos) { camera_.setPosition(pos); }
void setCameraTarget(const vec3& target) { camera_.setTarget(target); }
vec3 getCameraPosition() const { return camera_.getPosition(); }
vec3 getCameraTarget() const { return camera_.getTarget(); }
// Background color
void setBackgroundColor(const sf::Color& color) { bgColor_ = color; }
sf::Color getBackgroundColor() const { return bgColor_; }
// PS1 effect settings
void setVertexSnapEnabled(bool enable) { vertexSnapEnabled_ = enable; }
bool isVertexSnapEnabled() const { return vertexSnapEnabled_; }
void setAffineMappingEnabled(bool enable) { affineMappingEnabled_ = enable; }
bool isAffineMappingEnabled() const { return affineMappingEnabled_; }
void setDitheringEnabled(bool enable) { ditheringEnabled_ = enable; }
bool isDitheringEnabled() const { return ditheringEnabled_; }
void setFogEnabled(bool enable) { fogEnabled_ = enable; }
bool isFogEnabled() const { return fogEnabled_; }
void setFogColor(const sf::Color& color);
sf::Color getFogColor() const;
void setFogRange(float nearDist, float farDist);
float getFogNear() const { return fogNear_; }
float getFogFar() const { return fogFar_; }
// Animation property system
bool setProperty(const std::string& name, float value) override;
bool setProperty(const std::string& name, const sf::Color& value) override;
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
bool getProperty(const std::string& name, float& value) const override;
bool getProperty(const std::string& name, sf::Color& value) const override;
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
bool hasProperty(const std::string& name) const override;
// Python API
static PyGetSetDef getsetters[];
static PyObject* repr(PyViewport3DObject* self);
static int init(PyViewport3DObject* self, PyObject* args, PyObject* kwds);
private:
// Display size (screen coordinates)
sf::Vector2f size_;
// Internal render target dimensions (PS1 was 320x240)
int internalWidth_ = 320;
int internalHeight_ = 240;
// FBO for render-to-texture
unsigned int fbo_ = 0;
unsigned int colorTexture_ = 0;
unsigned int depthRenderbuffer_ = 0;
// Camera
Camera3D camera_;
// Background color
sf::Color bgColor_ = sf::Color(25, 25, 50);
// PS1 effect flags
bool vertexSnapEnabled_ = true;
bool affineMappingEnabled_ = true;
bool ditheringEnabled_ = true;
bool fogEnabled_ = true;
// Fog parameters
vec3 fogColor_ = vec3(0.5f, 0.5f, 0.6f);
float fogNear_ = 10.0f;
float fogFar_ = 100.0f;
// Render test geometry (temporary until Entity3D/MeshLayer added)
float testRotation_ = 0.0f;
// Shader for PS1-style rendering
std::unique_ptr<Shader3D> shader_;
// Test geometry VBO (cube)
unsigned int testVBO_ = 0;
unsigned int testVertexCount_ = 0;
// SFML texture for blitting (wraps GL texture)
std::unique_ptr<sf::Texture> blitTexture_;
// Initialize/cleanup FBO
void initFBO();
void cleanupFBO();
// Initialize shader and test geometry
void initShader();
void initTestGeometry();
void cleanupTestGeometry();
// Render 3D content to FBO
void render3DContent();
// Blit FBO to screen
void blitToScreen(sf::Vector2f offset, sf::RenderTarget& target);
};
} // namespace mcrf
// Forward declaration of methods array
extern PyMethodDef Viewport3D_methods[];
namespace mcrfpydef {
static PyTypeObject PyViewport3DType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Viewport3D",
.tp_basicsize = sizeof(PyViewport3DObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyViewport3DObject* obj = (PyViewport3DObject*)self;
PyObject_GC_UnTrack(self);
if (obj->weakreflist != NULL) {
PyObject_ClearWeakRefs(self);
}
if (obj->data) {
obj->data->click_unregister();
obj->data->on_enter_unregister();
obj->data->on_exit_unregister();
obj->data->on_move_unregister();
}
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)mcrf::Viewport3D::repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.tp_doc = PyDoc_STR("Viewport3D(pos=None, size=None, **kwargs)\n\n"
"A 3D rendering viewport that displays a 3D scene as a UI element.\n\n"
"Args:\n"
" pos (tuple, optional): Position as (x, y) tuple. Default: (0, 0)\n"
" size (tuple, optional): Display size as (width, height). Default: (320, 240)\n\n"
"Keyword Args:\n"
" render_resolution (tuple): Internal render resolution (width, height). Default: (320, 240)\n"
" fov (float): Camera field of view in degrees. Default: 60\n"
" camera_pos (tuple): Camera position (x, y, z). Default: (0, 0, 5)\n"
" camera_target (tuple): Camera look-at point (x, y, z). Default: (0, 0, 0)\n"
" bg_color (Color): Background clear color. Default: (25, 25, 50)\n"
" enable_vertex_snap (bool): PS1-style vertex snapping. Default: True\n"
" enable_affine (bool): PS1-style affine texture mapping. Default: True\n"
" enable_dither (bool): PS1-style color dithering. Default: True\n"
" enable_fog (bool): Distance fog. Default: True\n"
" fog_color (Color): Fog color. Default: (128, 128, 153)\n"
" fog_near (float): Fog start distance. Default: 10\n"
" fog_far (float): Fog end distance. Default: 100\n"),
.tp_traverse = [](PyObject* self, visitproc visit, void* arg) -> int {
PyViewport3DObject* obj = (PyViewport3DObject*)self;
if (obj->data) {
if (obj->data->click_callable) {
PyObject* callback = obj->data->click_callable->borrow();
if (callback && callback != Py_None) {
Py_VISIT(callback);
}
}
if (obj->data->on_enter_callable) {
PyObject* callback = obj->data->on_enter_callable->borrow();
if (callback && callback != Py_None) {
Py_VISIT(callback);
}
}
if (obj->data->on_exit_callable) {
PyObject* callback = obj->data->on_exit_callable->borrow();
if (callback && callback != Py_None) {
Py_VISIT(callback);
}
}
if (obj->data->on_move_callable) {
PyObject* callback = obj->data->on_move_callable->borrow();
if (callback && callback != Py_None) {
Py_VISIT(callback);
}
}
}
return 0;
},
.tp_clear = [](PyObject* self) -> int {
PyViewport3DObject* obj = (PyViewport3DObject*)self;
if (obj->data) {
obj->data->click_unregister();
obj->data->on_enter_unregister();
obj->data->on_exit_unregister();
obj->data->on_move_unregister();
}
return 0;
},
.tp_methods = Viewport3D_methods,
.tp_getset = mcrf::Viewport3D::getsetters,
.tp_base = &mcrfpydef::PyDrawableType,
.tp_init = (initproc)mcrf::Viewport3D::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyViewport3DObject* self = (PyViewport3DObject*)type->tp_alloc(type, 0);
if (self) {
self->data = std::make_shared<mcrf::Viewport3D>();
self->weakreflist = nullptr;
}
return (PyObject*)self;
}
};
} // namespace mcrfpydef