Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How best to access chunk/userData during block lifecycle hooks? #111

Closed
Jarred-Sumner opened this issue May 6, 2020 · 5 comments
Closed
Labels

Comments

@Jarred-Sumner
Copy link
Contributor

The onLoad function in my use case needs to be able to access additional data about the block. In my game, sometimes blocks will show a remote image for one of the sides and this will be user-generated content – so it likely doesn't make sense to use noa.registry.registerBlock here

Initially, I thought I could make this work by getting the chunk in the callback, but at the time of this function being called noa.world._getChunkByCoords(x,y,z) returns null. Additionally, userData is not set until after the chunk is initialized?

I've worked around this locally in the meantime, but it'd be great if userData was optionally passed in to each of the block lifecycle hooks or if the Chunk object itself was accessible.

For example:

  const onLoad = (x: number, y: number, z: number, userData) => {
    const state = userData.get(stateKey(x, y, z));

     // Do something with the specific block here based on userData 
  };

Do you want a PR that does this?

@fenomas
Copy link
Owner

fenomas commented May 6, 2020

Hm, I see what you're asking and it wouldn't be hard, but I'm not sure it makes sense API-wise. But let's think it out.

About that userdata field, originally it was there for the client to tell the server what kind of data its requesting (e.g. overworld vs nether). However this turned out to lead to unresolvable race conditions, so now noa.worldName as the more robust way of doing the same thing. I've actually been meaning to remove the userdata field, as I didn't think it currently has any use.

Now for your case, if I follow, you have the client asking the server for a chunk of data, and the server is replying with raw voxel data, and also using the userdata field to supply some metadata about certain voxels within the chunk, right? If that's correct, it sounds like the metadata is per-voxel, so it would seem to make sense to store it somewhere, keyed by (x,y,z), once you get it from the server. If you do that before you call setChunkData, then later on the voxel onLoad handlers can access it without needing to know which chunk event they were created from.

The reason I say this is, noa considers chunks a rendering abstraction. Chunk objects can get disposed and later recreated as the user moves around, and in the future might need to be remade for other reasons, and the engine won't try to persist any of their properties (it assumes the game client is managing its own state). Also, world generation is super asynchronous (chunks go through 4-5 async steps between being requested and being rendered), so if you have game logic that is sensitive to the timing of when chunk objects or their properties get created or disposed, I think you'll find it causes nasty bugs down the line.

Let me know if that makes sense, or if I'm misunderstanding the use case.

@Jarred-Sumner
Copy link
Contributor Author

Jarred-Sumner commented May 6, 2020

I see what you’re saying — it probably doesn’t make sense for the Chunk to know about this

Honestly, from an API perspective, it would be ideal if I could override a specific side of a block to insert a custom mesh on top as a Plane and as a one-off instead of registering a new block ID for it. This is a little like blockMesh — but the difference here is I won’t know until receiving the chunk data what the custom mesh will be (since it’s a URL to an image assigned to a specific side of a block).

With the game, it could really be either slightly offset from (like a picture frame) or replacing a side (like wallpaper). I'm not sure which is best yet. Also I will have to make it potentially stretch across multiple voxels

What do you think is the best approach for this?

  • Create it onLoad/dispose on unLoad
  • Just call noa.rendering.addMeshToScene at the precise position after setting worldData in a loop for each mesh
  • Something else?

I’m not sure how best to send the data from the server to the client either tbh. I don’t expect chunks to be filled with these objects (like block types), but I don’t think voxel-crunch supports strings (url). I currently have it as a map where the key is ${x},${y},${z} as a string (relative to the beginning of the chunk), but this would be inefficient if there were lots of them.


Unrelated, for my game I made it so there's no pointer lock, its third person with movement controls like World of Warcraft or Roblox – meaning you can comfortably move with just WSAD and no mouse, with the tradeoff that it loses precision expected for 1st person games

To do this, picking is based on mouse coords instead of where the camera is looking. To support that, I slightly modified noa._localPick and applyInputsToCamera:

const _localPick = (pos, vec, dist, blockIdTestFunction) => {
      if (_vec[0] === 0 && _vec[1] === 0 && _vec[2] === 0) {
        return null;
      }

      // do a raycast in local coords - result obj will be in global coords
      if (dist === 0) return null;
      var testFn = blockIdTestFunction || noa.registry.getBlockSolidity;
      var world = noa.world;

      var testVoxel = function (x, y, z) {
        var id = world.getBlockID(x + off[0], y + off[1], z + off[2]);
        return testFn(id);
      };

      if (!pos) {
        pos = noa.camera._localGetTargetPosition();
      }

      vec = _vec;

      dist = dist || noa.blockTestDistance;
      var rpos = _hitResult._localPosition;
      var rnorm = _hitResult.normal;

      var hit = raycast(testVoxel, pos, vec, dist, rpos, rnorm);
      if (!hit) {
        return null;
      }
      // position is right on a voxel border - adjust it so that flooring works reliably
      // adjust along normal direction, i.e. away from the block struck
      Vec3.scaleAndAdd(rpos, rpos, rnorm, 0.01);
      // add global result
      noa.localToGlobal(rpos, _hitResult.position);
      noa.localToGlobal(this.pickPoint, comparePos);

      if (Vec3.dist(comparePos, _hitResult.position) > 1) {
        return null;
      }

      return _hitResult;
    };

    noa._localPick = _localPick;
const updatePickPosition = (pointerInfo) => {
  if (pointerInfo.pickInfo.hit) {
    this.pickPoint = pointerInfo.pickInfo.pickedPoint.asArray();
    Ray.CreateNewFromTo(
      this.camera.parent.position,
      pointerInfo.pickInfo.pickedPoint
    ).direction.toArray(_vec);
  } else {
    _vec[0] = 0;
    _vec[1] = 0;
    _vec[2] = 0;
  }
};

scene.onPointerObservable.add((pointerInfo) => {
  updatePickPosition(pointerInfo);

  return true;
});

@fenomas
Copy link
Owner

fenomas commented May 6, 2020

Okay, I have a vague idea where you're going here. My guess is that it'll be most robust to register a single block type for these blocks and create/destroy meshes in the voxel lifecycle events. And then to manage the strings for which URL goes where as a second variety of world data, keyed by absolute world positions, which would be exchanged with the server separately from voxel data.

For how best to do this, broadly noa's goal is not to be a full complete game engine, but rather to manage messy voxel things and otherwise stay out of your way. So the intention is that most typical games will do a certain amount of loading their own images and making their own meshes, for anything that isn't part of the voxel terrain. In your case the meshes will look like terrain but they can't really be meshed together with terrain, so you probably want to manage them yourself.

The easiest way to do this is usually to make an entity for the thing you want to add, with a position component and a mesh component to specify what goes where. There's a helper API for this:

var pos = [5,5,5]
var wd = 1
var ht = 1
var mesh = // import...
var meshOffset = [0,0,0]
var doPhysics = false
var shadow = false
noa.entities.add(pos, wd, ht, mesh, meshOffset, doPhysics, shadow)

If you do that, noa will automatically add the mesh to the scene, in the right octree for its position, and the mesh will automatically get moved as needed when the world rebases its origin. And you can just delete the entity when you want to get rid of it, and the mesh/position/etc. should all get disposed. You can also ignore noa and do whatever directly to the Babylon scene object, but then you need to mess with octrees and world rebases yourself.

@Jarred-Sumner
Copy link
Contributor Author

This makes sense – I'll use that approach.

Do you suggest still using the registry for this, with custom block IDs? It would be helpful as a way of saying "place this here".

Alternatively, I could have a callbacks for each and every block that could have another mesh associated with it, and then do the lookup in those callbacks

@fenomas
Copy link
Owner

fenomas commented May 14, 2020

If I follow your question, it's just a matter of convenience - if you know all the different kinds of media blocks at init time you could register an ID for each, but if you don't you could register them all as the same block and have one set of event handlers for all of them.

Note though, if there are hundreds of different kinds then you'll want the latter approach, since voxel IDs are currently stored with 9 bits, so the maximum ID is 511.

@fenomas fenomas closed this as completed Jun 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants