lib/rubysketch/processing.rb in rubysketch-0.3.21 vs lib/rubysketch/processing.rb in rubysketch-0.3.22
- old
+ new
@@ -553,11 +553,11 @@
def <=>(o)
@point <=> o.getInternal__
end
# @private
- protected def getInternal__()
+ def getInternal__()
@point
end
# @private
private def toVector__(*args)
@@ -590,10 +590,24 @@
#
def height()
@image.height
end
+ # Applies an image filter.
+ #
+ # overload filter(shader)
+ # overload filter(type)
+ # overload filter(type, param)
+ #
+ # @param shader [Shader] a fragment shader to apply
+ # @param type [THRESHOLD, GRAY, INVERT, BLUR] filter type
+ # @param param [Numeric] a parameter for each filter
+ #
+ def filter(*args)
+ @filter = Shader.createFilter__(*args)
+ end
+
# Resizes image.
#
# @param width [Numeric] width for resized image
# @param height [Numeric] height for resized image
#
@@ -631,30 +645,28 @@
#
# @overload blend(sx, sy, sw, sh, dx, dy, dw, dh, mode)
# @overload blend(img, sx, sy, sw, sh, dx, dy, dw, dh, mode)
#
# @param img [Image] image for blend source
- # @param sx [Numrtic] x position of source region
- # @param sy [Numrtic] y position of source region
- # @param sw [Numrtic] width of source region
- # @param sh [Numrtic] height of source region
- # @param dx [Numrtic] x position of destination region
- # @param dy [Numrtic] y position of destination region
- # @param dw [Numrtic] width of destination region
- # @param dh [Numrtic] height of destination region
+ # @param sx [Numeric] x position of source region
+ # @param sy [Numeric] y position of source region
+ # @param sw [Numeric] width of source region
+ # @param sh [Numeric] height of source region
+ # @param dx [Numeric] x position of destination region
+ # @param dy [Numeric] y position of destination region
+ # @param dw [Numeric] width of destination region
+ # @param dh [Numeric] height of destination region
# @param mode [BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, EXCLUSION, MULTIPLY, SCREEN, REPLACE] blend mode
#
# @return [nil] nil
#
def blend(img = nil, sx, sy, sw, sh, dx, dy, dw, dh, mode)
img ||= self
@image.paint do |painter|
- current = painter.blend_mode
- painter.blend_mode = mode
- painter.image img.getInternal__, sx, sy, sw, sh, dx, dy, dw, dh
- painter.blend_mode = current
+ img.drawImage__ painter, sx, sy, sw, sh, dx, dy, dw, dh, blend_mode: mode
end
+ nil
end
# Saves image to file.
#
# @param filename [String] file name to save image
@@ -666,10 +678,18 @@
# @private
def getInternal__()
@image
end
+ # @private
+ def drawImage__(painter, *args, **states)
+ shader = painter.shader || @filter&.getInternal__
+ painter.push shader: shader, **states do |_|
+ painter.image getInternal__, *args
+ end
+ end
+
end# Image
# Font object.
#
@@ -751,10 +771,163 @@
end
end# Touch
+ # Shader object.
+ #
+ class Shader
+
+ # Initialize shader object.
+ #
+ # @param vertSrc [String] vertex shader source
+ # @param fragSrc [String] fragment shader source
+ #
+ def initialize(vertSrc, fragSrc)
+ @shader = Rays::Shader.new fragSrc, vertSrc, ENV__
+ end
+
+ # Sets uniform variables.
+ #
+ # @overload set(name, a)
+ # @overload set(name, a, b)
+ # @overload set(name, a, b, c)
+ # @overload set(name, a, b, c, d)
+ # @overload set(name, nums)
+ # @overload set(name, vec)
+ # @overload set(name, vec, ncoords)
+ # @overload set(name, tex)
+ #
+ # @param name [String] uniform variable name
+ # @param a [Numeric] int or float value
+ # @param b [Numeric] int or float value
+ # @param c [Numeric] int or float value
+ # @param d [Numeric] int or float value
+ # @param nums [Array] int or float array
+ # @param vec [Vector] vector
+ # @param ncoords [Integer] number of coordinates, max 4
+ # @param tex [Image] texture image
+ #
+ def setUniform(name, *args)
+ arg = args.first
+ case
+ when Array === arg
+ @shader.uniform name, *arg
+ when Numeric === arg
+ @shader.uniform name, *args
+ when Vector === arg
+ vec, ncoords = args
+ @shader.uniform name, vec.getInternal__.to_a(ncoords || 3)
+ when arg.respond_to?(:getInternal__)
+ @shader.uniform name, arg.getInternal__
+ else
+ raise ArgumentError
+ end
+ end
+
+ alias set setUniform
+
+ # @private
+ def getInternal__()
+ @shader
+ end
+
+ # @private
+ ENV__ = {
+ attribute_position: [:vertex, :position],
+ attribute_texcoord: :texCoord,
+ attribute_color: :color,
+ varying_position: :vertPosition,
+ varying_texcoord: :vertTexCoord,
+ varying_color: :vertColor,
+ uniform_position_matrix: [:transform, :transformMatrix],
+ uniform_texcoord_matrix: :texMatrix,
+ uniform_texcoord_min: :texMin,
+ uniform_texcoord_max: :texMax,
+ uniform_texcoord_offset: :texOffset,
+ uniform_texture: [:texMap, :texture]
+ }.freeze
+
+ # @private
+ def self.createFilter__(*args)
+ case arg = args.shift
+ when Shader
+ arg
+ when :threshold
+ self.new(nil, <<~END).tap {|sh| sh.set :threshold, (args.shift || 0.5)}
+ uniform float threshold;
+ uniform sampler2D texMap;
+ varying vec4 vertTexCoord;
+ varying vec4 vertColor;
+ void main() {
+ vec4 col = texture2D(texMap, vertTexCoord.xy) * vertColor;
+ float gray = col.r * 0.3 + col.g * 0.59 + col.b * 0.11;
+ gl_FragColor = vec4(vec3(gray > threshold ? 1.0 : 0.0), 1.0);
+ }
+ END
+ when :gray
+ self.new nil, <<~END
+ uniform sampler2D texMap;
+ varying vec4 vertTexCoord;
+ varying vec4 vertColor;
+ void main() {
+ vec4 col = texture2D(texMap, vertTexCoord.xy);
+ float gray = col.r * 0.3 + col.g * 0.59 + col.b * 0.11;
+ gl_FragColor = vec4(vec3(gray), 1.0) * vertColor;
+ }
+ END
+ when :invert
+ self.new nil, <<~END
+ uniform sampler2D texMap;
+ varying vec4 vertTexCoord;
+ varying vec4 vertColor;
+ void main() {
+ vec4 col = texture2D(texMap, vertTexCoord.xy);
+ gl_FragColor = vec4(vec3(1.0 - col.rgb), 1.0) * vertColor;
+ }
+ END
+ when :blur
+ self.new(nil, <<~END).tap {|sh| sh.set :radius, (args.shift || 1).to_f}
+ #define PI 3.1415926538
+ uniform float radius;
+ uniform sampler2D texMap;
+ uniform vec3 texMin;
+ uniform vec3 texMax;
+ uniform vec3 texOffset;
+ varying vec4 vertTexCoord;
+ varying vec4 vertColor;
+ float gaussian(vec2 pos, float sigma) {
+ float s2 = sigma * sigma;
+ return 1.0 / (2.0 * PI * s2) * exp(-(dot(pos, pos) / (2.0 * s2)));
+ }
+ void main() {
+ float sigma = radius * 0.5;
+ vec3 color = vec3(0.0);
+ float total_weight = 0.0;
+ for (float y = -radius; y < radius; y += 1.0)
+ for (float x = -radius; x < radius; x += 1.0) {
+ vec2 offset = vec2(x, y);
+ float weight = gaussian(offset, sigma);
+ vec2 texcoord = vertTexCoord.xy + offset * texOffset.xy;
+ if (
+ texcoord.x < texMin.x || texMax.x < texcoord.x ||
+ texcoord.y < texMin.y || texMax.y < texcoord.y
+ ) continue;
+ color += texture2D(texMap, texcoord).rgb * weight;
+ total_weight += weight;
+ }
+ gl_FragColor = vec4(color / total_weight, 1.0) * vertColor;
+ }
+ END
+ else
+ nil
+ end
+ end
+
+ end# Shader
+
+
# Camera object.
#
class Capture
# Returns a list of available camera device names
@@ -836,18 +1009,35 @@
#
def height()
@camera.image&.height || 0
end
+ # Applies an image filter.
+ #
+ # overload filter(shader)
+ # overload filter(type)
+ # overload filter(type, param)
+ #
+ # @param shader [Shader] a fragment shader to apply
+ # @param type [THRESHOLD, GRAY, INVERT, BLUR] filter type
+ # @param param [Numeric] a parameter for each filter
+ #
+ def filter(*args)
+ @filter = Shader.createFilter__(*args)
+ end
+
# @private
def getInternal__()
- @camera.image || dummyImage__
+ @camera.image || (@dummyImage ||= Rays::Image.new 1, 1)
end
# @private
- private def dummyImage__()
- @dummy ||= Rays::Image.new 1, 1
+ def drawImage__(painter, *args, **states)
+ shader = painter.shader || @filter&.getInternal__
+ painter.push shader: shader, **states do |_|
+ painter.image getInternal__, *args
+ end
end
end# Capture
@@ -976,10 +1166,22 @@
BOTTOM = :bottom
# Mode for textAlign().
BASELINE = :baseline
+ # Filter type for filter()
+ THRESHOLD = :threshold
+
+ # Filter type for filter()
+ GRAY = :gray
+
+ # Filter type for filter()
+ INVERT = :invert
+
+ # Filter type for filter()
+ BLUR = :blur
+
# Key codes.
ENTER = :enter
SPACE = :space
TAB = :tab
DELETE = :delete
@@ -1034,13 +1236,14 @@
@colorMaxes__ = [1.0] * 4
@angleScale__ = 1.0
@rectMode__ = nil
@ellipseMode__ = nil
@imageMode__ = nil
- @tint__ = nil
@textAlignH__ = nil
@textAlignV__ = nil
+ @tint__ = nil
+ @filter__ = nil
@matrixStack__ = []
@styleStack__ = []
@fontCache__ = {}
updateCanvas__ image, painter
@@ -1066,17 +1269,19 @@
@image__, @painter__ = image, painter
end
# @private
def beginDraw__()
+ raise "call beginDraw() before drawing" if @drawing__
@matrixStack__.clear
@styleStack__.clear
@drawing__ = true
end
# @private
def endDraw__()
+ raise unless @drawing__
@drawing__ = false
end
def width()
@image__.width
@@ -1440,10 +1645,44 @@
size = 256 if size > 256
font = @fontCache__[[name, size]] ||= Rays::Font.new name, size
@painter__.font = font
end
+ # Sets shader.
+ #
+ # @param shader [Shader] a shader to apply
+ #
+ # @return [nil] nil
+ #
+ def shader(shader)
+ @painter__.shader shader&.getInternal__
+ nil
+ end
+
+ # Resets shader.
+ #
+ # @return [nil] nil
+ #
+ def resetShader()
+ @painter__.no_shader
+ nil
+ end
+
+ # Applies an image filter to screen.
+ #
+ # overload filter(shader)
+ # overload filter(type)
+ # overload filter(type, param)
+ #
+ # @param shader [Shader] a shader to apply
+ # @param type [THRESHOLD, GRAY, INVERT, BLUR] filter type
+ # @param param [Numeric] a parameter for each filter
+ #
+ def filter(*args)
+ @filter__ = Shader.createFilter__(*args)
+ end
+
# Clears screen.
#
# @overload background(str)
# @overload background(str, alpha)
# @overload background(gray)
@@ -1725,16 +1964,13 @@
#
# @return [nil] nil
#
def image(img, a, b, c = nil, d = nil)
assertDrawing__
- i = img.getInternal__
- x, y, w, h = toXYWH__ @imageMode__, a, b, c || i.width, d || i.height
+ x, y, w, h = toXYWH__ @imageMode__, a, b, c || img.width, d || img.height
tint = @tint__ ? toRGBA__(*@tint__) : 1
- @painter__.push fill: tint, stroke: :none do |_|
- @painter__.image i, x, y, w, h
- end
+ img.drawImage__ @painter__, x, y, w, h, fill: tint, stroke: :none
nil
end
# Copies image.
#
@@ -1775,16 +2011,12 @@
#
# @return [nil] nil
#
def blend(img = nil, sx, sy, sw, sh, dx, dy, dw, dh, mode)
assertDrawing__
- src = img&.getInternal__ || @window__.canvas_image
- current = @painter__.blend_mode
-
- @painter__.blend_mode = mode
- @painter__.image src, sx, sy, sw, sh, dx, dy, dw, dh
- @painter__.blend_mode = current
+ img ||= self
+ img.drawImage__ @painter__, sx, sy, sw, sh, dx, dy, dw, dh, blend_mode: mode
end
# Saves screen image to file.
#
# @param filename [String] file name to save image
@@ -1844,15 +2076,14 @@
# @return [nil] nil
#
def pushMatrix(&block)
assertDrawing__
@matrixStack__.push @painter__.matrix
- if block
- block.call
- popMatrix
- end
+ block.call if block
nil
+ ensure
+ popMatrix if block
end
# Pops the current transformation matrix from stack.
#
# @return [nil] nil
@@ -1887,22 +2118,26 @@
@painter__.stroke_cap,
@painter__.stroke_join,
@painter__.clip,
@painter__.blend_mode,
@painter__.font,
+ @painter__.shader,
@hsbColor__,
@colorMaxes__,
@angleScale__,
@rectMode__,
@ellipseMode__,
- @imageMode__
+ @imageMode__,
+ @textAlignH__,
+ @textAlignV__,
+ @tint__,
+ @filter__,
]
- if block
- block.call
- popStyle
- end
+ block.call if block
nil
+ ensure
+ popStyle if block
end
# Restore style values from the style stack.
#
# @return [nil] nil
@@ -1916,30 +2151,34 @@
@painter__.stroke_cap,
@painter__.stroke_join,
@painter__.clip,
@painter__.blend_mode,
@painter__.font,
+ @painter__.shader,
@hsbColor__,
@colorMaxes__,
@angleScale__,
@rectMode__,
@ellipseMode__,
- @imageMode__ = @styleStack__.pop
+ @imageMode__,
+ @textAlignH__,
+ @textAlignV__,
+ @tint__,
+ @filter__ = @styleStack__.pop
nil
end
# Save current styles and transformations to stack.
#
# @return [nil] nil
#
def push(&block)
pushMatrix
pushStyle
- if block
- block.call
- pop
- end
+ block.call if block
+ ensure
+ pop if block
end
# Restore styles and transformations from stack.
#
# @return [nil] nil
@@ -1953,14 +2192,500 @@
def getInternal__()
@image__
end
# @private
+ def drawImage__(painter, *args, **states)
+ shader = painter.shader || @filter__&.getInternal__
+ painter.push shader: shader, **states do |_|
+ painter.image getInternal__, *args
+ end
+ end
+
+ # @private
private def assertDrawing__()
raise "call beginDraw() before drawing" unless @drawing__
end
+ #
+ # Utilities
+ #
+
+ # Returns the absolute number of the value.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] absolute number
+ #
+ def abs(value)
+ value.abs
+ end
+
+ # Returns the closest integer number greater than or equal to the value.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] rounded up number
+ #
+ def ceil(value)
+ value.ceil
+ end
+
+ # Returns the closest integer number less than or equal to the value.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] rounded down number
+ #
+ def floor(value)
+ value.floor
+ end
+
+ # Returns the closest integer number.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] rounded number
+ #
+ def round(value)
+ value.round
+ end
+
+ # Returns the natural logarithm (the base-e logarithm) of a number.
+ #
+ # @param value [Numeric] number (> 0.0)
+ #
+ # @return [Numeric] result number
+ #
+ def log(n)
+ Math.log n
+ end
+
+ # Returns Euler's number e raised to the power of value.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] result number
+ #
+ def exp(n)
+ Math.exp n
+ end
+
+ # Returns value raised to the power of exponent.
+ #
+ # @param value [Numeric] base number
+ # @param exponent [Numeric] exponent number
+ #
+ # @return [Numeric] value ** exponent
+ #
+ def pow(value, exponent)
+ value ** exponent
+ end
+
+ # Returns squared value.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] squared value
+ #
+ def sq(value)
+ value * value
+ end
+
+ # Returns squared value.
+ #
+ # @param value [Numeric] number
+ #
+ # @return [Numeric] squared value
+ #
+ def sqrt(value)
+ Math.sqrt value
+ end
+
+ # Returns the magnitude (or length) of a vector.
+ #
+ # @overload mag(x, y)
+ # @overload mag(x, y, z)
+ #
+ # @param x [Numeric] x of point
+ # @param y [Numeric] y of point
+ # @param z [Numeric] z of point
+ #
+ # @return [Numeric] magnitude
+ #
+ def mag(*args)
+ x, y, z = *args
+ case args.size
+ when 2 then Math.sqrt x * x + y * y
+ when 3 then Math.sqrt x * x + y * y + z * z
+ else raise ArgumentError
+ end
+ end
+
+ # Returns distance between 2 points.
+ #
+ # @overload dist(x1, y1, x2, y2)
+ # @overload dist(x1, y1, z1, x2, y2, z2)
+ #
+ # @param x1 [Numeric] x of first point
+ # @param y1 [Numeric] y of first point
+ # @param z1 [Numeric] z of first point
+ # @param x2 [Numeric] x of second point
+ # @param y2 [Numeric] y of second point
+ # @param z2 [Numeric] z of second point
+ #
+ # @return [Numeric] distance between 2 points
+ #
+ def dist(*args)
+ case args.size
+ when 4
+ x1, y1, x2, y2 = *args
+ xx, yy = x2 - x1, y2 - y1
+ Math.sqrt xx * xx + yy * yy
+ when 3
+ x1, y1, z1, x2, y2, z2 = *args
+ xx, yy, zz = x2 - x1, y2 - y1, z2 - z1
+ Math.sqrt xx * xx + yy * yy + zz * zz
+ else raise ArgumentError
+ end
+ end
+
+ # Normalize the value from range start..stop into 0..1.
+ #
+ # @param value [Numeric] number to be normalized
+ # @param start [Numeric] lower bound of the range
+ # @param stop [Numeric] upper bound of the range
+ #
+ # @return [Numeric] normalized value between 0..1
+ #
+ def norm(value, start, stop)
+ (value.to_f - start.to_f) / (stop.to_f - start.to_f)
+ end
+
+ # Returns the interpolated number between range start..stop.
+ #
+ # @param start [Numeric] lower bound of the range
+ # @param stop [Numeric] upper bound of the range
+ # @param amount [Numeric] amount to interpolate
+ #
+ # @return [Numeric] interporated number
+ #
+ def lerp(start, stop, amount)
+ start + (stop - start) * amount
+ end
+
+ # Maps a number from range start1..stop1 to range start2..stop2.
+ #
+ # @param value [Numeric] number to be mapped
+ # @param start1 [Numeric] lower bound of the range1
+ # @param stop1 [Numeric] upper bound of the range1
+ # @param start2 [Numeric] lower bound of the range2
+ # @param stop2 [Numeric] upper bound of the range2
+ #
+ # @return [Numeric] mapped number
+ #
+ def map(value, start1, stop1, start2, stop2)
+ lerp start2, stop2, norm(value, start1, stop1)
+ end
+
+ # Returns minimum value.
+ #
+ # @overload min(a, b)
+ # @overload min(a, b, c)
+ # @overload min(array)
+ #
+ # @param a [Numeric] value to compare
+ # @param b [Numeric] value to compare
+ # @param c [Numeric] value to compare
+ # @param array [Numeric] values to compare
+ #
+ # @return [Numeric] minimum value
+ #
+ def min(*args)
+ args.flatten.min
+ end
+
+ # Returns maximum value.
+ #
+ # @overload max(a, b)
+ # @overload max(a, b, c)
+ # @overload max(array)
+ #
+ # @param a [Numeric] value to compare
+ # @param b [Numeric] value to compare
+ # @param c [Numeric] value to compare
+ # @param array [Numeric] values to compare
+ #
+ # @return [Numeric] maximum value
+ #
+ def max(*args)
+ args.flatten.max
+ end
+
+ # Constrains the number between min..max.
+ #
+ # @param value [Numeric] number to be constrained
+ # @param min [Numeric] lower bound of the range
+ # @param max [Numeric] upper bound of the range
+ #
+ # @return [Numeric] constrained number
+ #
+ def constrain(value, min, max)
+ value < min ? min : (value > max ? max : value)
+ end
+
+ # Converts degree to radian.
+ #
+ # @param degree [Numeric] degree to convert
+ #
+ # @return [Numeric] radian
+ #
+ def radians(degree)
+ degree * DEG2RAD__
+ end
+
+ # Converts radian to degree.
+ #
+ # @param radian [Numeric] radian to convert
+ #
+ # @return [Numeric] degree
+ #
+ def degrees(radian)
+ radian * RAD2DEG__
+ end
+
+ # Returns the sine of an angle.
+ #
+ # @param angle [Numeric] angle in radians
+ #
+ # @return [Numeric] the sine
+ #
+ def sin(angle)
+ Math.sin angle
+ end
+
+ # Returns the cosine of an angle.
+ #
+ # @param angle [Numeric] angle in radians
+ #
+ # @return [Numeric] the cosine
+ #
+ def cos(angle)
+ Math.cos angle
+ end
+
+ # Returns the ratio of the sine and cosine of an angle.
+ #
+ # @param angle [Numeric] angle in radians
+ #
+ # @return [Numeric] the tangent
+ #
+ def tan(angle)
+ Math.tan angle
+ end
+
+ # Returns the inverse of sin().
+ #
+ # @param value [Numeric] value for calculation
+ #
+ # @return [Numeric] the arc sine
+ #
+ def asin(value)
+ Math.asin value
+ end
+
+ # Returns the inverse of cos().
+ #
+ # @param value [Numeric] value for calculation
+ #
+ # @return [Numeric] the arc cosine
+ #
+ def acos(value)
+ Math.acos value
+ end
+
+ # Returns the inverse of tan().
+ #
+ # @param value [Numeric] value for valculation
+ #
+ # @return [Numeric] the arc tangent
+ #
+ def atan(value)
+ Math.atan value
+ end
+
+ # Returns the angle from a specified point.
+ #
+ # @param y [Numeric] y of the point
+ # @param x [Numeric] x of the point
+ #
+ # @return [Numeric] the angle in radians
+ #
+ def atan2(y, x)
+ Math.atan2 y, x
+ end
+
+ # Returns the perlin noise value.
+ #
+ # @overload noise(x)
+ # @overload noise(x, y)
+ # @overload noise(x, y, z)
+ #
+ # @param x [Numeric] horizontal point in noise space
+ # @param y [Numeric] vertical point in noise space
+ # @param z [Numeric] depth point in noise space
+ #
+ # @return [Numeric] noise value (0.0..1.0)
+ #
+ def noise(x, y = 0, z = 0)
+ Rays.perlin(x, y, z) / 2.0 + 0.5
+ end
+
+ # Returns a random number in range low...high
+ #
+ # @overload random()
+ # @overload random(high)
+ # @overload random(low, high)
+ # @overload random(choices)
+ #
+ # @param low [Numeric] lower limit
+ # @param high [Numeric] upper limit
+ # @param choices [Array] array to choose from
+ #
+ # @return [Float] random number
+ #
+ def random(*args)
+ return args.first.sample if args.first.kind_of? Array
+ high, low = args.reverse
+ rand (low || 0).to_f...(high || 1).to_f
+ end
+
+ # Creates a new vector.
+ #
+ # @overload createVector()
+ # @overload createVector(x, y)
+ # @overload createVector(x, y, z)
+ #
+ # @param x [Numeric] x of new vector
+ # @param y [Numeric] y of new vector
+ # @param z [Numeric] z of new vector
+ #
+ # @return [Vector] new vector
+ #
+ def createVector(*args)
+ Vector.new(*args, context: self)
+ end
+
+ # Creates a new image.
+ #
+ # @overload createImage(w, h)
+ # @overload createImage(w, h, format)
+ #
+ # @param w [Numeric] width of new image
+ # @param h [Numeric] height of new image
+ # @param format [RGB, RGBA] image format
+ #
+ # @return [Image] new image
+ #
+ def createImage(w, h, format = RGBA)
+ colorspace = {RGB => Rays::RGB, RGBA => Rays::RGBA}[format]
+ raise ArgumentError, "Unknown image format" unless colorspace
+ Image.new Rays::Image.new(w, h, colorspace).paint {background 0, 0}
+ end
+
+ # Creates a new off-screen graphics context object.
+ #
+ # @param width [Numeric] width of graphics image
+ # @param height [Numeric] height of graphics image
+ #
+ # @return [Graphics] graphics object
+ #
+ def createGraphics(width, height)
+ Graphics.new width, height
+ end
+
+ # Creates a shader object.
+ #
+ # @overload createShader(vertPath, fragPath)
+ # @overload createShader(vertSource, fragSource)
+ #
+ # @param vertPath [String] vertex shader file path
+ # @param fragPath [String] fragment shader file path
+ # @param vertSource [String] vertex shader source
+ # @param fragSource [String] fragment shader source
+ #
+ # @return [Shader] shader object
+ #
+ def createShader(vert, frag)
+ vert = File.read if vert && File.exist?(vert)
+ frag = File.read if frag && File.exist?(frag)
+ Shader.new vert, frag
+ end
+
+ # Creates a camera object as a video input device.
+ #
+ # @return [Capture] camera object
+ #
+ def createCapture(*args)
+ Capture.new(*args)
+ end
+
+ # Loads image.
+ #
+ # @param filename [String] file name to load image
+ # @param extension [String] type of image to load (ex. 'png')
+ #
+ # @return [Image] loaded image object
+ #
+ def loadImage(filename, extension = nil)
+ filename = getImage__ filename, extension if filename =~ %r|^https?://|
+ Image.new Rays::Image.load filename
+ end
+
+ # Loads shader file.
+ #
+ # @overload loadShader(fragPath)
+ # @overload loadShader(fragPath, vertPath)
+ #
+ # @param fragPath [String] fragment shader file path
+ # @param vertPath [String] vertex shader file path
+ #
+ # @return [Shader] loaded shader object
+ #
+ def loadShader(fragPath, vertPath = nil)
+ createShader vertPath, fragPath
+ end
+
+ # @private
+ private def getImage__(uri, ext)
+ ext ||= File.extname uri
+ raise "unsupported image type -- #{ext}" unless ext =~ /^\.?(png|jpg|gif)$/i
+
+ tmpdir = tmpdir__
+ path = tmpdir + Digest::SHA1.hexdigest(uri)
+ path = path.sub_ext ext
+
+ unless path.file?
+ URI.open uri do |input|
+ input.set_encoding nil# disable default_internal
+ tmpdir.mkdir unless tmpdir.directory?
+ path.open('w') do |output|
+ output.set_encoding Encoding::ASCII_8BIT
+ while buf = input.read(2 ** 16)
+ output.write buf
+ end
+ end
+ end
+ end
+ path.to_s
+ end
+
+ # @private
+ private def tmpdir__()
+ Pathname(Dir.tmpdir) + Digest::SHA1.hexdigest(self.class.name)
+ end
+
end# GraphicsContext
# Draws graphics into an offscreen buffer
#
@@ -1976,12 +2701,12 @@
end
# Start drawing.
#
def beginDraw(&block)
- @painter__.__send__ :begin_paint
beginDraw__
+ @painter__.__send__ :begin_paint
push
if block
block.call
endDraw
end
@@ -1989,12 +2714,12 @@
# End drawing.
#
def endDraw()
pop
- endDraw__
@painter__.__send__ :end_paint
+ endDraw__
end
end# Graphics
@@ -2005,10 +2730,11 @@
include GraphicsContext
Vector = Processing::Vector
Capture = Processing::Capture
Graphics = Processing::Graphics
+ Shader = Processing::Shader
# @private
@@context__ = nil
# @private
@@ -2037,15 +2763,20 @@
@pointerPrevPos__ = Rays::Point.new 0
@pointersPressed__ = []
@touches__ = []
@motionGravity__ = createVector 0, 0
- @window__.before_draw = proc {beginDraw__}
- @window__.after_draw = proc {endDraw__}
+ @window__.before_draw = proc {beginDraw__}
+ @window__.after_draw = proc {endDraw__}
+ @window__.update_canvas = proc {|i, p| updateCanvas__ i, p}
+ @window__.instance_variable_set :@context, self
+ def @window__.draw_screen(painter)
+ @context.drawImage__ painter
+ end
+
drawFrame = -> {
- updateCanvas__ @window__.canvas_image, @window__.canvas_painter
begin
push
@drawBlock__.call if @drawBlock__
ensure
pop
@@ -2307,18 +3038,14 @@
# @private
def resizeCanvas__(name, width, height, pixelDensity)
raise '#{name}() must be called on startup or setup block' if @started__
@painter__.__send__ :end_paint
- begin
- @window__.__send__ :resize_canvas, width, height, pixelDensity
- updateCanvas__ @window__.canvas_image, @window__.canvas_painter
- ensure
- @painter__.__send__ :begin_paint
- end
-
+ @window__.resize_canvas width, height, pixelDensity
@window__.auto_resize = false
+ ensure
+ @painter__.__send__ :begin_paint
end
# Returns pixel density of display.
#
# @return [Numeric] pixel density
@@ -2451,455 +3178,9 @@
#
# @return [nil] nil
#
def redraw()
@redraw__ = true
- end
-
- #
- # Utilities
- #
-
- # Returns the absolute number of the value.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] absolute number
- #
- def abs(value)
- value.abs
- end
-
- # Returns the closest integer number greater than or equal to the value.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] rounded up number
- #
- def ceil(value)
- value.ceil
- end
-
- # Returns the closest integer number less than or equal to the value.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] rounded down number
- #
- def floor(value)
- value.floor
- end
-
- # Returns the closest integer number.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] rounded number
- #
- def round(value)
- value.round
- end
-
- # Returns the natural logarithm (the base-e logarithm) of a number.
- #
- # @param value [Numeric] number (> 0.0)
- #
- # @return [Numeric] result number
- #
- def log(n)
- Math.log n
- end
-
- # Returns Euler's number e raised to the power of value.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] result number
- #
- def exp(n)
- Math.exp n
- end
-
- # Returns value raised to the power of exponent.
- #
- # @param value [Numeric] base number
- # @param exponent [Numeric] exponent number
- #
- # @return [Numeric] value ** exponent
- #
- def pow(value, exponent)
- value ** exponent
- end
-
- # Returns squared value.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] squared value
- #
- def sq(value)
- value * value
- end
-
- # Returns squared value.
- #
- # @param value [Numeric] number
- #
- # @return [Numeric] squared value
- #
- def sqrt(value)
- Math.sqrt value
- end
-
- # Returns the magnitude (or length) of a vector.
- #
- # @overload mag(x, y)
- # @overload mag(x, y, z)
- #
- # @param x [Numeric] x of point
- # @param y [Numeric] y of point
- # @param z [Numeric] z of point
- #
- # @return [Numeric] magnitude
- #
- def mag(*args)
- x, y, z = *args
- case args.size
- when 2 then Math.sqrt x * x + y * y
- when 3 then Math.sqrt x * x + y * y + z * z
- else raise ArgumentError
- end
- end
-
- # Returns distance between 2 points.
- #
- # @overload dist(x1, y1, x2, y2)
- # @overload dist(x1, y1, z1, x2, y2, z2)
- #
- # @param x1 [Numeric] x of first point
- # @param y1 [Numeric] y of first point
- # @param z1 [Numeric] z of first point
- # @param x2 [Numeric] x of second point
- # @param y2 [Numeric] y of second point
- # @param z2 [Numeric] z of second point
- #
- # @return [Numeric] distance between 2 points
- #
- def dist(*args)
- case args.size
- when 4
- x1, y1, x2, y2 = *args
- xx, yy = x2 - x1, y2 - y1
- Math.sqrt xx * xx + yy * yy
- when 3
- x1, y1, z1, x2, y2, z2 = *args
- xx, yy, zz = x2 - x1, y2 - y1, z2 - z1
- Math.sqrt xx * xx + yy * yy + zz * zz
- else raise ArgumentError
- end
- end
-
- # Normalize the value from range start..stop into 0..1.
- #
- # @param value [Numeric] number to be normalized
- # @param start [Numeric] lower bound of the range
- # @param stop [Numeric] upper bound of the range
- #
- # @return [Numeric] normalized value between 0..1
- #
- def norm(value, start, stop)
- (value.to_f - start.to_f) / (stop.to_f - start.to_f)
- end
-
- # Returns the interpolated number between range start..stop.
- #
- # @param start [Numeric] lower bound of the range
- # @param stop [Numeric] upper bound of the range
- # @param amount [Numeric] amount to interpolate
- #
- # @return [Numeric] interporated number
- #
- def lerp(start, stop, amount)
- start + (stop - start) * amount
- end
-
- # Maps a number from range start1..stop1 to range start2..stop2.
- #
- # @param value [Numeric] number to be mapped
- # @param start1 [Numeric] lower bound of the range1
- # @param stop1 [Numeric] upper bound of the range1
- # @param start2 [Numeric] lower bound of the range2
- # @param stop2 [Numeric] upper bound of the range2
- #
- # @return [Numeric] mapped number
- #
- def map(value, start1, stop1, start2, stop2)
- lerp start2, stop2, norm(value, start1, stop1)
- end
-
- # Returns minimum value.
- #
- # @overload min(a, b)
- # @overload min(a, b, c)
- # @overload min(array)
- #
- # @param a [Numeric] value to compare
- # @param b [Numeric] value to compare
- # @param c [Numeric] value to compare
- # @param array [Numeric] values to compare
- #
- # @return [Numeric] minimum value
- #
- def min(*args)
- args.flatten.min
- end
-
- # Returns maximum value.
- #
- # @overload max(a, b)
- # @overload max(a, b, c)
- # @overload max(array)
- #
- # @param a [Numeric] value to compare
- # @param b [Numeric] value to compare
- # @param c [Numeric] value to compare
- # @param array [Numeric] values to compare
- #
- # @return [Numeric] maximum value
- #
- def max(*args)
- args.flatten.max
- end
-
- # Constrains the number between min..max.
- #
- # @param value [Numeric] number to be constrained
- # @param min [Numeric] lower bound of the range
- # @param max [Numeric] upper bound of the range
- #
- # @return [Numeric] constrained number
- #
- def constrain(value, min, max)
- value < min ? min : (value > max ? max : value)
- end
-
- # Converts degree to radian.
- #
- # @param degree [Numeric] degree to convert
- #
- # @return [Numeric] radian
- #
- def radians(degree)
- degree * DEG2RAD__
- end
-
- # Converts radian to degree.
- #
- # @param radian [Numeric] radian to convert
- #
- # @return [Numeric] degree
- #
- def degrees(radian)
- radian * RAD2DEG__
- end
-
- # Returns the sine of an angle.
- #
- # @param angle [Numeric] angle in radians
- #
- # @return [Numeric] the sine
- #
- def sin(angle)
- Math.sin angle
- end
-
- # Returns the cosine of an angle.
- #
- # @param angle [Numeric] angle in radians
- #
- # @return [Numeric] the cosine
- #
- def cos(angle)
- Math.cos angle
- end
-
- # Returns the ratio of the sine and cosine of an angle.
- #
- # @param angle [Numeric] angle in radians
- #
- # @return [Numeric] the tangent
- #
- def tan(angle)
- Math.tan angle
- end
-
- # Returns the inverse of sin().
- #
- # @param value [Numeric] value for calculation
- #
- # @return [Numeric] the arc sine
- #
- def asin(value)
- Math.asin value
- end
-
- # Returns the inverse of cos().
- #
- # @param value [Numeric] value for calculation
- #
- # @return [Numeric] the arc cosine
- #
- def acos(value)
- Math.acos value
- end
-
- # Returns the inverse of tan().
- #
- # @param value [Numeric] value for valculation
- #
- # @return [Numeric] the arc tangent
- #
- def atan(value)
- Math.atan value
- end
-
- # Returns the angle from a specified point.
- #
- # @param y [Numeric] y of the point
- # @param x [Numeric] x of the point
- #
- # @return [Numeric] the angle in radians
- #
- def atan2(y, x)
- Math.atan2 y, x
- end
-
- # Returns the perlin noise value.
- #
- # @overload noise(x)
- # @overload noise(x, y)
- # @overload noise(x, y, z)
- #
- # @param x [Numeric] horizontal point in noise space
- # @param y [Numeric] vertical point in noise space
- # @param z [Numeric] depth point in noise space
- #
- # @return [Numeric] noise value (0.0..1.0)
- #
- def noise(x, y = 0, z = 0)
- Rays.perlin(x, y, z) / 2.0 + 0.5
- end
-
- # Returns a random number in range low...high
- #
- # @overload random()
- # @overload random(high)
- # @overload random(low, high)
- # @overload random(choices)
- #
- # @param low [Numeric] lower limit
- # @param high [Numeric] upper limit
- # @param choices [Array] array to choose from
- #
- # @return [Float] random number
- #
- def random(*args)
- return args.first.sample if args.first.kind_of? Array
- high, low = args.reverse
- rand (low || 0).to_f...(high || 1).to_f
- end
-
- # Creates a new vector.
- #
- # @overload createVector()
- # @overload createVector(x, y)
- # @overload createVector(x, y, z)
- #
- # @param x [Numeric] x of new vector
- # @param y [Numeric] y of new vector
- # @param z [Numeric] z of new vector
- #
- # @return [Vector] new vector
- #
- def createVector(*args)
- Vector.new(*args, context: self)
- end
-
- # Creates a new image.
- #
- # @overload createImage(w, h)
- # @overload createImage(w, h, format)
- #
- # @param w [Numeric] width of new image
- # @param h [Numeric] height of new image
- # @param format [RGB, RGBA] image format
- #
- # @return [Image] new image
- #
- def createImage(w, h, format = RGBA)
- colorspace = {RGB => Rays::RGB, RGBA => Rays::RGBA}[format]
- raise ArgumentError, "Unknown image format" unless colorspace
- Image.new Rays::Image.new(w, h, colorspace).paint {background 0, 0}
- end
-
- # Creates a camera object as a video input device.
- #
- # @return [Capture] camera object
- #
- def createCapture(*args)
- Capture.new(*args)
- end
-
- # Creates a new off-screen graphics context object.
- #
- # @param width [Numeric] width of graphics image
- # @param height [Numeric] height of graphics image
- #
- # @return [Graphics] graphics object
- #
- def createGraphics(width, height)
- Graphics.new width, height
- end
-
- # Loads image.
- #
- # @param filename [String] file name to load image
- # @param extension [String] type of image to load (ex. 'png')
- #
- # @return [Image] loaded image object
- #
- def loadImage(filename, extension = nil)
- filename = getImage__ filename, extension if filename =~ %r|^https?://|
- Image.new Rays::Image.load filename
- end
-
- # @private
- private def getImage__(uri, ext)
- ext ||= File.extname uri
- raise "unsupported image type -- #{ext}" unless ext =~ /^\.?(png)$/i
-
- tmpdir = tmpdir__
- path = tmpdir + Digest::SHA1.hexdigest(uri)
- path = path.sub_ext ext
-
- unless path.file?
- URI.open uri do |input|
- input.set_encoding nil# disable default_internal
- tmpdir.mkdir unless tmpdir.directory?
- path.open('w') do |output|
- output.set_encoding Encoding::ASCII_8BIT
- while buf = input.read(2 ** 16)
- output.write buf
- end
- end
- end
- end
- path.to_s
- end
-
- # @private
- private def tmpdir__()
- Pathname(Dir.tmpdir) + Digest::SHA1.hexdigest(self.class.name)
end
end# Context