Saturday, March 25, 2006

more zoom/origin hell

The really weird thing is, it looks much better if the origin DOESN'T move in mirror mode!!! So it's actually not broken at the moment, except for

1) the move and return thing, and that's gone now that the normalized origin isn't zoomed
2) the problem with trail (outer rings move when they shouldn't)
3) make DIB must still use center zoom, otherwise scale to fit will screw up mirrored snapshots

Thursday, March 23, 2006

zoom/origin hell

Unmirrored zooming is supposed to be origin-centered, i.e. you zoom into or out of the ring origin. This isn't allowed in mirror mode because it would change the image. In mirror mode, you zoom into or out of the *mirror origin* (i.e. the center of the screen), and let the ring origin move as needed so that the image stays the same.

origin behavior with zooming, version 1.1:

mirror drag behavior
no no stationary: correct
yes no moves: correct
no yes stationary: correct
yes yes stationary (or with damping, moves and then returns): BOGUS

origin behavior with zooming, version 1.3 (trail):

mirror drag behavior
no no moves: BOGUS
yes no moves: correct
no yes stationary, but with damping, moves and then returns: BOGUS
yes yes stationary (or with damping, moves and then returns): BOGUS

Note that random origin may have the same problems as drag origin; it's hard to tell.

Trail complicates things further, because if the origin moves and returns, the trail pattern is lost.

Really there are two separate problems:

1) The damping feature of the origin motion prevents the ring origin from moving during zoom, or causes it to slip back. As a result, zooming in mirror mode changes the mirror imagery. This behavior always occurred in mirror mode, but somehow it went unnoticed; adding trail made it more noticeable, by causing it to affect unmirrored mode also.

The likely cause is that When you zoom in mirrored mode, the view's normalized origin changes, but the main frame's target origin (also in normalized space) DOESN'T change, so the view eventually slips back to the target origin. This can be solved by saving the normalized origin before zooming the view, and then correcting the target origin by the difference between the new normalized origin and the saved one.

2) Adding trail broke zooming into the ring origin. It could be argued that this was a bad idea anyway and that we should always zoom into the mirror origin, in which case this bug is actually a feature. However, this argument doesn't really hold up, because zooming into the ring origin is typically more interesting than zooming into whatever happens to be in the center of the screen.

This problem could possibly be solved by a temporary coordinate space change, so that the zoom scaling is done in ring-origin space instead of mirror-origin space. You could calculate the difference between the master ring origin and the center of the screen, and then for each ring, subtract the difference from its origin, multiply by zoom, and add the difference back on.

Monday, March 20, 2006

FillPath vs. StrokeAndFillPath

benchmark of FillPath vs. StrokeAndFillPath with null brush:
no significant difference

uniform curvature with respect to number of sides


// decrease curvature as number of sides increases
// behavior is somewhat more orderly but not as interesting either
double s = 1.0f / float(Ring.Sides - 2);
Ring.EvenCurve = float(m_Parms.EvenCurve * s);
Ring.OddCurve = float(m_Parms.OddCurve * s);

Tuesday, February 28, 2006

help changes for 1.3

add Random Ramp to Waveforms GIF (done)
add Trail to Master Controls (done)
add Convex to Trail (done)
add Convex to Effects (done)
modify Origin Motion for Trail (done)
modify Mouse Modes for Trail (done)
modify Canvas Scale for new origin limits (done)
modify Zoom for new origin limits and Window/Zoom (done)
modify Panic for Trail reset (done)
update shortcut tables (done)
add Trail to features list on web site (done)
idea: comprehensive list of menu commands?

help changes for 1.2

Master offsets are now saved. Affected topics: Master Offsets, Playlists
Add oscillator override (new topic)
Add MIDI setup's Advanced checkbox
Zero controllers / Panic reset oscillator frequency overrides

Monday, February 27, 2006

MIDI color oscillator overrides

Frequency AND amplitude, for each color component (HLS), for both foreground and background color is 2 * 3 * 2 = 12 knobs. It turns out to be too much to handle live. Better to control the frequencies only, and hard-code the amplitudes:

amp freq (range)
color speed 1 .1
lightness .25 .1
saturation .5 .1
bk hue 180 .01
bk lightness .25 .1
bk saturation .5 .1

Thursday, February 23, 2006

don't draw to video memory

Supposedly mirrored mode is faster because it reduces drawing by 3/4, but even if this difference is eliminated (by restricting drawing to the upper left quadrant), mirrored mode is STILL faster. The reason is that mirrored mode always draws to system memory. The solution is to draw to system memory in all cases.

Tuesday, February 07, 2006

awesome matt trail demo

patch "pinwheel fab color", master 158, zoom 119, damping 95.5, tempo 14.96, unmirrored, fill, outline (requires serious hotrod action)

This demo proves that trail is way better than odd shift and should be up next on the enhancement list.

Friday, February 03, 2006

allow user to register BmpToAvi

Cool idea, but unnecessary: now that the installer correctly handles upgrading (each release must have a new project GUID, but the same upgrade GUID), this situation can't arise anymore.

if (bta.GetLastError() == CBmpToAvi::ERR_CREATE_SOURCE
&& hr == REGDB_E_CLASSNOTREG) {
static const LPCSTR BmpToAviName = "BmpToAvi.ax";
if (AfxMessageBox(IDS_MEX_BTA_INSTALL, MB_YESNO) == IDYES) {
CPathStr path = CMainFrame::GetAppPath();
path.Append(BmpToAviName);
if (PathFileExists(path)) {
CString cmd;
cmd.Format("regsvr32 /s \"%s\"", path);
int retc = system(cmd);
if (retc)
AfxMessageBox(IDS_MEX_BTA_CANT_REG);
else
goto retry;
} else {
CString s;
s.Format(IDS_MEX_BTA_NOT_FOUND, BmpToAviName);
AfxMessageBox(s);
}
}
}

no more 2 GB limit

I took a look at the OpenDML stuff, but in the end I decided to solve this the MS way: I wrote my own DirectShow show source filter that inserts frames (as bitmaps) into a video stream.

That turned out to be the *easy* part! The SDK's "ball" example was a pretty good starting part for writing a DirectShow source filter. The only real problem is that the ball filter draws the frames itself, whereas I needed to draw the frames in the application, and then pass them to the source filter. Turns out MS thought of that: you can override the DoBufferProcessingLoop function, cool.

There's also the minor detail of how to call my filter's "add frame" function from the application: turns out that requires creating a new COM interface. A bit of research, but not too bad. And of course I need some application code to create a filter graph, connect all the "pins" together and run the graph. There are examples of this all over the place, nothing new here.

The real problem came from an unexpected direction: how to set the video compression options? The Vfw AVIFile API had a wonderful function called AVISaveOptions: it displayed a dialog listing all your video compressors, and allowed you to select one and configure it. And in DirectShow? Nothing like that exists. Insane but true.

So I wound up writing a DirectShow Video Compression dialog object. You pass it your source and destination filters, and it displays the dialog, and returns a fully-configured compressor filter.

The bottom line? I have developed two reusable technologies:
1) A DirectShow BMP to AVI converter.
2) A DirectShow Video Compression dialog

I suspect both could be useful to other developers, so I'm going to submit them to CodeProject later. Right now my priority is integrating them into a new version of Whorld.

Wednesday, February 01, 2006

data rates

In AVIFile's Video Compression dialog, tried setting data rate for a few encoders:
indeo video 5.1
Intel indeo video R3.2
used steve1, crop, 320x240, frames 5000-5200
In both cases, checking the box caused the data rate in the file to drop by almost half REGARDLESS of what number was typed for data rate. crazy!

Things that weren't obvious

1) IAMVfwCompressDialogs must be used AFTER THE COMPRESSOR IS CONNECTED, otherwise Configure ignores SetState and reinitializes the dialog every time
2) IAMVideoCompression must be created on the OUTPUT PIN of the compressor, not the on compressor itself
3) any size other than 320 x 240 causes first-chance exception in QCAP.DLL (debug only)

more problems: can't seem to set output bit rate, maybe that just doesn't work

IPin *pComprOut = CDSBmpToAvi::GetPin(ip->pCompr, PINDIR_OUTPUT);
if (pComprOut != NULL) { // if we got compressor's output pin
IAMStreamConfig *pStreamCfg = NULL;
m_hr = pComprOut->QueryInterface( // get IAMVideoCompression
IID_IAMStreamConfig, (void **)&pStreamCfg);
if (pStreamCfg != NULL) { // do GetFormat after input pin is connected
AM_MEDIA_TYPE *pmt;
m_hr = pStreamCfg->GetFormat(&pmt);
if (SUCCEEDED(m_hr)) {
printf("get format ok\n");
if (pmt->formattype == FORMAT_VideoInfo) {
VIDEOINFOHEADER *pvh = (VIDEOINFOHEADER *)pmt->pbFormat;
printf("%d\n", pvh->dwBitRate);
pvh->dwBitRate = 10000;
m_hr = pStreamCfg->SetFormat(pmt);
if (SUCCEEDED(m_hr)) {
printf("set format ok\n");
}
// DeleteMediaType(pmt);
}
}
}
}

Sunday, January 29, 2006

DirectShow source filter

CodeGuru's snake sample prog doesn't work, it hangs
to register or unregister a filter (e.g. ball.ax):
regsrv32 ball.ax
regsrv32 /u ball.ax
then draw a filter graph using graphedt.exe
to view, connect ball -> video renderer
to create AVI file, connect ball -> AVI mux -> file writer
this works fine, but important questions remain
1) how do you run the filter chain without using graphedt?
2) how do you get the app to communicate the frames to the filter?
3) where does the source filter's GUID come from?

Tuesday, January 10, 2006

invalid pixel format

Ben C's Gateway won't run Whorld in DirectDraw mode: it gives the error DDERR_INVALIDPIXELFORMAT. He initially had DirectX 8.0, I upgraded to 8.1 but it didn't help. Checked the Display Settings, he had 24-bit 1024x768. Tried 640x480, didn't help. Try 16-bit color?

Tuesday, January 03, 2006

POINTF vs DPOINT

Using POINTF (e.g. for Pucker) saves 8 bytes in the RING struct, which reduces the disk throughput requirements for snapshot movies. It doesn't seem to measureably worsen CPU usage. The downside is trading 15 digits of precision for 7. For items that remain constant over the life of a ring (e.g. Pucker), it might not matter, but for items that are incremented per frame (e.g. origin), it definitely *does* matter.

Thursday, December 22, 2005

hogging CPU breaks About dialog

If main is saturating the CPU, Help/About makes the app modal, but no dialog is displayed. The bug occurs with any dialog created via DoModal, but it does NOT occur with the common dialogs (e.g. CFileDialog), presumably because they use a different creation method. Help/About is currently the only non-common modal dialog accessible directly from the main menu, which is why the bug wasn't discovered earlier.

One workaround is to disable the main timer in CAboutDlg::OnInitDialog, and then re-enable it in ShowWindow. An easier one is to do an explicit ShowWindow in CAboutDlg::OnInitDialog.

Wednesday, December 21, 2005

changing hue loop length

Changing the hue loop length without compensation causes distracting jumps in hue. Reflecting the hue position prevents jumps entirely when the loop length is increased, and minimizes the probability of a jump when the loop length is decreased. Without changing the base position, it's impossible to prevent jumps entirely in the latter case, because the current hue may no longer fall within the range of the loop.

if (m_st.HueLoop && m_st.HueLoopLength) // try to avoid hue jump
m_st.HueLoopPos = Reflect(m_st.HueLoopPos, m_st.HueLoopLength);
m_st.HueLoopLength = Length;

Friday, November 11, 2005

limiting random origin motion


double RandOrgRange = 1; // 0 to 2, default is 1
DPOINT RandOrgBias = {0, 0}; // -.5 to .5, default is 0
double ofs = (1 - RandOrgRange) / 2;
m_TargetOrg.x = double(rand()) / RAND_MAX
* RandOrgRange + ofs + RandOrgBias.x;
m_TargetOrg.y = double(rand()) / RAND_MAX
* RandOrgRange + ofs + RandOrgBias.y;

Wednesday, November 09, 2005

seed of life

It's dependent on the frame rate. Frame rate, birth rate, ring spacing, and skew angle delta interact to determine how many ring tunnels are generated.

If birth rate = 1, ring spacing = 5, and skew angle freq = 1, at 25 FPS you get 5 ring tunnels (a 5-way seed of life), because you're generating 5 rings per second, and each ring has a fifth of the skew angle delta (.2 Hz = 72 degrees), and 5 clock ticks.

at 30 FPS, with the same settings, you get a 6-way seed of life.

Color speed can also be adjusted to achieve a constant color for each tunnel. 14.4 (a fifth of 72) works for the 5-way at skew angle = 1; 90 works for most of the others.

Since the rings continue to intersect at the origin no matter how big they get, the rings will never be entirely off-canvas, and thus will never be deleted. This is a dangerous side effect and yet another argument for a max rings parameter.

Useful formulas:
Birth Ring Skew ring color
FPS Rate Space Ang Hz tunnels speed
25 .25 5 1.25 1 90
25 .5 5 1.25 2 90
25 .75 5 1.25 3 90
25 1 5 1.25 4 90
25 1.25 5 1.25 5 72
25 1.5 5 1.25 6 90
25 1.75 5 1.25 7 90*
25 2 5 1.25 8 90*
25 2.25 5 1.25 9 90*
25 2.5 5 1.25 10 72*

*colors are stable but tunnels don't appear in spectral order