home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1995-05-17 | 43.2 KB | 1,319 lines
// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT // see COPYRIGHT for reuse legalities // #import "WWInterp.h" #import "EveVarTypeInfo.h" #define PI (3.1415926535897932384626433) #define DtoR (PI/180.0) #define RtoD (180.0/PI) // for the routines I'm using written by Darwyn Peachey // from the book: // "Texturing and Modeling: A Procedural Approach" // by Ebert, Musgrave, Peachey, Perlin, and Worley // ISBN 0-12-228761-4 #import "proctext.h" @implementation WWInterp + initialize { return [WWInterp setVersion:1], self; } // need to get rid of this! static char errBuf[1024]; /////////////////////////////// ///// math functions ////////// /////////////////////////////// static int exprCmd_pi(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // pi takes no arguments; returns // it returns a double corresponding to the converted value in radians resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = (double)3.1415926535897932384626433; return TCL_OK; } static int exprCmd_radians(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // radians takes one argument; a value expressed in degrees // it returns a double corresponding to the converted value in radians resultPtr->type = TCL_DOUBLE; if (args->type == TCL_INT) { resultPtr->doubleValue = (double)(args->intValue) * DtoR; } else { resultPtr->doubleValue = args->doubleValue * DtoR; } return TCL_OK; } static int exprCmd_degrees(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // degrees takes one argument; a value expressed in radians // it returns a double corresponding to the converted value in degrees resultPtr->type = TCL_DOUBLE; if (args->type == TCL_INT) { resultPtr->doubleValue = (double)(args->intValue) * RtoD; } else { resultPtr->doubleValue = args->doubleValue * RtoD; } return TCL_OK; } static int exprCmd_sign(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // sign takes one argument // it returns a double : -1 if the value is negative, 0 if zero, and +1 is the value is positive resultPtr->type = TCL_INT; if (args->type == TCL_INT) { if (args->intValue == 0) { resultPtr->intValue = 0; } else { if (args->intValue < 0) { resultPtr->intValue = -1; } else { resultPtr->intValue = 1; } } } else { if (args->doubleValue == 0.0) { resultPtr->intValue = 0; } else { if (args->doubleValue < 0.0) { resultPtr->intValue = -1; } else { resultPtr->intValue = 1; } } } return TCL_OK; } static int exprCmd_min(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // min takes two arguments // it returns a value corresponding to minimum of the two if (args[0].type == TCL_INT) { if (args[1].type == TCL_INT) // both args are ints { resultPtr->type = TCL_INT; if (args[0].intValue < args[1].intValue) { resultPtr->intValue = args[0].intValue; } else { resultPtr->intValue = args[1].intValue; } } else // the first arg is an int, the second is a double { resultPtr->type = TCL_DOUBLE; if (args[0].intValue < args[1].doubleValue) { resultPtr->doubleValue = (double)(args[0].intValue); } else { resultPtr->doubleValue = args[1].doubleValue; } } } else // the first arg is a double { if (args[1].type == TCL_DOUBLE) // both args are doubles { resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue < args[1].doubleValue) { resultPtr->doubleValue = args[0].doubleValue; } else { resultPtr->doubleValue = args[1].doubleValue; } } else // the first arg is a double, the second is an int { resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue < args[1].intValue) { resultPtr->doubleValue = args[0].doubleValue; } else { resultPtr->intValue = (double)(args[1].intValue); } } } return TCL_OK; } static int exprCmd_max(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // max takes two arguments // it returns a value corresponding to maximum of the two if (args[0].type == TCL_INT) { if (args[1].type == TCL_INT) // both args are ints { resultPtr->type = TCL_INT; if (args[0].intValue > args[1].intValue) { resultPtr->intValue = args[0].intValue; } else { resultPtr->intValue = args[1].intValue; } } else // the first arg is an int, the second is a double { resultPtr->type = TCL_DOUBLE; if (args[0].intValue > args[1].doubleValue) { resultPtr->doubleValue = (double)(args[0].intValue); } else { resultPtr->doubleValue = args[1].doubleValue; } } } else // the first arg is a double { if (args[1].type == TCL_DOUBLE) // both args are doubles { resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue > args[1].doubleValue) { resultPtr->doubleValue = args[0].doubleValue; } else { resultPtr->doubleValue = args[1].doubleValue; } } else // the first arg is a double, the second is an int { resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue > args[1].intValue) { resultPtr->doubleValue = args[0].doubleValue; } else { resultPtr->intValue = (double)(args[1].intValue); } } } return TCL_OK; } static int exprCmd_clamp(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // clamp takes three float arguments, a, min, max // it returns a value clamped between min and max resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue < args[1].doubleValue) { resultPtr->doubleValue = args[1].doubleValue; } else { if (args[0].doubleValue > args[2].doubleValue) { resultPtr->doubleValue = args[2].doubleValue; } else { resultPtr->doubleValue = args[0].doubleValue; } } return TCL_OK; } static int exprCmd_step(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // clamp takes two float arguments: min, value // if value is less than min, it returns 0, otherwise it returns 1 resultPtr->type = TCL_DOUBLE; if (args[1].doubleValue < args[0].doubleValue) { resultPtr->doubleValue = 0.0; } else { resultPtr->doubleValue = 1.0; } return TCL_OK; } static int exprCmd_spline(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { float vec[4]; // spline takes 5 arguments; the first is the point along u (0 -> 1) and the // other 4 are control points // it smoothly interpolates between them resultPtr->type = TCL_DOUBLE; vec[0] = args[1].doubleValue; vec[1] = args[2].doubleValue; vec[2] = args[3].doubleValue; vec[3] = args[4].doubleValue; resultPtr->doubleValue = spline(args[0].doubleValue, 4, vec); return TCL_OK; } static int exprCmd_smoothstep(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // smoothstep takes three float arguments: min, max, value // if value is less than min, it returns min, if it's above max, it returns max, otherwise // it smoothly interpolates between them resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = smoothstep(args[0].doubleValue, args[1].doubleValue, args[2].doubleValue); return TCL_OK; } static int exprCmd_lerpDown(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // lerpDown takes three float arguments: u, min, max // if u is less than or equal 0, it returns max, if it's above or equal 1, it returns min, otherwise // it smoothly interpolates between them, downwards resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue <= 0.0) { resultPtr->doubleValue = args[2].doubleValue; } else { if (args[0].doubleValue >= 1.0) { resultPtr->doubleValue = args[1].doubleValue; } else { resultPtr->doubleValue = args[2].doubleValue; // give it a base resultPtr->doubleValue -= args[0].doubleValue * (args[2].doubleValue - args[1].doubleValue); } } return TCL_OK; } static int exprCmd_lerpUp(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // lerpUp takes three float arguments: u, min, max // if u is less than 0, it returns min, if it's above 1, it returns max, otherwise // it smoothly interpolates between them resultPtr->type = TCL_DOUBLE; if (args[0].doubleValue <= 0.0) { resultPtr->doubleValue = args[1].doubleValue; } else { if (args[0].doubleValue >= 1.0) { resultPtr->doubleValue = args[2].doubleValue; } else { resultPtr->doubleValue = args[1].doubleValue; // give it a base resultPtr->doubleValue += args[0].doubleValue * (args[2].doubleValue - args[1].doubleValue); } } return TCL_OK; } static int exprCmd_noise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // noise takes three float arguments: x, y, and z // it returns some value between 0 and 1 which is a random function of its argument resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = (double)gnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue)); return TCL_OK; } static int exprCmd_gvnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // gvnoise takes three float arguments: x, y, and z // it returns some value between 0 and 1 which is a random function of its argument resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = (double)gvnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue)); return TCL_OK; } static int exprCmd_scnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // scoise takes three float arguments: x, y, and z // it returns some value between 0 and 1 which is a random function of its argument resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = (double)scnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue)); return TCL_OK; } static int exprCmd_vnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // scoise takes three float arguments: x, y, and z // it returns some value between 0 and 1 which is a random function of its argument resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = (double)vnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue)); return TCL_OK; } static int exprCmd_vcnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr) { // scoise takes three float arguments: x, y, and z // it returns some value between 0 and 1 which is a random function of its argument resultPtr->type = TCL_DOUBLE; resultPtr->doubleValue = (double)vcnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue)); return TCL_OK; } /////////////////////////////// ///// math commands /////////// /////////////////////////////// // the following routines are more flexible versions of some of the above math functions static int cmd_spline(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { char *my_args = "u rank {knot0 knot1 knot2 ... knotN}"; char **argv2; int argc2, i, j, rank, nKnots, knotArgIndex; float u, *knots, *retVector, *knotPtr; double dU, dKnot; if (argc != 4) // need to allow non-list knots... { sprintf(errBuf, "USAGE: %s %s (not right number of args: need at least 4 knots (remember the knots should be a single arg - enclose them in quotes)", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } if (Tcl_GetDouble(interp, argv[1], &dU) != TCL_OK) { sprintf(errBuf, "USAGE: %s %s (1st arg not a valid float)", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } u = (float)dU; if (Tcl_GetInt(interp, argv[2], &rank) != TCL_OK) { sprintf(errBuf, "USAGE: %s %s (2nd arg not a valid integer)", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } if (argc == 4) // assuming second arg is actually a list; need to split it { Tcl_SplitList(interp, argv[3], &argc2, &argv2); // the knot vector must be at least (rank * 4) if (argc2 < (rank * 4)) { sprintf(errBuf, "USAGE: %s %s (after splitting 3rd arg still not enough args: need at least 4 knots)", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } if (argc2 % rank) { sprintf(errBuf, "USAGE: %s %s (after splitting 3rd arg, number of elements wasn't evenly divisible by %d)", argv[0], my_args, rank); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // okay, the number of elements in the list is correct. // now we need to malloc up memory for the float vector to hand spline() // we only need enough memory for a rank==1 vector, since we'll spline // each component separately. nKnots = argc2 / rank; knots = (float *)malloc(sizeof(float) * nKnots); retVector = (float *)malloc(sizeof(float) * rank); for (i = 0; i < rank; i++) { knotPtr = knots; for (j = 0; j < nKnots; j++) { knotArgIndex = (j * rank) + i; if (Tcl_GetDouble(interp, argv2[knotArgIndex], &dKnot) != TCL_OK) { sprintf(errBuf, "USAGE: %s %s (vector component (%d, %d) not a valid float)", argv[0], my_args, j, i); Tcl_AppendResult(interp, errBuf, (char *)NULL); free(knots); free(retVector); return TCL_ERROR; } *knotPtr++ = (float)dKnot; } retVector[i] = spline(u, nKnots, knots); } for (i = 0; i < rank; i++) { sprintf(errBuf, "%f ", retVector[i]); Tcl_AppendElement(interp, errBuf); } } else // should also allow non-list version... {} return TCL_OK; } // registerSampleGenerator static int cmd_registerSampleGenerator(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { char *my_args = "sampleGeneratorName weight"; if (argc != 3) { sprintf(errBuf, "USAGE: %s %s", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } if (![me registerCurrentSampleGeneratorName:(const char *)argv[1] weight:(float)atof(argv[2])]) { sprintf(errBuf, "unable to register sample generator %s with a weight of %f", argv[1], (float)atof(argv[2])); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } return TCL_OK; } // sampleSet static int cmd_setSample(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { char *my_args = "varName [newValue sampleGeneratorName]"; char *varValue; if ((argc != 2) && (argc != 4)) { sprintf(errBuf, "USAGE: %s %s", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } if (argc == 2) { varValue = [me getVar:argv[1]]; if (!varValue) { sprintf(errBuf, "can't read \"%s\": no such variable", argv[1]); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } Tcl_AppendResult(interp, varValue, (char *)NULL); return TCL_OK; } [me setCurrentSampleGeneratorName:argv[3]]; varValue = [me setVar:argv[1] toValue:argv[2]]; if (!varValue) { sprintf(errBuf, "couldn't set \"%s\" to \"%s\"", argv[1], argv[2]); Tcl_AppendResult(interp, errBuf, (char *)NULL); [me setCurrentSampleGeneratorName:NULL]; return TCL_ERROR; } Tcl_AppendResult(interp, varValue, (char *)NULL); [me setCurrentSampleGeneratorName:NULL]; return TCL_OK; } /////////////////////////////// ///// type functions ////////// /////////////////////////////// static char *WriteProcForPI(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags) { return "PI is an eve-specific read-only variable"; } #if 0 static char *WriteProcForEnumVariable(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags) { char *varString; // at this point, the new value of the variable has been written. We need to make // sure that the new value is okay. If it is, great, we write the new value into // the varTypeInfo object as the value. If it isn't okay, we need to grab the old // value from the varTypeInfo object and reset the value of the variable. varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2]; if ([[(WWTCLVarTrace *)clientData datum] isThisAValidEnumValue:varString]) { // this is a valid value; update varTypeInfo object and get out [[(WWTCLVarTrace *)clientData datum] setValue:varString]; return NULL; } else { // value isn't valid; reset to old value and return error [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:(char *)[[(WWTCLVarTrace *)clientData datum] value]]; return "invalid value for enumerated string variable"; } return NULL; } // the following routines extend tcl with some rudimentary typing, which is used in eve static int cmd_setEnum(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { char *my_args = "varName [initialValue] [list-of-possible-values]"; id varTypeInfo; char **argv2; int argc2, i; if ((argc != 2) && (argc != 4)) { sprintf(errBuf, "USAGE: %s %s", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // if there is only two arguments, return the enumeration list of the arg // if it doesn't already exist, we need to return an error // probably should have a hash Table of the various variables, right? if (argc == 2) { // they just want to know the value, not set it if (![me enumExists:argv[1]]) { sprintf(errBuf, "can't read \"%s\": no such enumerated variable", argv[0]); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // okay, the variable does exist as an enumerated value // we want to return return TCL_OK; } // the first thing to do is unset this variable so that if any unset // traces need to be run, they will be. [me unsetVar:argv[1]]; // otherwise, check to see if there is an enum for this named variable // already, throw it away, and generate a new one. // this routine is pretty cool. It constrains the values of the // named variable to be ones matching the list of list. What's neat is // that any given element of that list can be a "regular expression". // All writes to this variable will checked against this new value. // actually, it shouldn't just alloc one up, it should ask the interp // for it, so the interp can see if it already has one... varTypeInfo = [[EveVarTypeInfo alloc] initForVar:argv[1] withInterp:me]; [me addVarTypeInfo:varTypeInfo]; [varTypeInfo setTypeEnum]; Tcl_SplitList(interp, argv[2], &argc2, &argv2); for (i = 0; i < argc2; i++) { [varTypeInfo addEnum:argv2[i]]; } // need to set up a write trace for this variable, handing in the id // of the varTypeInfo object. In this routine, a message needs to get // sent to the varTypeInfo with the value of the new value. If it's // okay, the varTypeInfo object will return YES. If it's not, it will // return NO. At this point, if the new value is okay, the varTypeInfo // object should get sent a message setting the value to be that; otherwise // the old value should be gotten from there and the new value should be // set. [me traceWritesOn:argv[1] andCall:(Tcl_VarTraceProc *)WriteProcForEnumVariable usingData:(ClientData)varTypeInfo]; return TCL_OK; } static char *WriteProcForFloatVariable(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags) { char *varString; // at this point, the new value of the variable has been written. We need to make // sure that the new value is okay. If it is, great, we write the new value into // the varTypeInfo object as the value. If it isn't okay, we need to grab the old // value from the varTypeInfo object and reset the value of the variable. varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2]; if ([[(WWTCLVarTrace *)clientData datum] isThisAValidEnumValue:varString]) { // this is a valid value; update varTypeInfo object and get out [[(WWTCLVarTrace *)clientData datum] setValue:varString]; return NULL; } else { // value isn't valid; reset to old value and return error [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:(char *)[[(WWTCLVarTrace *)clientData datum] value]]; return "invalid value for enumerated string variable"; } return NULL; } // the following routines extend tcl with some rudimentary typing, which is used in eve static int cmd_setFloatMinMax(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { char *my_args = "varName [initialFloatValue] [floatMinValue floatMaxValue]"; id varTypeInfo; char **argv2; int argc2, i; if ((argc != 2) && (argc != 3) && (argc != 5)) { sprintf(errBuf, "USAGE: %s %s", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // if there is only two arguments, return the value, min, and max of the variable // if it doesn't already exist, we need to return an error // probably should have a hash Table of the various variables, right? if (argc == 2) { // they just want to know the value, not set it if (![me floatMinMaxExists:argv[1]]) { sprintf(errBuf, "can't read \"%s\": no such minMax constrained float variable", argv[0]); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // okay, the variable does exist as a min/max float // we want to return cons up the current value, min, and max return TCL_OK; } if (argc == 3) // new value { // they just want to set a new value, not define a new one if (![me floatMinMaxExists:argv[1]]) { sprintf(errBuf, "can't read \"%s\": no such minMax constrained float variable", argv[0]); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // okay, the variable does exist as a min/max float // we want to just set the new value and then return it. // we let the variable tracing routines check the bounds return [me setVar:argv[1] toValue:argv[2]]; } // the first thing to do is unset this variable so that if any unset // traces need to be run, they will be. [me unsetVar:argv[1]]; // otherwise, check to see if there is an enum for this named variable // already, throw it away, and generate a new one. return TCL_OK; } #endif static char *WriteProcForReadOnly(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags) { return "this is a read-only variable"; } // the following routines extend tcl with some rudimentary typing, which is used in eve static int cmd_setReadOnly(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { char *my_args = "readOnlyVarName value"; if (argc != 3) { sprintf(errBuf, "USAGE: %s %s", argv[0], my_args); Tcl_AppendResult(interp, errBuf, (char *)NULL); return TCL_ERROR; } // the first thing to do is unset this variable so that if any unset // traces need to be run, they will be. [me unsetVar:argv[1]]; [me setVar:argv[1] toValue:argv[2]]; [me traceWritesOn:argv[1] andCall:(Tcl_VarTraceProc *)WriteProcForReadOnly usingData:(ClientData)me]; // need to add it to the wwVarList, so we know that it's special... return TCL_OK; } static int cmd_noop(WWInterp *me, Tcl_Interp *interp, int argc, char **argv) { return TCL_OK; } - (char *)setVar:(char *)variableName toReadOnlyValue:(char *)newValue { char *ret; [self unsetVar:variableName]; ret = [self setVar:variableName toValue:newValue]; [self traceWritesOn:variableName andCall:(Tcl_VarTraceProc *)WriteProcForReadOnly usingData:(ClientData)nil]; return ret; } - (char *)setVar2:(char *)variableName1 :(char *)variableName2 toReadOnlyValue:(char *)newValue { char *ret; [self unsetVar2:variableName1 :variableName2]; ret = [self setVar2:variableName1 :variableName2 toValue:newValue]; [self traceWritesOn2:variableName1 :variableName2 andCall:(Tcl_VarTraceProc *)WriteProcForReadOnly usingData:(ClientData)nil]; return ret; } ////////////////////////////////////////////// ////// okay, on to the instance methods ////// ////////////////////////////////////////////// - setupInterp { int i, howMany, numArgs; Tcl_ValueType argTypes[10]; char *valAsString, *varName; if (interpSetup) { return self; } valAsString = (char *)NXZoneCalloc([self zone], 256, sizeof(char)); varName = (char *)NXZoneCalloc([self zone], 256, sizeof(char)); // add the new functions to do variable typing, including their trace functions //[self addCommand:"setEnum" :(Tcl_CmdProc *)cmd_setEnum :self]; [self addCommand:"setReadOnly" :(Tcl_CmdProc *)cmd_setReadOnly :self]; [self addCommand:"spline" :(Tcl_CmdProc *)cmd_spline :self]; [self addCommand:"setSample" :(Tcl_CmdProc *)cmd_setSample :self]; [self addCommand:"sSet" :(Tcl_CmdProc *)cmd_setSample :self]; [self addCommand:"setS" :(Tcl_CmdProc *)cmd_setSample :self]; [self addCommand:"sS" :(Tcl_CmdProc *)cmd_setSample :self]; [self addCommand:"registerSampleGenerator" :(Tcl_CmdProc *)cmd_registerSampleGenerator :self]; [self addCommand:"sampleGeneratorIsExecuting" :(Tcl_CmdProc *)cmd_noop :self]; // need to make this do something, pal! // now add in the new expr math functions // pi() numArgs = 0; [self createMathFunc:"pi" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_pi clientData:(ClientData)self]; // radians(a) numArgs = 1; argTypes[0] = TCL_DOUBLE; [self createMathFunc:"radians" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_radians clientData:(ClientData)self]; // degrees(a) numArgs = 1; argTypes[0] = TCL_DOUBLE; [self createMathFunc:"degrees" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_degrees clientData:(ClientData)self]; // sign(a) numArgs = 1; argTypes[0] = TCL_EITHER; [self createMathFunc:"sign" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_sign clientData:(ClientData)self]; // min(a, b) numArgs = 2; argTypes[0] = TCL_EITHER; argTypes[1] = TCL_EITHER; [self createMathFunc:"min" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_min clientData:(ClientData)self]; // max(a, b) numArgs = 2; argTypes[0] = TCL_EITHER; argTypes[1] = TCL_EITHER; [self createMathFunc:"max" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_max clientData:(ClientData)self]; // clamp(a, min, max) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"clamp" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_clamp clientData:(ClientData)self]; // step(min, value) numArgs = 2; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; [self createMathFunc:"step" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_step clientData:(ClientData)self]; // spline(u, pt0, pt1, pt2, pt3) numArgs = 5; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; argTypes[3] = TCL_DOUBLE; argTypes[4] = TCL_DOUBLE; [self createMathFunc:"spline" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_spline clientData:(ClientData)self]; // smoothstep(min, max, value) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"smoothstep" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_smoothstep clientData:(ClientData)self]; // lerpDown(u, min, max) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"lerpDown" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_lerpDown clientData:(ClientData)self]; // lerpUp(u, min, max) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"lerpUp" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_lerpUp clientData:(ClientData)self]; // noise(x, y, z) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"noise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_noise clientData:(ClientData)self]; // gvnoise(x, y, z) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"gvnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_gvnoise clientData:(ClientData)self]; // scnoise(x, y, z) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"scnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_scnoise clientData:(ClientData)self]; // vnoise(x, y, z) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"vnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_vnoise clientData:(ClientData)self]; // vcnoise(x, y, z) numArgs = 3; argTypes[0] = TCL_DOUBLE; argTypes[1] = TCL_DOUBLE; argTypes[2] = TCL_DOUBLE; [self createMathFunc:"vcnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes proc:(Tcl_MathProc *)exprCmd_vcnoise clientData:(ClientData)self]; // now put any read only constants into the environment (like PI) strcpy(varName, "PI"); sprintf(valAsString, "%f", PI); // turn the value into its tcl equivalent [self setVar:varName toValue:valAsString]; [self traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForPI usingData:(ClientData)self]; // wave NOTE: need to make this un-unsettable with an unset trace... // need to grovel over the typed variables and reset the traces. howMany = [wwVarList count]; for (i = 0; i < howMany; i++) { // for each variable, reset the traces appropriately // I really should have probably done this in read: NXLogError("wave is lame\n"); } interpSetup = YES; NXZoneFree([self zone], valAsString); NXZoneFree([self zone], varName); return self; } - init { [super init]; wwVarList = [[List alloc] init]; interpSetup = NO; [self setupInterp]; return self; } - awake { [super awake]; interpSetup = NO; [self setupInterp]; return self; } - setRunTime:obj { runTime = obj; return self; } - registerCurrentSampleGeneratorName:(const char *)newCurrentSampleGeneratorName weight:(float)weight { return [runTime registerCurrentSampleGeneratorName:newCurrentSampleGeneratorName weight:weight]; } - setCurrentSampleGeneratorName:(const char *)newCurrentSampleGeneratorName { return [runTime setCurrentSampleGeneratorName:newCurrentSampleGeneratorName]; } - addVarTypeInfo:newVarTypeInfo { [wwVarList addObject:newVarTypeInfo]; return self; } - (BOOL)enumExists:(const char *)enumVarName { // this method gets called when defining a new enumerated string value // the idea is to check and see if an enumerated variable for this // guy already exists. Note this routine doesn't check to see if // there is any other type of variable already defined that has // this name return YES; } - (int)unsetVar:(char *)variableName { // first need to check and see if this variable is "special". // If so, do the appropriate activity to get rid of it. return [super unsetVar:variableName]; } // we need to deal with a few extra things here... // need to think about "sets" that happened in the context of a EveCmd // need to deal with typed variables (which, in a sense, a set inside an EveCmd is) - writeState:(NXStream *)stream { int i, j, argc, argc2, ret, cnt; char **argv, **argv2, *varNameList, *procNameList, *aCmd, *varValue, *arrayElementList, *arrayName; Tcl_DString setCmd, infoCmd, procCmd, arrayElementCmd; BOOL skip; // at this point, we need to suck out the names and values of all // the global variables in the tcl interp. Then we need to get // the names, args, and bodies of all the procs that have been // defined. We write these out, and then when we do a read:, // we shove those back into the interp. // okay, so first we send "info globals" to the interp. // We'll get back a string which we'll do a Tcl_SplitList on. // we'll write out the argc of that list to the stream. // for each element in the string, we'll stick "set " in front of it // and evaluate it. We'll then save the string "set <variable> <value>" // out to the stream. // we'll then send "info procs" to the interp. // We'll get back a string which we'll do a Tcl_SplitList on. // we'll write out the argc of that list to the stream. // for each element in the string, we'll call that element "procName" // for each procName, we'll then send "info args <procName>" and call the // result "procArgs", we'll then send "info body <procName>" and call the // the result "procBody", // we'll then sprintf into a string variable: // sprintf(procDef, "proc %s {%s} {%s}", procName, procArgs, procBody); // and save "procDef" out to the stream ret = Tcl_GlobalEval(interp, "info globals"); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <info globals> yielded <%s>)\n", interp->result); [self showError:interp->result]; varNameList = NXCopyStringBuffer(""); } else { varNameList = NXCopyStringBuffer(interp->result); } Tcl_ResetResult(interp); argc = 0; argv = NULL; Tcl_SplitList(interp, varNameList, &argc, &argv); Tcl_ResetResult(interp); cnt = 0; for (i = 0; i < argc; i++) { skip = NO; if (!strcmp(argv[i], "env")) { skip = YES; } if (!strcmp(argv[i], "PI")) { skip = YES; } if (!strcmp(argv[i], "ticksPerSecond")) { skip = YES; } if (!strcmp(argv[i], "errorInfo")) { skip = YES; } if (!strcmp(argv[i], "scene")) { skip = YES; } if (!skip) { varValue = Tcl_GetVar(interp, argv[i], TCL_GLOBAL_ONLY); if (varValue) { cnt++; } else { Tcl_DStringInit(&arrayElementCmd); Tcl_DStringAppend(&arrayElementCmd, "array names ", -1); aCmd = Tcl_DStringAppend(&arrayElementCmd, argv[i], -1); ret = Tcl_GlobalEval(interp, aCmd); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", aCmd, interp->result); [self showError:interp->result]; arrayElementList = NXCopyStringBuffer(""); } else { arrayElementList = NXCopyStringBuffer(interp->result); } Tcl_ResetResult(interp); argc2 = 0; argv2 = NULL; Tcl_SplitList(interp, arrayElementList, &argc2, &argv2); for (j = 0; j < argc2; j++) { cnt++; } Tcl_ResetResult(interp); if (argv2) { free(argv2); } free(arrayElementList); // it was malloc'ed either way } } } free(varNameList); varNameList = NULL; // we now check to see if these strings are going to put us over the limit // on our tmp string array. If they will, realloc. while ((tmpStringIndex + cnt) > tmpStringc) { tmpStringc *= 2; tmpStringv = (char **)NXZoneRealloc([self zone], tmpStringv, (sizeof(char *) * tmpStringc)); } NXPrintf(stream, "# Writing out all the current values of the global variables:\n"); for (i = 0; i < argc; i++) { skip = NO; if (!strcmp(argv[i], "env")) { skip = YES; } if (!strcmp(argv[i], "PI")) { skip = YES; } if (!strcmp(argv[i], "ticksPerSecond")) { skip = YES; } if (!strcmp(argv[i], "errorInfo")) { skip = YES; } if (!strcmp(argv[i], "scene")) { skip = YES; } if (!skip) { Tcl_DStringInit(&setCmd); Tcl_DStringAppend(&setCmd, "set ", -1); Tcl_DStringAppend(&setCmd, argv[i], -1); varValue = Tcl_GetVar(interp, argv[i], TCL_GLOBAL_ONLY); if (varValue) { Tcl_DStringAppend(&setCmd, " {", -1); Tcl_DStringAppend(&setCmd, varValue, -1); aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&setCmd, "}", -1)); NXPrintf(stream, "%s;\n", aCmd); Tcl_DStringFree(&setCmd); } else { // if we failed to get a value for the var, it must be an array. // need to find out how many elements there are and save out each one. // eval "array names <var>", do a split list on it, etc. Tcl_DStringInit(&arrayElementCmd); Tcl_DStringAppend(&arrayElementCmd, "array names ", -1); aCmd = Tcl_DStringAppend(&arrayElementCmd, argv[i], -1); ret = Tcl_GlobalEval(interp, aCmd); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", aCmd, interp->result); [self showError:interp->result]; arrayElementList = NXCopyStringBuffer(""); } else { arrayElementList = NXCopyStringBuffer(interp->result); } Tcl_ResetResult(interp); arrayName = NXCopyStringBuffer(Tcl_DStringAppend(&setCmd, "", 0)); argc2 = 0; argv2 = NULL; Tcl_SplitList(interp, arrayElementList, &argc2, &argv2); for (j = 0; j < argc2; j++) { // need to join the current Tcl_DStringInit(&setCmd); Tcl_DStringAppend(&setCmd, arrayName, -1); Tcl_DStringAppend(&setCmd, "(", -1); Tcl_DStringAppend(&setCmd, argv2[j], -1); aCmd = Tcl_DStringAppend(&setCmd, ")", -1); ret = Tcl_GlobalEval(interp, aCmd); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", aCmd, interp->result); [self showError:interp->result]; aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer("BAD VALUE"); } else { Tcl_DStringAppend(&setCmd, " {", -1); Tcl_DStringAppend(&setCmd, interp->result, -1); aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&setCmd, "}", -1)); } Tcl_ResetResult(interp); NXPrintf(stream, "%s;\n", aCmd); Tcl_DStringFree(&setCmd); } if (arrayName) { free(arrayName); } } } } if (argv) { free(argv); } NXPrintf(stream, "# end of globals\n"); NXPrintf(stream, "# Writing out all the current definitions of all the non built-in procs:\n"); ret = Tcl_GlobalEval(interp, "info procs"); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <info procs> yielded <%s>)\n", interp->result); [self showError:interp->result]; } procNameList = NXCopyStringBuffer(interp->result); Tcl_ResetResult(interp); Tcl_SplitList(interp, procNameList, &argc, &argv); // we now check to see if these strings are going to put us over the limit // on our tmp string array. If they will, realloc. while ((tmpStringIndex + cnt) > tmpStringc) { tmpStringc *= 2; tmpStringv = (char **)NXZoneRealloc([self zone], tmpStringv, (sizeof(char *) * tmpStringc)); } for (i = 0; i < argc; i++) { Tcl_DStringInit(&procCmd); Tcl_DStringAppend(&procCmd, "proc ", -1); Tcl_DStringAppend(&procCmd, argv[i], -1); Tcl_DStringAppend(&procCmd, " { ", -1); Tcl_DStringInit(&infoCmd); Tcl_DStringAppend(&infoCmd, "info args ", -1); aCmd = Tcl_DStringAppend(&infoCmd, argv[i], -1); ret = Tcl_GlobalEval(interp, aCmd); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", aCmd, interp->result); [self showError:interp->result]; Tcl_ResetResult(interp); aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&procCmd, "} {echo {this proc had bad args\\n} }", -1)); } else { Tcl_DStringAppend(&procCmd, interp->result, -1); Tcl_DStringAppend(&procCmd, " } { ", -1); Tcl_ResetResult(interp); Tcl_DStringFree(&infoCmd); Tcl_DStringInit(&infoCmd); Tcl_DStringAppend(&infoCmd, "info body ", -1); aCmd = Tcl_DStringAppend(&infoCmd, argv[i], -1); ret = Tcl_GlobalEval(interp, aCmd); if (ret != TCL_OK) { NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", aCmd, interp->result); [self showError:interp->result]; aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&procCmd, "echo {this proc had a bad body\\n} }", -1)); } else { Tcl_DStringAppend(&procCmd, interp->result, -1); Tcl_DStringFree(&infoCmd); aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&procCmd, " }", -1)); } Tcl_ResetResult(interp); } //NXLogError("writing out: <%s> at address %d\n", aCmd, aCmd); NXPrintf(stream, "%s;\n", aCmd); Tcl_DStringFree(&procCmd); } NXPrintf(stream, "# end of procs\n"); if (argv) { free(argv); } if (procNameList) { free(procNameList); } return self; } #define typeVector "@" #define typeValues &wwVarList - read:(NXTypedStream*)stream { int version; [super read:stream]; NX_DURING version = NXTypedStreamClassVersion(stream,"WWInterp"); if (version == 0) NXReadTypes(stream,"i",&version), version=1; if (version == 1) { NXReadTypes(stream, typeVector, typeValues); } NX_HANDLER NXLogError("in read: %s, exception [%d] raised.\n", [[self class] name], NXLocalHandler.code); return nil; NX_ENDHANDLER return self; } - write:(NXTypedStream*)stream { [super write:stream]; NXWriteTypes(stream, typeVector, typeValues); return self; } @end