├── README.md └── tinyshrot.h /README.md: -------------------------------------------------------------------------------- 1 | # TinySHRot, header-only C89 spherical harmonics rotation codes. 2 | 3 | Based on my quite old blog post back to 2003. 4 | http://lucille.sourceforge.net/blog/archives/000005.html 5 | 6 | TinySHRot is good for your rendering engine and game engine. 7 | 8 | ## Features 9 | 10 | * C89 + Header-only 11 | * SH rotation only 12 | 13 | ## Status 14 | 15 | * Shold work (visually) correctly with unit tests, but not verified in terms of mathematical correctness(Contribution is welcome!). 16 | 17 | ## Notice 18 | 19 | If you want fast and full-featured SH rotation code, there are good OSS codes these days, e.g. https://github.com/google/spherical-harmonics. 20 | 21 | ## References 22 | 23 | * Evaluation of the rotation matrices in the basis of real spherical harmonics 24 | 25 | 26 | ## Papers worth to see 27 | 28 | * Z. Gimbutas and L. Greengard, "A fast and stable method for rotating spherical harmonic expansions", 2009 29 | 30 | ## License 31 | 32 | MIT license 33 | 34 | ## Contributors 35 | 36 | * Takeshi Haga : Fixing bugs in original code. 37 | -------------------------------------------------------------------------------- /tinyshrot.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2003-2017 Syoyo Fujita and many contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #ifndef TINYSHROT_H_ 26 | #define TINYSHROT_H_ 27 | 28 | #include 29 | #include 30 | #include // abs() 31 | 32 | /* 33 | * Builds rotation matrix for spherical harmoinics. 34 | * `a`, `b` and `c` parameter are based on 35 | * "Evaluation of the rotation matrices in the basis of real spherical harmonics", 1997 36 | * 37 | * TODO(syoyo): Require more tests and mathematical verification. 38 | * NOTE(syoyo): Not numerically stable for larger l(l <= 4 should work well) 39 | * 40 | * `ret` must be memory allocated like this: 41 | * 42 | * double **mat; 43 | * 44 | * mat = (double **)malloc(sizeof(double *) * (l + 1)); 45 | * 46 | * for (i = 0; i <= l; i++) { 47 | * 48 | * mat[i] = (double *)malloc(sizeof(double) * (2 * l + 1) * (2 * l + 1)); 49 | * 50 | * } 51 | * 52 | * tinysh_rotation(mat, l, a, b, r); 53 | * 54 | */ 55 | 56 | void tinysh_rotation(double **ret, unsigned int l, double a, double b, double r); 57 | 58 | #endif /* TINYSHROT_H_ */ 59 | 60 | #ifdef TINYSHROT_IMPLEMENTATION 61 | #ifndef TINYSHROT_IMPLEMENTATION_DEIFNED 62 | #define TINYSHROT_IMPLEMENTATION_DEIFNED 63 | # 64 | static void buildcoeff(double *ret, double *c, int l, double a, double r); 65 | static double phi(int m, double p); 66 | static double sign(int m); 67 | static int map(int l, int m, int md); 68 | static void fill_d(double *m, int l); 69 | 70 | int map(int l, int m, int md) { 71 | /* map subscript (m, m') to array index. 72 | * mapping is 73 | * f(m, m') = (m + l) * (2 * l + 1) + (m' + l) 74 | */ 75 | return (m + l) * (2 * l + 1) + (md + l); 76 | } 77 | 78 | double sign(int m) { 79 | if (m >= 0) 80 | return 1.0; 81 | 82 | return -1.0; 83 | } 84 | 85 | double phi(int m, double p) { 86 | double val = 0.0; 87 | 88 | if (m > 0) { 89 | val = sqrt(2.0) * cos((double)(m * p)); 90 | } else if (m < 0) { 91 | val = sqrt(2.0) * sin((double)(-m * p)); 92 | } else { 93 | val = 1.0; 94 | } 95 | 96 | return val; 97 | } 98 | 99 | void tinysh_rotation(double **ret, unsigned int l, double a, double b, double r) { 100 | int i; 101 | int id; 102 | int mid; 103 | int mid1, mid2; 104 | int m, md; 105 | double **d; 106 | double c1, c2, c3; 107 | 108 | d = (double **)malloc(sizeof(double *) * (l + 1)); 109 | 110 | for (i = 0; i <= (int)l; i++) { 111 | d[i] = (double *)malloc(sizeof(double) * (2 * l + 1) * (2 * l + 1)); 112 | } 113 | 114 | d[0][0] = 1.0; 115 | ret[0][0] = 1.0; 116 | 117 | id = map(1, 0, 0); 118 | d[1][id] = cos(b); /* d1_(0, 0) */ 119 | 120 | id = map(1, 1, -1); 121 | d[1][id] = sin(b * 0.5) * sin(b * 0.5); /* d1_(1,-1) */ 122 | 123 | id = map(1, 1, 0); 124 | d[1][id] = (-1.0 / sqrt(2.0)) * sin(b); /* d1_(1, 0) */ 125 | 126 | id = map(1, 1, 1); 127 | d[1][id] = cos(b * 0.5) * cos(b * 0.5); /* d1_(1, 1) */ 128 | 129 | /* d1_(0,-1) = d1_(1, 0) */ 130 | id = map(1, 0, -1); 131 | d[1][id] = d[1][map(1, 1, 0)]; 132 | 133 | /* d1_(0,1) = -d1_(1, 0) */ 134 | id = map(1, 0, 1); 135 | d[1][id] = -d[1][map(1, 1, 0)]; 136 | 137 | buildcoeff(ret[1], d[1], 1, a, r); 138 | 139 | for (i = 2; i <= (int)l; i++) { 140 | for (m = 0; m <= i - 2; m++) { 141 | for (md = -m; md <= m; md++) { 142 | mid = map(i, m, md); 143 | 144 | c1 = (double)(i * (2 * i - 1)) / 145 | sqrt((double)((i * i - m * m) * (i * i - md * md))); 146 | 147 | c2 = d[1][4] - (m * md / (double)((i * (i - 1)))); 148 | 149 | c3 = ((i - 1) * (i - 1) - m * m) * ((i - 1) * (i - 1) - md * md); 150 | c3 = sqrt(c3); 151 | c3 = c3 / (double)((i - 1) * (2 * i - 1)); 152 | 153 | mid1 = map(i - 1, m, md); 154 | mid2 = map(i - 2, m, md); 155 | d[i][mid] = c1 * (c2 * d[i - 1][mid1] - c3 * d[i - 2][mid2]); 156 | } 157 | } 158 | 159 | /* dl_(l, l) */ 160 | mid = map(i, i, i); 161 | mid1 = map(1, 1, 1); 162 | mid2 = map(i - 1, i - 1, i - 1); 163 | d[i][mid] = d[1][mid1] * d[i - 1][mid2]; 164 | 165 | /* dl_(l-1, l-1) */ 166 | mid = map(i, i - 1, i - 1); 167 | mid1 = map(1, 0, 0); 168 | mid2 = map(i - 1, i - 1, i - 1); 169 | 170 | d[i][mid] = (i * d[1][mid1] - i + 1) * d[i - 1][mid2]; 171 | 172 | /* dl_(l, m - 1) */ 173 | for (md = i - 1; md >= -i; md--) { 174 | mid = map(i, i, md); 175 | c1 = -sqrt((double)(i + (md + 1)) / (double)(i - (md + 1) + 1)); 176 | 177 | d[i][mid] = c1 * tan(0.5 * b) * d[i][mid + 1]; 178 | } 179 | 180 | /* dl_(l - 1, m - 1) */ 181 | for (md = i - 2; md >= 1 - i; md--) { 182 | mid = map(i, i - 1, md); 183 | c1 = -(double)(i * cos(b) - (md + 1) + 1) / 184 | (double)(i * cos(b) - (md + 1)); 185 | 186 | c1 *= sqrt((double)(i + (md + 1)) / (double)(i - (md + 1) + 1)); 187 | 188 | d[i][mid] = c1 * tan(0.5 * b) * d[i][mid + 1]; 189 | } 190 | 191 | fill_d(d[i], i); 192 | buildcoeff(ret[i], d[i], i, a, r); 193 | } 194 | 195 | for (i = 0; i <= (int)l; i++) { 196 | free(d[i]); 197 | } 198 | free(d); 199 | } 200 | 201 | void buildcoeff(double *ret, double *c, int l, double a, double r) { 202 | int m, md; 203 | int id; 204 | int id1, id2; 205 | double w; 206 | 207 | for (m = -l; m <= l; m++) { 208 | for (md = -l; md <= l; md++) { 209 | id = map(l, m, md); 210 | id1 = (abs(md) + l) * (2 * l + 1) + (abs(m) + l); 211 | id2 = (abs(m) + l) * (2 * l + 1) + (-abs(md) + l); 212 | 213 | if (m == 0) { 214 | w = 1.0; 215 | } else { 216 | w = ((m % 2) == 0) ? 1.0 : -1.0; 217 | } 218 | 219 | ret[id] = 220 | sign(md) * phi(m, a) * phi(md, r) * ((c[id1] + w * c[id2]) * 0.5); 221 | 222 | ret[id] -= 223 | sign(m) * phi(-m, a) * phi(-md, r) * ((c[id1] - w * c[id2]) * 0.5); 224 | } 225 | } 226 | } 227 | 228 | void fill_d(double *m, int l) { 229 | int i, j; 230 | double w; 231 | 232 | /* left triangle of matrix with m>= 0 */ 233 | for (j = 0; j < l; j++) { 234 | for (i = -l; i < -j; i++) { 235 | /* dl_(j, i) = dl_(-i, -j) */ 236 | m[map(l, j, i)] = m[map(l, -i, -j)]; 237 | } 238 | } 239 | 240 | /* right triangle of matrix with m>= 0 */ 241 | for (j = 0; j < l; j++) { 242 | for (i = j + 1; i <= l; i++) { 243 | /* dl_(j, i) = (-1)^(j+i) dl_(i, j) */ 244 | if (j + i == 0) { 245 | w = 1.0; 246 | } else { 247 | w = ((j + i) % 2 == 0) ? 1.0 : -1.0; 248 | } 249 | m[map(l, j, i)] = w * m[map(l, i, j)]; 250 | } 251 | } 252 | } 253 | 254 | #endif /* TINYSHROT_IMPLEMENTATION_DEIFNED */ 255 | #endif /* TINYSHROT_IMPLEMENTATION */ 256 | --------------------------------------------------------------------------------