voxel / ideas

issue tracker for new voxel.js plugin ideas
4 stars 1 forks source link

[block-models] Custom block models #3

Closed deathcap closed 10 years ago

deathcap commented 10 years ago

Support for blocks aligned to the world grid as usual (voxels), but with custom non-cube shapes.

This would go beyond transparency (https://github.com/maxogden/voxel-engine/issues/34), which by itself still only allows rendering cube faces, to additionally support arbitrary models. Examples: torches, slabs

There are several existing 3D model formats (three.js http://threejs.org/examples/ supports: assimp, collada, ctm, 'json', obj, scene, stl, 'utf8', vrml, vtk); MC is also adding their own format for resource packs: http://www.reddit.com/r/Minecraft/comments/1xg4k1/i_like_14w06b/cfcv1dc . Some mods use: http://www.minecraftforge.net/wiki/Rendering_a_Techne_Model_as_a_Block

gl-modules parser for PLY file format: https://www.npmjs.org/package/parse-ply http://www.cc.gatech.edu/projects/large_models/ply.html "The PLY file format is a simple object description that was designed as a convenient format for researchers who work with polygonal models"

deathcap commented 10 years ago

ref GH-5 ladders, GH-7 microblocks

deathcap commented 10 years ago

At least custom blocks heights would go a long way (water, slabs, ...)

deathcap commented 10 years ago

Found this pretty cool 3D map viewer for Minecraft/Bukkit, built on WebGL (frontend written in Haxe), renders many complex block models and the author is working on supporting more: https://github.com/thinkofdeath/ThinkMap

deathcap commented 10 years ago

Some interesting discussion of how MC is changing their block models at https://pay.reddit.com/r/Minecraft/comments/22vu5w/upcoming_changes_to_the_block_model_system/

Hi, folks! There have been a number of changes made to the block model system over the past few weeks - mainly pertaining to textures - so I figured now would be as good a time as any to let you all know what's coming down the pipe. I figure you can start updating your resource packs before the first snapshot hits, so that you can have updated packs available as soon as the relevant 1.8 snapshot becomes available, rather than having a time when you can't use anything.

One of the main things that has bugged me and Grum about the current block model system is that it's still terribly reliant on code. For example, in the currently released version of Minecraft, the beacon model specifies a textureFacing of "up" in order to fetch the glass texture, "down" to fetch the obsidian texture, and "north" to fetch the beacon texture. This means that the model is still reliant on the values returned by code, which makes the block model system ridiculously hard-coded and not a whole lot better than having the vertices themselves being specified by code. As a result, Erik and I decided to revisit the system.

Now, rather than supplying a "textureFacing" parameter, you simply specify a "texture" parameter. The texture specifier can be either direct or hierarchical. If it's meant to be filled in by a child model, it is prepended with the hash symbol (#). If it's a direct texture reference, it's just the name of a file in assets/minecraft/models/textures/blocks/. I'll get to the exact usage later in this post.

Other changes to the format involve how face culling is specified and how options for the model are specified. Here's a short list of the changes:

  • "useAmbientOcclusion" has been renamed to "ambientocclusion" for capitalization consistency.
  • "textureFacing" has been deprecated in favor of "texture", a reference to the texture itself.
  • "cull" has been renamed to "cullface", and specifies the opposite of which neighboring face causes culling to occur. For example, if you have an east-facing face but want it to be culled along a different axis (let's say Z), you would specify "cullface": "north" or "cullface": "south".
  • Element rotation has been made more verbose, so that it is more clear that element rotation can only occur on a single axis. For example, the rotation for one of the two faces of the "cross" model (used by saplings and such) is now: "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": true },
  • A new flag, "rescale", has been added to the rotation parameters. The flag being set to true means that the face should be scaled along the non-rotation axes by the inverse size of the hypotenuse. In plain English, a face covering 0-1 being rotated 45 degrees on the Y axis would have only ended up covering 0.292 to 0.707. By setting "rescale" to true, it will be re-scaled to 0 to 1 once again.

As an actual practical example, let's look at Stone Brick. Previously, all of the variants of Stone Brick just refer to the "cube" model, since the texture is supplied by code. Now that it's supplied by the model itself, we need a unique model for each variant. Here is the model definition file in the latest codebase: http://pastebin.com/CMLCbsmU

{
    "__comment": "Fair warning, this format is highly likely to change even more in the future!",
    "variants": {
        "normal": { "model": "stonebrick_normal" },
        "mossy": { "model": "stonebrick_mossy" },
        "cracked": { "model": "stonebrick_cracked" },
        "chiseled": { "model": "stonebrick_chiseled" }
    }
}

Going further up the chain, let's have a look at stonebrick_normal.json, which defines the model for the un-cracked, un-vined, un-chiseled version of stone brick: http://pastebin.com/jadWesjb

{
    "__comment": "Fair warning, this format is highly likely to change even more in the future!",
    "parent": "cube_all",
    "textures": {
        "all": "stonebrick"
    }
}

You can see that the only unique thing about the model is its texture. The JSON definition indicates that the "all" texture reference should be filled in by "stonebrick", which refers to assets/minecraft/textures/blocks/stonebrick.png. Moving one step further up the chain, we have cube_all.json: http://pastebin.com/EaxknCzN

{
    "__comment": "Fair warning, this format is highly likely to change even more in the future!",
    "parent": "cube",
    "textures": {
        "particle": "#all",
        "down": "#all",
        "up": "#all",
        "north": "#all",
        "east": "#all",
        "south": "#all",
        "west": "#all"
    }
}

As you can see, it's just one more step of indirection. It uses the model specified by cube.json, but rather than having individual textures per-face, all of the faces refer to one single reference called "all". Last but not least, let's take a look at cube.json itself: http://pastebin.com/MGcueNpX

{
    "__comment": "Fair warning, this format is highly likely to change even more in the future!",
    "elements": [
        { "from": [ 0, 0, 0 ],
            "to": [ 16, 16, 16 ],
            "faces": {
                "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#down", "cullface": "down" },
                "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#up", "cullface": "up" },
                "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#north", "cullface": "north" },
                "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#south", "cullface": "south" },
                "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#west", "cullface": "west" },
                "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#east", "cullface": "east" }
            }
        }
    ]
}

Nothing special here other than the changes that I've already mentioned earlier in the post. Last but not least, let's move onto the subject of an entirely new feature for the block model system, which I call "UV Lock". It's specified as an additional parameter when defining the variant for a given model. As an example, here's the definition file for quartz stairs: http://pastebin.com/KwgHMsHd

{
    "__comment": "Fair warning, this format is highly likely to change even more in the future!",
    "variants": {
        "inventory": { "model": "quartz_stairs" },
        "east": { "model": "quartz_stairs" },
        "west": { "model": "quartz_stairs", "y": 180, "uvlock": true },
        "south": { "model": "quartz_stairs", "y": 90, "uvlock": true },
        "north": { "model": "quartz_stairs", "y": 270, "uvlock": true },
        "outer_corner_east": { "model": "quartz_outer_stairs" },
        "outer_corner_west": { "model": "quartz_outer_stairs", "y": 180, "uvlock": true },
        "outer_corner_south": { "model": "quartz_outer_stairs", "y": 90, "uvlock": true },
        "outer_corner_north": { "model": "quartz_outer_stairs", "y": 270, "uvlock": true },
        "outer_corner_alt_east": { "model": "quartz_outer_stairs", "y": 270, "uvlock": true },
        "outer_corner_alt_west": { "model": "quartz_outer_stairs", "y": 90, "uvlock": true },
        "outer_corner_alt_south": { "model": "quartz_outer_stairs" },
        "outer_corner_alt_north": { "model": "quartz_outer_stairs", "y": 180, "uvlock": true },
        "inner_corner_east": { "model": "quartz_inner_stairs" },
        "inner_corner_west": { "model": "quartz_inner_stairs", "y": 180, "uvlock": true },
        "inner_corner_south": { "model": "quartz_inner_stairs", "y": 90, "uvlock": true },
        "inner_corner_north": { "model": "quartz_inner_stairs", "y": 270, "uvlock": true },
        "inner_corner_alt_east": { "model": "quartz_inner_stairs", "y": 270, "uvlock": true },
        "inner_corner_alt_west": { "model": "quartz_inner_stairs", "y": 90, "uvlock": true },
        "inner_corner_alt_south": { "model": "quartz_inner_stairs" },
        "inner_corner_alt_north": { "model": "quartz_inner_stairs", "y": 180, "uvlock": true },
        "upper_east": { "model": "quartz_stairs", "x": 180, "uvlock": true },
        "upper_west": { "model": "quartz_stairs", "x": 180, "y": 180, "uvlock": true },
        "upper_south": { "model": "quartz_stairs", "x": 180, "y": 90, "uvlock": true },
        "upper_north": { "model": "quartz_stairs", "x": 180, "y": 270, "uvlock": true },
        "outer_corner_upper_east": { "model": "quartz_outer_stairs", "x": 180, "uvlock": true },
        "outer_corner_upper_west": { "model": "quartz_outer_stairs", "x": 180, "y": 180, "uvlock": true },
        "outer_corner_upper_south": { "model": "quartz_outer_stairs", "x": 180, "y": 90, "uvlock": true },
        "outer_corner_upper_north": { "model": "quartz_outer_stairs", "x": 180, "y": 270, "uvlock": true },
        "outer_corner_alt_upper_east": { "model": "quartz_outer_stairs", "x": 180, "y": 90, "uvlock": true },
        "outer_corner_alt_upper_west": { "model": "quartz_outer_stairs", "x": 180, "y": 270, "uvlock": true },
        "outer_corner_alt_upper_south": { "model": "quartz_outer_stairs", "x": 180, "y": 180, "uvlock": true },
        "outer_corner_alt_upper_north": { "model": "quartz_outer_stairs", "x": 180, "uvlock": true },
        "inner_corner_upper_east": { "model": "quartz_inner_stairs", "x": 180, "uvlock": true },
        "inner_corner_upper_west": { "model": "quartz_inner_stairs", "x": 180, "y": 180, "uvlock": true },
        "inner_corner_upper_south": { "model": "quartz_inner_stairs", "x": 180, "y": 90, "uvlock": true },
        "inner_corner_upper_north": { "model": "quartz_inner_stairs", "x": 180, "y": 270, "uvlock": true },
        "inner_corner_alt_upper_east": { "model": "quartz_inner_stairs", "x": 180, "y": 90, "uvlock": true },
        "inner_corner_alt_upper_west": { "model": "quartz_inner_stairs", "x": 180, "y": 270, "uvlock": true },
        "inner_corner_alt_upper_south": { "model": "quartz_inner_stairs", "x": 180, "y": 180, "uvlock": true },
        "inner_corner_alt_upper_north": { "model": "quartz_inner_stairs", "x": 180, "uvlock": true }
    }
}

"uvlock" is a parameter that directs the model system to re-compute the UVs for a given model after rotation based on a "shrink-wrap" type of algorithm. I implemented it after I had the painful realization that I wouldn't be able to get pixel-identical results for things like fences using the new block model system. Fence, for example, has one single 90-degree connection model that gets rotated by 90 degrees for the other three states. There's one model that includes the north/east connections, then it's rotated into place for south/east, south/west, and north/west.

However, rotating the block preserves the UV coordinates that were originally specified. In some cases, this can be desirable. In other cases, though, like fence, you want (let's say) the contiguous horizontal bars of a given section to have a consistently repeating pattern. If you have different UVs thrown in due to the physical rotation, then it's going to look really weird and ugly. Therefore, "uvlock" was devised. If you have an upper face that goes from 0,0 to 8,8 and it's rotated by 180 degrees about the Y axis, then rather than still being 0,0-8,8, it will now cover 8,8-16,16 as it should. "Should" is maybe a strong term to use here, but it's how it worked when everything was hard-coded, and we're trying to have as little visual impact from the block model system as possible.

Anyway, those are all of the upcoming changes to the block model system in a nutshell. There probably won't be a snapshot in this coming week due to the Easter holiday and the fact that Thursday is a half-day, combined with us all leaving at 10:30 in the morning to go to the Blockholm exhibit at the Museum of Architecture in order to meet-and-greet (mark it on your calendars, folks!), so that means you have just over a week and a half in order to make updated versions of your models.

Godspeed!

deathcap commented 10 years ago

This should be useful: https://github.com/hughsk/gl-geometry A flexible wrapper for gl-vao and gl-buffer that you can use to set up renderable WebGL geometries from a variety of different formats.

deathcap commented 10 years ago

Basic support is in:

deathcap commented 9 years ago

Interesting comment about MC 1.8's new JSON-based block model format, and some problems with it for developers (to keep in mind to avoid with voxel.js):

https://www.reddit.com/r/feedthebeast/comments/2ni6av/eli5_the_18_block_model_change/

Okay, here's the deal. I maintain BuildCraft. I have this things called "pipes" in the mod.

Now, in Minecraft 1.8, there is no more rendering hooks - only BlockState models. A BlockState is a unique state of a block, think combined block and metadata. Due to format limitations, there can only be 2^16 = 65536 of them (in 1.7.10 and below, this was 4096 block IDs times 16 different metadata values, also 65536 - see?). It also stores a model - one per BlockState. You cannot have the model depend on the in-world location or neighboring blocks - it is unique to the BlockState.

Now, how many models does BuildCraft use? Let's do a rough approximation.

A pipe has six neighbouring sides. A side can have a gate on it (of which we have about 20 types, in all combinations), a pipe plug, a robot docking station, a solid connection, a standard connection, nothing or one of the many facades. For vanilla facades only, we can assume the combination of things per side is about 128, or 2^7. That means it takes 7 bits to store, so for six sides you need to store 7*6, or 42 bits, or 2^42 combinations.

But there's more!

We also have about 40 different pipe types, multiplied by 17 inside types (no colored glass or one of the 16 colors of colored glass). That means the estimate at this point is 40 x 17 x 2^42, or about 2990671627550720 combinations. Now, compare that to the 65536 states we can have, tops, and that is shared between all mods.

Boo-boo. I didn't even take into account the fact Daizuli pipes have 16 possible color markings on them (same for Lapis pipes) or the pipe wires (up to 4 wires on each pipe). As you can tell, the amount of data required is simply RIDICULOUS.

(After adding those two into account, we get (40 + 2 x 16) x 17 x 2^42 x 2^4 = 86131342873460700 combinations. And I still might have forgotten something. Remember that this excludes mod facades.)

What are our options?

  • Make everything TESRs. A TESR is a Tile Entity Special Renderer, which lets you force custom rendering on a tile entity. This is laggy, though, as it also forces re-rendering on every frame. For hundreds of pipes, this would add up very quickly.
  • Restore ISBRHs. Those are the old block render overrides from 1.7.10 and below. They are not too hard to restore and let you do all those things we did before in the old ways, HOWEVER LexManos seems generally opposed to not using Mojang's model system,
  • Something else (dynamic model generation? Etc)

This is what the model change and the outcry is about.

And yes, I have looked into the system's design via both JSON and Minecraft 1.8 source code while I was playing with beta versions of FML. I also consulted other devs on this. I know what I am talking about.

EDIT: Bonus facts:

  • Fire uses 95 JSON files
  • Mojang themselves have a special workaround for liquids in the source code.