mirror of
https://github.com/schrodinger/pymol-open-source.git
synced 2026-06-04 20:04:21 +08:00
199 lines
4.9 KiB
C++
199 lines
4.9 KiB
C++
/*
|
|
* Color picking
|
|
*
|
|
* (C) Schrodinger, Inc.
|
|
*/
|
|
|
|
#include "Picking.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
|
|
constexpr auto MAX_BITS = 8u;
|
|
|
|
/**
|
|
* Value to put in the "unused bits" part of a color channel.
|
|
* May serve several purposes:
|
|
* - validation (check bits)
|
|
* - no-pick vs. through-pick
|
|
* - rounding
|
|
*
|
|
* Disclaimer: I'm not sure if rounding is ever needed and if rounding and
|
|
* validation should be mutually exclusive.
|
|
*/
|
|
constexpr unsigned make_check_value(unsigned bits)
|
|
{
|
|
return 0x80u >> bits;
|
|
}
|
|
|
|
/**
|
|
* Return true if this RGBA color looks valid, and false if it should
|
|
* be ignored.
|
|
*/
|
|
bool PickColorConverter::validateCheckBits(const channel_t* rgba) const
|
|
{
|
|
for (unsigned i = 0; i != 4; ++i) {
|
|
assert(m_rgba_and_check_bits[i] >= m_rgba_bits[i]);
|
|
|
|
// mask to stamp out the validation bits
|
|
const channel_t check_mask = (0xFFu >> m_rgba_bits[i]) & //
|
|
~(0xFFu >> m_rgba_and_check_bits[i]);
|
|
|
|
const channel_t check_value = make_check_value(m_rgba_bits[i]);
|
|
|
|
if ((rgba[i] & check_mask) != (check_value & check_mask)) {
|
|
// found antialiased bit (or cPickableNoPick/cPickableThrough which can
|
|
// also be ignored)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set the number of bits per picking channel
|
|
* @param rgba_bits Number of available bits per channel
|
|
* @param max_check_bits Maximum number of bits (per channel) to use for
|
|
* validation
|
|
*/
|
|
void PickColorConverter::setRgbaBits(const int* rgba_bits, int max_check_bits)
|
|
{
|
|
for (unsigned i = 0; i != 4; ++i) {
|
|
m_rgba_bits[i] = std::min<unsigned>(MAX_BITS, rgba_bits[i]);
|
|
|
|
// Use at most half of the pixels for validation
|
|
int const check_bits = std::min(m_rgba_bits[i] / 2, max_check_bits);
|
|
|
|
m_rgba_and_check_bits[i] = m_rgba_bits[i];
|
|
m_rgba_bits[i] = std::max(0, int(m_rgba_bits[i]) - check_bits);
|
|
}
|
|
|
|
// Use one bit to distinguish no-pick and through-pick.
|
|
// This bit only has to reach the shader, it's not relevant for the
|
|
// output buffer. This means it can be part of the check bits, but
|
|
// doesn't have to be. It can also be beyond the color depth of the
|
|
// output buffer (In theory, I have no suitable system to test this
|
|
// assumption).
|
|
m_rgba_bits[3] = std::min<unsigned>(MAX_BITS - 1, m_rgba_bits[3]);
|
|
}
|
|
|
|
/**
|
|
* Get the picking index from color
|
|
* @param rgba RGBA color
|
|
* @return Picking index
|
|
*/
|
|
PickColorConverter::index_t PickColorConverter::indexFromColor(
|
|
const channel_t* rgba) const
|
|
{
|
|
if (!validateCheckBits(rgba)) {
|
|
return 0;
|
|
}
|
|
|
|
unsigned idx = 0;
|
|
unsigned bits = 0;
|
|
for (unsigned i = 0; i != 4; ++i) {
|
|
idx |= (unsigned(rgba[i]) >> (MAX_BITS - m_rgba_bits[i])) << bits;
|
|
bits += m_rgba_bits[i];
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/**
|
|
* Get color from picking index
|
|
* @param[out] rgba RGBA color
|
|
* @param idx Picking index
|
|
*/
|
|
void PickColorConverter::colorFromIndex(channel_t* rgba, index_t idx) const
|
|
{
|
|
unsigned bits = 0;
|
|
for (unsigned i = 0; i != 4; ++i) {
|
|
rgba[i] = ((idx >> bits) & 0xFFu) << (MAX_BITS - m_rgba_bits[i]);
|
|
|
|
rgba[i] |= make_check_value(m_rgba_bits[i]);
|
|
|
|
bits += m_rgba_bits[i];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get no-pick color
|
|
* @param[out] rgba RGBA color
|
|
*/
|
|
void PickColorConverter::colorNoPick(channel_t* rgba) const
|
|
{
|
|
rgba[0] = 0;
|
|
rgba[1] = 0;
|
|
rgba[2] = 0;
|
|
rgba[3] = make_check_value(m_rgba_bits[3]);
|
|
assert(rgba[3] != 0);
|
|
}
|
|
|
|
/**
|
|
* Get pick-through color
|
|
* @param[out] rgba RGBA color
|
|
*
|
|
* Note: This only works if the shader discards full-transparent pixels (e.g. may not
|
|
* work in immediate mode)
|
|
*/
|
|
void PickColorConverter::colorPickThrough(channel_t* rgba) const
|
|
{
|
|
rgba[0] = 0;
|
|
rgba[1] = 0;
|
|
rgba[2] = 0;
|
|
rgba[3] = 0;
|
|
}
|
|
|
|
/**
|
|
* Get the color for (context, index, bond, current picking pass).
|
|
*/
|
|
void PickColorManager::colorNext(unsigned char* color,
|
|
const PickContext* context, unsigned int index, int bond)
|
|
{
|
|
if (bond == cPickableNoPick) {
|
|
colorNoPick(color);
|
|
return;
|
|
}
|
|
|
|
if (bond == cPickableThrough) {
|
|
// TODO has no effect on immediate mode (fragment not discarded)
|
|
colorPickThrough(color);
|
|
return;
|
|
}
|
|
|
|
const Picking p_new = {{index, bond}, *context};
|
|
|
|
assert(m_count <= m_identifiers.size());
|
|
|
|
// compare with previous pick color, increment if different
|
|
if (m_count == 0 || m_identifiers[m_count - 1] != p_new) {
|
|
++m_count;
|
|
}
|
|
|
|
unsigned j = m_count;
|
|
|
|
if (m_pass > 0) {
|
|
assert(m_count <= m_identifiers.size());
|
|
j >>= getTotalBits() * m_pass;
|
|
} else if (m_count == m_identifiers.size() + 1) {
|
|
m_identifiers.push_back(p_new);
|
|
}
|
|
|
|
// if this assertion fails, then invalidate() was not called, but should be
|
|
assert(m_identifiers[m_count - 1] == p_new);
|
|
|
|
colorFromIndex(color, j);
|
|
}
|
|
|
|
/**
|
|
* Get identifier for the 1-based picking color index.
|
|
* Returns nullptr if the index is out of bounds.
|
|
*/
|
|
const Picking* PickColorManager::getIdentifier(unsigned index) const
|
|
{
|
|
if (index > 0 && index <= m_identifiers.size()) {
|
|
return m_identifiers.data() + index - 1;
|
|
}
|
|
return nullptr;
|
|
}
|