Bezier Curve Demo version 1.0.1
Author: LionelForager
This set of scripts will draw a bezier curve segment controlled by 3 control points.
It tries to be the foundation of a more general graph server in SL.
The idea and first version of the bezier curve demo script, was given to me by CatherineOmega. She had a working script that rezzed small spheres in each point position.
Then i added some modifications and the possibility of drawing segments made up of cylinders to rezz a line instead of just points.
The complete bezier server consists of several parts:
- Server:An object that controls and creates the graph. We call this
- Control Points p1,p2,p3: control points that controls the begining, direction and end of the bezier segments.
- Point Mark: the object (may be a sphere or other) contained in the server that is rezzed in each created point mark position.
- Segment: the object (a cylinder) contained in the server that is rezzed when a line segments are required instead of point marks.
A description of each part will be given.
Server object
Description
The server object is the one which calculates the point coordinates interpolated using a bezier curve.
It rezzes the segments or point marks.
It shows the positions of the three control points in its display text.
Commands to it are given in the channel 1.
It receives the positions in that channel from the control points, and when it has all three, draws the curve.
When a new position is received from a control point, it clears the previously rezzed objects and draws new ones.
The server knows which control point has moved by the name of the object that has said the command it receives.
Commands - How to use
Once you have rezzed the bezier server, you can say commands to it through channel #1 to control its behaviour.
Commands are:
- "/1 markers on": activate the drawing of markers. This is the default option.
- "/1 markers off": deactivate them.
- "/1 lines on": activate the drawing of a line made of segments.
- "/1 lines off": deactivate the drawing of segments. This is the default option.
- "/1 segments n": set the number of segments that should be drawn to n.
- "/1 start": this command forces the drawing of the curve.
- "/1 mu float": draws a point corresponding to the given value of mu parameter.
How to create de server
To create the bezier demo server object, create a prim that you like and put the following script in its inventory.
Then you should create the segment and point mark objects (see descriptions below) and put them in the inventory of the server object too (see bellow).
// Bezier Curve Demo 1.0.1 // Catherine Omega Heavy Industries // Modified by Lionel Forager 29-12-2006: Added segment drawing between points. Corrected minor bugs. key gOwner; // object owner float diam=0.05; //diameter of line segments. list gPoints; // list of vectors to feed to bezier3 string gObject="Point Mark"; // name of object to rez as the mark in each point string gSegment="Segment"; // name of object to rez as the mark in each point integer gNumSegments = 10; // number of segments to rez integer gMarks= TRUE; //Draw marks in the point positions. integer gLines= FALSE; //Don't draw segments between points. float gMu; vector gPos1; vector gPos2; vector gPos3; //codes a line number and a segment in a unique ID integer integer codeID(integer line, integer segment) { //Code de id: line# in 16 most significant bits, segment# in 16 less significant bits return ( (line << 16) + segment); } //Rezz a segment between 2 given points and assigning it to a line # and a segment # drawLineSegment(vector p1,vector p2,integer line,integer seg) { //Calc the position of the center of the segment vector center= (p1+p2)/2.; vector localZ= p2 - p1; //Get distance between points float distance= llVecMag(localZ); //Normalize the vector localZ= localZ / distance; vector xAxis; //Let's choose as x local axis the direction of a vector normal to localZ and //contained in the plain given by localZ and global X or global Y, the one that is less parallel to localZ if ( localZ.x < localZ.y ) xAxis= <1.,0.,0.>; else xAxis= <0.,1.,0.>; //localX is the one contain in the plane given by localZ and xAxis that is normal to localZ. //that's it localX= xAxis - localZ*xaxis*localZ. We should normalize the vector to get a unitary one. vector localX= xAxis - (localZ * xAxis) * localZ; localX= llVecNorm(localX); //Now get the rotation to put axis Z oriented pointing to the direction between the two given points. rotation rot= llAxes2Rot(localX,localZ % localX, localZ); //Crear el objeto en la posici?n del server. llRezObject(gSegment,center,ZERO_VECTOR,rot,codeID(line,seg)); llSay(line,"segInit "+(string)seg+","+(string)diam+"," + (string)distance); } //Three control point Bezier interpolation //mu ranges from 0 to 1, start to end of the curve vector bezier3(vector p1, vector p2, vector p3, float mu) { float mum1; float mum12; float mu2; vector p; mu2 = mu * mu; mum1 = 1 - mu; mum12 = mum1 * mum1; // p= (1-mu)^2 p1 + 2*mu*(1-mu)*p2 + mu^2 * p3 p.x = p1.x * mum12 + 2. * p2.x * mum1 * mu + p3.x * mu2; p.y = p1.y * mum12 + 2. * p2.y * mum1 * mu + p3.y * mu2; p.z = p1.z * mum12 + 2. * p2.z * mum1 * mu + p3.z * mu2; return(p); } start(integer segments) { if (gPos1 != ZERO_VECTOR && gPos2 != ZERO_VECTOR && gPos3 != ZERO_VECTOR) { gNumSegments = segments; float increment = 1.0 / gNumSegments; float current_mu; integer i; vector oldPos= gPos1; vector pos; //Render the mark at the position of the first point if( gMarks ) llRezObject(gObject,gPos1,ZERO_VECTOR,ZERO_ROTATION,0); for (i = 1; i <= gNumSegments; i++) { current_mu= i * increment; pos = bezier3(gPos1,gPos2,gPos3,current_mu); if(gMarks) llRezObject(gObject,pos,ZERO_VECTOR,ZERO_ROTATION,0); if( gLines ) drawLineSegment(oldPos,pos,1,i); oldPos= pos; } } } updateText() { vector color = <1,0,0>; if (gPos1 != ZERO_VECTOR && gPos2 != ZERO_VECTOR && gPos3 != ZERO_VECTOR) { color = <0,1,0>; } string output = (string)gPos1 + "\n" + (string)gPos2 + "\n" + (string)gPos3; llSetText(output, color, 1.0); } // get gCommand and gSubCommand (now improved to handle null gSubCommands.) string gCommand; string gSubCommand; string gCommandLower; string gSubCommandLower; parseCommands(string input, string divider) { integer command_divide = llSubStringIndex(input,divider); if (command_divide == -1) { gCommand = llGetSubString(input, 0, command_divide); gSubCommand = ""; } else { gCommand = llGetSubString(input, 0, command_divide - 1); gSubCommand = llGetSubString(input, command_divide + 1, llStringLength(input)); } gCommandLower = llToLower(gCommand); gSubCommandLower = llToLower(gSubCommand); } default { state_entry() { gOwner = llGetOwner(); //gObject = llGetInventoryName(INVENTORY_OBJECT,0); updateText(); llListen(1,"","",""); llSay(0,"Online."); } listen(integer channel, string name, key id, string m) { if (llGetOwnerKey(id) == gOwner) { if (id == gOwner) { parseCommands(m," "); if (gCommandLower == "mu") { vector pos = bezier3(gPos1,gPos2,gPos3,(float)gSubCommand); llRezObject(gObject,pos,ZERO_VECTOR,ZERO_ROTATION,0); } else if (gCommandLower == "start") { start((integer)gSubCommand); llSay(0,"Done."); } else if (gCommandLower == "segments") { gNumSegments = (integer)gSubCommand; } else if (gCommandLower == "markers") { if( gSubCommand != "off" ) { gMarks= TRUE; llSay(0,"Markers on"); } else { gMarks=FALSE; llSay(0,"Markers off"); } } else if (gCommandLower == "lines") { if( gSubCommand != "off" ) { gLines= TRUE; llSay(0,"Lines on"); } else { gLines=FALSE; llSay(0,"Lines on"); } } } else { // make sure it's actually a vector if ((vector)m != ZERO_VECTOR) { if (llToLower(name) == "p1") { gPos1 = (vector)m; llSay(1,"clear"); updateText(); start(gNumSegments); } else if (llToLower(name) == "p2") { gPos2 = (vector)m; llSay(1,"clear"); updateText(); start(gNumSegments); } else if (llToLower(name) == "p3") { gPos3 = (vector)m; llSay(1,"clear"); updateText(); start(gNumSegments); } } } } } on_rez(integer start_param) { llResetScript(); } }
--------
Control Points
Description
The Control Point objects are the prims that you can position in place to control the shape of the bezier curve.
A Bezier curve has three control points (p1,p2, and p3).
The curve passes through p1 and p3 and p2 controls the curvature and tangent direction of the curve in p1 and p3.
When a control point is moved or you touch it, it says its position through channel #1 to the bezier server.
To use them, just rezz one object of each type (p1,p2 and p3) and move it to the position you want. The curve should be drawn if you have rezzed the bezier server first.
Commands - How to use
The control points don't obey any command. They just have a simple script to say their position to the bezier server trough channel #1
How to Create the Control Points
Create three prims (may be a simple sphere prim) with different colors and name them p1, p2 and p3 (be sure to name it that way: the server uses the name to differentiate which point has been moved or touched).
Then put the following script in each control point.
--------
reportPos() { vector pos = llGetPos(); llSay(1, (string)pos); } default { touch_start(integer total_number) { if (llDetectedKey(0) == llGetOwner()) { reportPos(); } } moving_end() { reportPos(); } }
------------
Point Mark
Description
The Point Marks are the prims drawn in each interpolated point of the bezier curve when markers mode is active.
You can use any prim, as a small sphere for example.
The Point Mark object should be in the inventory of the bezier server object, as it is rezzed from the script of that object.
The Point Marks are very simple, as they don't need to communicate with other scripts.
For convenience, they just listen in channel # 1 for clear commands, to delete themselves.
Commands - How to use
The point marks listen in channel #1 for clear commands to delete themselves.
How to Create the Point Mark object
Create the prim or object you want to be rezzed in each point position. Name it Point Mark. Then put the following script in it.
Take the object to your inventory and copy it from there to the inventory of the bezier server object.
default { state_entry() { llListen(1,"","",""); } listen(integer channel, string name, key id, string m) { if (llGetOwnerKey(id) == llGetOwner()) { if (m == "clear") { llDie(); } } } }
-------------
Segment
Description
The Segments are the prims drawn to make the lines of the bezier curve when you set lines mode on.
The prim should be a cylindre.
The Segment object should be in the inventory of the bezier server object, as it is rezzed from the script of that object.
The bezier server object rezzed the cylindre, rotates it to orient it properly in the direction of the line and then gives it a command to initialize its size correctly (as the size of an object cannot be stablished when rezzed and the parameters of a prim can only be altered by itshelf).
Commands - How to use
Each segment object listens in the channel corresponding to its line number for clear commands to delete themselves.
The segment object listen for seginit commands from the bezier server object in its line# channel addressed to its segment number.
The channel number and segment number are coded in an integer that is passed to the segment when rezzed.
In this example the line # is always 1.
The command is:
How to Create the Segment Object
Create a cylindre of any size, set a blank texture and choose its color. Save it with name Segment.
Put the following script in it.
Then, take the object to your inventory and copy it from there to the inventory of the bezier server object.
----------
//Author: Lionel Forager (c) 2007 //segmentServer 1.0.1 //This script is designed to work in conjunction with graphics sever 1.0 //It renders the segments in a line, resizing them, changing colors, etc. //#### internal variables ### //Line number that the segment belongs to. //It is used as a line # id and it is the channel # to comunicate with the graphics server integer lineNum; integer segmentNum; list parseCommands(string msg) { msg= llToLower(msg); return llParseString2List(msg,[" ",","],[]); } default { on_rez(integer param) { //get the line# and sement# coded in the param //line# is in 16 most significant bits lineNum= param>>16; //segment# is in 16 less significant bits. Clear 16 most significant bits. segmentNum= param^(lineNum<<16); if( (lineNum == 0)||(segmentNum==0) ) llWhisper(0,"This object is not designed to be created directly, it works in collaboration with graphics server."); else llListen(lineNum,"",NULL_KEY,""); } listen(integer channel, string name, key id, string m) { //ignore commands not given by the owner. if (llGetOwnerKey(id) != llGetOwner()) return; list cmds= parseCommands(m); m= llList2String(cmds,0); if( m == "clear") { llDie(); } else if (m=="seginit" ) { integer seg= llList2Integer(cmds,1); //if command is not for current segment, just ignore it. if( seg != segmentNum) return; float diam= llList2Float(cmds,2); float size= llList2Float(cmds,3); llSetScale(); } } }