feat(3d): gl1 shim M4+M6+M7 — GLU quadrics, production link, stub retired; 47/47 parity
- gl1_glu: gluCylinder/gluDisk/gluSphere per the SGI GLU reference tessellation (sin/cos phase, strip emission order, pole fans — Apple's GLU derives from the same source, so vertices match the goldens); emission goes through the public glBegin/glVertex entry points so quadrics record into display lists - glLineWidth>1 one-time warning (browsers clamp to 1; affected coverage stays under the parity floor — zero floors.json overrides needed) - production link site: build-kicad-target.sh now compiles wasm/gl1 from sources.txt and applies wrapped_symbols.txt --wrap flags (same manifests as the test harness); kicad_editor's RENDER_3D_OPENGL links against the real emulation layer instead of no-ops - wasm/stubs/gl_ffp_stub.c deleted (role fully superseded; history in git) - docs: 3d-regression README port-status section (floor-blind small geometry + line-width caveats), harness comments de-red-stated Parity: 47/47 under the 0.02 floor, zero overrides. Native golden path byte-identical to bb0e238 (whose gate ran green) — scenarios/, native/, baseline/, manifest, floors, spec all untouched by the port. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3fe4f6fb00
commit
27992f4f18
8 changed files with 240 additions and 157 deletions
|
|
@ -1,19 +1,49 @@
|
|||
/*
|
||||
* gl1_glu — GLU quadrics and gluPerspective.
|
||||
*
|
||||
* M4 ports the SGI GLU reference tessellation (quad.c) for
|
||||
* gluCylinder/gluDisk/gluSphere — the native goldens were rendered with
|
||||
* Apple's SGI-derived GLU, so vertex placement and emission order must match.
|
||||
* The quadrics emit through the shim's internal immediate-mode path
|
||||
* (immBegin/immVertex/immEnd) so they also record into display lists.
|
||||
* The tessellation follows the SGI GLU reference implementation (the
|
||||
* OpenGL Sample Implementation's libutil/quad.c, SGI Free Software License
|
||||
* B) restricted to what the renderer uses: GLU_FILL draw style, GLU_SMOOTH
|
||||
* normals, GLU_OUTSIDE orientation, no texture coordinates. Apple's GLU —
|
||||
* which rendered the native goldens — derives from the same source, so
|
||||
* vertex placement (sin/cos phase), emission order and stack/slice loops
|
||||
* must match exactly for pixel parity.
|
||||
*
|
||||
* Note: the GLU *tesselator* (gluNewTess & co, declared in the same
|
||||
* Geometry is emitted through the shim's public immediate-mode entry points
|
||||
* (glBegin/glNormal3f/glVertex3f/glEnd), so quadrics drawn between
|
||||
* glNewList/glEndList record into the display list like any other geometry.
|
||||
*
|
||||
* The GLU *tesselator* (gluNewTess & co, declared in the same
|
||||
* wasm/stubs/GL/glu.h) is a separate concern implemented in
|
||||
* kicad/libs/kimath/glu_tess/ — not part of this shim.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const double GLU_PI = 3.14159265358979323846;
|
||||
|
||||
// SGI's normal3f helper: normalize, then emit.
|
||||
void emitNormal( float x, float y, float z )
|
||||
{
|
||||
const float mag = std::sqrt( x * x + y * y + z * z );
|
||||
|
||||
if( mag > 0.00001f )
|
||||
{
|
||||
x /= mag;
|
||||
y /= mag;
|
||||
z /= mag;
|
||||
}
|
||||
|
||||
glNormal3f( x, y, z );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
|
|
@ -68,33 +98,164 @@ void gluQuadricNormals( GLUquadric* q, GLenum normals )
|
|||
void gluCylinder( GLUquadric* q, double base, double top, double height, int slices, int stacks )
|
||||
{
|
||||
(void) q;
|
||||
(void) base;
|
||||
(void) top;
|
||||
(void) height;
|
||||
(void) slices;
|
||||
(void) stacks;
|
||||
GL1_WARN_ONCE( "gluCylinder not implemented yet (M4) — geometry dropped" );
|
||||
|
||||
if( slices < 2 || stacks < 1 || height == 0.0 )
|
||||
return;
|
||||
|
||||
const float da = (float) ( 2.0 * GLU_PI / slices );
|
||||
const float dr = (float) ( ( top - base ) / stacks );
|
||||
const float dz = (float) ( height / stacks );
|
||||
const float nz = (float) ( ( base - top ) / height ); // combined with (x,y), normalized
|
||||
|
||||
float t = 0.0f;
|
||||
float z = 0.0f;
|
||||
float r = (float) base;
|
||||
|
||||
for( int j = 0; j < stacks; j++ )
|
||||
{
|
||||
glBegin( GL_QUAD_STRIP );
|
||||
|
||||
for( int i = 0; i <= slices; i++ )
|
||||
{
|
||||
// SGI closes the strip with the angle-0 vertex, not slice*da.
|
||||
const float a = ( i == slices ) ? 0.0f : (float) i * da;
|
||||
const float x = std::sin( a );
|
||||
const float y = std::cos( a );
|
||||
|
||||
emitNormal( x, y, nz );
|
||||
glVertex3f( x * r, y * r, z );
|
||||
emitNormal( x, y, nz );
|
||||
glVertex3f( x * ( r + dr ), y * ( r + dr ), z + dz );
|
||||
}
|
||||
|
||||
glEnd();
|
||||
|
||||
r += dr;
|
||||
t += dz; // (texture t unused; kept for structural parity)
|
||||
z += dz;
|
||||
}
|
||||
|
||||
(void) t;
|
||||
}
|
||||
|
||||
|
||||
void gluDisk( GLUquadric* q, double inner, double outer, int slices, int loops )
|
||||
{
|
||||
(void) q;
|
||||
(void) inner;
|
||||
(void) outer;
|
||||
(void) slices;
|
||||
(void) loops;
|
||||
GL1_WARN_ONCE( "gluDisk not implemented yet (M4) — geometry dropped" );
|
||||
|
||||
if( slices < 2 || loops < 1 )
|
||||
return;
|
||||
|
||||
// GLU_OUTSIDE: disk normal is +Z; the innermost ring of a full disk
|
||||
// (inner == 0) degenerates its inner vertices at the origin, exactly as
|
||||
// the reference implementation renders it.
|
||||
glNormal3f( 0.0f, 0.0f, 1.0f );
|
||||
|
||||
const float da = (float) ( 2.0 * GLU_PI / slices );
|
||||
const float dr = (float) ( ( outer - inner ) / loops );
|
||||
|
||||
float r1 = (float) inner;
|
||||
|
||||
for( int l = 0; l < loops; l++ )
|
||||
{
|
||||
const float r2 = r1 + dr;
|
||||
|
||||
glBegin( GL_QUAD_STRIP );
|
||||
|
||||
for( int s = 0; s <= slices; s++ )
|
||||
{
|
||||
const float a = ( s == slices ) ? 0.0f : (float) s * da;
|
||||
const float sa = std::sin( a );
|
||||
const float ca = std::cos( a );
|
||||
|
||||
glVertex2f( r2 * sa, r2 * ca );
|
||||
glVertex2f( r1 * sa, r1 * ca );
|
||||
}
|
||||
|
||||
glEnd();
|
||||
|
||||
r1 = r2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void gluSphere( GLUquadric* q, double radius, int slices, int stacks )
|
||||
{
|
||||
(void) q;
|
||||
(void) radius;
|
||||
(void) slices;
|
||||
(void) stacks;
|
||||
GL1_WARN_ONCE( "gluSphere not implemented yet (M4) — geometry dropped" );
|
||||
|
||||
if( slices < 2 || stacks < 2 )
|
||||
return;
|
||||
|
||||
const float drho = (float) ( GLU_PI / stacks );
|
||||
const float dtheta = (float) ( 2.0 * GLU_PI / slices );
|
||||
const float rad = (float) radius;
|
||||
|
||||
// +Z pole cap (triangle fan).
|
||||
glBegin( GL_TRIANGLE_FAN );
|
||||
glNormal3f( 0.0f, 0.0f, 1.0f );
|
||||
glVertex3f( 0.0f, 0.0f, rad );
|
||||
|
||||
for( int j = 0; j <= slices; j++ )
|
||||
{
|
||||
const float theta = ( j == slices ) ? 0.0f : (float) j * dtheta;
|
||||
const float x = -std::sin( theta ) * std::sin( drho );
|
||||
const float y = std::cos( theta ) * std::sin( drho );
|
||||
const float z = std::cos( drho );
|
||||
|
||||
glNormal3f( x, y, z );
|
||||
glVertex3f( x * rad, y * rad, z * rad );
|
||||
}
|
||||
|
||||
glEnd();
|
||||
|
||||
// Intermediate stacks as quad strips.
|
||||
for( int i = 1; i < stacks - 1; i++ )
|
||||
{
|
||||
const float rho = (float) i * drho;
|
||||
|
||||
glBegin( GL_QUAD_STRIP );
|
||||
|
||||
for( int j = 0; j <= slices; j++ )
|
||||
{
|
||||
const float theta = ( j == slices ) ? 0.0f : (float) j * dtheta;
|
||||
|
||||
float x = -std::sin( theta ) * std::sin( rho );
|
||||
float y = std::cos( theta ) * std::sin( rho );
|
||||
float z = std::cos( rho );
|
||||
|
||||
glNormal3f( x, y, z );
|
||||
glVertex3f( x * rad, y * rad, z * rad );
|
||||
|
||||
x = -std::sin( theta ) * std::sin( rho + drho );
|
||||
y = std::cos( theta ) * std::sin( rho + drho );
|
||||
z = std::cos( rho + drho );
|
||||
|
||||
glNormal3f( x, y, z );
|
||||
glVertex3f( x * rad, y * rad, z * rad );
|
||||
}
|
||||
|
||||
glEnd();
|
||||
}
|
||||
|
||||
// -Z pole cap (triangle fan, reverse slice order).
|
||||
glBegin( GL_TRIANGLE_FAN );
|
||||
glNormal3f( 0.0f, 0.0f, -1.0f );
|
||||
glVertex3f( 0.0f, 0.0f, -rad );
|
||||
|
||||
const float rho = (float) GLU_PI - drho;
|
||||
|
||||
for( int j = slices; j >= 0; j-- )
|
||||
{
|
||||
const float theta = ( j == slices ) ? 0.0f : (float) j * dtheta;
|
||||
const float x = -std::sin( theta ) * std::sin( rho );
|
||||
const float y = std::cos( theta ) * std::sin( rho );
|
||||
const float z = std::cos( rho );
|
||||
|
||||
glNormal3f( x, y, z );
|
||||
glVertex3f( x * rad, y * rad, z * rad );
|
||||
}
|
||||
|
||||
glEnd();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ void stateBlendFunc( GLenum sfactor, GLenum dfactor )
|
|||
|
||||
void stateLineWidth( GLfloat width )
|
||||
{
|
||||
if( width > 1.0f )
|
||||
GL1_WARN_ONCE( "glLineWidth(%g): browsers clamp line width to 1 — lines render thinner "
|
||||
"than native (known WebGL2 limitation)", (double) width );
|
||||
|
||||
S().lineWidth = width;
|
||||
__real_glLineWidth( width );
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue