/* * Copyright 2011-2018 Blender Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "util/util_foreach.h" #include "util/util_ies.h" #include "util/util_math.h" #include "util/util_string.h" CCL_NAMESPACE_BEGIN // NOTE: For some reason gcc-7.2 does not instantiate this versio of allocator // gere (used in IESTextParser). Works fine for gcc-6, gcc-7.3 and gcc-8. // // TODO(sergey): Get to the root of this issue, or confirm this i a compiler // issue. template class GuardedAllocator; bool IESFile::load(ustring ies) { clear(); if(!parse(ies) || !process()) { clear(); return false; } return true; } void IESFile::clear() { intensity.clear(); v_angles.clear(); h_angles.clear(); } int IESFile::packed_size() { if(v_angles.size() && h_angles.size() > 0) { return 2 + h_angles.size() + v_angles.size() + h_angles.size()*v_angles.size(); } return 0; } void IESFile::pack(float *data) { if(v_angles.size() && h_angles.size()) { *(data++) = __int_as_float(h_angles.size()); *(data++) = __int_as_float(v_angles.size()); memcpy(data, &h_angles[0], h_angles.size()*sizeof(float)); data += h_angles.size(); memcpy(data, &v_angles[0], v_angles.size()*sizeof(float)); data += v_angles.size(); for(int h = 0; h < intensity.size(); h++) { memcpy(data, &intensity[h][0], v_angles.size()*sizeof(float)); data += v_angles.size(); } } } class IESTextParser { public: vector text; char *data; IESTextParser(ustring str) : text(str.begin(), str.end()) { std::replace(text.begin(), text.end(), ',', ' '); data = strstr(&text[0], "\nTILT="); } bool eof() { return (data == NULL) || (data[0] == '\0'); } double get_double() { if(eof()) { return 0.0; } char *old_data = data; double val = strtod(data, &data); if(data == old_data) { data = NULL; return 0.0; } return val; } long get_long() { if(eof()) { return 0; } char *old_data = data; long val = strtol(data, &data, 10); if(data == old_data) { data = NULL; return 0; } return val; } }; bool IESFile::parse(ustring ies) { if(ies.empty()) { return false; } IESTextParser parser(ies); if(parser.eof()) { return false; } /* Handle the tilt data block. */ if(strncmp(parser.data, "\nTILT=INCLUDE", 13) == 0) { parser.data += 13; parser.get_double(); /* Lamp to Luminaire geometry */ int num_tilt = parser.get_long(); /* Amount of tilt angles and factors */ /* Skip over angles and factors. */ for(int i = 0; i < 2*num_tilt; i++) { parser.get_double(); } } else { /* Skip to next line. */ parser.data = strstr(parser.data+1, "\n"); } if(parser.eof()) { return false; } parser.data++; parser.get_long(); /* Number of lamps */ parser.get_double(); /* Lumens per lamp */ double factor = parser.get_double(); /* Candela multiplier */ int v_angles_num = parser.get_long(); /* Number of vertical angles */ int h_angles_num = parser.get_long(); /* Number of horizontal angles */ type = (IESType) parser.get_long(); /* Photometric type */ /* TODO(lukas): Test whether the current type B processing can also deal with type A files. * In theory the only difference should be orientation which we ignore anyways, but with IES you never know... */ if(type != TYPE_B && type != TYPE_C) { return false; } parser.get_long(); /* Unit of the geometry data */ parser.get_double(); /* Width */ parser.get_double(); /* Length */ parser.get_double(); /* Height */ factor *= parser.get_double(); /* Ballast factor */ factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */ parser.get_double(); /* Input Watts */ /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity. * Cycles expects radiometric quantities, though, which requires a conversion. * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution * of the light source since lumens take human perception into account. * Since this spectral distribution is not known from the IES file, a typical one must be assumed. * The D65 standard illuminant has a Luminous efficacy of 177.83, which is used here to convert to Watt/sr. * A more advanced approach would be to add a Blackbody Temperature input to the node and numerically * integrate the Luminous efficacy from the resulting spectral distribution. * Also, the Watt/sr value must be multiplied by 4*pi to get the Watt value that Cycles expects * for lamp strength. Therefore, the conversion here uses 4*pi/177.83 as a Candela to Watt factor. */ factor *= 0.0706650768394; v_angles.reserve(v_angles_num); for(int i = 0; i < v_angles_num; i++) { v_angles.push_back((float) parser.get_double()); } h_angles.reserve(h_angles_num); for(int i = 0; i < h_angles_num; i++) { h_angles.push_back((float) parser.get_double()); } intensity.resize(h_angles_num); for(int i = 0; i < h_angles_num; i++) { intensity[i].reserve(v_angles_num); for(int j = 0; j < v_angles_num; j++) { intensity[i].push_back((float) (factor * parser.get_double())); } } return !parser.eof(); } bool IESFile::process_type_b() { vector > newintensity; newintensity.resize(v_angles.size()); for(int i = 0; i < v_angles.size(); i++) { newintensity[i].reserve(h_angles.size()); for(int j = 0; j < h_angles.size(); j++) { newintensity[i].push_back(intensity[j][i]); } } intensity.swap(newintensity); h_angles.swap(v_angles); float h_first = h_angles[0], h_last = h_angles[h_angles.size()-1]; if(h_last != 90.0f) { return false; } if(h_first == 0.0f) { /* The range in the file corresponds to 90°-180°, we need to mirror that to get the * full 180° range. */ vector new_h_angles; vector > new_intensity; int hnum = h_angles.size(); new_h_angles.reserve(2*hnum-1); new_intensity.reserve(2*hnum-1); for(int i = hnum-1; i > 0; i--) { new_h_angles.push_back(90.0f - h_angles[i]); new_intensity.push_back(intensity[i]); } for(int i = 0; i < hnum; i++) { new_h_angles.push_back(90.0f + h_angles[i]); new_intensity.push_back(intensity[i]); } h_angles.swap(new_h_angles); intensity.swap(new_intensity); } else if(h_first == -90.0f) { /* We have full 180° coverage, so just shift to match the angle range convention. */ for(int i = 0; i < h_angles.size(); i++) { h_angles[i] += 90.0f; } } /* To get correct results with the cubic interpolation in the kernel, the horizontal range * has to cover all 360°. Therefore, we copy the 0° entry to 360° to ensure full coverage * and seamless interpolation. */ h_angles.push_back(360.0f); intensity.push_back(intensity[0]); float v_first = v_angles[0], v_last = v_angles[v_angles.size()-1]; if(v_last != 90.0f) { return false; } if(v_first == 0.0f) { /* The range in the file corresponds to 90°-180°, we need to mirror that to get the * full 180° range. */ vector new_v_angles; int hnum = h_angles.size(); int vnum = v_angles.size(); new_v_angles.reserve(2*vnum-1); for(int i = vnum-1; i > 0; i--) { new_v_angles.push_back(90.0f - v_angles[i]); } for(int i = 0; i < vnum; i++) { new_v_angles.push_back(90.0f + v_angles[i]); } for(int i = 0; i < hnum; i++) { vector new_intensity; new_intensity.reserve(2*vnum-1); for(int j = vnum-2; j >= 0; j--) { new_intensity.push_back(intensity[i][j]); } new_intensity.insert(new_intensity.end(), intensity[i].begin(), intensity[i].end()); intensity[i].swap(new_intensity); } v_angles.swap(new_v_angles); } else if(v_first == -90.0f) { /* We have full 180° coverage, so just shift to match the angle range convention. */ for(int i = 0; i < v_angles.size(); i++) { v_angles[i] += 90.0f; } } return true; } bool IESFile::process_type_c() { if(h_angles[0] == 90.0f) { /* Some files are stored from 90° to 270°, so we just rotate them to the regular 0°-180° range here. */ for(int i = 0; i < v_angles.size(); i++) { h_angles[i] -= 90.0f; } } if(h_angles[0] != 0.0f) { return false; } if(h_angles.size() == 1) { h_angles.push_back(360.0f); intensity.push_back(intensity[0]); } if(h_angles[h_angles.size()-1] == 90.0f) { /* Only one quadrant is defined, so we need to mirror twice (from one to two, then to four). * Since the two->four mirroring step might also be required if we get an input of two quadrants, * we only do the first mirror here and later do the second mirror in either case. */ int hnum = h_angles.size(); for(int i = hnum-2; i >= 0; i--) { h_angles.push_back(180.0f - h_angles[i]); intensity.push_back(intensity[i]); } } if(h_angles[h_angles.size()-1] == 180.0f) { /* Mirror half to the full range. */ int hnum = h_angles.size(); for(int i = hnum-2; i >= 0; i--) { h_angles.push_back(360.0f - h_angles[i]); intensity.push_back(intensity[i]); } } /* Some files skip the 360° entry (contrary to standard) because it's supposed to be identical to the 0° entry. * If the file has a discernible order in its spacing, just fix this. */ if(h_angles[h_angles.size()-1] != 360.0f) { int hnum = h_angles.size(); float last_step = h_angles[hnum-1]-h_angles[hnum-2]; float first_step = h_angles[1]-h_angles[0]; float difference = 360.0f - h_angles[hnum-1]; if(last_step == difference || first_step == difference) { h_angles.push_back(360.0f); intensity.push_back(intensity[0]); } else { return false; } } float v_first = v_angles[0], v_last = v_angles[v_angles.size()-1]; if(v_first == 90.0f) { if(v_last == 180.0f) { /* Flip to ensure that vertical angles always start at 0°. */ for(int i = 0; i < v_angles.size(); i++) { v_angles[i] = 180.0f - v_angles[i]; } } else { return false; } } else if(v_first != 0.0f) { return false; } return true; } bool IESFile::process() { if(h_angles.size() == 0 || v_angles.size() == 0) { return false; } if(type == TYPE_B) { if(!process_type_b()) { return false; } } else { assert(type == TYPE_C); if(!process_type_c()) { return false; } } assert(v_angles[0] == 0.0f); assert(h_angles[0] == 0.0f); assert(h_angles[h_angles.size()-1] == 360.0f); /* Convert from deg to rad. */ for(int i = 0; i < v_angles.size(); i++) { v_angles[i] *= M_PI_F / 180.f; } for(int i = 0; i < h_angles.size(); i++) { h_angles[i] *= M_PI_F / 180.f; } return true; } IESFile::~IESFile() { clear(); } CCL_NAMESPACE_END