I discovered MagicaVoxel from Giawa's dev blog, and he already made some code models into his program, which is already available for use. After modifying the code a bit, I was able to get a model loading correctly into my game. I had to flip some coordinate values around, because Magica treats Z axis as vertical, while I use the Y axis for that. Also, I had trouble expanding the 16-bit color values back into a 32-bit integer. The colors were approximate but still looked off, especially with skin tones and lighter colors. So my importer code stores voxels with 24-bit color values. Here is a comparison with the model in Magica and in the game with my importer:
Models made with Magica use a palette of 256 colors which can be user-defined. My model format in the game uses the first 768 bytes to store the RGB values for the palette (alpha not supported yet), and the rest of the bytes are X, Y, Z and color index for each solid voxel. At the end I store two more X, Y, Z locations to get the bounding box of the model. This helps me center and position it for player movement.
The code for converting the voxels to meshes is a lot like the code for the chunk meshes. The process is split up into three steps - copy all voxels to a 3D array, cull visible voxels, and finally check neighbors to make the voxel cubes with the right combination of sides.
As with the chunks, each step is also thread-able to speed up loading of models. It now seems possible to condense a lot of this code into one generic class or set of functions that can work with any kind of voxel object. But the code still needs more cleaning up in order to make this happen.
One trick I use to make this code more readable is to pad the voxel array with a 1 unit "wall" of empty voxels on each side. For example, if I want to import a model 32x32x128 in size, I load it into a 34x34x130 array. Then instead of looping from 0 to 31 or 0 to 127, I loop from 1 to 32, 1 to 128, etc. This way I can guarantee that each voxel in the model doesn't have any neighbors that fall out of bounds in the array.
Now it's time for me to get a bit creative. Time to start making my own voxel models for the landscape, as I will be adding grass, bushes, etc. and coming up with items that can be picked up.