//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// #pragma once #include "BsEditorPrerequisites.h" #include "Math/BsMath.h" namespace bs { /** @addtogroup GUI-Editor * @{ */ /** Determines how should ticks reported by be distributed. */ enum class BS_SCRIPT_EXPORT(m:GUIEditor,api:bed) GUITickStepType { /** Ticks represent time values (Multiples of 60). */ Time, /** Ticks represent generic values (Multiples of 10). */ Generic }; /** * Generates a set of locations that can be used for rendering ticks on a graph. As input the class takes valid range, * size of the area the ticks will be displayed on, type of ticks and minimum/maximum spacing and outputs a set of * coordinates along which ticks should be positioned. Ticks are reported as multiple separate levels with different * strengths, depending on how close their distribution is to the upper valid range. */ class BS_SCRIPT_EXPORT(m:GUIEditor,api:bed) GUIGraphTicks { public: /** * Contructs a new tick generating object. * * @param[in] stepType Determines how will ticks be distributed. */ BS_SCRIPT_EXPORT() GUIGraphTicks(GUITickStepType stepType = GUITickStepType::Generic) { if(stepType == GUITickStepType::Generic) setGenericSteps(); else setTimeSteps(); rebuild(); } /** Number of tick levels that will be generated. */ BS_SCRIPT_EXPORT(pr:getter,n:NumLevels) UINT32 getNumLevels() const { return mNumLevels; } /** * Sets the range which ticks are to be displayed for, and the range along which the ticks will be displayed. * * @param[in] valueRangeStart Start of the range the ticks are to display. * @param[in] valueRangeEnd End of the range the ticks are to display. * @param[in] pixelRange Width or height on which the ticks will be rendered. In pixels. */ BS_SCRIPT_EXPORT() void setRange(float valueRangeStart, float valueRangeEnd, UINT32 pixelRange) { this->mValueRangeStart = valueRangeStart; this->mValueRangeEnd = valueRangeEnd; this->mPixelRange = pixelRange; rebuild(); } /** * Sets valid spacing between two ticks. Tick strength will be determined by how far away are they from either * end of this range. * * @param[in] minPx Minimum spacing between two ticks, in pixels. * @param[in] maxPx Maximum spacing between two ticks, in pixels. */ BS_SCRIPT_EXPORT() void setTickSpacing(int minPx, int maxPx) { mMinTickSpacingPx = minPx; mMaxTickSpacingPx = maxPx; rebuild(); } /** * Returns the strength of a particular tick level. Levels are ordered in descending order of strength (level 0 is * the strongest). * * @param[in] level Level for which to retrieve the strength. Must not be larger than getNumLevels() - 1. * @return Strength of the ticks at this level, in range [0, 1]. */ BS_SCRIPT_EXPORT() float getLevelStrength(UINT32 level) { if (level >= mNumLevels) return 0.0f; return Math::clamp01(mLevelStrengths[mMaxLevel + level]); } /** * Returns positions of all ticks of the provided level. The ticks will be within the range provided to setRange(). * * @param[in] level Level for which to retrieve the positions. Must not be larger than getNumLevels() - 1. * @return Positions of all ticks of the provided level. */ BS_SCRIPT_EXPORT() Vector getTicks(UINT32 level) { if (level < 0 || level >= mNumLevels) return { }; const float step = mValidSteps[mMaxLevel + level]; // Round up and down so we get one extra tick on either side (outside of value range) // (Useful when rendering text above the ticks, so the text doesn't just pop-in when the tick appears, instead // it is slowly clipped-in.) const INT32 startTick = Math::ceilToInt(mValueRangeStart / step); const INT32 endTick = Math::floorToInt(mValueRangeEnd / step); const INT32 numTicks = endTick - startTick + 1; Vector ticks(numTicks); for (INT32 i = startTick; i <= endTick; i++) ticks[i - startTick] = i*step; return ticks; } protected: /** Rebuilds the tick positions and strengths after some relevant parameter changes. */ void rebuild() { mLevelStrengths = Vector(mValidSteps.size()); mMaxLevel = 0; const float valueRange = mValueRangeEnd - mValueRangeStart; const INT32 tickSpacing = (INT32)mMaxTickSpacingPx - (INT32)mMinTickSpacingPx; UINT32 i = 0; for (; i < (UINT32)mValidSteps.size(); i++) { const float tickSpacingPx = (mValidSteps[i]/valueRange) * mPixelRange; mLevelStrengths[i] = (tickSpacingPx - mMinTickSpacingPx)/tickSpacing; if (mLevelStrengths[i] > 1.0f) mMaxLevel = i; else if (mLevelStrengths[i] < 0.0f) break; } if (i > 0) mNumLevels = i - mMaxLevel; else mNumLevels = 0; } /** * Sets tick steps corresponding to time values. This will split the ticks into intervals relevant for displaying * times. */ void setTimeSteps() { mValidSteps = { 3600.0f, 1800.0f, 600.0f, 300.0f, 60.0f, 30.0f, 10.0f, 5.0f, 1.0f, 0.5f, 0.25f, 0.1f, 0.05f, 0.01f }; } /** Sets tick steps corresponding to generic values (as opposed to displaying time values). */ void setGenericSteps() { constexpr UINT32 numSteps = 15; float minStep = 0.0000001f; mValidSteps = Vector(numSteps * 2); for (int i = numSteps - 1; i >= 0; i--) { mValidSteps[i * 2 + 1] = minStep; mValidSteps[i * 2 + 0] = minStep * 5; minStep *= 10.0f; } } UINT32 mPixelRange = 100; float mValueRangeStart = 0.0f; float mValueRangeEnd = 1.0f; UINT32 mMinTickSpacingPx = 5; UINT32 mMaxTickSpacingPx = 30; Vector mValidSteps = { 1.0f }; Vector mLevelStrengths = { 1.0f }; UINT32 mNumLevels = 1; UINT32 mMaxLevel = 0; }; /** @} */ }