quadricShape/quadricShape.cpp

quadricShape/quadricShape.cpp
//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+
// DESCRIPTION:
//
// Produces the shape node "quadricShape".
//
// This plug-in registers a new type of shape with Maya called "quadricShape".
// This shape will display spheres, cylinders, disks, and partial disks using
// the OpenGL gluQuadric functions.
//
// For example, to create a sphere:
// createNode quadricShape -n qSphere;
// setAttr qSphere.shapeType 3;
//
// There are no output attributes for this shape.
// The following input attributes define the type of shape to draw:
//
// shapeType : 0=cylinder, 1=disk, 2=partialDisk, 3=sphere
// radius1 : cylinder base radius, disk inner radius, sphere radius
// radius2 : cylinder top radius, disk outer radius
// height : cylinder height
// startAngle : partial disk start angle
// sweepAngle : partial disk sweep angle
// slices : cylinder, disk, sphere slices
// loops : disk loops
// stacks : cylinder, sphere stacks
//
#include <maya/MIOStream.h>
#include <maya/MPxSurfaceShape.h>
#include <maya/MPxSurfaceShapeUI.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnEnumAttribute.h>
#include <maya/MPoint.h>
#include <maya/MPlug.h>
#include <maya/MDrawData.h>
#include <maya/MDrawRequest.h>
#include <maya/MSelectionMask.h>
#include <maya/MSelectionList.h>
#include <maya/MDagPath.h>
#include <maya/MMaterial.h>
#if defined(OSMac_MachO_)
#include <OpenGL/glu.h>
#else
#include <GL/glu.h>
#endif
#define MCHECKERROR(STAT,MSG) \
if ( MS::kSuccess != STAT ) { \
cerr << MSG << endl; \
return MS::kFailure; \
}
#define MAKE_NUMERIC_ATTR( NAME, SHORTNAME, TYPE, DEFAULT, KEYABLE ) \
MStatus NAME##_stat; \
MFnNumericAttribute NAME##_fn; \
NAME = NAME##_fn.create( #NAME, SHORTNAME, TYPE, DEFAULT ); \
MCHECKERROR(NAME##_stat, "numeric attr create error"); \
NAME##_fn.setHidden( !KEYABLE ); \
NAME##_fn.setKeyable( KEYABLE ); \
NAME##_fn.setInternal( true ); \
NAME##_stat = addAttribute( NAME ); \
MCHECKERROR(NAME##_stat, "addAttribute error");
#define LEAD_COLOR 18 // green
#define ACTIVE_COLOR 15 // white
#define ACTIVE_AFFECTED_COLOR 8 // purple
#define DORMANT_COLOR 4 // blue
#define HILITE_COLOR 17 // pale blue
//
// Geometry class
//
class quadricGeom
{
public:
double radius1;
double radius2;
double height;
double startAngle;
double sweepAngle;
short slices;
short loops;
short stacks;
short shapeType;
};
//
// Shape class - defines the non-UI part of a shape node
//
class quadricShape : public MPxSurfaceShape
{
public:
quadricShape();
~quadricShape() override;
void postConstructor() override;
MStatus compute( const MPlug&, MDataBlock& ) override;
bool getInternalValue( const MPlug&, MDataHandle& ) override;
bool setInternalValue( const MPlug&, const MDataHandle& ) override;
bool isBounded() const override;
MBoundingBox boundingBox() const override;
static void * creator();
static MStatus initialize();
quadricGeom* geometry();
private:
quadricGeom* fGeometry;
// Attributes
//
static MObject shapeType;
static MObject radius1;
static MObject radius2;
static MObject height;
static MObject startAngle;
static MObject sweepAngle;
static MObject slices;
static MObject loops;
static MObject stacks;
public:
// Shape type id
//
static MTypeId id;
};
//
// UI class - defines the UI part of a shape node
//
class quadricShapeUI : public MPxSurfaceShapeUI
{
public:
quadricShapeUI();
~quadricShapeUI() override;
void getDrawRequests( const MDrawInfo & info,
bool objectAndActiveOnly,
MDrawRequestQueue & requests ) override;
void draw( const MDrawRequest & request,
M3dView & view ) const override;
bool select( MSelectInfo &selectInfo,
MSelectionList &selectionList,
MPointArray &worldSpaceSelectPts ) const override;
void getDrawRequestsWireframe( MDrawRequest&,
const MDrawInfo& );
void getDrawRequestsShaded( MDrawRequest&,
const MDrawInfo&,
MDrawData& data );
static void * creator();
private:
enum {
kDrawCylinder,
kDrawDisk,
kDrawPartialDisk,
kDrawSphere
};
// Draw Tokens
//
enum {
kDrawWireframe,
kDrawWireframeOnShaded,
kDrawSmoothShaded,
kDrawFlatShaded,
kLastToken
};
};
// SHAPE NODE IMPLEMENTATION
MObject quadricShape::shapeType;
MObject quadricShape::radius1;
MObject quadricShape::radius2;
MObject quadricShape::height;
MObject quadricShape::startAngle;
MObject quadricShape::sweepAngle;
MObject quadricShape::slices;
MObject quadricShape::loops;
MObject quadricShape::stacks;
MTypeId quadricShape::id( 0x80111 );
quadricShape::quadricShape()
{
fGeometry = new quadricGeom;
fGeometry->radius1 = 1.0;
fGeometry->radius2 = 1.0;
fGeometry->height = 2.0;
fGeometry->startAngle = 0.0;
fGeometry->sweepAngle = 90.0;
fGeometry->slices = 8;
fGeometry->loops = 6;
fGeometry->stacks = 4;
fGeometry->shapeType = 0;
}
quadricShape::~quadricShape()
{
delete fGeometry;
}
/* override */
void quadricShape::postConstructor()
//
// Description
//
// When instances of this node are created internally, the MObject associated
// with the instance is not created until after the constructor of this class
// is called. This means that no member functions of MPxSurfaceShape can
// be called in the constructor.
// The postConstructor solves this problem. Maya will call this function
// after the internal object has been created.
// As a general rule do all of your initialization in the postConstructor.
//
{
// This call allows the shape to have shading groups assigned
//
setRenderable( true );
}
/* override */
MStatus quadricShape::compute( const MPlug& /*plug*/, MDataBlock& /*datablock*/ )
//
// Since there are no output attributes this is not necessary but
// if we wanted to compute an output mesh for rendering it would
// be done here base on the inputs.
//
{
return MS::kUnknownParameter;
}
/* override */
bool quadricShape::getInternalValue( const MPlug& plug, MDataHandle& datahandle )
//
// Handle internal attributes.
// In order to impose limits on our attribute values we
// mark them internal and use the values in fGeometry intead.
//
{
bool isOk = true;
if ( plug == radius1 ) {
datahandle.set( fGeometry->radius1 );
isOk = true;
}
else if ( plug == radius2 ) {
datahandle.set( fGeometry->radius2 );
isOk = true;
}
else if ( plug == height ) {
datahandle.set( fGeometry->height );
isOk = true;
}
else if ( plug == startAngle ) {
datahandle.set( fGeometry->startAngle );
isOk = true;
}
else if ( plug == sweepAngle ) {
datahandle.set( fGeometry->sweepAngle );
isOk = true;
}
else if ( plug == slices ) {
datahandle.set( fGeometry->slices );
isOk = true;
}
else if ( plug == loops ) {
datahandle.set( fGeometry->loops );
isOk = true;
}
else if ( plug == stacks ) {
datahandle.set( fGeometry->stacks );
isOk = true;
}
else {
isOk = MPxSurfaceShape::getInternalValue( plug, datahandle );
}
return isOk;
}
/* override */
bool quadricShape::setInternalValue( const MPlug& plug, const MDataHandle& datahandle )
//
// Handle internal attributes.
// In order to impose limits on our attribute values we
// mark them internal and use the values in fGeometry intead.
//
{
bool isOk = true;
// In the case of a disk or partial disk the inner radius must
// never exceed the outer radius and the minimum radius is 0
//
if ( plug == radius1 ) {
double innerRadius = datahandle.asDouble();
double outerRadius = fGeometry->radius2;
if ( innerRadius > outerRadius ) {
outerRadius = innerRadius;
}
if ( innerRadius < 0 ) {
innerRadius = 0;
}
fGeometry->radius1 = innerRadius;
fGeometry->radius2 = outerRadius;
isOk = true;
}
else if ( plug == radius2 ) {
double outerRadius = datahandle.asDouble();
double innerRadius = fGeometry->radius1;
if ( outerRadius <= 0 ) {
outerRadius = 0.1;
}
if ( innerRadius > outerRadius ) {
innerRadius = outerRadius;
}
if ( innerRadius < 0 ) {
innerRadius = 0;
}
fGeometry->radius1 = innerRadius;
fGeometry->radius2 = outerRadius;
isOk = true;
}
else if ( plug == height ) {
double val = datahandle.asDouble();
if ( val <= 0 ) {
val = 0.1;
}
fGeometry->height = val;
}
else if ( plug == startAngle ) {
double val = datahandle.asDouble();
fGeometry->startAngle = val;
}
else if ( plug == sweepAngle ) {
double val = datahandle.asDouble();
fGeometry->sweepAngle = val;
}
else if ( plug == slices ) {
short val = datahandle.asShort();
if ( val < 3 ) {
val = 3;
}
fGeometry->slices = val;
}
else if ( plug == loops ) {
short val = datahandle.asShort();
if ( val < 3 ) {
val = 3;
}
fGeometry->loops = val;
}
else if ( plug == stacks ) {
short val = datahandle.asShort();
if ( val < 2 ) {
val = 2;
}
fGeometry->stacks = val;
}
else {
isOk = MPxSurfaceShape::setInternalValue( plug, datahandle );
}
return isOk;
}
/* override */
bool quadricShape::isBounded() const { return true; }
/* override */
MBoundingBox quadricShape::boundingBox() const
//
// Returns the bounding box for the shape.
// In this case just use the radius and height attributes
// to determine the bounding box.
//
{
MBoundingBox result;
quadricShape* nonConstThis = const_cast <quadricShape*> (this);
quadricGeom* geom = nonConstThis->geometry();
double r = geom->radius1;
result.expand( MPoint(r,r,r) ); result.expand( MPoint(-r,-r,-r) );
r = geom->radius2;
result.expand( MPoint(r,r,r) ); result.expand( MPoint(-r,-r,-r) );
r = geom->height;
result.expand( MPoint(r,r,r) ); result.expand( MPoint(-r,-r,-r) );
return result;
}
void* quadricShape::creator()
{
return new quadricShape();
}
MStatus quadricShape::initialize()
{
MStatus stat;
MFnNumericAttribute numericAttr;
MFnEnumAttribute enumAttr;
// QUADRIC type enumerated attribute
//
shapeType = enumAttr.create( "shapeType", "st", 0, &stat );
MCHECKERROR( stat, "create shapeType attribute" );
enumAttr.addField( "cylinder", 0 );
enumAttr.addField( "disk", 1 );
enumAttr.addField( "partialDisk", 2 );
enumAttr.addField( "sphere", 3 );
enumAttr.setHidden( false );
enumAttr.setKeyable( true );
stat = addAttribute( shapeType );
MCHECKERROR( stat, "Error adding shapeType attribute." );
// QUADRIC ATTRIBUTES
//
MAKE_NUMERIC_ATTR( radius1, "r1", MFnNumericData::kDouble, 1.0, true );
MAKE_NUMERIC_ATTR( radius2, "r2", MFnNumericData::kDouble, 1.0, true );
MAKE_NUMERIC_ATTR( height, "ht", MFnNumericData::kDouble, 2.0, true );
MAKE_NUMERIC_ATTR( startAngle, "sta", MFnNumericData::kDouble, 0.0, true );
MAKE_NUMERIC_ATTR( sweepAngle, "swa", MFnNumericData::kDouble, 90.0, true );
MAKE_NUMERIC_ATTR( slices, "sl", MFnNumericData::kShort, 8, true );
MAKE_NUMERIC_ATTR( loops, "lp", MFnNumericData::kShort, 6, true );
MAKE_NUMERIC_ATTR( stacks, "sk", MFnNumericData::kShort, 4, true );
return stat;
}
quadricGeom* quadricShape::geometry()
//
// This function gets the values of all the attributes and
// assigns them to the fGeometry. Calling MPlug::getValue
// will ensure that the values are up-to-date.
//
{
MObject this_object = thisMObject();
MPlug plug( this_object, radius1 ); plug.getValue( fGeometry->radius1 );
plug.setAttribute( radius2 ); plug.getValue( fGeometry->radius2 );
plug.setAttribute( height ); plug.getValue( fGeometry->height );
plug.setAttribute( startAngle ); plug.getValue( fGeometry->startAngle );
plug.setAttribute( sweepAngle ); plug.getValue( fGeometry->sweepAngle );
plug.setAttribute( slices ); plug.getValue( fGeometry->slices );
plug.setAttribute( loops ); plug.getValue( fGeometry->loops );
plug.setAttribute( stacks ); plug.getValue( fGeometry->stacks );
plug.setAttribute( shapeType ); plug.getValue( fGeometry->shapeType );
return fGeometry;
}
// UI IMPLEMENTATION
quadricShapeUI::quadricShapeUI() {}
quadricShapeUI::~quadricShapeUI() {}
void* quadricShapeUI::creator()
{
return new quadricShapeUI();
}
/* override */
void quadricShapeUI::getDrawRequests( const MDrawInfo & info,
bool /*objectAndActiveOnly*/,
{
// The draw data is used to pass geometry through the
// draw queue. The data should hold all the information
// needed to draw the shape.
//
MDrawData data;
MDrawRequest request = info.getPrototype( *this );
quadricShape* shapeNode = (quadricShape*)surfaceShape();
quadricGeom* geom = shapeNode->geometry();
getDrawData( geom, data );
request.setDrawData( data );
// Are we displaying meshes?
return;
// Use display status to determine what color to draw the object
//
switch ( info.displayStyle() )
{
getDrawRequestsWireframe( request, info );
queue.add( request );
break;
request.setToken( kDrawSmoothShaded );
getDrawRequestsShaded( request, info, queue, data );
queue.add( request );
break;
request.setToken( kDrawFlatShaded );
getDrawRequestsShaded( request, info, queue, data );
queue.add( request );
break;
default:
break;
}
}
/* override */
void quadricShapeUI::draw( const MDrawRequest & request, M3dView & view ) const
//
// From the given draw request, get the draw data and determine
// which quadric to draw and with what values.
//
{
MDrawData data = request.drawData();
quadricGeom * geom = (quadricGeom*)data.geometry();
int token = request.token();
bool drawTexture = false;
view.beginGL();
if ( (token == kDrawSmoothShaded) || (token == kDrawFlatShaded) )
{
#if defined(SGI) || defined(MESA)
glEnable( GL_POLYGON_OFFSET_EXT );
#else
glEnable( GL_POLYGON_OFFSET_FILL );
#endif
// Set up the material
//
MMaterial material = request.material();
material.setMaterial( request.multiPath(), request.isTransparent() );
// Enable texturing
//
drawTexture = material.materialIsTextured();
if ( drawTexture ) glEnable(GL_TEXTURE_2D);
// Apply the texture to the current view
//
if ( drawTexture ) {
material.applyTexture( view, data );
}
}
GLUquadricObj* qobj = gluNewQuadric();
switch( token )
{
case kDrawWireframe :
case kDrawWireframeOnShaded :
gluQuadricDrawStyle( qobj, GLU_LINE );
break;
case kDrawSmoothShaded :
gluQuadricNormals( qobj, GLU_SMOOTH );
gluQuadricTexture( qobj, true );
gluQuadricDrawStyle( qobj, GLU_FILL );
break;
case kDrawFlatShaded :
gluQuadricNormals( qobj, GLU_FLAT );
gluQuadricTexture( qobj, true );
gluQuadricDrawStyle( qobj, GLU_FILL );
break;
}
switch ( geom->shapeType )
{
case kDrawCylinder :
gluCylinder( qobj, geom->radius1, geom->radius2, geom->height,
geom->slices, geom->stacks );
break;
case kDrawDisk :
gluDisk( qobj, geom->radius1, geom->radius2, geom->slices, geom->loops );
break;
case kDrawPartialDisk :
gluPartialDisk( qobj, geom->radius1, geom->radius2, geom->slices,
geom->loops, geom->startAngle, geom->sweepAngle );
break;
case kDrawSphere :
default :
gluSphere( qobj, geom->radius1, geom->slices, geom->stacks );
break;
}
// Turn off texture mode
//
if ( drawTexture ) glDisable(GL_TEXTURE_2D);
view.endGL();
}
/* override */
bool quadricShapeUI::select( MSelectInfo &selectInfo,
MSelectionList &selectionList,
MPointArray &worldSpaceSelectPts ) const
//
// Select function. Gets called when the bbox for the object is selected.
// This function just selects the object without doing any intersection tests.
//
{
item.add( selectInfo.selectPath() );
MPoint xformedPt;
selectInfo.addSelection( item, xformedPt, selectionList,
worldSpaceSelectPts, priorityMask, false );
return true;
}
void quadricShapeUI::getDrawRequestsWireframe( MDrawRequest& request,
const MDrawInfo& info )
{
request.setToken( kDrawWireframe );
M3dView::DisplayStatus displayStatus = info.displayStatus();
switch ( displayStatus )
{
request.setColor( LEAD_COLOR, activeColorTable );
break;
request.setColor( ACTIVE_COLOR, activeColorTable );
break;
request.setColor( ACTIVE_AFFECTED_COLOR, activeColorTable );
break;
request.setColor( DORMANT_COLOR, dormantColorTable );
break;
request.setColor( HILITE_COLOR, activeColorTable );
break;
default:
break;
}
}
void quadricShapeUI::getDrawRequestsShaded( MDrawRequest& request,
const MDrawInfo& info,
MDrawData& data )
{
// Need to get the material info
//
MDagPath path = info.multiPath(); // path to your dag object
M3dView view = info.view();; // view to draw to
M3dView::DisplayStatus displayStatus = info.displayStatus();
// Evaluate the material and if necessary, the texture.
//
if ( ! material.evaluateMaterial( view, path ) ) {
cerr << "Couldnt evaluate\n";
}
bool drawTexture = true;
if ( drawTexture && material.materialIsTextured() ) {
material.evaluateTexture( data );
}
request.setMaterial( material );
bool materialTransparent = false;
material.getHasTransparency( materialTransparent );
if ( materialTransparent ) {
request.setIsTransparent( true );
}
// create a draw request for wireframe on shaded if
// necessary.
//
if ( (displayStatus == M3dView::kActive) ||
(displayStatus == M3dView::kLead) ||
(displayStatus == M3dView::kHilite) )
{
MDrawRequest wireRequest = info.getPrototype( *this );
wireRequest.setDrawData( data );
getDrawRequestsWireframe( wireRequest, info );
wireRequest.setToken( kDrawWireframeOnShaded );
queue.add( wireRequest );
}
}
MStatus initializePlugin( MObject obj )
{
MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");
return plugin.registerShape( "quadricShape", quadricShape::id,
&quadricShape::creator,
&quadricShape::initialize,
&quadricShapeUI::creator );
}
MStatus uninitializePlugin( MObject obj)
{
MFnPlugin plugin( obj );
return plugin.deregisterNode( quadricShape::id );
}