├── MIT-LICENSE ├── normal_encoding.sln ├── normal_encoding ├── main.cpp ├── normal_encoding.cpp ├── normal_encoding.hpp ├── normal_encoding.vcxproj └── precalc.cpp └── readme.md /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 atyuwen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /normal_encoding.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "normal_encoding", "normal_encoding\normal_encoding.vcxproj", "{2D92789E-C4CB-4D34-99CA-A9438B422082}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {2D92789E-C4CB-4D34-99CA-A9438B422082}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {2D92789E-C4CB-4D34-99CA-A9438B422082}.Debug|Win32.Build.0 = Debug|Win32 14 | {2D92789E-C4CB-4D34-99CA-A9438B422082}.Release|Win32.ActiveCfg = Release|Win32 15 | {2D92789E-C4CB-4D34-99CA-A9438B422082}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /normal_encoding/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "normal_encoding.hpp" 7 | 8 | float clamp(float v, float low, float high) 9 | { 10 | return v < low ? low : (v > high ? high : v); 11 | } 12 | 13 | int main() 14 | { 15 | #define PRECALC 1 16 | #if PRECALC 17 | // calculate the parameters and look-up tables needed in encoding/decoding 18 | // the result is written to "out.txt". 19 | void precalc(); 20 | precalc(); 21 | #endif 22 | 23 | 24 | #define JAGGED_TO_LINEAR_TEST 0 25 | #if JAGGED_TO_LINEAR_TEST 26 | void test(); 27 | test(); 28 | #endif 29 | 30 | std::cout << std::endl; 31 | std::cout << "now testing..." << std::endl; 32 | srand(static_cast(time(0))); 33 | 34 | const int num_test = 1000000; 35 | float max_err = 0; 36 | float avg_err = 0; 37 | for (int i = 0; i < num_test; ++i) 38 | { 39 | float x = static_cast(rand()) / RAND_MAX - 0.5f; 40 | float y = static_cast(rand()) / RAND_MAX - 0.5f; 41 | float z = static_cast(rand()) / RAND_MAX - 0.5f; 42 | float len = sqrt(x * x + y * y + z * z); 43 | float3 v = {x / len, y / len, z / len}; 44 | 45 | uint16_t s = encode(v); 46 | float3 u = decode(s); 47 | float dot = v.x * u.x + v.y * u.y + v.z * u.z; 48 | float err = acos(clamp(dot, -1.0f, 1.0f)); 49 | if (err > max_err) max_err = err; 50 | avg_err += err; 51 | } 52 | avg_err /= num_test; 53 | 54 | const float c_pi = 3.14159265358979323846f; 55 | std::cout << "max error (in rad): " << max_err << std::endl; 56 | std::cout << "max error (in deg): " << max_err / c_pi * 180 << std::endl; 57 | std::cout << "avg error (in rad): " << avg_err << std::endl; 58 | std::cout << "avg error (in deg): " << avg_err / c_pi * 180 << std::endl; 59 | 60 | system("pause"); 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /normal_encoding/normal_encoding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "normal_encoding.hpp" 3 | 4 | static const float c_pi = 3.14159265358979323846f; 5 | static const float c_2pi = 2 * c_pi; 6 | 7 | struct alias 8 | { 9 | int16_t n; 10 | int16_t k; 11 | int16_t d; 12 | }; 13 | 14 | struct reference 15 | { 16 | int16_t n; 17 | int16_t k; 18 | int16_t m; 19 | }; 20 | 21 | static const int Nphi = 227; 22 | static const int Ntheta_avg = 289; 23 | 24 | static const int16_t Nthetas[] = { 25 | 1, 8, 15, 21, 27, 33, 40, 46, 52, 59, 65, 71, 77, 83, 90, 96, 26 | 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 185, 191, 27 | 197, 203, 208, 214, 219, 225, 230, 236, 241, 246, 252, 257, 262, 267, 272, 277, 28 | 282, 287, 292, 297, 301, 306, 311, 315, 320, 324, 329, 333, 337, 341, 346, 350, 29 | 354, 357, 361, 365, 369, 372, 376, 380, 383, 386, 390, 393, 396, 399, 402, 405, 30 | 408, 410, 413, 416, 418, 420, 423, 425, 427, 429, 431, 433, 435, 437, 438, 440, 31 | 441, 443, 444, 445, 447, 448, 449, 450, 450, 451, 452, 452, 453, 453, 453, 454, 32 | 454, 454, 454, 454, 453, 453, 453, 452, 452, 451, 450, 450, 449, 448, 447, 445, 33 | 444, 443, 441, 440, 438, 437, 435, 433, 431, 429, 427, 425, 423, 420, 418, 416, 34 | 413, 410, 408, 405, 402, 399, 396, 393, 390, 386, 383, 380, 376, 372, 369, 365, 35 | 361, 357, 354, 350, 346, 341, 337, 333, 329, 324, 320, 315, 311, 306, 301, 297, 36 | 292, 287, 282, 277, 272, 267, 262, 257, 252, 246, 241, 236, 230, 225, 219, 214, 37 | 208, 203, 197, 191, 185, 180, 174, 168, 162, 156, 150, 144, 138, 132, 126, 120, 38 | 114, 108, 102, 96, 90, 83, 77, 71, 65, 59, 52, 46, 40, 33, 27, 21, 39 | 15, 8, 1, 40 | }; 41 | 42 | static const alias alias_table[] = { 43 | {1, 115, 165}, {8, 113, 165}, {15, 111, 165}, {21, 117, 164}, 44 | {27, 110, 164}, {33, 108, 164}, {40, 119, 163}, {46, 106, 163}, 45 | {52, 105, 162}, {59, 122, 161}, {65, 103, 161}, {71, 102, 160}, 46 | {77, 101, 159}, {83, 100, 158}, {90, 99, 156}, {96, 98, 155}, 47 | {102, 97, 154}, {108, 96, 152}, {114, 95, 151}, {120, 94, 149}, 48 | {126, 93, 148}, {132, 92, 146}, {138, 91, 144}, {144, 90, 142}, 49 | {150, 89, 140}, {156, 88, 138}, {162, 87, 136}, {168, 85, 131}, 50 | {174, 83, 127}, {180, 145, 121}, {185, 80, 119}, {191, 78, 113}, 51 | {197, 151, 104}, {203, 74, 101}, {208, 72, 94}, {214, 157, 83}, 52 | {219, 68, 80}, {225, 66, 72}, {230, 64, 65}, {236, 165, 52}, 53 | {241, 167, 44}, {246, 169, 35}, {252, 55, 26}, {257, 53, 17}, 54 | {262, 82, 14}, {267, 174, 12}, {272, 145, 12}, {277, 71, 10}, 55 | {282, 140, 7}, {237, 158, -3}, {271, 177, -2}, {286, 165, -1}, 56 | {286, 63, 2}, {274, 75, 12}, {276, 73, 11}, {278, 148, 9}, 57 | {281, 141, 8}, {281, 69, 8}, {285, 160, 3}, {285, 139, 3}, 58 | {239, 49, -2}, {272, 50, -1}, {273, 61, -1}, {274, 62, -1}, 59 | {241, 60, -2}, {275, 63, -1}, {286, 51, 0}, {286, 66, 0}, 60 | {243, 64, -2}, {286, 67, 0}, {245, 68, -2}, {247, 70, -2}, 61 | {278, 144, 9}, {249, 71, -2}, {275, 86, 11}, {286, 89, 1}, 62 | {276, 70, 11}, {285, 62, 3}, {274, 52, 12}, {284, 64, 6}, 63 | {272, 83, 12}, {284, 158, 5}, {276, 155, 10}, {284, 88, 5}, 64 | {276, 146, 10}, {286, 64, 1}, {286, 158, 0}, {251, 73, -2}, 65 | {286, 69, 0}, {253, 87, -2}, {286, 141, 0}, {282, 67, 7}, 66 | {278, 87, 9}, {274, 153, 11}, {269, 147, 12}, {265, 72, 13}, 67 | {260, 74, 15}, {256, 173, 17}, {251, 171, 26}, {246, 57, 35}, 68 | {241, 59, 44}, {236, 61, 52}, {231, 164, 57}, {226, 162, 65}, 69 | {220, 67, 76}, {214, 69, 83}, {209, 156, 87}, {203, 153, 97}, 70 | {197, 75, 104}, {191, 149, 110}, {191, 77, 110}, {180, 81, 121}, 71 | {173, 84, 129}, {173, 143, 127}, {166, 86, 134}, {166, 141, 131}, 72 | {185, 147, 116}, {185, 79, 116}, {179, 82, 124}, {203, 73, 97}, 73 | {196, 76, 107}, {208, 155, 91}, {220, 160, 72}, {213, 70, 87}, 74 | {225, 161, 68}, {230, 163, 61}, {235, 62, 57}, {239, 60, 48}, 75 | {245, 58, 40}, {250, 56, 31}, {254, 54, 22}, {259, 78, 15}, 76 | {263, 84, 13}, {268, 149, 12}, {272, 81, 12}, {276, 85, 10}, 77 | {280, 154, 8}, {284, 161, 4}, {276, 65, -1}, {277, 138, -1}, 78 | {286, 88, 0}, {286, 140, 0}, {278, 139, -1}, {285, 50, 3}, 79 | {255, 89, -2}, {284, 65, 4}, {222, -1, -1}, {281, 66, 8}, 80 | {257, 144, -2}, {280, 142, 8}, {225, 146, -3}, {279, 175, 8}, 81 | {228, 150, -3}, {285, 163, 2}, {279, 142, -1}, {231, 152, -3}, 82 | {280, 154, -1}, {259, 148, -2}, {234, 155, -3}, {281, 156, -1}, 83 | {282, 159, -1}, {283, 160, -1}, {261, 157, -2}, {263, 162, -2}, 84 | {284, 161, -1}, {285, 164, -1}, {284, 176, 3}, {285, 162, 2}, 85 | {281, 51, 8}, {281, 156, 7}, {277, 68, 10}, {277, 152, 9}, 86 | {274, 143, 11}, {273, 77, 12}, {279, 157, 8}, {265, 163, -2}, 87 | {267, 175, -2}, {269, 176, -2}, {282, 159, 6}, {277, 150, 9}, 88 | {272, 79, 12}, {267, 151, 12}, {262, 76, 14}, {257, 80, 15}, 89 | {252, 172, 22}, {246, 170, 31}, {241, 168, 40}, {236, 166, 48}, 90 | {230, 63, 61}, {225, 65, 68}, {219, 159, 76}, {214, 158, 80}, 91 | {208, 71, 91}, {203, 154, 94}, {197, 152, 101}, {191, 150, 107}, 92 | {185, 148, 113}, {180, 146, 119}, {174, 144, 124}, {168, 142, 129}, 93 | {162, 140, 134}, {156, 139, 136}, {150, 138, 138}, {144, 137, 140}, 94 | {138, 136, 142}, {132, 135, 144}, {126, 134, 146}, {120, 133, 148}, 95 | {114, 132, 149}, {108, 131, 151}, {102, 130, 152}, {96, 129, 154}, 96 | {90, 128, 155}, {83, 127, 156}, {77, 126, 158}, {71, 125, 159}, 97 | {65, 124, 160}, {59, 104, 161}, {52, 123, 161}, {46, 121, 162}, 98 | {40, 107, 163}, {33, 120, 163}, {27, 109, 164}, {21, 116, 164}, 99 | {15, 118, 164}, {8, 112, 165}, {1, 114, 165}, 100 | }; 101 | 102 | static const int gap_begin = 42416; 103 | static const int gap_end = 42483; 104 | static const int gap_length = gap_end - gap_begin; 105 | 106 | static const reference reference_table[] = { 107 | {1, 0, 0}, {8, 1, 0}, {15, 2, 0}, {21, 3, 0}, {27, 4, 0}, 108 | {33, 5, 0}, {40, 6, 0}, {46, 7, 0}, {52, 8, 0}, {59, 9, 0}, 109 | {65, 10, 0}, {71, 11, 0}, {77, 12, 0}, {83, 13, 0}, {90, 14, 0}, 110 | {96, 15, 0}, {102, 16, 0}, {108, 17, 0}, {114, 18, 0}, {120, 19, 0}, 111 | {126, 20, 0}, {132, 21, 0}, {138, 22, 0}, {144, 23, 0}, {150, 24, 0}, 112 | {156, 25, 0}, {162, 26, 0}, {168, 27, 0}, {174, 28, 0}, {180, 29, 0}, 113 | {185, 30, 0}, {191, 31, 0}, {197, 32, 0}, {203, 33, 0}, {208, 34, 0}, 114 | {214, 35, 0}, {219, 36, 0}, {225, 37, 0}, {230, 38, 0}, {236, 39, 0}, 115 | {241, 40, 0}, {246, 41, 0}, {252, 42, 0}, {257, 43, 0}, {262, 44, 0}, 116 | {267, 45, 0}, {272, 46, 0}, {277, 47, 0}, {282, 48, 0}, {237, 49, 0}, 117 | {287, 60, 239}, {271, 50, 0}, {288, 61, 272}, {292, 143, 285}, {286, 51, 0}, 118 | {289, 66, 286}, {297, 168, 281}, {286, 52, 0}, {301, 78, 274}, {274, 53, 0}, 119 | {306, 43, 257}, {276, 54, 0}, {311, 130, 254}, {278, 55, 0}, {315, 42, 252}, 120 | {281, 56, 0}, {320, 129, 250}, {281, 57, 0}, {324, 99, 246}, {285, 58, 0}, 121 | {329, 128, 245}, {285, 59, 0}, {333, 100, 241}, {239, 60, 0}, {287, 64, 241}, 122 | {337, 127, 239}, {272, 61, 0}, {288, 62, 273}, {341, 101, 236}, {273, 62, 0}, 123 | {288, 63, 274}, {292, 77, 285}, {346, 126, 235}, {274, 63, 0}, {288, 65, 275}, 124 | {291, 52, 286}, {350, 188, 230}, {241, 64, 0}, {287, 68, 243}, {290, 85, 286}, 125 | {295, 79, 284}, {354, 38, 230}, {275, 65, 0}, {288, 138, 276}, {293, 145, 284}, 126 | {357, 189, 225}, {286, 66, 0}, {289, 67, 286}, {297, 147, 281}, {361, 37, 225}, 127 | {286, 67, 0}, {289, 69, 286}, {296, 91, 282}, {365, 104, 220}, {243, 68, 0}, 128 | {287, 70, 245}, {299, 170, 277}, {369, 36, 219}, {286, 69, 0}, {289, 88, 286}, 129 | {297, 57, 281}, {372, 105, 214}, {245, 70, 0}, {287, 71, 247}, {300, 76, 276}, 130 | {376, 123, 213}, {247, 71, 0}, {287, 73, 249}, {299, 47, 277}, {380, 192, 208}, 131 | {278, 72, 0}, {302, 95, 265}, {383, 34, 208}, {249, 73, 0}, {287, 87, 251}, 132 | {300, 54, 276}, {386, 119, 203}, {275, 74, 0}, {304, 96, 260}, {390, 33, 203}, 133 | {286, 75, 0}, {301, 53, 274}, {393, 108, 197}, {276, 76, 0}, {303, 182, 262}, 134 | {396, 120, 196}, {285, 77, 0}, {301, 173, 273}, {399, 110, 191}, {274, 78, 0}, 135 | {304, 131, 259}, {402, 31, 191}, {284, 79, 0}, {301, 180, 272}, {405, 117, 185}, 136 | {272, 80, 0}, {304, 183, 257}, {408, 30, 185}, {284, 81, 0}, {301, 134, 272}, 137 | {410, 111, 180}, {276, 82, 0}, {303, 44, 262}, {413, 118, 179}, {284, 83, 0}, 138 | {301, 80, 272}, {416, 28, 174}, {276, 84, 0}, {302, 132, 263}, {418, 112, 173}, 139 | {286, 85, 0}, {299, 135, 276}, {420, 27, 168}, {286, 86, 0}, {300, 74, 275}, 140 | {423, 114, 166}, {251, 87, 0}, {287, 89, 253}, {298, 92, 278}, {425, 26, 162}, 141 | {286, 88, 0}, {289, 140, 286}, {294, 83, 284}, {427, 25, 156}, {253, 89, 0}, 142 | {287, 144, 255}, {290, 75, 286}, {429, 24, 150}, {286, 90, 0}, {431, 23, 144}, 143 | {282, 91, 0}, {433, 22, 138}, {278, 92, 0}, {435, 21, 132}, {274, 93, 0}, 144 | {437, 20, 126}, {269, 94, 0}, {438, 19, 120}, {265, 95, 0}, {440, 18, 114}, 145 | {260, 96, 0}, {441, 17, 108}, {256, 97, 0}, {443, 16, 102}, {251, 98, 0}, 146 | {444, 15, 96}, {246, 99, 0}, {445, 14, 90}, {241, 100, 0}, {447, 13, 83}, 147 | {236, 101, 0}, {448, 12, 77}, {231, 102, 0}, {449, 11, 71}, {226, 103, 0}, 148 | {450, 10, 65}, {220, 104, 0}, {450, 217, 59}, {214, 105, 0}, {451, 8, 52}, 149 | {209, 106, 0}, {452, 7, 46}, {203, 107, 0}, {452, 220, 40}, {197, 108, 0}, 150 | {453, 5, 33}, {191, 109, 0}, {453, 222, 27}, {191, 110, 0}, {453, 4, 27}, 151 | {180, 111, 0}, {454, 2, 15}, {173, 112, 0}, {454, 225, 8}, {173, 113, 0}, 152 | {454, 1, 8}, {166, 114, 0}, {454, 226, 1}, {166, 115, 0}, {454, 0, 1}, 153 | {185, 116, 0}, {453, 223, 21}, {185, 117, 0}, {453, 3, 21}, {179, 118, 0}, 154 | {453, 224, 15}, {203, 119, 0}, {452, 6, 40}, {196, 120, 0}, {452, 221, 33}, 155 | {208, 121, 0}, {451, 219, 46}, {220, 122, 0}, {450, 9, 59}, {213, 123, 0}, 156 | {450, 218, 52}, {225, 124, 0}, {449, 216, 65}, {230, 125, 0}, {448, 215, 71}, 157 | {235, 126, 0}, {447, 214, 77}, {239, 127, 0}, {445, 213, 83}, {245, 128, 0}, 158 | {444, 212, 90}, {250, 129, 0}, {443, 211, 96}, {254, 130, 0}, {441, 210, 102}, 159 | {259, 131, 0}, {440, 209, 108}, {263, 132, 0}, {438, 208, 114}, {268, 133, 0}, 160 | {437, 207, 120}, {272, 134, 0}, {435, 206, 126}, {276, 135, 0}, {433, 205, 132}, 161 | {280, 136, 0}, {431, 204, 138}, {284, 137, 0}, {429, 203, 144}, {276, 138, 0}, 162 | {288, 139, 277}, {427, 202, 150}, {277, 139, 0}, {288, 142, 278}, {292, 59, 285}, 163 | {425, 201, 156}, {286, 140, 0}, {289, 141, 286}, {296, 48, 282}, {423, 200, 162}, 164 | {286, 141, 0}, {289, 90, 286}, {297, 56, 281}, {420, 115, 166}, {278, 142, 0}, 165 | {288, 154, 279}, {297, 149, 280}, {418, 199, 168}, {285, 143, 0}, {300, 172, 274}, 166 | {416, 113, 173}, {255, 144, 0}, {287, 148, 257}, {298, 72, 278}, {413, 198, 174}, 167 | {284, 145, 0}, {301, 46, 272}, {410, 29, 180}, {222, 146, 0}, {286, 150, 225}, 168 | {299, 84, 276}, {408, 197, 180}, {281, 147, 0}, {301, 94, 269}, {405, 116, 185}, 169 | {257, 148, 0}, {287, 157, 259}, {298, 55, 278}, {402, 196, 185}, {280, 149, 0}, 170 | {301, 133, 268}, {399, 109, 191}, {225, 150, 0}, {286, 152, 228}, {298, 179, 277}, 171 | {396, 195, 191}, {279, 151, 0}, {301, 181, 267}, {393, 32, 197}, {228, 152, 0}, 172 | {286, 155, 231}, {298, 171, 277}, {390, 194, 197}, {285, 153, 0}, {300, 93, 274}, 173 | {386, 107, 203}, {279, 154, 0}, {288, 156, 280}, {297, 136, 280}, {383, 193, 203}, 174 | {231, 155, 0}, {286, 158, 234}, {299, 82, 276}, {380, 121, 208}, {280, 156, 0}, 175 | {288, 159, 281}, {296, 169, 281}, {376, 106, 209}, {259, 157, 0}, {287, 162, 261}, 176 | {297, 174, 279}, {372, 35, 214}, {234, 158, 0}, {286, 49, 237}, {289, 86, 286}, 177 | {294, 81, 284}, {369, 191, 214}, {281, 159, 0}, {288, 160, 282}, {295, 178, 282}, 178 | {365, 190, 219}, {282, 160, 0}, {288, 161, 283}, {292, 58, 285}, {361, 122, 220}, 179 | {283, 161, 0}, {288, 164, 284}, {293, 137, 284}, {357, 124, 225}, {261, 162, 0}, 180 | {287, 163, 263}, {291, 167, 285}, {354, 103, 226}, {263, 163, 0}, {287, 175, 265}, 181 | {291, 153, 285}, {350, 125, 230}, {284, 164, 0}, {288, 165, 285}, {346, 102, 231}, 182 | {285, 165, 0}, {288, 51, 286}, {341, 39, 236}, {284, 166, 0}, {337, 187, 236}, 183 | {285, 167, 0}, {333, 40, 241}, {281, 168, 0}, {329, 186, 241}, {281, 169, 0}, 184 | {324, 41, 246}, {277, 170, 0}, {320, 185, 246}, {277, 171, 0}, {315, 98, 251}, 185 | {274, 172, 0}, {311, 184, 252}, {273, 173, 0}, {306, 97, 256}, {279, 174, 0}, 186 | {301, 45, 267}, {265, 175, 0}, {287, 176, 267}, {297, 151, 279}, {267, 176, 0}, 187 | {287, 177, 269}, {292, 166, 284}, {269, 177, 0}, {287, 50, 271}, {282, 178, 0}, 188 | {277, 179, 0}, {272, 180, 0}, {267, 181, 0}, {262, 182, 0}, {257, 183, 0}, 189 | {252, 184, 0}, {246, 185, 0}, {241, 186, 0}, {236, 187, 0}, {230, 188, 0}, 190 | {225, 189, 0}, {219, 190, 0}, {214, 191, 0}, {208, 192, 0}, {203, 193, 0}, 191 | {197, 194, 0}, {191, 195, 0}, {185, 196, 0}, {180, 197, 0}, {174, 198, 0}, 192 | {168, 199, 0}, {162, 200, 0}, {156, 201, 0}, {150, 202, 0}, {144, 203, 0}, 193 | {138, 204, 0}, {132, 205, 0}, {126, 206, 0}, {120, 207, 0}, {114, 208, 0}, 194 | {108, 209, 0}, {102, 210, 0}, {96, 211, 0}, {90, 212, 0}, {83, 213, 0}, 195 | {77, 214, 0}, {71, 215, 0}, {65, 216, 0}, {59, 217, 0}, {52, 218, 0}, 196 | {46, 219, 0}, {40, 220, 0}, {33, 221, 0}, {27, 222, 0}, {21, 223, 0}, 197 | {15, 224, 0}, {8, 225, 0}, {1, 226, 0}, 198 | }; 199 | 200 | static const int16_t offset_table[] = { 201 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 202 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 203 | 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 204 | 48, 49, 51, 54, 57, 59, 61, 63, 65, 67, 69, 71, 73, 76, 79, 83, 205 | 87, 92, 96, 100, 104, 108, 112, 116, 120, 123, 127, 130, 133, 136, 139, 142, 206 | 145, 148, 151, 154, 157, 160, 163, 166, 170, 174, 178, 180, 182, 184, 186, 188, 207 | 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 208 | 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 209 | 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 277, 281, 285, 289, 293, 210 | 296, 300, 303, 307, 310, 314, 317, 321, 324, 328, 331, 335, 339, 343, 347, 352, 211 | 356, 360, 364, 368, 372, 375, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 212 | 399, 402, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 213 | 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 214 | 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 215 | 450, 451, 452, 216 | }; 217 | 218 | uint16_t encode(const float3& v) 219 | { 220 | float phi = acos(v.z); 221 | float theta = atan2(v.y, v.x); 222 | if (theta < 0) theta += c_2pi; 223 | 224 | int i = static_cast(phi * (Nphi - 1) / c_pi + 0.5f); 225 | int Ntheta = Nthetas[i]; 226 | int j = static_cast(theta * Ntheta / c_2pi + 0.5f); 227 | if (j == Ntheta) j = 0; 228 | 229 | // map jagged array entry to a linearized index 230 | int index = 0; 231 | int prev = 0; 232 | for (int t = offset_table[i]; ; ++t) 233 | { 234 | reference ref = reference_table[t]; 235 | if (j < ref.n) 236 | { 237 | index = ref.k * Ntheta_avg + ref.m + j - prev; 238 | break; 239 | } 240 | prev = ref.n; 241 | } 242 | 243 | if (index >= gap_end) index -= gap_length; 244 | return static_cast(index); 245 | } 246 | 247 | float3 decode(uint16_t s) 248 | { 249 | int index = s; 250 | if (index >= gap_begin) index += gap_length; 251 | int i = index / Ntheta_avg; 252 | int j = index % Ntheta_avg; 253 | 254 | // redirection by alias method 255 | alias a = alias_table[i]; 256 | if (j >= a.n) 257 | { 258 | i = a.k; 259 | j = j + a.d; 260 | } 261 | 262 | float phi = i * c_pi / (Nphi - 1); 263 | float theta = j * c_2pi / Nthetas[i]; 264 | float3 v = {sin(phi) * cos(theta), sin(phi) * sin(theta), cos(phi)}; 265 | return v; 266 | } 267 | 268 | 269 | #if 0 270 | //////////////////////////////////////////////////////////////////// 271 | // code for test only 272 | 273 | // map jagged array entry to a linearized index 274 | uint16_t jagged_to_linear(int i, int j) 275 | { 276 | int index = 0; 277 | int prev = 0; 278 | for (int t = offset_table[i]; ; ++t) 279 | { 280 | reference ref = reference_table[t]; 281 | if (j < ref.n) 282 | { 283 | index = ref.k * Ntheta_avg + ref.m + j - prev; 284 | break; 285 | } 286 | prev = ref.n; 287 | } 288 | 289 | if (index >= gap_end) index -= gap_length; 290 | return static_cast(index); 291 | } 292 | 293 | // map linearized index to a jagged array entry 294 | void linear_to_jagged(uint16_t s, int &i, int &j) 295 | { 296 | int index = s; 297 | if (index >= gap_begin) index += gap_length; 298 | i = index / Ntheta_avg; 299 | j = index % Ntheta_avg; 300 | 301 | // redirection by alias method 302 | alias a = alias_table[i]; 303 | if (j >= a.n) 304 | { 305 | i = a.k; 306 | j = j + a.d; 307 | } 308 | } 309 | 310 | void test() 311 | { 312 | for (int i = 0; i < Nphi; ++i) 313 | { 314 | for (int j = 0; j < Nthetas[i]; ++j) 315 | { 316 | uint16_t index = jagged_to_linear(i, j); 317 | int ni, nj; 318 | linear_to_jagged(index, ni, nj); 319 | if (i != ni || j != nj) 320 | { 321 | __asm int 3; 322 | } 323 | } 324 | } 325 | } 326 | #endif 327 | -------------------------------------------------------------------------------- /normal_encoding/normal_encoding.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct float3 6 | { 7 | float x, y, z; 8 | }; 9 | 10 | // encode normalized 3d vector to 16-bits unsigned integer 11 | uint16_t encode(const float3& v); 12 | 13 | float3 decode(uint16_t s); 14 | -------------------------------------------------------------------------------- /normal_encoding/normal_encoding.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {2D92789E-C4CB-4D34-99CA-A9438B422082} 15 | normal_encoding 16 | 17 | 18 | 19 | Application 20 | true 21 | v110 22 | MultiByte 23 | 24 | 25 | Application 26 | false 27 | v110 28 | true 29 | MultiByte 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Level3 45 | Disabled 46 | 47 | 48 | true 49 | 50 | 51 | 52 | 53 | Level3 54 | MaxSpeed 55 | true 56 | true 57 | 58 | 59 | true 60 | true 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /normal_encoding/precalc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static const double c_pi = 3.14159265358979323846; 12 | static const double c_2pi = 2 * c_pi; 13 | 14 | double clamp(double v, double low, double high) 15 | { 16 | return v < low ? low : (v > high ? high : v); 17 | } 18 | 19 | int total_points(double eps, int Nphi, int * Nthetas = nullptr) 20 | { 21 | double s = 0.5 * c_pi / (Nphi - 1); 22 | int num_points = 0; 23 | for (int j = 0, k = Nphi - 1; j <= k; ++j, --k) 24 | { 25 | int Ntheta = 1; 26 | if (j > 0) 27 | { 28 | double phi_hat = j * c_pi / (Nphi - 1); 29 | double n = cos(eps) - cos(phi_hat) * cos(phi_hat + s); 30 | double d = sin(phi_hat) * sin(phi_hat + s); 31 | double q = n / d; 32 | q = clamp(q, -1.0, 1.0); 33 | Ntheta = static_cast(ceil(c_pi / acos(q))); 34 | } 35 | 36 | if (Nthetas != nullptr) Nthetas[j] = Nthetas[k] = Ntheta; 37 | num_points += j < k ? 2 * Ntheta : Ntheta; 38 | } 39 | return num_points; 40 | } 41 | 42 | // Given eps, find the optimal Nphi 43 | int opt_total_points(double eps, int *optNphi = nullptr) 44 | { 45 | int Nphi = static_cast(ceil(0.5 * c_pi / eps)) + 1; 46 | int opt_points = total_points(eps, Nphi); 47 | int max_search_step = 1000; 48 | while (max_search_step--) 49 | { 50 | int points = total_points(eps, Nphi + 1); 51 | if (points > opt_points) break; 52 | Nphi += 1; 53 | opt_points = points; 54 | } 55 | if (optNphi != nullptr) *optNphi = Nphi; 56 | return opt_points; 57 | } 58 | 59 | // find the optimal epsilon by binary search 60 | double search_opt_eps() 61 | { 62 | const int max_points = (1 << 16); 63 | const double stop_criterion = 1e-9; 64 | double low = 0.01; 65 | double high = 1.0; 66 | while (opt_total_points(low) <= max_points) 67 | { 68 | low *= 0.5; 69 | } 70 | 71 | while (high - low > stop_criterion) 72 | { 73 | double mid = (low + high) / 2; 74 | if (opt_total_points(mid) > max_points) 75 | { 76 | low = mid; 77 | } 78 | else 79 | { 80 | high = mid; 81 | } 82 | } 83 | return high; 84 | } 85 | 86 | struct bin 87 | { 88 | int16_t i; 89 | int16_t n; 90 | bool operator < (bin b) const {return n < b.n || (n == b.n && i < b.i);} 91 | }; 92 | 93 | struct alias 94 | { 95 | int16_t n; 96 | int16_t k; 97 | int16_t d; 98 | }; 99 | 100 | struct reference 101 | { 102 | int16_t i; 103 | int16_t n; 104 | int16_t k; 105 | int16_t m; 106 | 107 | bool operator < (reference r) const 108 | { 109 | // can't be both equal, so don't need to compare k or m 110 | return i < r.i || (i == r.i && n < r.n); 111 | } 112 | }; 113 | 114 | void precalc() 115 | { 116 | double eps = search_opt_eps(); 117 | int Nphi = 0; 118 | opt_total_points(eps, &Nphi); 119 | 120 | std::vector Nthetas(Nphi); 121 | int num_points = total_points(eps, Nphi, &Nthetas[0]); 122 | int Ntheta_max = *std::max_element(Nthetas.begin(), Nthetas.end()); 123 | int Ntheta_avg = (num_points + Nphi - 1) / Nphi; 124 | 125 | std::cout << "total points: " << num_points << std::endl; 126 | std::cout << "max error (in rad): " << eps << std::endl; 127 | std::cout << "max error (in degree): " << eps * 180 / c_pi << std::endl; 128 | 129 | std::cout << "Nphi: " << Nphi << std::endl; 130 | std::cout << "Ntheta max: " << Ntheta_max << std::endl; 131 | std::cout << "Ntheta avg: " << Ntheta_avg << std::endl; 132 | 133 | std::cout << "alias table size: " << sizeof(alias) * Nphi << " bytes" << std::endl; 134 | 135 | // calculate alias table 136 | std::set bins; 137 | for (int i = 0; i < Nphi; ++i) 138 | { 139 | bin b = {i, Nthetas[i]}; 140 | bins.insert(b); 141 | } 142 | 143 | std::vector alias_table(Nphi); 144 | std::vector reference_table; 145 | bin last_bin; 146 | 147 | while (!bins.empty()) 148 | { 149 | if (bins.size() == 1) 150 | { 151 | bin b = *bins.begin(); 152 | last_bin = b; 153 | alias_table[b.i].n = b.n; 154 | alias_table[b.i].k = -1; 155 | alias_table[b.i].d = -1; 156 | bins.clear(); 157 | 158 | reference ref = {b.i, b.n, b.i, 0}; 159 | reference_table.push_back(ref); 160 | } 161 | else 162 | { 163 | bin a = *bins.begin(); 164 | bin b = *bins.rbegin(); 165 | alias_table[a.i].n = a.n; 166 | alias_table[a.i].k = b.i; 167 | alias_table[a.i].d = b.n - Ntheta_avg; 168 | 169 | bin c = {b.i, b.n - (Ntheta_avg - a.n)}; 170 | bins.erase(a); 171 | bins.erase(b); 172 | bins.insert(c); 173 | 174 | reference ref_a = {a.i, a.n, a.i, 0 }; 175 | reference ref_b = {b.i, b.n, a.i, a.n}; 176 | reference_table.push_back(ref_a); 177 | reference_table.push_back(ref_b); 178 | } 179 | } 180 | 181 | std::sort(reference_table.begin(), reference_table.end()); 182 | 183 | std::vector reference_count(Nphi); 184 | std::vector offset_table(Nphi); 185 | for (int i = 0; i != reference_table.size(); ++i) 186 | { 187 | reference_count[reference_table[i].i] += 1; 188 | if (i > 0 && reference_table[i].i > reference_table[i-1].i) 189 | offset_table[reference_table[i].i] = i; 190 | } 191 | 192 | std::cout << "reference table size: " << sizeof(int16_t) * 3 * reference_table.size() << " bytes" << std::endl; 193 | std::cout << "offset table size: " << sizeof(int16_t) * Nphi << " bytes" << std::endl; 194 | std::cout << "max reference:" << *max_element(reference_count.begin(), reference_count.end()) << std::endl; 195 | std::cout << "avg reference:" << std::accumulate(reference_count.begin(), reference_count.end(), 0) / static_cast(Nphi) << std::endl; 196 | 197 | std::ofstream ofs("out.txt"); 198 | 199 | // write Nphi 200 | ofs << "static const int16_t Nphi = " << Nphi << ";" << std::endl << std::endl; 201 | 202 | // write Nthetas 203 | ofs << "static const int16_t Nthetas[] = {"; 204 | for (int i = 0; i < Nphi; ++i) 205 | { 206 | if (i % 16 == 0) ofs << std::endl << " "; 207 | ofs << Nthetas[i] << ", "; 208 | } 209 | ofs << std::endl << "};" << std::endl << std::endl; 210 | 211 | // write alias table 212 | ofs << "static const alias alias_table[] = {"; 213 | for (int i = 0; i < Nphi; ++i) 214 | { 215 | if (i % 4 == 0) ofs << std::endl << " "; 216 | ofs << "{" << alias_table[i].n << ", " << alias_table[i].k << ", " << alias_table[i].d << "}, "; 217 | } 218 | ofs << std::endl << "};" << std::endl << std::endl; 219 | 220 | // write gap info 221 | ofs << "static const int gap_begin = " << last_bin.i * Ntheta_avg + last_bin.n << ";" << std::endl; 222 | ofs << "static const int gap_end = " << (last_bin.i + 1) * Ntheta_avg << ";" << std::endl; 223 | ofs << "static const int gap_length = gap_end - gap_begin;" << std::endl; 224 | ofs << std::endl; 225 | 226 | // write out reference table 227 | ofs << "static const reference reference_table[] = {"; 228 | for (int i = 0; i != reference_table.size(); ++i) 229 | { 230 | if (i % 5 == 0) ofs << std::endl << " "; 231 | ofs << "{" << reference_table[i].n << ", " << reference_table[i].k << ", " << reference_table[i].m << "}, "; 232 | } 233 | ofs << std::endl << "};" << std::endl << std::endl; 234 | 235 | // write out offset table 236 | ofs << "static const int16_t offset_table[] = {"; 237 | for (int i = 0; i < Nphi; ++i) 238 | { 239 | if (i % 16 == 0) ofs << std::endl << " "; 240 | ofs << offset_table[i] << ", "; 241 | } 242 | ofs << std::endl << "};" << std::endl << std::endl; 243 | } 244 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Normal Vector Encoder/Decoder 2 | 3 | --- 4 | 5 | - Encoding float3 normal vectors to uint16 by using optimized spherical coords together with alias method, with a bounded error less than 0.562432 degree. 6 | 7 | - Both encoding and decoding are very fast. Encoding do use a search process to map jagged array entry to a linearized index, but the searching is short and cache friendly. (5 steps at most, <2 steps in average). Decoding don't do any searching at all. 8 | 9 | - Encoder need a 3172-bytes look up table, while decoder only needs 1362 bytes. (can be further reduced to 1/2 if exploit symmetries) 10 | 11 | - The code is not carefully optimized. 12 | 13 | --- 14 | 15 | - Normal Encoding ref: J. Smith, G. Petrova, S. Schaefe, Encoding Normal Vectors using Optimized Spherical Coordinates. 16 | http://faculty.cs.tamu.edu/schaefer/research/normalCompression.pdf 17 | 18 | - Alias Method ref: Knuth, The Art of Computer Programming, Vol. 2, Sect. 3.4.1, ex. 7. 19 | 20 | --------------------------------------------------------------------------------