xeolabs / scenejs

An extensible WebGL-based 3D engine. This is an archived project.
https://xeolabs.github.io/scenejs/
Other
649 stars 165 forks source link

Adding points to scene only after image is loaded #217

Closed aqiruse closed 8 years ago

aqiruse commented 11 years ago

I am testing the custom node, and trying to use an image as heightmap and generate the points for a terrain node and then add it to the scene. I am able to load the image, generate the points, however, there is no method for me to load the points into a node only AFTER the image has been loaded and points generated. If the code to addNode is called from the html file from within a getNode function, the data is null for the terrain because it has not been loaded yet. If the terrain data is generated from within an onload function for the image, then the image onload has no method of setting the point data for a scene node. Any suggestions on how to achieve this?

xeolabs commented 11 years ago

You could try using a data source plugin for the geometry node - this enables you to create the scene graph containing the geometry node, then defer the population of that geometry to when the data is actually available.

The idea is that you create a geometry node wired to get its data from a symbolic "source" (ie. a factory), then via the plugin, post your data to that source and the geometry will collect it before the next scene render.

Two patterns of usage:

  1. A source that autonomously regenerates data, to periodically refresh the geometry. This is what would happen if updated geometry was being asynchronously pushed to a client scene from a server:

http://scenejs.org/examples.html?page=geometryPluginPushStream

  1. A source that regenerates data on request by the geometry (as you reconfigure the source params on the geometry, the geometry reconfigures the source, which responds with refresh data for the geometry). This is what would happen if say, you used a source to create some primitive, then reconfigured the primitive through the geometry node, eg. to add vertices or whatever:

http://scenejs.org/examples.html?page=geometryPluginPullStream

These descriptions are maybe not that intuitive - check out those examples and hopefully it makes sense.

xeolabs commented 11 years ago

PS What about just doing this?

SceneJS.Types.addType("foo/bar", {

    init:function (params) {

        var self = this;

        loadMyImage("foo.jpg",
            function (image) {

                self.addNode({
                    type: "geometry",
                    positions: [ /* data from image */]
                });
            });
    }
});
xeolabs commented 11 years ago

PS. If you come up with a way to automatically compute the normals for a mesh, I'd be really interesting in getting that into the framework somewhere, perhaps within the grid plugin: https://github.com/xeolabs/scenejs/blob/V3.0/src/plugins/node/ground/grid.js

aqiruse commented 11 years ago

I have uploaded what I am trying to my fork at https://github.com/aqiruse/scenejs

The actual code I am working on is located in two files:

https://github.com/aqiruse/scenejs/blob/V3.0/examples/ex/customNodes/bundled/skyHeightmapAndTeapot.html

and https://github.com/aqiruse/scenejs/blob/V3.0/api/latest/plugins/node/heightmap/heightmap.js

I am trying the addNode technique during loadImage now, however that is not working. When you get a chance, do you think you could look at these files? Maybe you will have a better idea of what I am doing wrong.

xeolabs commented 11 years ago

I had a play around and got a heightmap rendering, you may need to change a path or two, but these work for me:

The heightmap plugin: https://gist.github.com/xeolabs/5855386

The HTML page: https://gist.github.com/xeolabs/5855394

aqiruse commented 11 years ago

Thank you for the work. It does work for me.Though I am not quite sure why a flat area and a height area are both being created when only one image is being loaded. Any ideas on that? I am now in the process of modifying this to create a terrain. When I did the points, I guess I was not sure what to expect and it does indeed create points. However they are too spread for a terrain. I am researching point to triangle algorithms for heightmaps now and it will probably take some time before I have another implementation with the desired result. I did find one, and when I attempted to adapt from opengl to webgl, I receive a giant block, lol. Will need some time for more analysis and work. Thank you for the help.

xeolabs commented 11 years ago

Not sure why there is the flat and height area

For triangulation, you could start with a plane geometry which already has the triangles (as in the 'plane' plugin, linked below), then sample the heightmap image for each vertex position, to obtain the height for that vertex.

https://github.com/xeolabs/scenejs/blob/V3.0/api/latest/plugins/geometry/plane.js

If the flat area problem is related to vertex index calculations, then this technique should fix that as well.

Your next problem would be determining the normals on the mesh..

aqiruse commented 11 years ago

I have a basic working example for the noisy image. However, the terrain does not display properly. In one direction the terrain is the correct size, however in the other dimension it appears half of what it should be, or else vice versa.

The terrain sample I have is at:

https://github.com/aqiruse/scenejs/blob/V3.0/examples/ex/customNodes/bundled/skyHeightmap.html

The heightmap.js file is at the previous link I gave for it and have updated it.

I have a new difficulty though, when I load an image that is a simple black and white image, that should react the same way as the noisy terrain image, it does not. In fact, the black and white image creates something which looks nothing like the actual image. I have tried with both an image and negative of that image with the same results. I will work on this later and try to diagnose the problem. It is getting there though. I have also updated the heightmap code to actually use the texture map. Though current I am drawing lines instead of a triangle-fan (which I would use others since it displays 'more' correctly) in order to see what is going on. The heightmap black and white image is located in the images directory on my github.

aqiruse commented 11 years ago

I have used a diamond-square algorithm in JavaScript from Sann-Remy Chea and adapted it to fit. I have two versions in the heightmap.js file. One which generates a totally random terrain, and another which generates the terrain using the heightmap image file for its data points then applies the diamond-square algorithm to it in order to generate the final terrain. His terrain generator was originally written for Three.js and I adapted it for SceneJS. I have added two sample files. One which shows the terrain from an image and another that shows the terrain from a random generation. I also added in Stats.js so that the frames per second of the drawing may be seen.

The files are located at:

https://github.com/aqiruse/scenejs/blob/V3.0/examples/ex/customNodes/bundled/skyHeightmap.html

https://github.com/aqiruse/scenejs/blob/V3.0/examples/ex/customNodes/bundled/skyHeightmapRandom.html

https://github.com/aqiruse/scenejs/blob/V3.0/api/latest/plugins/node/heightmap/heightmap.js

I am working on another version for drawing a building from a heightmap though I am having some trouble with it drawing the walls (which can be in either direction on the image) and generating the building from this image. I have thought today about modifying your box or quad code and adapting it to fit the needs of this type of generation. Unfortunately, the current heightmap image terrain generator does work for such an image, it looks horrible when it attempts to draw it, though the current heightmap image system looks great for a noisy image though I am curious why certain anomalies exist like a hole in the center of the map instead of a mountain peak? Regardless, it does work and I am working on a new custom plugin, I am calling building_heightmap which will specifically handle a black and white image with straight lines primarily and draw a 3D terrain based upon it.

If you have any suggestions or would like to incorporate the heightmap with the diamond-square algorithm based code, feel free.

xeolabs commented 11 years ago

I just tried these out, nice work!

I'll incorporate it into the plugins set at some point soon. I'll probably just give the var names some tweaking, just for style consistency.

What we really need now though is computation of the normals (unless I missed something in the code)..

I wonder if the problems you're having are something to do with the max vertex array size allowed on WebGL?

aqiruse commented 11 years ago

I figured out a basic technique for the height-map for a building layout. It works and is available on:

https://github.com/aqiruse/scenejs/blob/V3.0/examples/ex/customNodes/bundled/skyBuildingHeightmap.html

with the heightmap plugin at: https://github.com/aqiruse/scenejs/blob/V3.0/api/latest/plugins/node/building_heightmap/building_heightmap.js

However, I encountered a serious issue if a person tries to texture each block node individually with a ton of block nodes, there gets what chrome calls a "snag". I also found another error a couple times that lead back to gl calls like gl.uniform3fv and not behaving properly. All these were to do with the texturing though.

The "Snag" version is at:

https://github.com/aqiruse/scenejs/blob/V3.0/examples/ex/customNodes/bundled/skyBuildingHeightmapWebGLSnag.html

and https://github.com/aqiruse/scenejs/blob/V3.0/api/latest/plugins/node/building_heightmap_webgl_snag/building_heightmap_webgl_snag.js

I updated this working version so that it will take a gray-scale image and draw it, but two notes are that 1) it adds a plane as a floor and everything below threshold is cutoff and marked as floor, and 2) it uses a cube as the basis of the drawing, which means that each pixel in the map, becomes another cube in the building. This can cause problems due to a large map size when drawing, but overall, the frame-rate is not that bad. Need to play around more with like a "moving user" POV camera to determine if a large map will be a problem or not. Should be some way to only draw visible portions of it if required. Anyways, it does work. I do not like how jpeg results in uneven walls due to slight variations in the pixels stored in the image. I may add a feature to toggle between exact heights and 1.0 / 0.0 heights in the future. If you would like to check out either the working or non-working versions, feel free.

PS. I have some readings on automatically being able to calculate the normals, though I have not had the chance to go in depth to the readings or implement anything yet. On my todo list though.

aqiruse commented 11 years ago

The following code presents two versions of Calculating surface normals, the first version is for a triangle and the second version is for a polygon. Not sure if this is what you wanted, but here is it. To calculate for a single vertex, you would calculate the triangle normal and then average them for the individual vertex normal. This is code I translated from the psuedo-code at: http://www.opengl.org/wiki/Calculating_a_Surface_Normal

btw, I have not tested this yet. Just finished translating them. Need to find some known values for testing. Regardless, here they are and should work with little changes. I also have two versions of each function, one for a vector with x, y, z, and another for [0], [1], [2] values.

code:

    function Normalize(vector) 
    {
        var u = vector;
        var v = vector;
        var veclen = Math.sqrt(u.x * v.x + u.y * v.y + u.z * v.z);
        var s = 1.0 / veclen;
        var dest = [];

        dest.x = v.x * s;
        dest.y = v.y * s;
        dest.z = v.z * s;

        return dest;     
    }
    function NormalizeNonX(vector) {
        var u = vector;
        var v = vector;
        var veclen = Math.sqrt(u[0] * v[0] + u[1] * v[1] + u[2] * v[2]);
        var s = 1.0 / veclen;
        var dest = [];

        dest[0] = v[0] * s;
        dest[1] = v[1] * s;
        dest[2] = v[2] * s;

        return dest;     
    }
    function CalculateSurfaceNormal(TriangleVertexP1, TriangleVertexP2, TriangleVertexP3)
    {
        var U = [(TriangleVertexP2.x - TriangleVertexP1,x),(TriangleVertexP2.y - TriangleVertexP1.y),(TriangleVertexP2.z - TriangleVertexP1.z)];
        var V = [(TriangleVertexP3.x - TriangleVertexP1.x),(TriangleVertexP3.y - TriangleVertexP1.y),(TriangleVertexP3.z - TriangleVertexP1.z)];

        var Normal = [];
        Normal.x = (U.y * V.z) - (U.z * V.y);
        Normal.y = (U.z * V.x) - (U.x * V.z);
        Normal.z = (U.x * V.y) - (U.y * V.x);

        return Normal;
    }
    function CalculateSurfaceNormalPolygon(Polygon)
    {
       var Normal = [0, 0, 0];

       var Current = [];
       var Next = [];
       for (Index=0; Index < Polygon.length; Index++)
       {
          Current = Polygon.verts[Index];
          Next    = Polygon.verts[(Index + 1) % Polygon.length];

          Normal.x = Normal.x + ((Current.y + Next.y) * (Current.z + Next.z));
          Normal.y = Normal.y + ((Current.z + Next.z) * (Current.x + Next.x));
          Normal.z = Normal.z + ((Current.x + Next.x) * (Current.y + Next.y));

       }
       return Normalize(Normal);
    }
    function CalculateSurfaceNormalNonX(TriangleVertexP1, TriangleVertexP2, TriangleVertexP3)
    {
        var U = [(TriangleVertexP2[0] - TriangleVertexP1[0]),(TriangleVertexP2[1] - TriangleVertexP1[1]),(TriangleVertexP2[2] - TriangleVertexP1[2])];
        var V = [(TriangleVertexP3[0] - TriangleVertexP1[0]),(TriangleVertexP3[1] - TriangleVertexP1[1]),(TriangleVertexP3[2] - TriangleVertexP1[2])];

        var Normal = [];
        Normal[0] = (U[1] * V[2]) - (U[2] * V[1]);
        Normal[1] = (U[2] * V[0]) - (U[0] * V[2]);
        Normal[2] = (U[0] * V[1]) - (U[1] * V[0]);

        return Normal;
    }
    function CalculateSurfaceNormalPolygonNonX(Polygon)
    {
       var Normal = [0, 0, 0];

       var Current = [];
       var Next = [];
       for (Index=0; Index < Polygon.length; Index++)
       {
          Current = Polygon.verts[Index];
          Next    = Polygon.verts[(Index + 1) % Polygon.length];

          Normal[0] = Normal[0] + ((Current[1] + Next[1]) * (Current[2] + Next[2]));
          Normal[1] = Normal[1] + ((Current[2] + Next[2]) * (Current[0] + Next[0]));
          Normal[2] = Normal[2] + ((Current[0] + Next[0]) * (Current[1] + Next[1]));

       }
       return NormalizeNonX(Normal);
    }
xeolabs commented 11 years ago

Sorry, been snowed under with work - I just realised that there is also a method for computing surface normals in the teapot plugin:

https://github.com/xeolabs/scenejs/blob/V3.0/src/plugins/geometry/teapot.js#L5776

I'm thinking of using one of these techniques to the geometry node, so it can automatically calculate normals, maybe to use like this:

    node.addNode({
          type: "geometry",
          positions: ...,
          indices: ....,
          autoNormals: true
    });

I'll play around with it tomorrow.

xeolabs commented 11 years ago

I've built auto vertex normal calculation into the geometry node -

Live example: http://scenejs.org/examples.html?page=autoNormals

Gist showing the API usage: https://gist.github.com/xeolabs/6142368#file-scenejs-auto-normals-js