ruby-sfml 3.0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +101 -0
- data/LICENSE.txt +21 -0
- data/README.md +245 -0
- data/ext/ruby-sfml/extconf.rb +69 -0
- data/lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt +78 -0
- data/lib/sfml/assets/fonts/DejaVuSans.ttf +0 -0
- data/lib/sfml/assets.rb +121 -0
- data/lib/sfml/audio/listener.rb +55 -0
- data/lib/sfml/audio/music.rb +88 -0
- data/lib/sfml/audio/sound.rb +102 -0
- data/lib/sfml/audio/sound_buffer.rb +38 -0
- data/lib/sfml/audio/sound_buffer_recorder.rb +71 -0
- data/lib/sfml/audio/sound_recorder.rb +30 -0
- data/lib/sfml/c/audio.rb +106 -0
- data/lib/sfml/c/graphics.rb +425 -0
- data/lib/sfml/c/network.rb +79 -0
- data/lib/sfml/c/system.rb +43 -0
- data/lib/sfml/c/window.rb +186 -0
- data/lib/sfml/c.rb +72 -0
- data/lib/sfml/game.rb +101 -0
- data/lib/sfml/graphics/blend_mode.rb +108 -0
- data/lib/sfml/graphics/circle_shape.rb +67 -0
- data/lib/sfml/graphics/color.rb +89 -0
- data/lib/sfml/graphics/convex_shape.rb +82 -0
- data/lib/sfml/graphics/font.rb +67 -0
- data/lib/sfml/graphics/image.rb +125 -0
- data/lib/sfml/graphics/rectangle_shape.rb +62 -0
- data/lib/sfml/graphics/render_states.rb +56 -0
- data/lib/sfml/graphics/render_target.rb +146 -0
- data/lib/sfml/graphics/render_texture.rb +72 -0
- data/lib/sfml/graphics/render_window.rb +154 -0
- data/lib/sfml/graphics/shader.rb +132 -0
- data/lib/sfml/graphics/sprite.rb +75 -0
- data/lib/sfml/graphics/text.rb +144 -0
- data/lib/sfml/graphics/texture.rb +79 -0
- data/lib/sfml/graphics/transform.rb +150 -0
- data/lib/sfml/graphics/transformable.rb +74 -0
- data/lib/sfml/graphics/vertex.rb +53 -0
- data/lib/sfml/graphics/vertex_array.rb +114 -0
- data/lib/sfml/graphics/view.rb +126 -0
- data/lib/sfml/network/ip_address.rb +67 -0
- data/lib/sfml/network/tcp_listener.rb +61 -0
- data/lib/sfml/network/tcp_socket.rb +74 -0
- data/lib/sfml/network/udp_socket.rb +71 -0
- data/lib/sfml/system/clock.rb +44 -0
- data/lib/sfml/system/rect.rb +64 -0
- data/lib/sfml/system/time.rb +48 -0
- data/lib/sfml/system/vector2.rb +66 -0
- data/lib/sfml/system/vector3.rb +63 -0
- data/lib/sfml/version.rb +19 -0
- data/lib/sfml/window/clipboard.rb +38 -0
- data/lib/sfml/window/cursor.rb +68 -0
- data/lib/sfml/window/event.rb +133 -0
- data/lib/sfml/window/joystick.rb +90 -0
- data/lib/sfml/window/keyboard.rb +60 -0
- data/lib/sfml/window/mouse.rb +71 -0
- data/lib/sfml/window/video_mode.rb +37 -0
- data/lib/sfml/window/window.rb +149 -0
- data/lib/sfml.rb +98 -0
- data/ruby-sfml.gemspec +38 -0
- metadata +163 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# An arbitrary convex polygon. Define its outline by listing points in
|
|
3
|
+
# order (clockwise or counter-clockwise — just stay consistent). The
|
|
4
|
+
# shape must remain convex — drawing a non-convex point set produces
|
|
5
|
+
# visual artifacts (CSFML doesn't validate).
|
|
6
|
+
#
|
|
7
|
+
# star = SFML::ConvexShape.new(
|
|
8
|
+
# points: [[60, 0], [80, 40], [120, 50], [90, 80],
|
|
9
|
+
# [100, 120], [60, 95], [20, 120], [30, 80],
|
|
10
|
+
# [0, 50], [40, 40]],
|
|
11
|
+
# fill_color: SFML::Color.yellow,
|
|
12
|
+
# )
|
|
13
|
+
# window.draw(star)
|
|
14
|
+
class ConvexShape
|
|
15
|
+
include Graphics::Transformable
|
|
16
|
+
CSFML_PREFIX = :sfConvexShape
|
|
17
|
+
|
|
18
|
+
def initialize(points: nil, **opts)
|
|
19
|
+
ptr = C::Graphics.sfConvexShape_create
|
|
20
|
+
raise Error, "sfConvexShape_create returned NULL" if ptr.null?
|
|
21
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfConvexShape_destroy))
|
|
22
|
+
|
|
23
|
+
self.points = points if points
|
|
24
|
+
self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
|
|
25
|
+
self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
|
|
26
|
+
self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
|
|
27
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
28
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
29
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
30
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def point_count = C::Graphics.sfConvexShape_getPointCount(@handle)
|
|
34
|
+
|
|
35
|
+
# Returns the polygon vertices as an Array of Vector2.
|
|
36
|
+
def points
|
|
37
|
+
n = point_count
|
|
38
|
+
(0...n).map do |i|
|
|
39
|
+
Vector2.from_native(C::Graphics.sfConvexShape_getPoint(@handle, i))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Replace every point. Accepts a list of [x, y] arrays or Vector2s.
|
|
44
|
+
def points=(list)
|
|
45
|
+
C::Graphics.sfConvexShape_setPointCount(@handle, list.length)
|
|
46
|
+
list.each_with_index do |pt, i|
|
|
47
|
+
vec = pt.is_a?(Vector2) ? pt : Vector2.new(*pt)
|
|
48
|
+
C::Graphics.sfConvexShape_setPoint(@handle, i, vec.to_native_f)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Mutate a single vertex by index.
|
|
53
|
+
def set_point(index, point)
|
|
54
|
+
vec = point.is_a?(Vector2) ? point : Vector2.new(*point)
|
|
55
|
+
C::Graphics.sfConvexShape_setPoint(@handle, Integer(index), vec.to_native_f)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def fill_color = Color.from_native(C::Graphics.sfConvexShape_getFillColor(@handle))
|
|
59
|
+
|
|
60
|
+
def fill_color=(c)
|
|
61
|
+
C::Graphics.sfConvexShape_setFillColor(@handle, c.to_native)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def outline_color = Color.from_native(C::Graphics.sfConvexShape_getOutlineColor(@handle))
|
|
65
|
+
|
|
66
|
+
def outline_color=(c)
|
|
67
|
+
C::Graphics.sfConvexShape_setOutlineColor(@handle, c.to_native)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def outline_thickness = C::Graphics.sfConvexShape_getOutlineThickness(@handle)
|
|
71
|
+
|
|
72
|
+
def outline_thickness=(t)
|
|
73
|
+
C::Graphics.sfConvexShape_setOutlineThickness(@handle, t.to_f)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
77
|
+
target._draw_native(:ConvexShape, @handle, states_ptr)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
attr_reader :handle # :nodoc:
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A typeface loaded from a TTF/OTF file.
|
|
3
|
+
#
|
|
4
|
+
# font = SFML::Font.default # bundled DejaVu Sans
|
|
5
|
+
# font = SFML::Font.load("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
|
6
|
+
# font = SFML::Font.find("DejaVuSans") # search common locations
|
|
7
|
+
class Font
|
|
8
|
+
SEARCH_PATHS = [
|
|
9
|
+
"/usr/share/fonts",
|
|
10
|
+
"/usr/local/share/fonts",
|
|
11
|
+
"/Library/Fonts",
|
|
12
|
+
"/System/Library/Fonts",
|
|
13
|
+
File.expand_path("~/Library/Fonts"),
|
|
14
|
+
"C:/Windows/Fonts",
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
# Path to the font ruby-sfml ships with: DejaVu Sans (Bitstream Vera
|
|
18
|
+
# license, redistributable). See lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt.
|
|
19
|
+
DEFAULT_PATH = File.expand_path("../assets/fonts/DejaVuSans.ttf", __dir__).freeze
|
|
20
|
+
|
|
21
|
+
def self.load(path)
|
|
22
|
+
ptr = C::Graphics.sfFont_createFromFile(path.to_s)
|
|
23
|
+
raise Error, "Could not load font from #{path.inspect}" if ptr.null?
|
|
24
|
+
|
|
25
|
+
font = allocate
|
|
26
|
+
font.send(:_take_ownership, ptr)
|
|
27
|
+
font
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The default font bundled with ruby-sfml. Use this when you don't
|
|
31
|
+
# care which typeface as long as you can render text — examples,
|
|
32
|
+
# debug HUDs, prototypes. Memoized so subsequent calls return the
|
|
33
|
+
# same Font instance.
|
|
34
|
+
def self.default
|
|
35
|
+
@default ||= load(DEFAULT_PATH)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Look up a font on disk by basename (with or without extension). Useful
|
|
39
|
+
# for examples that should "just run" — production code should ship its
|
|
40
|
+
# own font files. Returns nil if nothing is found.
|
|
41
|
+
def self.find(name)
|
|
42
|
+
target = name.to_s.downcase.sub(/\.(ttf|otf)\z/, "")
|
|
43
|
+
SEARCH_PATHS.each do |dir|
|
|
44
|
+
next unless File.directory?(dir)
|
|
45
|
+
match = Dir.glob(File.join(dir, "**", "*.{ttf,otf}")).find do |path|
|
|
46
|
+
File.basename(path).downcase.sub(/\.(ttf|otf)\z/, "") == target
|
|
47
|
+
end
|
|
48
|
+
return load(match) if match
|
|
49
|
+
end
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def smooth? = C::Graphics.sfFont_isSmooth(@handle)
|
|
54
|
+
|
|
55
|
+
def smooth=(value)
|
|
56
|
+
C::Graphics.sfFont_setSmooth(@handle, !!value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
attr_reader :handle # :nodoc:
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def _take_ownership(ptr)
|
|
64
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfFont_destroy))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# CPU-side bitmap. Lives in main memory and supports per-pixel reads /
|
|
3
|
+
# writes — handy for procedural generation, screenshots, masks, and
|
|
4
|
+
# anything that needs to inspect or modify pixel data before upload.
|
|
5
|
+
#
|
|
6
|
+
# img = SFML::Image.new(800, 600, fill: SFML::Color.cornflower_blue)
|
|
7
|
+
# img = SFML::Image.load("assets/hero.png")
|
|
8
|
+
#
|
|
9
|
+
# img[10, 20] = SFML::Color.red
|
|
10
|
+
# img[10, 20] #=> Color(255, 0, 0, 255)
|
|
11
|
+
#
|
|
12
|
+
# img.flip_vertically
|
|
13
|
+
# img.save("out.png")
|
|
14
|
+
#
|
|
15
|
+
# Convert to a GPU-side texture for drawing:
|
|
16
|
+
#
|
|
17
|
+
# tex = SFML::Texture.from_image(img)
|
|
18
|
+
# sprite = SFML::Sprite.new(tex)
|
|
19
|
+
class Image
|
|
20
|
+
# Create a blank image of the given size. With `fill:` it's filled
|
|
21
|
+
# with that colour; without, with transparent black.
|
|
22
|
+
def initialize(width, height, fill: nil)
|
|
23
|
+
size = C::System::Vector2u.new
|
|
24
|
+
size[:x] = Integer(width)
|
|
25
|
+
size[:y] = Integer(height)
|
|
26
|
+
|
|
27
|
+
ptr = if fill
|
|
28
|
+
C::Graphics.sfImage_createFromColor(size, fill.to_native)
|
|
29
|
+
else
|
|
30
|
+
C::Graphics.sfImage_create(size)
|
|
31
|
+
end
|
|
32
|
+
raise Error, "sfImage_create returned NULL" if ptr.null?
|
|
33
|
+
_take_ownership(ptr)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.load(path)
|
|
37
|
+
ptr = C::Graphics.sfImage_createFromFile(path.to_s)
|
|
38
|
+
raise Error, "Could not load image from #{path.inspect}" if ptr.null?
|
|
39
|
+
img = allocate
|
|
40
|
+
img.send(:_take_ownership, ptr)
|
|
41
|
+
img
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Build an image from a raw RGBA byte string. `pixels` must be
|
|
45
|
+
# exactly width*height*4 bytes, row-major from the top-left.
|
|
46
|
+
def self.from_pixels(width, height, pixels)
|
|
47
|
+
expected = Integer(width) * Integer(height) * 4
|
|
48
|
+
raise ArgumentError, "expected #{expected} bytes, got #{pixels.bytesize}" if pixels.bytesize != expected
|
|
49
|
+
|
|
50
|
+
buf = FFI::MemoryPointer.new(:uint8, expected)
|
|
51
|
+
buf.write_bytes(pixels)
|
|
52
|
+
|
|
53
|
+
size = C::System::Vector2u.new
|
|
54
|
+
size[:x] = Integer(width)
|
|
55
|
+
size[:y] = Integer(height)
|
|
56
|
+
ptr = C::Graphics.sfImage_createFromPixels(size, buf)
|
|
57
|
+
raise Error, "sfImage_createFromPixels returned NULL" if ptr.null?
|
|
58
|
+
|
|
59
|
+
img = allocate
|
|
60
|
+
img.send(:_take_ownership, ptr)
|
|
61
|
+
img
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def size
|
|
65
|
+
Vector2.from_native(C::Graphics.sfImage_getSize(@handle))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def width = size.x
|
|
69
|
+
def height = size.y
|
|
70
|
+
|
|
71
|
+
# Read the colour of a single pixel.
|
|
72
|
+
def [](x, y)
|
|
73
|
+
coord = C::System::Vector2u.new
|
|
74
|
+
coord[:x] = Integer(x); coord[:y] = Integer(y)
|
|
75
|
+
Color.from_native(C::Graphics.sfImage_getPixel(@handle, coord))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Write a single pixel.
|
|
79
|
+
def []=(x, y, color)
|
|
80
|
+
coord = C::System::Vector2u.new
|
|
81
|
+
coord[:x] = Integer(x); coord[:y] = Integer(y)
|
|
82
|
+
C::Graphics.sfImage_setPixel(@handle, coord, color.to_native)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Write the entire pixel buffer back as a Ruby String. Useful for
|
|
86
|
+
# piping to image-processing libraries or writing custom file formats.
|
|
87
|
+
# Format: width*height*4 bytes, RGBA, row-major from top-left.
|
|
88
|
+
def pixels
|
|
89
|
+
ptr = C::Graphics.sfImage_getPixelsPtr(@handle)
|
|
90
|
+
ptr.read_bytes(width * height * 4)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def save(path)
|
|
94
|
+
ok = C::Graphics.sfImage_saveToFile(@handle, path.to_s)
|
|
95
|
+
raise Error, "Could not save image to #{path.inspect}" unless ok
|
|
96
|
+
path
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Replace any pixel matching `color` with that colour at `alpha`
|
|
100
|
+
# opacity — typical use is to turn a fixed background colour
|
|
101
|
+
# transparent: img.mask_color!(SFML::Color.magenta, alpha: 0).
|
|
102
|
+
def mask_color!(color, alpha: 0)
|
|
103
|
+
C::Graphics.sfImage_createMaskFromColor(@handle, color.to_native, Integer(alpha))
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def flip_horizontally
|
|
108
|
+
C::Graphics.sfImage_flipHorizontally(@handle)
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def flip_vertically
|
|
113
|
+
C::Graphics.sfImage_flipVertically(@handle)
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
attr_reader :handle # :nodoc:
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def _take_ownership(ptr)
|
|
122
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfImage_destroy))
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# An axis-aligned filled rectangle.
|
|
3
|
+
#
|
|
4
|
+
# wall = SFML::RectangleShape.new(
|
|
5
|
+
# size: [200, 40],
|
|
6
|
+
# position: [100, 500],
|
|
7
|
+
# fill_color: SFML::Color["#444"],
|
|
8
|
+
# )
|
|
9
|
+
# window.draw(wall)
|
|
10
|
+
class RectangleShape
|
|
11
|
+
include Graphics::Transformable
|
|
12
|
+
CSFML_PREFIX = :sfRectangleShape
|
|
13
|
+
|
|
14
|
+
def initialize(size:, **opts)
|
|
15
|
+
ptr = C::Graphics.sfRectangleShape_create
|
|
16
|
+
raise Error, "sfRectangleShape_create returned NULL" if ptr.null?
|
|
17
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRectangleShape_destroy))
|
|
18
|
+
|
|
19
|
+
self.size = size
|
|
20
|
+
self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
|
|
21
|
+
self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
|
|
22
|
+
self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
|
|
23
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
24
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
25
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
26
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def size
|
|
30
|
+
Vector2.from_native(C::Graphics.sfRectangleShape_getSize(@handle))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def size=(value)
|
|
34
|
+
vec = value.is_a?(Vector2) ? value : Vector2.new(*value)
|
|
35
|
+
C::Graphics.sfRectangleShape_setSize(@handle, vec.to_native_f)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fill_color = Color.from_native(C::Graphics.sfRectangleShape_getFillColor(@handle))
|
|
39
|
+
|
|
40
|
+
def fill_color=(c)
|
|
41
|
+
C::Graphics.sfRectangleShape_setFillColor(@handle, c.to_native)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def outline_color = Color.from_native(C::Graphics.sfRectangleShape_getOutlineColor(@handle))
|
|
45
|
+
|
|
46
|
+
def outline_color=(c)
|
|
47
|
+
C::Graphics.sfRectangleShape_setOutlineColor(@handle, c.to_native)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def outline_thickness = C::Graphics.sfRectangleShape_getOutlineThickness(@handle)
|
|
51
|
+
|
|
52
|
+
def outline_thickness=(t)
|
|
53
|
+
C::Graphics.sfRectangleShape_setOutlineThickness(@handle, t.to_f)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
57
|
+
target._draw_native(:RectangleShape, @handle, states_ptr)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
attr_reader :handle # :nodoc:
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# The state passed alongside a draw call: blend mode, texture, shader,
|
|
3
|
+
# transform, coordinate type. You rarely instantiate this directly —
|
|
4
|
+
# `window.draw(...)` accepts `blend_mode:`, `texture:`, etc. shortcuts
|
|
5
|
+
# that build a RenderStates internally. Construct one yourself when
|
|
6
|
+
# you need to keep the same combination across many draws:
|
|
7
|
+
#
|
|
8
|
+
# states = SFML::RenderStates.new(
|
|
9
|
+
# blend_mode: SFML::BlendMode::ADD,
|
|
10
|
+
# texture: glow_texture,
|
|
11
|
+
# )
|
|
12
|
+
# window.draw(va, render_states: states)
|
|
13
|
+
# window.draw(other, render_states: states)
|
|
14
|
+
#
|
|
15
|
+
# All fields default to the CSFML `sfRenderStates_default` value.
|
|
16
|
+
class RenderStates
|
|
17
|
+
COORDINATE_TYPES = %i[normalized pixels].freeze
|
|
18
|
+
COORDINATE_INDEX = COORDINATE_TYPES.each_with_index.to_h.freeze
|
|
19
|
+
|
|
20
|
+
attr_reader :blend_mode, :texture, :shader, :coordinate_type
|
|
21
|
+
|
|
22
|
+
def initialize(blend_mode: nil, texture: nil, shader: nil, coordinate_type: :normalized)
|
|
23
|
+
@blend_mode = blend_mode
|
|
24
|
+
@texture = texture
|
|
25
|
+
@shader = shader # placeholder until SFML::Shader lands in the next P1 step
|
|
26
|
+
@coordinate_type = coordinate_type
|
|
27
|
+
raise ArgumentError, "unknown coordinate_type: #{coordinate_type.inspect}" unless COORDINATE_INDEX.key?(coordinate_type)
|
|
28
|
+
freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @!visibility private
|
|
32
|
+
# Allocate and populate a sfRenderStates buffer. Returns an
|
|
33
|
+
# FFI::MemoryPointer ready to pass as the `const sfRenderStates*`
|
|
34
|
+
# argument of a CSFML draw function.
|
|
35
|
+
def to_native_pointer
|
|
36
|
+
buffer = C::Graphics::RenderStates.new
|
|
37
|
+
# Start from the CSFML default so we get sane stencil + transform.
|
|
38
|
+
C::Graphics.sfRenderStates_default.pointer.read_bytes(C::Graphics::RenderStates.size)
|
|
39
|
+
.then { |bytes| buffer.pointer.write_bytes(bytes) }
|
|
40
|
+
|
|
41
|
+
@blend_mode&.populate(buffer[:blend_mode])
|
|
42
|
+
buffer[:coordinate_type] = COORDINATE_INDEX[@coordinate_type]
|
|
43
|
+
buffer[:texture] = @texture ? @texture.handle : nil
|
|
44
|
+
buffer[:shader] = @shader ? @shader.handle : nil
|
|
45
|
+
buffer.pointer
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Convenience: build a RenderStates from the same shortcut kwargs
|
|
49
|
+
# that RenderTarget#draw accepts. Returns nil if no opts given (so
|
|
50
|
+
# the draw call uses the CSFML default without allocating anything).
|
|
51
|
+
def self.from_draw_opts(opts)
|
|
52
|
+
return nil if opts.empty?
|
|
53
|
+
new(**opts)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Graphics
|
|
3
|
+
# Shared behaviour between SFML::RenderWindow and SFML::RenderTexture.
|
|
4
|
+
# The two CSFML APIs are near-mirrors of each other (sfRenderWindow_*
|
|
5
|
+
# vs sfRenderTexture_*), so the Ruby side dispatches by the includer's
|
|
6
|
+
# CSFML_PREFIX constant. Adding a new render target only takes:
|
|
7
|
+
#
|
|
8
|
+
# class NewTarget
|
|
9
|
+
# include Graphics::RenderTarget
|
|
10
|
+
# CSFML_PREFIX = :sfNewTarget
|
|
11
|
+
# ...
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# Drawables call `target._draw_native(:CircleShape, handle)` to dispatch
|
|
15
|
+
# through the right CSFML draw function for whichever target they're
|
|
16
|
+
# being rendered to.
|
|
17
|
+
module RenderTarget
|
|
18
|
+
def clear(color = Color::BLACK)
|
|
19
|
+
_csfml(:clear, @handle, color.to_native)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def display
|
|
24
|
+
_csfml(:display, @handle)
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Polymorphic draw: any drawable with a #draw_on(target, [states])
|
|
29
|
+
# method. Built-in drawables call back into target._draw_native.
|
|
30
|
+
#
|
|
31
|
+
# Pass shortcut kwargs to apply render states without instantiating
|
|
32
|
+
# SFML::RenderStates yourself:
|
|
33
|
+
#
|
|
34
|
+
# window.draw(va, texture: tile_texture)
|
|
35
|
+
# window.draw(glow, blend_mode: SFML::BlendMode::ADD)
|
|
36
|
+
# window.draw(thing, texture: tex, blend_mode: SFML::BlendMode::ADD)
|
|
37
|
+
#
|
|
38
|
+
# Or pass a pre-built object for re-use across calls:
|
|
39
|
+
#
|
|
40
|
+
# window.draw(thing, render_states: shared_states)
|
|
41
|
+
def draw(drawable, render_states: nil, **opts)
|
|
42
|
+
states = render_states || RenderStates.from_draw_opts(opts)
|
|
43
|
+
states_ptr = states&.to_native_pointer
|
|
44
|
+
drawable.draw_on(self, states_ptr)
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @!visibility private
|
|
49
|
+
# Invoke the right CSFML draw function for this target + drawable
|
|
50
|
+
# kind. `kind` is the suffix after `draw`: e.g. :CircleShape →
|
|
51
|
+
# sfRenderWindow_drawCircleShape on a window, sfRenderTexture_drawCircleShape
|
|
52
|
+
# on a texture. `states_ptr` may be nil for default render states.
|
|
53
|
+
def _draw_native(kind, drawable_handle, states_ptr = nil)
|
|
54
|
+
C::Graphics.public_send(
|
|
55
|
+
:"#{self.class::CSFML_PREFIX}_draw#{kind}",
|
|
56
|
+
@handle, drawable_handle, states_ptr,
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Draw a one-shot batch of vertices without allocating a
|
|
61
|
+
# SFML::VertexArray. Useful for tight inner loops (a few dozen
|
|
62
|
+
# primitives per frame, where the VertexArray's per-object
|
|
63
|
+
# bookkeeping is itself the cost).
|
|
64
|
+
#
|
|
65
|
+
# window.draw_primitives(
|
|
66
|
+
# [SFML::Vertex.new([0, 0], color: SFML::Color.red),
|
|
67
|
+
# SFML::Vertex.new([100, 0], color: SFML::Color.green),
|
|
68
|
+
# SFML::Vertex.new([50, 80], color: SFML::Color.blue)],
|
|
69
|
+
# :triangles,
|
|
70
|
+
# )
|
|
71
|
+
#
|
|
72
|
+
# Accepts the same render-states kwargs as #draw.
|
|
73
|
+
def draw_primitives(vertices, primitive_type = :points, render_states: nil, **opts)
|
|
74
|
+
type_code = VertexArray::PRIMITIVE_INDEX.fetch(primitive_type) do
|
|
75
|
+
raise ArgumentError, "Unknown primitive type: #{primitive_type.inspect}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Pack the vertex array into a contiguous buffer.
|
|
79
|
+
n = vertices.length
|
|
80
|
+
buf = FFI::MemoryPointer.new(C::Graphics::Vertex, n)
|
|
81
|
+
vertices.each_with_index do |v, i|
|
|
82
|
+
slot = C::Graphics::Vertex.new(buf + i * C::Graphics::Vertex.size)
|
|
83
|
+
slot[:position][:x] = v.position.x.to_f
|
|
84
|
+
slot[:position][:y] = v.position.y.to_f
|
|
85
|
+
slot[:color][:r] = v.color.r
|
|
86
|
+
slot[:color][:g] = v.color.g
|
|
87
|
+
slot[:color][:b] = v.color.b
|
|
88
|
+
slot[:color][:a] = v.color.a
|
|
89
|
+
slot[:tex_coords][:x] = v.tex_coords.x.to_f
|
|
90
|
+
slot[:tex_coords][:y] = v.tex_coords.y.to_f
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
states = render_states || RenderStates.from_draw_opts(opts)
|
|
94
|
+
states_ptr = states&.to_native_pointer
|
|
95
|
+
|
|
96
|
+
C::Graphics.public_send(
|
|
97
|
+
:"#{self.class::CSFML_PREFIX}_drawPrimitives",
|
|
98
|
+
@handle, buf, n, type_code, states_ptr,
|
|
99
|
+
)
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def view=(value)
|
|
104
|
+
raise ArgumentError, "#{self.class}#view= requires a SFML::View" unless value.is_a?(View)
|
|
105
|
+
_csfml(:setView, @handle, value.handle)
|
|
106
|
+
@view = value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def view
|
|
110
|
+
View.from_borrowed(_csfml(:getView, @handle))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The default 1:1 view that matches the target's pixel size.
|
|
114
|
+
# Memoised — see the comment in render_window.rb for why.
|
|
115
|
+
def default_view
|
|
116
|
+
@default_view ||= View.from_borrowed(_csfml(:getDefaultView, @handle))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def map_pixel_to_coords(pixel, view: nil)
|
|
120
|
+
vec = C::System::Vector2i.new
|
|
121
|
+
px, py = pixel.is_a?(Vector2) ? [pixel.x, pixel.y] : pixel
|
|
122
|
+
vec[:x] = Integer(px); vec[:y] = Integer(py)
|
|
123
|
+
|
|
124
|
+
v_handle = view ? view.handle : _csfml(:getView, @handle)
|
|
125
|
+
result = _csfml(:mapPixelToCoords, @handle, vec, v_handle)
|
|
126
|
+
Vector2.new(result[:x], result[:y])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def map_coords_to_pixel(coord, view: nil)
|
|
130
|
+
vec = C::System::Vector2f.new
|
|
131
|
+
cx, cy = coord.is_a?(Vector2) ? [coord.x, coord.y] : coord
|
|
132
|
+
vec[:x] = cx.to_f; vec[:y] = cy.to_f
|
|
133
|
+
|
|
134
|
+
v_handle = view ? view.handle : _csfml(:getView, @handle)
|
|
135
|
+
result = _csfml(:mapCoordsToPixel, @handle, vec, v_handle)
|
|
136
|
+
Vector2.new(result[:x], result[:y])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def _csfml(suffix, *args)
|
|
142
|
+
C::Graphics.public_send(:"#{self.class::CSFML_PREFIX}_#{suffix}", *args)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Off-screen rendering target. Anything you can draw on a RenderWindow
|
|
3
|
+
# — sprites, shapes, text, vertex arrays — you can also draw on a
|
|
4
|
+
# RenderTexture and then use its #texture as a Sprite source. Typical
|
|
5
|
+
# uses: minimaps, post-processing, motion-blur trails, custom UIs that
|
|
6
|
+
# composite multiple layers.
|
|
7
|
+
#
|
|
8
|
+
# rt = SFML::RenderTexture.new(400, 300)
|
|
9
|
+
# rt.clear(SFML::Color.cornflower_blue)
|
|
10
|
+
# rt.draw(sprite)
|
|
11
|
+
# rt.draw(text)
|
|
12
|
+
# rt.display
|
|
13
|
+
#
|
|
14
|
+
# sprite = SFML::Sprite.new(rt.texture)
|
|
15
|
+
# window.draw(sprite)
|
|
16
|
+
#
|
|
17
|
+
# Note: rt.texture returns a *borrowed* reference owned by the
|
|
18
|
+
# RenderTexture. Keep the RenderTexture alive for as long as anything
|
|
19
|
+
# uses its texture.
|
|
20
|
+
class RenderTexture
|
|
21
|
+
include Graphics::RenderTarget
|
|
22
|
+
CSFML_PREFIX = :sfRenderTexture
|
|
23
|
+
|
|
24
|
+
def initialize(width, height, smooth: false, repeated: false)
|
|
25
|
+
size = C::System::Vector2u.new
|
|
26
|
+
size[:x] = Integer(width)
|
|
27
|
+
size[:y] = Integer(height)
|
|
28
|
+
|
|
29
|
+
ptr = C::Graphics.sfRenderTexture_create(size, nil)
|
|
30
|
+
raise Error, "sfRenderTexture_create returned NULL" if ptr.null?
|
|
31
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRenderTexture_destroy))
|
|
32
|
+
|
|
33
|
+
self.smooth = smooth
|
|
34
|
+
self.repeated = repeated
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def size
|
|
38
|
+
v = C::Graphics.sfRenderTexture_getSize(@handle)
|
|
39
|
+
Vector2.new(v[:x], v[:y])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def smooth? = C::Graphics.sfRenderTexture_isSmooth(@handle)
|
|
43
|
+
|
|
44
|
+
def smooth=(value)
|
|
45
|
+
C::Graphics.sfRenderTexture_setSmooth(@handle, !!value)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def repeated? = C::Graphics.sfRenderTexture_isRepeated(@handle)
|
|
49
|
+
|
|
50
|
+
def repeated=(value)
|
|
51
|
+
C::Graphics.sfRenderTexture_setRepeated(@handle, !!value)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The Texture this RenderTexture is rendering into. Borrowed — its
|
|
55
|
+
# lifetime is bounded by `self`. Memoised so repeated calls return
|
|
56
|
+
# the same Ruby wrapper.
|
|
57
|
+
def texture
|
|
58
|
+
@texture ||= begin
|
|
59
|
+
ptr = C::Graphics.sfRenderTexture_getTexture(@handle)
|
|
60
|
+
raise Error, "sfRenderTexture_getTexture returned NULL" if ptr.null?
|
|
61
|
+
# Borrowed — RenderTexture owns the underlying sf::Texture, so
|
|
62
|
+
# we wrap with a raw pointer (no AutoPointer / no destructor).
|
|
63
|
+
# Sprite.new(@texture) will still get a valid handle through it.
|
|
64
|
+
tex = Texture.allocate
|
|
65
|
+
tex.instance_variable_set(:@handle, ptr)
|
|
66
|
+
tex
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attr_reader :handle # :nodoc:
|
|
71
|
+
end
|
|
72
|
+
end
|