1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "ConfigManager.h"
17 
18 #include "json/json.h"
19 
20 #include <assert.h>
21 #include <math.h>
22 
23 #include <fstream>
24 
25 static const float kDegreesToRadians = M_PI / 180.0f;
26 
normalizeToPlusMinus180degrees(float theta)27 static float normalizeToPlusMinus180degrees(float theta) {
28     const float wraps = floor((theta + 180.0f) / 360.0f);
29     return theta - wraps * 360.0f;
30 }
31 
readChildNodeAsFloat(const char * groupName,const Json::Value & parentNode,const char * childName,float * value)32 static bool readChildNodeAsFloat(const char* groupName, const Json::Value& parentNode,
33                                  const char* childName, float* value) {
34     // Must have a place to put the value!
35     assert(value);
36 
37     Json::Value childNode = parentNode[childName];
38     if (!childNode.isNumeric()) {
39         printf("Missing or invalid field %s in record %s", childName, groupName);
40         return false;
41     }
42 
43     *value = childNode.asFloat();
44     return true;
45 }
46 
initialize(const char * configFileName)47 bool ConfigManager::initialize(const char* configFileName) {
48     bool complete = true;
49 
50     // Set up a stream to read in the input file
51     std::ifstream configStream(configFileName);
52 
53     // Parse the stream into JSON objects
54     Json::CharReaderBuilder builder;
55     builder["collectComments"] = false;
56     std::string errorMessage;
57     Json::Value rootNode;
58     bool parseOk = Json::parseFromStream(builder, configStream, &rootNode, &errorMessage);
59     if (!parseOk) {
60         printf("Failed to read configuration file %s\n", configFileName);
61         printf("%s\n", errorMessage.c_str());
62         return false;
63     }
64 
65     //
66     // Read car information
67     //
68     {
69         Json::Value car = rootNode["car"];
70         if (!car.isObject()) {
71             printf("Invalid configuration format -- we expect a car description\n");
72             return false;
73         }
74         complete &= readChildNodeAsFloat("car", car, "width", &mCarWidth);
75         complete &= readChildNodeAsFloat("car", car, "wheelBase", &mWheelBase);
76         complete &= readChildNodeAsFloat("car", car, "frontExtent", &mFrontExtent);
77         complete &= readChildNodeAsFloat("car", car, "rearExtent", &mRearExtent);
78     }
79 
80     //
81     // Read display layout information
82     //
83     {
84         Json::Value displayArray = rootNode["displays"];
85         if (!displayArray.isArray()) {
86             printf("Invalid configuration format -- we expect an array of displays\n");
87             return false;
88         }
89 
90         mDisplays.reserve(displayArray.size());
91         for (auto&& node : displayArray) {
92             DisplayInfo info;
93             info.port = node.get("displayPort", 0).asUInt();
94             info.function = node.get("function", "").asCString();
95             info.frontRangeInCarSpace = node.get("frontRange", -1).asFloat();
96             info.rearRangeInCarSpace = node.get("rearRange", -1).asFloat();
97 
98             mDisplays.emplace_back(info);
99         }
100     }
101 
102     //
103     // Car top view texture properties for top down view
104     //
105     {
106         Json::Value graphicNode = rootNode["graphic"];
107         if (!graphicNode.isObject()) {
108             printf("Invalid configuration format -- we expect a graphic description\n");
109             return false;
110         }
111         complete &=
112                 readChildNodeAsFloat("graphic", graphicNode, "frontPixel", &mCarGraphicFrontPixel);
113         complete &=
114                 readChildNodeAsFloat("display", graphicNode, "rearPixel", &mCarGraphicRearPixel);
115     }
116 
117     //
118     // Read camera information
119     // NOTE:  Missing positions and angles are not reported, but instead default to zero
120     //
121     {
122         Json::Value cameraArray = rootNode["cameras"];
123         if (!cameraArray.isArray()) {
124             printf("Invalid configuration format -- we expect an array of cameras\n");
125             return false;
126         }
127 
128         mCameras.reserve(cameraArray.size());
129         for (auto&& node : cameraArray) {
130             // Get data from the configuration file
131             Json::Value nameNode = node.get("cameraId", "MISSING");
132             const char* cameraId = nameNode.asCString();
133 
134             Json::Value usageNode = node.get("function", "");
135             const char* function = usageNode.asCString();
136 
137             float yaw = node.get("yaw", 0).asFloat();
138             float pitch = node.get("pitch", 0).asFloat();
139             float roll = node.get("roll", 0).asFloat();
140             float hfov = node.get("hfov", 0).asFloat();
141             float vfov = node.get("vfov", 0).asFloat();
142             bool hflip = node.get("hflip", false).asBool();
143             bool vflip = node.get("vflip", false).asBool();
144 
145             // Wrap the direction angles to be in the 180deg to -180deg range
146             // Rotate 180 in yaw if necessary to flip the pitch into the +/-90degree range
147             pitch = normalizeToPlusMinus180degrees(pitch);
148             if (pitch > 90.0f) {
149                 yaw += 180.0f;
150                 pitch = 180.0f - pitch;
151             }
152             if (pitch < -90.0f) {
153                 yaw += 180.0f;
154                 pitch = -180.0f + pitch;
155             }
156             yaw = normalizeToPlusMinus180degrees(yaw);
157             roll = normalizeToPlusMinus180degrees(roll);
158 
159             // Range check the FOV values to ensure they are postive and less than 180degrees
160             if (hfov > 179.0f) {
161                 printf("Pathological horizontal field of view %f clamped to 179 degrees\n", hfov);
162                 hfov = 179.0f;
163             }
164             if (hfov < 1.0f) {
165                 printf("Pathological horizontal field of view %f clamped to 1 degree\n", hfov);
166                 hfov = 1.0f;
167             }
168             if (vfov > 179.0f) {
169                 printf("Pathological horizontal field of view %f clamped to 179 degrees\n", vfov);
170                 vfov = 179.0f;
171             }
172             if (vfov < 1.0f) {
173                 printf("Pathological horizontal field of view %f clamped to 1 degree\n", vfov);
174                 vfov = 1.0f;
175             }
176 
177             // Store the camera info (converting degrees to radians in the process)
178             CameraInfo info;
179             info.position[0] = node.get("x", 0).asFloat();
180             info.position[1] = node.get("y", 0).asFloat();
181             info.position[2] = node.get("z", 0).asFloat();
182             info.yaw = yaw * kDegreesToRadians;
183             info.pitch = pitch * kDegreesToRadians;
184             info.roll = roll * kDegreesToRadians;
185             info.hfov = hfov * kDegreesToRadians;
186             info.vfov = vfov * kDegreesToRadians;
187             info.hflip = hflip;
188             info.vflip = vflip;
189             info.cameraId = cameraId;
190             info.function = function;
191 
192             mCameras.emplace_back(info);
193         }
194     }
195 
196     // If we got this far, we were successful as long as we found all our child fields
197     return complete;
198 }
199