From e52d4ac9cb766ac5c3f40b3639b49248be6eccd9 Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Mon, 9 Feb 2026 14:17:46 +0100 Subject: [PATCH] refactoring --- .gitea/config/gitversion.yml | 9 +++ .gitea/readme/screenshot.png | Bin 0 -> 40498 bytes .gitea/workflows/analyze.yml | 34 ++++++++++ .gitea/workflows/analyze_and_publish.yml | 75 +++++++++++++++++++++++ README.md | 64 ++++++++++++++++++- requirements.txt | 2 + 3d_renderer.py => src/__main__.py | 2 +- {3d_renderer => src}/app.py | 4 +- {3d_renderer => src}/camera.py | 2 +- {3d_renderer => src}/object.py | 2 +- {3d_renderer => src}/object_builder.py | 4 +- {3d_renderer => src}/renderer.py | 4 +- {3d_renderer => src}/transformations.py | 0 {3d_renderer => src}/vertex.py | 0 14 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 .gitea/config/gitversion.yml create mode 100644 .gitea/readme/screenshot.png create mode 100644 .gitea/workflows/analyze.yml create mode 100644 .gitea/workflows/analyze_and_publish.yml create mode 100644 requirements.txt rename 3d_renderer.py => src/__main__.py (71%) rename {3d_renderer => src}/app.py (98%) rename {3d_renderer => src}/camera.py (99%) rename {3d_renderer => src}/object.py (97%) rename {3d_renderer => src}/object_builder.py (89%) rename {3d_renderer => src}/renderer.py (97%) rename {3d_renderer => src}/transformations.py (100%) rename {3d_renderer => src}/vertex.py (100%) diff --git a/.gitea/config/gitversion.yml b/.gitea/config/gitversion.yml new file mode 100644 index 0000000..57caa9e --- /dev/null +++ b/.gitea/config/gitversion.yml @@ -0,0 +1,9 @@ +next-version: 1.0.0 +mode: ContinuousDeployment +assembly-versioning-scheme: MajorMinorPatch +assembly-file-versioning-scheme: MajorMinorPatch + +branches: + main: + regex: ^main$ + increment: Patch \ No newline at end of file diff --git a/.gitea/readme/screenshot.png b/.gitea/readme/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..1eaa408882daa592d635083e6c3d8d56f94c9759 GIT binary patch literal 40498 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 Renderer + +

A simple 3D renderer that allows you to move around the scene using the keys.

+ + + +

3D Renderer was written using Python and PyGame library. This project was part of "Computer Graphics" course at Warsaw University of Technology

+ +--- + +## Features + +- Create your own scene +- Move around the scene using the keys + +## Installation + +Download latest package version from Releases tab, unpack, install requirements and you good to go + +**Requirements** + +- Python installed +- PIP packages: + - `pygame` + - `numpy` + +You can also use `requirements.txt` file to install PIP dependencies + +``` +pip install -r requirements.txt +``` + +## Usage + +``` +python 3d_renderer +``` + +**Create the scene:** + +You can define your own scene in `main` method of `App` class in `3d_renderer/app.py` file. + +- Create object builder: `obj_builder1 = ObjectBuilder()` +- Add as many vertices to the object as you want: `va = obj_builder1.add_vertex(-1, 1, 1)` +- Connect vertices to make edges: `obj_builder1.add_vertices_connection(va, vb)` +- Build object and add it to the scene: `self.renderer.add_object(obj_builder1.build())` + +**Controls:** + +- W - move forward +- S - move backward +- A - move left +- D - move right +- Space - move up +- LShift - move down +- = - FOV up +- - - FOV down +- F - Pitch up +- R - Pitch down +- E - Yaw up +- Q - Yaw down +- C - Roll up +- Z - Roll down \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4091431 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pygame +numpy diff --git a/3d_renderer.py b/src/__main__.py similarity index 71% rename from 3d_renderer.py rename to src/__main__.py index 4b586ac..f573483 100644 --- a/3d_renderer.py +++ b/src/__main__.py @@ -1,4 +1,4 @@ -from src.app import App +from app import App if __name__ == "__main__": app = App() diff --git a/3d_renderer/app.py b/src/app.py similarity index 98% rename from 3d_renderer/app.py rename to src/app.py index 4fbe8ba..441e9f7 100644 --- a/3d_renderer/app.py +++ b/src/app.py @@ -1,5 +1,5 @@ -from src.renderer import Renderer -from src.object_builder import ObjectBuilder +from renderer import Renderer +from object_builder import ObjectBuilder class App: renderer : Renderer diff --git a/3d_renderer/camera.py b/src/camera.py similarity index 99% rename from 3d_renderer/camera.py rename to src/camera.py index dd25726..cf623d3 100644 --- a/3d_renderer/camera.py +++ b/src/camera.py @@ -1,6 +1,6 @@ import numpy as np import math as mt -import src.transformations as tn +import transformations as tn class Camera: __position: np.ndarray[float] diff --git a/3d_renderer/object.py b/src/object.py similarity index 97% rename from 3d_renderer/object.py rename to src/object.py index f32d367..2624748 100644 --- a/3d_renderer/object.py +++ b/src/object.py @@ -1,7 +1,7 @@ import numpy as np import pygame as pg import os -from src.vertex import Vertex +from vertex import Vertex class Object: vertices: list[Vertex] diff --git a/3d_renderer/object_builder.py b/src/object_builder.py similarity index 89% rename from 3d_renderer/object_builder.py rename to src/object_builder.py index 14d46bb..67df6d3 100644 --- a/3d_renderer/object_builder.py +++ b/src/object_builder.py @@ -1,5 +1,5 @@ -from src.object import Object -from src.vertex import Vertex +from object import Object +from vertex import Vertex class ObjectBuilder: __vertices: list[Vertex] diff --git a/3d_renderer/renderer.py b/src/renderer.py similarity index 97% rename from 3d_renderer/renderer.py rename to src/renderer.py index 194c150..baeabb8 100644 --- a/3d_renderer/renderer.py +++ b/src/renderer.py @@ -1,8 +1,8 @@ import pygame as pg import numpy as np from math import * -from src.camera import Camera -from src.object import Object +from camera import Camera +from object import Object class Renderer: __clock: pg.time.Clock diff --git a/3d_renderer/transformations.py b/src/transformations.py similarity index 100% rename from 3d_renderer/transformations.py rename to src/transformations.py diff --git a/3d_renderer/vertex.py b/src/vertex.py similarity index 100% rename from 3d_renderer/vertex.py rename to src/vertex.py