voxelamming_gem 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,524 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "voxelamming_gem/version"
4
+ require 'json'
5
+ require 'faye/websocket'
6
+ require 'eventmachine'
7
+ require 'date'
8
+ require_relative 'matrix_util'
9
+ require 'csv'
10
+
11
+ module VoxelammingGem
12
+ class Error < StandardError; end
13
+
14
+ # test
15
+ def self.greet(name)
16
+ puts "Hello, #{name}! I'm Ruby!"
17
+ end
18
+
19
+ # Main process
20
+ class BuildBox
21
+ @@texture_names = ["grass", "stone", "dirt", "planks", "bricks"]
22
+
23
+ def initialize(room_name)
24
+ @room_name = room_name
25
+ @is_allowed_matrix = 0
26
+ @saved_matrices = []
27
+ @translation = [0, 0, 0, 0, 0, 0]
28
+ @matrix_translation = [0, 0, 0, 0, 0, 0]
29
+ @frame_translations = []
30
+ @global_animation = [0, 0, 0, 0, 0, 0, 1, 0]
31
+ @animation = [0, 0, 0, 0, 0, 0, 1, 0]
32
+ @boxes = []
33
+ @frames = []
34
+ @sentence = []
35
+ @lights = []
36
+ @commands = []
37
+ @size = 1
38
+ @shape = 'box'
39
+ @is_metallic = 0
40
+ @roughness = 0.5
41
+ @is_allowed_float = 0
42
+ @build_interval = 0.01
43
+ @is_framing = false
44
+ @frame_id = 0
45
+ end
46
+
47
+ def clear_data
48
+ @translation = [0, 0, 0, 0, 0, 0]
49
+ @is_allowed_matrix = 0
50
+ @saved_matrices = []
51
+ @translation = [0, 0, 0, 0, 0, 0]
52
+ @matrix_translation = [0, 0, 0, 0, 0, 0]
53
+ @frame_translations = []
54
+ @global_animation = [0, 0, 0, 0, 0, 0, 1, 0]
55
+ @animation = [0, 0, 0, 0, 0, 0, 1, 0]
56
+ @boxes = []
57
+ @frames = []
58
+ @sentence = []
59
+ @lights = []
60
+ @commands = []
61
+ @size = 1
62
+ @shape = 'box'
63
+ @is_metallic = 0
64
+ @roughness = 0.5
65
+ @is_allowed_float = 0
66
+ @build_interval = 0.01
67
+ @is_framing = false
68
+ @frame_id = 0
69
+ end
70
+
71
+ def set_frame_fps(fps = 2)
72
+ @commands << "fps #{fps}"
73
+ end
74
+
75
+ def set_frame_repeats(repeats = 10)
76
+ @commands << "repeats #{repeats}"
77
+ end
78
+
79
+ def frame_in
80
+ @is_framing = true
81
+ end
82
+
83
+ def frame_out
84
+ @is_framing = false
85
+ @frame_id += 1
86
+ end
87
+
88
+ def push_matrix
89
+ @is_allowed_matrix += 1
90
+ @saved_matrices.push(@matrix_translation)
91
+ end
92
+
93
+ def pop_matrix
94
+ @is_allowed_matrix -= 1
95
+ @matrix_translation = @saved_matrices.pop
96
+ end
97
+
98
+ def translate(x, y, z, pitch: 0, yaw: 0, roll: 0)
99
+ if @is_allowed_matrix > 0
100
+ # 移動用のマトリックスを計算する
101
+ matrix = @saved_matrices.last
102
+ puts "before matrix: #{matrix}"
103
+ base_position = matrix[0..2]
104
+
105
+ if matrix.length == 6
106
+ base_rotation_matrix = get_rotation_matrix(matrix[3], matrix[4], matrix[5])
107
+ else
108
+ base_rotation_matrix = [matrix[3..5], matrix[6..8], matrix[9..11]]
109
+ end
110
+
111
+ # Compute the new position after translation
112
+ add_x, add_y, add_z = transform_point_by_rotation_matrix([x, y, z], transpose_3x3(base_rotation_matrix))
113
+ x, y, z = add_vectors(base_position, [add_x, add_y, add_z])
114
+ x, y, z = round_numbers([x, y, z])
115
+
116
+ # Compute the rotation after translation
117
+ translate_rotation_matrix = get_rotation_matrix(-pitch, -yaw, -roll)
118
+ rotate_matrix = matrix_multiply(translate_rotation_matrix, base_rotation_matrix)
119
+ @matrix_translation = [x, y, z, *rotate_matrix[0], *rotate_matrix[1], *rotate_matrix[2]]
120
+ else
121
+ x, y, z = round_numbers([x, y, z])
122
+
123
+ if @is_framing
124
+ @frame_translations.append([x, y, z, pitch, yaw, roll, @frame_id])
125
+ else
126
+ @translation = [x, y, z, pitch, yaw, roll]
127
+ end
128
+ end
129
+ end
130
+
131
+ def create_box(x, y, z, r: 1, g: 1, b: 1, alpha: 1, texture: '')
132
+ if @is_allowed_matrix > 0
133
+ # 移動用のマトリックスにより位置を計算する
134
+ matrix = @matrix_translation
135
+ base_position = matrix[0..2]
136
+
137
+ if matrix.length == 6
138
+ base_rotation_matrix = get_rotation_matrix(matrix[3], matrix[4], matrix[5])
139
+ else
140
+ base_rotation_matrix = [matrix[3..5], matrix[6..8], matrix[9..11]]
141
+ end
142
+
143
+ # Compute the new position after translation
144
+ add_x, add_y, add_z = transform_point_by_rotation_matrix([x, y, z], transpose_3x3(base_rotation_matrix))
145
+ x, y, z = add_vectors(base_position, [add_x, add_y, add_z])
146
+ end
147
+
148
+ x, y, z = round_numbers([x, y, z])
149
+ r, g, b, alpha = round_colors([r, g, b, alpha])
150
+
151
+ # 重ねておくことを防止
152
+ remove_box(x, y, z)
153
+ if !@@texture_names.include?(texture)
154
+ texture_id = -1
155
+ else
156
+ texture_id = @@texture_names.index(texture)
157
+ end
158
+
159
+ if @is_framing
160
+ @frames.append([x, y, z, r, g, b, alpha, texture_id, @frame_id])
161
+ else
162
+ @boxes.append([x, y, z, r, g, b, alpha, texture_id])
163
+ end
164
+ end
165
+
166
+ def remove_box(x, y, z)
167
+ x, y, z = round_numbers([x, y, z])
168
+
169
+ if @is_framing
170
+ @frames.reject! { |frame| frame[0] == x && frame[1] == y && frame[2] == z && frame[8] == @frame_id }
171
+ else
172
+ @boxes.reject! { |box| box[0] == x && box[1] == y && box[2] == z }
173
+ end
174
+ end
175
+
176
+ def animate_global(x, y, z, pitch: 0, yaw: 0, roll: 0, scale: 1, interval: 10)
177
+ x, y, z = round_numbers([x, y, z])
178
+ @global_animation = [x, y, z, pitch, yaw, roll, scale, interval]
179
+ end
180
+
181
+ def animate(x, y, z, pitch: 0, yaw: 0, roll: 0, scale: 1, interval: 10)
182
+ x, y, z = round_numbers([x, y, z])
183
+ @animation = [x, y, z, pitch, yaw, roll, scale, interval]
184
+ end
185
+
186
+ def set_box_size(box_size)
187
+ @size = box_size
188
+ end
189
+
190
+ def set_build_interval(interval)
191
+ @build_interval = interval
192
+ end
193
+
194
+ def write_sentence(sentence, x, y, z, r: 1, g: 1, b: 1, alpha: 1)
195
+ x, y, z = round_numbers([x, y, z]).map(&:to_s)
196
+ r, g, b, alpha = round_colors([r, g, b, alpha])
197
+ r, g, b, alpha = [r, g, b, alpha].map(&:floor).map(&:to_s)
198
+ @sentence = [sentence, x, y, z, r, g, b, alpha]
199
+ end
200
+
201
+ def set_light(x, y, z, r: 1, g: 1, b: 1, alpha: 1, intensity: 1000, interval: 1, light_type: 'point')
202
+ x, y, z = round_numbers([x, y, z])
203
+ r, g, b, alpha = round_colors([r, g, b, alpha])
204
+
205
+ if light_type == 'point'
206
+ light_type = 1
207
+ elsif light_type == 'spot'
208
+ light_type = 2
209
+ elsif light_type == 'directional'
210
+ light_type = 3
211
+ else
212
+ light_type = 1
213
+ end
214
+ @lights << [x, y, z, r, g, b, alpha, intensity, interval, light_type]
215
+ end
216
+
217
+ def set_command(command)
218
+ @commands << command
219
+
220
+ if command == 'float'
221
+ @is_allowed_float = 1
222
+ end
223
+ end
224
+
225
+ def draw_line(x1, y1, z1, x2, y2, z2, r: 1, g: 1, b: 1, alpha: 1)
226
+ x1, y1, z1, x2, y2, z2 = [x1, y1, z1, x2, y2, z2].map(&:floor)
227
+ diff_x = x2 - x1
228
+ diff_y = y2 - y1
229
+ diff_z = z2 - z1
230
+ max_length = [diff_x.abs, diff_y.abs, diff_z.abs].max
231
+
232
+ return false if diff_x == 0 && diff_y == 0 && diff_z == 0
233
+
234
+ if diff_x.abs == max_length
235
+ if x2 > x1
236
+ (x1..x2).each do |x|
237
+ y = y1 + (x - x1) * diff_y.to_f / diff_x
238
+ z = z1 + (x - x1) * diff_z.to_f / diff_x
239
+ create_box(x, y, z, r: r, g: g, b: b, alpha: alpha)
240
+ end
241
+ else
242
+ x1.downto(x2 + 1) do |x|
243
+ y = y1 + (x - x1) * diff_y.to_f / diff_x
244
+ z = z1 + (x - x1) * diff_z.to_f / diff_x
245
+ create_box(x, y, z, r: r, g: g, b: b, alpha: alpha)
246
+ end
247
+ end
248
+ elsif diff_y.abs == max_length
249
+ if y2 > y1
250
+ (y1..y2).each do |y|
251
+ x = x1 + (y - y1) * diff_x.to_f / diff_y
252
+ z = z1 + (y - y1) * diff_z.to_f / diff_y
253
+ create_box(x, y, z, r: r, g: g, b: b, alpha: alpha)
254
+ end
255
+ else
256
+ y1.downto(y2 + 1) do |y|
257
+ x = x1 + (y - y1) * diff_x.to_f / diff_y
258
+ z = z1 + (y - y1) * diff_z.to_f / diff_y
259
+ create_box(x, y, z, r: r, g: g, b: b, alpha: alpha)
260
+ end
261
+ end
262
+ elsif diff_z.abs == max_length
263
+ if z2 > z1
264
+ (z1..z2).each do |z|
265
+ x = x1 + (z - z1) * diff_x.to_f / diff_z
266
+ y = y1 + (z - z1) * diff_y.to_f / diff_z
267
+ create_box(x, y, z, r: r, g: g, b: b, alpha: alpha)
268
+ end
269
+ else
270
+ z1.downto(z2 + 1) do |z|
271
+ x = x1 + (z - z1) * diff_x.to_f / diff_z
272
+ y = y1 + (z - z1) * diff_y.to_f / diff_z
273
+ create_box(x, y, z, r: r, g: g, b: b, alpha: alpha)
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ def change_shape(shape)
280
+ @shape = shape
281
+ end
282
+
283
+ def change_material(is_metallic: false, roughness: 0.5)
284
+ @is_metallic = is_metallic ? 1 : 0
285
+ @roughness = roughness
286
+ end
287
+
288
+ def send_data
289
+ puts 'send_data'
290
+ now = DateTime.now
291
+ data_to_send = {
292
+ "translation": @translation,
293
+ "frameTranslations": @frame_translations,
294
+ "globalAnimation": @global_animation,
295
+ "animation": @animation,
296
+ "boxes": @boxes,
297
+ "frames": @frames,
298
+ "sentence": @sentence,
299
+ "lights": @lights,
300
+ "commands": @commands,
301
+ "size": @size,
302
+ "shape": @shape,
303
+ "interval": @build_interval,
304
+ "isMetallic": @is_metallic,
305
+ "roughness": @roughness,
306
+ "isAllowedFloat": @is_allowed_float,
307
+ "date": now.to_s
308
+ }.to_json
309
+
310
+ EM.run do
311
+ ws = Faye::WebSocket::Client.new('wss://websocket.voxelamming.com')
312
+
313
+ ws.on :open do |_event|
314
+ p [:open]
315
+ puts 'WebSocket connection open'
316
+ ws.send(@room_name)
317
+ puts "Joined room: #{@room_name}"
318
+ ws.send(data_to_send)
319
+ puts data_to_send
320
+ puts 'Sent data to server'
321
+
322
+ EM.add_timer(1) do
323
+ ws.close
324
+ EM.stop
325
+ end
326
+ end
327
+
328
+ ws.on :error do |event|
329
+ puts "WebSocket error: #{event.message}"
330
+ EM.stop
331
+ end
332
+
333
+ ws.on :close do |_event|
334
+ puts 'WebSocket connection closed'
335
+ EM.stop
336
+ end
337
+ end
338
+ end
339
+
340
+ def round_numbers(num_list)
341
+ if @is_allowed_float == 1
342
+ num_list.map { |val| val.round(2) }
343
+ else
344
+ num_list.map { |val| val.round(1).floor }
345
+ end
346
+ end
347
+
348
+ def round_colors(num_list)
349
+ num_list.map { |val| val.round(2) }
350
+ end
351
+ end
352
+
353
+ # Turtle graphics
354
+ class Turtle
355
+ include Math
356
+
357
+ def initialize(build_box)
358
+ @build_box = build_box
359
+ @x = 0
360
+ @y = 0
361
+ @z = 0
362
+ @polar_theta = 90
363
+ @polar_phi = 0
364
+ @drawable = true
365
+ @color = [0, 0, 0, 1]
366
+ @size = 1
367
+ end
368
+
369
+ def forward(length)
370
+ z = @z + length * sin(radians(@polar_theta)) * cos(radians(@polar_phi))
371
+ x = @x + length * sin(radians(@polar_theta)) * sin(radians(@polar_phi))
372
+ y = @y + length * cos(radians(@polar_theta))
373
+ x, y, z = x.round(3), y.round(3), z.round(3)
374
+
375
+ if @drawable
376
+ @build_box.draw_line(@x, @y, @z, x, y, z, r: @color[0], g: @color[1], b: @color[2])
377
+ end
378
+
379
+ @x = x
380
+ @y = y
381
+ @z = z
382
+ end
383
+
384
+ def backward(length)
385
+ forward(-length)
386
+ end
387
+
388
+ def up(degree)
389
+ @polar_theta -= degree
390
+ end
391
+
392
+ def down(degree)
393
+ @polar_theta += degree
394
+ end
395
+
396
+ def right(degree)
397
+ @polar_phi -= degree
398
+ end
399
+
400
+ def left(degree)
401
+ @polar_phi += degree
402
+ end
403
+
404
+ def set_color(r, g, b, alpha = 1)
405
+ @color = [r, g, b, alpha]
406
+ end
407
+
408
+ def pen_down
409
+ @drawable = true
410
+ end
411
+
412
+ def pen_up
413
+ @drawable = false
414
+ end
415
+
416
+ def set_pos(x, y, z)
417
+ @x = x
418
+ @y = y
419
+ @z = z
420
+ end
421
+
422
+ def reset
423
+ @x = 0
424
+ @y = 0
425
+ @z = 0
426
+ @polar_theta = 90
427
+ @polar_phi = 0
428
+ @drawable = true
429
+ @color = [0, 0, 0, 1]
430
+ @size = 1
431
+ end
432
+
433
+ private
434
+
435
+ def radians(degrees)
436
+ degrees * Math::PI / 180
437
+ end
438
+ end
439
+
440
+ # Make model
441
+ def self.get_boxes_from_ply(ply_file)
442
+ box_positions = Set.new
443
+ File.open("./ply_file/#{ply_file}", 'r') do |f|
444
+ lines = f.read
445
+ lines = lines.gsub("\r\n", "\n")
446
+ lines = lines.strip
447
+ positions = lines.split("\n").select { |ln| self.is_included_six_numbers(ln) }.map { |ln| ln.split.map(&:to_f) }
448
+
449
+ number_of_faces = positions.length / 4
450
+ (0...number_of_faces).each do |i|
451
+ vertex1 = positions[i * 4]
452
+ vertex2 = positions[i * 4 + 1]
453
+ vertex3 = positions[i * 4 + 2]
454
+ vertex4 = positions[i * 4 + 3] # no need
455
+ x = [vertex1[0], vertex2[0], vertex3[0]].min
456
+ y = [vertex1[1], vertex2[1], vertex3[1]].min
457
+ z = [vertex1[2], vertex2[2], vertex3[2]].min
458
+ r = vertex1[3] / 255.0
459
+ g = vertex1[4] / 255.0
460
+ b = vertex1[5] / 255.0
461
+ alpha = 1
462
+
463
+ # ボックスを置く方向を解析
464
+ if vertex1[0] == vertex2[0] && vertex2[0] == vertex3[0] # y-z plane
465
+ step = [vertex1[1], vertex2[1], vertex3[1]].max - y
466
+ x -= step if vertex1[1] != vertex2[1]
467
+ elsif vertex1[1] == vertex2[1] && vertex2[1] == vertex3[1] # z-x plane
468
+ step = [vertex1[2], vertex2[2], vertex3[2]].max - z
469
+ y -= step if vertex1[2] != vertex2[2]
470
+ else # x-y plane
471
+ step = [vertex1[0], vertex2[0], vertex3[0]].max - x
472
+ z -= step if vertex1[0] != vertex2[0]
473
+ end
474
+
475
+ # minimum unit: 0.1
476
+ position_x = (x * 10.0 / step).round / 10.0
477
+ position_y = (y * 10.0 / step).round / 10.0
478
+ position_z = (z * 10.0 / step).round / 10.0
479
+ box_positions.add([position_x, position_z, -position_y, r, g, b, alpha])
480
+ end
481
+ end
482
+
483
+ box_positions
484
+ end
485
+
486
+ def self.is_included_six_numbers(line)
487
+ line_list = line.split
488
+ return false if line_list.length != 6
489
+
490
+ line_list.all? { |num| Float(num) rescue false }
491
+ end
492
+
493
+ # Map
494
+ $column_num, $row_num = 257, 257
495
+
496
+ def self.get_map_data_from_csv(csv_file, height_scale)
497
+ # csvファイルから地図データを読み込み
498
+ heights = []
499
+
500
+ CSV.foreach("./map_file/#{csv_file}") do |row|
501
+ for h in row
502
+ h = h.to_f
503
+ h = h != 0 ? (h * height_scale).floor : -1
504
+ heights << h
505
+ end
506
+ end
507
+ # puts "heights: #{heights}"
508
+
509
+ max_height = heights.max.floor
510
+ box_positions = (0...$row_num).map { |i| heights[i * $column_num, $column_num] }
511
+ puts "max_height: #{max_height}"
512
+ # puts "box_positions: #{box_positions}"
513
+ { 'boxes' => box_positions, 'max_height' => max_height }
514
+ end
515
+
516
+ def self.get_box_color(height, max_height, high_color, low_color)
517
+ # 高さによって色を変える
518
+ r = (high_color[0] - low_color[0]) * height * 1.0 / max_height + low_color[0]
519
+ g = (high_color[1] - low_color[1]) * height * 1.0 / max_height + low_color[1]
520
+ b = (high_color[2] - low_color[2]) * height * 1.0 / max_height + low_color[2]
521
+
522
+ [r, g, b]
523
+ end
524
+ end
data/main.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'voxelamming_gem'
2
+
3
+ room_name = '1000'
4
+ build_box = VoxelammingGem::BuildBox.new(room_name)
5
+
6
+ build_box.set_box_size(0.5)
7
+ build_box.set_build_interval(0.01)
8
+
9
+ for i in 0...100
10
+ build_box.create_box(-1, i, 0, r: 0, g: 1, b: 1)
11
+ build_box.create_box(0, i, 0, r: 1, g: 0, b: 0)
12
+ build_box.create_box(1, i, 0, r: 1, g: 1, b: 0)
13
+ build_box.create_box(2, i, 0, r: 0, g: 1, b: 1)
14
+ end
15
+
16
+ for i in 0...50
17
+ build_box.remove_box(0, i * 2, 0)
18
+ build_box.remove_box(1, i * 2 + 1, 0)
19
+ end
20
+
21
+ build_box.send_data
@@ -0,0 +1,18 @@
1
+ require 'voxelamming_gem'
2
+
3
+ room_name = '1000'
4
+ build_box = VoxelammingGem::BuildBox.new(room_name)
5
+
6
+ build_box.set_box_size(0.5)
7
+ build_box.set_build_interval(0.01)
8
+
9
+ ply_file_name = 'piyo.ply'
10
+
11
+ boxes = VoxelammingGem.get_boxes_from_ply(ply_file_name)
12
+ # puts boxes
13
+
14
+ boxes.each do |b|
15
+ build_box.create_box(b[0], b[1], b[2], r: b[3], g: b[4], b: b[5])
16
+ end
17
+
18
+ build_box.send_data
data/main_map.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'voxelamming_gem'
2
+
3
+ room_name = "1000"
4
+ build_box = VoxelammingGem::BuildBox.new(room_name)
5
+
6
+ build_box.set_box_size(0.1)
7
+ build_box.set_build_interval(0.001)
8
+ build_box.set_command('liteRender')
9
+
10
+ column_num, row_num = 257, 257
11
+ csv_file = 'map_38_138_100km.csv'
12
+ height_scale = 100
13
+ high_color = [0.5, 0, 0]
14
+ low_color = [0, 1, 0]
15
+ map_data = VoxelammingGem.get_map_data_from_csv(csv_file, height_scale)
16
+ boxes = map_data['boxes']
17
+ max_height = map_data['max_height']
18
+ # skip = 1 # high power device
19
+ skip = 2 # normal device
20
+ # skip = 4 # low power device
21
+
22
+ (row_num / skip).times do |j|
23
+ (column_num / skip).times do |i|
24
+ # puts "#{i} #{j}"
25
+ x = i - column_num / (skip * 2).floor
26
+ z = j - row_num / (skip * 2).floor
27
+ y = boxes[j * skip][i * skip]
28
+
29
+ if y >= 0
30
+ r, g, b = VoxelammingGem.get_box_color(y, max_height, high_color, low_color)
31
+ # print("r: #{r}, g: #{g}, b: #{b}\n")
32
+ build_box.create_box(x, y, z, r: r, g: g, b: b)
33
+ end
34
+ end
35
+ end
36
+
37
+ build_box.send_data()