1 /******************************************************************************
2
3 This source file is part of the Avogadro project.
4
5 Copyright 2013 Kitware, Inc.
6
7 This source code is released under the New BSD License, (the "License").
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14
15 ******************************************************************************/
16
17 #include "overlayaxes.h"
18
19 #include <avogadro/rendering/avogadrogl.h>
20 #include <avogadro/rendering/camera.h>
21 #include <avogadro/rendering/geometrynode.h>
22 #include <avogadro/rendering/groupnode.h>
23 #include <avogadro/rendering/meshgeometry.h>
24
25 #include <avogadro/core/array.h>
26 #include <avogadro/core/vector.h>
27
28 #include <Eigen/Geometry>
29
30 using Avogadro::Core::Array;
31 using Avogadro::Rendering::Camera;
32 using Avogadro::Rendering::GeometryNode;
33 using Avogadro::Rendering::GroupNode;
34 using Avogadro::Rendering::MeshGeometry;
35 using Avogadro::Vector3f;
36 using Eigen::Affine3f;
37
38 namespace {
39 const static float M_PI_F = 3.14159265358979323846f;
40
41 // Mesh class that overrides the camera used in rendering.
42 class CustomMesh : public MeshGeometry
43 {
44 public:
CustomMesh()45 CustomMesh() { setRenderPass(Avogadro::Rendering::Overlay3DPass); }
~CustomMesh()46 ~CustomMesh() override {}
47
48 void render(const Camera& camera) override;
49 };
50
render(const Camera & camera)51 void CustomMesh::render(const Camera& camera)
52 {
53 // Swap in a new viewport/camera for the overlay
54 /// @todo This is messy, it would be better to specify camera/viewport in a
55 /// group/geometry node that the renderer could apply to all children.
56
57 // Keep the rotation, lose the translation:
58 Affine3f mv(camera.modelView());
59 mv.matrix().block<3, 1>(0, 3) = Vector3f::Zero();
60
61 // Save the actual viewport - works better on high resolution screens
62 GLint viewport[4];
63 glGetIntegerv(GL_VIEWPORT, viewport);
64
65 // The largest window dimension, used to scale the axes
66 // (again, grab from the actual viewport)
67 const int maxDim = std::max(viewport[2], viewport[3]);
68
69 Camera meshCamera(camera);
70 meshCamera.setViewport(maxDim / 10, maxDim / 10);
71 meshCamera.setModelView(mv);
72 meshCamera.calculateOrthographic(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
73
74 glViewport(static_cast<GLint>(10), static_cast<GLsizei>(10),
75 static_cast<GLint>(meshCamera.width()),
76 static_cast<GLsizei>(meshCamera.height()));
77
78 MeshGeometry::render(meshCamera);
79
80 glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
81 }
82 } // end anon namespace
83
84 namespace Avogadro {
85 namespace QtPlugins {
86
87 class OverlayAxes::RenderImpl
88 {
89 public:
90 RenderImpl();
91 ~RenderImpl();
92
93 CustomMesh* mesh;
94
95 private:
96 void buildMesh();
97 // axis must be normalized:
98 void addAxis(const Vector3f& axis, const Vector3ub& color);
99 };
100
RenderImpl()101 OverlayAxes::RenderImpl::RenderImpl() : mesh(new CustomMesh)
102 {
103 buildMesh();
104 }
105
~RenderImpl()106 OverlayAxes::RenderImpl::~RenderImpl()
107 {
108 delete mesh;
109 }
110
buildMesh()111 void OverlayAxes::RenderImpl::buildMesh()
112 {
113 addAxis(Vector3f(1.f, 0.f, 0.f), Vector3ub(255, 0, 0));
114 addAxis(Vector3f(0.f, 1.f, 0.f), Vector3ub(0, 255, 0));
115 addAxis(Vector3f(0.f, 0.f, 1.f), Vector3ub(0, 0, 255));
116 }
117
addAxis(const Vector3f & axis,const Vector3ub & color)118 void OverlayAxes::RenderImpl::addAxis(const Vector3f& axis,
119 const Vector3ub& color)
120 {
121 mesh->setColor(color);
122
123 // Number of angular samples:
124 const unsigned int res = 12;
125 const float resf = static_cast<float>(res);
126 // Cylinder length:
127 const float cylLength = .75f;
128 // Cylinder radius:
129 const float cylRadius = 0.0625f;
130 // Cone length:
131 const float coneLength = .25f;
132 // Cone radius:
133 const float coneRadius = .125f;
134
135 // Some vectors that will simplify things later:
136 const Vector3f origin(0.f, 0.f, 0.f);
137 const Vector3f cylVector(axis * cylLength);
138 const Vector3f coneVector(axis * coneLength);
139 const Vector3f axisVector(coneVector + cylVector);
140 const Vector3f radialUnit(axis.unitOrthogonal());
141
142 // Index offsets:
143 const unsigned int coneBaseOffset = 0;
144 const unsigned int coneBaseRadialsOffset = coneBaseOffset + 1;
145 const unsigned int coneSideRadialsOffset = coneBaseRadialsOffset + res;
146 const unsigned int coneTipsOffset = coneSideRadialsOffset + res;
147 const unsigned int cylBaseRadialsOffset = coneTipsOffset + res;
148 const unsigned int cylTopRadialsOffset = cylBaseRadialsOffset + res;
149 const unsigned int numVertices = cylTopRadialsOffset + res;
150
151 // Allocate arrays:
152 Array<Vector3f> vertices(numVertices);
153 Array<Vector3f> normals(numVertices);
154
155 // This point doesn't change:
156 vertices[coneBaseOffset] = origin + cylVector;
157 normals[coneBaseOffset] = -axis;
158
159 // Initial radial:
160 Vector3f radial(radialUnit);
161
162 // Create radial transform:
163 Eigen::Affine3f xform(Eigen::AngleAxisf(2.f * M_PI_F / resf, axis));
164
165 // Build vertex list:
166 const Vector3f coneTip(origin + axisVector);
167 Vector3f coneRadial;
168 Vector3f coneSideNormal;
169 Vector3f cylRadial;
170 for (unsigned int i = 0; i < res; ++i) {
171 coneRadial = origin + cylVector + (radial * coneRadius);
172 // Calculating the cone side normal:
173 //
174 // /| (z points out of screen) z = coneVector x coneRadial
175 // / | a = coneVector - coneRadial
176 // a / | coneVector n = z x a
177 // / |
178 // / | (n is the normal for vector a)
179 // /_____|
180 // coneRadial
181 coneSideNormal = -(coneVector.cross(coneRadial))
182 .cross(coneVector - coneRadial)
183 .normalized();
184
185 vertices[coneBaseRadialsOffset + i] = coneRadial;
186 normals[coneBaseRadialsOffset + i] = -axis;
187
188 vertices[coneSideRadialsOffset + i] = coneRadial;
189 normals[coneSideRadialsOffset + i] = coneSideNormal;
190
191 cylRadial = origin + (radial * cylRadius);
192 vertices[cylBaseRadialsOffset + i] = cylRadial;
193 normals[cylBaseRadialsOffset + i] = radial;
194
195 vertices[cylTopRadialsOffset + i] = cylVector + cylRadial;
196 normals[cylTopRadialsOffset + i] = radial;
197
198 radial = xform * radial;
199 }
200
201 // Cone tip normals are averages of the side radial normals:
202 for (unsigned int i = 0; i < res; ++i) {
203 unsigned int ind1 = coneSideRadialsOffset + i;
204 unsigned int ind2 = coneSideRadialsOffset + ((i + 1) % res);
205 vertices[coneTipsOffset + i] = coneTip;
206 normals[coneTipsOffset + i] = (normals[ind1] + normals[ind2]).normalized();
207 }
208
209 // Add the vertices and get our index offset:
210 const unsigned int baseOffset = mesh->addVertices(vertices, normals);
211
212 // Stitch the vertices together:
213 Array<unsigned int> triangles(3 * 4 * res); // 3 verts * 4 tri * nsamples
214 unsigned int* ptr = triangles.data();
215 for (unsigned int i = 0; i < res; ++i) {
216 unsigned int i2 = (i + 1) % res;
217 // Cone sides:
218 *ptr++ = baseOffset + coneTipsOffset + i;
219 *ptr++ = baseOffset + coneSideRadialsOffset + i;
220 *ptr++ = baseOffset + coneSideRadialsOffset + i2;
221
222 // Cone base:
223 *ptr++ = baseOffset + coneBaseRadialsOffset + i;
224 *ptr++ = baseOffset + coneBaseOffset;
225 *ptr++ = baseOffset + coneBaseRadialsOffset + i2;
226
227 // Cylinder side quad:
228 *ptr++ = baseOffset + cylTopRadialsOffset + i;
229 *ptr++ = baseOffset + cylBaseRadialsOffset + i;
230 *ptr++ = baseOffset + cylTopRadialsOffset + i2;
231
232 *ptr++ = baseOffset + cylBaseRadialsOffset + i;
233 *ptr++ = baseOffset + cylTopRadialsOffset + i2;
234 *ptr++ = baseOffset + cylBaseRadialsOffset + i2;
235 }
236
237 // Add the indices to the mesh
238 mesh->addTriangles(triangles);
239 }
240
OverlayAxes(QObject * p)241 OverlayAxes::OverlayAxes(QObject* p)
242 : ScenePlugin(p), m_enabled(true), m_render(new RenderImpl)
243 {
244 }
245
~OverlayAxes()246 OverlayAxes::~OverlayAxes()
247 {
248 delete m_render;
249 }
250
process(const Core::Molecule &,Rendering::GroupNode & node)251 void OverlayAxes::process(const Core::Molecule&, Rendering::GroupNode& node)
252 {
253 GeometryNode* geo = new GeometryNode;
254 // Since our geometry doesn't change, we just make a copy of the pre-built
255 // set of axes.
256 geo->addDrawable(new CustomMesh(*m_render->mesh));
257 node.addChild(geo);
258 }
259
processEditable(const QtGui::RWMolecule &,Rendering::GroupNode & node)260 void OverlayAxes::processEditable(const QtGui::RWMolecule&,
261 Rendering::GroupNode& node)
262 {
263 GeometryNode* geo = new GeometryNode;
264 // Since our geometry doesn't change, we just make a copy of the pre-built
265 // set of axes.
266 geo->addDrawable(new CustomMesh(*m_render->mesh));
267 node.addChild(geo);
268 }
269
isEnabled() const270 bool OverlayAxes::isEnabled() const
271 {
272 return m_enabled;
273 }
274
setEnabled(bool enable)275 void OverlayAxes::setEnabled(bool enable)
276 {
277 m_enabled = enable;
278 }
279 }
280 }
281