Class: NormalMap

Inherits:
Object
  • Object
show all
Includes:
Magick
Defined in:
lib/normal_map.rb,
lib/normal_map/version.rb

Defined Under Namespace

Modules: Version Classes: CLI

Constant Summary collapse

VERSION =
Version::STRING

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename, opts = {}) ⇒ NormalMap

Returns a new instance of NormalMap.



11
12
13
14
15
16
17
# File 'lib/normal_map.rb', line 11

def initialize filename, opts = {}
  @colors = ImageList.new(filename)[0]
  opts.each do |k, v|
    instance_variable_set :"@#{k}", v
  end
  convert!
end

Instance Attribute Details

#colorsObject (readonly)

Returns the value of attribute colors.



9
10
11
# File 'lib/normal_map.rb', line 9

def colors
  @colors
end

Instance Method Details

#calculate_normal(x, y) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/normal_map.rb', line 108

def calculate_normal x, y
  sx, sy = width, height
  
  # vertical/horizontal
  az = intensity x+1, y
  bz = intensity x, y+1
  cz = intensity x-1, y
  dz = intensity x, y-1
  normal = normalize [cz - az, dz - bz, 2 ]

  if diagonal?
    # diagonal
    az = intensity x+1, y+1
    bz = intensity x-1, y+1
    cz = intensity x+1, y-1
    dz = intensity x-1, y-1
    normal2 = normalize [cz - az, dz - bz, 2 ]
    
    # average them together
    normal[0] = (normal[0] + normal2[0]) * 0.5
    normal[1] = (normal[1] + normal2[1]) * 0.5
    normal[2] = (normal[2] + normal2[2]) * 0.5
  end

  normal
end

#convert!Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/normal_map.rb', line 70

def convert!
  total = width * height
  increment = 0.05
  current = 0
  frac = 1.0 / total
  pixels = Array.new total
  width.times do |x|
    height.times do |y|
      current += frac
      increment = progress current, increment
      normal = calculate_normal x, y
      normal[0] = normal[0] * 0.5 + 0.5
      normal[1] = normal[1] * 0.5 + 0.5
      normal[2] = normal[2] * 0.5 + 0.5
      pixels[offset x, y] = Pixel.new normal[0] * QuantumRange,
                                      normal[1] * QuantumRange,
                                      normal[2] * QuantumRange, 
                                      QuantumRange
    end
  end
  normals.store_pixels 0, 0, width, height, pixels
  puts
end

#diagonal?Boolean

If true, normals will be calculated from diagonally adjacent pixels as well as vertically and horizontally adjacent. If ‘smooth` is also true, diagonal pixels will be considered in the smoothing algorithm as well.

Returns:

  • (Boolean)


53
54
55
# File 'lib/normal_map.rb', line 53

def diagonal?
  !!@diagonal
end

#heightObject



190
191
192
# File 'lib/normal_map.rb', line 190

def height
  @height ||= colors.rows
end

#intensity(x, y) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/normal_map.rb', line 135

def intensity(x, y)
  # return intensity averaged with all adjacent pixels to produce
  # a smoother result. Weight the current pixel to give it a little
  # more influence.
  if smooth?
    sum = 0.0
    sum += raw_intensity x-1, y
    sum += raw_intensity x+1, y
    sum += raw_intensity x, y-1
    sum += raw_intensity x, y+1
    if diagonal?
      sum += raw_intensity x-1, y-1
      sum += raw_intensity x-1, y+1
      sum += raw_intensity x+1, y-1
      sum += raw_intensity x+1, y+1
      sum /= 8.0
    else
      sum / 4.0
    end
  else
    raw_intensity x, y
  end
end

#normalize(vec) ⇒ Object



178
179
180
181
182
183
184
# File 'lib/normal_map.rb', line 178

def normalize vec
  mag = 1.0 / Math.sqrt(vec[0]**2 + vec[1]**2 + vec[2]**2)
  vec[0] *= mag
  vec[1] *= mag
  vec[2] *= mag
  vec
end

#normalsObject



66
67
68
# File 'lib/normal_map.rb', line 66

def normals
  @normals ||= Image.new width, height
end

#offset(x, y) ⇒ Object



174
175
176
# File 'lib/normal_map.rb', line 174

def offset x, y
  y * width + x
end

#outObject

The output stream to receive status updates. Defaults to ‘$stdout`.



58
59
60
# File 'lib/normal_map.rb', line 58

def out
  @out ||= $stdout
end

#pixelsObject



104
105
106
# File 'lib/normal_map.rb', line 104

def pixels
  @pixels ||= colors.get_pixels 0, 0, width, height
end


23
24
25
# File 'lib/normal_map.rb', line 23

def print *args
  out.print *args unless silent?
end

#progress(percent, increment) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'lib/normal_map.rb', line 94

def progress percent, increment
  if percent >= increment
    print '.'
    increment += 0.05
  end
  percent = (percent*100).to_i.to_s
  print "  => ", percent, "%", "\b" * (percent.length + 6)
  increment
end

#puts(*args) ⇒ Object



27
28
29
# File 'lib/normal_map.rb', line 27

def puts *args
  out.puts *args unless silent?
end

#raw_intensity(x, y) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/normal_map.rb', line 159

def raw_intensity(x, y)
  if wrap?
    x += width  if x < 0
    x -= width  if x >= width
    y += height if y < 0
    y -= height if y >= height
  else
    x = 0          if x <  0
    x = width  - 1 if x >= width
    y = 0          if y <  0
    y = height - 1 if y >= height
  end
  pixels[offset x, y].intensity.to_f / QuantumRange.to_f
end

#silent?Boolean

If true, nothing will be output during generation.

Returns:

  • (Boolean)


45
46
47
# File 'lib/normal_map.rb', line 45

def silent?
  !!@silent
end

#smooth?Boolean

If true, calculated normals will be averaged with adjacent normals to produce a smoother, but less accurate, normal map.

Returns:

  • (Boolean)


33
34
35
# File 'lib/normal_map.rb', line 33

def smooth?
  !!@smooth
end

#to_blob(*a, &b) ⇒ Object



19
20
21
# File 'lib/normal_map.rb', line 19

def to_blob *a, &b
  to_image.to_blob *a, &b
end

#to_imageObject



62
63
64
# File 'lib/normal_map.rb', line 62

def to_image
  normals
end

#widthObject



186
187
188
# File 'lib/normal_map.rb', line 186

def width
  @width ||= colors.columns
end

#wrap?Boolean

If true, pixel coordinates out of range will be wrapped to the other side of the texture; otherwise, they will be clamped to the edge of the texture.

Returns:

  • (Boolean)


40
41
42
# File 'lib/normal_map.rb', line 40

def wrap?
  !!@wrap
end