/* * Copyright (C) EdgeTX * * Based on code named * opentx - https://github.com/opentx/opentx * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "edgetx.h" extern int32_t getSourceNumFieldValue(int16_t val, int16_t min, int16_t max); constexpr int DEFAULT_POINTS = 5; constexpr int STD_CURVE_POINTS(int p) { return p + DEFAULT_POINTS; } constexpr int CUSTOM_CURVE_POINTS(int p) { return 2 * p + (2 * DEFAULT_POINTS - 2); } int8_t * curveEnd[MAX_CURVES]; uint8_t getCurvePoints(uint8_t index) { if (index >= MAX_CURVES) return 0; const auto& curve = g_model.curves[index]; if (curve.type == CURVE_TYPE_STANDARD) return STD_CURVE_POINTS(curve.points); else if (curve.type == CURVE_TYPE_CUSTOM) return CUSTOM_CURVE_POINTS(curve.points); return 0; } void loadCurves() { bool showWarning= false; int8_t * tmp = g_model.points; for (int i=0; i maxend) { tmp = maxend; g_model.curves[i].type=CURVE_TYPE_STANDARD; g_model.curves[i].points=-3; showWarning=true; } curveEnd[i] = tmp; } if (showWarning) { POPUP_WARNING("Invalid curve data repaired", "check your curves, logic switches"); } } int8_t * curveAddress(uint8_t idx) { return idx==0 ? g_model.points : curveEnd[idx-1]; } static void curveMove_unsafe(uint8_t index, int8_t shift) { int8_t* src = curveAddress(index+1); int8_t* dst = src + shift; size_t points = curveEnd[MAX_CURVES-1] - curveEnd[index] + getCurvePoints(MAX_CURVES-1); // points in the last curve memmove(dst, src, points); if (shift < 0) { // clear points at the tail memclear(dst + points, src - dst); } while (index < MAX_CURVES) { curveEnd[index++] += shift; } } bool moveCurve(uint8_t index, int8_t shift) { if (curveEnd[MAX_CURVES-1] + shift > g_model.points + sizeof(g_model.points)) { AUDIO_WARNING2(); return false; } curveMove_unsafe(index, shift); storageDirty(EE_MODEL); return true; } int8_t getCurveX(int noPoints, int point) { return -100 + divRoundClosest((point*2000) / (noPoints-1), 10); } void resetCustomCurveX(int8_t * points, int noPoints) { for (int i=0; i= MAX_CURVES) return; CurveHeader * curve = &g_model.curves[index]; int8_t * curvePoints = curveAddress(index); uint8_t nPoints = getCurvePoints(index); memclear(curvePoints, nPoints); memclear(curve, sizeof(CurveHeader)); uint8_t newPoints = getCurvePoints(index); int8_t shift = newPoints - nPoints; if (shift != 0) { curveMove_unsafe(index, shift); } } void curveMirror(uint8_t index) { if (index >= MAX_CURVES) return; CurveHeader & curve = g_model.curves[index]; int8_t * points = curveAddress(index); // we only mirror Y axis: X axis does not change for (int i = 0; i < STD_CURVE_POINTS(curve.points); i++) points[i] = -points[i]; } bool isCurveUsed(uint8_t index) { return !is_memclear(&g_model.curves[index], sizeof(CurveHeader)) || !is_memclear(curveAddress(index), DEFAULT_POINTS); } #define CUSTOM_POINT_X(points, count, idx) \ ((idx) == 0 ? -100 : (((idx) == (count)-1) ? 100 : points[(count) + (idx)-1])) int32_t compute_tangent(CurveHeader* crv, const int8_t* points, int i) { int32_t m = 0; uint8_t num_points = STD_CURVE_POINTS(crv->points); #define MMULT 1024 if (i == 0) { // linear interpolation between 1st 2 points // keep 3 decimal-places for m if (crv->type == CURVE_TYPE_CUSTOM) { int8_t x0 = CUSTOM_POINT_X(points, num_points, 0); int8_t x1 = CUSTOM_POINT_X(points, num_points, 1); if (x1 > x0) m = (MMULT * (points[1] - points[0])) / (x1 - x0); } else { int32_t delta = (2 * 100) / (num_points - 1); m = (MMULT * (points[1] - points[0])) / delta; } } else if (i == num_points - 1) { // linear interpolation between last 2 points // keep 3 decimal-places for m if (crv->type == CURVE_TYPE_CUSTOM) { int8_t x0 = CUSTOM_POINT_X(points, num_points, num_points - 2); int8_t x1 = CUSTOM_POINT_X(points, num_points, num_points - 1); if (x1 > x0) m = (MMULT * (points[num_points - 1] - points[num_points - 2])) / (x1 - x0); } else { int32_t delta = (2 * 100) / (num_points - 1); m = (MMULT * (points[num_points - 1] - points[num_points - 2])) / delta; } } else { // apply monotone rules from // http://en.wikipedia.org/wiki/Monotone_cubic_interpolation // 1) compute slopes of secant lines int32_t d0 = 0, d1 = 0; if (crv->type == CURVE_TYPE_CUSTOM) { int8_t x0 = CUSTOM_POINT_X(points, num_points, i - 1); int8_t x1 = CUSTOM_POINT_X(points, num_points, i); int8_t x2 = CUSTOM_POINT_X(points, num_points, i + 1); if (x1 > x0) d0 = (MMULT * (points[i] - points[i - 1])) / (x1 - x0); if (x2 > x1) d1 = (MMULT * (points[i + 1] - points[i])) / (x2 - x1); } else { int32_t delta = (2 * 100) / (num_points - 1); d0 = (MMULT * (points[i] - points[i - 1])) / (delta); d1 = (MMULT * (points[i + 1] - points[i])) / (delta); } // 2) compute initial average tangent m = (d0 + d1) / 2; // 3 check for horizontal lines if (d0 == 0 || d1 == 0 || (d0 > 0 && d1 < 0) || (d0 < 0 && d1 > 0)) { m = 0; } else if (MMULT * m / d0 > 3 * MMULT) { m = 3 * d0; } else if (MMULT * m / d1 > 3 * MMULT) { m = 3 * d1; } } return m; } /* The following is a hermite cubic spline. The basis functions can be found here: http://en.wikipedia.org/wiki/Cubic_Hermite_spline The tangents are computed via the 'cubic monotone' rules (allowing for local-maxima) */ int16_t hermite_spline(int16_t x, uint8_t idx) { CurveHeader &crv = g_model.curves[idx]; int8_t *points = curveAddress(idx); uint8_t count = STD_CURVE_POINTS(crv.points); bool custom = (crv.type == CURVE_TYPE_CUSTOM); if (x < -RESX) x = -RESX; else if (x > RESX) x = RESX; for (int i=0; i0 ? calc100toRESX(points[count+i-1]) : -RESX); p3x = (i= p0x && x <= p3x) { int32_t p0y = calc100toRESX(points[i]); int32_t p3y = calc100toRESX(points[i+1]); int32_t m0 = compute_tangent(&crv, points, i); int32_t m3 = compute_tangent(&crv, points, i+1); int32_t y; int32_t h = p3x - p0x; int32_t t = (h > 0 ? (MMULT * (x - p0x)) / h : 0); int32_t t2 = t * t / MMULT; int32_t t3 = t2 * t / MMULT; int32_t h00 = 2*t3 - 3*t2 + MMULT; int32_t h10 = t3 - 2*t2 + t; int32_t h01 = -2*t3 + 3*t2; int32_t h11 = t3 - t2; y = p0y * h00 + h * (m0 * h10 / MMULT) + p3y * h01 + h * (m3 * h11 / MMULT); y /= MMULT; return y; } } return 0; } int intpol(int x, uint8_t idx) // -100, -75, -50, -25, 0 ,25 ,50, 75, 100 { CurveHeader& crv = g_model.curves[idx]; int8_t* points = curveAddress(idx); uint8_t count = STD_CURVE_POINTS(crv.points); bool custom = (crv.type == CURVE_TYPE_CUSTOM); int16_t erg; x += RESXu; if (x <= 0) { erg = (int16_t)points[0] * (RESX / 4); } else if (x >= (RESX * 2)) { erg = (int16_t)points[count - 1] * (RESX / 4); } else { uint16_t a = 0, b = 0; uint8_t i; if (custom) { for (i = 0; i < count - 1; i++) { a = b; b = (i == count - 2 ? 2 * RESX : RESX + calc100toRESX(points[count + i])); if ((uint16_t)x <= b) break; } } else { uint16_t d = (RESX * 2) / (count - 1); i = (uint16_t)x / d; a = i * d; b = a + d; } erg = (int16_t)points[i] * (RESX / 4) + ((int32_t)(x - a) * (points[i + 1] - points[i]) * (RESX / 4)) / ((b - a)); } return erg / 25; // 100*D5/RESX; } int applyCurve(int x, CurveRef & curve) { SourceNumVal v; v.rawValue = curve.value; switch (curve.type) { case CURVE_REF_DIFF: { int curveParam = getSourceNumFieldValue(curve.value, -100, 100); if (curveParam > 0 && x < 0) x = (x * (1000 - curveParam)) / 1000; else if (curveParam < 0 && x > 0) x = (x * (1000 + curveParam)) / 1000; return x; } case CURVE_REF_EXPO: { int curveParam = getSourceNumFieldValue(curve.value, -100, 100) / 10; return expo(x, curveParam); } case CURVE_REF_FUNC: switch (v.value) { case CURVE_X_GT0: if (x < 0) x = 0; //x|x>0 return x; case CURVE_X_LT0: if (x > 0) x = 0; //x|x<0 return x; case CURVE_ABS_X: // x|abs(x) return abs(x); case CURVE_F_GT0: //f|f>0 return x > 0 ? RESX : 0; case CURVE_F_LT0: //f|f<0 return x < 0 ? -RESX : 0; case CURVE_ABS_F: //f|abs(f) return x > 0 ? RESX : -RESX; } break; case CURVE_REF_CUSTOM: { int curveParam = v.value; if (curveParam < 0) { x = -x; curveParam = -curveParam; } if (curveParam > 0 && curveParam <= MAX_CURVES) { return applyCustomCurve(x, curveParam - 1); } break; } } return x; } int applyCustomCurve(int x, uint8_t idx) { if (idx >= MAX_CURVES) return 0; CurveHeader & crv = g_model.curves[idx]; if (crv.smooth) return hermite_spline(x, idx); else return intpol(x, idx); } point_t getPoint(uint8_t curveIndex, uint8_t index) { point_t result = {0, 0}; CurveHeader & crv = g_model.curves[curveIndex]; int8_t * points = curveAddress(curveIndex); bool custom = (crv.type == CURVE_TYPE_CUSTOM); uint8_t count = STD_CURVE_POINTS(crv.points); if (index < count) { if (custom && index > 0 && index < count - 1) { result.x = calc100toRESX(points[count + index - 1]); } else { result.x = -RESX + calc100toRESX(200 * index / (count - 1)); } result.y = calc100toRESX(points[index]); } return result; } #if !defined(COLORLCD) point_t getPoint(uint8_t index) { point_t result = getPoint(s_currIdxSubMenu, index); result.x = CURVE_CENTER_X + divRoundClosest(result.x * CURVE_SIDE_WIDTH, RESX); result.y = CURVE_CENTER_Y - divRoundClosest(result.y * CURVE_SIDE_WIDTH, RESX); return result; } int applyCurrentCurve(int x) { return applyCustomCurve(x, s_currIdxSubMenu); } #endif #if defined(COLORLCD) char *getCurveRefString(char *dest, size_t len, const CurveRef& curve) { if (!len) return dest; char *s = dest; SourceNumVal v; v.rawValue = curve.value; if (v.value != 0) { switch (curve.type) { case CURVE_REF_DIFF: *(s++) = 'D'; if (--len == 0) return dest; getValueOrSrcVarString(s, len, curve.value, 0, "%"); return dest; case CURVE_REF_EXPO: *(s++) = 'E'; if (--len == 0) return dest; getValueOrSrcVarString(s, len, curve.value, 0, "%"); return dest; case CURVE_REF_FUNC: strAppend(dest, STR_VCURVEFUNC[v.value], len); return dest; case CURVE_REF_CUSTOM: return getCurveString(dest, v.value); } } return dest; } #endif