File: fpscamera.py

package info (click to toggle)
pyglet 2.0.17%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 15,560 kB
  • sloc: python: 80,579; xml: 50,988; ansic: 171; makefile: 146
file content (170 lines) | stat: -rw-r--r-- 6,120 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import weakref

from math import cos, sin, radians

import pyglet

from pyglet.gl import glEnable, GL_DEPTH_TEST, GL_CULL_FACE
from pyglet.math import Vec2, Vec3, Mat4, clamp


class FPSCamera:
    """A 3D projection "first person" camera example.

    Windows in pyglet contain `view` and `projection` matrices,
    which are shared with all default shaders in a `WindowBlock` UBO.
    This Camera class handles events from the mouse & keyboard and
    Controller, and updates these Window matrices.

    Note mouse input will be captured once you click on the Window,
    which sets the mouse as exclusive. Pressing ESC once will set
    the mouse as non-exclusive.
    """
    def __init__(self, window, position=Vec3(0, 0, 0), target=Vec3(0, 0, -1), up=Vec3(0, 1, 0)):
        self.position = position
        self.target = target
        self.up = up

        self.walk_speed = 10.0
        self.look_speed = 10.0
        self.deadzone = 0.1

        # TODO: calculate these values from the passed Vectors
        self.pitch = 0
        self.yaw = 270
        self.roll = 0

        self.input_map = {pyglet.window.key.W: "forward",
                          pyglet.window.key.S: "backward",
                          pyglet.window.key.A: "left",
                          pyglet.window.key.D: "right"}

        self.inputs = {direction: False for direction in self.input_map.values()}

        self.mouse_look = Vec2()
        self.keybord_move = Vec2()
        self.controller_look = Vec2()
        self.controller_move = Vec2()

        self._window = weakref.proxy(window)
        self._window.view = Mat4.look_at(position, target, up)
        self._window.push_handlers(self)

        self._exclusive_mouse = False

    def on_deactivate(self):
        # Prevent input from getting "stuck"
        self.keybord_move = Vec2()

    def on_resize(self, width, height):
        self._window.viewport = (0, 0, width, height)
        self._window.projection = Mat4.perspective_projection(self._window.aspect_ratio, z_near=0.1, z_far=1000, fov=45)
        return pyglet.event.EVENT_HANDLED

    def on_refresh(self, dt):
        walk_speed = self.walk_speed * dt
        norm_target = self.target.normalize()

        # Look

        if abs(self.controller_look) > self.deadzone:
            # Don't reset the vector to 0,0 because new events don't come
            # in when the analoge stick is held in a steady position.
            look_speed = self.look_speed ** 2 * dt
            self.yaw += self.controller_look.x * look_speed
            self.pitch = clamp(self.pitch + self.controller_look.y * look_speed, -89.0, 89.0)

        if abs(self.mouse_look) > 0.0:
            # Reset the vector back to 0 each time, because there is no event
            # for when the mouse stops moving. It will get "stuck" otherwise.
            look_speed = self.look_speed * dt
            self.yaw += self.mouse_look.x * look_speed
            self.pitch = clamp(self.pitch + self.mouse_look.y * look_speed, -89.0, 89.0)
            self.mouse_look[:] = 0.0, 0.0

        self.target = Vec3(cos(radians(self.yaw)) * cos(radians(self.pitch)),
                           sin(radians(self.pitch)),
                           sin(radians(self.yaw)) * cos(radians(self.pitch))).normalize()

        # Movement

        if abs(self.controller_move) > self.deadzone:
            self.position += (norm_target * self.controller_move.y +
                              norm_target.cross(self.up).normalize() * self.controller_move.x) * walk_speed

        if abs(self.keybord_move) > 0:
            self.position += (norm_target * self.keybord_move.y +
                              norm_target.cross(self.up).normalize() * self.keybord_move.x) * walk_speed

        self._window.view = Mat4.look_at(self.position, self.position + self.target, self.up)

    # Mouse input

    def on_mouse_motion(self, x, y, dx, dy):
        if not self._exclusive_mouse:
            return
        self.mouse_look[:] = dx, dy

    def on_mouse_press(self, x, y, button, modifiers):
        if not self._exclusive_mouse:
            self._exclusive_mouse = True
            self._window.set_exclusive_mouse(True)

    # Keyboard input

    def on_key_press(self, symbol, mod):
        if symbol == pyglet.window.key.ESCAPE:
            if not self._exclusive_mouse:
                pyglet.app.exit()
            self._exclusive_mouse = False
            self._window.set_exclusive_mouse(False)
            return pyglet.event.EVENT_HANDLED

        if direction := self.input_map.get(symbol):
            self.inputs[direction] = True
            f, b, l, r = self.inputs.values()
            self.keybord_move = Vec2(-float(l) + float(r), float(f) + -float(b)).normalize()

    def on_key_release(self, symbol, mod):
        if direction := self.input_map.get(symbol):
            self.inputs[direction] = False
            f, b, l, r = self.inputs.values()
            self.keybord_move = Vec2(-float(l) + float(r), float(f) + -float(b)).normalize()

    # Controller input

    def on_stick_motion(self, _controller, stick, xvalue, yvalue):
        if stick == "leftstick":
            self.controller_move = Vec2(xvalue, yvalue)

        elif stick == "rightstick":
            self.controller_look = Vec2(xvalue, yvalue)


if __name__ == "__main__":
    window = pyglet.window.Window(width=964, height=540, resizable=True)
    batch = pyglet.graphics.Batch()

    @window.event
    def on_draw():
        window.clear()
        batch.draw()

    glEnable(GL_DEPTH_TEST)
    glEnable(GL_CULL_FACE)

    model_logo = pyglet.model.load("logo3d.obj", batch=batch)
    model_box = pyglet.model.load("box.obj", batch=batch)

    # Camera controls the global projection & view matrixes:
    camera = FPSCamera(window, position=Vec3(0, 0, 5))

    if controllers := pyglet.input.get_controllers():
        controller = controllers[0]
        controller.open()
        controller.push_handlers(camera)

    model_logo.matrix = Mat4.from_translation(Vec3(1.75, 0, 0))
    model_box.matrix = Mat4.from_translation(Vec3(-1.75, 0, 0))

    pyglet.app.run()