Metadata-Version: 2.4
Name: speepy3d
Version: 0.8.4
Summary: A Python 3D game framework powered by a C++ OpenGL backend.
Author: speepy3d contributors
License-Expression: MIT
Keywords: opengl,3d,graphics,window,cpp-extension
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Pillow>=10
Dynamic: license-file

# speepy3d

`speepy3d` is a Python 3D game framework with a C++ OpenGL backend.

The backend is currently Windows-only through Win32/WGL. It uses a shader/VBO
renderer while keeping simple helpers for debug grid/axes.

## Features

- Native OpenGL window creation.
- Keyboard and mouse state: `is_key_down`, `is_mouse_down`, `mouse_delta`.
- Cursor helpers: `set_cursor_locked`, `set_cursor_visible`.
- Timing: `delta_time`, `fps`, `fpsbar=True`, optional `set_vsync`.
- Resize and close callbacks.
- Input bindings: `Input.action()`, `Input.axis()`, `Input.vec2()`, mouse sensitivity and click-to-lock mouse.
- Input contexts for game/menu/pause bindings.
- Game layer helpers: `GameApp`, `AssetManager`, `Timer`, `Profiler`, controllers and particles.
- Entity/component layer with lifecycle: `Component.start()`, `update()`, `late_update()`.
- Transform hierarchy: parent/children entities.
- Events/signals: `EventBus`, `app.events`, `app.on()`, `app.emit()`.
- Prefabs and scene JSON save/load.
- Collision callbacks and trigger callbacks for entities.
- Tween and coroutine managers for small animations and timed logic.
- Render layers for ordered model drawing.
- Modern OpenGL basics: `Shader`, `Material`, `Mesh`, `Texture`, `Renderer`.
- `MeshBatch` for building many cubes into one fast mesh.
- Built-in `Shader.basic()`, `Mesh.cube()`, `Mesh.plane()`, `Texture.checkerboard()`.
- Mesh normals and basic ambient + directional lighting.
- Simple OBJ loader: `Mesh.from_obj("model.obj")`.
- Simple glTF loader: `Mesh.from_gltf("model.gltf")`.
- Texture loading for `.png`, `.jpg`, `.jpeg`, `.bmp` and `.ppm`.
- Math: `Vec2`, `Vec3`, `Mat4`.
- Scene objects: `Camera3D`, `Transform`, `Model`, `Scene`, `DirectionalLight`.
- Collision helpers: `AABB`, `PhysicsBody`, `PhysicsWorld`, `move_and_collide`, `SphereCollider`, `Ray`, `raycast_aabb`.
- ECS collision callbacks: `on_trigger_enter`, `on_trigger_exit`, `on_collision_enter`, `on_collision_exit`.
- Collision layers: `scene.ignore_collision()`, `scene.only_collision()`.
- Scene queries: `scene.query()`, `scene.find_all()`, `scene.find_component()`.
- Asset folder loading: `assets.load_folder("assets")`.
- Debug drawing: `DebugDraw.line()`, `DebugDraw.box()`, `DebugDraw.ray()`.
- WAV sound helpers on Windows: `Sound`, `Music`.
- Immediate UI: `Label`, `Button`, `Panel`, `Menu`, `ContextMenu`, `UIManager`.
- Game loop helper: `run(window, update, render)`.

## Install

```powershell
python -m pip install speepy3d
```

For local development from this source folder:

```powershell
python -m pip install -e .
```

## Quick Start

```powershell
speepy3d new mygame --template platformer
cd mygame
speepy3d run
```

Useful CLI commands:

```powershell
speepy3d new mygame --template empty
speepy3d new mygame --template menu
speepy3d new mygame --template fps
speepy3d info
speepy3d examples
speepy3d --version
```

## Modern Renderer Example

```python
from speepy3d import Camera3D, DirectionalLight, Input, Model, Renderer, Scene, Texture, Vec3, Window

win = Window(800, 600, "speepy3d", fpsbar=True)
win.enable_depth_test(True)

camera = Camera3D(position=Vec3(0.0, 2.0, 6.0), pitch=-15.0)
renderer = Renderer(win)
controls = Input(win, mouse_sensitivity=0.08, mouse_locked_by_click=True)
controls.action("jump", "SPACE")
controls.axis("move_x", negative="A", positive="D")
controls.axis("move_z", negative="S", positive="W")

scene = Scene(
    camera=camera,
    ambient_light=Vec3(0.36, 0.40, 0.48),
    directional_light=DirectionalLight(direction=Vec3(-0.35, -1.0, -0.45), intensity=1.1),
)
cube = scene.add(Model.cube(1.5, texture=Texture.checkerboard()))
scene.add(Model.grid(10.0, 1.0))
scene.add(Model.axes(2.0))

while not win.should_close():
    win.poll_events()
    controls.update()

    dt = win.delta_time or 1.0 / 60.0
    raw_move = controls.vec2("move")
    move = camera.right * raw_move.x + camera.forward * raw_move.y
    if move.length > 0.0:
        camera.position += move.normalized() * camera.move_speed * dt

    controls.apply_look(camera)

    cube.transform.rotation.y += 60.0 * dt
    renderer.render(scene)
    win.swap_buffers()
```

## Game Examples

```powershell
python examples\snake3d.py
python examples\minicraft3d.py
python examples\platformer3d.py
```

## Input

```python
from speepy3d import Input

controls = Input(win, mouse_sensitivity=0.06, mouse_locked_by_click=True)
controls.action("jump", "SPACE")
controls.axis("move_x", negative="A", positive="D")
controls.axis("move_z", negative="S", positive="W")

while not win.should_close():
    win.poll_events()
    controls.update()

    if controls.action("jump"):
        jump()

    move = controls.vec2("move")
```

## Game Layer

```python
from speepy3d import GameApp, MeshBatch, PhysicsBody, PlatformerController, Vec3

app = GameApp(1280, 720, "short game")
app.set_camera((0, 3, 10), pitch=-15)

app.input.action("jump", "SPACE")
app.input.axis("move_x", negative="A", positive="D")

material = app.assets.material("grass", color=Vec3(0.25, 0.85, 0.35))
cube = app.assets.cube()
player = app.scene.spawn(cube, position=(0, 2, 0), material=material, name="player", tag="actor")

world = MeshBatch()
world.cube((0, 0, 0), size=(12, 1, 4), color=(0.3, 0.8, 0.35))
app.scene.add(world.build_model(material))

body = PhysicsBody.from_values(player.position, (1, 1, 1))
app.physics.static_box((0, 0, 0), (12, 1, 4))
controller = PlatformerController(body, app.input, app.physics)

@app.update
def update(dt):
    controller.update(dt)
    player.position = body.position
    app.debug.box(body.box)

@app.render
def render():
    app.draw_scene()
    app.ui.begin()
    app.ui.panel(20, 20, 220, 90)
    app.ui.label(f"FPS {app.fps:.0f}")
    app.profiler.draw(app.ui, 260, 20)
    app.ui.end()

app.run()
```

Useful systems:

```python
app.timer.after(2.0, spawn_enemy)
app.timer.every(1.0, add_score)

app.particles.emit((0, 2, 0), count=24)

player = app.scene.find("player")
enemies = app.scene.find_by_tag("enemy")
```

## Entity Components

```python
from speepy3d import Component, GameApp, MeshRenderer, Prefab, Rigidbody, Vec3

app = GameApp(1280, 720, "components")
app.input.context("game").action("jump", "SPACE").axis("move_x", negative="A", positive="D")
app.input.use_context("game")

class Player(Component):
    def start(self):
        self.body = self.entity.get(Rigidbody)

    def update(self, dt):
        self.body.velocity.x = self.input.axis("move_x") * 7.0
        if self.input.action("jump") and self.body.body.on_ground:
            self.body.velocity.y = 10.0

player = app.scene.entity("player", tag="player", position=(0, 2, 0))
player.add(Player)
player.add(MeshRenderer(app.assets.cube(), app.assets.material("player", color=Vec3(0.2, 0.7, 1.0))))
player.add(Rigidbody(size=(1, 1, 1)))

prefab = Prefab.from_entity(player)
clone = prefab.instantiate(app.scene, name="clone", position=(3, 2, 0))
```

Collision and triggers:

```python
@app.on_trigger_enter("player", "coin")
def collect(player, coin):
    coin.destroy()
    app.emit("score:add", 1)

@player.on_collision("enemy")
def hurt(enemy):
    app.tween(player, "position.y", to=4.0, time=0.25, ease="smooth")
```

Scene save/load:

```python
app.scene.save("level.json")
app.scene.load("level.json")
```

Queries:

```python
enemies = app.scene.query(tag="enemy", component=Rigidbody)
body = app.scene.find_component(Rigidbody)
app.scene.destroy("enemy_1")
app.scene.clear()
```

Collision layers:

```python
player.add(Rigidbody(size=(1, 1, 1), layer="player"))
coin.add(Collider(size=(1, 1, 1), trigger=True, layer="pickup"))
app.scene.ignore_collision("enemy", "enemy")
```

Coroutines:

```python
def spawn_loop():
    while True:
        spawn_enemy()
        yield app.wait(2.0)

app.start_coroutine(spawn_loop)
```

## Loading Assets

```python
from speepy3d import Material, Mesh, Model, Shader, Texture, Vec3

mesh = Mesh.from_gltf("assets/crate.gltf")
material = Material(
    shader=Shader.basic(),
    texture=Texture.from_file("assets/crate.png"),
    color=Vec3(1.0, 1.0, 1.0),
    roughness=0.5,
)
model = Model(mesh, material=material)
```

`Mesh.from_gltf()` supports `.gltf` JSON files with triangle meshes and external
or embedded buffers. `.glb`, skinning and animation are not implemented yet.

Folder workflow:

```python
app.assets.load_folder("assets")
crate = app.assets.get_material("crate")
player_mesh = app.assets.get_mesh("player")
```

## Collision

```python
from speepy3d import AABB, Vec3, intersects

player = AABB.from_center_size(Vec3(0, 1, 0), Vec3(1, 2, 1))
wall = AABB.from_center_size(Vec3(2, 1, 0), Vec3(1, 2, 4))

if intersects(player, wall):
    print("hit")
```

## UI

```python
from speepy3d import Button, Label, Menu, MenuItem, UIManager

ui = UIManager()
ui.add(Label(20, 20, "SCORE 0"))
ui.add(Button(20, 55, 140, 36, text="START", on_click=start_game))
ui.add(Menu(20, 100, 120, 32, title="GAME", items=[
    MenuItem("RESET", reset_game),
    MenuItem("QUIT", win.close),
]))

while not win.should_close():
    win.poll_events()
    ui.update(win)
    render_3d()
    ui.draw(win)
    win.swap_buffers()
```

Immediate-mode UI:

```python
ui = UIManager(win)

while not win.should_close():
    win.poll_events()
    render_3d()

    ui.begin()
    ui.panel(20, 20, 250, 180)
    ui.label("Score: 10")
    if ui.button("Restart"):
        restart()
    ui.end()

    win.swap_buffers()
```

## Sound

```python
from speepy3d import Sound

Sound("jump.wav").play()
```

Sound currently uses Python's Windows `winsound`, so WAV is the practical target.

## Build

```powershell
python setup.py sdist bdist_wheel
```
