File indexing completed on 2024-05-12 04:06:21

0001 /*
0002     SPDX-FileCopyrightText: 2010 Johannes Loehnert <loehnert.kde@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "utilities.h"
0008 
0009 #include <QDebug>
0010 #include <QRandomGenerator>
0011 
0012 void getBestFit(int &xCount, int &yCount, qreal target_aspect, int approx_count) {
0013     qreal nx_exact = sqrt(approx_count * target_aspect);
0014     // avoid taking the sqrt again
0015     qreal ny_exact = approx_count / nx_exact;
0016 
0017     // catch very odd cases
0018     if (nx_exact < 1) nx_exact = 1.01;
0019     if (ny_exact < 1) ny_exact = 1.01;
0020 
0021     qreal aspect1 = floor(nx_exact) / ceil(ny_exact);
0022     qreal aspect2 = ceil(nx_exact) / floor(ny_exact);
0023 
0024     aspect1 = target_aspect - aspect1;
0025     aspect2 = aspect2 - target_aspect;
0026 
0027     if (aspect1 < aspect2) ny_exact += 1.0; else nx_exact += 1.0;
0028 
0029     xCount = (int)(floor(nx_exact) + 0.1);
0030     yCount = (int)(floor(ny_exact) + 0.1);
0031 }
0032 
0033 void getBestFitExtended(int &xCount, int &yCount, qreal target_aspect, int approx_count,
0034             qreal tiles_per_cell, qreal additional_tiles_per_row, qreal additional_tiles_per_column, qreal additional_tiles) {
0035     // solves the equations
0036     //  N = TPC * x * y  +  ATPC * x + ATPR * y + AT
0037     //  target_aspect = x / y
0038     //
0039     // for x, y; and rounds them to the nearest integer values giving least distance to target_aspect.
0040     qreal p_half = (target_aspect * additional_tiles_per_column + additional_tiles_per_row) / (2 * target_aspect * tiles_per_cell);
0041     qreal q = (approx_count - additional_tiles) / (target_aspect * tiles_per_cell);
0042     
0043     qreal p_half_sq = p_half*p_half;
0044     if (p_half_sq + q < 0) {
0045         xCount = 1;
0046         yCount = 1;
0047         return;
0048     }
0049     
0050     qreal ny_exact = -p_half + sqrt(p_half_sq + q);
0051     qreal nx_exact = target_aspect * ny_exact;
0052 
0053     qDebug() << "nx_exact: " << nx_exact << " ny_exact: " << ny_exact <<
0054                 "giving count: " << (tiles_per_cell*nx_exact*ny_exact + additional_tiles_per_column * nx_exact + additional_tiles_per_row * ny_exact + additional_tiles);
0055 
0056     // catch very odd cases
0057     if (nx_exact < 1) nx_exact = 1.01;
0058     if (ny_exact < 1) ny_exact = 1.01;
0059 
0060     qreal aspect1 = floor(nx_exact) / ceil(ny_exact);
0061     qreal aspect2 = ceil(nx_exact) / floor(ny_exact);
0062     qreal aspect3 = ceil(nx_exact) / ceil(ny_exact);
0063 
0064     aspect1 = target_aspect - aspect1;
0065     aspect2 = aspect2 - target_aspect;
0066     aspect3 = abs(aspect3 - target_aspect);
0067 
0068     if (aspect1 < aspect2) {
0069         ny_exact += 1.0; 
0070         if (aspect3 < aspect1) nx_exact += 1.0;
0071     }
0072     else {
0073         nx_exact += 1.0;
0074         if (aspect3 < aspect2) ny_exact += 1.0;
0075     }
0076 
0077     xCount = (int)(floor(nx_exact) + 0.1);
0078     yCount = (int)(floor(ny_exact) + 0.1);
0079 }
0080 
0081 // skews x with "strength" a.
0082 // x is expected to lie within [0, 1].
0083 // a = +/-1 is already a very strong skew.
0084 // negative a: skew towards x=0, positive: skew towards x=1.
0085 qreal skew_randnum(qreal x, qreal a) {
0086     if (a==0) return x;
0087 
0088     qreal asq = exp(-2 * abs(a));
0089     if (a>0) x = 1-x;
0090 
0091     qreal mp2 = (x-1) * (2/asq - 1);
0092     qreal q = (x-1)*(x-1) - 1;
0093 
0094     // We apply a function on x, which is a hyperbola through (0,0) and (1,1)
0095     // with (1, 0) as focal point. You really don't want to know the gory details.
0096     x = mp2 + sqrt(mp2*mp2 - q);
0097 
0098     if (a>0) x = 1-x;
0099     return x;
0100 }
0101 
0102 
0103 qreal nonuniform_rand(qreal min, qreal max, qreal sigma, qreal skew) {
0104 
0105     // 0.4247: sigma at which distribution function is 1/2 of max at interval boundaries
0106 
0107     qreal randNum;
0108 
0109     auto *generator = QRandomGenerator::global();
0110     if (sigma > 0.4247) {
0111         // "wide" distribution, use rejection sampling
0112         qreal x, y;
0113         qreal ssq = 2 * sigma * sigma;
0114         do {
0115             x = 0.000001 * qreal(generator->bounded(1000000));
0116             y = 0.000001 * qreal(generator->bounded(1000000));
0117         } while (y > exp(-(x-0.5)*(x-0.5)/ssq));
0118 
0119         randNum = x;
0120     }
0121     else {
0122         // "narrow" distribution, use Marsaglia method until a random number within 0, 1 is found.
0123         qreal u1, u2, p, q, x1, x2;
0124 
0125         randNum = -1;
0126         do {
0127             do {
0128                 u1 = 0.000002 * qreal(generator->bounded(1000000)) - 1;
0129                 u2 = 0.000002 * qreal(generator->bounded(1000000)) - 1;
0130                 q = u1*u1 + u2*u2;
0131             } while (q>1);
0132             p = sqrt(-2 * log(q) / q) * sigma;
0133             x1 = u1 * p + 0.5; 
0134             x2 = u2 * p + 0.5;
0135 
0136             if (x1>= 0 && x1 <= 1) {
0137                 randNum = x1;
0138             }
0139             else {
0140                 if (x2 >= 0 && x2 <= 1) randNum = x2;
0141             }
0142         } while (randNum < 0);
0143     }
0144 
0145     return min + (max - min) * skew_randnum(randNum, skew);
0146 }
0147