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