bomp 1.0.5 → 1.0.5.1
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/LICENSE +21 -0
- data/README.md +74 -0
- data/bomp.gemspec +13 -0
- data/lib/bomp.rb +399 -0
- data/lib/collision/collision_aabb.rb +81 -0
- data/lib/collision/collision_sat.rb +7 -0
- data/lib/collision/collisions.rb +7 -0
- data/lib/rect.rb +82 -0
- data/lib/vector2.rb +61 -0
- data/sample/hello_world.rb +23 -0
- data/sample/sample_ruby2d.rb +89 -0
- metadata +15 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06a8f25bf7a4ca659cf83abf8fba9cf4bb305ba64bef872964aa6fbbb8ce6ba7
|
|
4
|
+
data.tar.gz: 98290ff5312e27ceaae2347085c6b5b502e2cea716f3854a7a9782694a5aff15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cd6bb90cd3d9d16662ebbc3eb1b05d62ae5bdf5f22826eedec0365c0032c8a3775ad00775cbaa788f7f3c736d39fad31c65d87838af1693c2f60e3dc113c96d
|
|
7
|
+
data.tar.gz: 584778eca57105a264de6331bcfefc36bf9203c707b892a7f5c71d966d6c6530672bcbde2bf4a7952c5d1d544955d51204e1f2a0dd5e006105af28b4ddf01c2f
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 SealtielFreak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# bomp.rb
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
A collision detection library for Ruby, works in [Ruby2D](https://github.com/ruby2d/ruby2d), [Gosu](https://github.com/gosu/gosu) and [Tic-80](https://github.com/nesbox/TIC-80)
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
A collision detection library for Ruby that makes it easy to detect collisions between 2D objects in your game or application. The library implements two popular collision detection algorithms AABB and SAT.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Ensure you have the latest versions of [Ruby](https://www.ruby-lang.org/) and [Gem](https://rubygems.org/pages/download) installed on your system to work with containerization.
|
|
17
|
+
|
|
18
|
+
### Initial Setup
|
|
19
|
+
|
|
20
|
+
1. **Clone the Repository**:
|
|
21
|
+
```
|
|
22
|
+
git clone https://github.com/sealtielfreak/bomp.rb.git
|
|
23
|
+
cd bomp.rb
|
|
24
|
+
```
|
|
25
|
+
2. **Install Gemfile**:
|
|
26
|
+
- Follow the instructions at [Install gems](https://www.bacancytechnology.com/blog/install-ruby-gems).
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
Contributions are what make the open-source community an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
31
|
+
|
|
32
|
+
### Setting Up Your Development Environment
|
|
33
|
+
|
|
34
|
+
Before you start contributing, it's important to set up your development environment. This includes installing necessary tools and configuring pre-commit hooks to ensure code quality.
|
|
35
|
+
|
|
36
|
+
#### Pre-Commit Hooks
|
|
37
|
+
|
|
38
|
+
To maintain code quality and consistency, we use pre-commit hooks. Follow these steps to set up pre-commit hooks in your local development environment:
|
|
39
|
+
|
|
40
|
+
1. **Install Pre-Commit**:
|
|
41
|
+
- Ensure you have Python installed on your system.
|
|
42
|
+
- Install pre-commit globally using pip:
|
|
43
|
+
```
|
|
44
|
+
pip install pre-commit
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Clone the Repository** (if not already done):
|
|
48
|
+
```
|
|
49
|
+
git clone https://github.com/sealtielfreak/bomp.rb.git
|
|
50
|
+
cd bomp.rb
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. **Set Up Pre-Commit Hooks**:
|
|
54
|
+
- In the root directory of the cloned repository, run:
|
|
55
|
+
```
|
|
56
|
+
pre-commit install
|
|
57
|
+
```
|
|
58
|
+
- The hook repository must have a `*.gemspec`. It will be installed via gem build `*.gemspec && gem install *.gem`. The installed package will produce an executable that will match the entry.
|
|
59
|
+
|
|
60
|
+
4. **Usage**:
|
|
61
|
+
- Pre-commit hooks will now run automatically on the files you've staged whenever you commit.
|
|
62
|
+
- You can manually run the hooks on all files in the repository at any time with:
|
|
63
|
+
```
|
|
64
|
+
pre-commit run --all-files
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Creating a Pull Request with your changes
|
|
68
|
+
|
|
69
|
+
Now that you have done some changes and want to merge them in our repository, feel free to:
|
|
70
|
+
|
|
71
|
+
1. Create your Feature Branch (`git checkout -b feature/amazing_feature`)
|
|
72
|
+
2. Commit your Changes (`git commit -m "Add some AmazingFeature"`)
|
|
73
|
+
3. Push to the Branch (`git push origin feature/amazing_feature`)
|
|
74
|
+
4. Open a Pull Request
|
data/bomp.gemspec
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.name = 'bomp'
|
|
3
|
+
s.version = '1.0.5.1'
|
|
4
|
+
s.summary = 'A collision detection library for Ruby'
|
|
5
|
+
s.description = "Ruby collision-detection library for axis-aligned rectangles, inspired by 'bump.lua' but using native Ruby capabilities"
|
|
6
|
+
s.authors = 'SealtielFreak'
|
|
7
|
+
s.email = 'SealtielFreak@yandex.com'
|
|
8
|
+
s.files = Dir['**/**'].grep_v(/.gem$/)
|
|
9
|
+
s.require_paths = %w[lib sample]
|
|
10
|
+
s.homepage = 'https://github.com/SealtielFreak/bomp.rb'
|
|
11
|
+
s.license = 'MIT'
|
|
12
|
+
s.required_ruby_version = '>= 2.5.0'
|
|
13
|
+
end
|
data/lib/bomp.rb
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
require_relative 'vector2'
|
|
2
|
+
require_relative 'rect'
|
|
3
|
+
require_relative 'collision/collision_aabb'
|
|
4
|
+
require_relative 'collision/collision_sat'
|
|
5
|
+
require_relative 'collision/collisions'
|
|
6
|
+
|
|
7
|
+
module Bomp
|
|
8
|
+
class ColliderSystem
|
|
9
|
+
attr_reader :items
|
|
10
|
+
|
|
11
|
+
# Create collision system
|
|
12
|
+
def initialize
|
|
13
|
+
@items = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param [Rect] item Add item
|
|
17
|
+
def add(item)
|
|
18
|
+
@items.push item unless @items.include? item
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param [Rect] item Remove item
|
|
22
|
+
def remove(item)
|
|
23
|
+
@items -= [item]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Sort all items
|
|
27
|
+
def sort(group = nil, reload = true)
|
|
28
|
+
raise NotImplementedError.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Clean
|
|
32
|
+
def clear!
|
|
33
|
+
@items&.clear
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reload
|
|
37
|
+
def reload!
|
|
38
|
+
raise NotImplementedError.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Restart
|
|
42
|
+
def restart!
|
|
43
|
+
raise NotImplementedError.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Cast to array
|
|
47
|
+
# @return [Array]
|
|
48
|
+
def to_a
|
|
49
|
+
self.sort
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class Lineal < ColliderSystem
|
|
54
|
+
# Initialize lineal collision system
|
|
55
|
+
def initialize
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sort(group = nil, reload = true)
|
|
60
|
+
[@items]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clear!
|
|
64
|
+
@items&.clear
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def reload!; end
|
|
68
|
+
|
|
69
|
+
def restart!; end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class QuadTree < ColliderSystem
|
|
73
|
+
class QuadNode < Rect
|
|
74
|
+
attr_reader :items
|
|
75
|
+
|
|
76
|
+
def initialize(x, y, w, h, args = {})
|
|
77
|
+
super(Vector2[x, y], Vector2[w, h])
|
|
78
|
+
limit = args[:limit_w] || 64, args[:limit_h] || 64
|
|
79
|
+
@limit_w = (limit[0]).to_f
|
|
80
|
+
@limit_h = (limit[1]).to_f
|
|
81
|
+
@limit = (args[:limit]) || 16
|
|
82
|
+
|
|
83
|
+
@items = []
|
|
84
|
+
@children = []
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def empty?
|
|
88
|
+
@items.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def subdivided?
|
|
92
|
+
!@children.empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def insert(item)
|
|
96
|
+
return unless CollisionAABB.is_overlaps? self, item
|
|
97
|
+
|
|
98
|
+
subdivided unless subdivided?
|
|
99
|
+
@children.each { |child| child&.insert item }
|
|
100
|
+
|
|
101
|
+
@items << item unless subdivided? && @items.size <= @limit
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def clear
|
|
105
|
+
@children.each { |child| child&.clear }
|
|
106
|
+
@items.clear
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def release
|
|
110
|
+
@children.each { |child| child&.release }
|
|
111
|
+
@children.clear
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def limit_size
|
|
115
|
+
[@limit_w, @limit_h]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def limit_size=(limit)
|
|
119
|
+
self.each { |c| c.limit_size = limit }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def each(&block)
|
|
123
|
+
@children.each { |c| c&.each(&block) }
|
|
124
|
+
block.call @items.clone unless @items.empty?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def each_children(&block)
|
|
128
|
+
@children.each { |c| c&.each_children(&block) }
|
|
129
|
+
block.call self unless @items.empty?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def each_children_by(item, &block)
|
|
133
|
+
@children.each { |c| c&.each_children_by(item, &block) }
|
|
134
|
+
block.call self if CollisionAABB.is_overlaps?(self, item) and not @items.empty?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def to_a
|
|
138
|
+
items = []
|
|
139
|
+
|
|
140
|
+
self.each { |item| items.push item }
|
|
141
|
+
|
|
142
|
+
items
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def group_by(item)
|
|
146
|
+
children = []
|
|
147
|
+
|
|
148
|
+
self.each_children_by(item) { |c| children.push c.items }
|
|
149
|
+
|
|
150
|
+
children
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def subdivided
|
|
156
|
+
x = self.x
|
|
157
|
+
y = self.y
|
|
158
|
+
w = self.width / 2
|
|
159
|
+
h = self.height / 2
|
|
160
|
+
|
|
161
|
+
return if self.width <= @limit_w || self.height <= @limit_h
|
|
162
|
+
|
|
163
|
+
@children = [
|
|
164
|
+
QuadNode.new(*config_child(x, y, w, h)),
|
|
165
|
+
QuadNode.new(*config_child(x + w, y, w, h)),
|
|
166
|
+
QuadNode.new(*config_child(x, y + h, w, h)),
|
|
167
|
+
QuadNode.new(*config_child(x + w, y + h, w, h))
|
|
168
|
+
]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def config_child(x, y, w, h)
|
|
172
|
+
[x, y, w, h, {
|
|
173
|
+
limit_w: @limit_w, limit_h: @limit_h,
|
|
174
|
+
limit: @limit
|
|
175
|
+
}]
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
attr_reader :child
|
|
180
|
+
|
|
181
|
+
def initialize(width, height, **opts)
|
|
182
|
+
super()
|
|
183
|
+
@opts = opts
|
|
184
|
+
@child = QuadNode.new(0, 0, width, height, @opts)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def sort(group = nil, reload = true)
|
|
188
|
+
reload! if reload
|
|
189
|
+
|
|
190
|
+
if group.nil?
|
|
191
|
+
@child.to_a
|
|
192
|
+
else
|
|
193
|
+
@child.group_by group
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def reload!
|
|
198
|
+
@child&.clear
|
|
199
|
+
@items.each { |item| @child.insert item }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def restart!
|
|
203
|
+
clear!
|
|
204
|
+
@child = QuadNode.new(0, 0, width, height, @opts)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
class CollisionInfo
|
|
209
|
+
attr_reader :item, :other, :goal, :overlaps, :response
|
|
210
|
+
|
|
211
|
+
def initialize(item, other, goal, overlaps, response)
|
|
212
|
+
@item = item
|
|
213
|
+
@other = other
|
|
214
|
+
@goal = goal
|
|
215
|
+
@overlaps = overlaps
|
|
216
|
+
@response = response
|
|
217
|
+
@normal = Vector2[
|
|
218
|
+
goal[0] <=> 0,
|
|
219
|
+
goal[1] <=> 0
|
|
220
|
+
]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def to_s
|
|
224
|
+
[@item, @other, @goal, @overlaps, @response, @normal].to_s
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def self.[](*args)
|
|
228
|
+
CollisionInfo.new *args
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class World
|
|
233
|
+
DEFAULT_FILTER = lambda { |a, b| :slide }
|
|
234
|
+
|
|
235
|
+
attr_reader :system_collision, :response
|
|
236
|
+
|
|
237
|
+
# Create world for collisions processing
|
|
238
|
+
# @param width [Integer] The first number
|
|
239
|
+
# @param height [Integer] The second number
|
|
240
|
+
def initialize(width, height, **opts)
|
|
241
|
+
@opts = opts
|
|
242
|
+
@system_collision = @opts[:system] || QuadTree.new(width, height, **opts)
|
|
243
|
+
@response = { 'bounce': lambda { |item, other, goal| CollisionAABB.bounce(item, other, goal) },
|
|
244
|
+
'cross': lambda { |item, other, goal| CollisionAABB.cross(item, other, goal) },
|
|
245
|
+
'touch': lambda { |item, other, goal| CollisionAABB.touch(item, other, goal) },
|
|
246
|
+
'slide': lambda { |item, other, goal| CollisionAABB.slide(item, other, goal) },
|
|
247
|
+
'push': lambda { |item, other, goal| CollisionAABB.push(item, other, goal) },
|
|
248
|
+
'nothing': lambda { |item, other, goal| item } }
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Add item to world
|
|
252
|
+
# @param [Rect] item Add item to world
|
|
253
|
+
def add(item)
|
|
254
|
+
@system_collision&.add item
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Remove item from world
|
|
258
|
+
# @param [Rect] item Remove item from world
|
|
259
|
+
def remove(item)
|
|
260
|
+
@system_collision&.remove item
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Select item from world
|
|
264
|
+
# @param [Rect] index Index
|
|
265
|
+
def [](index)
|
|
266
|
+
@system_collision&.items[index]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# @param [Integer] index
|
|
270
|
+
# @param [Rect] item
|
|
271
|
+
def []=(index, item)
|
|
272
|
+
@system_collision&.items[index] = item
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @return [Array, nil]
|
|
276
|
+
def items
|
|
277
|
+
@system_collision&.items
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Cast to array
|
|
281
|
+
# @return [Array]
|
|
282
|
+
def to_a
|
|
283
|
+
@system_collision&.sort || []
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Check if item include in world
|
|
287
|
+
# @param [Rect] item Check if include item
|
|
288
|
+
def include?(item)
|
|
289
|
+
items.include? item
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# At item
|
|
293
|
+
# @param [Rect] item
|
|
294
|
+
def at(item)
|
|
295
|
+
add item unless include? item
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Query point
|
|
299
|
+
# @param [Vector2] point
|
|
300
|
+
# @param [Proc] filter
|
|
301
|
+
def query_point(point, &filter)
|
|
302
|
+
_, cols = check Rect[point[0], point[1], 1, 1], &filter
|
|
303
|
+
cols
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Query rect
|
|
307
|
+
# @param [Rect] rect
|
|
308
|
+
# @param [Proc] filter
|
|
309
|
+
def query_rect(rect, &filter)
|
|
310
|
+
raise NotImplementedError.new
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Query segment
|
|
314
|
+
# @param [Vector2] p0
|
|
315
|
+
# @param [Vector2] p1
|
|
316
|
+
# @param [Proc] filter
|
|
317
|
+
def query_segment(p0, p1, &filter)
|
|
318
|
+
raise NotImplementedError.new
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Add response
|
|
322
|
+
# @param [String] name
|
|
323
|
+
def add_response(name, &block)
|
|
324
|
+
@response[name.to_sym] = block
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Move item in the world
|
|
328
|
+
# @param [Rect] item
|
|
329
|
+
# @param [Vector2] goal
|
|
330
|
+
# @param [Proc] filter
|
|
331
|
+
# @return [[Rect, Array]]
|
|
332
|
+
def move(item, goal, &filter)
|
|
333
|
+
filter = DEFAULT_FILTER if filter.nil?
|
|
334
|
+
item = self[item] if item.is_a? Integer
|
|
335
|
+
cols = []
|
|
336
|
+
|
|
337
|
+
all_sort_items = @system_collision&.sort
|
|
338
|
+
|
|
339
|
+
[Vector2[goal[0], 0], Vector2[0, goal[1]]].each do |g|
|
|
340
|
+
item.position += g
|
|
341
|
+
|
|
342
|
+
all_sort_items.each do |others|
|
|
343
|
+
next unless others.include? item
|
|
344
|
+
|
|
345
|
+
others -= [item]
|
|
346
|
+
cols += check_and_resolve item, g, others, filter
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
[item, cols]
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Check
|
|
354
|
+
# @param [Rect] item
|
|
355
|
+
# @param [Proc] filter
|
|
356
|
+
# @return [[Rect, Array]]
|
|
357
|
+
def check(item, &filter)
|
|
358
|
+
filter = DEFAULT_FILTER if filter.nil?
|
|
359
|
+
item = self[item] if item.is_a? Integer
|
|
360
|
+
cols = []
|
|
361
|
+
|
|
362
|
+
all_sort_items = @system_collision&.sort
|
|
363
|
+
|
|
364
|
+
all_sort_items.each do |others|
|
|
365
|
+
next unless others.include? item
|
|
366
|
+
|
|
367
|
+
others -= [item]
|
|
368
|
+
cols += check_and_resolve item, [0, 0], others, filter
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
[item, cols]
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Check and resolve
|
|
375
|
+
# @param [Rect] item
|
|
376
|
+
# @param [Vector2] goal
|
|
377
|
+
# @param [Array] others
|
|
378
|
+
# @param [Object] filter
|
|
379
|
+
# @return [Array]
|
|
380
|
+
private
|
|
381
|
+
def check_and_resolve(item, goal, others, filter)
|
|
382
|
+
cols = []
|
|
383
|
+
|
|
384
|
+
others.each do |other|
|
|
385
|
+
overlaps = CollisionAABB.is_overlaps? item, other
|
|
386
|
+
res = :nothing
|
|
387
|
+
|
|
388
|
+
if overlaps and goal.sum != 0
|
|
389
|
+
res = filter.call(item, other) || :nothing
|
|
390
|
+
@response[res]&.call item, other, goal
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
cols.push CollisionInfo[item, other, goal, overlaps, res]
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
cols
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CollisionAABB
|
|
4
|
+
def self.was_horizontal_aligned?(a, b)
|
|
5
|
+
a.left < b.right && a.right > b.left
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.was_vertical_aligned?(a, b)
|
|
9
|
+
a.top < b.bottom && a.bottom > b.top
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.is_overlaps?(a, b)
|
|
13
|
+
was_vertical_aligned?(a, b) && was_horizontal_aligned?(a, b)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.bounce(item, other, goal) end
|
|
17
|
+
|
|
18
|
+
def self.cross(item, other, goal)
|
|
19
|
+
x, y = goal.to_a
|
|
20
|
+
|
|
21
|
+
if x != 0
|
|
22
|
+
goal.x *= -1
|
|
23
|
+
item.position.x += x
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if y != 0
|
|
27
|
+
goal.y *= -1
|
|
28
|
+
item.position.y += y
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
item
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.touch(item, other, goal) end
|
|
35
|
+
|
|
36
|
+
def self.slide(item, other, goal)
|
|
37
|
+
x, y = goal.to_a
|
|
38
|
+
|
|
39
|
+
if x != 0
|
|
40
|
+
item.position.x += x
|
|
41
|
+
if x > 0
|
|
42
|
+
item.right = other.left
|
|
43
|
+
elsif x < 0
|
|
44
|
+
item.left = other.right
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if y != 0
|
|
49
|
+
item.position.y += y
|
|
50
|
+
if y > 0
|
|
51
|
+
item.bottom = other.top
|
|
52
|
+
elsif y < 0
|
|
53
|
+
item.top = other.bottom
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
item
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.push(item, other, goal)
|
|
61
|
+
x, y = goal.to_a
|
|
62
|
+
|
|
63
|
+
if x != 0
|
|
64
|
+
if x > 0
|
|
65
|
+
other.right = item.left
|
|
66
|
+
elsif x < 0
|
|
67
|
+
other.left = item.right
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if y != 0
|
|
72
|
+
if y > 0
|
|
73
|
+
other.bottom = item.top
|
|
74
|
+
elsif y < 0
|
|
75
|
+
other.top = item.bottom
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
item
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/rect.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rect
|
|
4
|
+
attr_accessor :position, :size
|
|
5
|
+
|
|
6
|
+
def initialize(pos, size)
|
|
7
|
+
@position = Vector2[*pos]
|
|
8
|
+
@size = Vector2[*size]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def x
|
|
12
|
+
@position.x
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def y
|
|
16
|
+
@position.y
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def width
|
|
20
|
+
@size.x
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def width=(value)
|
|
24
|
+
@size.x = value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def height
|
|
28
|
+
@size.y
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def height=(value)
|
|
32
|
+
@size.y = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def top
|
|
36
|
+
@position.y
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def top=(value)
|
|
40
|
+
@position.y = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def bottom
|
|
44
|
+
@position.y + height
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def bottom=(value)
|
|
48
|
+
@position.y += value - self.bottom
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def left
|
|
52
|
+
@position.x
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def left=(value)
|
|
56
|
+
@position.x = value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def right
|
|
60
|
+
@position.x + width
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def right=(value)
|
|
64
|
+
@position.x += value - self.right
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_s
|
|
68
|
+
self.to_a.to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def clone
|
|
72
|
+
Rect.new([@position.x, @position.y], [@size.x, @size.y])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_a
|
|
76
|
+
[@position.x, @position.y, @size.x, @size.y]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.[](x, y, w, h)
|
|
80
|
+
Rect.new([x, y], [w, h])
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/vector2.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Vector2
|
|
4
|
+
attr_accessor :x, :y
|
|
5
|
+
|
|
6
|
+
def initialize(x, y)
|
|
7
|
+
@x = x
|
|
8
|
+
@y = y
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def +(other)
|
|
12
|
+
Vector2[@x + other[0], @y + other[1]]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def -(other)
|
|
16
|
+
Vector2[@x - other[0], @y - other[1]]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def *(scalar)
|
|
20
|
+
Vector2[@x * scalar, @y * scalar]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def /(scalar)
|
|
24
|
+
Vector2[@x / scalar, @y / scalar]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def ==(other)
|
|
28
|
+
@x == other[0] && @y == other[1]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def [](index)
|
|
32
|
+
case index
|
|
33
|
+
when 0 then @x
|
|
34
|
+
when 1 then @y
|
|
35
|
+
else nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def []=(index, value)
|
|
40
|
+
case index
|
|
41
|
+
when 0 then @x = value
|
|
42
|
+
when 1 then @y = value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
self.to_a.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_a
|
|
51
|
+
[@x, @y]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def sum
|
|
55
|
+
@x + @y
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.[](x, y)
|
|
59
|
+
Vector2.new(x, y)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/bomp'
|
|
4
|
+
|
|
5
|
+
include Bomp
|
|
6
|
+
|
|
7
|
+
world = World.new 640, 480
|
|
8
|
+
|
|
9
|
+
world.add(Rect[0, 0, 10, 10])
|
|
10
|
+
world.add(Rect[2, 3, 10, 10])
|
|
11
|
+
world.add(Rect[300, 300, 10, 10])
|
|
12
|
+
|
|
13
|
+
puts 'Current position: ' + world[0].position.to_s
|
|
14
|
+
cols = world.move(0, Vector2[3, -1]) do |item, other|
|
|
15
|
+
puts 'Other collision is ' + other.to_s
|
|
16
|
+
:push
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
puts 'Current position: ' + world[0].position.to_s
|
|
20
|
+
|
|
21
|
+
cols.each do |col|
|
|
22
|
+
puts 'Collision: ' + col.to_s
|
|
23
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby2d'
|
|
4
|
+
|
|
5
|
+
require_relative '../lib/bomp'
|
|
6
|
+
include Bomp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize(vec)
|
|
10
|
+
x, y = vec.to_a
|
|
11
|
+
|
|
12
|
+
length = Math.sqrt(x ** 2 + y ** 2)
|
|
13
|
+
|
|
14
|
+
[x / length, y / length]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class MyRect < Rectangle
|
|
18
|
+
attr_reader :world, :rect
|
|
19
|
+
|
|
20
|
+
def initialize(world, **opts)
|
|
21
|
+
super **opts
|
|
22
|
+
|
|
23
|
+
@world = world
|
|
24
|
+
@rect = Rect.new Vector2[0, 0], Vector2[opts[:width] || 0, opts[:height] || 0]
|
|
25
|
+
|
|
26
|
+
@world.add rect
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def position
|
|
30
|
+
[@rect.left, @rect.top]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def position=(pos)
|
|
34
|
+
x, y = pos.to_a
|
|
35
|
+
|
|
36
|
+
self.x = x
|
|
37
|
+
self.y = y
|
|
38
|
+
|
|
39
|
+
@rect.left = x
|
|
40
|
+
@rect.top = y
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def size=(size)
|
|
44
|
+
w, h = size.to_a
|
|
45
|
+
self.width = w
|
|
46
|
+
self.height = h
|
|
47
|
+
@rect.width = w
|
|
48
|
+
@rect.height = h
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def move(goal)
|
|
52
|
+
rect, col = @world.move @rect, normalize(goal)
|
|
53
|
+
|
|
54
|
+
@rect = rect
|
|
55
|
+
self.x = @rect.x
|
|
56
|
+
self.y = @rect.y
|
|
57
|
+
self.width = @rect.width
|
|
58
|
+
self.height = @rect.height
|
|
59
|
+
|
|
60
|
+
col
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@world = World.new 640, 480
|
|
65
|
+
|
|
66
|
+
@player = MyRect.new @world, color: 'red', width: 3, height: 3
|
|
67
|
+
@walls = Array.new(5) { MyRect.new @world, color: 'random' }.map do |o|
|
|
68
|
+
o.position = [rand(0..640), rand(0..480)]
|
|
69
|
+
o.size = [rand(5...25), rand(5...25)]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
on :key_held do |event|
|
|
73
|
+
goal = Vector2[0, 0]
|
|
74
|
+
|
|
75
|
+
case event.key
|
|
76
|
+
when 'w'
|
|
77
|
+
goal.y -= 5
|
|
78
|
+
when 's'
|
|
79
|
+
goal.y += 5
|
|
80
|
+
when 'a'
|
|
81
|
+
goal.x -= 5
|
|
82
|
+
when 'd'
|
|
83
|
+
goal.x += 5
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@player.move goal
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
show
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bomp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.5
|
|
4
|
+
version: 1.0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SealtielFreak
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-03-
|
|
11
|
+
date: 2024-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Ruby collision-detection library for axis-aligned rectangles, inspired
|
|
14
14
|
by 'bump.lua' but using native Ruby capabilities
|
|
@@ -16,7 +16,18 @@ email: SealtielFreak@yandex.com
|
|
|
16
16
|
executables: []
|
|
17
17
|
extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
|
19
|
-
files:
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- bomp.gemspec
|
|
23
|
+
- lib/bomp.rb
|
|
24
|
+
- lib/collision/collision_aabb.rb
|
|
25
|
+
- lib/collision/collision_sat.rb
|
|
26
|
+
- lib/collision/collisions.rb
|
|
27
|
+
- lib/rect.rb
|
|
28
|
+
- lib/vector2.rb
|
|
29
|
+
- sample/hello_world.rb
|
|
30
|
+
- sample/sample_ruby2d.rb
|
|
20
31
|
homepage: https://github.com/SealtielFreak/bomp.rb
|
|
21
32
|
licenses:
|
|
22
33
|
- MIT
|
|
@@ -25,6 +36,7 @@ post_install_message:
|
|
|
25
36
|
rdoc_options: []
|
|
26
37
|
require_paths:
|
|
27
38
|
- lib
|
|
39
|
+
- sample
|
|
28
40
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
29
41
|
requirements:
|
|
30
42
|
- - ">="
|