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