/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * * * FLEXIBLE.CPP * * by Melinda Green * * melinda@superliminal.com * * Oct. 1996 * * * * DESCRIPTION: * * Implements the Flexible class. * * * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include "Flexible.h" #include "Vec.h" #include /* * UserVisual callback which directs control back to the Flexible * object being rendered. */ static int FlexibleCallback(LPDIRECT3DRMUSERVISUAL uvis, void *arg, D3DRMUSERVISUALREASON reason, LPDIRECT3DRMDEVICE /* dev */ , LPDIRECT3DRMVIEWPORT view) { Flexible *flexi = (Flexible *)arg; if (reason == D3DRMUSERVISUAL_CANSEE) return TRUE; // could be smarter if given bounding volume if (reason == D3DRMUSERVISUAL_RENDER) { if (flexi->Render(view)) return D3D_OK; else return DDERR_GENERIC; } return 0; } /* * Called by D3D when the UserVisual's reference reaches zero. * It deletes the containing Flexible object which will call * any derived class' destructor allowing it to free any resources * it may have allocated. */ static void FlexibleDestroyCB(LPDIRECT3DRMOBJECT obj, void *arg) { Flexible *flexi = (Flexible *)arg; delete flexi; } /* * The base class' destructor. */ Flexible:: ~Flexible () { RELEASE(eb); if (mats) { for (int m = 0; m < n_mats; m++) RELEASE(mats[m]); delete[] mats; } RELEASE(m_d3ddev); } typedef struct { D3DINSTRUCTION op_set_status; D3DSTATUS set_defaults; D3DINSTRUCTION op_state_render; D3DSTATE shade_mode; D3DINSTRUCTION exit1; } OPData; // all but the vertex and triangle data static HRESULT __stdcall validate_cb(void *arg, DWORD offset) { *((DWORD *)arg) = offset; return 0; } Flexible::Flexible (LPDIRECT3DRMDEVICE dev, LPDIRECT3DRM lpd3drm, int n_verts, int verts_per_block, int n_blocks, int tris_per_block, const int tri_block[][3], int n_colors, const double colors[][3], int blocks_per_color, int flat_shade) { eb = NULL; uvis = NULL; n_mats = n_colors; void *p; void *p_start, *v_end; long e_end; DWORD error_offset = 0; // DWORD cb_arg; // would have been used by Validate. See below // Compute a few useful constants // int n_tris = n_blocks * tris_per_block; int verts_per_color = verts_per_block * blocks_per_color; int tris_per_color = tris_per_block * blocks_per_color; D3DEXECUTEBUFFERDESC desc; D3DEXECUTEDATA data; LPDIRECT3D lpD3D = NULL; int c, block, block_offset = 0; dev->GetDirect3DDevice(&m_d3ddev); if (!m_d3ddev) goto generic_error; if (FAILED(m_d3ddev->GetDirect3D(&lpD3D))) goto generic_error; // create the user visual object // if (FAILED (lpd3drm->CreateUserVisual(FlexibleCallback, (void *)this, &uvis))) goto generic_error; if (FAILED(uvis->AddDestroyCallback(FlexibleDestroyCB, (void *)this))) goto generic_error; // Create the Execute Buffer // desc.dwSize = sizeof(desc); desc.dwFlags = D3DDEB_BUFSIZE; desc.dwBufferSize = sizeof(OPData) + // mode setting stuff, etc. n_verts * sizeof(D3DVERTEX) + // the vertices n_tris * sizeof(D3DTRIANGLE) + // the triangle indices n_blocks * sizeof(D3DINSTRUCTION) + // triangle lists n_colors * (sizeof(D3DINSTRUCTION) + sizeof(D3DSTATE)) + n_colors * (sizeof(D3DINSTRUCTION) + sizeof(D3DPROCESSVERTICES)); if (FAILED(m_d3ddev->CreateExecuteBuffer(&desc, &eb, NULL))) goto generic_error; // Create the materials // mats = new LPDIRECT3DMATERIAL[n_colors]; for (c = 0; c < n_colors; c++) { if (FAILED(lpD3D->CreateMaterial(&mats[c], NULL))) goto generic_error; D3DMATERIAL color; memset(&color, 0, sizeof(color)); color.dwSize = sizeof(color); color.diffuse.r = color.ambient.r = D3DVAL (colors[c][0]); color.diffuse.g = color.ambient.g = D3DVAL (colors[c][1]); color.diffuse.b = color.ambient.b = D3DVAL (colors[c][2]); color.dwRampSize = 32; if (FAILED(mats[c]->SetMaterial(&color))) goto generic_error; } // Lock the execute buffer and mark the following offsets: // p_start = the beginning of the buffer // p = the beginning of the non-vertex data (incremented) // v_end = first address of the non-vertex data // e_end = end of buffer I.E. expected ending value for p // if (FAILED(eb->Lock(&desc))) goto generic_error; p = (void *)((char *)desc.lpData + n_verts * sizeof(D3DVERTEX)); p_start = desc.lpData; v_end = p; e_end = (long)desc.lpData + desc.dwBufferSize; // expected end // load defaults // OP_SET_STATUS(D3DSETSTATUS_ALL, D3DSTATUS_DEFAULT, 2048, 2048, 0, 0, p); // load vertices // note that they are colored in verts_per_color sets // for (c = 0; c < n_colors; c++) { D3DMATERIALHANDLE hMat; if (FAILED(mats[c]->GetHandle(m_d3ddev, &hMat))) goto generic_error; OP_STATE_LIGHT(1, p); STATE_DATA(D3DLIGHTSTATE_MATERIAL, hMat, p); OP_PROCESS_VERTICES(1, p); PROCESSVERTICES_DATA(D3DPROCESSVERTICES_TRANSFORMLIGHT, c * verts_per_color, verts_per_color, p); } // set the shading mode // OP_STATE_RENDER(1, p); STATE_DATA(D3DRENDERSTATE_SHADEMODE, flat_shade ? D3DSHADE_FLAT : D3DSHADE_GOURAUD, p); // casche some constants needed during picking // m_state_setup_size = (long)p - (long)v_end; m_block_size = sizeof(D3DINSTRUCTION) + tris_per_block * sizeof(D3DTRIANGLE); // add all the triangle blocks // for (block = 0; block < n_blocks; block++) { // OP_BRANCH_FORWARD(... too bad this feature doesn't work OP_TRIANGLE_LIST(tris_per_block, p); for (int tri = 0; tri < tris_per_block; tri++) { D3DTRIANGLE *t = (D3DTRIANGLE *)p; t->v1 = block * verts_per_block + tri_block[tri][0]; t->v2 = block * verts_per_block + tri_block[tri][1]; t->v3 = block * verts_per_block + tri_block[tri][2]; t->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE; t++; p = (char *)t; } } OP_EXIT(p); // end of data marker assert((long)p == e_end); // make sure buffer loaded as expected // give the driver back control of the data contents // if (FAILED(eb->Unlock())) goto generic_error; // tell the driver how the buffer data is layed out // data.dwSize = sizeof(data); data.dwVertexOffset = 0; data.dwVertexCount = n_verts; data.dwInstructionOffset = n_verts * sizeof(D3DVERTEX); data.dwInstructionLength = desc.dwBufferSize - data.dwInstructionOffset; data.dwHVertexOffset = 0; if (FAILED(eb->SetExecuteData(&data))) goto generic_error; // The ExecuteBuffer::Validate call seems to always return // garbage. Also, it won't accept a NULL callback argument // like the documentation says it should. // /* if (FAILED(eb->Validate(&error_offset, validate_cb, &cb_arg, 0))); goto generic_error; if(error_offset == 0) goto generic_error; if(cb_arg == 0) goto generic_error; */ RELEASE(lpD3D); return; generic_error: RELEASE(lpD3D); RELEASE(m_d3ddev); // RELEASE(mat); need to release all mats and delete array RELEASE(eb); RELEASE(uvis); return; } // Determines whether this object is under the given pixel in // the given view, and if so, returns the index of the closest // block under that point, and the index of the triangle hit // within that block. // int Flexible::Pick(LPDIRECT3DRMVIEWPORT rmview, long x, long y, int *blockptr, int *triptr) { // get the immediate mode view from the retained mode viewport // LPDIRECT3DVIEWPORT view = NULL; rmview->GetDirect3DViewport(&view); // initialize an infinitely thin pick region // D3DRECT where; where.x1 = where.x2 = x; where.y1 = where.y2 = y; // perform a pick on the object's execute buffer // DWORD dummy = 0; // initialization just stop the warning m_d3ddev->Pick(eb, view, dummy, &where); // make sure *some* part of the object was hit. // Presumably the app did a retained mode pick first and // is calling this routine for further information, but // let's not make any assumptions. // unsigned long hits, i; m_d3ddev->GetPickRecords(&hits, NULL); if (0 == hits) return 0; // get all the hit data from the driver, and find // the frontmost record. Seems like that would be // a useful instruction to add to the driver API, // but for now, this is the only way. // NOTE: the speed of memory allocation and deallocation // used here should not be a problem since most all // picks are generated by some user action and is therefore // probably not super time critical. // D3DPICKRECORD *recs = new D3DPICKRECORD[hits]; m_d3ddev->GetPickRecords(&hits, recs); D3DVALUE front_z = D3DVAL (8888888.8); int front_id; for (i = 0; i < hits; i++) if (recs[i].dvZ < front_z) { front_z = recs[i].dvZ; front_id = i; } int eboff = recs[front_id].dwOffset; delete[] recs; // tri_offset is position of hit triangle from first trilist // int tri_offset = eboff - m_state_setup_size; *blockptr = tri_offset / m_block_size; *triptr = (tri_offset % m_block_size) - 1; RELEASE(view); return hits; } /* * Called indirectly by the render driver during rendering, * it first gives the derived class a chance to update it's vertices * then it executes the buffer and flushes the results. */ BOOL Flexible::Render(LPDIRECT3DRMVIEWPORT view) { D3DVERTEX *v; D3DEXECUTEBUFFERDESC desc; D3DEXECUTEDATA data; LPDIRECT3DVIEWPORT lpD3DView = NULL; view->GetDirect3DViewport(&lpD3DView); if (!lpD3DView) goto ret_with_error; desc.dwSize = sizeof(desc); desc.dwFlags = 0; if (FAILED(eb->Lock(&desc))) goto ret_with_error; v = (D3DVERTEX *)desc.lpData; UpdateVertexData(v); // this is where derived classes move the // verts if (FAILED(eb->Unlock())) goto ret_with_error; if (FAILED(m_d3ddev->Execute(eb, lpD3DView, D3DEXECUTE_CLIPPED))) goto ret_with_error; data.dwSize = sizeof data; if (FAILED(eb->GetExecuteData(&data))) goto ret_with_error; if (FAILED(view->ForceUpdate(data.dsStatus.drExtent.x1, data.dsStatus.drExtent.y1, data.dsStatus.drExtent.x2, data.dsStatus.drExtent.y2))) { goto ret_with_error; } RELEASE(lpD3DView); return TRUE; ret_with_error: RELEASE(lpD3DView); return FALSE; } #define NORMALIZE_VEC3(norm, vec) \ { \ double len = sqrt(NORMSQRD3(norm)); \ VDS3(norm, norm, len); \ } /* * This is a simple utility method which can be called from UpdateVertexData * to compute face normals when flat shading. */ void Flexible::ComputeFlatNormals(D3DVERTEX v[], int verts_per_block, int n_blocks, int tris_per_block, const int tri_block[][3]) { int i0, last_i0 = -1; for (int b = 0; b < n_blocks; b++) { int off = b * verts_per_block; // offset into vertex list for (int t = 0; t < tris_per_block; t++) { i0 = tri_block[t][0]; if (i0 == last_i0) continue; // test is just to reduce redundancy int i1 = tri_block[t][1]; int i2 = tri_block[t][2]; double v0[3], v1[3], v2[3], v01[3], v02[3], norm[3]; v0[0] = v[off + i0].x; v0[1] = v[off + i0].y; v0[2] = v[off + i0].z; v1[0] = v[off + i1].x; v1[1] = v[off + i1].y; v1[2] = v[off + i1].z; v2[0] = v[off + i2].x; v2[1] = v[off + i2].y; v2[2] = v[off + i2].z; VMV3(v01, v1, v0); // vector from 0 to 1 VMV3(v02, v2, v0); // vector from 0 to 2 VXV3(norm, v01, v02); // cross product is normal to both NORMALIZE_VEC3(norm, v[v0]); // make into unit vector v[off + i0].nx = D3DVAL (norm[0]); v[off + i0].ny = D3DVAL (norm[1]); v[off + i0].nz = D3DVAL (norm[2]); last_i0 = i0; } } }