EG/RenderPipelineFile/rpcore/native/source/ies_dataset.cpp
2025-12-12 16:16:15 +08:00

234 lines
9.4 KiB
C++

/**
*
* RenderPipeline
*
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include "ies_dataset.h"
#define _USE_MATH_DEFINES
#include <math.h>
NotifyCategoryDef(iesdataset, "")
/**
* @brief Constructs a new empty dataset.
* @details This constructs a new IESDataset with no data set.
*/
IESDataset::IESDataset() {
}
/**
* @brief Sets the vertical angles of the dataset.
* @details This sets the list of vertical angles of the dataset.
*
* @param vertical_angles Vector of all vertical angles.
*/
void IESDataset::set_vertical_angles(const PTA_float &vertical_angles) {
nassertv(vertical_angles.size() > 0);
_vertical_angles = vertical_angles;
}
/**
* @brief Sets the horizontal angles of the dataset.
* @details This sets the list of horizontal angles of the dataset.
*
* @param horizontal_angles Vector of all horizontal angles.
*/
void IESDataset::set_horizontal_angles(const PTA_float &horizontal_angles) {
nassertv(horizontal_angles.size() > 0);
_horizontal_angles = horizontal_angles;
}
/**
* @brief Sets the candela values.
* @details This sets the candela values of the dataset. They should be an
* interleaved 2D array with the dimensions vertical_angles x horizontal_angles.
* They also should be normalized by dividing by the maximum entry.
* @param candela_values Interleaved 2D-vector of candela values.
*/
void IESDataset::set_candela_values(const PTA_float &candela_values) {
nassertv(candela_values.size() == _horizontal_angles.size() * _vertical_angles.size());
_candela_values = candela_values;
}
/**
* @brief Internal method to access the candela data.
* @details This lookups a candela value in the candela values. It converts a
* two dimensional index to a onedimensional index and then returns the candela
* value at that position.
*
* @param vertical_angle_idx Index of the vertical angle
* @param horizontal_angle_idx Index of the horizontal angle
*
* @return Candela value between 0 .. 1
*/
float IESDataset::get_candela_value_from_index(size_t vertical_angle_idx, size_t horizontal_angle_idx) const {
size_t index = vertical_angle_idx + horizontal_angle_idx * _vertical_angles.size();
nassertr(index >= 0 && index < _candela_values.size(), 0.0);
return _candela_values[index];
}
/**
* @brief Samples the dataset at the given position
* @details This looks up a value in the dataset, by specifying a horizontal and
* vertical angle. This is used for generating the LUT. The vertical and horizontal
* angle should be inside of the bounds of the vertical and horizontal angle arrays.
*
* @param vertical_angle Vertical angle, from 0 .. 90 or 0 .. 180 depending on the dataset
* @param horizontal_angle Horizontal angle, from 0 .. 180 or 0 .. 360 depending on the dataset.
*
* @return Candela value between 0 .. 1
*/
float IESDataset::get_candela_value(float vertical_angle, float horizontal_angle) const {
// Special case for datasets without horizontal angles
if (_horizontal_angles.size() == 1) {
return get_vertical_candela_value(0, vertical_angle);
}
float max_angle = _horizontal_angles[_horizontal_angles.size() - 1];
// Wrap angle to fit from 0 .. 360 degree. Most profiles only distribute
// candela values from 0 .. 180 or even 0 .. 90. We have to mirror the
// values at those borders (so 2 times for 180 degree and 4 times for 90 degree)
horizontal_angle = fmod(horizontal_angle, 2.0f * max_angle);
if (horizontal_angle > max_angle) {
horizontal_angle = 2.0 * max_angle - horizontal_angle;
}
// Simlar to the vertical step, we now try interpolating a horizontal angle,
// but we need to evaluate the vertical value for each row instead of fetching
// the value directly
for (size_t horizontal_index = 1; horizontal_index < _horizontal_angles.size(); ++horizontal_index) {
float curr_angle = _horizontal_angles[horizontal_index];
if (curr_angle >= horizontal_angle) {
// Get previous angle data
float prev_angle = _horizontal_angles[horizontal_index - 1];
float prev_value = get_vertical_candela_value(horizontal_index - 1, vertical_angle);
float curr_value = get_vertical_candela_value(horizontal_index, vertical_angle);
// Interpolate lineary
float lerp = (horizontal_angle - prev_angle) / (curr_angle - prev_angle);
// Should never occur, but to be safe:
if (lerp < 0.0 || lerp > 1.0) {
iesdataset_cat.error() << "Invalid horizontal lerp: " << lerp
<< ", requested angle was " << horizontal_angle
<< ", prev = " << prev_angle << ", cur = " << curr_angle
<< endl;
}
return curr_value * lerp + prev_value * (1-lerp);
}
}
return 0.0;
}
/**
* @brief Fetches a vertical candela value
* @details Fetches a vertical candela value, using a given horizontal position.
* This does an 1D interpolation in the candela values array.
*
* @param horizontal_angle_idx The index of the horizontal angle in the horizontal
* angle array.
* @param vertical_angle The vertical angle. Interpolation will be done if the
* vertical angle is not in the vertical angles array.
*
* @return Candela value between 0 .. 1
*/
float IESDataset::get_vertical_candela_value(size_t horizontal_angle_idx, float vertical_angle) const {
nassertr(horizontal_angle_idx >= 0 && horizontal_angle_idx < _horizontal_angles.size(), 0.0);
// Lower bound
if (vertical_angle < 0.0) return 0.0;
// Upper bound
if (vertical_angle > _vertical_angles[_vertical_angles.size() - 1] ) return 0.0;
// Find lowest enclosing angle
for (size_t vertical_index = 1; vertical_index < _vertical_angles.size(); ++vertical_index) {
float curr_angle = _vertical_angles[vertical_index];
// Found value
if (curr_angle > vertical_angle) {
// Get previous angle data
float prev_angle = _vertical_angles[vertical_index - 1];
float prev_value = get_candela_value_from_index(vertical_index - 1, horizontal_angle_idx);
float curr_value = get_candela_value_from_index(vertical_index, horizontal_angle_idx);
// Interpolate lineary
float lerp = (vertical_angle - prev_angle) / (curr_angle - prev_angle);
// Should never occur, but to be safe:
if (lerp < 0.0 || lerp > 1.0) {
iesdataset_cat.error() << "ERROR: Invalid vertical lerp: " << lerp
<< ", requested angle was " << vertical_angle
<< ", prev = " << prev_angle << ", cur = " << curr_angle
<< endl;
}
return curr_value * lerp + prev_value * (1-lerp);
}
}
return 0.0;
}
/**
* @brief Generates the IES LUT
* @details This generates the LUT into a given dataset texture. The x-axis
* referes to the vertical_angle, whereas the y-axis refers to the
* horizontal angle.
*
* @param dest_tex Texture to write the LUT into
* @param z Layer to write the LUT into, in case the texture is a 3D Texture or
* 2D Texture Array.
*/
void IESDataset::generate_dataset_texture_into(Texture* dest_tex, size_t z) const {
size_t resolution_vertical = dest_tex->get_y_size();
size_t resolution_horizontal = dest_tex->get_x_size();
// Candla values are stored flippped - vertical angles in the x - Axis
// and horizontal angles in the y - Axis
PNMImage dest = PNMImage(resolution_vertical, resolution_horizontal, 1, 65535);
for (size_t vert = 0; vert < resolution_vertical; ++vert) {
for (size_t horiz = 0; horiz < resolution_horizontal; ++horiz) {
float vert_angle = (float)vert / (float)(resolution_vertical-1);
vert_angle = cos(vert_angle * M_PI) * 90.0 + 90.0;
float horiz_angle = (float)horiz / (float)(resolution_horizontal-1) * 360.0;
float candela = get_candela_value(vert_angle, horiz_angle);
dest.set_xel(vert, horiz, candela);
}
}
dest_tex->load(dest, z, 0);
}