/*----------------------------------------- linescape.cpp Written by Camille Utterback August 2002 camille@camilleutterback.com www.camilleutterback.com WHAT: This program moves and connects 3 dots. Each of the 3 dots animates around it's own rectangle. The 3 dots are connected in their current location by a translucent white triangle. My program keeps track of former dot locations, and draws blue triangles connecting the 3 dots in places they used to be. Like most (all?) things, the traces of where the dots have been fade over time. You can change the rectangles, and therefore the trajectories of the dots, and therefore the patterns created over time, by clicking anywhere on the screen. A random corner of one of the 3 rectangles will relocate to the spot you clicked. The dot controlled by that rectangle will move back onto it's trajectory around the new triangle (most of the time - sometimes it doesn't quite get back 'on track' but that was a mistake I liked so I left it.) To quit the program, hit ENTER on your keyboard. WHY: This was apparently a very simple assignment: 'move and connect 3 dots'. But all motion implies time. Time and motion can create complexity out of very simple things. This is especially the case when a simple shape (a triangle) repeated over and over again, following another simple shape (a rectangle) creates a complicated network of lines. Maybe it's simple math, but it fascinates me to watch curves emerge from the accumulation of straight lines. And I know it's all 0's and 1's but sometimes it's hard to believe the garbledeegook of code I write is a system of simple rules from which I can create endless variation and complexity. Computers are not necessary to experience the emergence of something complicated out of simple rules, they just make it faster. (After completing this program I realized it is was an inadvertent homage to string art from my childhood.) CODE NOTES: This code works on Windows98, Windows 2000, and Windows XP machines with graphics cards that support OpenGL. I've arranged the code so that my main functions are in one section titled 'My Functions' and the functions that allow this program to interact with the Windows operating system are in a section titled 'Windows Main and Windows Management Functions' I was a bit embarrassed and nervous to think of people looking at my code, so I've taken out a lot of my comments where I'm cursing, venting my frustration and thoughts and keeping track of what I've tried and not. In retrospect maybe this was a mistake, as this code no longer really looks like what a working file of mine looks like. What can I say? I'm one of those people that clean my bathroom if my friends are coming over. --------------------------------------------------*/ //--------------------------------------------------------------------------------------- //---------------------------------INCLUDES/DEFINES //--------------------------------------------------------------------------------------- #include <windows.h> #include <gl/glut.h> //uses glut, guarantees gl.h and glu.h are properly included #include <time.h> #define MAXTRAILPOINTS 500 //--------------------------------------------------------------------------------------- //---------------------------------FUNCTIONS //--------------------------------------------------------------------------------------- //---Windows Management Functions LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM); //---My General Setup and Cleanup Functions void MySetUp(HWND passedhwnd); bool CreateCurrentGLContext(HGLRC whichglrc,HDC whichdc,int width,int height); void SetupOpenGLParams(); void CleanUp(); //---My Drawing Functions and Functions they rely on void MyMainFunction(); void MoveConnectAndDrawStuff(); void IncrementPointToTarget(POINT *ppoint, int *ptarget, int targetdir, int speed, RECT rect); void IncrementTrailPtPtr(); void MyMouseDownFunction(int mouseX,int mouseY); //--------------------------------------------------------------------------------------- //---------------------------------VARIABLES //--------------------------------------------------------------------------------------- //-------------GENERAL int ScreenX, ScreenY; //screen dimensions clock_t LastClockTime; //-------------WINDOW/OBJECTS HWND hwnd; //main window HDC hdc; //screen device context HGLRC hglrc; //opengl rendering context, uses a dc to draw on //-------------DRAWING VARIABLES POINT dot[3]; //-- array of 3 points RECT tracerect[3]; //-- array of 3 rectangles //each dot animates around one of these 'tracerects' int targetcorner[3]; //-- array of 3 numbers //this stores the target corner //that each point is animating towards in its respective 'tracerect' //initially, 0 is the top left, 1 the top right, 2 the bottom right, 3 the bottom left. //but since clicking can reorient the rectangle I pick numbers for the corners //instead of referring to them as top right etc. int dotdirection[3]={ 1, //dot 0 1, //dot 1 -1 //dot 2 }; //--dot direction controls whether dots are moving from targe corner 0-1-2 etc, or 3-2-1 etc. int dotspeed[3]= { 20, //dot 0 3, //dot 1 15 //dot 2 }; //--dot speed controls how many pixels a dot moves each GLfloat TrailPtList[3][MAXTRAILPOINTS*3]; //3 lists - one for each dot //each list stores the location (x,y) and alpha value //of each dot over time, up to MAXTRAILPOINTS int TrailPtPtr=0; //pointer to next available space in TrailLists //the lists are filled consistently, so one pointer will do //I could have made all this a struct - oh well! //--------------------------------------------------------------------------------------- //---------------------------------MY FUNCTIONS //--------------------------------------------------------------------------------------- void MyMainFunction(){ if((clock()-LastClockTime)>(CLOCKS_PER_SEC/30)){ //test if enough time has gone by glClear(GL_COLOR_BUFFER_BIT); //clear screen buffer to bkgd color MoveConnectAndDrawStuff(); //this is the 'move and connect 3 dots' part glFlush(); //force drawing to finish SwapBuffers(hdc); //move drawing to screen LastClockTime=clock(); //store the current time } }//end MyMainFunction //----------------------------- void MyMouseDownFunction(int mouseX,int mouseY){ int whichrect; //--adjust the mousey for OpenGL coords mouseY=ScreenY-mouseY; //--reset a random corner of a random trace rect to the mouse position whichrect=rand()%3; //pick a random number 0,1,or 2 switch(rand()%4){ case 0: tracerect[whichrect].left=mouseX; tracerect[whichrect].top=mouseY; break; case 1: tracerect[whichrect].right=mouseX; tracerect[whichrect].top=mouseY; break; case 2: tracerect[whichrect].right=mouseX; tracerect[whichrect].bottom=mouseY; break; case 3: tracerect[whichrect].left=mouseX; tracerect[whichrect].bottom=mouseY; break; } //--reset the speed for this rect to a random number between 3 and 20 dotspeed[whichrect] = rand()%20+3; }//end MyMouseDownFunction //------------------------------ void MoveConnectAndDrawStuff(){ //This function does all kinds of stuff, //i.e. move, connect, and draw the dots //please see the numbered headings for details //this function calls: //IncrementPointToTarget //IncrementTrailPtPtr //1. MOVE DOT LOCATIONS AROUND 'TRACE RECTS' //--move all 3 dot locations around their respective 'trace rects' IncrementPointToTarget(&dot[0], &targetcorner[0], dotdirection[0], dotspeed[0], tracerect[0]); IncrementPointToTarget(&dot[1], &targetcorner[1], dotdirection[1], dotspeed[1], tracerect[1]); IncrementPointToTarget(&dot[2], &targetcorner[2], dotdirection[2], dotspeed[2], tracerect[2]); //2. FADE OLD DOT'S ALPHA VALUES AND ADD NEW DOTS TO TRAIL LISTS //a. --loop through all trail points and decrease alpha values for every point- ie fade them int i,j; GLfloat faderate= 0.005; for(i=0;i<3;i++){ for(j=0;j<MAXTRAILPOINTS;j++){ TrailPtList[i][j*3+2]=max(0.0,TrailPtList[i][j*3+2]-faderate ); TrailPtList[i][j*3+2]=max(0.0,TrailPtList[i][j*3+2]-faderate ); TrailPtList[i][j*3+2]=max(0.0,TrailPtList[i][j*3+2]-faderate ); }//end j loop }//end i loop //b. --add the new dots to their trailpoint list and set that alpha to 1.0 for(i=0;i<3;i++){ for(j=0;j<MAXTRAILPOINTS;j++){ TrailPtList[i][TrailPtPtr*3]=dot[i].x; //set dot x loc TrailPtList[i][TrailPtPtr*3+1]=dot[i].y; //set dot y loc TrailPtList[i][TrailPtPtr*3+2]=1.0; //set alpha value }//end j loop }//end i loop //c. --increment trail point list pointer to next available storage index in the arrays IncrementTrailPtPtr(); //This function increments TrailPtPtr and shifts active index points up the array if TrailPtPtr is at the end of the arrays //3. PRINT - DRAW LINES BETWEEN ALL THE DOTS (former dot locations) IN TRAIL LISTS glBegin(GL_LINE_STRIP); for(j=0;j<MAXTRAILPOINTS;j++){ if(TrailPtList[0][j*3+2]>0.0){ //if the point has not faded to 0.0 for(i=0;i<3;i++){ //connect one dot from each list to form a triangle glColor4f(0.5, 0.5, 1.0, TrailPtList[i][j*3+2]); //use stored alpha value glVertex2i(TrailPtList[i][j*3],TrailPtList[i][j*3+1]); } }//end if }//end maxtrailpoints loop glEnd(); //4. PRINT - DRAW THE 'TRACE RECTS' THAT DOTS ARE MOVING AROUND glColor4f(0.5, 0.5, 1.0, 1.0); //set color and alpha for(i=0;i<3;i++){ glBegin(GL_LINE_LOOP); glVertex2i(tracerect[i].left,tracerect[i].top); glVertex2i(tracerect[i].right,tracerect[i].top); glVertex2i(tracerect[i].right,tracerect[i].bottom); glVertex2i(tracerect[i].left,tracerect[i].bottom); glEnd(); }//end tracerect loop //5. PRINT --DRAW A TRANSPARENT WHITE TRIANGLE CONNECTING THE CURRENT 3 DOTS glColor4f(1.0, 1.0, 1.0, 0.75); glBegin(GL_POLYGON); glVertex2i(dot[0].x,dot[0].y); glVertex2i(dot[1].x,dot[1].y); glVertex2i(dot[2].x,dot[2].y); glEnd(); }//end MoveConnectAndDrawStuff //----------------------------- void IncrementPointToTarget(POINT *ppoint, int *ptarget, int targetdir, int speed, RECT rect){ //This function moves a point passed to it towards a target corner in the passed in rect structure //The amount of motion is determined by the passed speed var //If the new point location overshoots the target corner loc, then the point is reset to the corner loc //and a new target corner is set based on the targetdir (some points move from corner 0 to 1 to 2, other from 2 to 1 to 0 etc.) POINT corner[4]; corner[0].x = rect.left; corner[0].y = rect.top; corner[1].x = rect.right; corner[1].y = rect.top; corner[2].x = rect.right; corner[2].y = rect.bottom; corner[3].x = rect.left; corner[3].y = rect.bottom; //these coords may no longer BE at the top, bottom etc if rect has been moved //ptarget is the _number_ of the corner the point is aiming for though. int xdir, ydir; //--figure out the x dir point needs to travel based on point and targetcorner if(corner[*ptarget].x - ppoint->x < 0) xdir=-1; if(corner[*ptarget].x - ppoint->x > 0) xdir=1; if(corner[*ptarget].x - ppoint->x == 0) xdir=0; //--figure out y dir point needs to travel based on point and targetcorner if(corner[*ptarget].y - ppoint->y < 0) ydir=-1; if(corner[*ptarget].y - ppoint->y > 0) ydir=1; if(corner[*ptarget].y - ppoint->y == 0) ydir=0; //--set new point location based on dot's speed and the direction calculated above ppoint->x=ppoint->x+xdir*speed; ppoint->y=ppoint->y+ydir*speed; if( (ydir==1 && ppoint->y >= corner[*ptarget].y) || (ydir<0 && ppoint->y <= corner[*ptarget].y)){ //dot is equal to or beyond it's target vertically (relative to the direction dot was moving) ppoint->y=corner[*ptarget].y; //set y to the target corner y (in case we overshot) if(targetdir==1){ *ptarget=(*ptarget+1<=3)?*ptarget+1:*ptarget=0; //increment target corner }else{ *ptarget=(*ptarget-1>=0)?*ptarget-1:*ptarget=3; //decrement target corner } }else{ if((xdir==1 && ppoint->x>=corner[*ptarget].x) || (xdir<0 && ppoint->x<=corner[*ptarget].x)){ //dot is equal to or beyond it's target horizontally (relative to the direction dot was moving) ppoint->x=corner[*ptarget].x; //set x to the target corner x (in case we overshot) if(targetdir==1){ *ptarget=(*ptarget+1<=3)?*ptarget+1:*ptarget=0; //increment target corner }else{ *ptarget=(*ptarget-1>=0)?*ptarget-1:*ptarget=3; //decrement target corner } } } }//end IncrementPointToTarget //--------------- void IncrementTrailPtPtr(){ //--This function tries to increment TrailPtPtr, //--(which keeps track of where to add new dot locations into the TrailPointLists) //--but first checks if TrailPtPtr is at the end of the array, //--If it is, the function shifts all the active (not completely faded) points //--back to the start of the array to make space at the end and sets TrailPtPtr accordingly //--(Shifting all the points seems dorky, but if I pass a 'wraped around' array to an openGL //--draw function I got a 'seam' between the begining and end of the active points that didn't look good. //--this was the simplest way I thought of to solve it.) int shiftindex; int i,j,k; if(TrailPtPtr+1<MAXTRAILPOINTS){ //--not at end of array, increment normally TrailPtPtr++; }else{ //--at the end of our array - shift active array elements back towards 0 index //find first active index pt. (point who's alpha is > 0.0) for(j=0;j<MAXTRAILPOINTS;j++){ if(TrailPtList[0][j*3+2]>0.0) break; } //j should now be the first index who's value is over 0.0 shiftindex=j; //move all index points up shiftindex places towards index 0 for(k=0;k<MAXTRAILPOINTS;k++){ //loop through all the array points if(j<MAXTRAILPOINTS){ // if j (which started at shiftindex) is still less than our original array for(i=0;i<3;i++){ //for each of our 3 arrays //shift the point at this index as far towards the begining of the array as possible TrailPtList[i][k*3]=TrailPtList[i][j*3]; TrailPtList[i][k*3+1]=TrailPtList[i][j*3+1]; TrailPtList[i][k*3+2]=TrailPtList[i][j*3+2]; }//end i loop - once for each list j++; }else{ //all the old active values have been shifted up for(i=0;i<3;i++){ TrailPtList[i][k*3+2]=0.0; //reset the rest of each array to 0.0 } }//end if j < MAXTRAILPOINTS }//end shift loop - looping through individual lists TrailPtPtr=TrailPtPtr-shiftindex+1; //move pointer 1 past end of newly shifted list } }//end IncrementTrailPtPtr //--------------- void MySetUp(HWND passedhwnd){ //this function calls //CreateCurrentGLContext //SetupOpenGLParams int i,j; //-------------GET A HANDLE TO A DEVICE CONTEXT hdc=GetDC(passedhwnd); //get a handle to the screen //-------------CREATE OPENGL RENDERING CONTEXT //this should be an IF, if it doesn't return a 1, program should quit //oh well. CreateCurrentGLContext(hglrc,hdc,ScreenX,ScreenY); //creates an openGLContext and sets it's viewport to the screen size //also sets a 2D orthographic projection mode to screen size //-------------SET INITIAL VALUES FOR OPENGL PARAMS SetupOpenGLParams(); //------------INITIALIZE TRACE RECTS int ctrX, ctrY; ctrX=ScreenX/2; ctrY=ScreenY/2; //--rect 0 tracerect[0].left= ctrX-100; tracerect[0].top= ctrY+20; tracerect[0].right= tracerect[0].left+ 250; //width tracerect[0].bottom=tracerect[0].top - 100; //height //--rect 1 - upper right tracerect[1].left= ctrX-200; tracerect[1].top= ctrY+200; tracerect[1].right= tracerect[1].left+ 100; //width tracerect[1].bottom=tracerect[1].top - 100; //height //--rect 2 - long thin tracerect[2].left= ctrX-20; tracerect[2].top= ctrY+250; tracerect[2].right= tracerect[2].left+ 50; //width tracerect[2].bottom=tracerect[2].top - 500; //height //-------------INITIALIZE DOTS for(i=0;i<3;i++){ //--start each dot in the ctr. of it's tracerect dot[i].x = tracerect[i].left+(tracerect[i].right-tracerect[i].left)/2; dot[i].y = tracerect[i].bottom+(tracerect[i].top-tracerect[i].bottom)/2; //--assign it a random target corner targetcorner[i]=rand()%3; } //-------------INITIALIZE TRAILPOINTLISTS //--set all the locations and alpha values of the 3 trailpoint lists to 0.0 for(i=0;i<3;i++){ for(j=0;j<3*MAXTRAILPOINTS;j+=3){ TrailPtList[i][j]=0.0; TrailPtList[i][j+1]=0.0; TrailPtList[i][j+2]=0.0; } } //-------------STORE THE CURRENT TIME LastClockTime=clock(); }//end MySetUp //------------------ void CleanUp(){ //--------cleanup openGL rendering context if(hglrc = wglGetCurrentContext()) { //if the thread has a rendering context: // make the rendering context not current wglMakeCurrent(NULL, NULL); // delete the OpenGL rendering context wglDeleteContext(hglrc); } //-------clean up GDI stuff DeleteDC(hdc); //release the screen device context }//--end CleanUp function //------------------------------ bool CreateCurrentGLContext(HGLRC whichglrc,HDC whichdc,int width,int height){ //This function creates an OpenGL rendering context using the buffers passed to it //and sets the rendering context's viewport and an Ortho2Dprojection mode //based on the height and width passed in bool breturnflag=0; int iPixelFormat; //--setup the desired pixelformat structure PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd 1, // version number //--dwFlags PFD_DRAW_TO_WINDOW | //The buffer can draw to a window or device surface PFD_SUPPORT_OPENGL| // The buffer supports OpenGL drawing. PFD_DOUBLEBUFFER| //The buffer is double-buffered. //--ipixeltype PFD_TYPE_RGBA, // iPixelType RGBA or index 24, // cColorBits - //Specifies the number of color bitplanes in each color buffer. //For RGBA pixel types, it is the size of the color buffer, excluding the alpha bitplanes. //24-bit color depth //--color bits - ignored 8, //cRedBits -the number of red bitplanes in each RGBA color buffer. 0, //cRedShift - the shift count for red bitplanes in each RGBA color buffer. 8, 0, //cBlueBits, cBlueShift 8, 0, //cGreenBits, cGreenShift 0, 0, //AlphaBits, AlphaShift //Specifies the number of alpha bitplanes in each RGBA color buffer. //in generic gdi imp. Alpha bitplanes are not supported. 0, //cAccumBits - the total number of bitplanes in the accumulation buffer. 0, 0, 0, 0, //cAccumRedBits, Green, Blue, Alpha 0, //cDepthBits - the depth of the depth (z-axis) buffer. //was 32 0, //cStencilBits - the depth of the stencil buffer. //8 is the max it will give me so far 0, //cAuxBuffers - the number of auxiliary buffers. //in generic gdi imp. Auxiliary buffers are not supported. PFD_MAIN_PLANE, //iLayerType - Ignored. older implementations used this 0, //bReserved - the number of overlay and underlay planes. //Bits 0 through 3 specify up to 15 overlay planes //and bits 4 through 7 specify up to 15 underlay planes. 0, //dwLayerMask -Ignored. older implementations used this 0, //dwVisibleMask - the transparent color or index of an underlay plane. //When the pixel type is RGBA, dwVisibleMask is a transparent RGB color value. //When the pixel type is color index, it is a transparent index value. 0 //dwDamageMask -Ignored. older implementations used this }; //--- set the pixel format based on the pixelformat we setup above //--try to find a PIXELFORMATDESCRIPTOR with the specified attribs. //ChoosePixelFormat gets the best available match of pixel format for the device context iPixelFormat = ChoosePixelFormat(whichdc, &pfd); //--make that the pixel format of the device context SetPixelFormat(whichdc, iPixelFormat, &pfd); //--- Create an OpenGL rendering context and set it's viewport and projection mode based on params passed to this function if (whichglrc = wglCreateContext(whichdc) ) { //--create a rendering context using the device context passed to this function if(wglMakeCurrent(whichdc, whichglrc)){ //--make this rendering context the thread's current rendering context breturnflag=1; //rendering context was created and selected OK //--Set the OpenGL viewport to match the screen size glViewport( 0, //The lower-left corner of the viewport rectangle, in pixels. The default is (0,0). 0, (GLsizei) width, //The width and height, respectively, of the viewport. (GLsizei) height ); //--set and load the matrix mode glMatrixMode(GL_PROJECTION); //Applies subsequent matrix operations to the specified matrix stack. glLoadIdentity(); //replaces current matrix with the base matrix //--set the projection mode gluOrtho2D( 0.0, //The coordinates for the left and right vertical clipping planes. (GLdouble) width, 0.0, //The coordinates for the bottom and top horizontal clipping planes. (GLdouble) height ); }//end if context made current }//end if context created OK return breturnflag; } void SetupOpenGLParams(){ //--------SET clearing/bkgd color //glClearColor(0.0, 0.0, 0.0, 0.0);//black glClearColor(1.0, 1.0, 1.0, 1.0); //white //--------SET INITIAL LINE SIZES glLineWidth(1.0); //--------SET SHADING OPTIONS glShadeModel(GL_FLAT); //-------DEPTH glDisable(GL_DEPTH_TEST); //-------LIGHTING glDisable(GL_LIGHTING); //-------BLENDING OPTIONS glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); }// end SetupOpenGLParams //--------------------------------------------------------------------------------------- //------------------------WINDOWS MAIN //------------------------AND //------------------------WINDOWS MANAGEMENT FUNCTIONS //--------------------------------------------------------------------------------------- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow){ static char szAppName[] = "dotodot"; //my application name MSG msg; //var to hold Windows messages WNDCLASSEX wndclass; //var to hold my application window parameters //------DEFINE AND REGISTER A WINDOW CLASS wndclass.cbSize = sizeof(wndclass); wndclass.style = 0; //default style wndclass.hInstance = hInstance; //handle to this instance wndclass.lpszClassName = szAppName; //window class name wndclass.lpfnWndProc = WindowProc; //window function wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //icon style wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //cursor style wndclass.lpszMenuName = NULL; //no menu wndclass.cbClsExtra = 0; //no extra wndclass.cbWndExtra = 0; //info needed wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); RegisterClassEx (&wndclass); //------GET SCREEN DIMENSIONS ScreenX=GetSystemMetrics(SM_CXSCREEN); //get the x dimension of the screen ScreenY=GetSystemMetrics(SM_CYSCREEN); //get the y dimension of the screen //------CREATE THE WINDOW hwnd = CreateWindow( szAppName, //name of window class NULL, //title WS_POPUP, //window style 0, //x coordinate 0, //y coordinate ScreenX, //width ScreenY, //height NULL, //no parent window NULL, //menu handle (if parent) - child id if window is a child hInstance, //handle of this instance of the program NULL //no additional arguments ); //note: windows sends a WM_CREATE msg to WindowProc while processing createwindow //------DISPLAY WINDOW ShowWindow(hwnd, iCmdShow); //windows sends WM_SIZE, WM_SHOWWINDOW msg to WindowProc //------MAIN LOOP while (TRUE) { MyMainFunction(); //--deal with messages windows is passing to my program if (PeekMessage (&msg, NULL, 0,0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } }//end while true return msg.wParam; } //end Windows Main LRESULT CALLBACK WindowProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { //This function handles messages from the Operating System Message Queue switch(iMsg) { case WM_CREATE: //--The Application Window was just created - Call My SetUp Function MySetUp(hwnd); return 0; case WM_LBUTTONDOWN: //--User just clicked the left mouse button - Call My MouseDown Function MyMouseDownFunction(LOWORD(lParam),HIWORD(lParam)); //pass it the x and y loc of the mouse return 0; case WM_KEYDOWN: switch(wParam) { case VK_RETURN: //--user clicked the return key, quit program: CleanUp(); //call my clean up function PostQuitMessage(0); //put WM_QUIT message in the message queue break; } //end switch return 0; } // end msg switch return DefWindowProc(hwnd, iMsg, wParam, lParam); //let windows handle any messages if I haven't }//end WindProc callback function
Note: The project is an executable (PC only)
that you can download or run from its current location.