Last active
May 7, 2016 17:28
-
-
Save wanabe/aef90faa10e8268d4d1efb0dd68488d4 to your computer and use it in GitHub Desktop.
Three.js on Opal (wrapped)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <style> | |
| html { | |
| height: 100%; | |
| } | |
| body { | |
| margin: 0px; | |
| height:100% | |
| } | |
| #three-canvas { | |
| height: 100%; | |
| width: 100%; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script src="//cdn.opalrb.org/opal/0.9.2/opal.min.js"></script> | |
| <script src="//cdn.opalrb.org/opal/0.9.2/opal-parser.min.js"></script> | |
| <script src="//cdn.opalrb.org/opal/0.9.2/external/opal-browser-0.2.0.js"></script> | |
| <script src="https://raw.githubusercontent.com/mrdoob/three.js/r76/build/three.min.js"></script> | |
| <script src="https://raw.githubusercontent.com/marcj/css-element-queries/master/src/ResizeSensor.js"></script> | |
| <div id="three-canvas"></div> | |
| <script>Opal.load('opal-parser')</script> | |
| <script type="text/ruby"> | |
| class NativeClass | |
| include Native | |
| def initialize(*args) | |
| @native = native(*args) | |
| end | |
| end | |
| module THREE | |
| class Base < NativeClass | |
| end | |
| class Camera < Base | |
| def native | |
| `new THREE.Camera()` | |
| end | |
| alias_native :position | |
| end | |
| class Scene < Base | |
| def native | |
| `new THREE.Scene()` | |
| end | |
| alias_native :add | |
| end | |
| class WebGLRenderer < Base | |
| def native | |
| `new THREE.WebGLRenderer()` | |
| end | |
| alias_native :pixel_ratio=, :setPixelRatio | |
| alias_native :domElement | |
| alias_native :set_size, :setSize | |
| alias_native :render | |
| end | |
| class PlaneGeometry < Base | |
| def native(width, height, width_segments = nil, height_segments = nil) | |
| `new THREE.PlaneGeometry(width, height, width_segments, height_segments)` | |
| end | |
| end | |
| class ShaderMaterial < Base | |
| def native(parameters) | |
| parameters = parameters.to_n | |
| `new THREE.ShaderMaterial(parameters)` | |
| end | |
| end | |
| class Mesh < Base | |
| def native(geometry, material) | |
| geometry, material = geometry.to_n, material.to_n | |
| `new THREE.Mesh(geometry, material)` | |
| end | |
| end | |
| class Vector2 < Base | |
| def native | |
| `new THREE.Vector2()` | |
| end | |
| alias_native :x | |
| alias_native :y | |
| end | |
| end | |
| class ResizeSensor < NativeClass | |
| def native(element, proc) | |
| element = element.to_n | |
| proc = proc.to_proc unless proc.is_a? Proc | |
| `new ResizeSensor(element, proc)` | |
| end | |
| end | |
| class App | |
| class << self | |
| def run | |
| new.run | |
| end | |
| end | |
| def run | |
| animate | |
| end | |
| def initialize | |
| @camera = THREE::Camera.new | |
| @scene = THREE::Scene.new | |
| @renderer = THREE::WebGLRenderer.new | |
| @renderer.pixel_ratio = $$.devicePixelRatio | |
| @container = $document['three-canvas'] | |
| @container << @renderer.domElement.to_n | |
| @renderer.set_size @container.width, @container.height | |
| @animate = method(:animate).to_proc | |
| @running = true | |
| end | |
| def animate | |
| return unless @running | |
| $$.requestAnimationFrame @animate | |
| render | |
| @renderer.render(@scene, @camera) | |
| end | |
| end | |
| class Shader | |
| BRACE_TABLE = { "(" => [1, 0], ")" => [-1, 1], "," => [0, 0] } | |
| OP_TABLE = { | |
| "plus" => "+", "minus" => "-", "times" => "*", "divide" => "/", | |
| "lt" => "<", "gt" => "<", "le" => "<=", "ge" => ">=" | |
| } | |
| TYPE_TABLE = { "float" => "f", "vec2" => "v2" } | |
| def initialize(**initial_values) | |
| @initial_values = initial_values | |
| parse | |
| end | |
| def to_s | |
| @shader | |
| end | |
| def uniforms | |
| uniforms = Native(@uniforms.to_n) | |
| @initial_values.each do |name, value| | |
| uniforms[name].value = value | |
| end | |
| uniforms | |
| end | |
| private | |
| def parse | |
| meth = method(:main) | |
| c = 0 | |
| str = `String(meth.method)` | |
| .sub(/function */, 'void main') | |
| .sub(/ *var( .*)? self = .*\n *\n/, '') | |
| .gsub(/ return /, ' ') | |
| .gsub(/(gl_Position|gl_FragColor)\$/) { $1 } | |
| .gsub(/[(),]/) { | |
| d, o = BRACE_TABLE[$&] | |
| c += d | |
| "#{$&}<#{c + o}>" | |
| } | |
| str = str.sub(/void main\(<1>(.+?)\)<1>/, "void main(<1>)<1>") | |
| if $1 | |
| uniforms = $1.split(/,<1> /) | |
| @uniforms = {} | |
| uniforms.each_with_index do |uniform, i| | |
| str = str.sub(/ *if \(<1>#{uniform} == null\)<1> {\n *#{uniform} = self\.\$([^()]+)\(<1>\)<1>\n *}\n/, "") | |
| raise "shader parse error" unless $1 | |
| str = "uniform #{$1} #{uniform};\n" + str | |
| @uniforms[uniform] = { type: TYPE_TABLE[$1] } | |
| end | |
| end | |
| str = str | |
| .gsub(/( *)(.*) = self.\$(float)\(<\d+>\)<\d+>;$/) { | |
| "#{$1}#{$3} #{$2.gsub(/ += +/, ', ')};" | |
| } | |
| .gsub(/^( *)(\w+) = self.\$(float|vec[2])\(<1>(.*?)\)<1>;$/) { | |
| "#{$1}#{$3} #{$2} = #{$4};" | |
| } | |
| .gsub(/(?:self\.|(\.))?\$([^()]*)\(<\d+>\)<\d+>/) { | |
| "#{$1}#{$2}" | |
| } | |
| .gsub(/if \(<1>\(<2>\(<3>\$a = (.*?)\)<3> !== nil && \(<3>!\$a.\$\$is_boolean \|\| \$a == true\)<3>\)<2>\)<1> {/) { | |
| "if (#{$1}) {" | |
| } | |
| .gsub(/ ( *)(.*)};/) { | |
| " #{$1}#{$2};\n#{$1}}" | |
| } | |
| .gsub(/([>\- ])(\d+)([), ])/) { | |
| "#{$1}#{$2}.0#{$3}" | |
| } | |
| str2 = nil | |
| while str != str2 | |
| str = str2 || str | |
| str2 = str | |
| .gsub(/\$rb_(plus|minus|times|divide|lt|le|gt|ge)\(<(\d+)>(.*?),<\2> *(.*?)\)<\2>/) { | |
| "#{$3} #{OP_TABLE[$1]} #{$4}" | |
| } | |
| .gsub(/self\.\$([^()]+)\(<(\d+)>(.*?)\)<\2>/) { | |
| "#{$1}(#{$3})" | |
| } | |
| end | |
| @shader = str.gsub(/([(),])<\d+>/) { $1 } | |
| end | |
| end | |
| class VertexShader < Shader | |
| end | |
| class FragmentShader < Shader | |
| end | |
| </script> | |
| <script type="text/ruby"> | |
| class MyVertexShader < VertexShader | |
| def main | |
| gl_Position = vec4( position, 1.0 ) | |
| end | |
| end | |
| class MyFragmentShader < FragmentShader | |
| def main(resolution = vec2, time = float) | |
| p = vec2 -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy | |
| a = float time * 40.0 | |
| g = float 1.0 / 40.0 | |
| d = e = f = h = i = r = q = float | |
| e = 400.0 * (p.x * 0.5 + 0.5) | |
| f = 400.0 * (p.y * 0.5 + 0.5) | |
| i = 200.0 + sin(e * g + a / 150.0) * 20.0 | |
| d = 200.0 + cos(f * g / 2.0) * 18.0 + cos(e * g) * 7.0 | |
| r = sqrt(pow(abs(i - e), 2.0) + pow(abs(d - f), 2.0)) | |
| q = f / r | |
| e = (r * cos(q)) - a / 2.0 | |
| f = (r * sin(q)) - a / 2.0 | |
| d = sin(e * g) * 176.0 + sin(e * g) * 164.0 + r | |
| h = ((f + d) + a / 2.0) * g | |
| i = cos(h + r * p.x / 1.3) * (e + e + a) + cos(q * g * 6.0) * (r + h / 3.0) | |
| h = sin(f * g) * 144.0 - sin(e * g) * 212.0 * p.x | |
| h = (h + (f - e) * q + sin(r - (a + h) / 7.0) * 10.0 + i / 4.0) * g | |
| i += cos(h * 2.3 * sin(a / 350.0 - q)) * 184.0 * sin(q - (r * 4.3 + a / 12.0) * g) + tan(r * g + h) * 184.0 * cos(r * g + h) | |
| i = mod(i / 5.6, 256.0) / 64.0 | |
| i += 4.0 if i < 0.0 | |
| i = 4.0 - i if i >= 2.0 | |
| d = r / 350.0 | |
| d += sin(d * d * 8.0) * 0.52 | |
| f = (sin(a * g) + 1.0) / 2.0 | |
| gl_FragColor = vec4(vec3(f * i / 1.6, i / 2.0 + d / 13.0, i) * d * p.x + vec3(i / 1.3 + d / 8.0, i / 2.0 + d / 18.0, i) * d * (1.0 - p.x), 1.0) | |
| end | |
| end | |
| class MyApp < App | |
| def run | |
| on_resize | |
| super | |
| end | |
| def initialize | |
| @margin = 5 | |
| super | |
| @container.on :click do |event| | |
| @running = !@running | |
| run if @running | |
| end | |
| @camera.position.z = 1 | |
| geometry = THREE::PlaneGeometry.new(2, 2) | |
| vertex_shader = MyVertexShader.new | |
| fragment_shader = MyFragmentShader.new(time: 1.0, resolution: THREE::Vector2.new) | |
| @uniforms = fragment_shader.uniforms | |
| material_arg = { | |
| uniforms: @uniforms, | |
| vertexShader: vertex_shader.to_s, | |
| fragmentShader: fragment_shader.to_s | |
| } | |
| material = THREE::ShaderMaterial.new material_arg | |
| mesh = THREE::Mesh.new geometry, material | |
| @scene.add mesh | |
| @sensor = ResizeSensor.new(@container, method("on_resize")) | |
| end | |
| def render | |
| @uniforms.time.value += 0.05 | |
| end | |
| def on_resize | |
| return unless @running | |
| @renderer.set_size @container.width - @margin, @container.height - @margin | |
| @uniforms.resolution.value.x = @renderer.domElement.width | |
| @uniforms.resolution.value.y = @renderer.domElement.height | |
| end | |
| end | |
| MyApp.run | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment