fenomas / noa

Experimental voxel game engine.
MIT License
616 stars 91 forks source link

Adding Skybox To Scene #41

Closed masonmahaffey closed 6 years ago

masonmahaffey commented 6 years ago

I realize this mostly has to do with Babylon.js and not the noa voxel engine so I understand if you'd like for me to ask this question else where but I'm curious how to add a scene to noa that is stable?? I'm very new to Babylon.js and am still learning.

I followed along with the code nesh108 wrote that sets up the skybox:

function Skybox(opts) {
  if (!(this instanceof Skybox)) return new Skybox(opts || {});
  this.scene = opts.scene;
  this.skybox_size = opts.skybox_size || 200.0;
  this.skybox_path = opts.skybox_path;
  this.skybox_name = opts.skybox_name;

  // Setup skybox
  this.skybox = BABYLON.Mesh.CreateBox("skyBox", this.skybox_size, this.scene);
  this.skybox.renderingGroupId = 0;
  this.skybox.infiniteDistance = true;

  if (this.skybox_path && this.skybox_name) {
    this.set_skybox(this.skybox_name);
  }
}

Skybox.prototype.get_skybox = function() {
  return this.skybox;
};

Skybox.prototype.set_skybox = function(skybox_name) {
  this.skybox_material = new BABYLON.StandardMaterial("skyBox", this.scene);
  this.skybox_material.backFaceCulling = false;
  this.skybox_material.disableLighting = true;
  this.skybox_material.diffuseColor = new BABYLON.Color3(0, 0, 100);
  this.skybox_material.specularColor = new BABYLON.Color3(0, 0, 100);

  this.skybox_material.reflectionTexture = new BABYLON.CubeTexture(this.skybox_path + skybox_name, this.scene);
  this.skybox_material.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;

  this.skybox.material = this.skybox_material;
};

var noaEngine = __webpack_require__(20)

var opts = {
    showFPS: true,
    inverseY: false,
    chunkSize: 32,
    chunkAddDistance: 3,
    chunkRemoveDistance: 4,
    blockTestDistance: 10, //distance at which you can interact with blocks
    texturePath: 'textures/',
    playerStart: [0.5, 5, 0.5],
    playerHeight: 1.4,
    playerWidth: 0.6,
    playerAutoStep: false,
    useAO: true,
    AOmultipliers: [0.92, 0.8, 0.5],
    reverseAOmultiplier: 1.0,
}

// create engine
var noa = noaEngine(opts);

var noaScene = noa.rendering.getScene();

let skybox = new Skybox({
    // Pass it a copy of the Babylon scene
  scene: noaScene,
  // Path skybox folder
  skybox_path: 'textures/',
  // Skybox name
  skybox_name: 'TropicalSunnyDay',
  // Size skybox
  skybox_size: 1000.0,
});

// If using Noa-Engine: Tell engine to render it
noa.rendering.addMeshToScene(skybox.get_skybox());

It's rendering into the scene, however when you walk around the world, the skybox is flickering on and off randomly regardless of what skybox assets I use. Any ideas on what I should do?

fenomas commented 6 years ago

Hey, I don't know anything about how BJS handles skyboxes, but I think the flickering issue is a bug on my side, or rather a use case that needs to be addressed.

Basically, when you call addMeshToScene it assumes that the mesh is something smallish (compared to a chunk), and it adds the mesh in such a way that its visibility is linked to the visibility of the chunk it's in. This way when you add e.g. a flower, then Babylon knows to stop rendering the flower when the view frustum doesn't include the chunk that flower is in.

However this doesn't make sense for very large things like skyboxes, and there's currently no flag for that. The workaround would be this:

noa.rendering._octree.dynamicContent.push(skybox_mesh);

That puts the mesh into an array of stuff whose visibility is calculated separately.

I think the fix here is probably:

addMeshToScene(mesh, isStatic, isLarge)

or similar but I need to think about it a little.

masonmahaffey commented 6 years ago

Excellent, that did the trick... on a side note, if you WERE to add a flower mesh, how would you bundle the mesh in with the chunk so it is handled like the rest of the voxels? ;O

fenomas commented 6 years ago

There are a couple of options. One is, when you register a block type you can specify a blockMesh property in the options. Doing this means that when the engine creates a chunk with one of those voxels, instead of meshing terrain it will create an instance of whatever mesh you supplied. This is probably best if it's a simple, static mesh.

One other option is, you can specify some lifetime events for a given block type, such that you get events when voxel of that ID is created/destroyed/etc. You can use those events to add/remove/manage custom meshes any way you like.

masonmahaffey commented 6 years ago

Ok, so once the engine can recognize what a particular object is when generating the chunks, you can simply specify the block id and it will generate this block given the fact that you've added it into the registry. Awesome.

I also see that you can use setBlock() to update a block at a given coordinate. Any ideas on how I could possibly persist chunk data between clients? i.e. Client1 player updates block at (1,1,1) to id 0 air. Any ideas on how I should update the particular voxel in the chunk ndarray stored on my backend without sending the entire updated ndarray to the backend? My plan is for the backend to keep the state of what chunks the client has and try to persist data between clients.

masonmahaffey commented 6 years ago

Oh ok, I see. So the setBlockID determines which chunk the voxel resides in and the coordinates of the voxel itself in within that chunk based on the standard voxel coordinates within the world and sends this data to _updateChunkAndBorders which determines which chunks/voxels are bordered to the chunk containing the targeted block and loops over all the bordered voxels/chunks and sends the targeted chunk/voxel and bordered chunk/voxel data to _modifyBlockData which in turn calls the getChunk to get the specific chunk in memory and then overrides it with the new block id using setChunk. Am I on track here?

So i'd need to add a setStoredVoxelMethod() which would then pass an additional paramater saying whether or not this is a stored voxel down into the setBlockID all the way down into _modifyBlockData which would then call a getStoredVoxel() method which I would create to then reference the stored chunk ndarray right?

Once a player on one client modifies a block, then:

noa.inputs.down.on('fire', function () {
  // io.emit using socket.io ---> broadcast noa.targetedBlock to server
    if (noa.targetedBlock) noa.setBlock(0, noa.targetedBlock.position);
});

I will emit the targeted block to the clients.

masonmahaffey commented 6 years ago

HA! I don't need to do any of that...duh. Your engine really is minimal, modular, and ready to go. All I've got to do is broadcast the updated player blocks to all the players and if the voxel exists in a chunk in memory it will update it. Otherwise I dont need to worry about it and then I can just store chunk data on the server when the chunk is about to be removed or upon player disconnect since the voxel data for all chunks should have persisted amongst the clients through the event handlers.

masonmahaffey commented 6 years ago

Well... in theory that sounded great but I'm running into issues trying to implement this. I did get the voxels to persist between clients as long as both clients are in the same chunk, however, im having problems persisting the chunks. How would you recommend persisting chunk data across clients?

fenomas commented 6 years ago

Well, the short answer is that persisting mutable data between a server and multiple clients is a really nontrivial task, and if it was me I'd probably be looking for an existing solution before trying to roll something by hand. However I haven't played with any game servers, or thought about any kind of multiplayer approaches.

Suffice to say that noa is intended to live far underneath the code that would handle the kind of stuff you're talking about. That is, I'd expect any kind of multiplayer game to be conceptually divided up into layers - at the top there'd be a data layer, probably on the server, that knows the "true" value of every voxel, and below that would be a persistence layer that keeps clients and server in sync, and below that maybe an interaction layer that knows how to ask the persistence layer for more data (when the player moves around) or sends it updates (when the player changes a voxel). I would expect noa to live below all those other layers, because all it's really there for is to draw voxels onto the screen. Does that make sense?

masonmahaffey commented 6 years ago

Yes, that does help. Thanks for taking the time to respond. I think I'm starting to get a better concept of what would need to be built.

I found a really well written article describing a general server-client game architecture and it helped me to understand some of the problems you'd run into when building such a thing. Here's the link to the article: client-server-game-artchitecture

I'm definitely going to take your advice and see if I can find some sort of existing library. Also, It looks like Babylon already has a library called NullEngine() for server-side simulation so that's pretty great ;0

With null engine, it looks like you can simply replace this line:

    self._engine = new BABYLON.Engine(canvas, opts.antiAlias)

with this:

    self._engine = new BABYLON.NullEngine({
        renderWidth: 512,
        renderHeight: 256,
        textureSize: 512
    })

(given you've imported the library into node.js)

Additionally, BabylonJs says this about NullEngine():

- camera.attachControl cannot be used as it requires a HTML eLement
- DynamicTexture cannot be used as they rely on HTML canvas

So I guess I'd need to comment out any lines that use those classes, but that seems like the gist of it.

Can't wait to dig into it ;O

masonmahaffey commented 6 years ago

so far so good with nullEngine() ;)