Using Procedural Materials, it is possible to play movies (AVIs, specifically) in the place of materials in a map. I am using this code for an automated news show, but there are a lot of cool things you could do with it. Using this technique, for example, you could have a movie theatre in your map that was actually displaying a full movie on the screen; with a little tweaking, you could probably also add active AVIs to your VGUI panels. While Source already allows you to easily view scripted events elsewhere in the map (using Point_cameras and func_monitors), this technique allows you to display actual movies from your hard drive. This code/mini-tutorial gives you everything you need to fairly easily add AVIMaterial entities to your map. Each entity controls drawing to a certain texture, and allows you to play a movie, pause the movie, change the movie, advance one frame, etc. This code draws heavily from the excellent Procedural Materials tutorial and code kindly provided from Valve (thanks again, Tom and Mike!)
Procedural Materials allow you to identify certain materials as procedural meaning that its pixel values are set programmatically, as the level is running; conceptually, you get a 2d array of pixels representing the texture that you then set to be whatever color you want. In our case, we read in the appropriate frame of the movie (which frame is of course dependent on where we are in playback), read its pixels one-by-one, and write them to the texture. Since this is done roughly 60 times a second, we get a nice, smooth movie playback.
How to install
Unfortunately, there is a fair amount of code to install, and some project settings to change. It's all fairly straight-forward, however, and I believe that if you follow these instructions everything should work correctly.
- Read the tutorial for Procedural Materials. Everything in this tutorial builds off of that.
- Download this zip file; it's a ~15meg zip file. AVIMaterial.zip. It has all the files necessary.
- Copy vf32.lib from C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Lib (or wherever your VS2003 is installed) to C:\MyMod\src\lib\public (changing the directory to be appropriate for your mod.) Add vf32.lib to the client project in Visual Studio. vfw32.lib is Video for Windows, which has the functions we'll use to read the AVI.
- Copy AVIMaterial.h and AVIMaterial.cpp to C:\MyMod\src\dlls. These files declare and define CAVIMaterial, the server-side entity that controls the movies.
- Copy c_aviMaterial.h, c_aviMaterial.cpp, AVIMaterialProxy.h, AVIMaterialProxy.cpp, AVIMaterialRegen.h, and AVIMaterial.cpp to C:\SecondCity\src\cl_dll. c_aviMaterial declares and defines C_AVIMaterial, the cleverly-named client-side version of CAVIMaterial. AVIMaterialProxy declares and defines CAviMaterialProxy which controls instances of CAviTextureRegen, which does the actual movie drawing.
- Add the entry in SecondCity.fgd to your mod's .fgd. This exposes our AVIMaterial entity to Hammer.
- Copy avi_panel1.vmt, avi_panel1.vtf, avi_panel2.vmt, and avi_panel2.vtf to your mod's materials directory. They go in the root.
- Copy quickone.vmf and quickone.bsp to your mod's maps directory. Again, they go in the root.
- Copy newyorkmap.avi and category.avi to the root of your c: drive. (If you don't want to put them there, feel free to put them somewhere else, just make sure to update the AVIMaterial entities in quickone to point at the right location.)
- Open Visual Studio, and add vfw32.lib to the client project. (Right-click on client->Add->Add Existing Item.)
- In Visual Studio, add c_aviMaterial.h, AVIMaterialProxy.h, and AVIMaterialRegen.h to the Header Files folder of your client project.
- In Visual Studio, add c_aviMaterial.cpp, AVIMaterialProxy.cpp, and AVIMaterialRegen.cpp to the Source Files folder of your client project.
- In Visual Studio, add AVIMaterial.h to the Header Files folder of your hl (server) project.
- In Visual Studio, add AVIMaterial.cpp to the Source Files folder of your hl (server) project.
- Compile your project, and run it. In the quickone map, you should see two movies hanging and playing in the middle of the room.
How it works
CAVIMaterial and C_AVIMaterial are a typical client/server entity. Please look at the excellent article on Networking entities for more information. When the map loads, avi_panel1 and avi_panel2 each have a corresponding CAviMaterialProxy instantiated for them. (The line
EXPOSE_INTERFACE( CAviMaterialProxy, IMaterialProxy, "AviRenderer" IMATERIAL_PROXY_INTERFACE_VERSION );
links them to the AviRenderer listed as the proxy in avi_panel1.vmt and avi_panel2.vmt. When Init()ted, each proxy is passed in a pointer to the IMaterial they are in charge of. CAviMaterialProxy also has a static function that searches a static array of CAviMaterialProxy on the basis of their texture names. This is a clunky way of allowing C_AVIMaterials to link up to the appropriate textures (otherwise you couldn't control which movie was getting drawn on which texture.) I'm sure there's a better way to do this, please let me know if you know of one. The CAviMaterialProxy instances are used internally by Source to draw the procedural textures; we mainly use it to passthrough commands to its CAviMaterialRegen, however. Once a C_AVIMaterial is passed its movie filename and texture, it searches the proxies for the right one (meaning the proxy that is drawing to its texture), and records it. It then tells the proxy (which passes it to the regenerator) the movie file to load. From then on, the CAVIMaterial entity can be manipulated using the normal Source I/O system. The code is commented, please see it for implementation details.
How to use it
Currently, the CAVIMaterial entity has the following inputs:
- Play - This will start playing whatever movie is set as the current movie. If the movie has not played before, it will start from the beginning; if the movie is paused, it will resume playing back from where it was paused. If the movie is already playing, it has no effect. If no movie is loaded, nothing will happen.
- Pause - This will pause the currently-playing movie, leaving the last frame visible. If the movie is already paused, nothing will happen. If there is no currently-playing movie, nothing will happen.
- Stop - This will end and clear the currently playing movie.
- AdvanceFrame - This will advance the movie one frame. If no movie is currently loaded, nothing will happen. This input assumes the movie is already paused, but doesn't enforce it.
- SetMovie - This input takes a string which should be the absolute path to an AVI on disk, and sets that AVI to be the currently loaded movie. You should stop playing any active movies before setting a new movie.
The CAVIMaterial entity has the following KeyValues:
- TextureName - This is the name of the material that this CAVIMaterial will draw to. If a material is in more than one place of the map, the movie will be shown on all of them.
- MovieName - This is the absolute path on disk to the AVI that will be initially loaded by the CAviMaterialRegen.
The CAVIMaterial entity has the following spawn flags:
- Play movie immediately - This will start the movie specified in MovieName playing as soon as the map loads.
- Loop movie - This will cause the movie to begin playing again immediately after it is completed.
There are a lot of improvements one could make to this code. A few of these are listed below.
- Fix bugs in the existing code. I am only moderately familiar with C++ and the Source engine, so if something looks wrong or unnecessarily awkward, it definitely may be. Please let [[user::ndnichols | me]] know, and I'll be happy to update the code.
- Add various movie controls. You could straight-forwardly add fast-forward, rewind, slo-mo, etc.
- Optimize this code. This code has not been tweaked for performance at all; it runs acceptably, but there is definitely room for speedup in CAviMaterialRegen::RegenerateTextureBits().
- Have the path for the movies use Steam directories. Right now, the path to the movie is always an absolute directory, but it should probably be specified in relation to the Steam directories if you actually want to use movies in your mod.
- Add support for other movie types You could presumably add support for other movie types (Divx, WMV, etc.), but you're probably better off using a program to convert other movie types to AVIs offline. We've had luck with the open source | ffmpeg, but YMMV.
- Improve entity use It's kind of weird/clunky to have to specify the texture name to draw to in the Hammer entries for the CAVIMaterials. There's probably a better way to do this; figure it out!
- Add more avi_panels With avi_panel1 and avi_panel2, you can only show two distinct movies at the same time; if you need more than that, simply copy and paste avi_panel1.vtf and avi_panel1.vmt and rename them appropriately. Don't forget to update the .vmt to point at the new .vtf.
This code is far from bug-free or production ready. It definitely works, though, and it certainly won't kill your computer or anything. If you find bugs, please contact me. I will be using this code in the future, and will certainly find bugs of my own, and I'll update the code as necessary. I wanted to get this code out and available to people before I lost the drive to write it up and everything, so here it is, warts and all.
As mentioned above, this code is really just an aggregation of different peoples' work. user:zerodegrez did the work for the original procedural materials tutorial, and all of this procedural code is taken straight from that. Tom Leonard at Valve was nice enough to give me the AVI opening/reading code, and Mike Durand at Valve was very friendly and helpful about getting procedural materials working again in the SDK (turns out they were fixed in the Aug, 2006 update, who knew?) Thanks again. Please let me know if you have any questions, problems or suggestions. Good luck!