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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <kicad_gl/kiglu.h> // Must be included first
22#include <glm/geometric.hpp>
23#include <limits>
24
25#include "3d_spheres_gizmo.h"
26
27
29{
30 if( m_quadric )
31 {
32 gluDeleteQuadric( m_quadric );
33 m_quadric = nullptr;
34 }
35}
36
37
38SPHERES_GIZMO::SPHERES_GIZMO( int aGizmoPosX, int aGizmoPosY )
39{
40 m_gizmoPosX = aGizmoPosX;
41 m_gizmoPosY = aGizmoPosY;
42
43 m_quadric = gluNewQuadric();
44 gluQuadricNormals( m_quadric, GLU_SMOOTH );
45}
46
47
48void SPHERES_GIZMO::setViewport( int ax, int ay, int aWidth, int aHeight )
49{
50 m_viewportX = ax;
51 m_viewportY = ay;
52 m_viewportW = aWidth;
53 m_viewportH = aHeight;
54}
55
56
57std::tuple<int, int, int, int> SPHERES_GIZMO::getViewport() const
58{
59 return std::make_tuple( m_viewportX, m_viewportY, m_viewportW, m_viewportH );
60}
61
62
63void SPHERES_GIZMO::setGizmoPosition( int ax, int ay )
64{
65 m_gizmoPosX = ax;
66 m_gizmoPosY = ay;
67}
68
69
71{
72 glEnable( GL_COLOR_MATERIAL );
73 glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
74
75 const SFVEC4F ambient = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f );
76 const SFVEC4F diffuse = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f );
77 const SFVEC4F emissive = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f );
78 const SFVEC4F specular = SFVEC4F( 0.1f, 0.1f, 0.1f, 1.0f );
79
80 glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, &specular.r );
81 glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 96.0f );
82
83 glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, &ambient.r );
84 glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, &diffuse.r );
85 glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, &emissive.r );
86}
87
88void SPHERES_GIZMO::handleMouseInput( int aMouseX, int aMouseY )
89{
90 int smallViewportW = m_viewportH / 8;
91 int smallViewportH = m_viewportH / 8;
92
93 bool inside = ( aMouseX >= m_gizmoPosX && aMouseX <= m_gizmoPosX + smallViewportW && aMouseY >= m_gizmoPosY
94 && aMouseY <= m_gizmoPosY + smallViewportH );
95
96 if( inside )
97 {
98 m_ndcX = 2.0f * static_cast<float>( aMouseX - m_gizmoPosX ) / smallViewportW - 1.0f;
99 m_ndcY = 2.0f * static_cast<float>( aMouseY - m_gizmoPosY ) / smallViewportH - 1.0f;
100 }
101 else
102 {
103 m_ndcX = -1.0f;
104 m_ndcY = -1.0f;
105 }
106}
107
108
109void SPHERES_GIZMO::render3dSpheresGizmo( glm::mat4 aCameraRotationMatrix )
110{
111 float fov = 60.0f;
112
113 glDisable( GL_CULL_FACE );
114
115 // Set up a square viewport (Y x Y)
116 glViewport( m_gizmoPosX, m_gizmoPosY, m_viewportH / 8, m_viewportH / 8 );
117 glClear( GL_DEPTH_BUFFER_BIT );
118
119 glMatrixMode( GL_PROJECTION );
120 glLoadIdentity();
121
122 gluPerspective( fov, 1.0f, 0.001f, 2.0f * RANGE_SCALE_3D );
123
124 glMatrixMode( GL_MODELVIEW );
125 glLoadIdentity();
126
127 glm::mat4 TranslationMatrix = glm::translate( glm::mat4( 1.0f ), SFVEC3F( 0.0f, 0.0f, -( m_arrowSize * 2.75f ) ) );
128 glm::mat4 ViewMatrix = TranslationMatrix * aCameraRotationMatrix;
129 glLoadMatrixf( glm::value_ptr( ViewMatrix ) );
130
132
133 auto drawBillboardCircle =
134 []( const glm::vec3& aCenter, float aRadius, const glm::vec3& aColor,
135 const glm::vec3& aCamRight, const glm::vec3& aCamUp, int aSegments = 64 )
136 {
137 float thickness = aRadius * 0.4f;
138 glColor3f( aColor.r, aColor.g, aColor.b );
139
140 glBegin( GL_TRIANGLE_STRIP );
141 for( int i = 0; i <= aSegments; ++i )
142 {
143 float angle = 2.0f * glm::pi<float>() * i / aSegments;
144 glm::vec3 dir = cos( angle ) * aCamRight + sin( angle ) * aCamUp;
145
146 glm::vec3 outer = aCenter + dir * ( aRadius + thickness * 0.5f );
147 glm::vec3 inner = aCenter + dir * ( aRadius - thickness * 0.5f );
148
149 glVertex3f( outer.x, outer.y, outer.z );
150 glVertex3f( inner.x, inner.y, inner.z );
151 }
152 glEnd();
153 };
154
155 glm::vec3 camRight( aCameraRotationMatrix[0][0], aCameraRotationMatrix[1][0], aCameraRotationMatrix[2][0] );
156 glm::vec3 camUp( aCameraRotationMatrix[0][1], aCameraRotationMatrix[1][1], aCameraRotationMatrix[2][1] );
157
158 glEnable( GL_BLEND );
159 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
160
161 for( const auto& sphere : m_spheres )
162 {
163 glColor4f( sphere.m_color.r, sphere.m_color.g, sphere.m_color.b, 0.3f );
164 glPushMatrix();
165 glTranslatef( sphere.m_position.x, sphere.m_position.y, sphere.m_position.z );
166 if( m_quadric )
167 {
168 gluSphere( m_quadric, sphere.m_radius, 32, 32 );
169 }
170 glPopMatrix();
171
172 drawBillboardCircle( sphere.m_position, sphere.m_radius, sphere.m_color, camRight, camUp );
173 }
174
175 // Draw sphere labels
176
177 glDisable( GL_DEPTH_TEST );
178 glDisable( GL_LIGHTING );
179
180 std::array<std::string, 6> labels = { "X", "", "Y", "", "Z", "" };
181
182 // View direction (camera looks along negative Z in view space)
183 // So we offset a little toward the camera to avoid z-fighting
184 glm::vec3 camForward( m_cameraRotationMatrix[0][2], m_cameraRotationMatrix[1][2], m_cameraRotationMatrix[2][2] );
185 glm::vec3 offset = camForward * 0.02f;
186
187 glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
188
189 auto drawX =
190 []( const glm::vec3& aPos, float aSize, const glm::vec3& aColor, const glm::vec3& aCamRight,
191 const glm::vec3& aCamUp )
192 {
193 glColor3f( aColor.r, aColor.g, aColor.b );
194 glLineWidth( 3.0f );
195
196 float h = aSize * 0.5f;
197
198 // Define two diagonal line directions in camera-facing plane
199 glm::vec3 dir1 = ( -aCamRight + aCamUp ) * h; // one diagonal
200 glm::vec3 dir2 = ( -aCamRight - aCamUp ) * h; // other diagonal
201
202 glBegin( GL_LINES );
203 glVertex3f( ( aPos - dir1 ).x, ( aPos - dir1 ).y, ( aPos - dir1 ).z );
204 glVertex3f( ( aPos + dir1 ).x, ( aPos + dir1 ).y, ( aPos + dir1 ).z );
205
206 glVertex3f( ( aPos - dir2 ).x, ( aPos - dir2 ).y, ( aPos - dir2 ).z );
207 glVertex3f( ( aPos + dir2 ).x, ( aPos + dir2 ).y, ( aPos + dir2 ).z );
208 glEnd();
209 };
210
211 auto drawY =
212 []( const glm::vec3& aPos, float aSize, const glm::vec3& aColor, const glm::vec3& aCamRight,
213 const glm::vec3& aCamUp )
214 {
215 glColor3f( aColor.r, aColor.g, aColor.b );
216 glLineWidth( 3.0f );
217
218 float h = aSize * 0.5f;
219
220 // Top-left and top-right in screen plane
221 glm::vec3 topLeft = aPos + aCamUp * h - aCamRight * h;
222 glm::vec3 topRight = aPos + aCamUp * h + aCamRight * h;
223 glm::vec3 bottom = aPos - aCamUp * h;
224
225 glBegin( GL_LINES );
226 glVertex3f( topLeft.x, topLeft.y, topLeft.z );
227 glVertex3f( aPos.x, aPos.y, aPos.z );
228
229 glVertex3f( topRight.x, topRight.y, topRight.z );
230 glVertex3f( aPos.x, aPos.y, aPos.z );
231
232 glVertex3f( aPos.x, aPos.y, aPos.z );
233 glVertex3f( bottom.x, bottom.y, bottom.z );
234 glEnd();
235 };
236
237 auto drawZ =
238 []( const glm::vec3& aPos, float aSize, const glm::vec3& aColor, const glm::vec3& aCamRight,
239 const glm::vec3& aCamUp )
240 {
241 glColor3f( aColor.r, aColor.g, aColor.b );
242 glLineWidth( 3.0f );
243
244 float h = aSize * 0.5f;
245
246 // Define corners in screen plane relative to camera
247 glm::vec3 topLeft = aPos + aCamUp * h - aCamRight * h;
248 glm::vec3 topRight = aPos + aCamUp * h + aCamRight * h;
249 glm::vec3 bottomLeft = aPos - aCamUp * h - aCamRight * h;
250 glm::vec3 bottomRight = aPos - aCamUp * h + aCamRight * h;
251
252 glBegin( GL_LINE_STRIP );
253 glVertex3f( topLeft.x, topLeft.y, topLeft.z );
254 glVertex3f( topRight.x, topRight.y, topRight.z );
255 glVertex3f( bottomLeft.x, bottomLeft.y, bottomLeft.z );
256 glVertex3f( bottomRight.x, bottomRight.y, bottomRight.z );
257 glEnd();
258 };
259
260 for( size_t i = 0; i < m_spheres.size(); ++i )
261 {
262 if( labels[i].empty() )
263 continue;
264
265 glm::vec3 textPos = m_spheres[i].m_position + offset;
266 const std::string& label = labels[i];
267
268 if( label == "X" )
269 {
270 drawX( textPos, 0.30f, glm::vec3( 0.0f ), camRight, camUp );
271 }
272 else if( label == "Y" )
273 {
274 drawY( textPos, 0.30f, glm::vec3( 0.0f ), camRight, camUp );
275 }
276 else if( label == "Z" )
277 {
278 drawZ( textPos, 0.30f, glm::vec3( 0.0f ), camRight, camUp );
279 }
280 }
281
282 glEnable( GL_LIGHTING );
283 glEnable( GL_DEPTH_TEST );
284
285 // Draw lines only to the positive axis spheres
286 glLineWidth( 2.0f );
287 glBegin( GL_LINES );
288
289 glColor3f( 0.9f, 0.0f, 0.0f ); // X+
290 glVertex3f( 0.0f, 0.0f, 0.0f );
291 glVertex3f( m_arrowSize, 0.0f, 0.0f );
292
293 glColor3f( 0.0f, 0.9f, 0.0f ); // Y+
294 glVertex3f( 0.0f, 0.0f, 0.0f );
295 glVertex3f( 0.0f, m_arrowSize, 0.0f );
296
297 glColor3f( 0.0f, 0.0f, 0.9f ); // Z+
298 glVertex3f( 0.0f, 0.0f, 0.0f );
299 glVertex3f( 0.0f, 0.0f, m_arrowSize );
300
301 glEnd();
302
303 glEnable( GL_CULL_FACE );
304}
305
310
317
318
319void SPHERES_GIZMO::updateSelection( glm::mat4 aCameraRotationMatrix )
320{
321 m_cameraRotationMatrix = aCameraRotationMatrix;
322
323 float fov = 60.0f;
324 glm::mat4 TranslationMatrix = glm::translate( glm::mat4( 1.0f ), SFVEC3F( 0.0f, 0.0f, -( m_arrowSize * 2.75f ) ) );
325 glm::mat4 ViewMatrix = TranslationMatrix * aCameraRotationMatrix;
326
327 glm::mat4 proj = glm::perspective( glm::radians( fov ), 1.0f, 0.001f, 2.0f * RANGE_SCALE_3D );
328 glm::mat4 invVP = glm::inverse( proj * ViewMatrix );
329
330 glm::vec4 rayStartNDC( m_ndcX, m_ndcY, -1.0f, 1.0f );
331 glm::vec4 rayEndNDC( m_ndcX, m_ndcY, 1.0f, 1.0f );
332
333 glm::vec4 rayStartWorld = invVP * rayStartNDC;
334 rayStartWorld /= rayStartWorld.w;
335
336 glm::vec4 rayEndWorld = invVP * rayEndNDC;
337 rayEndWorld /= rayEndWorld.w;
338
339 glm::vec3 rayOrigin = glm::vec3( rayStartWorld );
340 glm::vec3 rayDirection = glm::normalize( glm::vec3( rayEndWorld - rayStartWorld ) );
341
342 auto intersectDist = []( const glm::vec3& aRayOrigin, const glm::vec3& aRayDir, const glm::vec3& aSphereCenter,
343 float aRadius ) -> float
344 {
345 glm::vec3 L = aSphereCenter - aRayOrigin;
346 float tca = glm::dot( L, aRayDir );
347 float d2 = glm::dot( L, L ) - tca * tca;
348
349 if( d2 > aRadius * aRadius )
350 return -1.0f;
351
352 return tca;
353 };
354
355 int clickedIndex = -1;
356 float closestDist = std::numeric_limits<float>::max();
358
359 for( size_t i = 0; i < m_spheres.size(); ++i )
360 {
361 const auto& sphere = m_spheres[i];
362 float dist = intersectDist( rayOrigin, rayDirection, sphere.m_position, sphere.m_radius );
363
364 if( dist >= 0.0f && dist < closestDist )
365 {
366 closestDist = dist;
367 clickedIndex = static_cast<int>( i );
369 }
370 }
371
372 for( size_t i = 0; i < m_spheres.size(); ++i )
373 {
374 if( static_cast<int>( i ) == clickedIndex )
375 m_spheres[i].m_color = { 1.0f, 1.0f, 1.0f };
376 else
377 m_spheres[i].m_color = m_spheres[i].m_originalColor;
378 }
379}
#define RANGE_SCALE_3D
This defines the range that all coord will have to be rendered.
GizmoSphereSelection
Enum to indicate which sphere (direction) is selected.
void updateSelection(glm::mat4 aCameraRotationMatrix)
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)
glm::mat4 m_cameraRotationMatrix
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:40
glm::vec4 SFVEC4F
Definition xv3d_types.h:42