// ControlledVisualise5D.cxx // Derived from Visualise5D.cxx by Peter Boyle // // A minimal-protocol rendering engine for 5D DWF eigenvector-density data. // Intended to be driven by an external intelligent controller (e.g. Claude) // that handles all natural-language interpretation and state tracking. // // Commands are sent one per line to the named pipe /tmp/visualise_cmd. // State is reported to stdout after every command. // // Wire protocol (all fields whitespace-separated): // // slice set Slice[dim] = N (0-based, wraps to lattice size) // slice + increment Slice[dim] by N // slice - decrement Slice[dim] by N // zoom camera Dolly by factor (>1 = in, <1 = out) // iso set isosurface threshold to x RMS // file jump to file by absolute index // file + advance N files // file - go back N files // render force a render with current state // status print current state to stdout // quit exit cleanly // // Dimension indices for 5D DWF grid (e.g. --grid 48.32.32.32.32): // s=0 (Ls) x=1 y=2 z=3 t=4 // For a 4D grid (--grid 32.32.32.32): // x=0 y=1 z=2 t=3 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MPEG #ifdef MPEG #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USE_FLYING_EDGES #ifdef USE_FLYING_EDGES #include typedef vtkFlyingEdges3D isosurface; #else #include typedef vtkMarchingCubes isosurface; #endif #define CMD_PIPE "/tmp/visualise_cmd" static int g_mpeg = 0; static int g_framerate = 10; // ─── Thread-safe command queue ──────────────────────────────────────────────── static std::queue g_cmdQueue; static std::mutex g_cmdMutex; static std::atomic g_running{true}; static double g_spinDeg = 0.0; // degrees per poll tick; 0 = stopped // ─── Grid I/O ───────────────────────────────────────────────────────────────── template void readFile(T& out, const std::string& fname) { Grid::emptyUserRecord record; Grid::ScidacReader RD; RD.open(fname); RD.readScidacFieldRecord(out, record); RD.close(); } using namespace Grid; // ─── Command reader thread ──────────────────────────────────────────────────── void CommandReaderThread() { mkfifo(CMD_PIPE, 0666); std::cout << "[cmd] Listening on " << CMD_PIPE << std::endl; while (g_running) { int fd = open(CMD_PIPE, O_RDONLY | O_NONBLOCK); if (fd < 0) { usleep(200000); continue; } int flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); char buf[4096]; std::string partial; ssize_t n; while (g_running && (n = read(fd, buf, sizeof(buf) - 1)) > 0) { buf[n] = '\0'; partial += buf; size_t pos; while ((pos = partial.find('\n')) != std::string::npos) { std::string line = partial.substr(0, pos); if (!line.empty() && line.back() == '\r') line.pop_back(); if (!line.empty()) { std::lock_guard lk(g_cmdMutex); g_cmdQueue.push(line); } partial = partial.substr(pos + 1); } } close(fd); } } // ─── FrameUpdater ───────────────────────────────────────────────────────────── class FrameUpdater : public vtkCallbackCommand { public: FrameUpdater() : ffile(0), TimerCount(0), old_file(-1), timerId(-2), maxCount(-1) {} static FrameUpdater* New() { return new FrameUpdater; } int ffile; int old_file; int timerId; int maxCount; Coordinate latt; Coordinate xyz_dims, xyz_ranges, g_xyz_ranges; uint64_t xyz_vol; Coordinate loop_dims, loop_ranges; uint64_t loop_vol; Coordinate sum_dims, sum_ranges; uint64_t sum_vol; Coordinate slice_dims; Coordinate Slice; std::vector files; int Nd; GridBase* grid; Grid::LatticeComplexD* grid_data; double rms; vtkImageData* imageData = nullptr; vtkTextActor* text = nullptr; isosurface* posExtractor = nullptr; isosurface* negExtractor = nullptr; void SetGrid(GridBase* _grid) { grid = _grid; Nd = grid->Nd(); latt = grid->GlobalDimensions(); grid_data = new Grid::LatticeComplexD(grid); } void SetFiles(std::vector list) { files = list; old_file = -1; } void SetSlice(Coordinate _Slice) { Slice = _Slice; } void SetSumDimensions(Coordinate _SumDims) { sum_dims = _SumDims; sum_ranges = Coordinate(Nd); sum_vol = 1; for (int d = 0; d < Nd; d++) { sum_ranges[d] = sum_dims[d] ? latt[d] : 1; sum_vol *= sum_ranges[d]; } } void SetLoopDimensions(Coordinate _LoopDims) { loop_dims = _LoopDims; loop_ranges = Coordinate(Nd); loop_vol = 1; for (int d = 0; d < Nd; d++) { loop_ranges[d] = loop_dims[d] ? latt[d] : 1; loop_vol *= loop_ranges[d]; } } void SetDisplayDimensions(Coordinate _xyz_dims) { xyz_dims = _xyz_dims; g_xyz_ranges = Coordinate(Nd); xyz_ranges = Coordinate(3); xyz_vol = 1; for (int d = 0; d < 3; d++) { xyz_ranges[d] = latt[xyz_dims[d]]; xyz_vol *= xyz_ranges[d]; } for (int d = 0; d < Nd; d++) { g_xyz_ranges[d] = 1; for (int dd = 0; dd < 3; dd++) if (xyz_dims[dd] == d) g_xyz_ranges[d] = latt[d]; } } void SetSliceDimensions() { Coordinate sd; for (int d = 0; d < Nd; d++) { if (g_xyz_ranges[d] > 1 || loop_dims[d] || sum_dims[d]) continue; sd.push_back(d); } slice_dims = sd; std::cout << " Slice dimensions: " << slice_dims << std::endl; } void FillImageData(int loop_idx) { Coordinate loop_coor; Lexicographic::CoorFromIndex(loop_coor, loop_idx, loop_ranges); Coordinate xyz_coor(3), g_xyz_coor(Nd), sum_coor(Nd); for (uint64_t xyz = 0; xyz < xyz_vol; xyz++) { Lexicographic::CoorFromIndex(xyz_coor, xyz, xyz_ranges); Lexicographic::CoorFromIndex(g_xyz_coor, xyz, g_xyz_ranges); RealD val = 0.0; for (uint64_t si = 0; si < sum_vol; si++) { Lexicographic::CoorFromIndex(sum_coor, si, sum_ranges); Coordinate site(Nd); for (int d = 0; d < Nd; d++) site[d] = (sum_coor[d] + loop_coor[d] + g_xyz_coor[d] + Slice[d]) % latt[d]; val += real(peekSite(*grid_data, site)); } imageData->SetScalarComponentFromDouble(xyz_coor[0], xyz_coor[1], xyz_coor[2], 0, val); } imageData->Modified(); } // Reload if needed, fill image, update label, render — no timer advance. void ForceRender(vtkRenderWindowInteractor* iren) { int file = ((TimerCount / (int)loop_vol) + ffile) % (int)files.size(); if (file != old_file) { std::cout << "[render] Loading " << files[file] << std::endl; readFile(*grid_data, files[file]); old_file = file; } FillImageData(TimerCount % (int)loop_vol); UpdateLabel(file, TimerCount % (int)loop_vol); iren->GetRenderWindow()->Render(); } virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData) { if (vtkCommand::KeyPressEvent == eventId) { vtkRenderWindowInteractor* iren = static_cast(caller); std::string key = iren->GetKeySym(); if (slice_dims.size() > 0) { int vert = slice_dims[slice_dims.size() - 1]; int horz = slice_dims[0]; if (key == "Up") Slice[vert] = (Slice[vert] + 1) % latt[vert]; if (key == "Down") Slice[vert] = (Slice[vert] + latt[vert] - 1) % latt[vert]; if (key == "Right") Slice[horz] = (Slice[horz] + 1) % latt[horz]; if (key == "Left") Slice[horz] = (Slice[horz] + latt[horz] - 1) % latt[horz]; } if (key == "greater") ffile = (ffile + 1) % (int)files.size(); if (key == "less") ffile = (ffile - 1 + (int)files.size()) % (int)files.size(); ForceRender(iren); return; } if (vtkCommand::TimerEvent == eventId) { // timerId == -2: no animation timer (--notime), ignore all timer events if (timerId < 0) return; int tid = *(reinterpret_cast(callData)); if (tid != timerId) return; int file = ((TimerCount / (int)loop_vol) + ffile) % (int)files.size(); if (file != old_file) { readFile(*grid_data, files[file]); old_file = file; } FillImageData(TimerCount % (int)loop_vol); UpdateLabel(file, TimerCount % (int)loop_vol); dynamic_cast(caller)->GetRenderWindow()->Render(); ++TimerCount; if (TimerCount >= maxCount && timerId > -1) dynamic_cast(caller)->DestroyTimer(timerId); } } private: int TimerCount; void UpdateLabel(int file, int loop_idx) { Coordinate loop_coor; Lexicographic::CoorFromIndex(loop_coor, loop_idx, loop_ranges); // Extract tau value from filename (last '_'-delimited field) const std::string& path = files[file]; std::string tau = path.substr(path.rfind('_') + 1); std::stringstream ss; ss << "tau = " << tau << "\nSlice " << Slice << "\nLoop " << loop_coor; text->SetInput(ss.str().c_str()); } }; // ─── SliderCallback ─────────────────────────────────────────────────────────── class SliderCallback : public vtkCommand { public: static SliderCallback* New() { return new SliderCallback; } virtual void Execute(vtkObject* caller, unsigned long, void*) { vtkSliderWidget* sw = vtkSliderWidget::SafeDownCast(caller); if (sw) contour = ((vtkSliderRepresentation*)sw->GetRepresentation())->GetValue(); fu->posExtractor->SetValue(0, contour * fu->rms); fu->negExtractor->SetValue(0, -contour * fu->rms); fu->posExtractor->Modified(); fu->negExtractor->Modified(); } static double contour; FrameUpdater* fu; }; double SliderCallback::contour = 1.0; // ─── CommandHandler ─────────────────────────────────────────────────────────── // Minimal parser for the wire protocol. Natural-language interpretation // is handled externally (by Claude) before commands reach this program. class CommandHandler : public vtkCallbackCommand { public: static CommandHandler* New() { return new CommandHandler; } FrameUpdater* fu; vtkCamera* camera; vtkRenderer* renderer; vtkRenderWindowInteractor* iren; vtkSliderRepresentation2D* sliderRep; int pollTimerId = -1; double isosurfaceLevel = 1.0; // in RMS units void SetIsosurface(double level) { isosurfaceLevel = std::max(0.0, std::min(10.0, level)); sliderRep->SetValue(isosurfaceLevel); SliderCallback::contour = isosurfaceLevel; fu->posExtractor->SetValue(0, isosurfaceLevel * fu->rms); fu->negExtractor->SetValue(0, -isosurfaceLevel * fu->rms); fu->posExtractor->Modified(); fu->negExtractor->Modified(); } void PrintStatus() { std::cout << "[status] file = " << fu->ffile << " : " << fu->files[fu->ffile] << "\n" << "[status] Slice = " << fu->Slice << "\n" << "[status] latt = " << fu->latt << "\n" << "[status] isosurface = " << isosurfaceLevel << " x RMS (" << isosurfaceLevel * fu->rms << ")" << std::endl; } // Execute one line of the wire protocol. void RunLine(const std::string& line) { std::istringstream iss(line); std::string verb; if (!(iss >> verb)) return; // ── slice ──────────────────────────────────────────── if (verb == "slice") { int dim; std::string valstr; if (!(iss >> dim >> valstr)) { std::cout << "[cmd] slice: expected dim value" << std::endl; return; } if (dim < 0 || dim >= fu->Nd) { std::cout << "[cmd] slice: dim out of range" << std::endl; return; } int n = (int)fu->latt[dim]; int newval; if (!valstr.empty() && (valstr[0] == '+' || valstr[0] == '-')) { int delta = std::stoi(valstr); newval = ((fu->Slice[dim] + delta) % n + n) % n; } else { newval = ((std::stoi(valstr) % n) + n) % n; } fu->Slice[dim] = newval; fu->ForceRender(iren); PrintStatus(); } // ── zoom ──────────────────────────────────────────────────── else if (verb == "zoom") { double factor; if (!(iss >> factor)) { std::cout << "[cmd] zoom: expected factor" << std::endl; return; } camera->Dolly(factor); renderer->ResetCameraClippingRange(); iren->GetRenderWindow()->Render(); } // ── azimuth ──────────────────────────────────────────────── else if (verb == "azimuth") { double deg; if (!(iss >> deg)) { std::cout << "[cmd] azimuth: expected degrees" << std::endl; return; } camera->Azimuth(deg); renderer->ResetCameraClippingRange(); iren->GetRenderWindow()->Render(); } // ── elevation ────────────────────────────────────────────── else if (verb == "elevation") { double deg; if (!(iss >> deg)) { std::cout << "[cmd] elevation: expected degrees" << std::endl; return; } camera->Elevation(deg); renderer->ResetCameraClippingRange(); iren->GetRenderWindow()->Render(); } // ── spin ────────────────────────────────────────── // Applies azimuth rotation on every 100ms poll tick. spin 0 stops. else if (verb == "spin") { double deg; if (!(iss >> deg)) { std::cout << "[cmd] spin: expected degrees" << std::endl; return; } g_spinDeg = deg; std::cout << "[cmd] spin rate = " << g_spinDeg << " deg/tick" << std::endl; } // ── iso ────────────────────────────────────────────────────── else if (verb == "iso") { double val; if (!(iss >> val)) { std::cout << "[cmd] iso: expected value" << std::endl; return; } SetIsosurface(val); fu->ForceRender(iren); PrintStatus(); } // ── file ─────────────────────────────────────────────── else if (verb == "file") { std::string valstr; if (!(iss >> valstr)) { std::cout << "[cmd] file: expected index" << std::endl; return; } int n = (int)fu->files.size(); int newval; if (!valstr.empty() && (valstr[0] == '+' || valstr[0] == '-')) { int delta = std::stoi(valstr); newval = ((fu->ffile + delta) % n + n) % n; } else { newval = ((std::stoi(valstr) % n) + n) % n; } fu->ffile = newval; fu->old_file = -1; fu->ForceRender(iren); PrintStatus(); } // ── render ─────────────────────────────────────────────────────────── else if (verb == "render") { fu->ForceRender(iren); } // ── status ─────────────────────────────────────────────────────────── else if (verb == "status") { PrintStatus(); } // ── quit ───────────────────────────────────────────────────────────── else if (verb == "quit" || verb == "exit") { g_running = false; iren->TerminateApp(); } else { std::cout << "[cmd] Unknown command: '" << line << "'" << std::endl; } } virtual void Execute(vtkObject*, unsigned long eventId, void* callData) { if (eventId != vtkCommand::TimerEvent) return; if (pollTimerId >= 0) { int tid = *(reinterpret_cast(callData)); if (tid != pollTimerId) return; } std::vector pending; { std::lock_guard lk(g_cmdMutex); while (!g_cmdQueue.empty()) { pending.push_back(g_cmdQueue.front()); g_cmdQueue.pop(); } } for (const auto& line : pending) { std::cout << "[cmd] >> " << line << std::endl; RunLine(line); } // Apply continuous spin (if active) at poll-timer rate if (g_spinDeg != 0.0) { camera->Azimuth(g_spinDeg); renderer->ResetCameraClippingRange(); iren->GetRenderWindow()->Render(); } } }; // ─── main ───────────────────────────────────────────────────────────────────── int main(int argc, char* argv[]) { using namespace Grid; Grid_init(&argc, &argv); GridLogLayout(); auto latt_size = GridDefaultLatt(); auto simd_layout = GridDefaultSimd(latt_size.size(), vComplex::Nsimd()); auto mpi_layout = GridDefaultMpi(); GridCartesian Grid(latt_size, simd_layout, mpi_layout); double default_contour = 1.0; std::string arg; std::vector file_list({"file1","file2","file3","file4", "file5","file6","file7","file8"}); if (GridCmdOptionExists(argv, argv+argc, "--files")) { arg = GridCmdOptionPayload(argv, argv+argc, "--files"); GridCmdOptionCSL(arg, file_list); } #ifdef MPEG if (GridCmdOptionExists(argv, argv+argc, "--mpeg")) g_mpeg = 1; #endif if (GridCmdOptionExists(argv, argv+argc, "--fps")) { arg = GridCmdOptionPayload(argv, argv+argc, "--fps"); GridCmdOptionInt(arg, g_framerate); } if (GridCmdOptionExists(argv, argv+argc, "--isosurface")) { arg = GridCmdOptionPayload(argv, argv+argc, "--isosurface"); GridCmdOptionFloat(arg, default_contour); } int NoTime = 0, Nd = Grid.Nd(); Coordinate Slice(Nd,0), SumDims(Nd,0), LoopDims(Nd,0), XYZDims({0,1,2}); if (GridCmdOptionExists(argv, argv+argc, "--slice")) { arg = GridCmdOptionPayload(argv, argv+argc, "--slice"); GridCmdOptionIntVector(arg, Slice); } if (GridCmdOptionExists(argv, argv+argc, "--sum")) { arg = GridCmdOptionPayload(argv, argv+argc, "--sum"); GridCmdOptionIntVector(arg, SumDims); } if (GridCmdOptionExists(argv, argv+argc, "--loop")) { arg = GridCmdOptionPayload(argv, argv+argc, "--loop"); GridCmdOptionIntVector(arg, LoopDims); } if (GridCmdOptionExists(argv, argv+argc, "--xyz")) { arg = GridCmdOptionPayload(argv, argv+argc, "--xyz"); GridCmdOptionIntVector(arg, XYZDims); } if (GridCmdOptionExists(argv, argv+argc, "--notime")) { NoTime = 1; } std::thread cmdThread(CommandReaderThread); cmdThread.detach(); // ── VTK scene ──────────────────────────────────────────────────────────── vtkNew colors; std::array posColor{{240,184,160,255}}; colors->SetColor("posColor", posColor.data()); std::array bkg{{51,77,102,255}}; colors->SetColor("BkgColor", bkg.data()); vtkNew renWin; vtkNew iren; iren->SetRenderWindow(renWin); int frameCount = (int)file_list.size(); for (int d = 0; d < Nd; d++) if (LoopDims[d]) frameCount *= latt_size[d]; vtkNew aCamera; aCamera->SetViewUp(0,0,-1); aCamera->SetPosition(0,-1000,0); aCamera->SetFocalPoint(0,0,0); aCamera->ComputeViewPlaneNormal(); aCamera->Azimuth(30.0); aCamera->Elevation(30.0); vtkNew aRenderer; renWin->AddRenderer(aRenderer); double nrm, rms, contour; { LatticeComplexD data(&Grid); readFile(data, file_list[0]); nrm = norm2(data); } rms = std::sqrt(nrm / Grid.gSites()); contour = default_contour * rms; vtkNew imageData; imageData->SetDimensions(latt_size[XYZDims[0]], latt_size[XYZDims[1]], latt_size[XYZDims[2]]); imageData->AllocateScalars(VTK_DOUBLE, 1); for (int xx=0;xxSetScalarComponentFromDouble(xx,yy,zz,0,0.0); vtkNew posExtractor; posExtractor->SetInputData(imageData); posExtractor->SetValue(0, contour); vtkNew posStripper; posStripper->SetInputConnection(posExtractor->GetOutputPort()); vtkNew posMapper; posMapper->SetInputConnection(posStripper->GetOutputPort()); posMapper->ScalarVisibilityOff(); vtkNew pos; pos->SetMapper(posMapper); pos->GetProperty()->SetDiffuseColor(colors->GetColor3d("posColor").GetData()); pos->GetProperty()->SetSpecular(0.3); pos->GetProperty()->SetSpecularPower(20); pos->GetProperty()->SetOpacity(0.5); vtkNew negExtractor; negExtractor->SetInputData(imageData); negExtractor->SetValue(0, -contour); vtkNew negStripper; negStripper->SetInputConnection(negExtractor->GetOutputPort()); vtkNew negMapper; negMapper->SetInputConnection(negStripper->GetOutputPort()); negMapper->ScalarVisibilityOff(); vtkNew neg; neg->SetMapper(negMapper); neg->GetProperty()->SetDiffuseColor(colors->GetColor3d("Ivory").GetData()); vtkNew outlineData; outlineData->SetInputData(imageData); vtkNew mapOutline; mapOutline->SetInputConnection(outlineData->GetOutputPort()); vtkNew outline; outline->SetMapper(mapOutline); outline->GetProperty()->SetColor(colors->GetColor3d("Black").GetData()); vtkNew TextT; TextT->SetInput("Initialising..."); TextT->SetPosition(0, 0.7*1025); TextT->GetTextProperty()->SetFontSize(24); TextT->GetTextProperty()->SetColor(colors->GetColor3d("Gold").GetData()); aRenderer->AddActor(TextT); aRenderer->AddActor(outline); aRenderer->AddActor(pos); aRenderer->AddActor(neg); vtkNew fu; fu->SetGrid(&Grid); fu->SetFiles(file_list); fu->SetSlice(Slice); fu->SetSumDimensions(SumDims); fu->SetLoopDimensions(LoopDims); fu->SetDisplayDimensions(XYZDims); fu->SetSliceDimensions(); fu->imageData = imageData; fu->text = TextT; fu->maxCount = frameCount; fu->posExtractor = posExtractor; fu->negExtractor = negExtractor; fu->rms = rms; iren->AddObserver(vtkCommand::TimerEvent, fu); iren->AddObserver(vtkCommand::KeyPressEvent, fu); aRenderer->SetActiveCamera(aCamera); aRenderer->ResetCamera(); aRenderer->SetBackground(colors->GetColor3d("BkgColor").GetData()); aCamera->Dolly(1.0); aRenderer->SetViewport(0.0,0.0,1.0,1.0); aRenderer->ResetCameraClippingRange(); renWin->SetSize(1024,1024); renWin->SetWindowName("ControlledFieldDensity"); renWin->Render(); iren->Initialize(); // Slider vtkSmartPointer sliderRep = vtkSmartPointer::New(); sliderRep->SetMinimumValue(0.0); sliderRep->SetMaximumValue(10.0); sliderRep->SetValue(default_contour); sliderRep->SetTitleText("Fraction RMS"); sliderRep->GetTitleProperty()->SetColor( colors->GetColor3d("AliceBlue").GetData()); sliderRep->GetLabelProperty()->SetColor( colors->GetColor3d("AliceBlue").GetData()); sliderRep->GetSelectedProperty()->SetColor(colors->GetColor3d("DeepPink").GetData()); sliderRep->GetTubeProperty()->SetColor( colors->GetColor3d("MistyRose").GetData()); sliderRep->GetCapProperty()->SetColor( colors->GetColor3d("Yellow").GetData()); sliderRep->SetSliderLength(0.05); sliderRep->SetSliderWidth(0.025); sliderRep->SetEndCapLength(0.02); sliderRep->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedDisplay(); sliderRep->GetPoint1Coordinate()->SetValue(0.1,0.1); sliderRep->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedDisplay(); sliderRep->GetPoint2Coordinate()->SetValue(0.9,0.1); vtkSmartPointer sliderWidget = vtkSmartPointer::New(); sliderWidget->SetInteractor(iren); sliderWidget->SetRepresentation(sliderRep); sliderWidget->SetAnimationModeToAnimate(); sliderWidget->EnabledOn(); vtkSmartPointer slidercallback = vtkSmartPointer::New(); slidercallback->fu = fu; SliderCallback::contour = default_contour; sliderWidget->AddObserver(vtkCommand::InteractionEvent, slidercallback); // CommandHandler on fast poll timer vtkNew cmdHandler; cmdHandler->fu = fu; cmdHandler->camera = aCamera; cmdHandler->renderer = aRenderer; cmdHandler->iren = iren; cmdHandler->sliderRep = sliderRep; cmdHandler->isosurfaceLevel = default_contour; iren->AddObserver(vtkCommand::TimerEvent, cmdHandler); cmdHandler->pollTimerId = iren->CreateRepeatingTimer(100); if (g_mpeg == 0 && NoTime == 0) { fu->timerId = iren->CreateRepeatingTimer(10000 / g_framerate); } if (g_mpeg) { #ifdef MPEG vtkWindowToImageFilter* imageFilter = vtkWindowToImageFilter::New(); imageFilter->SetInput(renWin); imageFilter->SetInputBufferTypeToRGB(); vtkFFMPEGWriter* writer = vtkFFMPEGWriter::New(); writer->SetFileName("movie.avi"); writer->SetRate(g_framerate); writer->SetInputConnection(imageFilter->GetOutputPort()); writer->Start(); for (int i = 0; i < fu->maxCount; i++) { fu->Execute(iren, vtkCommand::TimerEvent, &fu->timerId); imageFilter->Modified(); writer->Write(); } writer->End(); writer->Delete(); imageFilter->Delete(); #else assert(-1 && "MPEG support not compiled"); #endif } else { iren->Start(); } g_running = false; Grid_finalize(); return EXIT_SUCCESS; }