diff --git a/examples/js_dom_cube/README.md b/examples/js_dom_cube/README.md new file mode 100644 index 0000000000..b108c00d8f --- /dev/null +++ b/examples/js_dom_cube/README.md @@ -0,0 +1,7 @@ +Interactive 3D cube using DOM & WebGL API. + +# Compiling +``` +v -b js_browser examples/js_dom_cube/cube.js.v +``` +Then you can open `index.html` with your favourite browser. diff --git a/examples/js_dom_cube/cube.js.v b/examples/js_dom_cube/cube.js.v new file mode 100644 index 0000000000..3e5758daf0 --- /dev/null +++ b/examples/js_dom_cube/cube.js.v @@ -0,0 +1,452 @@ +import jsdom +import math + +const ( + vert_code = 'attribute vec3 position;uniform mat4 Pmatrix;uniform mat4 Vmatrix;uniform mat4 Mmatrix;attribute vec3 color;varying vec3 vColor;void main(void) {gl_Position = Pmatrix * Vmatrix * Mmatrix * vec4(position,1.);vColor = color;} + ' + + frag_code = 'precision mediump float;varying vec3 vColor;void main(void) {gl_FragColor = vec4(vColor, 1.);} + ' + + vertices = [ + f32(-1), + -1, + -1, + 1, + -1, + -1, + 1, + 1, + -1, + -1, + 1, + -1, + -1, + -1, + 1, + 1, + -1, + 1, + 1, + 1, + 1, + -1, + 1, + 1, + -1, + -1, + -1, + -1, + 1, + -1, + -1, + 1, + 1, + -1, + -1, + 1, + 1, + -1, + -1, + 1, + 1, + -1, + 1, + 1, + 1, + 1, + -1, + 1, + -1, + -1, + -1, + -1, + -1, + 1, + 1, + -1, + 1, + 1, + -1, + -1, + -1, + 1, + -1, + -1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + -1, + ] + colors = [ + f32(5), + 3, + 7, + 5, + 3, + 7, + 5, + 3, + 7, + 5, + 3, + 7, + 1, + 1, + 3, + 1, + 1, + 3, + 1, + 1, + 3, + 1, + 1, + 3, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 1, + 0, + 1, + 1, + 0, + 1, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + ] + indices = [ + u16(0), + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23, + ] + amortization = 0.95 +) + +fn get_webgl() (&JS.HTMLCanvasElement, JS.WebGLRenderingContext) { + elem := jsdom.document.getElementById('myCanvas'.str) or { panic('cannot get canvas') } + match elem { + JS.HTMLCanvasElement { + webgl := elem.getContext('experimental-webgl'.str, js_undefined()) or { + panic('context not found') + } + match webgl { + JS.WebGLRenderingContext { + return elem, webgl + } + else { + panic('cannot get webgl') + } + } + } + else { + panic('not an canvas') + } + } +} + +fn get_projection(angle f64, a f64, z_min f64, z_max f64) []f64 { + ang := math.tan((angle * 0.5) * math.pi / 180) + return [ + 0.5 / ang, + 0, + 0, + 0, + 0, + 0.5 * a / ang, + 0, + 0, + 0, + 0, + -(z_max + z_min) / (z_max - z_min), + -1, + 0, + 0, + (-2 * z_max * z_min) / (z_max - z_min), + 0, + ] +} + +fn JS.Math.cos(JS.Number) JS.Number +fn JS.Math.sin(JS.Number) JS.Number +fn rotate_x(mut m []f64, angle f64) { + c := math.cos(angle) + s := math.sin(angle) + mv1 := m[1] + mv5 := m[5] + mv9 := m[9] + m[1] = m[1] * c - m[2] * s + m[5] = m[5] * c - m[6] * s + m[9] = m[9] * c - m[10] * s + + m[2] = m[2] * c + mv1 * s + m[6] = m[6] * c + mv5 * s + m[10] = m[10] * c + mv9 * s +} + +fn rotate_y(mut m []f64, angle f64) { + c := math.cos(angle) + s := math.sin(angle) + + mv0 := m[0] + mv4 := m[4] + mv8 := m[8] + m[0] = c * m[0] + s * m[2] + m[4] = c * m[4] + s * m[6] + m[8] = c * m[8] + s * m[10] + + m[2] = c * m[2] - s * mv0 + m[6] = c * m[6] - s * mv4 + m[10] = c * m[10] - s * mv8 +} + +struct State { +mut: + drag bool + gl JS.WebGLRenderingContext + canvas JS.HTMLCanvasElement + old_x f64 + old_y f64 + dx f64 + dy f64 + theta f64 + phi f64 + time_old f64 + mo_matrix []f64 + view_matrix []f64 + proj_matrix []f64 + pmatrix JS.WebGLUniformLocation + vmatrix JS.WebGLUniformLocation + mmatrix JS.WebGLUniformLocation + index_buffer JS.WebGLBuffer +} + +fn animate(mut state State, time f64) { + if !state.drag { + state.dx = state.dx * amortization + state.dy = state.dy * amortization + state.theta += state.dx + state.phi += state.dy + } + + state.mo_matrix[0] = 1 + state.mo_matrix[1] = 0 + state.mo_matrix[2] = 0 + state.mo_matrix[3] = 0 + + state.mo_matrix[4] = 0 + state.mo_matrix[5] = 1 + state.mo_matrix[6] = 0 + state.mo_matrix[7] = 0 + + state.mo_matrix[8] = 0 + state.mo_matrix[9] = 0 + state.mo_matrix[10] = 1 + state.mo_matrix[11] = 0 + + state.mo_matrix[12] = 0 + state.mo_matrix[13] = 0 + state.mo_matrix[14] = 0 + state.mo_matrix[15] = 1 + // println('${state.theta} ${state.phi}') + rotate_x(mut state.mo_matrix, state.phi) + rotate_y(mut state.mo_matrix, state.theta) + state.time_old = time + state.gl.enable(jsdom.gl_depth_test()) + state.gl.clearColor(0.5, 0.5, 0.5, 0.9) + state.gl.clearDepth(1.0) + state.gl.viewport(0.0, 0.0, state.canvas.width, state.canvas.height) + state.gl.clear(JS.Number(int(jsdom.gl_color_buffer_bit()) | int(jsdom.gl_depth_buffer_bit()))) + + state.gl.uniformMatrix4fv(state.pmatrix, JS.Boolean(false), state.proj_matrix.to_number_array()) + state.gl.uniformMatrix4fv(state.vmatrix, JS.Boolean(false), state.view_matrix.to_number_array()) + state.gl.uniformMatrix4fv(state.mmatrix, JS.Boolean(false), state.mo_matrix.to_number_array()) + + state.gl.bindBuffer(jsdom.gl_element_array_buffer(), state.index_buffer) + state.gl.drawElements(jsdom.gl_triangles(), indices.len, jsdom.gl_unsigned_short(), + 0) + + jsdom.window().requestAnimationFrame(fn [mut state] (time JS.Number) { + animate(mut state, f64(time)) + }) +} + +fn main() { + canvas, gl := get_webgl() + + vertex_buffer := gl.createBuffer() ? + gl.bindBuffer(jsdom.gl_array_buffer(), vertex_buffer) + gl.bufferData(jsdom.gl_array_buffer(), float32_array(vertices), jsdom.gl_static_draw()) + + color_buffer := gl.createBuffer() ? + gl.bindBuffer(jsdom.gl_array_buffer(), color_buffer) + gl.bufferData(jsdom.gl_array_buffer(), float32_array(colors), jsdom.gl_static_draw()) + + index_buffer := gl.createBuffer() ? + gl.bindBuffer(jsdom.gl_element_array_buffer(), index_buffer) + gl.bufferData(jsdom.gl_element_array_buffer(), uint16_array(indices), jsdom.gl_static_draw()) + + vert_shader := gl.createShader(jsdom.gl_vertex_shader()) ? + gl.shaderSource(vert_shader, vert_code.str) + gl.compileShader(vert_shader) + + if !bool(JS.Boolean(gl.getShaderParameter(vert_shader, jsdom.gl_compile_status()))) { + panic('An error occurred when compiling vertex shader: ${string(gl.getShaderInfoLog(vert_shader))}') + } + + frag_shader := gl.createShader(jsdom.gl_fragment_shader()) ? + gl.shaderSource(frag_shader, frag_code.str) + gl.compileShader(frag_shader) + if !bool(JS.Boolean(gl.getShaderParameter(frag_shader, jsdom.gl_compile_status()))) { + panic('An error occurred when compiling fragment shader: ${string(gl.getShaderInfoLog(frag_shader))}') + } + + shader_program := gl.createProgram() ? + gl.attachShader(shader_program, vert_shader) + gl.attachShader(shader_program, frag_shader) + gl.linkProgram(shader_program) + + if !bool(JS.Boolean(gl.getProgramParameter(shader_program, jsdom.gl_link_status()))) { + panic('unable to initialize the shader program: ${string(gl.getProgramInfoLog(shader_program))}') + } + + pmatrix := gl.getUniformLocation(shader_program, 'Pmatrix'.str) ? + vmatrix := gl.getUniformLocation(shader_program, 'Vmatrix'.str) ? + mmatrix := gl.getUniformLocation(shader_program, 'Mmatrix'.str) ? + + gl.bindBuffer(jsdom.gl_array_buffer(), vertex_buffer) + position := gl.getAttribLocation(shader_program, 'position'.str) + gl.vertexAttribPointer(position, JS.Number(3), jsdom.gl_float(), JS.Boolean(false), + JS.Number(0), JS.Number(0)) + gl.enableVertexAttribArray(position) + + gl.bindBuffer(jsdom.gl_array_buffer(), color_buffer) + color := gl.getAttribLocation(shader_program, 'color'.str) + gl.vertexAttribPointer(color, JS.Number(3), jsdom.gl_float(), JS.Boolean(false), JS.Number(0), + JS.Number(0)) + gl.enableVertexAttribArray(color) + gl.useProgram(shader_program) + + mut proj_matrix := get_projection(40.0, f64(canvas.width) / f64(canvas.height), 1.0, + 100.0) + mut mo_matrix := [f64(1), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + mut view_matrix := [f64(1), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + + view_matrix[14] = view_matrix[14] - 6 + + mut state := State{false, gl, canvas, 0, 0, 0, 0, 0, 0, 0, mo_matrix, view_matrix, proj_matrix, pmatrix, vmatrix, mmatrix, index_buffer} + + canvas.addEventListener('mousedown'.str, fn [mut state] (e JS.Event) { + state.drag = true + match e { + JS.MouseEvent { + state.old_x = f64(e.pageX) + state.old_y = f64(e.pageY) + e.preventDefault() + } + else {} + } + }, JS.EventListenerOptions{}) + + canvas.addEventListener('mouseup'.str, fn [mut state] (e JS.Event) { + state.drag = false + }, JS.EventListenerOptions{}) + canvas.addEventListener('mouseout'.str, fn [mut state] (e JS.Event) { + state.drag = false + }, JS.EventListenerOptions{}) + canvas.addEventListener('mousemove'.str, fn [mut state] (e JS.Event) { + if !state.drag { + return + } + match e { + JS.MouseEvent { + state.dx = (f64(e.pageX) - state.old_x) * 2.0 * math.pi / f64(state.canvas.width) + state.dy = (f64(e.pageY) - state.old_y) * 2.0 * math.pi / f64(state.canvas.height) + state.theta += state.dx + state.phi += state.dy + state.old_x = f64(e.pageX) + state.old_y = f64(e.pageY) + e.preventDefault() + } + else { + panic('not a mouse event??') + } + } + }, JS.EventListenerOptions{}) + + animate(mut state, 0) +} diff --git a/examples/js_dom_cube/index.html b/examples/js_dom_cube/index.html new file mode 100644 index 0000000000..ce0e50d29d --- /dev/null +++ b/examples/js_dom_cube/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index ccadd18ec8..021ac160df 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -406,6 +406,16 @@ pub fn (a array) to_js_array() JS.Array { return tmp } +pub fn (a array) to_number_array() JS.Array { + tmp := JS.Array.prototype.constructor() + for i in 0 .. a.len { + elem := a.arr.get(i) + _ := elem + #tmp.push(Number(elem.valueOf())); + } + return tmp +} + type EveryFn = fn (JS.Number, JS.Number) JS.Boolean type BigEveryFn = fn (JS.BigInt, JS.Number) JS.Boolean diff --git a/vlib/jsdom/jsdom.js.v b/vlib/jsdom/jsdom.js.v index b3e20688ba..1087608358 100644 --- a/vlib/jsdom/jsdom.js.v +++ b/vlib/jsdom/jsdom.js.v @@ -576,6 +576,8 @@ pub fn on_device_orientation(cb fn (win JS.Window, ev JS.DeviceOrientationEvent) return clos } +pub type AnimationFrameCallback = fn (JS.Number) + pub interface JS.Window { JS.EventTarget closed JS.Boolean @@ -610,6 +612,7 @@ pub interface JS.Window { scroll(x JS.Number, y JS.Number) scrollBy(x JS.Number, y JS.Number) scrollTo(x JS.Number, y JS.Number) + requestAnimationFrame(callback AnimationFrameCallback) mut: name string opener JS.Any @@ -735,8 +738,109 @@ pub interface JS.WebGLRenderingContext { bufferData(target JS.Number, data JS.TypedArray, usage JS.Number) shaderSource(shader JS.WebGLShader, source JS.String) getShaderParameter(shader JS.WebGLShader, pname JS.Number) JS.Any + vertexAttribPointer(index JS.Number, size JS.Number, typ JS.Number, normalized JS.Boolean, stride JS.Number, offset JS.Number) + getAttribLocation(program JS.WebGLProgram, name JS.String) JS.Number + useProgram(program JS.WebGLProgram) + getUniformLocation(program JS.WebGLProgram, name JS.String) ?JS.WebGLUniformLocation + uniformMatrix2fv(location JS.WebGLUniformLocation, transpose JS.Boolean, value JS.Array) + uniformMatrix3fv(location JS.WebGLUniformLocation, transpose JS.Boolean, value JS.Array) + uniformMatrix4fv(location JS.WebGLUniformLocation, transpose JS.Boolean, value JS.Array) + getProgramInfoLog(program JS.WebGLProgram) JS.String + getShaderInfoLog(shader JS.WebGLShader) JS.String + viewport(x JS.Number, y JS.Number, width JS.Number, height JS.Number) } pub interface JS.WebGL2RenderingContext { JS.WebGLRenderingContext } + +pub fn gl_vertex_shader() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.VERTEX_SHADER; + + return num +} + +pub fn gl_fragment_shader() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.FRAGMENT_SHADER; + + return num +} + +pub fn gl_element_array_buffer() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.ELEMENT_ARRAY_BUFFER; + + return num +} + +pub fn gl_array_buffer() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.ARRAY_BUFFER; + + return num +} + +pub fn gl_color_buffer_bit() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.COLOR_BUFFER_BIT; + + return num +} + +pub fn gl_depth_buffer_bit() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.COLOR_BUFFER_BIT; + + return num +} + +pub fn gl_triangles() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.TRIANGLES; + + return num +} + +pub fn gl_unsigned_short() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.UNSIGNED_SHORT; + + return num +} + +pub fn gl_static_draw() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.STATIC_DRAW; + + return num +} + +pub fn gl_link_status() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.LINK_STATUS; + + return num +} + +pub fn gl_compile_status() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.COMPILE_STATUS; + + return num +} + +pub fn gl_float() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.FLOAT; + + return num +} + +pub fn gl_depth_test() JS.Number { + mut num := JS.Number{} + #num = WebGLRenderingContext.DEPTH_TEST; + + return num +} diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 89f3558074..abff4585ca 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -3184,7 +3184,15 @@ fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { } g.writeln('return s; })()') } else { - g.write("\"$text\"") + g.write('"') + for char in text { + if char == `\n` { + g.write('\\n') + } else { + g.write('$char.ascii_str()') + } + } + g.write('"') } if true || should_cast { g.write(')')