Setting up 3-D rotation in web page

In summary, the user is asking for help with embedding a CDF file into an HTML file. They are having difficulty doing it and would like some help. They mention that they have access to the data points inside the plot in Mathematica. They also mention that WebGL could be a possibility. They suggest looking into some of the available libraries.
  • #1
aheight
321
109
I would like to add an interactive 3-D rotation to my web page and I was wondering if someone could suggest what I could look into. I understand U3D is a common format for doing this in PDF files. Is that the format I would need to use in a web page? Would I need a Java applet to set this up? I have looked on-line but not successful in finding anything useful. I am using Mathematica.

I simply wish to plot a 3D graph and give the reader the ability to use the mouse to rotate it freely. Could anyone suggest a simple example on the web I could review the code for?
 
Technology news on Phys.org
  • #2
If you are using Mathematica, then you should do it using the Computable Document Format http://www.wolfram.com/cdf/adopting-cdf/deploying-cdf/web-delivery.html
 
  • Like
Likes aheight
  • #3
Thanks DrClaude. That seems to be the way to go. I'll work on it.
 
  • #4
Have you looked into WebGL ? It is a javascript library that could be of help. You can use it and handle your mouse events with js script too.
As for other resources to create 3d objects on a web page, css 3d and html5 canvas can also do this. Rotation you mean is about visible transformation of objects via computed matrices in a timely manner.
 
  • #5
Pepper Mint said:
Have you looked into WebGL ? It is a javascript library that could be of help. You can use it and handle your mouse events with js script too.
As for other resources to create 3d objects on a web page, css 3d and html5 canvas can also do this. Rotation you mean is about visible transformation of objects via computed matrices in a timely manner.

Thanks. I'll keep those in mind. I've been working on the CDF interface and finding it formidable; nothing is working. I can't even get it to work locally on my machine. Would like to get it working though so will keep at it.
 
  • #6
This is where I am regarding embedding CDF files in an HTML file:

1. I generate the plot in Mathematica. I then select just the plot and not the code that generates it. I then copy that plot to a new notebook and then save that notebook to a mycdfplot.cdf file in c:\cdfplayer directory.

2. I can then click on the mycdfplot.cdf file and it will automatically run the CDF player and display the plot with interactive 3D capabilities.

3. I next follow one of many instructions on the web including Wolfram's about how to set up an HTML file to run CDF. I then create the following HTML file:

HTML:
<html>
<head>
<script type="text/javascript" src="[PLAIN]http://www.wolfram.com/cdf-player/plugin/v2.1/cdfplugin.js"></script>[/PLAIN] 
<script type="text/javascript">
              var cdf = new cdfplugin();
              cdf.embed('c:cdfplayer\mycdfplot.cdf', 500,500);
</script>
</head>
<body>
<dif> this is a test </dif>
</body>
</html>

and when I open the HTML file in Google Chrome it doesn't run the CDF player but rather ask if I want to load the CDF player. I was wondering if someone more familiar with this could look at my code to see if there is some coding I'm not doing correctly?

Can I ask if there is a way to run an HTML file in debug mode and if so how?
 
Last edited by a moderator:
  • Like
Likes Pepper Mint
  • #7
I would try to stay away from solutions that require a plugin. Many people are not going to install another plugin just to view the graph on your page.
There are several WebGL based JavaScript libraries that can be used instead. For example "three.js".
Here is a demonstration with rotating geometric bodies. If you click on "view source" you can see that a new object can be placed in the 3D environment with just 2 to 3 lines of code.
 
  • #8
Hi DrZoidberg,

That's a good point about the plugins. What I would like is to have a 3D model displayed on my web page, then give the reader the option of using the mouse and interactively rotating it. I have complete access to the 3D data points (x,y,z) inside a 3D plot in Mathematica. Would you know if WebGL would allow me to add this feature in my web page? Sounds like a neat programming challenge to take a 3D file in Mathematica and then do this if it can be done. I'll look into it and am also working with the CDF viewer in Mathematica as I just upgraded to the newest version and have support now.

Ok, just looked at some of the examples. Looks nice and can interactively rotate the objects.
 
Last edited:
  • Like
Likes Pepper Mint
  • #9
Thanks Pepper Mint and Dr. Zoidberg for suggesting WebGL. It's terrific and challenging! The first part in setting up a WebGL interface in an HTML document is to create a drawing canvas object in the document . Once that's created, we can begin plotting on the canvas. Lots of good tutorials about WebGL on line. This is a very simple example of setting up a canvas and plotting a simple line and a long way to go before I can interactively rotate the attached plot in my web page. If it's ok with you guys, I'll post updates as I become better at coding WebGL.

HTML:
<html>
<head>
<title> simple line in 2d context</title>
<p> In order to draw in an html file, must first set  up a canvas object: </p>
<canvas id="mycanvas" style="border: none;" width="50" height="50"></canvas>
<p> This is more text then next is a simple java script to draw a line in this canvas </p>

<script type="text/javascript">
function drawLine() {
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(200,100);
ctx.stroke();
}
</script>
<body onload="drawLine();"
</body>
</html>

htmlfigure1.jpg
 
  • #10
Not to discourage you from creating a sweet live-rendered WebGL solution, but it need not be so complicated. If you can export a screenshot of your graph every few degrees from Mathematica you can tie them together with a bit of JavaScript and you're all set.

http://reel360.org/reel

^nice jQuery plugin that I've used in the past
 
  • Like
Likes Pepper Mint
  • #11
nolxiii said:
Not to discourage you from creating a sweet live-rendered WebGL solution, but it need not be so complicated. If you can export a screenshot of your graph every few degrees from Mathematica you can tie them together with a bit of JavaScript and you're all set.

http://reel360.org/reel

^nice jQuery plugin that I've used in the past

Thanks for that.
But I like the idea of learning WebGL and Three.js and doing something fantastic with them! Just ordered the two reference books on Amazon. It's all very new to me but I did do well in C++ and I see Java is very similar. Don't you guys like programming challenges? This is such a neat one for me I think especially since I'm working with complex functions like the one above.
 
  • Like
Likes Pepper Mint
  • #12
Will look forward to your updates!
 
  • #14
first gl canvas.png
This is a simple WegGL 3D canvas example and I'd like to go over primarily the loading of the data and the 3D placement of the objects. And note even though the display looks 2D, when the HTML file is loaded, it becomes a 3D canvas with the z-axis coming out and going into the screen as discussed below and in the learning exercises cited here.

Refer to https://developer.mozilla.org/en-US/docs/Web/API for information about the OpenGL interface.

The following is a slight modification of Lesson 01 at http://learningwebgl.com/blog/?page_id=1217
I'm running this code locally on my machine so I specify an absolute reference on my pc for the matrix code:
JavaScript:
<script type="text/javascript" src="c:/js/glMatrix.js"></script>
So if you want to experiment with this wegGL example, you'll have to download the glMatrix.js code from the learning site, and edit the src command as necessary before running it.

We wish to establish a WebGLRenderingContext in an HTML <canvas> object. This will give us the OpenGL interface to render objects in the web page using WebGL. We do this with two statements in the webGLStart function (complete source code is below):

JavaScript:
var canvas = document.getElementById("lesson01-canvas");
gl = canvas.getContext("experimental-webgl");
and we set the width and height of the canvas at this time. Once we obtain the gl object, we will use it to call the various canvas methods to set up and display the GL graphics in the canvas. A list of canvas methods can be found at https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext

We implement the following steps in the webGLStart function:

1. initShaders
2. initBuffers
3. configureCanvas
4. drawScene

The shaders basically instruct the graphics card how to shade the scene and is a bit complicated for me right now and I'll just skip for now. The lesson above goes over the shader in detail. What I’d like to discuss now is setting up the data to display the scene and it’s important to note we establish a 3D drawing surface in the webgl context with the z-axis going into the screen for negative values and out of the screen for positive values (we limit the view to 0.1 and -100) . So that when we position objects on the canvas with the code:
JavaScript:
mat4.translate(mvMatrix, [x,y,z])
and position object1 with a z-value of -2 and then position object2 with a z-value of -3, object 1 will be in front of object2 and object2 will appear slightly smaller since this is a projective view.

In this application, I draw a set of lines, a square, and a triangle using the following procedures in the code below:

1. create buffer
2. bind buffer
3. load buffer

And for more informationf about these functions, refer to the learning exercies and WebRenderingContext.bufferData at https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData

We have in the source file:
JavaScript:
// Set up buffer for line
//
        lineVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionBuffer);
        vertices = [
             -1,0,0,
              1,0,0,
              0,1,0,
              0,-1,0
                  ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        lineVertexPositionBuffer.itemSize = 3;
        lineVertexPositionBuffer.numItems =4;

and the format of the buffer will differ according to what object is being loaded. In the case of a line buffer, we store starting and ending points in 3D for a line so in the example above, we have the (x,y,z) coordinates for the x and y axis. The itemSize gives us the length of each item (3), and the numItems gives the length of the buffer. And we use the same basic procedure to load data for additional objects.

Refer to https://developer.mozilla.org/en-US/docs/Web/API for information about the OpenGL interface.

Once we've loaded the object array, we can draw it in the canvas paying particular attention to the z-coordinate. The important function below is the mat4.translate(mvMatrix,[x,y,x]) command as this translates our view before drawing the object. Below, once we've set up a projective display with objects farther away appearing smaller, we initialize the view with the identity matrix which just sets the view at the origin. And note below, in preparation for drawing the cross-hairs of the x-y coordinate system, we translate -10 into the field of view. Had I translated only -5, the crosshairs would appear larger since they would be closer in the view. It's important to note that the translate operations are composite transformations. That is, once we translate back into the screen with [0,0,-10] we remain there until we move the view from that location with anoter translate. For example, if we followed that command with a translate to [-2,0,0], then we would still be -10 into the screen but translated to the left by 2 units. Following that by the translate command [0,0,8] keeps us 2 units to the left but now we have advanced out of the screen by 8 units sothat when we draw the next object, it would be in front of the object drawn before this command and appear larger. And so forth. This way we can efficiently move about the canvas with a simple translate command.

JavaScript:
      gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
        mat4.identity(mvMatrix);
//
// now do line
//
        mat4.translate(mvMatrix, [0,0,-10]);
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, lineVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();

        gl.drawArrays(gl.LINES, 0, lineVertexPositionBuffer.numItems);

and we follow this set of commands with similar command for the square and triangle although the format of the data is slightly different and described in the lesson site. The following is the complete HTML source for this example:
HTML:
<html>

<head>
<title>Learning WebGL &mdash; lesson 1</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">

<script type="text/javascript" src="c:/js/glMatrix.js"></script>

<script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    void main(void) {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;
    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    }
</script><script type="text/javascript">
    var gl;
    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }
    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }
        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }
        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }
        gl.shaderSource(shader, str);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }
        return shader;
    }
    var shaderProgram;
    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");
        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);
        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }
        gl.useProgram(shaderProgram);
        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    }
    var mvMatrix = mat4.create();
    var pMatrix = mat4.create();
    function setMatrixUniforms() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
    }
//
// set up buffers for triangle
//
    var triangleVertexPositionBuffer;
    var squareVertexPositionBuffer;
    var lineVertexPositionBuffer;
    function initBuffers() {
        triangleVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        var vertices = [
             0.0,  1.0,  0.0,
            -1.0, -1.0,  0.0,
             1.0, -1.0,  0.0,
                   ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        triangleVertexPositionBuffer.itemSize = 3;
        triangleVertexPositionBuffer.numItems = 3;
//
// Set up buffer for square using triangles to draw the list
//
        squareVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        vertices = [
             1.0,  1.0,  0.0,
            -1.0,  1.0,  0.0,
             1.0, -1.0,  0,
            -1.0, -1.0,  0.0
                 ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        squareVertexPositionBuffer.itemSize = 3;
        squareVertexPositionBuffer.numItems = 4;
//
// Set up buffer for line
//
        lineVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionBuffer);
        vertices = [
             -1,0,0,
              1,0,0,
              0,1,0,
               0,-1,0
                  ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        lineVertexPositionBuffer.itemSize = 3;
        lineVertexPositionBuffer.numItems =4;
    }
    function drawScene() {
        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
        mat4.identity(mvMatrix);
//
// now draw line
//
        mat4.translate(mvMatrix, [0,0,-10]);
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, lineVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();

        gl.drawArrays(gl.LINES, 0, lineVertexPositionBuffer.numItems);
//
// draw triangle
//
        mat4.translate(mvMatrix, [2, 2,-20]);
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();

        gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
//
// draw square
//
        mat4.translate(mvMatrix, [-4, 0.0, 0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
    }
    function webGLStart() {
        var canvas = document.getElementById("lesson01-canvas");
        initGL(canvas);
        initBuffers();
        initShaders();
               gl.clearColor(0.0, 0.0, 1.0, 1.0);
        gl.enable(gl.DEPTH_TEST);
        drawScene();
    }
</script></head><body onload="webGLStart();">
    <a href="http://learningwebgl.com/blog/?p=28">&lt;&lt; Back to Lesson 1</a><br />
<p> this is some data to print </p>
    <canvas id="lesson01-canvas" style="border: none;" width="500" height="500"></canvas>

    <br/>

<p> more data to print </p>

 
</body>

</html>
 
Last edited:
  • Like
Likes Pepper Mint
  • #15
webglproject2.png

Hi,

I've been reading about OpenGL, shader programs and GLSL. Also got "Programming 3D Applications with HTML5 and WegGL". Of primary concern is to create shader and fragment programs written in the shader language GLSL to be passed to a WegGLContext program which is then linked into a rendering program with runs on the graphics card. Pretty complicated and just starting to learn about it. The User Manual for GLSL is here:

https://www.opengl.org/registry/doc/GLSLangSpec.Full.1.20.8.pdf

And I think one key to successfully implementing WegGL is to learn the shader language and experiment with it. for example, in my second program above, I added points to the canvas at the ends of the axes although they are square. This required a shader variable gl.PointSize added to the vertex shader and I specified a size of 10 but not sure what that refers to. These are some of my notes and I included the source for the canvas above:

The source for the shading programs is the shader-fs and shader-vs scripts in the HTML file. Note in particular the three types of GLSL variables that can be used in the shading programs: Attribute, uniform and varying. During the initialization of the shader programs, we read-in these scripts, compile, link, and bind them to the WebGLProgram. The following Javascript in getShaders() reads the source code from the scripts:
JavaScript:
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }

        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

At the end of this call, the string in str will contain the shader source. We follow these steps to implement the shaders which are implemented in getShader() and initShaders():1. Get shader source from shader-fs and shader-vs scripts
2. Create the fragment and vertex shader programs,
3. Compile and create the shader programs,
4. Create the WebGL program,
5. Attach the vertex and shader programs to the WebGL program,
6. Link the WebGL program,

All of these steps are implemented in the WegGLRenderingContext we set up in the variable gl. For example, to create a shader program, we call shaderProgram=gl.CreateProgram(). Once we initialize the shader programs, then in addition to initializing the vertex buffers for the shapes, we now add color buffers for the shapes. For example, I define the vertices for the triangle with:

var vertices = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
];

And the colors for those verticies:

var colors = [
1.0, 0.0, 0.0, 0.81,
0.0, 1.0, 0.0, 0.81,
0.0, 0.0, 1.0, 0.81
];

This gives us Red, Blue, and Green for the three verticies with opacity of 0.81. We next add the following code to the drawScene function for each shape. For example, we use the following for the triangle.

gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, polyVertexPositionBuffer.numItems);

And we do this for each shape in the scene. Note the use of gl.TRIANGLE. This is one of several drawing methods used by WegGL. The method WebGLRenderingContext.drawArrays() has the following drawing methods:

1. gl.POINTS: Draws a single dot.
2. gl.LINE_STRIP: Draws a straight line to the next vertex.
3. gl.LINE_LOOP: Draws a straight line to the next vertex, and connects the last vertex back to the first.
4. gl.LINES: Draws a line between a pair of vertices.
5. gl.TRIANGLE_STRIP
6. gl.TRIANGLE_FAN
7. gl.TRIANGLES: Draws a triangle for a group of three vertices.

The square figures at the ends of the axes are actually large points I drew with the gl.POINT method. I don't yet know if I can round them off. I drew the axes with the LINES method, the 8-sided octagon I drew with the gl.TRIANGLE_FAN method and the figure in quad 4 I drew with the gl.TRIANGLE_STRIP method and for the half-ellipse in quad 1, I first computed the values for just half of it in Mathematica with Table[{1.5 Cos[t],Sin[t],0] and then just pasted the numbers into the Java Script code so that I could see how the LINE_LOOP would connect the first and last points. I computed the values for the octagon in Mathematica the same way.

Also, I migrated my code to NetBeans and cleaned it up a little. For example, I place all the global variables together at the top of the JavaScript.

Pretty interesting I think. :)
JavaScript:
<html>
   <head>
    <title>Learning WebGL &mdash; lesson 2</title>
    <meta http-equiv="content-type" content="text/html;
              charset=ISO-8859-1">
    <script type="text/javascript" src="../js/glMatrix.js"></script>
</head>

<body onload="webGLStart();">
    <a href="http://learningwebgl.com/blog/?p=134">&lt;&lt;
        Back to Lesson 2</a><br />

    <canvas id="lesson02-canvas" style="border: none;" width="500"
            height="500"></canvas>

    <br/>
    <a href="http://learningwebgl.com/blog/?p=134">&lt;&lt;
        Back to Lesson 2</a><br /><script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;

    varying vec4 vColor;

    void main(void) {
        gl_FragColor = vColor;
    }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;

    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec4 vColor;

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        gl_PointSize=10.0;
        vColor = aVertexColor;
    }
</script><script type="text/javascript" id="mycode">

var gl;                     // Create WebGLRenderingContext
var shader;                 // the vertex or fragment shader program
var shaderProgram;          //WebGL program to run shaders
var mvMatrix = mat4.create();    // model view matrix
var pMatrix = mat4.create();     // projection matrix
var triangleVertexPositionBuffer;
var triangleVertexColorBuffer;
var squareVertexPositionBuffer;
var squareVertexColorBuffer;
var lineVertexPositionBuffer;
var lineVertexColorBuffer;
var polyuVertexPositionBuffer;
var polyVertexColorBuffer;
var stripVertexPositionBuffer;
var stripVertexColorBuffer;
var pointVertexPositionBuffer;
var pointVertexColorBuffer;
var elVertexPositionBuffer;
var elVertexColorBuffer;

    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }

     function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute =
                gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

        shaderProgram.vertexColorAttribute =
                gl.getAttribLocation(shaderProgram, "aVertexColor");
        gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);

        shaderProgram.pMatrixUniform =
                gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform =
                gl.getUniformLocation(shaderProgram, "uMVMatrix");
    }

    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }
        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }
        gl.shaderSource(shader, str);

        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    }
    function setMatrixUniforms() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
    }

    function initBuffers() {

//
// Set up data for the points
//
        pointVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, pointVertexPositionBuffer);
        var vertices = [
            2.5,0,0,
            -2.5,0,0,
            0,2.5,0,
            0,-2.5,0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
        gl.STATIC_DRAW);
        pointVertexPositionBuffer.itemSize = 3;
        pointVertexPositionBuffer.numItems = 4;
//
// now set up the color buffer for points
//
        pointVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, pointVertexColorBuffer);
        var colors=
           [1,0,0,1,
            0,1,0,1,
            0,0,1,1,
            1,1,0,1,
          ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors),
        gl.STATIC_DRAW);
        pointVertexColorBuffer.itemSize = 4;
        pointVertexColorBuffer.numItems = 4;

//
// Set up data for the triangle
//

        triangleVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        var vertices = [
             0.0,  1.0,  0.0,
            -1.0, -1.0,  0.0,
             1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
        gl.STATIC_DRAW);
        triangleVertexPositionBuffer.itemSize = 3;
        triangleVertexPositionBuffer.numItems = 3;
//
// now set up the color buffer for triangle
//

        triangleVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
        var colors = [
            1.0, 0.0, 0.0, 0.81,
            0.0, 1.0, 0.0, 0.81,
            0.0, 0.0, 1.0, 0.81
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors),
        gl.STATIC_DRAW);
        triangleVertexColorBuffer.itemSize = 4;
        triangleVertexColorBuffer.numItems = 3;
//
// set up data for the ellipse
//
        ellVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, ellVertexPositionBuffer);
        vertices = [
            1.5,0.,0.,1.49704,0.0627905,0.,1.48817,0.125333,0.,1.47343,0.187381,0.,1.45287,0.24869,0.,1.42658,0.309017,0.,1.39466,0.368125,0.,1.35724,0.425779,0.,1.31446,0.481754,0.,1.26649,0.535827,0.,1.21353,0.587785,0.,1.15577,0.637424,0.,1.09345,0.684547,0.,1.02682,0.728969,0.,0.956136,0.770513,0.,0.881678,0.809017,0.,0.80374,0.844328,0.,0.722631,0.876307,0.,0.638669,0.904827,0.,0.552187,0.929776,0.,0.463525,0.951057,0.,0.373035,0.968583,0.,0.281072,0.982287,0.,0.188,0.992115,0.,0.0941858,0.998027,0.,0.,1.,0.,-0.0941858,0.998027,0.,-0.188,0.992115,0.,-0.281072,0.982287,0.,-0.373035,0.968583,0.,-0.463525,0.951057,0.,-0.552187,0.929776,0.,-0.638669,0.904827,0.,-0.722631,0.876307,0.,-0.80374,0.844328,0.,-0.881678,0.809017,0.,-0.956136,0.770513,0.,-1.02682,0.728969,0.,-1.09345,0.684547,0.,-1.15577,0.637424,0.,-1.21353,0.587785,0.,-1.26649,0.535827,0.,-1.31446,0.481754,0.,-1.35724,0.425779,0.,-1.39466,0.368125,0.,-1.42658,0.309017,0.,-1.45287,0.24869,0.,-1.47343,0.187381,0.,-1.48817,0.125333,0.,-1.49704,0.0627905,0.,-1.5,0.,0.
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
            gl.STATIC_DRAW);
        ellVertexPositionBuffer.itemSize = 3;
        ellVertexPositionBuffer.numItems = 51;
//
// set up color data for square
//
        ellVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, ellVertexColorBuffer);
        colors = [];
        for (var i=0; i < 101; i++) {
            colors = colors.concat([0.5, 0.5, 1.0, 1]);
        }
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors),
            gl.STATIC_DRAW);
        ellVertexColorBuffer.itemSize = 4;
        ellVertexColorBuffer.numItems = 51;
//
// Set up buffer for line
//
        lineVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionBuffer);

        vertices = [
             -1,0,0,
              1,0,0,
              0,1,0,
               0,-1,0
                  ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        lineVertexPositionBuffer.itemSize = 3;
        lineVertexPositionBuffer.numItems =4;
//
// Set up color for the lines
//
        lineVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexColorBuffer);
        var colors = [
            1.0, 0.0, 0.0, 0.81,
            0.0, 1.0, 0.0, 0.81,
            0.0, 0.0, 1.0, 0.81,
            0.5,0.5,0.5,.81
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors),
        gl.STATIC_DRAW);
        lineVertexColorBuffer.itemSize = 4;
        lineVertexColorBuffer.numItems = 4;
//
// set up for polygon
//
        polyVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, polyVertexPositionBuffer);
        vertices = [0, 0, 0, 1., 0., 0., 0.707107, 0.707107, 0., 0., 1., 0., -0.707107,
0.707107, 0., -1., 0., 0., -0.707107, -0.707107, 0., 0., -1., 0.,
0.707107, -0.707107, 0., 1., 0., 0.  
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
            gl.STATIC_DRAW);
        polyVertexPositionBuffer.itemSize = 3;
        polyVertexPositionBuffer.numItems = 10;
//
// set up color data for polygon
//
        polyVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, polyVertexColorBuffer);
        colors = [];
        for (var i=0; i < 10; i++) {
            colors = colors.concat([0.5, 0.5, 1.0, 1]);
        }
        colors[0]=1;
        colors[1]=0;
        colors[2]=0;
        colors[3]=1;
   
         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors),
            gl.STATIC_DRAW);
        polyVertexColorBuffer.itemSize = 4;
        polyVertexColorBuffer.numItems = 10;
//
// Set up data for the triangle strip
//

        stripVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, stripVertexPositionBuffer);
        var vertices = [
             0.0,  0.0,  0.0,
            0,1,0,
            1,0,0,
            2,1,0,
            3.5,-1,0,
            1,-3,0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
        gl.STATIC_DRAW);
        stripVertexPositionBuffer.itemSize = 3;
        stripVertexPositionBuffer.numItems = 6;
//
// now set up the color buffer for strip
//

        stripVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, stripVertexColorBuffer);
        var colors = [
            1.0, 0.0, 0.0, 0.81,
            0.0, 1.0, 0.0, 0.81,
            0.0, 0.0, 1.0, 0.81,
           .5,.5,.5,1,
           1,.5,.7,1,
                   .2,.5,.8,.7
                    
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors),
        gl.STATIC_DRAW);
        stripVertexColorBuffer.itemSize = 4;
        stripVertexColorBuffer.numItems = 6;
    }
    function drawScene() {
        gl.viewport(0,0,gl.viewportWidth, gl.viewportHeight);

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);        mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

        mat4.identity(mvMatrix);
//
// draw points
//
         mat4.translate(mvMatrix, [0,0,-7]);
        gl.bindBuffer(gl.ARRAY_BUFFER, pointVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
        pointVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, pointVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
        pointVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

        setMatrixUniforms();

        gl.drawArrays(gl.POINTS, 0, pointVertexPositionBuffer.numItems);
 
//
// draw line
//
        mat4.translate(mvMatrix, [0,0,3]);
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
            lineVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
            lineVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

        setMatrixUniforms();

        gl.drawArrays(gl.LINES, 0, lineVertexPositionBuffer.numItems);
//
// draw triangle
//
        mat4.translate(mvMatrix, [-1.5, 2.0, -7.0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
            triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
        triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
//
// draw ellipse
//
        mat4.translate(mvMatrix, [3.5, 0.0, 0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, ellVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
    ellVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, ellVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
    ellVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

        setMatrixUniforms();
        gl.drawArrays(gl.LINE_LOOP, 0, ellVertexPositionBuffer.numItems);
//
// draw octagon
//
        mat4.translate(mvMatrix, [0, -3.5, 0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, polyVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
    polyVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, polyVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
    polyVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

         setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLE_FAN, 0, polyVertexPositionBuffer.numItems);

              

//
// draw triangle strip
//
        mat4.translate(mvMatrix, [-6, 0, -5]);
        gl.bindBuffer(gl.ARRAY_BUFFER, stripVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
    stripVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, stripVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
    stripVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);

        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, stripVertexPositionBuffer.numItems);    }
    function webGLStart() {
        var canvas = document.getElementById("lesson02-canvas");
        initGL(canvas);
        initShaders();
        initBuffers();

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.enable(gl.DEPTH_TEST);

        drawScene();
    }

</script>
</body>
</html>
 
Last edited:
  • Like
Likes DrZoidberg and Pepper Mint
  • #16
fourthrotationproject.png


This is oh so cool! Here is the working version of it. I am really amazed at the blinding speed of hardware (GPU) acceleration!

https://dl.dropboxusercontent.com/s/xvkksd4vr4hbh6j/index.html?dl=0

Some notes on migrating 3D data from Mathematica to HTML5/Javascript:

In Mathematica, I created the three branch plots above by computing traces along their branch surfaces in radial paths from 0.1 to 0.9 to create 1500 vertex points for each plot and then formed triangular polygons between each trace. The gl.drawElements(gl.TRIANGLES,…) method requires a 1D array of the vertices along with an index buffer identifying which vertices belong to each triangle. Data in Mathematica is delimited by curly braces so I had to switch these out for the javascript square brackets and then save both the vertex data and index data to a javascript-like file. For example, formed the "var branch2DataPoints" discriptor with the following Mathtmatica code (the 3D vertex data is stored in theTraceData):

functionVertexBuffer=Flatten[theTraceData];
myVertexData=ToString[functionVertexBuffer];
ilen=StringLength[myVertexData]
theDataPoints="var branch2DataPoints=["<>StringTake[myVertexData,{2,ilen-1}]<>"];";

When this is done, I now have a Java descriptor "var branch2DataPoints=[v1,v2,v3,...vn];"

I then do the same with the index buffer and save each branch data to "branchnData.js" in my netBeans project and then include a script;

<script text="text/javascript" scr="branch1Data.js"> </script>

for each branch in my HTML file.

In this project, I simply chose random colors for each vertex and then established a static animation to rotate each branch at a constant rate. It’s a simple matter to call tick() in the WebGLStart() function. Tick() then calls the animate() function which then controls the static rotation of the three function branches at constant rates. The animation code is explained in lesson three of the learning exercises: http://learningwebgl.com/blog/?p=239
 
Last edited by a moderator:
  • Like
Likes DrZoidberg and Pepper Mint
  • #17
If the data points have come from a formula (and not from a model), and you are feeling brave, you could plot the entire thing directly in the shader and have a completely smooth surface...

https://www.shadertoy.com/view/XslSWl

In this case your Vert Shader is basically just plotting a flat 2d rectangle, and it is in the Fragment Shader, per pixel, that you calculate the colors - based on whether a ray at that pixel collides with your 3d surface.

It has the benefit that the formula can be modified in real time with no limit on data points, and no need to continually pass arrays and buffers to the GPU.
But trickier mathematically.
 
  • #18
RockWieldingApe said:
If the data points have come from a formula (and not from a model), and you are feeling brave, you could plot the entire thing directly in the shader and have a completely smooth surface...

https://www.shadertoy.com/view/XslSWl

In this case your Vert Shader is basically just plotting a flat 2d rectangle, and it is in the Fragment Shader, per pixel, that you calculate the colors - based on whether a ray at that pixel collides with your 3d surface.

It has the benefit that the formula can be modified in real time with no limit on data points, and no need to continually pass arrays and buffers to the GPU.
But trickier mathematically.

Thanks. Sounds like that may be an interesting project. Although in my case data is not coming from a formula but rather numerical solutions to messy differential equations.

I've been studying the interactions between shaders and javascript. Very complicated I think but I'm getting it slowly. Working on coding the shaders for directional and ambient lighting now.
 
  • #19
Hey guys. I finally got one to interactively rotate. Added some diffuse and directional lighting but not sure it's working properly. Code is really messy though.

mouseversion.png


<Moderator's note: old link deleted. See next post for correct link.>
 
Last edited by a moderator:
  • Like
Likes nolxiii
  • #20
Hi guys,

I think I have it in the form I intend to publish on my blog. You guys have been great helping me get the code to work. Here's is the the version I like although the data for both real and imaginary parts is up to 4.5 Mb and takes a bit to load on slow machines. Would appreciate any suggestions for improvements:

https://dl.dropboxusercontent.com/s/dhdvgj0astxrah6/index.html?dl=0
 
Last edited by a moderator:
  • #21
mousewheel zoom no worky in firefox :(

(learningwebgl.com example mouse zoom no work either)
 
  • #22
nolxiii said:
mousewheel zoom no worky in firefox :(

(learningwebgl.com example mouse zoom no work either)

Thanks. Really didn't understand the mouse wheel routine I copied from the learning exercises but it did include a comment about Firefox: Could you explain to me what the line:

var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel";

means?

JavaScript:
 function setMouseWheelEvent(){
        var mousewheelevt=(/Firefox/i.test(navigator.userAgent))?
        "DOMMouseScroll" : "mousewheel"; //FF doesn't recognize mousewheel as of FF3.x
        if (canvas.attachEvent) //if IE (and Opera depending on user setting)
          canvas.attachEvent("on"+mousewheelevt, zoomimage);
        else if (canvas.addEventListener) //WC3 browsers
          canvas.addEventListener(mousewheelevt, zoomimage, false);
    }
 
  • #23
As far as I can tell that part is working, maybe in the zoomimage function? I dunno. At least the same code fires the event properly when I run it in console.

This line is setting the name of the event to listen for to detect mousewheel scrolling. Sometimes event names differ between browsers.

/Firefox/i is a regex to look for case insensitive "Firefox" inside your user agent string. If the regex test returns true it sets mousewheelevt to "DOMMouseScroll", which is what Firefox calls the mouse scroll event, and to "mousewheel" if it returns false, which is I guess what other browsers call it.
 
  • #24
Hi nolxill

Thanks for letting me know about this. I downloaded Firefox and confirmed the mouse wheel isn't working. Then I found this site about mouse wheel events in HTML5:
https://www.sitepoint.com/html5-javascript-mouse-wheel/

I then modified the code for my canvas application below and checked that it worked in Chrome, IE, Edge, and Firefox. Nice to have robust code! :)

JavaScript:
 function setMouseWheelEventTwo(){
     
       if (canvas.addEventListener) {
    // IE9, Chrome, Safari, Opera
    canvas.addEventListener("mousewheel", MouseWheelHandler, false);
    // Firefox
    canvas.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
        }
        // IE 6/7/8
        else canvas.attachEvent("onmousewheel", MouseWheelHandler);
    };
   
 function MouseWheelHandler(e) {
    // cross-browser wheel delta
    var e = window.event || e; // old IE support
    var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
        if(delta<0)
        {
            myZval-=0.2;
        }
        else if(delta>0)
        {
            myZval+=0.2;
        }
    }
 
Last edited:

Related to Setting up 3-D rotation in web page

1. What is 3-D rotation and why is it useful in web design?

3-D rotation is a technique that allows for the manipulation of an object in three-dimensional space on a web page. This can add depth and visual interest to a design, making it more engaging and interactive for users.

2. How do I set up 3-D rotation on my web page?

To set up 3-D rotation on a web page, you will need to use CSS properties such as transform and perspective. These properties allow you to rotate and position elements in 3-D space, creating the illusion of depth and movement.

3. Can I use 3-D rotation on any element on my web page?

Yes, you can use 3-D rotation on any element on your web page, including images, text, and buttons. However, the element must have a flat surface in order for the rotation to be visible.

4. How can I control the speed and direction of the 3-D rotation?

You can control the speed and direction of the 3-D rotation by adjusting the CSS transition and transform properties. These properties allow you to specify the duration, timing function, and rotation angles for the animation.

5. Is 3-D rotation supported by all web browsers?

No, support for 3-D rotation varies among web browsers. It is supported by most modern browsers, including Chrome, Firefox, Safari, and Edge. However, it may not be supported by older browsers or some mobile browsers.

Similar threads

Replies
5
Views
1K
  • Programming and Computer Science
2
Replies
50
Views
4K
  • Programming and Computer Science
2
Replies
39
Views
4K
  • Programming and Computer Science
Replies
7
Views
2K
  • Classical Physics
Replies
6
Views
658
  • Programming and Computer Science
Replies
13
Views
1K
  • Programming and Computer Science
Replies
11
Views
1K
  • Programming and Computer Science
Replies
1
Views
1K
  • Programming and Computer Science
Replies
2
Views
2K
  • Programming and Computer Science
Replies
4
Views
3K
Back
Top