Skip to content
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
57 changes: 55 additions & 2 deletions arcade/gl/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ def __init__(self, window: pyglet.window.Window, gc_mode: str = "context_gc", gl
self._point_size = 1.0
self._flags: Set[int] = set()
self._wireframe = False
# Options for cull_face
self._cull_face_options = {
"front": gl.GL_FRONT,
"back": gl.GL_BACK,
"front_and_back": gl.GL_FRONT_AND_BACK,
}
self._cull_face_options_reverse = {
gl.GL_FRONT: "front",
gl.GL_BACK: "back",
gl.GL_FRONT_AND_BACK: "front_and_back",
}

# Context GC as default. We need to call Context.gc() to free opengl resources
self._gc_mode = "context_gc"
Expand Down Expand Up @@ -618,8 +629,50 @@ def blend_func(self, value: Union[Tuple[int, int], Tuple[int, int, int, int]]):
ValueError("blend_func takes a tuple of 2 or 4 values")

# def blend_equation(self)
# def front_face(self)
# def cull_face(self)

@property
def front_face(self) -> str:
"""
Configure front face winding order of triangles.

By default the counter-clockwise winding side is the front face.
This can be set set to clockwise or counter-clockwise::

ctx.front_face = "cw"
ctx.front_face = "ccw"
"""
value = c_int()
gl.glGetIntegerv(gl.GL_FRONT_FACE, value)
return "cw" if value.value == gl.GL_CW else "ccw"

@front_face.setter
def front_face(self, value: str):
if value not in ["cw", "ccw"]:
raise ValueError("front_face must be 'cw' or 'ccw'")
gl.glFrontFace(gl.GL_CW if value == "cw" else gl.GL_CCW)

@property
def cull_face(self) -> str:
"""
The face side to cull when face culling is enabled.

By default the back face is culled. This can be set to
front, back or front_and_back::

ctx.cull_face = "front"
ctx.cull_face = "back"
ctx.cull_face = "front_and_back"
"""
value = c_int()
gl.glGetIntegerv(gl.GL_CULL_FACE_MODE, value)
return self._cull_face_options_reverse[value.value]

@cull_face.setter
def cull_face(self, value):
if value not in self._cull_face_options:
raise ValueError("cull_face must be", list(self._cull_face_options.keys()))

gl.glCullFace(self._cull_face_options[value])

@property
def wireframe(self) -> bool:
Expand Down
2 changes: 2 additions & 0 deletions doc/programming_guide/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ Changes
* Uniforms are now set using ``glProgramUniform`` instead of ``glUniform``
when the extension is available.
* Fixed many implicit type conversions in the shader code for wider support.
* Added ``front_face`` property on the context for configuring front face winding order of triangles
* Added ``cull_face`` property on the context for configuring what triangle face to cull

* :py:class:`~arcade.tilemap.TileMap`

Expand Down
32 changes: 32 additions & 0 deletions tests/unit/gl/test_opengl_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,35 @@ def test_shader_include_fail(ctx):
"""
with pytest.raises(FileNotFoundError):
ctx.shader_inc(src)


def test_front_face(ctx):
"""Test front face"""
# Default
assert ctx.front_face == "ccw"

# Set valid values
ctx.front_face = "cw"
assert ctx.front_face == "cw"
ctx.front_face = "ccw"
assert ctx.front_face == "ccw"

# Set invalid value
with pytest.raises(ValueError):
ctx.front_face = "moo"


def test_cull_face(ctx):
assert ctx.cull_face == "back"

# Set valid values
ctx.cull_face = "front"
assert ctx.cull_face == "front"
ctx.cull_face = "back"
assert ctx.cull_face == "back"
ctx.cull_face = "front_and_back"
assert ctx.cull_face == "front_and_back"

# Set invalid value
with pytest.raises(ValueError):
ctx.cull_face = "moo"