KiCad PCB EDA Suite
Loading...
Searching...
No Matches
3d_spheres_gizmo.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright 2025, Damjan Prerad <[email protected]>
5 * Copyright (C) The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <gal/opengl/kiglew.h> // Must be included first
26#include <glm/geometric.hpp>
27
28#include "3d_spheres_gizmo.h"
29
30
32{
33 if( m_quadric )
34 {
35 gluDeleteQuadric( m_quadric );
36 m_quadric = nullptr;
37 }
38}
39
40
41SPHERES_GIZMO::SPHERES_GIZMO( int aGizmoPosX, int aGizmoPosY )
42{
43 m_gizmoPosX = aGizmoPosX;
44 m_gizmoPosY = aGizmoPosY;
45
46 m_quadric = gluNewQuadric();
47 gluQuadricNormals( m_quadric, GLU_SMOOTH );
48}
49
50
51void SPHERES_GIZMO::setViewport( int ax, int ay, int aWidth, int aHeight )
52{
53 m_viewportX = ax;
54 m_viewportY = ay;
55 m_viewportW = aWidth;
56 m_viewportH = aHeight;
57}
58
59
60std::tuple<int, int, int, int> SPHERES_GIZMO::getViewport() const
61{
62 return std::make_tuple( m_viewportX, m_viewportY, m_viewportW, m_viewportH );
63}
64
65
66void SPHERES_GIZMO::setGizmoPosition( int ax, int ay )
67{
68 m_gizmoPosX = ax;
69 m_gizmoPosY = ay;
70}
71
72
74{
75 glEnable( GL_COLOR_MATERIAL );
76 glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
77
78 const SFVEC4F ambient = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f );
79 const SFVEC4F diffuse = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f );
80 const SFVEC4F emissive = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f );
81 const SFVEC4F specular = SFVEC4F( 0.1f, 0.1f, 0.1f, 1.0f );
82
83 glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, &specular.r );
84 glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 96.0f );
85
86 glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, &ambient.r );
87 glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, &diffuse.r );
88 glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, &emissive.r );
89}
90
91void SPHERES_GIZMO::handleMouseInput( int aMouseX, int aMouseY )
92{
93 int smallViewportW = m_viewportH / 8;
94 int smallViewportH = m_viewportH / 8;
95
96 bool inside = ( aMouseX >= m_gizmoPosX && aMouseX <= m_gizmoPosX + smallViewportW && aMouseY >= m_gizmoPosY
97 && aMouseY <= m_gizmoPosY + smallViewportH );
98
99 if( inside )
100 {
101 m_ndcX = 2.0f * static_cast<float>( aMouseX - m_gizmoPosX ) / smallViewportW - 1.0f;
102 m_ndcY = 2.0f * static_cast<float>( aMouseY - m_gizmoPosY ) / smallViewportH - 1.0f;
103 }
104 else
105 {
106 m_ndcX = -1.0f;
107 m_ndcY = -1.0f;
108 }
109}
110
111
112void SPHERES_GIZMO::render3dSpheresGizmo( glm::mat4 aCameraRotationMatrix )
113{
114 float fov = 60.0f;
115
116 glDisable( GL_CULL_FACE );
117
118 // Set up a square viewport (Y x Y)
119 glViewport( m_gizmoPosX, m_gizmoPosY, m_viewportH / 8, m_viewportH / 8 );
120 glClear( GL_DEPTH_BUFFER_BIT );
121
122 glMatrixMode( GL_PROJECTION );
123 glLoadIdentity();
124
125 gluPerspective( fov, 1.0f, 0.001f, 2.0f * RANGE_SCALE_3D );
126
127 glMatrixMode( GL_MODELVIEW );
128 glLoadIdentity();
129
130 glm::mat4 TranslationMatrix = glm::translate( glm::mat4( 1.0f ), SFVEC3F( 0.0f, 0.0f, -( m_arrowSize * 2.75f ) ) );
131 glm::mat4 ViewMatrix = TranslationMatrix * aCameraRotationMatrix;
132 glLoadMatrixf( glm::value_ptr( ViewMatrix ) );
133
135
136 // Intersection test
137 glm::mat4 proj = glm::perspective( glm::radians( fov ), 1.0f, 0.001f, 2.0f * RANGE_SCALE_3D );
138 glm::mat4 invVP = glm::inverse( proj * ViewMatrix );
139
140 glm::vec4 rayStartNDC( m_ndcX, m_ndcY, -1.0f, 1.0f );
141 glm::vec4 rayEndNDC( m_ndcX, m_ndcY, 1.0f, 1.0f );
142
143 glm::vec4 rayStartWorld = invVP * rayStartNDC;
144 rayStartWorld /= rayStartWorld.w;
145
146 glm::vec4 rayEndWorld = invVP * rayEndNDC;
147 rayEndWorld /= rayEndWorld.w;
148
149 glm::vec3 rayOrigin = glm::vec3( rayStartWorld );
150 glm::vec3 rayDirection = glm::normalize( glm::vec3( rayEndWorld - rayStartWorld ) );
151
152 auto intersects =
153 []( const glm::vec3& aRayOrigin, const glm::vec3& aRayDir, const glm::vec3& aSphereCenter, float aRadius )
154 {
155 glm::vec3 L = aSphereCenter - aRayOrigin;
156 float tca = glm::dot( L, aRayDir );
157 float d2 = glm::dot( L, L ) - tca * tca;
158 return d2 <= aRadius * aRadius;
159 };
160
161 int clickedIndex = -1;
163 for( size_t i = 0; i < m_spheres.size(); ++i )
164 {
165 const auto& sphere = m_spheres[i];
166 if( intersects( rayOrigin, rayDirection, sphere.m_position, sphere.m_radius ) )
167 {
168 clickedIndex = static_cast<int>( i );
169
171 break; // only pick the first intersected sphere
172 }
173 }
174
175 // Update colors
176 for( size_t i = 0; i < m_spheres.size(); ++i )
177 {
178 if( static_cast<int>( i ) == clickedIndex )
179 {
180 m_spheres[i].m_color = { 1.0f, 1.0f, 1.0f }; // White
181 }
182 else
183 {
184 m_spheres[i].m_color = m_spheres[i].m_originalColor; // Restore default
185 }
186 }
187
188 // Intersection test done
189
190 auto drawBillboardCircle = []( const glm::vec3& aCenter, float aRadius, const glm::vec3& aColor,
191 const glm::vec3& aCamRight, const glm::vec3& aCamUp, int aSegments = 64 )
192 {
193 float thickness = aRadius * 0.4f;
194 glColor3f( aColor.r, aColor.g, aColor.b );
195
196 glBegin( GL_TRIANGLE_STRIP );
197 for( int i = 0; i <= aSegments; ++i )
198 {
199 float angle = 2.0f * glm::pi<float>() * i / aSegments;
200 glm::vec3 dir = cos( angle ) * aCamRight + sin( angle ) * aCamUp;
201
202 glm::vec3 outer = aCenter + dir * ( aRadius + thickness * 0.5f );
203 glm::vec3 inner = aCenter + dir * ( aRadius - thickness * 0.5f );
204
205 glVertex3f( outer.x, outer.y, outer.z );
206 glVertex3f( inner.x, inner.y, inner.z );
207 }
208 glEnd();
209 };
210
211 glm::vec3 camRight( aCameraRotationMatrix[0][0], aCameraRotationMatrix[1][0], aCameraRotationMatrix[2][0] );
212 glm::vec3 camUp( aCameraRotationMatrix[0][1], aCameraRotationMatrix[1][1], aCameraRotationMatrix[2][1] );
213
214 glEnable( GL_BLEND );
215 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
216
217 for( const auto& sphere : m_spheres )
218 {
219 glColor4f( sphere.m_color.r, sphere.m_color.g, sphere.m_color.b, 0.3f );
220 glPushMatrix();
221 glTranslatef( sphere.m_position.x, sphere.m_position.y, sphere.m_position.z );
222 if( m_quadric )
223 {
224 gluSphere( m_quadric, sphere.m_radius, 32, 32 );
225 }
226 glPopMatrix();
227
228 drawBillboardCircle( sphere.m_position, sphere.m_radius, sphere.m_color, camRight, camUp );
229 }
230
231 // Draw sphere labels
232
233 glDisable( GL_DEPTH_TEST );
234 glDisable( GL_LIGHTING );
235
236 std::array<std::string, 6> labels = { "X", "", "Y", "", "Z", "" };
237
238 // View direction (camera looks along negative Z in view space)
239 // So we offset a little toward the camera to avoid z-fighting
240 glm::vec3 offset = glm::normalize( -rayDirection ) * 0.02f;
241
242 glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
243
244 auto drawX = []( const glm::vec3& aPos, float aSize, const glm::vec3& aColor, const glm::vec3& aCamRight,
245 const glm::vec3& aCamUp )
246 {
247 glColor3f( aColor.r, aColor.g, aColor.b );
248 glLineWidth( 3.0f );
249
250 float h = aSize * 0.5f;
251
252 // Define two diagonal line directions in camera-facing plane
253 glm::vec3 dir1 = ( -aCamRight + aCamUp ) * h; // one diagonal
254 glm::vec3 dir2 = ( -aCamRight - aCamUp ) * h; // other diagonal
255
256 glBegin( GL_LINES );
257 glVertex3f( ( aPos - dir1 ).x, ( aPos - dir1 ).y, ( aPos - dir1 ).z );
258 glVertex3f( ( aPos + dir1 ).x, ( aPos + dir1 ).y, ( aPos + dir1 ).z );
259
260 glVertex3f( ( aPos - dir2 ).x, ( aPos - dir2 ).y, ( aPos - dir2 ).z );
261 glVertex3f( ( aPos + dir2 ).x, ( aPos + dir2 ).y, ( aPos + dir2 ).z );
262 glEnd();
263 };
264
265 auto drawY = []( const glm::vec3& aPos, float aSize, const glm::vec3& aColor, const glm::vec3& aCamRight,
266 const glm::vec3& aCamUp )
267 {
268 glColor3f( aColor.r, aColor.g, aColor.b );
269 glLineWidth( 3.0f );
270
271 float h = aSize * 0.5f;
272
273 // Top-left and top-right in screen plane
274 glm::vec3 topLeft = aPos + aCamUp * h - aCamRight * h;
275 glm::vec3 topRight = aPos + aCamUp * h + aCamRight * h;
276 glm::vec3 bottom = aPos - aCamUp * h;
277
278 glBegin( GL_LINES );
279 glVertex3f( topLeft.x, topLeft.y, topLeft.z );
280 glVertex3f( aPos.x, aPos.y, aPos.z );
281
282 glVertex3f( topRight.x, topRight.y, topRight.z );
283 glVertex3f( aPos.x, aPos.y, aPos.z );
284
285 glVertex3f( aPos.x, aPos.y, aPos.z );
286 glVertex3f( bottom.x, bottom.y, bottom.z );
287 glEnd();
288 };
289
290 auto drawZ = []( const glm::vec3& aPos, float aSize, const glm::vec3& aColor, const glm::vec3& aCamRight,
291 const glm::vec3& aCamUp )
292 {
293 glColor3f( aColor.r, aColor.g, aColor.b );
294 glLineWidth( 3.0f );
295
296 float h = aSize * 0.5f;
297
298 // Define corners in screen plane relative to camera
299 glm::vec3 topLeft = aPos + aCamUp * h - aCamRight * h;
300 glm::vec3 topRight = aPos + aCamUp * h + aCamRight * h;
301 glm::vec3 bottomLeft = aPos - aCamUp * h - aCamRight * h;
302 glm::vec3 bottomRight = aPos - aCamUp * h + aCamRight * h;
303
304 glBegin( GL_LINE_STRIP );
305 glVertex3f( topLeft.x, topLeft.y, topLeft.z );
306 glVertex3f( topRight.x, topRight.y, topRight.z );
307 glVertex3f( bottomLeft.x, bottomLeft.y, bottomLeft.z );
308 glVertex3f( bottomRight.x, bottomRight.y, bottomRight.z );
309 glEnd();
310 };
311
312 for( size_t i = 0; i < m_spheres.size(); ++i )
313 {
314 if( labels[i].empty() )
315 continue;
316
317 glm::vec3 textPos = m_spheres[i].m_position + offset;
318 const std::string& label = labels[i];
319
320 if( label == "X" )
321 {
322 drawX( textPos, 0.30f, glm::vec3( 0.0f ), camRight, camUp );
323 }
324 else if( label == "Y" )
325 {
326 drawY( textPos, 0.30f, glm::vec3( 0.0f ), camRight, camUp );
327 }
328 else if( label == "Z" )
329 {
330 drawZ( textPos, 0.30f, glm::vec3( 0.0f ), camRight, camUp );
331 }
332 }
333
334 glEnable( GL_LIGHTING );
335 glEnable( GL_DEPTH_TEST );
336
337 // Draw lines only to the positive axis spheres
338 glLineWidth( 2.0f );
339 glBegin( GL_LINES );
340
341 glColor3f( 0.9f, 0.0f, 0.0f ); // X+
342 glVertex3f( 0.0f, 0.0f, 0.0f );
343 glVertex3f( m_arrowSize, 0.0f, 0.0f );
344
345 glColor3f( 0.0f, 0.9f, 0.0f ); // Y+
346 glVertex3f( 0.0f, 0.0f, 0.0f );
347 glVertex3f( 0.0f, m_arrowSize, 0.0f );
348
349 glColor3f( 0.0f, 0.0f, 0.9f ); // Z+
350 glVertex3f( 0.0f, 0.0f, 0.0f );
351 glVertex3f( 0.0f, 0.0f, m_arrowSize );
352
353 glEnd();
354
355 glEnable( GL_CULL_FACE );
356}
357
359{
361}
362
364{
366 m_ndcX = -1.0f;
367 m_ndcY = -1.0f;
368}
#define RANGE_SCALE_3D
This defines the range that all coord will have to be rendered.
Definition: board_adapter.h:66
GizmoSphereSelection
Enum to indicate which sphere (direction) is selected.
void handleMouseInput(int aMouseX, int aMouseY)
void render3dSpheresGizmo(glm::mat4 aCameraRotationMatrix)
GLUquadric * m_quadric
const float m_arrowSize
std::array< GizmoSphere, 6 > m_spheres
List of all directional gizmo spheres.
GizmoSphereSelection getSelectedGizmoSphere() const
std::tuple< int, int, int, int > getViewport() const
void setViewport(int ax, int ay, int aWidth, int aHeight)
GizmoSphereSelection m_selectedGizmoSphere
SPHERES_GIZMO(int aGizmoPosX, int aGizmoPosY)
void resetSelectedGizmoSphere()
void setGizmoPosition(int ax, int ay)
static bool empty(const wxTextEntryBase *aCtrl)
glm::vec3 SFVEC3F
Definition: xv3d_types.h:44
glm::vec4 SFVEC4F
Definition: xv3d_types.h:46