rays 0.3.11 → 0.3.13
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 +4 -4
- data/.doc/ext/rays/image.cpp +10 -0
- data/.doc/ext/rays/painter.cpp +49 -1
- data/.doc/ext/rays/shader.cpp +8 -6
- data/.github/workflows/release-gem.yml +5 -16
- data/.github/workflows/utils.rb +88 -17
- data/ChangeLog.md +26 -0
- data/README.md +164 -5
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/ext/rays/extconf.rb +3 -4
- data/ext/rays/image.cpp +11 -0
- data/ext/rays/painter.cpp +53 -1
- data/ext/rays/shader.cpp +8 -6
- data/include/rays/coord.h +6 -6
- data/include/rays/defs.h +2 -0
- data/include/rays/image.h +11 -1
- data/include/rays/painter.h +19 -0
- data/include/rays/ruby.h +2 -2
- data/include/rays/shader.h +5 -3
- data/include/rays.h +2 -2
- data/lib/rays/extension.rb +8 -2
- data/lib/rays/image.rb +2 -1
- data/lib/rays/shader.rb +13 -4
- data/rays.gemspec +3 -4
- data/src/color_space.cpp +1 -1
- data/src/coord.h +10 -0
- data/src/font.cpp +1 -1
- data/src/image.cpp +85 -10
- data/src/opengl/painter.cpp +559 -214
- data/src/opengl/render_buffer.cpp +1 -1
- data/src/opengl/sdl/opengl.cpp +6 -0
- data/src/opengl/shader.cpp +68 -51
- data/src/opengl/shader.h +10 -6
- data/src/opengl/shader_program.cpp +21 -7
- data/src/opengl/shader_program.h +2 -1
- data/src/opengl/shader_source.cpp +2 -4
- data/src/opengl/texture.cpp +18 -6
- data/src/osx/bitmap.mm +1 -1
- data/src/painter.cpp +80 -26
- data/src/painter.h +26 -13
- data/src/sdl/font.cpp +358 -9
- data/src/sdl/rays.cpp +5 -0
- data/src/texture.h +6 -0
- data/test/test_painter.rb +36 -25
- data/test/test_painter_batch.rb +254 -0
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be106158650d37c835a8396c0ce079a3bc829f6fdd10fed78153f5bba6e0246d
|
|
4
|
+
data.tar.gz: 833c9b8cd83d1e484d8424991cffd89af1a4af0a31eead4cfe8ea52db0dacead
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff626e5099d975fa64f4990d3c4ee12265612dde05b56103354f1cbbbbba6653770c183b262b757d103bf3e1c31824813d1afe66b3445c33090448587c81441a
|
|
7
|
+
data.tar.gz: 590f20a70dc9505ab609482ce98ff7fbcc9eddbdf03271597869dfadbec55a307f775327c41ffa7f630eef5d7dc7c0c6344d78070e9c5888f2c3bdef0f1de7cf
|
data/.doc/ext/rays/image.cpp
CHANGED
|
@@ -122,6 +122,15 @@ VALUE get_smooth(VALUE self)
|
|
|
122
122
|
return value(THIS->smooth());
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
static
|
|
126
|
+
VALUE compare(VALUE self, VALUE other)
|
|
127
|
+
{
|
|
128
|
+
CHECK;
|
|
129
|
+
auto* a = THIS->self.get();
|
|
130
|
+
auto* b = to<const Rays::Image*>(other)->self.get();
|
|
131
|
+
return value(a < b ? -1 : a > b ? 1 : 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
125
134
|
static
|
|
126
135
|
VALUE load(VALUE self, VALUE path)
|
|
127
136
|
{
|
|
@@ -149,6 +158,7 @@ Init_rays_image ()
|
|
|
149
158
|
rb_define_private_method(cImage, "get_bitmap", RUBY_METHOD_FUNC(get_bitmap), 1);
|
|
150
159
|
rb_define_method(cImage, "smooth=", RUBY_METHOD_FUNC(set_smooth), 1);
|
|
151
160
|
rb_define_method(cImage, "smooth", RUBY_METHOD_FUNC(get_smooth), 0);
|
|
161
|
+
cImage.define_method("<=>", compare);
|
|
152
162
|
cImage.define_module_function("load!", load);
|
|
153
163
|
}
|
|
154
164
|
|
data/.doc/ext/rays/painter.cpp
CHANGED
|
@@ -250,7 +250,17 @@ VALUE image(VALUE self)
|
|
|
250
250
|
CHECK;
|
|
251
251
|
check_arg_count(__FILE__, __LINE__, "Painter#image", argc, 1, 3, 5, 7, 9);
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
RUCY_SYM(to_image);
|
|
254
|
+
std::unique_ptr<Rays::Image> pimage;
|
|
255
|
+
|
|
256
|
+
const Rays::Image* image = NULL;
|
|
257
|
+
if (argv[0].is_a(Rays::image_class()))
|
|
258
|
+
image = to<Rays::Image*>(argv[0]);
|
|
259
|
+
else if (argv[0].respond_to(to_image))
|
|
260
|
+
{
|
|
261
|
+
pimage.reset(new Rays::Image(to<const Rays::Image&>(argv[0].call(to_image))));
|
|
262
|
+
image = pimage.get();
|
|
263
|
+
}
|
|
254
264
|
if (!image)
|
|
255
265
|
argument_error(__FILE__, __LINE__, "%s is not an image.", argv[0].inspect().c_str());
|
|
256
266
|
|
|
@@ -742,6 +752,38 @@ VALUE pop_matrix(VALUE self)
|
|
|
742
752
|
}
|
|
743
753
|
|
|
744
754
|
|
|
755
|
+
static
|
|
756
|
+
VALUE set_debug(VALUE self, VALUE debug)
|
|
757
|
+
{
|
|
758
|
+
CHECK;
|
|
759
|
+
if (debug)
|
|
760
|
+
THIS->remove_flag(Rays::Painter::FLAG_BATCHING);
|
|
761
|
+
else
|
|
762
|
+
THIS-> add_flag(Rays::Painter::FLAG_BATCHING);
|
|
763
|
+
return debug;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
static
|
|
767
|
+
VALUE get_debug(VALUE self)
|
|
768
|
+
{
|
|
769
|
+
CHECK;
|
|
770
|
+
return value(!THIS->has_flag(Rays::Painter::FLAG_BATCHING));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
static
|
|
774
|
+
VALUE set_global_debug(VALUE self, VALUE debug)
|
|
775
|
+
{
|
|
776
|
+
Rays::Painter::set_debug(debug);
|
|
777
|
+
return debug;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
static
|
|
781
|
+
VALUE get_global_debug(VALUE self)
|
|
782
|
+
{
|
|
783
|
+
return value(Rays::Painter::debug());
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
|
|
745
787
|
static Class cPainter;
|
|
746
788
|
|
|
747
789
|
void
|
|
@@ -822,6 +864,12 @@ Init_rays_painter ()
|
|
|
822
864
|
rb_define_method(cPainter, "matrix", RUBY_METHOD_FUNC(get_matrix), 0);
|
|
823
865
|
rb_define_method(cPainter, "push_matrix", RUBY_METHOD_FUNC(push_matrix), 0);
|
|
824
866
|
rb_define_method(cPainter, "pop_matrix", RUBY_METHOD_FUNC(pop_matrix), 0);
|
|
867
|
+
|
|
868
|
+
rb_define_method(cPainter, "debug=", RUBY_METHOD_FUNC(set_debug), 1);
|
|
869
|
+
cPainter.define_method("debug?", get_debug);
|
|
870
|
+
|
|
871
|
+
rb_define_singleton_method(cPainter, "debug=", RUBY_METHOD_FUNC(set_global_debug), 1);
|
|
872
|
+
cPainter.define_singleton_method("debug?", get_global_debug);
|
|
825
873
|
}
|
|
826
874
|
|
|
827
875
|
|
data/.doc/ext/rays/shader.cpp
CHANGED
|
@@ -66,15 +66,17 @@ make_env (const Value& names, const Value& ignore_no_uniform_location_error)
|
|
|
66
66
|
to_name_list(names, 0),
|
|
67
67
|
to_name_list(names, 1),
|
|
68
68
|
to_name_list(names, 2),
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
to_name_list(names, 3),
|
|
70
|
+
to_name_list(names, 4),
|
|
71
71
|
to_name( names, 5),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
to_name( names, 6),
|
|
73
|
+
to_name( names, 7),
|
|
74
|
+
to_name( names, 8),
|
|
75
|
+
to_name( names, 9),
|
|
76
76
|
to_name_list(names, 10),
|
|
77
77
|
to_name_list(names, 11),
|
|
78
|
+
to_name_list(names, 12),
|
|
79
|
+
to_name_list(names, 13),
|
|
78
80
|
flags);
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -4,6 +4,9 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
tags: ['v[0-9]*']
|
|
6
6
|
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
7
10
|
jobs:
|
|
8
11
|
release:
|
|
9
12
|
runs-on: macos-latest
|
|
@@ -33,23 +36,9 @@ jobs:
|
|
|
33
36
|
echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
|
|
34
37
|
|
|
35
38
|
- name: create github release
|
|
36
|
-
id: release
|
|
37
|
-
uses: actions/create-release@v1
|
|
38
39
|
env:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
tag_name: ${{ github.ref }}
|
|
42
|
-
release_name: ${{ github.ref }}
|
|
43
|
-
|
|
44
|
-
- name: upload to github release
|
|
45
|
-
uses: actions/upload-release-asset@v1
|
|
46
|
-
env:
|
|
47
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
48
|
-
with:
|
|
49
|
-
upload_url: ${{ steps.release.outputs.upload_url }}
|
|
50
|
-
asset_path: ./${{ steps.gem.outputs.path }}
|
|
51
|
-
asset_name: ${{ steps.gem.outputs.path }}
|
|
52
|
-
asset_content_type: application/zip
|
|
40
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
|
|
53
42
|
|
|
54
43
|
- name: upload to rubygems
|
|
55
44
|
env:
|
data/.github/workflows/utils.rb
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
|
|
3
|
+
ALL_REPO = 'xord/all'
|
|
4
|
+
ALL_DIR = '../all'
|
|
5
|
+
ALL_FETCH_DEPTH = 100
|
|
6
|
+
|
|
1
7
|
RENAMES = {reflex: 'reflexion'}
|
|
2
8
|
|
|
3
9
|
def sh(cmd)
|
|
@@ -5,7 +11,7 @@ def sh(cmd)
|
|
|
5
11
|
system cmd
|
|
6
12
|
end
|
|
7
13
|
|
|
8
|
-
def setup_dependencies(
|
|
14
|
+
def setup_dependencies(only: nil)
|
|
9
15
|
gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
|
|
10
16
|
return unless gemspec_path
|
|
11
17
|
|
|
@@ -13,44 +19,109 @@ def setup_dependencies(build: true, only: nil)
|
|
|
13
19
|
name = File.basename gemspec_path, '.gemspec'
|
|
14
20
|
|
|
15
21
|
exts = File.readlines('Rakefile')
|
|
16
|
-
.map {|l| l[%r|^\s*require\W+(\w+)/extension\W+$|, 1]}
|
|
22
|
+
.map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
|
|
17
23
|
.compact
|
|
18
24
|
.reject {|ext| ext == name}
|
|
19
25
|
exts = exts & [only].flatten.map(&:to_s) if only
|
|
26
|
+
return if exts.empty?
|
|
27
|
+
|
|
28
|
+
unless setup_dependencies_via_monorepo(exts)
|
|
29
|
+
setup_dependencies_via_each_repo_by_version(gemspec, exts)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
exts.each {|ext| sh %( cd ../#{ext} && rake ext )}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def setup_dependencies_via_monorepo(exts)
|
|
36
|
+
return false unless checkout_monorepo
|
|
37
|
+
exts.each {|ext| sh %( ln -snf all/#{ext} ../#{ext} )}
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def checkout_monorepo()
|
|
42
|
+
uuid = `git log -1 --format=%B`[/^\[\[([0-9a-fA-F-]+)\]\]$/, 1]
|
|
43
|
+
return false unless uuid
|
|
44
|
+
|
|
45
|
+
commit = setup_monorepo uuid
|
|
46
|
+
return false unless commit
|
|
47
|
+
|
|
48
|
+
Dir.chdir(ALL_DIR) {sh %( git checkout -q #{commit} )}
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def setup_monorepo(uuid)
|
|
53
|
+
unless File.directory? ALL_DIR
|
|
54
|
+
url = "https://github.com/#{ALL_REPO}.git"
|
|
55
|
+
sh %( git clone --no-tags --depth #{ALL_FETCH_DEPTH} #{url} #{ALL_DIR} )
|
|
56
|
+
end
|
|
57
|
+
loop do
|
|
58
|
+
commit = find_monorepo_commit uuid
|
|
59
|
+
return commit if commit
|
|
60
|
+
|
|
61
|
+
deepened = Dir.chdir ALL_DIR do
|
|
62
|
+
before = `git rev-list --count HEAD`.to_i
|
|
63
|
+
sh %( git fetch --deepen #{ALL_FETCH_DEPTH} )
|
|
64
|
+
`git rev-list --count HEAD`.to_i > before
|
|
65
|
+
end
|
|
66
|
+
return nil unless deepened
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def find_monorepo_commit(uuid)
|
|
71
|
+
Dir.chdir ALL_DIR do
|
|
72
|
+
out = `git log origin/HEAD -F --grep="[[#{uuid}]]" --format=%H -1`.strip
|
|
73
|
+
out.empty? ? nil : out
|
|
74
|
+
end
|
|
75
|
+
end
|
|
20
76
|
|
|
77
|
+
def setup_dependencies_via_each_repo_by_version(gemspec, exts)
|
|
21
78
|
exts.each do |ext|
|
|
22
79
|
gem = RENAMES[ext.to_sym].then {|s| s || ext}
|
|
23
|
-
ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s
|
|
80
|
+
ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
|
|
24
81
|
opts = '-c advice.detachedHead=false --depth 1'
|
|
25
82
|
clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
|
|
26
83
|
|
|
27
84
|
# 'rake subtree:push' pushes all subrepos, so cloning by new tag
|
|
28
85
|
# often fails before tagging each new tag
|
|
29
86
|
sh %( #{clone} --branch v#{ver} || #{clone} )
|
|
30
|
-
sh %( cd ../#{ext} && rake ext )
|
|
31
87
|
end
|
|
32
88
|
end
|
|
33
89
|
|
|
34
90
|
def tag_versions()
|
|
35
|
-
|
|
36
|
-
|
|
91
|
+
changes = changelogs
|
|
92
|
+
tags = `git tag`.lines chomp: true
|
|
93
|
+
vers = `git log --oneline ./VERSION`
|
|
37
94
|
.lines(chomp: true)
|
|
38
95
|
.map {|line| line.split.first[/^\w+$/]}
|
|
39
|
-
.map {|
|
|
40
|
-
.select {|ver,
|
|
96
|
+
.map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
|
|
97
|
+
.select {|ver, sha| ver && sha}
|
|
41
98
|
.reverse
|
|
42
99
|
.to_h
|
|
43
100
|
|
|
44
|
-
|
|
45
|
-
.split(/^\s*##\s*\[\s*v([\d\.]+)\s*\].*$/)
|
|
46
|
-
.slice(1..-1)
|
|
47
|
-
.each_slice(2)
|
|
48
|
-
.to_h
|
|
49
|
-
.transform_values(&:strip!)
|
|
50
|
-
|
|
51
|
-
vers.to_a.reverse.each do |ver, hash|
|
|
101
|
+
vers.to_a.reverse.each do |ver, sha|
|
|
52
102
|
tag = "v#{ver}"
|
|
53
103
|
break if tags.include?(tag)
|
|
54
|
-
sh %( git tag -a -m \"#{changes[
|
|
104
|
+
sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
|
|
55
105
|
end
|
|
56
106
|
end
|
|
107
|
+
|
|
108
|
+
def release(*paths)
|
|
109
|
+
tag = ENV['GITHUB_REF']&.sub(%r|^refs/tags/|, '') || raise('GITHUB_REF tag not set')
|
|
110
|
+
notes = (changelogs[tag] || '').shellescape
|
|
111
|
+
paths = paths.flatten.join ' '
|
|
112
|
+
|
|
113
|
+
sh(%( gh release create #{tag} #{paths} --notes #{notes} )) ||
|
|
114
|
+
sh(%( gh release upload #{tag} #{paths} --clobber )) ||
|
|
115
|
+
raise('failed to upload to releases')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def changelogs()
|
|
119
|
+
File.read('ChangeLog.md')
|
|
120
|
+
.split(/^\s*##\s*\[\s*(v[\d\.]+)\s*\].*$/)
|
|
121
|
+
.slice(1..)
|
|
122
|
+
.each_slice(2)
|
|
123
|
+
.to_h
|
|
124
|
+
.transform_values(&:strip!)
|
|
125
|
+
rescue Errno::ENOENT
|
|
126
|
+
raise 'failed to get changelogs'
|
|
127
|
+
end
|
data/ChangeLog.md
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# rays ChangeLog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [v0.3.13] - 2026-05-17
|
|
5
|
+
|
|
6
|
+
- Improve batch rendering: defer flush until state actually changes
|
|
7
|
+
- Rewrite README.md
|
|
8
|
+
- CI: Migrate release-gem.yml from actions/create-release to gh release create
|
|
9
|
+
|
|
10
|
+
- Fix text rendering corruption when batching is enabled
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [v0.3.12] - 2026-05-10
|
|
14
|
+
|
|
15
|
+
- Support WebAssembly
|
|
16
|
+
- Implement batch rendering
|
|
17
|
+
- Implement WASM font rendering via HTMLCanvasElement
|
|
18
|
+
- Implement SDL_ttf font rendering for SDL backend
|
|
19
|
+
- Use GL_DEPTH_COMPONENT16 on WASM builds
|
|
20
|
+
- Rename texcoord_offset to texcoord_pixel
|
|
21
|
+
- Pin external library versions
|
|
22
|
+
- Add Image and Texture comparison operators
|
|
23
|
+
- Add Painter::set_debug / debug singleton API
|
|
24
|
+
- Pin position attribute to location 0 on macOS
|
|
25
|
+
- Simplify Painter_draw_image to fill-only
|
|
26
|
+
- Add libsdl2-ttf-dev to CI apt packages
|
|
27
|
+
- Remove deprecated has_rdoc= from gemspecs
|
|
28
|
+
|
|
29
|
+
|
|
4
30
|
## [v0.3.11] - 2026-04-17
|
|
5
31
|
|
|
6
32
|
- Move OpenGL code into src/opengl/ and introduce Renderer abstraction
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Rays - A
|
|
1
|
+
# Rays - A 2D drawing engine on OpenGL
|
|
2
2
|
|
|
3
3
|
[](https://deepwiki.com/xord/rays)
|
|
4
4
|

|
|
@@ -21,18 +21,39 @@ Thanks for your support! 🙌
|
|
|
21
21
|
|
|
22
22
|
## 🚀 About
|
|
23
23
|
|
|
24
|
-
**Rays** is a drawing engine
|
|
24
|
+
**Rays** is a hardware-accelerated 2D drawing engine for Ruby. It is built on OpenGL and exposes a retained-mode-friendly API: build `Polygon`, `Polyline`, `Image`, `Shader`, and `Font` objects, then paint them into an off-screen `Image` (or a window provided by [Reflex](https://github.com/xord/reflex)) through a `Painter`.
|
|
25
25
|
|
|
26
|
-
It is
|
|
26
|
+
It is the rendering layer used by [Reflex](https://github.com/xord/reflex), [Processing](https://github.com/xord/processing), [RubySketch](https://github.com/xord/rubysketch), and [Reight](https://github.com/xord/reight). Like the rest of the `xord/*` family, it is primarily developed for our own use, but it also works as a standalone drawing gem.
|
|
27
|
+
|
|
28
|
+
## 📋 Requirements
|
|
29
|
+
|
|
30
|
+
- Ruby **3.0.0** or later
|
|
31
|
+
- A C++ compiler with C++20 support
|
|
32
|
+
- [Xot](https://rubygems.org/gems/xot) and [Rucy](https://rubygems.org/gems/rucy) (declared as runtime dependencies)
|
|
33
|
+
- Platform graphics backend:
|
|
34
|
+
- **macOS** — AppKit, OpenGL, AVFoundation (bundled with the OS)
|
|
35
|
+
- **iOS** — UIKit, OpenGL ES, AVFoundation (bundled with the OS)
|
|
36
|
+
- **Windows** — GDI32, OpenGL32, GLEW (`MINGW_PACKAGE_PREFIX-glew`)
|
|
37
|
+
- **Linux** — `libsdl2-dev`, `libsdl2-ttf-dev`, `libglew-dev`
|
|
38
|
+
|
|
39
|
+
The following third-party libraries are cloned from GitHub and statically linked while the native extension is being built, so you do not need to install them separately:
|
|
40
|
+
|
|
41
|
+
| Library | Role |
|
|
42
|
+
| ------------------------------------------------------------- | ------------------------------------------------- |
|
|
43
|
+
| [GLM](https://github.com/g-truc/glm) | Vector / matrix math used internally |
|
|
44
|
+
| [Clipper](https://github.com/skyrpex/clipper) | Polygon Boolean operations (`+`, `-`, `&`, `\|`, `^`) |
|
|
45
|
+
| [earcut.hpp](https://github.com/mapbox/earcut.hpp) | Polygon triangulation |
|
|
46
|
+
| [splines-lib](https://github.com/andrewwillmott/splines-lib) | Curve / spline math |
|
|
47
|
+
| [stb](https://github.com/nothings/stb) (Windows / Linux only) | Image file loading |
|
|
27
48
|
|
|
28
49
|
## 📦 Installation
|
|
29
50
|
|
|
30
51
|
Add this line to your Gemfile:
|
|
31
52
|
```ruby
|
|
32
|
-
|
|
53
|
+
gem 'rays'
|
|
33
54
|
```
|
|
34
55
|
|
|
35
|
-
Then
|
|
56
|
+
Then install:
|
|
36
57
|
```bash
|
|
37
58
|
$ bundle install
|
|
38
59
|
```
|
|
@@ -42,7 +63,145 @@ Or install it directly:
|
|
|
42
63
|
$ gem install rays
|
|
43
64
|
```
|
|
44
65
|
|
|
66
|
+
`require 'rays'` automatically calls `Rays.init!` and registers `Rays.fin!` at exit. Set `$RAYS_NOAUTOINIT = true` before requiring if you want to manage the lifetime yourself.
|
|
67
|
+
|
|
68
|
+
Rays needs a current OpenGL context. When used through [Reflex](https://github.com/xord/reflex), the window creates and binds a context for you. To use Rays standalone for off-screen rendering, it allocates a hidden context automatically.
|
|
69
|
+
|
|
70
|
+
## 📚 What's Included
|
|
71
|
+
|
|
72
|
+
### Geometry and color types
|
|
73
|
+
|
|
74
|
+
| Class | Purpose |
|
|
75
|
+
| -------------------- | ---------------------------------------------------------------- |
|
|
76
|
+
| `Rays::Point` | 2D / 3D point with arithmetic operators |
|
|
77
|
+
| `Rays::Bounds` | Axis-aligned rectangle (position + size) |
|
|
78
|
+
| `Rays::Color` | RGBA color in floating-point components |
|
|
79
|
+
| `Rays::ColorSpace` | Pixel format / color space descriptor (RGBA, ARGB, GRAY, ...) |
|
|
80
|
+
| `Rays::Matrix` | 4×4 transformation matrix |
|
|
81
|
+
|
|
82
|
+
### Drawing primitives
|
|
83
|
+
|
|
84
|
+
| Class | Purpose |
|
|
85
|
+
| -------------------- | -------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `Rays::Polyline` | A single open or closed polyline; expandable into a stroked polygon |
|
|
87
|
+
| `Rays::Polygon` | One or more polylines forming a closed shape; supports Boolean ops via `+`, `-`, `&`, `\|`, `^` |
|
|
88
|
+
| `Rays::Image` | A renderable texture with an associated `Painter` for off-screen drawing |
|
|
89
|
+
| `Rays::Bitmap` | CPU-side pixel buffer that can be uploaded to / downloaded from an `Image` |
|
|
90
|
+
| `Rays::Font` | Text rendering — created from a system font name and a size |
|
|
91
|
+
| `Rays::Shader` | GLSL fragment / vertex shader with `set_uniform` / `uniform` for parameters |
|
|
92
|
+
| `Rays::Camera` | Live camera capture (per platform) rendered into an `Image` |
|
|
93
|
+
|
|
94
|
+
### The `Painter`
|
|
95
|
+
|
|
96
|
+
`Rays::Painter` is the immediate-mode drawing surface. Obtain one through `Image#painter`, then between `painter.paint do |p| ... end` (or `begin_paint` / `end_paint`) issue draw calls:
|
|
97
|
+
|
|
98
|
+
- **Shapes** — `point`, `line`, `rect`, `ellipse`, `curve`, `bezier`, `triangle`, `quad`, `polygon`
|
|
99
|
+
- **Bitmaps & text** — `image`, `text`
|
|
100
|
+
- **State** — `fill`, `stroke`, `background`, `stroke_width`, `stroke_cap`, `stroke_join`, `blend_mode`, `clip`, `font`, `texture`, `shader`
|
|
101
|
+
- **Transforms** — `translate`, `scale`, `rotate`, `set_matrix`
|
|
102
|
+
- **Push / pop** — `push(:state, :matrix)` / `pop`, or the block form `push(fill: ..., stroke: ...) { ... }`
|
|
103
|
+
|
|
104
|
+
`stroke_cap`, `stroke_join`, `blend_mode`, `texcoord_mode`, `texcoord_wrap` accept symbols (e.g. `:round`, `:miter`, `:multiply`).
|
|
105
|
+
|
|
106
|
+
### Top-level helpers
|
|
107
|
+
|
|
108
|
+
- `Rays.init!` / `Rays.fin!` — explicit lifecycle (called automatically on `require`)
|
|
109
|
+
- `Rays::Image.load(path)` / `Rays::Image#save(path)` — image file I/O
|
|
110
|
+
- `Rays.renderer_info` — debug info about the current OpenGL context
|
|
111
|
+
|
|
112
|
+
## 💡 Usage
|
|
113
|
+
|
|
114
|
+
### Draw to an off-screen image and save it
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
require 'rays'
|
|
118
|
+
|
|
119
|
+
image = Rays::Image.new(200, 200)
|
|
120
|
+
image.paint do |p|
|
|
121
|
+
p.background 0, 0, 0
|
|
122
|
+
p.fill 1, 0.4, 0.1 # orange-ish
|
|
123
|
+
p.stroke 1, 1, 1
|
|
124
|
+
p.stroke_width 4
|
|
125
|
+
|
|
126
|
+
p.rect 20, 20, 160, 160, round: 16
|
|
127
|
+
p.ellipse 100, 100, 60, 60
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
image.save 'out.png'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Compose a polygon from Boolean operations
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
require 'rays'
|
|
137
|
+
|
|
138
|
+
a = Rays::Polygon.rect(0, 0, 100, 100)
|
|
139
|
+
b = Rays::Polygon.ellipse(50, 50, 80, 80)
|
|
140
|
+
|
|
141
|
+
union = a | b # outer outline of both
|
|
142
|
+
difference = a - b # rectangle with circular bite
|
|
143
|
+
intersection = a & b # lens-shaped overlap
|
|
144
|
+
xor = a ^ b # everything except the overlap
|
|
145
|
+
|
|
146
|
+
Rays::Image.new(140, 140).paint {|p| p.fill 1; p.polygon difference }.save 'diff.png'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
(The C++ operator names map to Ruby's `|`, `-`, `&`, `^`.)
|
|
150
|
+
|
|
151
|
+
### Use a fragment shader
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
require 'rays'
|
|
155
|
+
|
|
156
|
+
invert = Rays::Shader.new <<~GLSL
|
|
157
|
+
uniform sampler2D texture;
|
|
158
|
+
varying vec4 vTexCoord;
|
|
159
|
+
void main() {
|
|
160
|
+
vec4 c = texture2D(texture, vTexCoord.xy);
|
|
161
|
+
gl_FragColor = vec4(1.0 - c.rgb, c.a);
|
|
162
|
+
}
|
|
163
|
+
GLSL
|
|
164
|
+
|
|
165
|
+
src = Rays::Image.load('photo.png')
|
|
166
|
+
out = Rays::Image.new(src.width, src.height)
|
|
167
|
+
out.paint do |p|
|
|
168
|
+
p.shader = invert
|
|
169
|
+
p.image src
|
|
170
|
+
end
|
|
171
|
+
out.save 'photo-invert.png'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Drive `push` / `pop` with attributes
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
image.paint do |p|
|
|
178
|
+
p.push(:state, :matrix, fill: [1, 0, 0]) do
|
|
179
|
+
p.translate 50, 50
|
|
180
|
+
p.rotate 30
|
|
181
|
+
p.rect -20, -20, 40, 40
|
|
182
|
+
end
|
|
183
|
+
# fill, state, and matrix are restored here
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 🛠️ Development
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
$ rake vendor # clone the external libraries into vendor/
|
|
191
|
+
$ rake lib # build the native C++ library (librays)
|
|
192
|
+
$ rake ext # build the Ruby C extension
|
|
193
|
+
$ rake test # run the test suite
|
|
194
|
+
$ rake doc # generate RDoc from C++ sources
|
|
195
|
+
$ rake # default: builds the extension
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The drawing tests render into off-screen images and compare pixels, so they require a working OpenGL context. The `test_rays_init.rb` test must run in its own process and is listed in `TESTS_ALONE`.
|
|
199
|
+
|
|
200
|
+
In the [`xord/all`](https://github.com/xord/all) monorepo you can scope by module, e.g. `rake rays test`.
|
|
201
|
+
|
|
45
202
|
## 📜 License
|
|
46
203
|
|
|
47
204
|
**Rays** is licensed under the MIT License.
|
|
48
205
|
See the [LICENSE](./LICENSE) file for details.
|
|
206
|
+
|
|
207
|
+
The third-party libraries listed above retain their own licenses (all MIT-compatible: MIT, ISC, Boost-1.0, dual-licensed MIT/Happy-Bunny for GLM, Unlicense for splines-lib).
|
data/Rakefile
CHANGED
|
@@ -16,7 +16,7 @@ TESTS_ALONE = ['test/test_rays_init.rb']
|
|
|
16
16
|
|
|
17
17
|
install_packages(
|
|
18
18
|
mingw: %w[MINGW_PACKAGE_PREFIX-glew],
|
|
19
|
-
apt: %w[libglew-dev libsdl2-dev])
|
|
19
|
+
apt: %w[libglew-dev libsdl2-dev libsdl2-ttf-dev])
|
|
20
20
|
|
|
21
21
|
use_external_library 'https://github.com/g-truc/glm',
|
|
22
22
|
tag: '1.0.1',
|
|
@@ -29,7 +29,7 @@ use_external_library 'https://github.com/skyrpex/clipper',
|
|
|
29
29
|
excludes: 'clipper/cpp/cpp_'
|
|
30
30
|
|
|
31
31
|
use_external_library 'https://github.com/mapbox/earcut.hpp',
|
|
32
|
-
|
|
32
|
+
commit: '365be263bfed498f9d3d3bf065bcb39b0d0e4ba6',
|
|
33
33
|
incdirs: 'include/mapbox',
|
|
34
34
|
srcdirs: 'NOSRC'
|
|
35
35
|
|
|
@@ -44,7 +44,7 @@ use_external_library 'https://github.com/andrewwillmott/splines-lib',
|
|
|
44
44
|
end
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
if win32? || linux?
|
|
47
|
+
if win32? || linux? || wasm?
|
|
48
48
|
use_external_library 'https://github.com/nothings/stb',
|
|
49
49
|
commit: 'ae721c50eaf761660b4f90cc590453cdb0c2acd0',
|
|
50
50
|
srcdirs: 'NOSRC'
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.13
|
data/ext/rays/extconf.rb
CHANGED
|
@@ -13,11 +13,10 @@ Xot::ExtConf.new Xot, Rucy, Rays do
|
|
|
13
13
|
setup do
|
|
14
14
|
headers << 'ruby.h'
|
|
15
15
|
libs.unshift 'gdi32', 'opengl32', 'glew32' if win32?
|
|
16
|
-
libs.unshift 'SDL2', 'GLEW', 'GL'
|
|
16
|
+
libs.unshift 'SDL2', 'SDL2_ttf', 'GLEW', 'GL' if linux? || wasm?
|
|
17
17
|
frameworks << 'AppKit' << 'OpenGL' << 'AVFoundation' if osx?
|
|
18
|
-
|
|
19
|
-
$
|
|
20
|
-
$LDFLAGS << ' -Wl,--out-implib=rays_ext.dll.a' if mingw? || cygwin?
|
|
18
|
+
$CPPFLAGS << ' -DRAYS_32BIT_PIXELS_STRING' if RUBY_PLATFORM == 'x64-mingw-ucrt'
|
|
19
|
+
$LDFLAGS << ' -Wl,--out-implib=librays.dll.a' if mingw? || cygwin?
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
create_makefile 'rays_ext'
|
data/ext/rays/image.cpp
CHANGED
|
@@ -134,6 +134,16 @@ RUCY_DEF0(get_smooth)
|
|
|
134
134
|
}
|
|
135
135
|
RUCY_END
|
|
136
136
|
|
|
137
|
+
static
|
|
138
|
+
RUCY_DEF1(compare, other)
|
|
139
|
+
{
|
|
140
|
+
CHECK;
|
|
141
|
+
auto* a = THIS->self.get();
|
|
142
|
+
auto* b = to<const Rays::Image*>(other)->self.get();
|
|
143
|
+
return value(a < b ? -1 : a > b ? 1 : 0);
|
|
144
|
+
}
|
|
145
|
+
RUCY_END
|
|
146
|
+
|
|
137
147
|
static
|
|
138
148
|
RUCY_DEF1(load, path)
|
|
139
149
|
{
|
|
@@ -162,6 +172,7 @@ Init_rays_image ()
|
|
|
162
172
|
cImage.define_private_method("get_bitmap", get_bitmap);
|
|
163
173
|
cImage.define_method("smooth=", set_smooth);
|
|
164
174
|
cImage.define_method("smooth", get_smooth);
|
|
175
|
+
cImage.define_method("<=>", compare);
|
|
165
176
|
cImage.define_module_function("load!", load);
|
|
166
177
|
}
|
|
167
178
|
|