1 | // Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | /*! |
5 | * \class Qt3DExtras::QConeGeometry |
6 | * \ingroup qt3d-extras-geometries |
7 | * \inheaderfile Qt3DExtras/QConeGeometry |
8 | * \inmodule Qt3DExtras |
9 | * \brief The QConeGeometry class allows creation of a cone in 3D space. |
10 | * \since 5.7 |
11 | * \ingroup geometries |
12 | * \inherits Qt3DCore::QGeometry |
13 | * |
14 | * The QConeGeometry class is most commonly used internally by the QConeMesh |
15 | * but can also be used in custom Qt3DRender::QGeometryRenderer subclasses. The class |
16 | * allows for creation of both a cone and a truncated cone. |
17 | */ |
18 | |
19 | #ifndef _USE_MATH_DEFINES |
20 | # define _USE_MATH_DEFINES // For MSVC |
21 | #endif |
22 | |
23 | #include "qconegeometry.h" |
24 | #include "qconegeometry_p.h" |
25 | |
26 | #include <Qt3DCore/qattribute.h> |
27 | #include <Qt3DCore/qbuffer.h> |
28 | #include <QtGui/QVector3D> |
29 | |
30 | #include <cmath> |
31 | |
32 | QT_BEGIN_NAMESPACE |
33 | |
34 | using namespace Qt3DCore; |
35 | |
36 | namespace Qt3DExtras { |
37 | |
38 | namespace { |
39 | |
40 | int (int slices, int rings, int capCount) |
41 | { |
42 | return (slices * 2) * (rings - 1) + slices * capCount; |
43 | } |
44 | |
45 | int (int slices, int rings, int capCount) |
46 | { |
47 | return (slices + 1) * rings + capCount * (slices + 2); |
48 | } |
49 | |
50 | void (float *&verticesPtr, |
51 | int rings, |
52 | int slices, |
53 | double topRadius, |
54 | double bottomRadius, |
55 | double length) |
56 | { |
57 | const float dY = length / static_cast<float>(rings - 1); |
58 | const float dTheta = (M_PI * 2) / static_cast<float>(slices); |
59 | |
60 | for (int ring = 0; ring < rings; ++ring) { |
61 | const float y = -length / 2.0f + static_cast<float>(ring) * dY; |
62 | |
63 | const float t = (y + length / 2) / length; |
64 | const float radius = (bottomRadius * (1 - t)) + (t * topRadius); |
65 | |
66 | for (int slice = 0; slice <= slices; ++slice) { |
67 | const float theta = static_cast<float>(slice) * dTheta; |
68 | const float ta = std::tan(x: (M_PI/2) - std::atan(x: length / (bottomRadius - topRadius))); |
69 | const float ct = std::cos(x: theta); |
70 | const float st = std::sin(x: theta); |
71 | |
72 | *verticesPtr++ = radius * ct; |
73 | *verticesPtr++ = y; |
74 | *verticesPtr++ = radius * st; |
75 | |
76 | *verticesPtr++ = (y + length / 2.0) / length; |
77 | *verticesPtr++ = theta / (M_PI * 2); |
78 | |
79 | QVector3D n(ct, ta, st); |
80 | n.normalize(); |
81 | *verticesPtr++ = n.x(); |
82 | *verticesPtr++ = n.y(); |
83 | *verticesPtr++ = n.z(); |
84 | } |
85 | } |
86 | } |
87 | |
88 | void (quint16 *&indicesPtr, int rings, int slices) |
89 | { |
90 | for (int ring = 0; ring < rings-1; ++ring) { |
91 | const int ringIndexStart = ring * (slices + 1); |
92 | const int nextRingIndexStart = (ring + 1) * (slices + 1); |
93 | |
94 | for (int slice = 0; slice <= slices; ++slice) { |
95 | if (slice == slices) |
96 | continue; |
97 | |
98 | const int nextSlice = slice + 1; |
99 | |
100 | *indicesPtr++ = (ringIndexStart + slice); |
101 | *indicesPtr++ = (nextRingIndexStart + slice); |
102 | *indicesPtr++ = (ringIndexStart + nextSlice); |
103 | *indicesPtr++ = (ringIndexStart + nextSlice); |
104 | *indicesPtr++ = (nextRingIndexStart + slice); |
105 | *indicesPtr++ = (nextRingIndexStart + nextSlice); |
106 | } |
107 | } |
108 | } |
109 | |
110 | void (float *&verticesPtr, |
111 | int slices, |
112 | double topRadius, |
113 | double bottomRadius, |
114 | double length, |
115 | double yPosition) |
116 | { |
117 | const float dTheta = (M_PI * 2) / static_cast<float>(slices); |
118 | const double yNormal = (yPosition < 0.0f) ? -1.0f : 1.0f; |
119 | |
120 | *verticesPtr++ = 0.0f; |
121 | *verticesPtr++ = yPosition; |
122 | *verticesPtr++ = 0.0f; |
123 | |
124 | *verticesPtr++ = 1.0f; |
125 | *verticesPtr++ = 0.0f; |
126 | |
127 | *verticesPtr++ = 0.0f; |
128 | *verticesPtr++ = yNormal; |
129 | *verticesPtr++ = 0.0f; |
130 | |
131 | |
132 | for (int slice = 0; slice <= slices; ++slice) |
133 | { |
134 | const float theta = static_cast<float>(slice) * dTheta; |
135 | const float ct = std::cos(x: theta); |
136 | const float st = std::sin(x: theta); |
137 | |
138 | const float t = (yPosition + length / 2) / length; |
139 | const float radius = (bottomRadius * (1 - t)) + (t * topRadius); |
140 | |
141 | *verticesPtr++ = radius * ct; |
142 | *verticesPtr++ = yPosition; |
143 | *verticesPtr++ = radius * st; |
144 | |
145 | *verticesPtr++ = 1.0f; |
146 | *verticesPtr++ = theta / (M_PI * 2); |
147 | |
148 | *verticesPtr++ = 0.0f; |
149 | *verticesPtr++ = yNormal; |
150 | *verticesPtr++ = 0.0f; |
151 | } |
152 | } |
153 | |
154 | void (quint16 *&indicesPtr, |
155 | int discCenterIndex, |
156 | int slices, |
157 | bool isTopCap) |
158 | { |
159 | if ( !isTopCap ) { |
160 | for ( int i = slices - 1 ; i >= 0 ; --i ) |
161 | { |
162 | if ( i != 0 ) { |
163 | *indicesPtr++ = discCenterIndex; |
164 | *indicesPtr++ = discCenterIndex + i + 1; |
165 | *indicesPtr++ = discCenterIndex + i; |
166 | } else { |
167 | *indicesPtr++ = discCenterIndex; |
168 | *indicesPtr++ = discCenterIndex + i + 1; |
169 | *indicesPtr++ = discCenterIndex + slices; |
170 | } |
171 | } |
172 | } else { |
173 | for ( int i = 0 ; i < slices; ++i ) |
174 | { |
175 | if ( i != slices - 1 ) { |
176 | *indicesPtr++ = discCenterIndex; |
177 | *indicesPtr++ = discCenterIndex + i + 1; |
178 | *indicesPtr++ = discCenterIndex + i + 2; |
179 | } else { |
180 | *indicesPtr++ = discCenterIndex; |
181 | *indicesPtr++ = discCenterIndex + i + 1; |
182 | *indicesPtr++ = discCenterIndex + 1; |
183 | } |
184 | } |
185 | } |
186 | } |
187 | |
188 | } // anonymous |
189 | |
190 | QConeGeometryPrivate::() |
191 | : QGeometryPrivate() |
192 | , m_hasTopEndcap(true) |
193 | , m_hasBottomEndcap(true) |
194 | , m_rings(16) |
195 | , m_slices(16) |
196 | , m_topRadius(0.0f) |
197 | , m_bottomRadius(1.0f) |
198 | , m_length(1.0f) |
199 | , m_positionAttribute(nullptr) |
200 | , m_normalAttribute(nullptr) |
201 | , m_texCoordAttribute(nullptr) |
202 | , m_indexAttribute(nullptr) |
203 | , m_positionBuffer(nullptr) |
204 | , m_vertexBuffer(nullptr) |
205 | , m_indexBuffer(nullptr) |
206 | { |
207 | } |
208 | |
209 | void QConeGeometryPrivate::() |
210 | { |
211 | Q_Q(QConeGeometry); |
212 | m_positionAttribute = new QAttribute(q); |
213 | m_normalAttribute = new QAttribute(q); |
214 | m_texCoordAttribute = new QAttribute(q); |
215 | m_indexAttribute = new QAttribute(q); |
216 | m_vertexBuffer = new Qt3DCore::QBuffer(q); |
217 | m_indexBuffer = new Qt3DCore::QBuffer(q); |
218 | |
219 | // vec3 pos, vec2 tex, vec3 normal |
220 | const quint32 elementSize = 3 + 2 + 3; |
221 | const quint32 stride = elementSize * sizeof(float); |
222 | const int faces = faceCount(slices: m_slices, rings: m_rings, capCount: (m_hasTopEndcap + m_hasBottomEndcap)); |
223 | const int nVerts = vertexCount(slices: m_slices, rings: m_rings, capCount: (m_hasTopEndcap + m_hasBottomEndcap)); |
224 | |
225 | m_positionAttribute->setName(QAttribute::defaultPositionAttributeName()); |
226 | m_positionAttribute->setVertexBaseType(QAttribute::Float); |
227 | m_positionAttribute->setVertexSize(3); |
228 | m_positionAttribute->setAttributeType(QAttribute::VertexAttribute); |
229 | m_positionAttribute->setBuffer(m_vertexBuffer); |
230 | m_positionAttribute->setByteStride(stride); |
231 | m_positionAttribute->setCount(nVerts); |
232 | |
233 | m_texCoordAttribute->setName(QAttribute::defaultTextureCoordinateAttributeName()); |
234 | m_texCoordAttribute->setVertexBaseType(QAttribute::Float); |
235 | m_texCoordAttribute->setVertexSize(2); |
236 | m_texCoordAttribute->setAttributeType(QAttribute::VertexAttribute); |
237 | m_texCoordAttribute->setBuffer(m_vertexBuffer); |
238 | m_texCoordAttribute->setByteStride(stride); |
239 | m_texCoordAttribute->setByteOffset(3 * sizeof(float)); |
240 | m_texCoordAttribute->setCount(nVerts); |
241 | |
242 | m_normalAttribute->setName(QAttribute::defaultNormalAttributeName()); |
243 | m_normalAttribute->setVertexBaseType(QAttribute::Float); |
244 | m_normalAttribute->setVertexSize(3); |
245 | m_normalAttribute->setAttributeType(QAttribute::VertexAttribute); |
246 | m_normalAttribute->setBuffer(m_vertexBuffer); |
247 | m_normalAttribute->setByteStride(stride); |
248 | m_normalAttribute->setByteOffset(5 * sizeof(float)); |
249 | m_normalAttribute->setCount(nVerts); |
250 | |
251 | m_indexAttribute->setAttributeType(QAttribute::IndexAttribute); |
252 | m_indexAttribute->setVertexBaseType(QAttribute::UnsignedShort); |
253 | m_indexAttribute->setBuffer(m_indexBuffer); |
254 | |
255 | m_indexAttribute->setCount(faces * 3); |
256 | |
257 | m_vertexBuffer->setData(generateVertexData()); |
258 | m_indexBuffer->setData(generateIndexData()); |
259 | |
260 | q->addAttribute(attribute: m_positionAttribute); |
261 | q->addAttribute(attribute: m_texCoordAttribute); |
262 | q->addAttribute(attribute: m_normalAttribute); |
263 | q->addAttribute(attribute: m_indexAttribute); |
264 | } |
265 | |
266 | QByteArray QConeGeometryPrivate::() const |
267 | { |
268 | const int verticesCount = |
269 | vertexCount(slices: m_slices, rings: m_rings, capCount: (m_hasTopEndcap + m_hasBottomEndcap)); |
270 | |
271 | // vec3 pos, vec2 texCoord, vec3 normal |
272 | const quint32 vertexSize = (3 + 2 + 3) * sizeof(float); |
273 | |
274 | QByteArray verticesData; |
275 | verticesData.resize(size: vertexSize * verticesCount); |
276 | float *verticesPtr = reinterpret_cast<float*>(verticesData.data()); |
277 | |
278 | createSidesVertices(verticesPtr, rings: m_rings, slices: m_slices, topRadius: m_topRadius, bottomRadius: m_bottomRadius, length: m_length); |
279 | if ( m_hasTopEndcap ) |
280 | createDiscVertices(verticesPtr, slices: m_slices, topRadius: m_topRadius, bottomRadius: m_bottomRadius, length: m_length, yPosition: m_length * 0.5f); |
281 | if ( m_hasBottomEndcap ) |
282 | createDiscVertices(verticesPtr, slices: m_slices, topRadius: m_topRadius, bottomRadius: m_bottomRadius, length: m_length, yPosition: -m_length * 0.5f); |
283 | |
284 | return verticesData; |
285 | } |
286 | |
287 | QByteArray QConeGeometryPrivate::() const |
288 | { |
289 | const int facesCount = faceCount(slices: m_slices, rings: m_rings, capCount: (m_hasTopEndcap + m_hasBottomEndcap)); |
290 | |
291 | const int indicesCount = facesCount * 3; |
292 | const int indexSize = sizeof(quint16); |
293 | Q_ASSERT(indicesCount < 65536); |
294 | |
295 | QByteArray indicesBytes; |
296 | indicesBytes.resize(size: indicesCount * indexSize); |
297 | quint16 *indicesPtr = reinterpret_cast<quint16*>(indicesBytes.data()); |
298 | |
299 | createSidesIndices(indicesPtr, rings: m_rings, slices: m_slices); |
300 | if ( m_hasTopEndcap ) |
301 | createDiscIndices(indicesPtr, discCenterIndex: m_rings * (m_slices + 1) + m_slices + 2, slices: m_slices, isTopCap: true); |
302 | if ( m_hasBottomEndcap ) |
303 | createDiscIndices(indicesPtr, discCenterIndex: m_rings * (m_slices + 1), slices: m_slices, isTopCap: false); |
304 | |
305 | return indicesBytes; |
306 | } |
307 | |
308 | /*! |
309 | * \qmltype ConeGeometry |
310 | * \instantiates Qt3DExtras::QConeGeometry |
311 | * \inqmlmodule Qt3D.Extras |
312 | * \brief ConeGeometry allows creation of a cone in 3D space. |
313 | * |
314 | * The ConeGeometry type is most commonly used internally by the ConeMesh type |
315 | * but can also be used in custom GeometryRenderer types. |
316 | * The ConeGeometry type allows for creation of both a cone and a truncated cone. |
317 | */ |
318 | |
319 | /*! |
320 | * \qmlproperty bool ConeGeometry::hasTopEndcap |
321 | * |
322 | * Determines if the cone top is capped or open. |
323 | */ |
324 | |
325 | /*! |
326 | * \qmlproperty bool ConeGeometry::hasBottomEndcap |
327 | * |
328 | * Determines if the cone bottom is capped or open. |
329 | */ |
330 | |
331 | /*! |
332 | * \qmlproperty int ConeGeometry::rings |
333 | * |
334 | * Holds the number of rings in the geometry. |
335 | */ |
336 | |
337 | /*! |
338 | * \qmlproperty int ConeGeometry::slices |
339 | * |
340 | * Holds the number of slices in the geometry. |
341 | */ |
342 | |
343 | /*! |
344 | * \qmlproperty real ConeGeometry::topRadius |
345 | * |
346 | * Holds the top radius of the cone. |
347 | */ |
348 | |
349 | /*! |
350 | * \qmlproperty real ConeGeometry::bottomRadius |
351 | * |
352 | * Holds the bottom radius of the cone. |
353 | */ |
354 | |
355 | /*! |
356 | * \qmlproperty real ConeGeometry::length |
357 | * |
358 | * Holds the length of the cone. |
359 | */ |
360 | |
361 | /*! |
362 | * \qmlproperty Attribute ConeGeometry::positionAttribute |
363 | * |
364 | * Holds the geometry position attribute. |
365 | */ |
366 | |
367 | /*! |
368 | * \qmlproperty Attribute ConeGeometry::normalAttribute |
369 | * |
370 | * Holds the geometry normal attribute. |
371 | */ |
372 | |
373 | /*! |
374 | * \qmlproperty Attribute ConeGeometry::texCoordAttribute |
375 | * |
376 | * Holds the geometry texture coordinate attribute. |
377 | */ |
378 | |
379 | /*! |
380 | * \qmlproperty Attribute ConeGeometry::indexAttribute |
381 | * |
382 | * Holds the geometry index attribute. |
383 | */ |
384 | |
385 | QConeGeometry::(QNode *parent) |
386 | : QGeometry(*new QConeGeometryPrivate, parent) |
387 | { |
388 | Q_D(QConeGeometry); |
389 | d->init(); |
390 | } |
391 | |
392 | QConeGeometry::(QConeGeometryPrivate &dd, QNode *parent) |
393 | :QGeometry(dd, parent) |
394 | { |
395 | Q_D(QConeGeometry); |
396 | d->init(); |
397 | } |
398 | |
399 | |
400 | /*! \internal */ |
401 | QConeGeometry::() |
402 | { |
403 | } |
404 | |
405 | /*! |
406 | * Updates vertices based on geometry properties. |
407 | */ |
408 | void QConeGeometry::() |
409 | { |
410 | Q_D(QConeGeometry); |
411 | const int nVerts = vertexCount(slices: d->m_slices, rings: d->m_rings, |
412 | capCount: (d->m_hasTopEndcap + d->m_hasBottomEndcap)); |
413 | |
414 | d->m_positionAttribute->setCount(nVerts); |
415 | d->m_texCoordAttribute->setCount(nVerts); |
416 | d->m_normalAttribute->setCount(nVerts); |
417 | d->m_vertexBuffer->setData(d->generateVertexData()); |
418 | } |
419 | |
420 | /*! |
421 | * Updates indices based on geometry properties. |
422 | */ |
423 | void QConeGeometry::() |
424 | { |
425 | Q_D(QConeGeometry); |
426 | const int faces = faceCount(slices: d->m_slices, rings: d->m_rings, |
427 | capCount: (d->m_hasTopEndcap + d->m_hasBottomEndcap)); |
428 | |
429 | d->m_indexAttribute->setCount(faces * 3); |
430 | d->m_indexBuffer->setData(d->generateIndexData()); |
431 | } |
432 | |
433 | /*! |
434 | * \property QConeGeometry::hasTopEndcap |
435 | * |
436 | * Determines if the cone top is capped or open. |
437 | */ |
438 | /*! |
439 | * \property QConeGeometry::hasBottomEndcap |
440 | * |
441 | * Determines if the cone bottom is capped or open. |
442 | */ |
443 | |
444 | /*! |
445 | * \property QConeGeometry::rings |
446 | * |
447 | * Holds the number of rings in the geometry. |
448 | */ |
449 | |
450 | /*! |
451 | * \property QConeGeometry::slices |
452 | * |
453 | * Holds the number of slices in the geometry. |
454 | */ |
455 | |
456 | /*! |
457 | * \property QConeGeometry::topRadius |
458 | * |
459 | * Holds the top radius of the cone. |
460 | */ |
461 | |
462 | /*! |
463 | * \property QConeGeometry::bottomRadius |
464 | * |
465 | * Holds the bottom radius of the cone. |
466 | */ |
467 | |
468 | /*! |
469 | * \property QConeGeometry::length |
470 | * |
471 | * Holds the length of the cone. |
472 | */ |
473 | |
474 | /*! |
475 | * \property QConeGeometry::positionAttribute |
476 | * |
477 | * Holds the geometry position attribute. |
478 | */ |
479 | |
480 | /*! |
481 | * \property QConeGeometry::normalAttribute |
482 | * |
483 | * Holds the geometry normal attribute. |
484 | */ |
485 | |
486 | /*! |
487 | * \property QConeGeometry::texCoordAttribute |
488 | * |
489 | * Holds the geometry texture coordinate attribute. |
490 | */ |
491 | |
492 | /*! |
493 | * \property QConeGeometry::indexAttribute |
494 | * |
495 | * Holds the geometry index attribute. |
496 | */ |
497 | |
498 | void QConeGeometry::(bool hasTopEndcap) |
499 | { |
500 | Q_D(QConeGeometry); |
501 | if (hasTopEndcap != d->m_hasTopEndcap) { |
502 | d->m_hasTopEndcap = hasTopEndcap; |
503 | updateVertices(); |
504 | emit hasTopEndcapChanged(hasTopEndcap); |
505 | } |
506 | } |
507 | |
508 | void QConeGeometry::(bool hasBottomEndcap) |
509 | { |
510 | Q_D(QConeGeometry); |
511 | if (hasBottomEndcap != d->m_hasBottomEndcap) { |
512 | d->m_hasBottomEndcap = hasBottomEndcap; |
513 | updateVertices(); |
514 | emit hasBottomEndcapChanged(hasBottomEndcap); |
515 | } |
516 | } |
517 | |
518 | void QConeGeometry::(int rings) |
519 | { |
520 | Q_D(QConeGeometry); |
521 | if (rings != d->m_rings) { |
522 | d->m_rings = rings; |
523 | updateVertices(); |
524 | updateIndices(); |
525 | emit ringsChanged(rings); |
526 | } |
527 | } |
528 | |
529 | void QConeGeometry::(int slices) |
530 | { |
531 | Q_D(QConeGeometry); |
532 | if (slices != d->m_slices) { |
533 | d->m_slices = slices; |
534 | updateVertices(); |
535 | updateIndices(); |
536 | emit slicesChanged(slices); |
537 | } |
538 | } |
539 | |
540 | void QConeGeometry::(float topRadius) |
541 | { |
542 | Q_D(QConeGeometry); |
543 | if (topRadius != d->m_topRadius) { |
544 | d->m_topRadius = topRadius; |
545 | updateVertices(); |
546 | emit topRadiusChanged(topRadius); |
547 | } |
548 | } |
549 | |
550 | void QConeGeometry::(float bottomRadius) |
551 | { |
552 | Q_D(QConeGeometry); |
553 | if (bottomRadius != d->m_bottomRadius) { |
554 | d->m_bottomRadius = bottomRadius; |
555 | updateVertices(); |
556 | emit bottomRadiusChanged(bottomRadius); |
557 | } |
558 | } |
559 | |
560 | void QConeGeometry::(float length) |
561 | { |
562 | Q_D(QConeGeometry); |
563 | if (length != d->m_length) { |
564 | d->m_length = length; |
565 | updateVertices(); |
566 | updateIndices(); |
567 | emit lengthChanged(length); |
568 | } |
569 | } |
570 | |
571 | bool QConeGeometry::() const |
572 | { |
573 | Q_D(const QConeGeometry); |
574 | return d->m_hasTopEndcap; |
575 | } |
576 | |
577 | bool QConeGeometry::() const |
578 | { |
579 | Q_D(const QConeGeometry); |
580 | return d->m_hasBottomEndcap; |
581 | } |
582 | |
583 | float QConeGeometry::() const |
584 | { |
585 | Q_D(const QConeGeometry); |
586 | return d->m_topRadius; |
587 | } |
588 | |
589 | float QConeGeometry::() const |
590 | { |
591 | Q_D(const QConeGeometry); |
592 | return d->m_bottomRadius; |
593 | } |
594 | |
595 | int QConeGeometry::() const |
596 | { |
597 | Q_D(const QConeGeometry); |
598 | return d->m_rings; |
599 | } |
600 | |
601 | int QConeGeometry::() const |
602 | { |
603 | Q_D(const QConeGeometry); |
604 | return d->m_slices; |
605 | } |
606 | |
607 | float QConeGeometry::() const |
608 | { |
609 | Q_D(const QConeGeometry); |
610 | return d->m_length; |
611 | } |
612 | |
613 | QAttribute *QConeGeometry::() const |
614 | { |
615 | Q_D(const QConeGeometry); |
616 | return d->m_positionAttribute; |
617 | } |
618 | |
619 | QAttribute *QConeGeometry::() const |
620 | { |
621 | Q_D(const QConeGeometry); |
622 | return d->m_normalAttribute; |
623 | } |
624 | |
625 | QAttribute *QConeGeometry::() const |
626 | { |
627 | Q_D(const QConeGeometry); |
628 | return d->m_texCoordAttribute; |
629 | } |
630 | |
631 | QAttribute *QConeGeometry::() const |
632 | { |
633 | Q_D(const QConeGeometry); |
634 | return d->m_indexAttribute; |
635 | } |
636 | |
637 | } // namespace Qt3DExtras |
638 | |
639 | QT_END_NAMESPACE |
640 | |
641 | #include "moc_qconegeometry.cpp" |
642 | |