| 1 | // ============================================================ // |
|---|
| 2 | // // |
|---|
| 3 | // File : item_shader.h // |
|---|
| 4 | // Purpose : external interface of ITEM_SHADER // |
|---|
| 5 | // // |
|---|
| 6 | // Coded by Ralf Westram (coder@reallysoft.de) in June 2016 // |
|---|
| 7 | // http://www.arb-home.de/ // |
|---|
| 8 | // // |
|---|
| 9 | // ============================================================ // |
|---|
| 10 | |
|---|
| 11 | #ifndef ITEM_SHADER_H |
|---|
| 12 | #define ITEM_SHADER_H |
|---|
| 13 | |
|---|
| 14 | #ifndef SMARTPTR_H |
|---|
| 15 | #include <smartptr.h> |
|---|
| 16 | #endif |
|---|
| 17 | #ifndef ARB_MSG_H |
|---|
| 18 | #include <arb_msg.h> |
|---|
| 19 | #endif |
|---|
| 20 | #ifndef AW_BASE_HXX |
|---|
| 21 | #include <aw_base.hxx> |
|---|
| 22 | #endif |
|---|
| 23 | #ifndef ITEMS_H |
|---|
| 24 | #include <items.h> |
|---|
| 25 | #endif |
|---|
| 26 | #ifndef AW_COLOR_GROUPS_HXX |
|---|
| 27 | #include <aw_color_groups.hxx> |
|---|
| 28 | #endif |
|---|
| 29 | #ifndef _GLIBCXX_STRING |
|---|
| 30 | #include <string> |
|---|
| 31 | #endif |
|---|
| 32 | |
|---|
| 33 | #define is_assert(cond) arb_assert(cond) |
|---|
| 34 | |
|---|
| 35 | class GBDATA; |
|---|
| 36 | class AW_gc_manager; |
|---|
| 37 | |
|---|
| 38 | // ---------------- |
|---|
| 39 | // Phaser |
|---|
| 40 | |
|---|
| 41 | class Phaser { |
|---|
| 42 | float frequency; // 1.0 => [0.0 .. 1.0] gets mapped to whole color range; |
|---|
| 43 | // 2.0 => [0.0 .. 0.5] and [0.5 .. 1.0] each gets mapped to whole color range |
|---|
| 44 | |
|---|
| 45 | float preshift; // allowed values [0.0 .. 1.0]; applied BEFORE frequency mapping |
|---|
| 46 | float postshift; // allowed values [0.0 .. 1.0]; applied AFTER frequency mapping |
|---|
| 47 | |
|---|
| 48 | bool alternate; // if frequency>1.0 => use alternate mapping direction |
|---|
| 49 | |
|---|
| 50 | float result_lower_bound; // = result of rephase(0.0) |
|---|
| 51 | float result_upper_bound; // = result of rephase(1.0) |
|---|
| 52 | |
|---|
| 53 | static CONSTEXPR_INLINE bool is_normalized(const float& f) { |
|---|
| 54 | return f>=0.0 && f<=1.0; |
|---|
| 55 | } |
|---|
| 56 | static CONSTEXPR_INLINE float shift_and_wrap(const float& val, const float& shift, float wrapto) { |
|---|
| 57 | return shift>val ? val-shift+wrapto : val-shift; |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | float rephase_inbound(float f) const { |
|---|
| 61 | is_assert(f>=0.0 && f<=1.0); |
|---|
| 62 | |
|---|
| 63 | const float preshifted(shift_and_wrap(f, preshift, 1.0)); |
|---|
| 64 | const float blow(preshifted*frequency); |
|---|
| 65 | |
|---|
| 66 | int phase(blow); |
|---|
| 67 | float rest(blow-phase); |
|---|
| 68 | if (rest <= 0.0 && phase>0) { |
|---|
| 69 | rest = 1.0; |
|---|
| 70 | phase--; |
|---|
| 71 | } |
|---|
| 72 | |
|---|
| 73 | rest += postshift; |
|---|
| 74 | if (rest>1.0) { |
|---|
| 75 | rest -= 1.0; |
|---|
| 76 | phase++; |
|---|
| 77 | } |
|---|
| 78 | is_assert(is_normalized(rest)); |
|---|
| 79 | |
|---|
| 80 | const bool do_alter(alternate && (phase&1)); |
|---|
| 81 | const float altered(do_alter ? 1.0-rest : rest); |
|---|
| 82 | |
|---|
| 83 | #if defined(DEBUG) && 0 |
|---|
| 84 | fprintf(stderr, |
|---|
| 85 | "f=%f preshifted=%f blow=%f postshifted=%f phase=%i rest=%f do_alter=%i altered=%f\n", |
|---|
| 86 | f, preshifted, blow, postshifted, phase, rest, do_alter, altered); |
|---|
| 87 | #endif |
|---|
| 88 | |
|---|
| 89 | is_assert(is_normalized(altered)); |
|---|
| 90 | return altered; |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | void calc_bound_results() { |
|---|
| 94 | result_lower_bound = rephase_inbound(0.0); |
|---|
| 95 | result_upper_bound = rephase_inbound(1.0); |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | public: |
|---|
| 99 | Phaser() : // this Phaser does "nothing" |
|---|
| 100 | frequency(1.0), |
|---|
| 101 | preshift(0.0), |
|---|
| 102 | postshift(0.0), |
|---|
| 103 | alternate(false), |
|---|
| 104 | result_lower_bound(0.0), |
|---|
| 105 | result_upper_bound(1.0) |
|---|
| 106 | {} |
|---|
| 107 | |
|---|
| 108 | Phaser(float frequency_, bool alternate_, float preshift_, float postshift_) : |
|---|
| 109 | frequency(frequency_), |
|---|
| 110 | preshift(preshift_), |
|---|
| 111 | postshift(postshift_), |
|---|
| 112 | alternate(alternate_) |
|---|
| 113 | { |
|---|
| 114 | is_assert(is_normalized(preshift)); |
|---|
| 115 | is_assert(is_normalized(postshift)); |
|---|
| 116 | |
|---|
| 117 | calc_bound_results(); |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | float rephase(float f) const { |
|---|
| 121 | return |
|---|
| 122 | f<=0.0 |
|---|
| 123 | ? result_lower_bound |
|---|
| 124 | : (f>=1.0 |
|---|
| 125 | ? result_upper_bound |
|---|
| 126 | : rephase_inbound(f)); |
|---|
| 127 | } |
|---|
| 128 | }; |
|---|
| 129 | |
|---|
| 130 | // -------------------- |
|---|
| 131 | // ValueTuple |
|---|
| 132 | |
|---|
| 133 | class ValueTuple { |
|---|
| 134 | /*! Contains a value-tuple (used for shading items). |
|---|
| 135 | * |
|---|
| 136 | * Properties: |
|---|
| 137 | * - variable tuple-size (0-3) |
|---|
| 138 | * - values may be defined or not |
|---|
| 139 | * - values are normalized to [0.0 .. 1.0] |
|---|
| 140 | * - values can be mixed (using weights) |
|---|
| 141 | */ |
|---|
| 142 | |
|---|
| 143 | ValueTuple *undefined_reverse_mix() const { arb_assert(0); return NULp; } |
|---|
| 144 | |
|---|
| 145 | public: |
|---|
| 146 | typedef SmartPtr<ValueTuple> ShadedValue; |
|---|
| 147 | |
|---|
| 148 | virtual ~ValueTuple() {} |
|---|
| 149 | |
|---|
| 150 | virtual bool is_defined() const = 0; |
|---|
| 151 | virtual ShadedValue clone() const = 0; |
|---|
| 152 | virtual int range_offset(const Phaser&) const = 0; // returns int-offset into range [0 .. AW_RANGE_COLORS[ |
|---|
| 153 | |
|---|
| 154 | #if defined(UNIT_TESTS) |
|---|
| 155 | virtual const char *inspect() const = 0; |
|---|
| 156 | #endif |
|---|
| 157 | |
|---|
| 158 | // ValueTuple factory: |
|---|
| 159 | static ShadedValue undefined(); |
|---|
| 160 | static ShadedValue make(float f); |
|---|
| 161 | static ShadedValue make(float f1, float f2); |
|---|
| 162 | static ShadedValue make(float f1, float f2, float f3); |
|---|
| 163 | |
|---|
| 164 | // mix interface (main function + reverse visitors): |
|---|
| 165 | virtual ShadedValue mix(float my_ratio, const ValueTuple& other) const = 0; |
|---|
| 166 | virtual ShadedValue reverse_mix(float /*other_ratio*/, const class NoTuple& /*other*/) const { return undefined_reverse_mix(); } |
|---|
| 167 | virtual ShadedValue reverse_mix(float /*other_ratio*/, const class LinearTuple& /*other*/) const { return undefined_reverse_mix(); } |
|---|
| 168 | virtual ShadedValue reverse_mix(float /*other_ratio*/, const class PlanarTuple& /*other*/) const { return undefined_reverse_mix(); } |
|---|
| 169 | virtual ShadedValue reverse_mix(float /*other_ratio*/, const class SpatialTuple& /*other*/) const { return undefined_reverse_mix(); } |
|---|
| 170 | }; |
|---|
| 171 | |
|---|
| 172 | typedef ValueTuple::ShadedValue ShadedValue; |
|---|
| 173 | |
|---|
| 174 | inline ShadedValue mix(const ShadedValue& val1, float val1_ratio, const ShadedValue& val2) { |
|---|
| 175 | return val1->mix(val1_ratio, *val2); |
|---|
| 176 | } |
|---|
| 177 | |
|---|
| 178 | // -------------------------- |
|---|
| 179 | // ShaderPlugin |
|---|
| 180 | |
|---|
| 181 | class ItemShader; |
|---|
| 182 | |
|---|
| 183 | enum ReshadeMode { |
|---|
| 184 | SIMPLE_RESHADE = 0, |
|---|
| 185 | CHECK_DIMENSION_CHANGE = 1, |
|---|
| 186 | }; |
|---|
| 187 | |
|---|
| 188 | class ShaderPlugin { |
|---|
| 189 | RefPtr<ItemShader> plugged_into; |
|---|
| 190 | |
|---|
| 191 | std::string id; |
|---|
| 192 | std::string description; |
|---|
| 193 | std::string awar_prefix; // empty means "awars not initialized yet" |
|---|
| 194 | |
|---|
| 195 | virtual void init_specific_awars(AW_root *awr) = 0; |
|---|
| 196 | |
|---|
| 197 | protected: |
|---|
| 198 | const ItemShader *shader_plugged_into() const { return plugged_into; } |
|---|
| 199 | |
|---|
| 200 | public: |
|---|
| 201 | ShaderPlugin(const std::string& id_, const std::string& description_) : |
|---|
| 202 | plugged_into(NULp), |
|---|
| 203 | id(id_), |
|---|
| 204 | description(description_) |
|---|
| 205 | { |
|---|
| 206 | /*! construct a shader plugin |
|---|
| 207 | * @param id_ a unique id |
|---|
| 208 | * @param description_ description of plugin (ia. used as title of config window) |
|---|
| 209 | */ |
|---|
| 210 | } |
|---|
| 211 | virtual ~ShaderPlugin() {} |
|---|
| 212 | |
|---|
| 213 | void announce_shader(ItemShader *shader) { plugged_into = shader; } |
|---|
| 214 | |
|---|
| 215 | const std::string& get_id() const { return id; } |
|---|
| 216 | const std::string& get_description() const { return description; } |
|---|
| 217 | |
|---|
| 218 | inline const char *get_shader_local_id() const; |
|---|
| 219 | |
|---|
| 220 | void init_awars(AW_root *awr, const char *awar_prefix_); |
|---|
| 221 | const char *plugin_awar(const char *name) const { |
|---|
| 222 | is_assert(!awar_prefix.empty()); // forgot to call init_awars? |
|---|
| 223 | return GBS_global_string("%s/%s", awar_prefix.c_str(), name); |
|---|
| 224 | } |
|---|
| 225 | const char *dimension_awar(int dim, const char *name) const { |
|---|
| 226 | is_assert(!awar_prefix.empty()); // forgot to call init_awars? |
|---|
| 227 | is_assert(dim>=0 && dim<3); // invalid dimension specified |
|---|
| 228 | return GBS_global_string("%s/%s_%i", awar_prefix.c_str(), name, dim); |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | bool overlay_marked() const; // true if shader-plugin currently likes to display marked species in marked color |
|---|
| 232 | bool overlay_color_groups() const; // true if shader-plugin currently likes to display color groups |
|---|
| 233 | |
|---|
| 234 | virtual ShadedValue shade(GBDATA *gb_item) const = 0; |
|---|
| 235 | |
|---|
| 236 | virtual int get_dimension() const = 0; // returns (current) dimension of shader-plugin |
|---|
| 237 | |
|---|
| 238 | virtual bool customizable() const = 0; |
|---|
| 239 | virtual void customize(AW_root *awr) = 0; |
|---|
| 240 | |
|---|
| 241 | virtual char *store_config() const = 0; |
|---|
| 242 | virtual void load_or_reset_config(const char *cfgstr) = 0; |
|---|
| 243 | |
|---|
| 244 | virtual void activate(bool on) = 0; // called with true when plugin gets activated, with false when it gets deactivated |
|---|
| 245 | |
|---|
| 246 | void trigger_reshade_if_active_cb(ReshadeMode mode); |
|---|
| 247 | }; |
|---|
| 248 | |
|---|
| 249 | typedef SmartPtr<ShaderPlugin> ShaderPluginPtr; |
|---|
| 250 | |
|---|
| 251 | // -------------------- |
|---|
| 252 | // ItemShader |
|---|
| 253 | |
|---|
| 254 | #define NO_PLUGIN_SELECTED "" |
|---|
| 255 | |
|---|
| 256 | typedef void (*ReshadeCallback)(); |
|---|
| 257 | |
|---|
| 258 | class ItemShader { |
|---|
| 259 | std::string id; |
|---|
| 260 | std::string description; |
|---|
| 261 | ReshadeCallback reshade_cb; |
|---|
| 262 | |
|---|
| 263 | int undefined_gc; |
|---|
| 264 | |
|---|
| 265 | mutable int reshade_delay_level; |
|---|
| 266 | mutable bool reshade_was_suppressed; |
|---|
| 267 | |
|---|
| 268 | void delay_reshade_callbacks(bool suppress) const { |
|---|
| 269 | reshade_delay_level += suppress ? 1 : -1; |
|---|
| 270 | is_assert(reshade_delay_level>=0); |
|---|
| 271 | |
|---|
| 272 | if (!reshade_delay_level) { // switched off delay |
|---|
| 273 | if (reshade_was_suppressed) { |
|---|
| 274 | reshade_cb(); |
|---|
| 275 | reshade_was_suppressed = false; |
|---|
| 276 | } |
|---|
| 277 | } |
|---|
| 278 | |
|---|
| 279 | #if defined(ASSERTION_USED) |
|---|
| 280 | bool start_of_delay = reshade_delay_level == 1 && suppress; |
|---|
| 281 | is_assert(implicated(start_of_delay, !reshade_was_suppressed)); |
|---|
| 282 | #endif |
|---|
| 283 | } |
|---|
| 284 | friend class DelayReshade; |
|---|
| 285 | |
|---|
| 286 | protected: |
|---|
| 287 | ShaderPluginPtr active_plugin; // null means: no plugin active |
|---|
| 288 | int first_range_gc; // has to be set by init()! |
|---|
| 289 | Phaser phaser; |
|---|
| 290 | |
|---|
| 291 | public: |
|---|
| 292 | ItemShader(const std::string& id_, const std::string& description_, ReshadeCallback rcb, int undefined_gc_) : |
|---|
| 293 | id(id_), |
|---|
| 294 | description(description_), |
|---|
| 295 | reshade_cb(rcb), |
|---|
| 296 | undefined_gc(undefined_gc_), |
|---|
| 297 | reshade_delay_level(0), |
|---|
| 298 | reshade_was_suppressed(false), |
|---|
| 299 | first_range_gc(-1) |
|---|
| 300 | {} |
|---|
| 301 | virtual ~ItemShader() {} |
|---|
| 302 | |
|---|
| 303 | virtual void register_plugin(ShaderPluginPtr plugin) = 0; |
|---|
| 304 | virtual bool activate_plugin(const std::string& id) = 0; // returns 'true' on success |
|---|
| 305 | bool deactivate_plugin() { return activate_plugin(NO_PLUGIN_SELECTED); } |
|---|
| 306 | virtual std::string active_plugin_name() const = 0; |
|---|
| 307 | |
|---|
| 308 | bool is_active_plugin(const ShaderPlugin& plugin) const { |
|---|
| 309 | return active_plugin_name() == plugin.get_id(); |
|---|
| 310 | } |
|---|
| 311 | |
|---|
| 312 | virtual void init() = 0; // call once after register_plugin was called (activates plugin stored in AWAR) |
|---|
| 313 | virtual void popup_config_window(AW_root *awr) = 0; |
|---|
| 314 | |
|---|
| 315 | virtual void check_dimension_change() = 0; |
|---|
| 316 | |
|---|
| 317 | const std::string& get_id() const { return id; } |
|---|
| 318 | const std::string& get_description() const { return description; } |
|---|
| 319 | |
|---|
| 320 | bool active() const { return active_plugin.isSet(); } |
|---|
| 321 | bool overlay_marked() const { return !active() || active_plugin->overlay_marked(); } // if true, caller should use marked-GC |
|---|
| 322 | bool overlay_color_groups() const { return active() ? active_plugin->overlay_color_groups() : AW_color_groups_active(); } // if true, caller should use color-groups-GCs |
|---|
| 323 | |
|---|
| 324 | ShadedValue shade(GBDATA *gb_item) const { |
|---|
| 325 | is_assert(active()); // don't call if no shader is active |
|---|
| 326 | return active() ? active_plugin->shade(gb_item) : ValueTuple::undefined(); |
|---|
| 327 | } |
|---|
| 328 | int to_GC(const ShadedValue& val) const { |
|---|
| 329 | is_assert(first_range_gc>0); |
|---|
| 330 | if (val->is_defined()) { |
|---|
| 331 | return first_range_gc + val->range_offset(phaser); |
|---|
| 332 | } |
|---|
| 333 | return undefined_gc; |
|---|
| 334 | } |
|---|
| 335 | |
|---|
| 336 | void trigger_reshade_callback(ReshadeMode mode) { |
|---|
| 337 | if (mode == CHECK_DIMENSION_CHANGE) check_dimension_change(); |
|---|
| 338 | |
|---|
| 339 | if (reshade_delay_level) reshade_was_suppressed = true; |
|---|
| 340 | else reshade_cb(); |
|---|
| 341 | } |
|---|
| 342 | }; |
|---|
| 343 | |
|---|
| 344 | class DelayReshade : virtual Noncopyable { |
|---|
| 345 | const ItemShader *shader; |
|---|
| 346 | public: |
|---|
| 347 | DelayReshade(const ItemShader *shader_) : |
|---|
| 348 | shader(shader_) |
|---|
| 349 | { |
|---|
| 350 | shader->delay_reshade_callbacks(true); |
|---|
| 351 | } |
|---|
| 352 | ~DelayReshade() { |
|---|
| 353 | shader->delay_reshade_callbacks(false); |
|---|
| 354 | } |
|---|
| 355 | }; |
|---|
| 356 | |
|---|
| 357 | inline const char *ShaderPlugin::get_shader_local_id() const { |
|---|
| 358 | is_assert(plugged_into); |
|---|
| 359 | return GBS_global_string("%s_%s", plugged_into->get_id().c_str(), get_id().c_str()); |
|---|
| 360 | } |
|---|
| 361 | inline void ShaderPlugin::trigger_reshade_if_active_cb(ReshadeMode mode) { |
|---|
| 362 | if (plugged_into && plugged_into->is_active_plugin(*this)) { |
|---|
| 363 | plugged_into->trigger_reshade_callback(mode); |
|---|
| 364 | } |
|---|
| 365 | } |
|---|
| 366 | |
|---|
| 367 | // ----------------------------- |
|---|
| 368 | // ItemShader registry |
|---|
| 369 | |
|---|
| 370 | ItemShader *registerItemShader(AW_root *awr, AW_gc_manager *gcman, BoundItemSel& itemtype, const char *unique_id, const char *description, const char *help_id, ReshadeCallback reshade, int undef_gc); |
|---|
| 371 | |
|---|
| 372 | #else |
|---|
| 373 | #error item_shader.h included twice |
|---|
| 374 | #endif // ITEM_SHADER_H |
|---|