celestia/src/cel3ds/3dsread.cpp

603 lines
17 KiB
C++
Raw Normal View History

// 3dsread.cpp
2006-09-16 10:14:34 -06:00
//
// Copyright (C) 2000, Chris Laurel <claurel@shatters.net>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
2021-10-29 14:48:09 -06:00
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include <Eigen/Core>
2021-10-29 14:48:09 -06:00
#include <fmt/ostream.h>
#include "celutil/bytes.h"
#include "3dschunk.h"
#include "3dsmodel.h"
#include "3dsread.h"
namespace
{
2021-10-29 14:48:09 -06:00
constexpr std::int32_t READ_FAILURE = -1;
constexpr std::int32_t UNKNOWN_CHUNK = -2;
template<typename T>
2021-10-29 14:48:09 -06:00
using ProcessChunkFunc = std::int32_t (*)(std::istream &, std::uint16_t, std::int32_t, T*);
2021-10-29 14:48:09 -06:00
bool readInt(std::istream& in, std::int32_t& value)
{
2021-10-29 14:48:09 -06:00
in.read(reinterpret_cast<char*>(&value), sizeof(std::int32_t));
if (!in.good()) { return false; }
LE_TO_CPU_INT32(value, value);
return true;
}
2021-10-29 14:48:09 -06:00
bool readShort(std::istream& in, std::int16_t& value)
{
2021-10-29 14:48:09 -06:00
in.read(reinterpret_cast<char*>(&value), sizeof(std::int16_t));
if (!in.good()) { return false; }
LE_TO_CPU_INT16(value, value);
return true;
}
2021-10-29 14:48:09 -06:00
bool readUshort(std::istream& in, std::uint16_t& value)
{
2021-10-29 14:48:09 -06:00
in.read(reinterpret_cast<char*>(&value), sizeof(std::uint16_t));
if (!in.good()) { return false; }
LE_TO_CPU_INT16(value, value);
return true;
}
2006-09-16 10:14:34 -06:00
2021-10-29 14:48:09 -06:00
bool readFloat(std::istream& in, float& value)
{
2021-10-29 14:48:09 -06:00
in.read(reinterpret_cast<char*>(&value), sizeof(float));
if (!in.good()) { return false; }
LE_TO_CPU_FLOAT(value, value);
return true;
}
2021-10-29 14:48:09 -06:00
bool readUchar(std::istream& in, unsigned char& value)
{
char c;
2021-10-29 14:48:09 -06:00
in.get(c);
if (!in.good()) { return false; }
value = static_cast<unsigned char>(c);
return true;
}
2021-10-29 14:48:09 -06:00
std::int32_t readString(std::istream& in, std::string& value)
{
2021-10-29 14:48:09 -06:00
constexpr std::size_t maxLength = 1024;
char s[maxLength];
2021-10-29 14:48:09 -06:00
for (std::size_t count = 0; count < maxLength; count++)
{
in.read(s + count, 1);
2021-10-29 14:48:09 -06:00
if (!in.good()) { return READ_FAILURE; }
if (s[count] == '\0')
2021-10-29 14:48:09 -06:00
{
value = s;
return count + 1;
}
}
2021-10-29 14:48:09 -06:00
return READ_FAILURE;
}
template<typename T>
std::int32_t read3DSChunk(std::istream& in,
ProcessChunkFunc<T> chunkFunc,
T* obj)
{
2021-10-29 14:48:09 -06:00
std::uint16_t chunkType;
if (!readUshort(in, chunkType)) { return READ_FAILURE; }
std::int32_t chunkSize;
if (!readInt(in, chunkSize) || chunkSize < 6) { return READ_FAILURE; }
2021-10-29 14:48:09 -06:00
std::int32_t contentSize = chunkSize - 6;
std::int32_t processedSize = chunkFunc(in, chunkType, contentSize, obj);
switch (processedSize)
{
2021-10-29 14:48:09 -06:00
case READ_FAILURE:
return READ_FAILURE;
case UNKNOWN_CHUNK:
in.ignore(contentSize);
return in.good() ? chunkSize : READ_FAILURE;
default:
if (processedSize != contentSize)
{
fmt::print(std::clog, "Chunk type {:04x}, expected {} bytes but read {}\n", chunkType, contentSize, processedSize);
return READ_FAILURE;
}
return chunkSize;
}
}
template<typename T>
2021-10-29 14:48:09 -06:00
std::int32_t read3DSChunks(std::istream& in,
std::int32_t nBytes,
ProcessChunkFunc<T> chunkFunc,
T* obj)
{
2021-10-29 14:48:09 -06:00
std::int32_t bytesRead = 0;
while (bytesRead < nBytes)
2021-10-29 14:48:09 -06:00
{
std::int32_t chunkSize = read3DSChunk(in, chunkFunc, obj);
if (chunkSize < 0) {
fmt::print(std::clog, "Failed to read 3DS chunk\n");
return READ_FAILURE;
}
bytesRead += chunkSize;
}
if (bytesRead != nBytes)
2021-10-29 14:48:09 -06:00
{
fmt::print(std::clog, "Multiple chunks, expected {} bytes but read {}\n", nBytes, bytesRead);
return READ_FAILURE;
}
return bytesRead;
}
2021-10-29 14:48:09 -06:00
std::int32_t readColor(std::istream& in, M3DColor& color)
{
2021-10-29 14:48:09 -06:00
unsigned char r, g, b;
if (!readUchar(in, r) || !readUchar(in, g) || !readUchar(in, b)) { return READ_FAILURE; }
color = {static_cast<float>(r) / 255.0f,
static_cast<float>(g) / 255.0f,
static_cast<float>(b) / 255.0f};
2021-10-29 14:48:09 -06:00
return 3;
}
2021-10-29 14:48:09 -06:00
std::int32_t readFloatColor(std::istream& in, M3DColor& color)
{
2021-10-29 14:48:09 -06:00
float r, g, b;
if (!readFloat(in, r) || !readFloat(in, g) || !readFloat(in, b)) { return READ_FAILURE; }
2021-10-29 14:48:09 -06:00
color = { r, g, b };
return static_cast<std::int32_t>(3 * sizeof(float));
}
2021-10-29 14:48:09 -06:00
std::int32_t readMeshMatrix(std::istream& in, Eigen::Matrix4f& m)
{
2021-10-29 14:48:09 -06:00
float elements[12];
for (std::size_t i = 0; i < 12; ++i)
{
if (!readFloat(in, elements[i])) { return READ_FAILURE; }
}
2021-10-29 14:48:09 -06:00
m << elements[0], elements[1], elements[2], 0,
elements[3], elements[4], elements[5], 0,
elements[6], elements[7], elements[8], 0,
elements[9], elements[10], elements[11], 1;
2009-07-22 20:58:10 -06:00
2021-10-29 14:48:09 -06:00
return static_cast<std::int32_t>(12 * sizeof(float));
}
2021-10-29 14:48:09 -06:00
std::int32_t readPointArray(std::istream& in, M3DTriangleMesh* triMesh)
{
2021-10-29 14:48:09 -06:00
std::uint16_t nPoints;
if (!readUshort(in, nPoints)) { return READ_FAILURE; }
std::int32_t bytesRead = static_cast<std::int32_t>(sizeof(nPoints));
2021-10-29 14:48:09 -06:00
for (int i = 0; i < static_cast<int>(nPoints); i++)
{
2021-10-29 14:48:09 -06:00
float x, y, z;
if (!readFloat(in, x) || !readFloat(in, y) || !readFloat(in, z)) { return READ_FAILURE; }
bytesRead += static_cast<std::int32_t>(3 * sizeof(float));
triMesh->addVertex(Eigen::Vector3f(x, y, z));
}
2021-10-29 14:48:09 -06:00
return bytesRead;
}
2021-10-29 14:48:09 -06:00
std::int32_t readTextureCoordArray(std::istream& in, M3DTriangleMesh* triMesh)
{
2021-10-29 14:48:09 -06:00
std::int32_t bytesRead = 0;
std::uint16_t nPoints;
if (!readUshort(in, nPoints)) { return READ_FAILURE; }
bytesRead += static_cast<std::int32_t>(sizeof(nPoints));
2021-10-29 14:48:09 -06:00
for (int i = 0; i < static_cast<int>(nPoints); i++)
{
2021-10-29 14:48:09 -06:00
float u, v;
if (!readFloat(in, u) || !readFloat(in, v)) { return READ_FAILURE; }
bytesRead += static_cast<std::int32_t>(2 * sizeof(float));
triMesh->addTexCoord(Eigen::Vector2f(u, -v));
}
2021-10-29 14:48:09 -06:00
return bytesRead;
}
2021-10-29 14:48:09 -06:00
std::int32_t processFaceArrayChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t /*contentSize*/,
M3DTriangleMesh* triMesh)
{
2021-10-29 14:48:09 -06:00
std::int32_t bytesRead = 0;
std::uint16_t nFaces;
2021-10-29 14:48:09 -06:00
std::unique_ptr<M3DMeshMaterialGroup> matGroup;
switch (chunkType)
{
case M3DCHUNK_MESH_MATERIAL_GROUP:
2021-10-29 14:48:09 -06:00
matGroup = std::make_unique<M3DMeshMaterialGroup>();
2021-10-29 14:48:09 -06:00
bytesRead = readString(in, matGroup->materialName);
if (bytesRead == READ_FAILURE || !readUshort(in, nFaces)) { return READ_FAILURE; }
bytesRead += static_cast<std::int32_t>(sizeof(nFaces));
2006-09-16 10:14:34 -06:00
for (std::uint16_t i = 0; i < nFaces; i++)
{
2021-10-29 14:48:09 -06:00
std::uint16_t faceIndex;
if (!readUshort(in, faceIndex)) { return READ_FAILURE; }
bytesRead += static_cast<std::int32_t>(sizeof(faceIndex));
matGroup->faces.push_back(faceIndex);
}
triMesh->addMeshMaterialGroup(std::move(matGroup));
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MESH_SMOOTH_GROUP:
nFaces = triMesh->getFaceCount();
2006-09-16 10:14:34 -06:00
for (std::uint16_t i = 0; i < nFaces; i++)
{
2021-10-29 14:48:09 -06:00
std::int32_t groups;
if (!readInt(in, groups) || groups < 0) { return READ_FAILURE; }
bytesRead += static_cast<std::int32_t>(sizeof(groups));
2021-10-29 14:48:09 -06:00
triMesh->addSmoothingGroups(static_cast<std::uint32_t>(groups));
}
2021-10-29 14:48:09 -06:00
return bytesRead;
default:
return UNKNOWN_CHUNK;
}
}
2021-10-29 14:48:09 -06:00
std::int32_t readFaceArray(std::istream& in, M3DTriangleMesh* triMesh, std::int32_t contentSize)
{
2021-10-29 14:48:09 -06:00
std::uint16_t nFaces;
if (!readUshort(in, nFaces)) { return READ_FAILURE; }
std::int32_t bytesRead = static_cast<std::int32_t>(sizeof(nFaces));
2021-10-29 14:48:09 -06:00
for (int i = 0; i < static_cast<int>(nFaces); i++)
{
2021-10-29 14:48:09 -06:00
std::uint16_t v0, v1, v2, flags;
if (!readUshort(in, v0) || !readUshort(in, v1) || !readUshort(in, v2) || !readUshort(in, flags))
{
return READ_FAILURE;
}
bytesRead += static_cast<std::int32_t>(4 * sizeof(std::uint16_t));
triMesh->addFace(v0, v1, v2);
}
2021-10-29 14:48:09 -06:00
if (bytesRead > contentSize) { return READ_FAILURE; }
if (bytesRead < contentSize)
{
2021-10-29 14:48:09 -06:00
std::int32_t trailingSize = read3DSChunks(in,
contentSize - bytesRead,
processFaceArrayChunk,
triMesh);
bytesRead += trailingSize;
2006-09-16 10:14:34 -06:00
}
2021-10-29 14:48:09 -06:00
return bytesRead;
}
2021-10-29 14:48:09 -06:00
std::int32_t processTriMeshChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t contentSize,
M3DTriangleMesh* triMesh)
{
switch (chunkType)
{
case M3DCHUNK_POINT_ARRAY:
2021-10-29 14:48:09 -06:00
return readPointArray(in, triMesh);
case M3DCHUNK_MESH_TEXTURE_COORDS:
2021-10-29 14:48:09 -06:00
return readTextureCoordArray(in, triMesh);
case M3DCHUNK_FACE_ARRAY:
2021-10-29 14:48:09 -06:00
return readFaceArray(in, triMesh, contentSize);
case M3DCHUNK_MESH_MATRIX:
2021-10-29 14:48:09 -06:00
{
Eigen::Matrix4f matrix;
std::int32_t bytesRead = readMeshMatrix(in, matrix);
if (bytesRead < 0) { return READ_FAILURE; }
triMesh->setMatrix(matrix);
return bytesRead;
}
default:
return UNKNOWN_CHUNK;
}
}
2021-10-29 14:48:09 -06:00
std::int32_t processModelChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t contentSize,
M3DModel* model)
{
if (chunkType == M3DCHUNK_TRIANGLE_MESH)
{
2021-10-29 14:48:09 -06:00
auto triMesh = std::make_unique<M3DTriangleMesh>();
std::int32_t bytesRead = read3DSChunks(in, contentSize, processTriMeshChunk, triMesh.get());
if (bytesRead == READ_FAILURE) { return READ_FAILURE; }
model->addTriMesh(std::move(triMesh));
2021-10-29 14:48:09 -06:00
return bytesRead;
}
2021-10-29 14:48:09 -06:00
return UNKNOWN_CHUNK;
}
2021-10-29 14:48:09 -06:00
std::int32_t processColorChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t /*contentSize*/,
M3DColor* color)
{
switch (chunkType)
{
case M3DCHUNK_COLOR_24:
2021-10-29 14:48:09 -06:00
return readColor(in, *color);
case M3DCHUNK_COLOR_FLOAT:
2021-10-29 14:48:09 -06:00
return readFloatColor(in, *color);
default:
return UNKNOWN_CHUNK;
}
}
2021-10-29 14:48:09 -06:00
std::int32_t processPercentageChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t /*contentSize*/,
float* percent)
{
switch (chunkType)
{
case M3DCHUNK_INT_PERCENTAGE:
2021-10-29 14:48:09 -06:00
{
std::int16_t value;
if (!readShort(in, value)) { return READ_FAILURE; }
*percent = static_cast<float>(value);
return sizeof(value);
}
case M3DCHUNK_FLOAT_PERCENTAGE:
2021-10-29 14:48:09 -06:00
return readFloat(in, *percent) ? sizeof(float) : READ_FAILURE;
default:
return UNKNOWN_CHUNK;
}
}
2021-10-29 14:48:09 -06:00
std::int32_t processTexmapChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t /*contentSize*/,
M3DMaterial* material)
{
if (chunkType == M3DCHUNK_MATERIAL_MAPNAME)
{
2021-10-29 14:48:09 -06:00
std::string name;
std::int32_t bytesRead = readString(in, name);
if (bytesRead < 0) { return READ_FAILURE; }
material->setTextureMap(name);
2021-10-29 14:48:09 -06:00
return bytesRead;
}
2021-10-29 14:48:09 -06:00
return UNKNOWN_CHUNK;
}
2021-10-29 14:48:09 -06:00
std::int32_t processMaterialChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t contentSize,
M3DMaterial* material)
{
2021-10-29 14:48:09 -06:00
std::int32_t bytesRead;
std::string name;
M3DColor color;
float t;
switch (chunkType)
{
case M3DCHUNK_MATERIAL_NAME:
2021-10-29 14:48:09 -06:00
bytesRead = readString(in, name);
if (bytesRead < 0) { return READ_FAILURE; }
material->setName(name);
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MATERIAL_AMBIENT:
2021-10-29 14:48:09 -06:00
bytesRead = read3DSChunks(in, contentSize, processColorChunk, &color);
if (bytesRead < 0) { return READ_FAILURE; }
material->setAmbientColor(color);
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MATERIAL_DIFFUSE:
2021-10-29 14:48:09 -06:00
bytesRead = read3DSChunks(in, contentSize, processColorChunk, &color);
if (bytesRead < 0) { return READ_FAILURE; }
material->setDiffuseColor(color);
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MATERIAL_SPECULAR:
2021-10-29 14:48:09 -06:00
bytesRead = read3DSChunks(in, contentSize, processColorChunk, &color);
if (bytesRead < 0) { return READ_FAILURE; }
material->setSpecularColor(color);
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MATERIAL_SHININESS:
2021-10-29 14:48:09 -06:00
bytesRead = read3DSChunks(in, contentSize, processPercentageChunk, &t);
if (bytesRead < 0) { return READ_FAILURE; }
material->setShininess(t);
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MATERIAL_TRANSPARENCY:
2021-10-29 14:48:09 -06:00
bytesRead = read3DSChunks(in, contentSize, processPercentageChunk, &t);
if (bytesRead < 0) { return READ_FAILURE; }
material->setOpacity(1.0f - t / 100.0f);
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_MATERIAL_TEXMAP:
2021-10-29 14:48:09 -06:00
return read3DSChunks(in, contentSize, processTexmapChunk, material);
default:
return UNKNOWN_CHUNK;
}
}
2021-10-29 14:48:09 -06:00
std::int32_t processSceneChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t contentSize,
M3DScene* scene)
{
2021-10-29 14:48:09 -06:00
std::int32_t bytesRead, chunksSize;
std::unique_ptr<M3DModel> model;
std::unique_ptr<M3DMaterial> material;
M3DColor color;
std::string name;
switch (chunkType)
{
case M3DCHUNK_NAMED_OBJECT:
2021-10-29 14:48:09 -06:00
bytesRead = readString(in, name);
if (bytesRead < 0) { return READ_FAILURE; }
model = std::make_unique<M3DModel>();
model->setName(name);
2021-10-29 14:48:09 -06:00
chunksSize = read3DSChunks(in,
contentSize - bytesRead,
processModelChunk,
model.get());
if (chunksSize < 0) { return READ_FAILURE; }
scene->addModel(std::move(model));
2021-10-29 14:48:09 -06:00
return bytesRead + chunksSize;
case M3DCHUNK_MATERIAL_ENTRY:
2021-10-29 14:48:09 -06:00
material = std::make_unique<M3DMaterial>();
bytesRead = read3DSChunks(in,
contentSize,
processMaterialChunk,
material.get());
if (bytesRead < 0) { return READ_FAILURE; }
scene->addMaterial(std::move(material));
2021-10-29 14:48:09 -06:00
return bytesRead;
case M3DCHUNK_BACKGROUND_COLOR:
2021-10-29 14:48:09 -06:00
bytesRead = read3DSChunks(in, contentSize, processColorChunk, &color);
if (bytesRead < 0) { return READ_FAILURE; }
scene->setBackgroundColor(color);
2021-10-29 14:48:09 -06:00
return bytesRead;
default:
2021-10-29 14:48:09 -06:00
return UNKNOWN_CHUNK;
}
}
2021-10-29 14:48:09 -06:00
std::int32_t processTopLevelChunk(std::istream& in,
std::uint16_t chunkType,
std::int32_t contentSize,
M3DScene* scene)
{
if (chunkType == M3DCHUNK_MESHDATA)
{
2021-10-29 14:48:09 -06:00
return read3DSChunks(in, contentSize, processSceneChunk, scene);
}
2021-10-29 14:48:09 -06:00
return UNKNOWN_CHUNK;
}
} // end namespace
std::unique_ptr<M3DScene> Read3DSFile(std::istream& in)
{
2021-10-29 14:48:09 -06:00
std::uint16_t chunkType;
if (!readUshort(in, chunkType) || chunkType != M3DCHUNK_MAGIC)
{
2021-10-29 14:48:09 -06:00
fmt::print(std::clog, "Read3DSFile: Wrong magic number in header\n");
return nullptr;
}
2021-10-29 14:48:09 -06:00
std::int32_t chunkSize;
if (!readInt(in, chunkSize) || chunkSize < 6)
{
2021-10-29 14:48:09 -06:00
fmt::print(std::clog, "Read3DSFile: Error reading 3DS file top level chunk size\n");
return nullptr;
}
2006-09-16 10:14:34 -06:00
2021-10-29 14:48:09 -06:00
fmt::print(std::clog, "3DS file, {} bytes\n", chunkSize + 6);
2021-10-29 14:48:09 -06:00
auto scene = std::make_unique<M3DScene>();
std::int32_t contentSize = chunkSize - 6;
2021-10-29 14:48:09 -06:00
std::int32_t bytesRead = read3DSChunks(in, contentSize, processTopLevelChunk, scene.get());
if (bytesRead < 0) { return nullptr; }
if (bytesRead != contentSize)
{
return nullptr;
}
return scene;
}
std::unique_ptr<M3DScene> Read3DSFile(const fs::path& filename)
{
std::ifstream in(filename.string(), std::ios::in | std::ios::binary);
if (!in.good())
{
2021-10-29 14:48:09 -06:00
fmt::print(std::clog, "Read3DSFile: Error opening {}\n", filename);
return nullptr;
}
std::unique_ptr<M3DScene> scene = Read3DSFile(in);
in.close();
return scene;
}
#if 0
int main(int argc, char* argv[])
{
if (argc != 2)
{
cerr << "Usage: 3dsread <filename>\n";
exit(1);
}
ifstream in(argv[1], ios::in | ios::binary);
if (!in.good())
{
cerr << "Error opening " << argv[1] << '\n';
exit(1);
}
else
{
read3DSFile(in);
in.close();
}
return 0;
}
#endif