Voxel and Vertex Terrain generation

This was way harder than I expected it to be!

I this project turns @dave1707’s voxel terrain into a Craft model using the same points.

It also uses voxel volumes to generate the blocks. Volumes render way faster than the direct voxels used in the original, and because of that the project can do neat things like spinning parts of the terrain around separately.

To make the Craft model a little more interesting, and make the contours more visible in the absence of contour shadows, I applied some random color variation and random and positional variation to each vertex, which results in the bumpy and blotchy terrain you can see in the video.

With the default project settings you can also see both the voxels and the Craft model rendered right on top of each other, which makes it look kind of like a Minecraft world with little weeds and dirt clumps and patches of snow pilling up around the blocks. (I found that a surprisingly pleasing effect, actually.)

Give it a whirl!


@UberGoober - superb demo, now I know how to transfer some of my meshes to Craft. Thanks.

Will take some time but will post code if I manage to transfer.

@Bri_G If you want to convert meshes to Craft, I think there are better projects to look at, including the code @binaryblues wrote to do that exact thing in the ProcTree project.

That specific code, however, might be a little hard to extract from the JavaScript-related code.

Binaryblues, any chance you want to take that code and convert it into a standalone mesh-conversion project—it doesn’t seem like it should be too hard, given that you’ve done the hard work already?

You’re doing some nifty work, @UberGoober , keep it up!

@RonJeffries thanks Ron!

@UberGoober There is nothing particularly noteworthy about the conversion program. In fact, there is a one-to-one correspondence between the mesh data generated by JS in that program and the data from the craft model. Because the js has a faces property, it corresponds to the craft model’s indices property. All the conversion takes place in the 2 functions?

function convert(verts, normals, UV)
    local v,n,uv = {},{},{}
    for i=1,#verts do
        local x,y,z =verts[i][1], verts[i][2], verts[i][3]
        local n1,n2,n3 = normals[i][1], normals[i][2], normals[i][3]
        local u1,v1 = UV[i][1], UV[i][2]      
        table.insert(v, vec3(x,y,z))
        table.insert(n, vec3(n1,n2,n3))
        table.insert(uv, vec2(u1,v1))
    return v,n,uv

function convertFaces(faces)
    local f = {}
    -- ? face ????js???? +1??????1???
    -- ?? js ? faces ?????faces[0],faces[1],...,faces[#faces-1]
    -- ??? lua ??1?????
    if useJSSavedData then k =1 else k=0 end
    for i=1,#faces do
        local v1,v2,v3 = faces[i][1]+k, faces[i][2]+k, faces[i][3]+k
        table.insert(f, v3)
        table.insert(f, v2)
        table.insert(f, v1)
    return f

…ah, but GLSL meshes don’t automatically have a faces property, is that right?

I also wrote a function that converts the craft model to mesh data, which you can use if you want

LoadObject = class()

function LoadObject:init(objPath,id)

    self.e = scene:entity()
    -- self.e.position = vec3(0,0,0)
    local model = craft.model(objPath)
    -- self.e.material = craft.material(asset.builtin.Materials.Standard)
    -- self.e.material.map = readImage("Dropbox:face1")
    self.e.active = true
    self.id = id
    self.m = self:model2mesh(self.e.model, self.id)

    self.m.texture = readImage(asset.builtin.Blocks.Ice)
    ---[[ ??OpenGLES3.0 ???9_1 ? shader
    self.m.shader = shader(s.v3,s.f3)
    -- m.shader = shader(s.v2,s.f2)
    self.m.shader.modelMatrix = matrix()
    self.m.shader.viewMatrix = matrix()
    self.m.shader.projectionMatrix = matrix()    
    -- self.m.shader.modelViewProjection = matrix()    
    -- m.shader.modelMatrix=m.shader.modelMatrix:rotate(50, 	0,	1, 	0)
    self.m.shader.uCamera = vec3(0,5,1)
    self.m.shader.uLightLocation = vec3(100,103,103)
    self.m.shader.sTexture = self.m.texture    

function LoadObject:model2mesh(model,id)
    local m = mesh()
    local indices = model.indices    
    local vb = m:buffer("position")
    local tb = m:buffer("texCoord")
    local nb = m:buffer("normal")
    local cb = m:buffer("color")
    local vt,tt,nt,ct = {{}},{{}},{{}},{{}}

    -- ???? id ??????????    
    local str = readText(asset.."Model"..id..".txt")
    -- ????????????????????
    if str == "" then       
        for k=1,#indices do
            vb[k] = model.positions[model.indices[k]]
            tb[k] = model.uvs[model.indices[k]]
            nb[k] = model.normals[model.indices[k]]
            cb[k] = model.colors[model.indices[k]]
            -- ?vec3?vec2 ??????????????????
            vt[k] = {vb[k].x, vb[k].y, vb[k].z}
            tt[k] = {tb[k].x, tb[k].y}
            nt[k] = {nb[k].x, nb[k].y, nb[k].z}
            ct[k] = {cb[k].x, cb[k].y, cb[k].z}
        local modelString = json.encode({vt,tt,nt,ct})
        saveText(asset.."Model"..id..".txt", modelString)
        local t = json.decode(str)   
        vt,tt,nt,ct = t[1], t[2], t[3], t[4]              
        -- print("t[3][2], t[1][1][1]", #t[3][2], t[1][1][1])
        -- ????????????????
        for k = 1,#vt do
            vb[k] = vec3(vt[k][1], vt[k][2], vt[k][3])
            tb[k] = vec2(tt[k][1], tt[k][2])
            nb[k] = vec3(nt[k][1], nt[k][2], nt[k][3])
            cb[k] = vec3(ct[k][1], ct[k][2], ct[k][3])
    print("indices: ", #indices, type(id), id)
    return m

function LoadObject:update(dt)


function LoadObject:drawSelf()    
    -- ?? mesh ??

s = {
v3 =[[#version 300 es
uniform mat4 modelViewProjection; //?????
uniform mat4 modelMatrix; //????
uniform mat4 viewMatrix; //????
uniform mat4 projectionMatrix; //????
uniform vec3 uLightLocation;	//????
uniform vec3 uCamera;	//?????

in vec3 position;  //????
in vec3 normal;    //?????
in vec2 texCoord;    //??????

out vec4 ambient;
out vec4 diffuse;
out vec4 specular;
out vec2 vTextureCoord; 
void pointLight(					//??????????
  in vec3 iNormal,				//???
  inout vec4 ambient,			//???????
  inout vec4 diffuse,				//???????
  inout vec4 specular,			//???????
  in vec3 lightLocation,			//????
  in vec4 lightAmbient,			//?????
  in vec4 lightDiffuse,			//?????
  in vec4 lightSpecular			//?????
  ambient=lightAmbient;			//????????????  
  vec3 normalTarget=position+iNormal;	//?????????
  vec3 newNormal=(modelMatrix*vec4(normalTarget,1)).xyz-(modelMatrix*vec4(position,1)).xyz;
  newNormal=normalize(newNormal); 	//???????
  vec3 eye= normalize(uCamera-(modelMatrix*vec4(position,1)).xyz);  
  vec3 vp= normalize(lightLocation-(modelMatrix*vec4(position,1)).xyz);  
  vec3 halfVector=normalize(vp+eye);	//??????????    
  float shininess=30.0;			//?????????
  float nDotViewPosition=max(0.0,dot(newNormal,vp)); 	//?????vp????0????

  diffuse = lightDiffuse*nDotViewPosition;				//??????????
  float nDotViewHalfVector=dot(newNormal,halfVector);	//????????? 
  float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); 	//?????????
  specular=lightSpecular*powerFactor;    			//??????????

void main()     
// mat4 modelViewProject = projectionMatrix*(viewMatrix* modelMatrix);
    gl_Position = modelViewProjection * vec4(position,1); //??????????????????  

   // ????????????????????      
   vec4 ambientTemp, diffuseTemp, specularTemp;   
   vTextureCoord = texCoord;//????????????????

f3 = [[#version 300 es 
precision mediump float;
uniform sampler2D sTexture;//??????
in vec4 ambient;
in vec4 diffuse;
in vec4 specular;
in vec2 vTextureCoord;
out vec4 fragColor;

void main()                         
   vec4 finalColor=texture(sTexture, vTextureCoord);    
   fragColor = finalColor*ambient+finalColor*specular+finalColor*diffuse;


Project now available on WebRepo.

In fact, the complete OpenGL ES has a vertex index array, but Codea’s mesh is streamlined to use the order of the vertex array as a vertex index instead of using a separate vertex index.
So, if we use Codea’s mesh to construct the craft model, we have to manually construct the corresponding vertex index (generally, a set of three vertices) in real time according to the permutation order of each vertex, the specific code depends on the vertex construction algorithm, it is difficult to deal with this part of the independent abstract, because the order of its input vertices is not fixed, there is no regularity.