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 <fstream>
21 #include <math.h>
22 #include <assert.h>
23 
24 
25 static const float kDegreesToRadians = M_PI / 180.0f;
26 
27 
normalizeToPlusMinus180degrees(float theta)28 static float normalizeToPlusMinus180degrees(float theta) {
29     const float wraps = floor((theta+180.0f) / 360.0f);
30     return theta - wraps*360.0f;
31 }
32 
33 
readChildNodeAsFloat(const char * groupName,const Json::Value & parentNode,const char * childName,float * value)34 static bool readChildNodeAsFloat(const char* groupName,
35                                  const Json::Value& parentNode,
36                                  const char* childName,
37                                  float* value) {
38     // Must have a place to put the value!
39     assert(value);
40 
41     Json::Value childNode = parentNode[childName];
42     if (!childNode.isNumeric()) {
43         printf("Missing or invalid field %s in record %s", childName, groupName);
44         return false;
45     }
46 
47     *value = childNode.asFloat();
48     return true;
49 }
50 
51 
ReadChildNodeAsUint(const char * groupName,const Json::Value & parentNode,const char * childName,unsigned * value)52 static bool ReadChildNodeAsUint(const char* groupName,
53                                 const Json::Value& parentNode,
54                                 const char* childName,
55                                 unsigned* value) {
56     // Must have a place to put the value!
57     assert(value);
58 
59     Json::Value childNode = parentNode[childName];
60     if (!childNode.isNumeric()) {
61         printf("Missing or invalid field %s in record %s", childName, groupName);
62         return false;
63     }
64 
65     *value = childNode.asUInt();
66     return true;
67 }
68 
69 
initialize(const char * configFileName)70 bool ConfigManager::initialize(const char* configFileName)
71 {
72     bool complete = true;
73 
74     // Set up a stream to read in the input file
75     std::ifstream configStream(configFileName);
76 
77     // Parse the stream into JSON objects
78     Json::Reader reader;
79     Json::Value rootNode;
80     bool parseOk = reader.parse(configStream, rootNode, false /* don't need comments */);
81     if (!parseOk) {
82         printf("Failed to read configuration file %s\n", configFileName);
83         printf("%s\n", reader.getFormatedErrorMessages().c_str());
84         return false;
85     }
86 
87 
88     //
89     // Read car information
90     //
91     {
92         Json::Value car = rootNode["car"];
93         if (!car.isObject()) {
94             printf("Invalid configuration format -- we expect a car description\n");
95             return false;
96         }
97         complete &= readChildNodeAsFloat("car", car, "width",       &mCarWidth);
98         complete &= readChildNodeAsFloat("car", car, "wheelBase",   &mWheelBase);
99         complete &= readChildNodeAsFloat("car", car, "frontExtent", &mFrontExtent);
100         complete &= readChildNodeAsFloat("car", car, "rearExtent",  &mRearExtent);
101     }
102 
103 
104     //
105     // Read display layout information
106     //
107     {
108         Json::Value displayNode = rootNode["display"];
109         if (!displayNode.isObject()) {
110             printf("Invalid configuration format -- we expect a display description\n");
111             return false;
112         }
113         complete &= ReadChildNodeAsUint("display", displayNode, "width",      &mPixelWidth);
114         complete &= ReadChildNodeAsUint("display", displayNode, "height",     &mPixelHeight);
115         complete &= readChildNodeAsFloat("display", displayNode, "frontRange", &mFrontRangeInCarSpace);
116         complete &= readChildNodeAsFloat("display", displayNode, "rearRange",  &mRearRangeInCarSpace);
117     }
118 
119 
120     //
121     // Car top view texture properties for top down view
122     //
123     {
124         Json::Value graphicNode = rootNode["graphic"];
125         if (!graphicNode.isObject()) {
126             printf("Invalid configuration format -- we expect a graphic description\n");
127             return false;
128         }
129         complete &= readChildNodeAsFloat("graphic", graphicNode, "frontPixel", &mCarGraphicFrontPixel);
130         complete &= readChildNodeAsFloat("display", graphicNode, "rearPixel",  &mCarGraphicRearPixel);
131     }
132 
133 
134     //
135     // Read camera information
136     // NOTE:  Missing positions and angles are not reported, but instead default to zero
137     //
138     {
139         Json::Value cameraArray = rootNode["cameras"];
140         if (!cameraArray.isArray()) {
141             printf("Invalid configuration format -- we expect an array of cameras\n");
142             return false;
143         }
144 
145         mCameras.reserve(cameraArray.size());
146         for (auto&& node: cameraArray) {
147             // Get data from the configuration file
148             Json::Value nameNode = node.get("cameraId", "MISSING");
149             const char *cameraId = nameNode.asCString();
150             printf("Loading camera %s\n", cameraId);
151 
152             Json::Value usageNode = node.get("function", "");
153             const char *function = usageNode.asCString();
154 
155             float yaw   = node.get("yaw", 0).asFloat();
156             float pitch = node.get("pitch", 0).asFloat();
157             float hfov  = node.get("hfov", 0).asFloat();
158             float vfov  = node.get("vfov", 0).asFloat();
159 
160             // Wrap the direction angles to be in the 180deg to -180deg range
161             // Rotate 180 in yaw if necessary to flip the pitch into the +/-90degree range
162             pitch = normalizeToPlusMinus180degrees(pitch);
163             if (pitch > 90.0f) {
164                 yaw += 180.0f;
165                 pitch = 180.0f - pitch;
166             }
167             if (pitch < -90.0f) {
168                 yaw += 180.0f;
169                 pitch = -180.0f + pitch;
170             }
171             yaw = normalizeToPlusMinus180degrees(yaw);
172 
173             // Range check the FOV values to ensure they are postive and less than 180degrees
174             if (hfov > 179.0f) {
175                 printf("Pathological horizontal field of view %f clamped to 179 degrees\n", hfov);
176                 hfov = 179.0f;
177             }
178             if (hfov < 1.0f) {
179                 printf("Pathological horizontal field of view %f clamped to 1 degree\n", hfov);
180                 hfov = 1.0f;
181             }
182             if (vfov > 179.0f) {
183                 printf("Pathological horizontal field of view %f clamped to 179 degrees\n", vfov);
184                 vfov = 179.0f;
185             }
186             if (vfov < 1.0f) {
187                 printf("Pathological horizontal field of view %f clamped to 1 degree\n", vfov);
188                 vfov = 1.0f;
189             }
190 
191             // Store the camera info (converting degrees to radians in the process)
192             CameraInfo info;
193             info.position[0] = node.get("x", 0).asFloat();
194             info.position[1] = node.get("y", 0).asFloat();
195             info.position[2] = node.get("z", 0).asFloat();
196             info.yaw         = yaw   * kDegreesToRadians;
197             info.pitch       = pitch * kDegreesToRadians;
198             info.hfov        = hfov  * kDegreesToRadians;
199             info.vfov        = vfov  * kDegreesToRadians;
200             info.cameraId    = cameraId;
201             info.function    = function;
202 
203             mCameras.push_back(info);
204         }
205     }
206 
207     // If we got this far, we were successful as long as we found all our child fields
208     return complete;
209 }
210