glitch3d 0.1.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/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/glitch3d +6 -0
- data/bin/setup +8 -0
- data/fixtures/cube.obj +18 -0
- data/fixtures/demo.png +0 -0
- data/fixtures/m4a1.obj +128741 -0
- data/fixtures/male_head.obj +22543 -0
- data/fixtures/skull.obj +34962 -0
- data/fixtures/textures/00.jpg +0 -0
- data/fixtures/textures/01.jpg +0 -0
- data/fixtures/textures/02.jpg +0 -0
- data/fixtures/textures/03.jpg +0 -0
- data/fixtures/textures/04.jpg +0 -0
- data/fixtures/textures/05.jpg +0 -0
- data/fixtures/textures/06.jpg +0 -0
- data/fixtures/textures/07.jpg +0 -0
- data/fixtures/textures/08.jpg +0 -0
- data/fixtures/textures/09.jpg +0 -0
- data/fixtures/textures/10.jpg +0 -0
- data/fixtures/textures/11.jpg +0 -0
- data/fixtures/textures/12.jpg +0 -0
- data/fixtures/textures/13.jpg +0 -0
- data/fixtures/textures/14.jpg +0 -0
- data/fixtures/textures/15.jpg +0 -0
- data/fixtures/textures/16.jpg +0 -0
- data/fixtures/textures/17.jpg +0 -0
- data/fixtures/textures/18.jpg +0 -0
- data/fixtures/textures/19.jpg +0 -0
- data/fixtures/textures/20.jpg +0 -0
- data/fixtures/textures/21.jpg +0 -0
- data/fixtures/textures/22.jpg +0 -0
- data/fixtures/textures/23.jpg +0 -0
- data/fixtures/textures/24.jpg +0 -0
- data/fixtures/textures/25.jpg +0 -0
- data/fixtures/textures/27.jpg +0 -0
- data/fixtures/textures/28.jpg +0 -0
- data/fixtures/textures/29.jpg +0 -0
- data/fixtures/textures/30.jpg +0 -0
- data/glitch3d.gemspec +34 -0
- data/lib/glitch3d/bpy/helpers.py +97 -0
- data/lib/glitch3d/bpy/rendering.py +159 -0
- data/lib/glitch3d/objects/face.rb +22 -0
- data/lib/glitch3d/objects/vertex.rb +55 -0
- data/lib/glitch3d/strategies/default.rb +17 -0
- data/lib/glitch3d/strategies/localized.rb +25 -0
- data/lib/glitch3d/version.rb +3 -0
- data/lib/glitch3d.rb +137 -0
- metadata +157 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/glitch3d.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'glitch3d/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "glitch3d"
|
8
|
+
spec.version = Glitch3d::VERSION
|
9
|
+
spec.authors = ["pskl"]
|
10
|
+
spec.email = ["hello@pascal.cc"]
|
11
|
+
|
12
|
+
spec.summary = %q{Alter 3D models and renders pictures.}
|
13
|
+
spec.description = %q{Glitch3D is a library designed to transform a 3D model randomly and render screenshots.}
|
14
|
+
spec.homepage = "http://pascal.cc"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "bin"
|
27
|
+
spec.executables = ["glitch3d"]
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
spec.add_development_dependency "byebug"
|
34
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Helper methods
|
2
|
+
def look_at(camera_object, point):
|
3
|
+
location_camera = camera_object.matrix_world.to_translation()
|
4
|
+
location_point = point.matrix_world.to_translation()
|
5
|
+
direction = location_point - location_camera
|
6
|
+
rot_quat = direction.to_track_quat('-Z', 'Y')
|
7
|
+
camera_object.rotation_euler = rot_quat.to_euler()
|
8
|
+
|
9
|
+
def empty_materials():
|
10
|
+
mats = bpy.data.materials
|
11
|
+
for mat in mats.keys():
|
12
|
+
mats.remove(mats[mat])
|
13
|
+
|
14
|
+
def shoot(camera, model_object, filepath):
|
15
|
+
look_at(camera, model_object)
|
16
|
+
print('Camera now at location: ' + camera_location_string(camera) + ' / rotation: ' + camera_rotation_string(camera))
|
17
|
+
bpy.context.scene.render.filepath = filepath
|
18
|
+
bpy.ops.render.render(write_still=True)
|
19
|
+
|
20
|
+
def output_name(index, model_path):
|
21
|
+
return 'renders/' + os.path.splitext(model_path)[0].split('/')[1] + '_' + str(index) + '_' + str(datetime.date.today()) + '.png'
|
22
|
+
|
23
|
+
def rotate(model_object, index):
|
24
|
+
model_object.rotation_euler[2] = math.radians(index * (360.0 / shots_number))
|
25
|
+
|
26
|
+
def rand_color_value():
|
27
|
+
return round(random.uniform(0.1, 1.0), 10)
|
28
|
+
|
29
|
+
def rand_location():
|
30
|
+
return (rand_location_value(), rand_location_value(), rand_location_value())
|
31
|
+
|
32
|
+
def rand_rotation():
|
33
|
+
return (rand_rotation_value(), rand_rotation_value(), rand_rotation_value())
|
34
|
+
|
35
|
+
def rand_rotation_value():
|
36
|
+
return round(random.uniform(0, 1), 10)
|
37
|
+
|
38
|
+
def rand_location_value():
|
39
|
+
return round(random.uniform(-8, 8), 10)
|
40
|
+
|
41
|
+
def rand_color_vector():
|
42
|
+
return (rand_color_value(), rand_color_value(), rand_color_value(), 1)
|
43
|
+
|
44
|
+
def rand_scale():
|
45
|
+
return round(random.uniform(0, 0.3), 10)
|
46
|
+
|
47
|
+
def get_args():
|
48
|
+
parser = argparse.ArgumentParser()
|
49
|
+
|
50
|
+
# get all script args
|
51
|
+
_, all_arguments = parser.parse_known_args()
|
52
|
+
double_dash_index = all_arguments.index('--')
|
53
|
+
script_args = all_arguments[double_dash_index + 1: ]
|
54
|
+
|
55
|
+
# add parser rules
|
56
|
+
parser.add_argument('-f', '--file', help="obj file to render")
|
57
|
+
parser.add_argument('-n', '--shots-number', help="number of shots desired")
|
58
|
+
parser.add_argument('-m', '--mode', help="quality mode: low | high")
|
59
|
+
|
60
|
+
parsed_script_args, _ = parser.parse_known_args(script_args)
|
61
|
+
return parsed_script_args
|
62
|
+
|
63
|
+
def camera_rotation_string(camera):
|
64
|
+
return str(int(camera.rotation_euler.x)) + ' ' + str(int(camera.rotation_euler.y)) + ' ' + str(int(camera.rotation_euler.z))
|
65
|
+
|
66
|
+
def camera_location_string(camera):
|
67
|
+
return str(int(camera.location.x)) + ' ' + str(int(camera.location.y)) + ' ' + str(int(camera.location.z))
|
68
|
+
|
69
|
+
def assign_material(model_object, material):
|
70
|
+
model_object.data.materials.append(material)
|
71
|
+
|
72
|
+
def assign_node_to_output(material, new_node):
|
73
|
+
assert material.use_nodes == True
|
74
|
+
output_node = material.node_tree.nodes['Material Output']
|
75
|
+
material.node_tree.links.new(new_node.outputs[0], output_node.inputs['Surface'])
|
76
|
+
|
77
|
+
def create_cycles_material():
|
78
|
+
material = bpy.data.materials.new('Object Material - ' + str(uuid.uuid1()))
|
79
|
+
material.use_nodes = True
|
80
|
+
|
81
|
+
nodes = material.node_tree.nodes
|
82
|
+
new_node = nodes.new(random.choice(SHADERS))
|
83
|
+
|
84
|
+
assign_node_to_output(material, new_node)
|
85
|
+
return material
|
86
|
+
|
87
|
+
def assign_random_texture_to_material(material):
|
88
|
+
assert material.use_nodes == True
|
89
|
+
bsdf_node = material.node_tree.nodes['Diffuse BSDF']
|
90
|
+
assign_node_to_output(material, bsdf_node)
|
91
|
+
texture_node = material.node_tree.nodes.new('ShaderNodeTexture')
|
92
|
+
material.node_tree.links.new(texture_node.outputs[0], bsdf_node.inputs[0])
|
93
|
+
# code.interact(local=dict(globals(), **locals()))
|
94
|
+
texture_path = os.path.expanduser('fixtures/textures/25.jpg')
|
95
|
+
new_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')
|
96
|
+
new_texture.image = bpy.data.images.load(texture_path)
|
97
|
+
texture_node.texture = new_texture
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# Rendering script
|
2
|
+
# Run by calling the blender executable with -b -P <script_name>
|
3
|
+
# Disclaimer: I never did Python before so this is mostly hacks
|
4
|
+
#
|
5
|
+
# process:
|
6
|
+
# 1) Load model given as a parameter
|
7
|
+
# 2) Create emitting surfaces to act as lamps
|
8
|
+
# 3) Create camera
|
9
|
+
# 4) Rotate model and shoot image at each step
|
10
|
+
#
|
11
|
+
# Use `code.interact(local=dict(globals(), **locals()))` to pry into the script
|
12
|
+
|
13
|
+
import bpy
|
14
|
+
import os
|
15
|
+
import argparse
|
16
|
+
import datetime
|
17
|
+
import bmesh
|
18
|
+
import random
|
19
|
+
import code
|
20
|
+
import math
|
21
|
+
import mathutils
|
22
|
+
import random
|
23
|
+
import uuid
|
24
|
+
|
25
|
+
exec(open("lib/glitch3d/bpy/helpers.py").read())
|
26
|
+
|
27
|
+
# Arguments parsing
|
28
|
+
args = get_args()
|
29
|
+
file = args.file
|
30
|
+
mode = args.mode
|
31
|
+
shots_number = int(args.shots_number)
|
32
|
+
|
33
|
+
context = bpy.context
|
34
|
+
|
35
|
+
REFLECTOR_SCALE = 5
|
36
|
+
REFLECTOR_STRENGTH = 12
|
37
|
+
REFLECTOR_LOCATION_PADDING = 10
|
38
|
+
PROPS_NUMBER = 100
|
39
|
+
SHADERS = ['ShaderNodeBsdfGlossy', 'ShaderNodeBsdfDiffuse', 'ShaderNodeBsdfVelvet']
|
40
|
+
|
41
|
+
# Scene
|
42
|
+
new_scene = bpy.data.scenes.new("Automated Render Scene")
|
43
|
+
bpy.ops.scene.delete() # Delete old scene
|
44
|
+
context.screen.scene = new_scene # selects the new scene as the current one
|
45
|
+
|
46
|
+
# Render settings
|
47
|
+
context.scene.render.resolution_x = 1920
|
48
|
+
context.scene.render.resolution_y = 1080
|
49
|
+
context.scene.render.engine = 'CYCLES'
|
50
|
+
context.scene.render.resolution_percentage = 25
|
51
|
+
# bpy.context.scene.cycles.device = 'GPU'
|
52
|
+
context.scene.render.image_settings.compression = 0
|
53
|
+
context.scene.cycles.samples = 25
|
54
|
+
context.scene.render.image_settings.color_mode ='RGBA'
|
55
|
+
context.scene.render.image_settings.file_format='PNG'
|
56
|
+
|
57
|
+
if mode == 'high':
|
58
|
+
context.scene.render.image_settings.compression = 90
|
59
|
+
context.scene.cycles.samples = 500
|
60
|
+
context.scene.render.resolution_percentage = 100
|
61
|
+
|
62
|
+
# Load model
|
63
|
+
model_path = os.path.join(file)
|
64
|
+
bpy.ops.import_scene.obj(filepath = model_path, use_edges=True)
|
65
|
+
model_object = bpy.data.objects['Glitch3D']
|
66
|
+
|
67
|
+
# Use center of mass to center object
|
68
|
+
model_object.select = True
|
69
|
+
bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
|
70
|
+
model_object.location = (0, 0, 0)
|
71
|
+
|
72
|
+
# --------------
|
73
|
+
# Create camera
|
74
|
+
# --------------
|
75
|
+
camera_data = bpy.data.cameras.new('Render Camera')
|
76
|
+
bpy.data.objects.new('Render Camera', object_data=camera_data)
|
77
|
+
camera_object = bpy.data.objects['Render Camera']
|
78
|
+
new_scene.objects.link(camera_object)
|
79
|
+
camera_object.location = (8, 8, 0)
|
80
|
+
|
81
|
+
# Add reflectors
|
82
|
+
bpy.ops.mesh.primitive_plane_add(location=(0,8 + REFLECTOR_LOCATION_PADDING, 0))
|
83
|
+
bpy.ops.mesh.primitive_plane_add(location=(8 + REFLECTOR_LOCATION_PADDING,0,0))
|
84
|
+
|
85
|
+
plane1 = bpy.data.objects['Plane']
|
86
|
+
plane2 = bpy.data.objects['Plane.001']
|
87
|
+
|
88
|
+
# Adjust camera
|
89
|
+
context.scene.camera = camera_object
|
90
|
+
look_at(camera_object, model_object)
|
91
|
+
assign_material(model_object, create_cycles_material())
|
92
|
+
|
93
|
+
for index, plane in enumerate([plane1, plane2]):
|
94
|
+
plane.scale = (REFLECTOR_SCALE, REFLECTOR_SCALE, REFLECTOR_SCALE)
|
95
|
+
plane.rotation_euler.x += math.radians(90)
|
96
|
+
emissive_material = bpy.data.materials.new('Emissive Material #' + str(index))
|
97
|
+
emissive_material.use_nodes = True
|
98
|
+
emission_node = emissive_material.node_tree.nodes.new('ShaderNodeEmission')
|
99
|
+
# Set color
|
100
|
+
emission_node.inputs[0].default_value = rand_color_vector()
|
101
|
+
# Set strength
|
102
|
+
emission_node.inputs[1].default_value = REFLECTOR_STRENGTH
|
103
|
+
assign_node_to_output(emissive_material, emission_node)
|
104
|
+
assign_material(plane, emissive_material)
|
105
|
+
|
106
|
+
# Tilt one of the reflectors
|
107
|
+
plane2.rotation_euler.z += math.radians(90)
|
108
|
+
|
109
|
+
# Add other elements
|
110
|
+
for index in range(0, int(PROPS_NUMBER)):
|
111
|
+
bpy.ops.mesh.primitive_cube_add(location=rand_location(),radius=rand_scale(), rotation=rand_rotation())
|
112
|
+
if index == 0:
|
113
|
+
object_name = 'Cube'
|
114
|
+
elif index > 9:
|
115
|
+
object_name = 'Cube.0' + str(index)
|
116
|
+
elif index > 99:
|
117
|
+
object_name = 'Cube.' + str(index)
|
118
|
+
else:
|
119
|
+
object_name = 'Cube.00' + str(index)
|
120
|
+
object = bpy.data.objects[object_name]
|
121
|
+
new_material = create_cycles_material()
|
122
|
+
assign_random_texture_to_material(new_material)
|
123
|
+
assign_material(object, new_material)
|
124
|
+
|
125
|
+
for index in range(0, int(PROPS_NUMBER)):
|
126
|
+
bpy.ops.mesh.primitive_circle_add(location=rand_location(),radius=rand_scale(), rotation=rand_rotation())
|
127
|
+
if index == 0:
|
128
|
+
object_name = 'Circle'
|
129
|
+
elif index > 9:
|
130
|
+
object_name = 'Circle.0' + str(index)
|
131
|
+
elif index > 99:
|
132
|
+
object_name = 'Circle.' + str(index)
|
133
|
+
else:
|
134
|
+
object_name = 'Circle.00' + str(index)
|
135
|
+
object = bpy.data.objects[object_name]
|
136
|
+
new_material = create_cycles_material()
|
137
|
+
assign_random_texture_to_material(new_material)
|
138
|
+
assign_material(object, new_material)
|
139
|
+
|
140
|
+
# Add background to world
|
141
|
+
world = bpy.data.worlds[0]
|
142
|
+
world.use_nodes = True
|
143
|
+
world_node_tree = world.node_tree
|
144
|
+
# code.interact(local=dict(globals(), **locals()))
|
145
|
+
gradient_node = world_node_tree.nodes.new(type="ShaderNodeTexGradient")
|
146
|
+
background_node = world_node_tree.nodes['Background']
|
147
|
+
world_node_tree.links.new(gradient_node.outputs['Color'], background_node.inputs['Color'])
|
148
|
+
gradient_node.gradient_type = 'EASING'
|
149
|
+
|
150
|
+
# ------
|
151
|
+
# Shoot
|
152
|
+
# ------
|
153
|
+
print('Rendering images with resolution: ' + str(context.scene.render.resolution_x) + ' x ' + str(context.scene.render.resolution_y))
|
154
|
+
for index in range(0, int(shots_number)):
|
155
|
+
print("-------------------------- " + str(index) + " --------------------------")
|
156
|
+
rotate(model_object, index)
|
157
|
+
shoot(camera_object, model_object, output_name(index, model_path))
|
158
|
+
|
159
|
+
print('FINISHED ¯\_(ツ)_/¯')
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Face
|
2
|
+
attr_accessor :v1, :v2, :v3
|
3
|
+
|
4
|
+
def initialize(v1, v2, v3)
|
5
|
+
@v1 = v1
|
6
|
+
@v2 = v2
|
7
|
+
@v3 = v3
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
return nil unless !v1.nil? && !v2.nil? && !v3.nil?
|
12
|
+
"f #{v1.index} #{v2.index} #{v3.index}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rand_attr
|
16
|
+
[:v1, :v2, :v3].sample
|
17
|
+
end
|
18
|
+
|
19
|
+
def fuck(new_vertex)
|
20
|
+
send("#{rand_attr}=", new_vertex)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Vertex
|
2
|
+
attr_accessor :x, :y, :z, :index
|
3
|
+
|
4
|
+
def initialize(x, y, z, index)
|
5
|
+
@x = x
|
6
|
+
@y = y
|
7
|
+
@z = z
|
8
|
+
@index = index
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"v #{x} #{y} #{z}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rand_attr
|
16
|
+
[:x, :y, :z].sample
|
17
|
+
end
|
18
|
+
|
19
|
+
def rescale(offset)
|
20
|
+
[:x, :y, :z].each do |attr|
|
21
|
+
value = send(attr)
|
22
|
+
send("#{attr}=", value * 0.8)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def fuck
|
27
|
+
attr = rand_attr
|
28
|
+
send("#{attr}=", send(attr) + Glitch3d.rand_vertex_glitch_offset)
|
29
|
+
end
|
30
|
+
|
31
|
+
def max
|
32
|
+
[@x.abs, @y.abs].max.round
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.boundaries(vertices_list)
|
36
|
+
[
|
37
|
+
[vertices_list.max_by(&:x).x.ceil, vertices_list.min_by(&:x).x.round],
|
38
|
+
[vertices_list.max_by(&:y).y.ceil, vertices_list.min_by(&:y).y.round],
|
39
|
+
[vertices_list.max_by(&:z).z.ceil, vertices_list.min_by(&:z).z.round]
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.rescale(vertices, offset)
|
44
|
+
vertices.each do |v|
|
45
|
+
v.rescale(offset)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Pass functions like :negative? or :positive?
|
50
|
+
def self.subset(x:, y:, z:, vertex_list:)
|
51
|
+
vertex_list.select do |vertex|
|
52
|
+
vertex.x.send(x) && vertex.y.send(y) && vertex.y.send(z)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Glitch3d
|
2
|
+
module Default
|
3
|
+
def alter_vertices(vertices_objects_array)
|
4
|
+
(VERTEX_GLITCH_ITERATION_RATIO * vertices_objects_array.size).to_i.times do |_|
|
5
|
+
random_element(vertices_objects_array).fuck
|
6
|
+
end
|
7
|
+
vertices_objects_array
|
8
|
+
end
|
9
|
+
|
10
|
+
def alter_faces(faces_objects_array, vertex_objects_array)
|
11
|
+
(FACE_GLITCH_ITERATION_RATIO * faces_objects_array.count).to_i.times do |_|
|
12
|
+
random_element(faces_objects_array).fuck(random_element(vertex_objects_array))
|
13
|
+
end
|
14
|
+
faces_objects_array
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Glitch3d
|
2
|
+
module Localized
|
3
|
+
def alter_vertices(vertices_objects_array)
|
4
|
+
(VERTEX_GLITCH_ITERATION_RATIO * vertices_objects_array.size).to_i.times do |_|
|
5
|
+
random_element(target(vertices_objects_array)).fuck
|
6
|
+
end
|
7
|
+
vertices_objects_array
|
8
|
+
end
|
9
|
+
|
10
|
+
def alter_faces(faces_objects_array, vertices_objects_array)
|
11
|
+
(FACE_GLITCH_ITERATION_RATIO * faces_objects_array.count).to_i.times do |_|
|
12
|
+
random_element(faces_objects_array).fuck(random_element(target(vertices_objects_array)))
|
13
|
+
end
|
14
|
+
faces_objects_array
|
15
|
+
end
|
16
|
+
|
17
|
+
def selected_area(vertices_objects_array)
|
18
|
+
Vertex.subset(x: :negative?, y: :positive?, z: :zero?, vertex_list: vertices_objects_array)
|
19
|
+
end
|
20
|
+
|
21
|
+
def target(vertices_objects_array)
|
22
|
+
@target ||= selected_area(vertices_objects_array)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/glitch3d.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require "glitch3d/version"
|
2
|
+
require "glitch3d/objects/vertex"
|
3
|
+
require "glitch3d/objects/face"
|
4
|
+
require "glitch3d/strategies/default"
|
5
|
+
require "glitch3d/strategies/localized"
|
6
|
+
require "pry"
|
7
|
+
|
8
|
+
module Glitch3d
|
9
|
+
VERTEX_GLITCH_ITERATION_RATIO = 0.001
|
10
|
+
VERTEX_GLITCH_OFFSET = 1
|
11
|
+
|
12
|
+
FACE_GLITCH_ITERATION_RATIO = 0.0
|
13
|
+
FACE_GLITCH_OFFSET = 0.5
|
14
|
+
BOUNDARY_LIMIT = 4 # Contain model within 2x2x2 cube
|
15
|
+
|
16
|
+
BLENDER_EXECUTABLE_PATH = ENV['BLENDER_EXECUTABLE_PATH'].freeze
|
17
|
+
RENDERING_SCRIPT_PATH = "lib/glitch3d/bpy/rendering.py".freeze
|
18
|
+
|
19
|
+
def process_model(source_file)
|
20
|
+
raise 'Set Blender executable path in your env variables before using glitch3d' unless BLENDER_EXECUTABLE_PATH.present?
|
21
|
+
args = Hash[ARGV.join(' ').scan(/--?([^=\s]+)(?:=(\S+))?/)]
|
22
|
+
self.class.include infer_strategy(args["mode"] || 'default')
|
23
|
+
@quality = args["quality"] || 'low'
|
24
|
+
source_file = source_file
|
25
|
+
base_file_name = source_file.gsub(/.obj/, '')
|
26
|
+
target_file = base_file_name + '_glitched.obj'
|
27
|
+
boundaries = create_glitched_file(glitch(read_source(source_file)), target_file)
|
28
|
+
render(target_file, boundaries, args["shots-number"] || 6) unless args["no-render"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def infer_strategy(mode)
|
32
|
+
return Glitch3d::Default if mode.nil?
|
33
|
+
{
|
34
|
+
default: Glitch3d::Default,
|
35
|
+
localized: Glitch3d::Localized
|
36
|
+
}[mode.to_sym]
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def rand_vertex_glitch_offset
|
41
|
+
rand(-VERTEX_GLITCH_OFFSET..VERTEX_GLITCH_OFFSET)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def read_source(path)
|
48
|
+
File.open(path, 'r') do |f|
|
49
|
+
source_file_content = f.readlines
|
50
|
+
vertices_list = build_vertices(source_file_content.select { |s| s[0..1] == 'v ' })
|
51
|
+
faces_list = build_faces(source_file_content.select { |s| s[0..1] == 'f ' }, vertices_list)
|
52
|
+
{
|
53
|
+
vertices: vertices_list,
|
54
|
+
faces: faces_list
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_vertices(vertices_string_array)
|
60
|
+
vertices_list = []
|
61
|
+
vertices_string_array.each_with_index.map do |sv, i|
|
62
|
+
v = sv.split(' ')
|
63
|
+
vertices_list << Vertex.new(v[1].to_f, v[2].to_f, v[3].to_f, i)
|
64
|
+
end
|
65
|
+
vertices_list
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_faces(faces_string_array, vertices_list)
|
69
|
+
faces_list = []
|
70
|
+
faces_string_array.map do |sf|
|
71
|
+
f = sf.split(' ')
|
72
|
+
next if f.length <= 3
|
73
|
+
faces_list << Face.new(
|
74
|
+
vertices_list[f[1].to_i],
|
75
|
+
vertices_list[f[2].to_i],
|
76
|
+
vertices_list[f[3].to_i]
|
77
|
+
)
|
78
|
+
end
|
79
|
+
faces_list
|
80
|
+
end
|
81
|
+
|
82
|
+
def glitch(file_hash_content)
|
83
|
+
{
|
84
|
+
vertices: alter_vertices(file_hash_content[:vertices]),
|
85
|
+
faces: alter_faces(file_hash_content[:faces], file_hash_content[:vertices])
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def random_element(array)
|
90
|
+
array[rand(0..array.size - 1)]
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_glitched_file(content_hash, target_file)
|
94
|
+
boundaries = Vertex.boundaries(content_hash[:vertices])
|
95
|
+
puts boundaries.to_s
|
96
|
+
while rescale_needed?(boundaries)
|
97
|
+
content_hash[:vertices] = Vertex.rescale(content_hash[:vertices], (boundaries.flatten.map(&:abs).max.abs - BOUNDARY_LIMIT).abs)
|
98
|
+
boundaries = Vertex.boundaries(content_hash[:vertices])
|
99
|
+
end
|
100
|
+
boundaries = Vertex.boundaries(content_hash[:vertices])
|
101
|
+
puts boundaries.to_s
|
102
|
+
File.open(target_file, 'w') do |f|
|
103
|
+
f.puts '# Data corrupted with glitch3D script'
|
104
|
+
f.puts '# Boundaries: ' + boundaries.to_s
|
105
|
+
f.puts ''
|
106
|
+
f.puts 'g Glitch3D'
|
107
|
+
f.puts ''
|
108
|
+
f.puts content_hash[:vertices].map(&:to_s)
|
109
|
+
f.puts ''
|
110
|
+
f.puts content_hash[:faces].map(&:to_s).compact
|
111
|
+
end
|
112
|
+
boundaries
|
113
|
+
end
|
114
|
+
|
115
|
+
def rescale_needed?(boundaries)
|
116
|
+
boundaries.flatten.map(&:abs).max.abs > BOUNDARY_LIMIT
|
117
|
+
end
|
118
|
+
|
119
|
+
def render(file_path, boundaries, shots_number)
|
120
|
+
args = [
|
121
|
+
BLENDER_EXECUTABLE_PATH,
|
122
|
+
'-b',
|
123
|
+
'-P',
|
124
|
+
RENDERING_SCRIPT_PATH,
|
125
|
+
'--',
|
126
|
+
'-f',
|
127
|
+
file_path,
|
128
|
+
'-n',
|
129
|
+
shots_number.to_s,
|
130
|
+
'-m',
|
131
|
+
@quality
|
132
|
+
]
|
133
|
+
unless system(*args)
|
134
|
+
fail 'Make sure Blender is correctly installed'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|