ericw-tools/common/litfile.cc

166 lines
5.2 KiB
C++

/* Copyright (C) 1996-1997 Id Software, Inc.
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See file, 'COPYING', for details.
*/
#include <common/litfile.hh>
#include <common/cmdlib.hh>
#include <algorithm>
#include <fstream>
// litheader_t::v1_t
void litheader_t::v1_t::stream_write(std::ostream &s) const
{
s <= std::tie(ident, version);
}
void litheader_t::v1_t::stream_read(std::istream &s)
{
s >= std::tie(ident, version);
}
// litheader_t::v2_t
void litheader_t::v2_t::stream_write(std::ostream &s) const
{
s <= std::tie(numsurfs, lmsamples);
}
void litheader_t::v2_t::stream_read(std::istream &s)
{
s >= std::tie(numsurfs, lmsamples);
}
/**
* Packs a float3 into a 32-bit integer.
*
* Follows the OpenGL 4.6 Core spec, section 8.5.2 Encoding of Special Internal Formats.
*
* See HDR_UnpackE5BRG9 for the format description.
*/
uint32_t HDR_PackE5BRG9(qvec3f rgb)
{
constexpr int N = 9; // bits per component
constexpr int B = 15; // exponent bias
constexpr int Emax = 31; // max allowed exponent bias value
// slightly under 2^16
constexpr float max_representable = \
(static_cast<float>((1 << N) - 1) / static_cast<float>(1 << N)) * \
static_cast<float>(1 << (Emax - B));
// clamp inputs
const float r = std::max(0.0f, std::min(rgb[0], max_representable));
const float g = std::max(0.0f, std::min(rgb[1], max_representable));
const float b = std::max(0.0f, std::min(rgb[2], max_representable));
const float max_comp = std::max(std::max(r, g), b);
// avoid division by 0 below if the input is (0, 0, 0)
if (max_comp == 0.0f)
return 0;
// preliminary shared exponent
const int prelim_exponent = std::max(-B - 1, (int)std::floor(std::log2(max_comp))) + 1 + B;
// refined shared exponent
const int max_s = (int)std::floor((max_comp / std::pow(2.0f, prelim_exponent - B - N)) + 0.5f);
int refined_exponent = std::clamp((max_s < (1 << N)) ? prelim_exponent : prelim_exponent + 1, 0, 0x1f);
const float scale = std::pow(2.0f, refined_exponent - B - N);
int r_integer = std::clamp((int)std::floor((r / scale) + 0.5), 0, 0x1ff);
int g_integer = std::clamp((int)std::floor((g / scale) + 0.5), 0, 0x1ff);
int b_integer = std::clamp((int)std::floor((b / scale) + 0.5), 0, 0x1ff);
return (refined_exponent << 27) | (b_integer << 18) | (g_integer << 9) | (r_integer << 0);
}
/**
* Takes a e5bgr9 value as used in the LIGHTING_E5BGR9 lump and unpacks it into a float3
* in the order (red, green, blue).
*
* The packed format is, from highest-order to lowest-order bits:
*
* - top 5 bits: biased_exponent in [0, 31]
* - next 9 bits: blue_int in [0, 511]
* - next 9 bits: green_int in [0, 511]
* - bottom 9 bits: red_int in [0, 511]
*
* the conversion to floating point goes like:
*
* blue_float = 2^(biased_exponent - 24) * blue_int
*
* this is following OpenGL 4.6 Core spec, section 8.25 Shared Exponent Texture Color Conversion
*/
qvec3f HDR_UnpackE5BRG9(uint32_t packed)
{
// grab the top 5 bits. this is a value in [0, 31].
const uint32_t biased_exponent = packed >> 27;
// the actual exponent gets remapped to the range [-24, 7].
const int exponent = static_cast<int>(biased_exponent) - 24;
const uint32_t blue_int = (packed >> 18) & 0x1ff;
const uint32_t green_int = (packed >> 9) & 0x1ff;
const uint32_t red_int = packed & 0x1ff;
const float multiplier = std::pow(2.0f, static_cast<float>(exponent));
return qvec3f(red_int, green_int, blue_int) * multiplier;
}
lit_variant_t LoadLitFile(const fs::path &path)
{
std::ifstream stream(path, std::ios_base::in | std::ios_base::binary);
if (!stream.good()) {
return { lit_none() };
}
stream >> endianness<std::endian::little>;
std::array<char, 4> ident;
stream >= ident;
if (ident != std::array<char, 4>{'Q', 'L', 'I', 'T'}) {
throw std::runtime_error("invalid lit ident");
}
int version;
stream >= version;
if (version == LIT_VERSION) {
std::vector<uint8_t> litdata;
while (stream.good()) {
uint8_t b;
stream >= b;
litdata.push_back(b);
}
return {lit1_t{.rgbdata = std::move(litdata)}};
} else if (version == LIT_VERSION_E5BGR9) {
std::vector<uint32_t> litdata;
while (stream.good()) {
uint32_t sample;
stream >= sample;
litdata.push_back(sample);
}
return {lit_hdr{.samples = std::move(litdata)}};
}
throw std::runtime_error("invalid lit version");
}