fenomas / noa

Experimental voxel game engine.
MIT License
612 stars 87 forks source link

Stairs #5

Closed andrewtheone closed 7 years ago

andrewtheone commented 8 years ago

What would be the best approach to implement stairs? I mean, can it be done only by textures? 1 can imagine the top,back,and right,left sides' textures (transparent) but I cannot figure out how should I paint the front face?

Other option would be to handle all stairs/slabs as "object", and not terrain but then it'd be a pain in the ass to -lets say put a block above a stair-,or I had to rewrite the hole raycasting-block selection part, to include objects to, or to have a fully transparent block where the stair would go, and if the block is deleted then the stair is, it might be possible, due to my latest contribution (block data).

andrewtheone commented 8 years ago

I'm trying my 2nd/3nd idea, which is placing a full transparent block, and set block data {type: "stairs"} and listen on "setBlock", and if the setted block has block data, and it has a key type which is stairs, I add a custom mesh to the scene, exactly where the placed block is. My mesh is a "parent" mesh with 2 sub meshes, and they build up a stair but It just does not render correctly, the "holder" is always solid 1_1_1 cube, and the children never been rendered. Any idea?


var CustomObject = function(noa, blockManager) {

    this.noa = noa;
    this.blockManager = blockManager;

    this.objects = {};

    var self = this;
    this.blockManager.on('setBlock', function(position, blockId, value) {
        self.onSetBlock(position, blockId, value);
    });
}

CustomObject.prototype.onSetBlock = function(position, blockId, value) {

    var blockData = this.blockManager.getData(blockId);

    if(value == 0) {
        if(this.objects.hasOwnProperty(blockId)) {
            this.noa.entities.remove(this.objects[blockId].entity_id);
            delete this.objects[blockId];
        }
        return;
    }

    if(blockData.type == "stairs") {
        var holder = BABYLON.Mesh.CreateBox("box", 0.0, this.noa.rendering._scene);
        holder.position = new BABYLON.Vector3(position[0]+0.5, position[1], position[2]+0.5);

        var bottom = BABYLON.Mesh.CreateBox("box_2", 1, this.noa.rendering._scene);
        bottom.scaling.y = 0.5;
        bottom.parent = holder;
        bottom.position.y = -0.25;
        bottom.position.z = -0.25;

        var back = BABYLON.Mesh.CreateBox("box_3", 1, this.noa.rendering._scene);
        back.scaling.y = 0.5;
        back.rotation.x = Math.PI/2;
        back.parent = holder;

        this.objects[blockId] = {
            entity_id: this.noa.entities.add(
                [position[0]+0.5, position[1], position[2]+0.5],              // starting loc
                1, 1, holder, null,   // size, mesh, mesh offset
                null, null            // do physics, draw shadow
            ),
            mesh: holder
         }
    }
}

module.exports = CustomObject;
andrewtheone commented 8 years ago

Problem solved:

Rendering.prototype.addDynamicMesh = function(mesh) {
  var i = this._octree.dynamicContent.indexOf(mesh)
  if (i>=0) return
  this._octree.dynamicContent.push(mesh)

  if(mesh.getChildren) {
    for (var i = 0; i < mesh.getChildren().length; i++) {
          mesh.getChildren()[i].useOctreeForCollisions = true;
          this.addDynamicMesh(mesh.getChildren()[i])
    }
  }

  // wrap or create onDispose
  var self = this
  var disp = function() {
    self.removeDynamicMesh(mesh)
  }
  if (!mesh.onDispose) {
    mesh.onDispose = disp
  } else {
    var _dispose = mesh.onDispose
    mesh.onDispose = function() {
      _dispose.call(mesh)
      disp()
    }
  }
}
fenomas commented 8 years ago

So the key here was that my ad-hoc way of managing BJS's dynamic mesh list wasn't adding the children, right? This is a kind of widgety part of BJS that I'm not fully sure I'm using in the best possible way, but as long as it works.

PS: regarding the functionality of stairs, did you see the "autoStep" option? When set the player entity will automatically step onto 1-block obstructions, removing the need to jump up stairways.

andrewtheone commented 8 years ago

Yes, the key was to add the children too (btw later I added a custom property to the children, which indicates "add as dynamic mesh" - somehow I got trippy effects on some objects)

And yes I set autoStep and its currently on but planing to modify the physics module too, so it can get data from blockData (for example, is it water, is it stair and so on, so it wouldnt walk up on each block but stairs)

Now I have half slabs and stairs, but they are dynamic meshes. And a little bug in my code: when I add the block in "worldDataNeeded", I add the dynamic mesh too, so its always rendered.

My next point would be to be able to determine if a voxel has been rendered or got cut off (meaning it was rendered at a certain point but from now on it will not). As far as I know, it should do something with the chunker/greedy mesher part but its not that clear yet. Any ideas?

fenomas commented 8 years ago

Hm.. I don't entirely follow what you're doing, but the intent of the library is that you don't have to manage your own meshes that are linked to voxels. Rather, you do something like:

// (code not tested!)
// add a mesh to the registry
noa.registry.registerMesh( 'my-mesh', someMesh, null )
// register a block ID using that mesh
var objID = noa.registry.registerObjectBlock( 'whatever', 'my-mesh', null, true, false, false )
// set a world block to that ID
noa.setBlock( objID, 5, 5, 5 )

..and the engine should then know that a new mesh instance needs to be added, and if you change block (5,5,5) to some other ID, the engine will remove the custom mesh. Does that make sense? Of course any meshes that aren't linked to voxels (birds, elevators, etc) would be a different matter, but if you want meshes to appear depending on the state of the world voxel data then the engine should manage it.

If the only problem is child meshes, I think it would be better to use a BJS function to merge your parent/children into one mesh. BJS considers parent/child relationships to be temporary settings, not part of a mesh's definition, so using a single merged mesh will let BJS create/destroy instances of the mesh rather than copies.

If all this doesn't answer your question then please add more info.

andrewtheone commented 8 years ago

Oh I didnt know about registerObjectBlock, I'll look into it if its what I need :)

How do I merge multiple meshes?

fenomas commented 8 years ago

No idea - I assume Babylon has an API for merging geometries, or perhaps you could make new merged meshes in whatever manner you made the unmerged ones.