#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define Rad( x ) ( ( x ) * FPI / 180.0F ) #define Deg( x ) ( ( x ) * 180.0F / FPI ) #define FPI 3.14159265358979323846264338327950288419716939937510582F #define McheckErr( stat,msg ) \ if ( MS::kSuccess != stat ) \ { \ cerr << msg; \ return MS::kFailure; \ } class neoNode : public MPxNode { public: neoNode(); virtual ~neoNode(); virtual MStatus compute( const MPlug& plug, MDataBlock& data ); static void * creator(); static MStatus initialize(); public: static MTypeId id; ////////////////////////// // neoNode attributes // ////////////////////////// // Neo Parameters // --------------- static MObject alpha; // Profile (Helico-spiral) param #1 static MObject beta; // Profile (Helico-spiral) param #2 static MObject omin; // Spiral start angle static MObject omax; // Spiral end angle static MObject od; // Spiral angle step static MObject phi; // Section starting point static MObject my; // Section slant static MObject omega; // Section angle around Oz static MObject smin; // Section start angle static MObject smax; // Section end angle static MObject sd; // Section angle step static MObject A; // Distance of section from Z-axis static MObject a; // Section diameter #1 static MObject b; // Section diameter #2 static MObject scale; // Overall scale // Neo Ribs Parameters // -------------------- static MObject vamp; // Profile rib amplitude static MObject vfreq; // Profile rib frequency static MObject vrib; // Profile rib wave percent static MObject uamp; // Section rib amplitude static MObject ufreq; // Section rib frequency static MObject urib; // Section rib wave percent // Neo Primary Nodules // -------------------- static MObject P; // Position on section static MObject L; // Amplitude (extrusion) of nodule static MObject N; // Nodules frequency on profile static MObject W1; // Fatness of nodule 1 static MObject W2; // Fatness of nodule 2 static MObject nstart; // Starting point on spiral // Neo Secondary Nodules // ---------------------- static MObject P2; static MObject L2; static MObject N2; static MObject W12; static MObject W22; static MObject off2; static MObject nstart2; // Neo Tertiary Nodules // --------------------- static MObject P3; static MObject L3; static MObject N3; static MObject W13; static MObject W23; static MObject off3; static MObject nstart3; // Output mesh // ------------ static MObject outMesh; private: struct NeoParams { // Neo Parameters // --------------- float alpha; // Profile (Helico-spiral) param #1 float beta; // Profile (Helico-spiral) param #2 float omin; // Spiral start angle float omax; // Spiral end angle float od; // Spiral angle step float phi; // Section starting point float my; // Section slant float omega; // Section angle around Oz float smin; // Section start angle float smax; // Section end angle float sd; // Section angle step float A; // Distance of section from Z-axis float a; // Section diameter #1 float b; // Section diameter #2 float scale; // Overall scale // Neo Ribs Parameters // -------------------- float vamp; // Profile rib amplitude float vfreq; // Profile rib frequency float vrib; // Profile rib wave percent float uamp; // Section rib amplitude float ufreq; // Section rib frequency float urib; // Section rib wave percent // Neo Primary Nodules // -------------------- float P; // Position on section float L; // Amplitude (extrusion) of nodule float N; // Nodules frequency on profile float W1; // Fatness of nodule 1 float W2; // Fatness of nodule 2 float nstart; // Starting point on spiral // Neo Secondary Nodules // ---------------------- float P2; float L2; float N2; float W12; float W22; float off2; float nstart2; // Neo Tertiary Nodules // --------------------- float P3; float L3; float N3; float W13; float W23; float off3; float nstart3; }; NeoParams neoParams; bool redoTopology; bool rebuild; // Precomputed neo points // ----------------------- int ni, nj; float **pnts; private: float GetFloatParameter( MObject node, MObject attr ); float GetAngleParameter( MObject node, MObject attr ); static void addFloatParameter( MObject & attr, MString longName, MString briefName, float attrDefault ); static void addAngleParameter( MObject & attr, MString longName, MString briefName, float attrDefault ); void UpdateParameters(); void RedoTopology(); void Rebuild(); float Nodules( float s, float o ); float Ribs( float u, float v ); void Eval( float *p, float o, float s ); }; MTypeId neoNode::id( 0x1981a ); neoNode::neoNode() : rebuild( true ), redoTopology( true ), pnts( NULL ), ni( 0 ),nj( 0 ){ } neoNode::~neoNode(){ } MStatus neoNode::compute( const MPlug& plug, MDataBlock& data ) { MStatus returnStatus; int i, j; // Read updated input parameters // ------------------------------ UpdateParameters(); bool createNewMesh = redoTopology; RedoTopology(); Rebuild(); if ( !pnts || ni < 2 || nj < 2 ) return MS::kSuccess; if ( plug == outMesh ) { if ( !pnts || ni < 2 || nj < 2 ) return MS::kSuccess; MDataHandle outputHandle = data.outputValue( outMesh, &returnStatus ); McheckErr( returnStatus, "ERROR getting polygon data handle\n" ); MObject mesh = outputHandle.asMesh(); if ( createNewMesh || mesh.isNull() ) { MFnMeshData dataCreator; MObject newOutputData = dataCreator.create( &returnStatus ); McheckErr( returnStatus, "ERROR creating outputData" ); MFloatPointArray vertices; // Build vertices array // --------------------- for( j = 0 ; j < nj ; ++j ) { for( i = 0 ; i < ni ; ++i ) { const float *p = pnts[j] + 3 * i; vertices.append( MFloatPoint( p[0],p[1],p[2] ) ); } } // Build poly vertex count array // ------------------------------ MIntArray pcounts; i = ( nj - 1 ) * ( ni - 1 ); while( i-- ) { pcounts.append( 4 ); } // Build poly connectivity array // ------------------------------ MIntArray pconnect; for( j = 0 ; j < nj - 1 ; ++j ) { for( i = 0 ; i < ni - 1 ; ++i ) { int corner = i + j * ni; pconnect.append( corner ); pconnect.append( corner + 1 ); pconnect.append( corner + 1 + ni ); pconnect.append( corner + ni ); } } // Create some stuff needed by the mesh // ------------------------------------- MIntArray fec; MDoubleArray sp; MDoubleArray tp; // Build maya poly object // ----------------------- MStatus stat; MFnMesh meshFn; MObject mesh = meshFn.create( nj * ni, // number of vertices ( nj - 1 ) * ( ni - 1 ), // number of polygons vertices, // The points pcounts, // # of vertex for each poly pconnect, // Vertices index for each poly newOutputData, // Dependency graph data object &returnStatus ); // Update surface // --------------- outputHandle.set( newOutputData ); } else { // The topology hasn't changed, // so we can just set the points // in the existing mesh // ----------------------------------- MItMeshVertex vertIt( mesh, &returnStatus); McheckErr( returnStatus, "ERROR creating iterator\n" ); for( j = 0 ; j < nj ; ++j ) { for( i = 0 ; i < ni ; ++i ) { if ( vertIt.isDone() ) break; const float *p = pnts[j] + 3 * i; vertIt.setPosition( MPoint( p[0],p[1],p[2] ) ); vertIt.next(); } if ( vertIt.isDone() ) break; } } data.setClean( plug ); } return MS::kSuccess; } void* neoNode::creator() { return new neoNode(); } ////////////////////// // Neo Algorithms // ////////////////////// void neoNode::RedoTopology() // // Description: // Adjust our data storage to reflect new parameters // ------------------------------------------------------- { if ( !redoTopology ) return; redoTopology = false; int oldnj = pnts ? nj : 0; ni, nj = 0; /* Section start angle, Section end angle, Section angle step */ /* ----------------------------------------------------------- */ for( float s = neoParams.smin ; s < neoParams.smax ; s += neoParams.sd ) { ni++; // Lazy } /* Spiral start angle, Spiral end angle, Spiral angle step */ /* -------------------------------------------------------- */ for( float o = neoParams.omin ; o < neoParams.omax ; o += neoParams.od ) { nj++; // Lazy } if ( nj < oldnj ) { for( int i = nj ; i < oldnj ; ++i ) { if ( pnts[i] ) free( pnts[i] ); } } if ( nj != oldnj ) { pnts = ( float** ) realloc( pnts, nj * sizeof( float* ) ); } if ( nj > oldnj ) { memset( pnts + oldnj, 0, ( nj - oldnj ) * sizeof( float* ) ); } for( int j = 0 ; j < nj ; ++j ) { pnts[j] = ( float* ) realloc( pnts[j], 3 * ni * sizeof( float ) ); } } void neoNode::Rebuild() // // Description: // Rebuild the mesh geometry given the new inputs // ---------------------------------------------------- { if ( !rebuild ) return; rebuild = 0; int i; int j; float s; float o; float *p; o = neoParams.omin; // Spiral start angle for( j = 0 ; j < nj ; ++j ) { s = neoParams.smin; // Section start angle p = pnts[j]; for( i = 0 ; i < ni ; ++i ) { Eval( p, o, s ); s += neoParams.sd; // Section angle step p += 3; } o += neoParams.od; // Spiral angle step } } inline float SafeCot( float x ) { float s = sinf( x ); return s ? cosf( x ) / s : 0.0F; } inline float G( float a, float n ) { if ( !n ) return n; float z = 2.0F * FPI; a *= n / z; return z / n * ( a - floorf( 0.5F + a ) ); } float neoNode::Ribs( float u, float v ) { NeoParams& np = neoParams; float zu = 0.0F; if ( np.uamp ) /* Section rib amplitude */ { /* Section rib amplitude, Section rib frequency */ /* ---------------------- */ zu = np.uamp * cosf( 2.0F * FPI * np.ufreq * u ); /* Section rib wave percent */ /* ------------------------- */ if ( zu < 0 ) zu *= ( 1.0F - 2.0F * np.urib ); } float zv = 0.0F; if ( np.vamp ) /* Profile rib amplitude */ { /* Profile rib amplitude, Profile rib frequency */ /* ---------------------- */ zv = np.vamp * cosf( 2.0F * FPI * np.vfreq * v ); /* Profile rib wave percent */ /* ------------------------- */ if ( zv < 0 ) zv *= ( 1.0F - 2.0F * np.vrib ); } return zu + zv; } float neoNode::Nodules( float s, float o ) { NeoParams& np = neoParams; float p1; float p2; float k = 0.0F; float g = 0.0F; /* Amplitude of nodule, Nodules frequency on profile, */ /* Starting point on spiral */ /* --------------------------------------------------- */ if ( np.L && np.N && o >= np.nstart ) { g = G( o,np.N ); p1 = g / np.W2; // Fatness of nodule 2 /* Position on section, Fatness of nodule 1 */ /* ----------------------------------------- */ p2 = ( s - np.P ) / np.W1; k = np.L * expf( -4.0F * ( p1 * p1 + p2 * p2 ) ); } if ( np.L2 && np.N2 && o >= np.nstart2 ) { g = G( o + np.off2, np.N2 ); p1 = g / np.W22 ; p2 = ( s - np.P2 ) / np.W12; k += np.L2 * expf( -4.0F * ( p1 * p1 + p2 * p2 ) ); } if ( np.L3 && np.N3 && o >= np.nstart3 ) { g = G( o + np.off3, np.N3 ); p1 = g / np.W23; p2 = ( s - np.P3 ) / np.W13; k += np.L3 * expf( -4.0F * ( p1 * p1 + p2 * p2 ) ); } return k; } void neoNode::Eval( float *p, float o, float s ) { NeoParams& np = neoParams; float ss = sinf( s ); float cs = cosf( s ); /* Section diameter #1, Section diameter #2 */ /* ----------------------------------------- */ float re = 1.0F / sqrtf( cs * cs / ( np.a * np.a ) + ss * ss / ( np.b * np.b ) ); /* Overall scale, Profile (Helico-spiral) param #1 */ /* ------------------------------------------------ */ float sc = np.scale * expf( o * SafeCot( np.alpha ) ); /* Section starting point */ /* ----------------------- */ float csphi = cosf( s + np.phi ); float ssphi = sinf( s + np.phi ); /* Profile (Helico-spiral) param #2 */ /* --------------------------------- */ float sbeta = sinf( np.beta ); /* Section slant */ /* -------------- */ float smy = sinf( np.my ); /* COMPLETE DATA */ /* -------------- */ float r = re + Nodules( s, o ) + Ribs( s, o ); /* Section angle around Oz */ /* ------------------------ */ float x = np.A * sbeta * cosf( o ) + r * csphi * cosf( o + np.omega ) - r * smy * ssphi * sinf( o ); float y = - np.A * sbeta * sinf( o ) - r * csphi * sinf( o + np.omega ) - r * smy * ssphi * cosf( o ); float z = - np.A * cosf( np.beta ) + r * ssphi * cosf( np.my ); p[0] = x * sc; p[1] = -z * sc; p[2] = y * sc; } ///////////////////////////////////// // Attribute Setup and Maintenance // ///////////////////////////////////// // Neo parameters // --------------- MObject neoNode::alpha; // Profile (Helico-spiral) param #1 MObject neoNode::beta; // Profile (Helico-spiral) param #2 MObject neoNode::omin; // Spiral start angle MObject neoNode::omax; // Spiral end angle MObject neoNode::od; // Spiral angle step MObject neoNode::phi; // Section starting point MObject neoNode::my; // Section slant MObject neoNode::omega; // Section angle around Z MObject neoNode::smin; // Section start angle MObject neoNode::smax; // Section end angle MObject neoNode::sd; // Section angle step MObject neoNode::A; // Distance of section from Z-axis MObject neoNode::a; // Section diameter #1 MObject neoNode::b; // Section diameter #2 MObject neoNode::scale; // Overall scale // Neo Ribs Parameters // -------------------- MObject neoNode::vamp; // Profile rib amplitude MObject neoNode::vfreq; // Profile rib frequency MObject neoNode::vrib; // Profile rib/wave percent MObject neoNode::uamp; // Section rib amplitude MObject neoNode::ufreq; // Section rib frequency MObject neoNode::urib; // Section rib/wave percent // Neo Primary Nodules // -------------------- MObject neoNode::P; // Position on section MObject neoNode::L; // Amplitude (extrusion) of nodule MObject neoNode::N; // Nodules frequency on profile MObject neoNode::W1; // Fatness of nodule 1 MObject neoNode::W2; // Fatness of nodule 2 MObject neoNode::nstart; // Starting point on spiral // Neo Secondary Nodules // ---------------------- MObject neoNode::P2; MObject neoNode::L2; MObject neoNode::N2; MObject neoNode::W12; MObject neoNode::W22; MObject neoNode::off2; MObject neoNode::nstart2; // Neo Tertiary Nodules // --------------------- MObject neoNode::P3; MObject neoNode::L3; MObject neoNode::N3; MObject neoNode::W13; MObject neoNode::W23; MObject neoNode::off3; MObject neoNode::nstart3; // Output mesh // ------------ MObject neoNode::outMesh; void neoNode::addFloatParameter( MObject & attr, MString longName, MString briefName, float attrDefault ) // // Description: // Add a float input parameter to the node // --------------------------------------------- { MStatus stat; MFnNumericAttribute nAttr; attr = nAttr.create( longName, briefName, MFnNumericData::kFloat, 0.0, &stat ); if ( stat != MS::kSuccess ) throw stat; stat = nAttr.setDefault ( attrDefault ); if ( stat != MS::kSuccess ) throw stat; stat = nAttr.setKeyable ( true ); if ( stat != MS::kSuccess ) throw stat; stat = nAttr.setCached ( true ); if ( stat != MS::kSuccess ) throw stat; stat = nAttr.setStorable ( true ); if ( stat != MS::kSuccess ) throw stat; stat = addAttribute( attr ); if ( stat != MS::kSuccess ) throw stat; stat = attributeAffects( attr, outMesh ); if ( stat != MS::kSuccess ) throw stat; } void neoNode::addAngleParameter( MObject & attr, MString longName, MString briefName, float attrDefault ) // // Description: // Add an angle input parameter to the node // ---------------------------------------------- { MStatus stat; MFnUnitAttribute uAttr; MAngle defaultAngle( ( double )attrDefault, MAngle::kDegrees ); attr = uAttr.create( longName, briefName, defaultAngle, &stat ); if ( stat != MS::kSuccess ) throw stat; stat = uAttr.setKeyable ( true ); if ( stat != MS::kSuccess ) throw stat; stat = uAttr.setCached ( true ); if ( stat != MS::kSuccess ) throw stat; stat = uAttr.setStorable ( true ); if ( stat != MS::kSuccess ) throw stat; stat = addAttribute( attr ); if ( stat != MS::kSuccess ) throw stat; stat = attributeAffects( attr, outMesh ); if ( stat != MS::kSuccess ) throw stat; } MStatus neoNode::initialize() // // Description: // Set up node attributes // ---------------------------- { MFnTypedAttribute typedFn; MStatus stat; outMesh = typedFn.create( "outMesh", "o", MFnData::kMesh, &stat ); if ( MS::kSuccess != stat ) { cerr << "ERROR creating animCube output attribute\n"; return stat; } typedFn.setStorable( false ); typedFn.setWritable( false ); stat = addAttribute( outMesh ); McheckErr( stat, "ERROR adding attribute" ); try { // Neo Parameters // --------------- addAngleParameter ( alpha, "profileParam1", "pp1", 80.0F ); addAngleParameter ( beta, "profileParam2", "pp2", 90.0F ); addAngleParameter ( omin, "spiralStartAngle", "sps", 0.0F ); addAngleParameter ( omax, "spiralEndAngle", "spe", 1200.0F ); addAngleParameter ( od, "spiralAngleStep", "spa", 4.0F ); addAngleParameter ( phi, "sectionStartingPoint", "ssp", 1.0F ); addAngleParameter ( my, "sectionSlant", "ss", 1.0F ); addAngleParameter ( omega, "sectionAngleZ", "saz", 1.0F ); addAngleParameter ( smin, "sectionStartAngle", "ssa", -190.0F ); addAngleParameter ( smax, "sectionEndAngle", "sea", 190.0F ); addAngleParameter ( sd, "sectionAngleStep", "sas", 17.0F ); addFloatParameter ( A, "distanceFromZ", "dfz", 1.9F ); addFloatParameter ( a, "sectionDiameter1", "sd1", 1.0F ); addFloatParameter ( b, "sectionDiameter2", "sd2", 0.9F ); addFloatParameter ( scale, "scale", "s", 0.03F ); // Neo Ribs Parameters // -------------------- addFloatParameter ( vamp, "profileRibAmplitude", "pra", 0.0F ); addFloatParameter ( vfreq, "profileRibFrequency", "prf", 0.0F ); addFloatParameter ( vrib, "profileRibWavePercent", "prw", 0.0F ); addFloatParameter ( uamp, "sectionRibAmplitude", "sra", 0.0F ); addFloatParameter ( ufreq, "sectionRibFrequency", "srf", 0.0F ); addFloatParameter ( urib, "sectionRibWavePercent", "srw", 0.0F ); // Neo Primary Nodules // -------------------- addAngleParameter ( P, "positionOnSection1", "ps1", 10.0F ); addFloatParameter ( L, "noduleAmplitude1", "na1", 1.0F ); addFloatParameter ( N, "noduleProfileFrequency1", "nf1", 15.0F ); addAngleParameter ( W1, "noduleFatness1-1", "f11", 100.0F ); addAngleParameter ( W2, "noduleFatness2-1", "f21", 20.0F ); addAngleParameter ( nstart,"spiralStartingPoint1", "sp1", 0.0F ); // Neo Secondary Nodules // ---------------------- addAngleParameter ( P2, "positionOnSection2", "ps2", 0.0F ); addFloatParameter ( L2, "noduleAmplitude2", "na2", 0.0F ); addFloatParameter ( N2, "noduleProfileFrequency2", "nf2", 0.0F ); addAngleParameter ( W12, "noduleFatness1-2", "f12", 30.0F ); addAngleParameter ( W22, "noduleFatness2-2", "f22", 30.0F ); addAngleParameter ( off2, "noduleOffset2", "no2", 0.0F ); addAngleParameter ( nstart2,"spiralStartingPoint2", "sp2", 0.0F ); // Neo Tertiary Nodules // --------------------- addAngleParameter ( P3, "positionOnSection3", "ps3", 0.0F ); addFloatParameter ( L3, "noduleAmplitude3", "na3", 0.0F ); addFloatParameter ( N3, "noduleProfileFrequency3", "nf3", 0.0F ); addAngleParameter ( W13, "noduleFatness1-3", "f13", 30.0F ); addAngleParameter ( W23, "noduleFatness2-3", "f23", 30.0F ); addAngleParameter ( off3, "noduleOffset3", "no3", 0.0F ); addAngleParameter ( nstart3,"spiralStartingPoint3", "sp3", 0.0F ); } catch ( MStatus stat ) { fprintf( stderr,"Attribute Initialize Failed\n" ); return stat; } return MS::kSuccess; } inline float neoNode::GetFloatParameter( MObject node, MObject attr ) { MPlug plug( node, attr ); float value; plug.getValue( value ); return value; } inline float neoNode::GetAngleParameter( MObject node, MObject attr ) { MPlug plug( node, attr ); MAngle angle; plug.getValue( angle ); return ( float )angle.asRadians(); } #define UpdateFloatAttr( ATTR,TOPOLOGY ) \ oldValue = neoParams. ## ATTR; \ neoParams. ## ATTR = GetFloatParameter( thisObj, ATTR ); \ if ( neoParams. ## ATTR != oldValue ) \ { \ rebuild = true; \ redoTopology = TOPOLOGY ? true : redoTopology; \ } #define UpdateAngleAttr( ATTR,TOPOLOGY ) \ oldValue = neoParams. ## ATTR; \ neoParams. ## ATTR = GetAngleParameter( thisObj, ATTR ); \ if ( neoParams. ## ATTR != oldValue ) \ { \ rebuild = true; \ redoTopology = TOPOLOGY ? true : redoTopology; \ } void neoNode::UpdateParameters() // // Description: // Read all of the neo parameters and determine what has changed // ------------------------------------------------------------------- { MObject thisObj = thisMObject(); float oldValue; // Neo Parameters // --------------- UpdateAngleAttr ( alpha, false ); UpdateAngleAttr ( beta, false ); // These settings change the topology of the geometry // --------------------------------------------------- UpdateAngleAttr ( omin, true ); UpdateAngleAttr ( omax, true ); UpdateAngleAttr ( od, true ); UpdateAngleAttr ( phi, false ); UpdateAngleAttr ( my, false ); UpdateAngleAttr ( omega, false ); // These settings change the topology of the geometry // --------------------------------------------------- UpdateAngleAttr ( smin, true ); UpdateAngleAttr ( smax, true ); UpdateAngleAttr ( sd, true ); UpdateFloatAttr ( A, false ); UpdateFloatAttr ( a, false ); UpdateFloatAttr ( b, false ); UpdateFloatAttr ( scale, false ); // Neo Ribs Parameters // -------------------- UpdateFloatAttr ( vamp, false ); UpdateFloatAttr ( vfreq, false ); UpdateFloatAttr ( vrib, false ); UpdateFloatAttr ( uamp, false ); UpdateFloatAttr ( ufreq, false ); UpdateFloatAttr ( urib, false ); // Neo Primary Nodules // -------------------- UpdateAngleAttr ( P, false ); UpdateFloatAttr ( L, false ); UpdateFloatAttr ( N, false ); UpdateAngleAttr ( W1, false ); UpdateAngleAttr ( W2, false ); UpdateAngleAttr ( nstart, false ); // Neo Secondary Nodules // ---------------------- UpdateAngleAttr ( P2, false ); UpdateFloatAttr ( L2, false ); UpdateFloatAttr ( N2, false ); UpdateAngleAttr ( W12, false ); UpdateAngleAttr ( W22, false ); UpdateAngleAttr ( off2, false ); UpdateAngleAttr ( nstart2, false ); // Neo Tertiary Nodules // --------------------- UpdateAngleAttr ( P3, false ); UpdateFloatAttr ( L3, false ); UpdateFloatAttr ( N3, false ); UpdateAngleAttr ( W13, false ); UpdateAngleAttr ( W23, false ); UpdateAngleAttr ( off3, false ); UpdateAngleAttr ( nstart3, false ); } //////////////////////////// // Plug-in Initialization // //////////////////////////// MStatus initializePlugin( MObject obj ) { MStatus status; MFnPlugin plugin( obj, "Yuuichi Kawamoto", "2.5 - 4.0", "Any" ); status = plugin.registerNode( "neo", neoNode::id, &neoNode::creator, &neoNode::initialize, MPxNode::kDependNode ); if ( !status ) { status.perror(" registerNode" ); return status; } return status; } MStatus uninitializePlugin( MObject obj ) { MStatus status; MFnPlugin plugin( obj ); status = plugin.deregisterNode( neoNode::id ); if ( !status ) { status.perror( "deregisterNode" ); return status; } return status; } // end of script