/********************************************************************* * Software License Agreement (AGPL-3 License) * * OpenViBE Designer * Based on OpenViBE V1.1.0, Copyright (C) Inria, 2006-2015 * Copyright (C) Inria, 2015-2017,V1.0 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. * If not, see . */ #include "mCRendererTopo.hpp" #include "m_RendererTools.hpp" #include namespace OpenViBE { namespace AdvancedVisualization { const bool MULTI_SLICE = false; namespace { const size_t S = 1000; // Legendre polynomials // http://en.wikipedia.org/wiki/Legendre_polynomials void legendre(const size_t n, const double x, std::vector& legendres) { legendres.resize(n + 1); legendres[0] = 1; legendres[1] = x; for (size_t i = 2; i <= n; ++i) { const double invi = 1. / i; legendres[i] = (2 - invi) * x * legendres[i - 1] - (1 - invi) * legendres[i - 2]; } } // G function : // Spherical splines for scalp potential and current density mapping // http://www.sciencedirect.com/science/article/pii/0013469489901806 double g(const size_t n, const size_t m, const std::vector& legendres) { double result = 0; for (size_t i = 1; i <= n; ++i) { result += (2 * i + 1) / pow(double(i * (i + 1)), int(m)) * legendres[i]; } return result / (4 * M_PI); } // H function : // Spherical splines for scalp potential and current density mapping // http://www.sciencedirect.com/science/article/pii/0013469489901806 double h(const size_t n, const size_t m, const std::vector& legendres) { double result = 0; for (size_t i = 1; i <= n; ++i) { result += (2 * i + 1) / pow(double(i * (i + 1)), int(m - 1)) * legendres[i]; } return result / (4 * M_PI); } // Caching system void build(const size_t n, const size_t m, std::vector& gCache, std::vector& hCache) { gCache.resize(2 * S + 1); hCache.resize(2 * S + 1); for (size_t i = 0; i <= 2 * S; ++i) { std::vector legendres; const double cosine = (double(i) - S) / S; legendre(n, cosine, legendres); gCache[i] = g(n, m, legendres); hCache[i] = h(n, m, legendres); } gCache.push_back(gCache.back()); hCache.push_back(hCache.back()); } double cache(const double x, std::vector& rCache) { if (x < -1) { return rCache[0]; } if (x > 1) { return rCache[2 * S]; } double t = (x + 1) * S; const int i1 = int(t); const int i2 = int(t + 1); t -= i1; return rCache[i1] * (1 - t) + rCache[i2] * t; } } // namespace void CRendererTopo::rebuild(const CRendererContext& ctx) { IRenderer::rebuild(ctx); this->rebuild3DMeshesPre(ctx); // Projects electrode coordinates to 3D mesh std::vector projectedPositions; std::vector positions; positions.resize(ctx.getChannelCount()); for (size_t i = 0; i < ctx.getChannelCount(); ++i) { ctx.getChannelLocalisation(i, positions[i].x, positions[i].y, positions[i].z); } m_scalp.project(projectedPositions, positions); m_projectedPositions = projectedPositions; #if 0 m_projectedPositions.resize(ctx.getChannelCount()); for (size_t i = 0; i < ctx.getChannelCount(); ++i) { CVertex p, q; ctx.getChannelLocalisation(i, p.x, p.y, p.z); for (size_t j = 0; j < m_scalp.m_Triangles.size(); j += 3) { size_t i1, i2, i3; i1 = m_scalp.m_Triangles[j]; i2 = m_scalp.m_Triangles[j + 1]; i3 = m_scalp.m_Triangles[j + 2]; CVertex v1, v2, v3; v1 = m_scalp.m_vertex[i1]; v2 = m_scalp.m_vertex[i2]; v3 = m_scalp.m_vertex[i3]; CVertex e1(v1, v2); CVertex e2(v1, v3); CVertex n = CVertex::cross(e1, e2).normalize(); float t = CVertex::dot(v1, n) / CVertex::dot(p, n); q.x = t * p.x; q.y = t * p.y; q.z = t * p.z; if (CVertex::isInTriangle(q, v1, v2, v3) && t >= 0) { m_projectedPositions[i].x = q.x; m_projectedPositions[i].y = q.y; m_projectedPositions[i].z = q.z; } } if (m_projectedPositions[i].x == 0 && m_projectedPositions[i].y == 0 && m_projectedPositions[i].z == 0) { // ::printf("Could not project coordinates on mesh for channel %i [%s]\n", i+1, rContext.getChannelName(i).c_str()); } } #endif // Generates transformation matrices based spherical spline interpolations const size_t m = 3; const auto n = size_t(pow(10., 10. / (2 * m - 2))); std::vector gCaches; std::vector hCaches; build(n, m, gCaches, hCaches); const size_t nc = ctx.getChannelCount(); const size_t vc = m_scalp.m_Vertices.size(); A = Eigen::MatrixXd(nc + 1, nc + 1); A(nc, nc) = 0; for (size_t i = 0; i < nc; ++i) { A(i, nc) = 1; A(nc, i) = 1; for (size_t j = 0; j <= i; ++j) { CVertex v1, v2; ctx.getChannelLocalisation(i, v1.x, v1.y, v1.z); ctx.getChannelLocalisation(j, v2.x, v2.y, v2.z); const double cosine = CVertex::dot(v1, v2); A(i, j) = cache(cosine, gCaches); A(j, i) = cache(cosine, gCaches); } } B = Eigen::MatrixXd(vc + 1, nc + 1); D = Eigen::MatrixXd(vc + 1, nc + 1); B(vc, nc) = 0; D(vc, nc) = 0; for (size_t i = 0; i < vc; ++i) { B(i, nc) = 1; D(i, nc) = 1; for (size_t j = 0; j < nc; ++j) { B(vc, j) = 1; D(vc, j) = 1; CVertex v1, v2; v1 = m_scalp.m_Vertices[i]; v1.normalize(); ctx.getChannelLocalisation(j, v2.x, v2.y, v2.z); const double cosine = CVertex::dot(v1, v2); B(i, j) = cache(cosine, gCaches); D(i, j) = cache(cosine, hCaches); } } Ai = A.inverse(); // Post processed 3D meshes when needed this->rebuild3DMeshesPost(ctx); // Rebuilds texture coordinates array if (MULTI_SLICE) { m_interpolatedSamples.clear(); m_interpolatedSamples.resize(m_nSample, Eigen::VectorXd::Zero(m_scalp.m_Vertices.size())); } // Finalizes m_historyIdx = 0; } // V has sensor potentials // W has interpolated potentials // Z has interpolated current densities void CRendererTopo::interpolate(const Eigen::VectorXd& v, Eigen::VectorXd& w, Eigen::VectorXd& z) const { Eigen::VectorXd c = Ai * v; w = B * c; c[v.size() - 1] = 0; z = D * c; } void CRendererTopo::refresh(const CRendererContext& ctx) { IRenderer::refresh(ctx); if (!m_nHistory) { return; } size_t nc = ctx.getChannelCount(); const size_t vc = m_scalp.m_Vertices.size(); std::vector samples; Eigen::VectorXd v = Eigen::VectorXd::Zero(nc + 1); Eigen::VectorXd w; Eigen::VectorXd z; if (!MULTI_SLICE) { this->getSampleAtERPFraction(m_erpFraction, samples); for (size_t i = 0; i < nc; ++i) { v(i) = samples[i]; } this->interpolate(v, w, z); for (size_t j = 0; j < vc; ++j) { m_scalp.m_Vertices[j].u = float(w(j)); } } else { if (m_nHistory >= m_nSample) { for (size_t k = 0; k < m_nSample; ++k) { for (size_t i = 0; i < nc; ++i) { v(i) = m_history[i][m_nHistory - m_nSample + k]; } this->interpolate(v, w, z); m_interpolatedSamples[k] = w; } } } m_historyIdx = m_nHistory; } bool CRendererTopo::render(const CRendererContext& ctx) { if (!ctx.getSelectedCount() || m_scalp.m_Vertices.empty() || !m_nHistory) { return false; } const float d = 3.5; // ::glEnable(GL_DEPTH_TEST); // ::glDisable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPerspective(60, ctx.getAspect(), .01, 100); glTranslatef(0, 0, -d); glRotatef(ctx.getRotationX() * 10, 1, 0, 0); glRotatef(ctx.getRotationY() * 10, 0, 1, 0); glMatrixMode(GL_TEXTURE); glPushMatrix(); glScalef(ctx.getScale(), 1, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(ctx.getZoom(), ctx.getZoom(), ctx.getZoom()); // Now renders glPushMatrix(); #if 1 glTranslatef(0, .5F, 0); glRotatef(19, 1, 0, 0); glTranslatef(0, -.2F, .35F); // ::glScalef(1.8f, 1.8f, 1.8f); #else ::glRotatef(19, 1, 0, 0); ::glTranslatef(0, -.2f, .35f); // ::glScalef(1.8f, 1.8f, 1.8f); #endif if (ctx.isFaceMeshVisible()) { glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_TEXTURE_1D); if (!m_face.m_Triangles.empty()) { if (!m_face.m_Normals.empty()) { glEnable(GL_LIGHTING); glEnableClientState(GL_NORMAL_ARRAY); } glColor3f(m_face.m_Color[0], m_face.m_Color[1], m_face.m_Color[2]); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, sizeof(CVertex), &m_face.m_Vertices[0].x); if (!m_face.m_Normals.empty()) { glNormalPointer(GL_FLOAT, sizeof(CVertex), &m_face.m_Normals[0].x); } glDrawElements(GL_TRIANGLES, GLsizei(m_face.m_Triangles.size()), GL_UNSIGNED_INT, &m_face.m_Triangles[0]); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_LIGHTING); } } if (ctx.isScalpMeshVisible()) { glEnable(GL_TEXTURE_1D); if (!m_scalp.m_Triangles.empty()) { if (!m_scalp.m_Normals.empty()) { glEnable(GL_LIGHTING); glEnableClientState(GL_NORMAL_ARRAY); } glColor3f(m_scalp.m_Color[0], m_scalp.m_Color[1], m_scalp.m_Color[2]); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(3, GL_FLOAT, sizeof(CVertex), &m_scalp.m_Vertices[0].x); if (!m_scalp.m_Normals.empty()) { glNormalPointer(GL_FLOAT, sizeof(CVertex), &m_scalp.m_Normals[0].x); } if (!MULTI_SLICE) { glColor3f(1, 1, 1); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); glTexCoordPointer(1, GL_FLOAT, sizeof(CVertex), &m_scalp.m_Vertices[0].u); glDrawElements(GL_TRIANGLES, GLsizei(m_scalp.m_Triangles.size()), GL_UNSIGNED_INT, &m_scalp.m_Triangles[0]); } else { glColor4f(1.F, 1.F, 1.F, 4.F / m_nSample); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); for (size_t i = 0; i < m_nSample; ++i) { float scale = 1.F + i * 0.25F / m_nSample; glPushMatrix(); glScalef(scale, scale, scale); glTexCoordPointer(1, GL_DOUBLE, 0, &m_interpolatedSamples[i][0]); glDrawElements(GL_TRIANGLES, GLsizei(m_scalp.m_Triangles.size()), GL_UNSIGNED_INT, &m_scalp.m_Triangles[0]); glPopMatrix(); } } glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_LIGHTING); } } glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_TEXTURE_1D); glLineWidth(3); for (size_t j = 0; j < ctx.getChannelCount(); ++j) { const float scale = .025F; const CVertex v = m_projectedPositions[j]; //ctx.getChannelLocalisation(j, v.x, v.y, v.z); glPushMatrix(); glTranslatef(v.x, v.y, v.z); glScalef(scale, scale, scale); const float value = ctx.isSelected(j) ? 1.0F : 0.2F; const std::array color = { value, value, value }; glColor3fv(color.data()); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); cube(); glColor3f(0, 0, 0); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); cube(); glPopMatrix(); } glPopMatrix(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (ctx.getCheckBoardVisibility()) { this->drawCoordinateSystem(); } glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_TEXTURE); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); return true; } } // namespace AdvancedVisualization } // namespace OpenViBE