// license:LGPL-2.1+ // copyright-holders:David Haywood, Angelo Salese, ElSemi, Andrew Gardner, Andrew Zaferakis ///////////////////////////////// /// Hyper NeoGeo 64 - 3D bits /// ///////////////////////////////// // Polygon rasterizer interface hng64_poly_renderer::hng64_poly_renderer(hng64_state& state) : poly_manager(state.machine()) , m_state(state) , m_colorBuffer3d(state.m_screen->visible_area().width(), state.m_screen->visible_area().height()) { const int32_t bufferSize = state.m_screen->visible_area().width() * state.m_screen->visible_area().height(); m_depthBuffer3d = std::make_unique(bufferSize); } // Hardware calls these '3d buffers' // They're only read during the startup check of fatfurwa. Z-buffer memory? Front buffer, back buffer? // They're definitely mirrored in the startup test, according to ElSemi // 30100000-3011ffff is framebuffer A0 // 30120000-3013ffff is framebuffer A1 // 30140000-3015ffff is ZBuffer A READ32_MEMBER(hng64_state::hng64_3d_1_r) { return m_3d_1[offset]; } WRITE32_MEMBER(hng64_state::hng64_3d_1_w) { COMBINE_DATA (&m_3d_1[offset]); } READ32_MEMBER(hng64_state::hng64_3d_2_r) { return m_3d_2[offset]; } WRITE32_MEMBER(hng64_state::hng64_3d_2_w) { COMBINE_DATA (&m_3d_2[offset]); } // The 3d 'display list' WRITE16_MEMBER(hng64_state::dl_w) { COMBINE_DATA(&m_dl[offset]); } WRITE32_MEMBER(hng64_state::dl_upload_w) { // Data is: // 00000b50 for the sams64 games // 00000f00 for everything else // TODO: different param for the two sams64 games, less FIFO to process? // This is written after the game uploads 16 packets, each 16 words long // We're assuming it to be a 'send to 3d hardware' trigger. // This can be called multiple times per frame (at least 2, as long as it gets the expected interrupt / status flags) g_profiler.start(PROFILER_USER1); for(int packetStart = 0; packetStart < 0x100; packetStart += 16) { // Send it off to the 3d subsystem. hng64_command3d(&m_dl[packetStart]); } // Schedule a small amount of time to let the 3d hardware rasterize the display buffer m_3dfifo_timer->adjust(m_maincpu->cycles_to_attotime(0x200*8)); g_profiler.stop(); } TIMER_CALLBACK_MEMBER(hng64_state::hng64_3dfifo_processed) { set_irq(0x0008); } WRITE32_MEMBER(hng64_state::dl_control_w) { // This could be a multiple display list thing, but the palette seems to be lost between lists? // Many games briefly set this to 0x4 on startup. Maybe there are 3 display lists? // The sams64 games briefly set this value to 0x0c00 on boot. Maybe there are 4 lists and they can be combined? if (data & 0x01) m_activeDisplayList = 0; else if (data & 0x02) m_activeDisplayList = 1; // printf("dl_control_w %08x %08x\n", data, mem_mask); // // if(data & 2) // swap buffers // { // clear3d(); // } // // printf("%02x\n",data); // // if(data & 1) // process DMA from 3d FIFO to framebuffer // if(data & 4) // reset buffer count } //////////////////// // 3d 'Functions' // //////////////////// void hng64_state::printPacket(const uint16_t* packet, int hex) { if (hex) { printf("Packet : %04x %04x 2:%04x %04x 4:%04x %04x 6:%04x %04x 8:%04x %04x 10:%04x %04x 12:%04x %04x 14:%04x %04x\n", packet[0], packet[1], packet[2], packet[3], packet[4], packet[5], packet[6], packet[7], packet[8], packet[9], packet[10], packet[11], packet[12], packet[13], packet[14], packet[15]); } else { printf("Packet : %04x %3.4f 2:%3.4f %3.4f 4:%3.4f %3.4f 6:%3.4f %3.4f 8:%3.4f %3.4f 10:%3.4f %3.4f 12:%3.4f %3.4f 14:%3.4f %3.4f\n", packet[0], uToF(packet[1] )*128, uToF(packet[2] )*128, uToF(packet[3] )*128, uToF(packet[4] )*128, uToF(packet[5] )*128, uToF(packet[6] )*128, uToF(packet[7] )*128, uToF(packet[8] )*128, uToF(packet[9] )*128, uToF(packet[10])*128, uToF(packet[11])*128, uToF(packet[12])*128, uToF(packet[13])*128, uToF(packet[14])*128, uToF(packet[15])*128); } } // Operation 0001 // Camera transformation. void hng64_state::setCameraTransformation(const uint16_t* packet) { /*////////////// // PACKET FORMAT // [0] - 0001 ... ID // [1] - xxxx ... Extrinsic camera matrix // [2] - xxxx ... Extrinsic camera matrix // [3] - xxxx ... Extrinsic camera matrix // [4] - xxxx ... Extrinsic camera matrix // [5] - xxxx ... Extrinsic camera matrix // [6] - xxxx ... Extrinsic camera matrix // [7] - xxxx ... Extrinsic camera matrix // [8] - xxxx ... Extrinsic camera matrix // [9] - xxxx ... Extrinsic camera matrix // [10] - xxxx ... Extrinsic camera matrix // [11] - xxxx ... Extrinsic camera matrix // [12] - xxxx ... Extrinsic camera matrix // [13] - ???? ... ? Flips per-frame during fatfurwa 'HNG64' // [14] - ???? ... ? Could be some floating-point values during buriki 'door run' // [15] - ???? ... ? Same as 13 & 14 ////////////*/ // CAMERA TRANSFORMATION MATRIX m_cameraMatrix[0] = uToF(packet[1]); m_cameraMatrix[4] = uToF(packet[2]); m_cameraMatrix[8] = uToF(packet[3]); m_cameraMatrix[3] = 0.0f; m_cameraMatrix[1] = uToF(packet[4]); m_cameraMatrix[5] = uToF(packet[5]); m_cameraMatrix[9] = uToF(packet[6]); m_cameraMatrix[7] = 0.0f; m_cameraMatrix[2] = uToF(packet[7]); m_cameraMatrix[6] = uToF(packet[8]); m_cameraMatrix[10] = uToF(packet[9]); m_cameraMatrix[11] = 0.0f; m_cameraMatrix[12] = uToF(packet[10]); m_cameraMatrix[13] = uToF(packet[11]); m_cameraMatrix[14] = uToF(packet[12]); m_cameraMatrix[15] = 1.0f; } // Operation 0010 // Lighting information void hng64_state::setLighting(const uint16_t* packet) { /*////////////// // PACKET FORMAT // [0] - 0010 ... ID // [1] - ???? ... ? Always zero // [2] - ???? ... ? Always zero // [3] - xxxx ... X light vector direction // [4] - xxxx ... Y light vector direction // [5] - xxxx ... Z light vector direction // [6] - ???? ... ? Seems to be another light vector ? // [7] - ???? ... ? Seems to be another light vector ? // [8] - ???? ... ? Seems to be another light vector ? // [9] - xxxx ... Strength according to sams64_2 (in combination with vector length) [0,512] // [10] - ???? ... ? Used in fatfurwa // [11] - ???? ... ? Used in fatfurwa // [12] - ???? ... ? Used in fatfurwa // [13] - ???? ... ? Used in fatfurwa // [14] - ???? ... ? Used in fatfurwa // [15] - ???? ... ? Used in fatfurwa ////////////*/ if (packet[1] != 0x0000) printf("ZOMG! packet[1] in setLighting function is non-zero!\n"); if (packet[2] != 0x0000) printf("ZOMG! packet[2] in setLighting function is non-zero!\n"); m_lightVector[0] = uToF(packet[3]); m_lightVector[1] = uToF(packet[4]); m_lightVector[2] = uToF(packet[5]); m_lightStrength = uToF(packet[9]); } // Operation 0011 // Palette / Model flags? void hng64_state::set3dFlags(const uint16_t* packet) { /*////////////// // PACKET FORMAT // [0] - 0011 ... ID // [1] - ???? ... // [2] - ???? ... // [3] - ???? ... // [4] - ???? ... // [5] - ???? ... // [6] - ???? ... // [7] - ???? ... // [8] - xx?? ... Palette offset & ?? // [9] - ???? ... ? Very much used - seem to bounce around when characters are on screen // [10] - ???? ... ? '' '' // [11] - ???? ... ? '' '' // [12] - ???? ... ? '' '' // [13] - ???? ... ? '' '' // [14] - ???? ... ? '' '' // [15] - ???? ... ? '' '' ////////////*/ m_paletteState3d = (packet[8] & 0xff00) >> 8; } // Operation 0012 // Projection Matrix. void hng64_state::setCameraProjectionMatrix(const uint16_t* packet) { /*////////////// // PACKET FORMAT // [0] - 0012 ... ID // [1] - ???? ... ? Contains a value in buriki's 'how to play' - probably a projection window/offset. // [2] - ???? ... ? Contains a value in buriki's 'how to play' - probably a projection window/offset. // [3] - ???? ... ? Contains a value // [4] - xxxx ... Camera projection near - confirmed by sams64_2 // [5] - xxxx ... Camera projection near - confirmed by sams64_2 // [6] - xxxx ... Camera projection near - confirmed by sams64_2 // [7] - xxxx ... Camera projection far (?) // [8] - xxxx ... Camera projection far (?) // [9] - xxxx ... Camera projection far (?) // [10] - xxxx ... Camera projection right - confirmed by sams64_2 // [11] - xxxx ... Camera projection left - confirmed by sams64_2 // [12] - xxxx ... Camera projection top - confirmed by sams64_2 // [13] - xxxx ... Camera projection bottom - confirmed by sams64_2 // [14] - ???? ... ? Gets data during buriki door-run // [15] - ???? ... ? Gets data during buriki door-run ////////////*/ // Heisted from GLFrustum - 6 parameters... const float left = uToF(packet[11]); const float right = uToF(packet[10]); const float top = uToF(packet[12]); const float bottom = uToF(packet[13]); // TODO: It's unclear how the 3 values combine to make a near clipping plane const float near_ = uToF(packet[6]) + (uToF(packet[6]) * uToF(packet[4])); const float far_ = 0.9f; // uToF(packet[9]) + (uToF(packet[9]) * uToF(packet[7])); m_projectionMatrix[0] = (2.0f*near_)/(right-left); m_projectionMatrix[1] = 0.0f; m_projectionMatrix[2] = 0.0f; m_projectionMatrix[3] = 0.0f; m_projectionMatrix[4] = 0.0f; m_projectionMatrix[5] = (2.0f*near_)/(top-bottom); m_projectionMatrix[6] = 0.0f; m_projectionMatrix[7] = 0.0f; m_projectionMatrix[8] = (right+left)/(right-left); m_projectionMatrix[9] = (top+bottom)/(top-bottom); m_projectionMatrix[10] = -((far_+near_)/(far_-near_)); m_projectionMatrix[11] = -1.0f; m_projectionMatrix[12] = 0.0f; m_projectionMatrix[13] = 0.0f; m_projectionMatrix[14] = -((2.0f*far_*near_)/(far_-near_)); m_projectionMatrix[15] = 0.0f; } // Operation 0100 // Polygon rasterization. void hng64_state::recoverPolygonBlock(const uint16_t* packet, int& numPolys) { //printPacket(packet, 1); /*////////////// // PACKET FORMAT // [0] - 0100 ... ID // [1] - ?--- ... Flags [?000 = ??? // 0?00 = ??? // 00?0 = ??? // 000? = ???] // [1] - -?-- ... Flags [?000 = ??? // 0?00 = ??? // 00?0 = ??? // 000x = Dynamic palette bit] // [1] - --?- ... Flags [?000 = ??? // 0?00 = ??? // 00?0 = ??? // 000? = ???] // [1] - ---? ... Flags [x000 = Apply lighting bit // 0?00 = ??? // 00?0 = ??? // 000? = ???] // [2] - xxxx ... offset into ROM // [3] - xxxx ... offset into ROM // [4] - xxxx ... Transformation matrix // [5] - xxxx ... Transformation matrix // [6] - xxxx ... Transformation matrix // [7] - xxxx ... Transformation matrix // [8] - xxxx ... Transformation matrix // [9] - xxxx ... Transformation matrix // [10] - xxxx ... Transformation matrix // [11] - xxxx ... Transformation matrix // [12] - xxxx ... Transformation matrix // [13] - xxxx ... Transformation matrix // [14] - xxxx ... Transformation matrix // [15] - xxxx ... Transformation matrix ////////////*/ float objectMatrix[16]; setIdentity(objectMatrix); ///////////////// // HEADER INFO // ///////////////// // THE OBJECT TRANSFORMATION MATRIX objectMatrix[8] = uToF(packet[7]); objectMatrix[4] = uToF(packet[8]); objectMatrix[0] = uToF(packet[9]); objectMatrix[3] = 0.0f; objectMatrix[9] = uToF(packet[10]); objectMatrix[5] = uToF(packet[11]); objectMatrix[1] = uToF(packet[12]); objectMatrix[7] = 0.0f; objectMatrix[10] = uToF(packet[13]); objectMatrix[6 ] = uToF(packet[14]); objectMatrix[2 ] = uToF(packet[15]); objectMatrix[11] = 0.0f; objectMatrix[12] = uToF(packet[4]); objectMatrix[13] = uToF(packet[5]); objectMatrix[14] = uToF(packet[6]); objectMatrix[15] = 1.0f; uint32_t size[4]; uint32_t address[4]; uint32_t megaOffset; polygon lastPoly = { 0 }; ////////////////////////////////////////////////////////// // EXTRACT DATA FROM THE ADDRESS POINTED TO IN THE FILE // ////////////////////////////////////////////////////////// /*////////////////////////////////////////////// // DIRECTLY-POINTED-TO FORMAT (7 words x 3 ROMs) // [0] - lower word of sub-address 1 // [1] - lower word of sub-address 2 // [2] - upper word of all sub-addresses // [3] - lower word of sub-address 3 // [4] - lower word of sub-address 4 // [5] - ???? always 0 ???? // [6] - number of chunks in sub-address 1 block // [7] - number of chunks in sub-address 2 block // [8] - ???? always 0 ???? // [9] - number of chunks in sub-address 3 block // [10] - number of chunks in sub-address 4 block // [11] - ? definitely used. // [12] - ? definitely used. // [13] - ? definitely used. // [14] - ? definitely used. // [15] - ???? always 0 ???? // [16] - ???? always 0 ???? // [17] - ???? always 0 ???? // [18] - ???? always 0 ???? // [19] - ???? always 0 ???? // [20] - ???? always 0 ???? //////////////////////////////////////////////*/ // 3d ROM Offset uint16_t* threeDRoms = m_vertsrom; uint32_t threeDOffset = (((uint32_t)packet[2]) << 16) | ((uint32_t)packet[3]); uint16_t* threeDPointer = &threeDRoms[threeDOffset * 3]; if (threeDOffset >= m_vertsrom_size) { printf("Strange geometry packet: (ignoring)\n"); printPacket(packet, 1); return; } #if 0 // Debug - ajg printf("%08x : ", threeDOffset*3*2); for (int k = 0; k < 7*3; k++) { printf("%04x ", threeDPointer[k]); if ((k % 3) == 2) printf(" "); } printf("\n"); #endif // There are 4 hunks per address. address[0] = threeDPointer[0]; address[1] = threeDPointer[1]; megaOffset = threeDPointer[2]; address[2] = threeDPointer[3]; address[3] = threeDPointer[4]; if (threeDPointer[5] != 0x0000) printf("ZOMG! 3dPointer[5] is non-zero!\n"); size[0] = threeDPointer[6]; size[1] = threeDPointer[7]; if (threeDPointer[8] != 0x0000) printf("ZOMG! 3dPointer[8] is non-zero!\n"); size[2] = threeDPointer[9]; size[3] = threeDPointer[10]; // ???? [11]; Used. // ???? [12]; Used. // ???? [13]; Used. // ???? [14]; Used. if (threeDPointer[15] != 0x0000) printf("ZOMG! 3dPointer[15] is non-zero!\n"); if (threeDPointer[16] != 0x0000) printf("ZOMG! 3dPointer[16] is non-zero!\n"); if (threeDPointer[17] != 0x0000) printf("ZOMG! 3dPointer[17] is non-zero!\n"); if (threeDPointer[18] != 0x0000) printf("ZOMG! 3dPointer[18] is non-zero!\n"); if (threeDPointer[19] != 0x0000) printf("ZOMG! 3dPointer[19] is non-zero!\n"); if (threeDPointer[20] != 0x0000) printf("ZOMG! 3dPointer[20] is non-zero!\n"); // Concatenate the megaOffset with the addresses address[0] |= (megaOffset << 16); address[1] |= (megaOffset << 16); address[2] |= (megaOffset << 16); address[3] |= (megaOffset << 16); // Debug - ajg //uint32_t tdColor = 0xff000000; //if (threeDPointer[14] & 0x0002) tdColor |= 0x00ff0000; //if (threeDPointer[14] & 0x0001) tdColor |= 0x0000ff00; //if (threeDPointer[14] & 0x0000) tdColor |= 0x000000ff; // For all 4 polygon chunks for (int k = 0; k < 4; k++) { uint16_t* chunkOffset = &threeDRoms[address[k] * 3]; for (int l = 0; l < size[k]; l++) { //////////////////////////////////////////// // GATHER A SINGLE TRIANGLE'S INFORMATION // //////////////////////////////////////////// // SINGLE POLY CHUNK FORMAT // [0] 0000 0000 cccc cccc 0 = always 0 | c = chunk type / format of data that follows (see below) // [1] u--l pppp pppp ssss u = unknown, always on for most games, on for the backgrounds only on sams64, l = low-res texture? p = palette? s = texture sheet (1024 x 1024 pages) // [2] S?XX *--- -YY# ---- S = use 4x4 sub-texture pages? ? = SNK logo roadedge / bbust2 / broken banners in xrally, XX = horizontal subtexture * = broken banners in xrally YY = vertical subtexture @ = broken banners in xrally // we currently use one of the palette bits to enable a different palette mode.. seems hacky... // looks like vertical / horizontal sub-pages might be 3 bits, not 2, ? could be enable bit for that.. // 'Welcome to South Africa' roadside banner on xrally | 000e 8c0d d870 or 0096 8c0d d870 (8c0d, d870 seems key 1000 1100 0000 1101 // 1101 1000 0111 0000 ) uint8_t chunkType = chunkOffset[0] & 0x00ff; // Debug - ajg if (chunkOffset[0] & 0xff00) { printf("Weird! The top byte of the chunkType has a value %04x!\n", chunkOffset[0]); continue; } // Syntactical simplification polygon& currentPoly = m_polys[numPolys]; // Debug - Colors polygons with certain flags bright blue! ajg currentPoly.debugColor = 0; //currentPoly.debugColor = tdColor; // Debug - ajg //printf("%d (%08x) : %04x %04x %04x\n", k, address[k]*3*2, chunkOffset[0], chunkOffset[1], chunkOffset[2]); //break; // TEXTURE // There may be more than just high & low res texture types, so I'm keeping texType as a uint8_t. */ if (chunkOffset[1] & 0x1000) currentPoly.texType = 0x1; else currentPoly.texType = 0x0; currentPoly.texPageSmall = (chunkOffset[2] & 0xc000)>>14; // Just a guess. currentPoly.texPageHorizOffset = (chunkOffset[2] & 0x3800) >> 11; currentPoly.texPageVertOffset = (chunkOffset[2] & 0x0070) >> 4; currentPoly.texIndex = chunkOffset[1] & 0x000f; // PALETTE currentPoly.palOffset = 0; currentPoly.palPageSize = 0x100; // FIXME: This isn't correct. // Buriki & Xrally need this line. Roads Edge needs it removed. // So instead we're looking for a bit that is on for XRally & Buriki, but noone else. if (m_3dregs[0x00/4] & 0x2000) { if (strcmp(machine().basename(), "roadedge")) currentPoly.palOffset += 0x800; } //uint16_t explicitPaletteValue0 = ((chunkOffset[?] & 0x????) >> ?) * 0x800; uint16_t explicitPaletteValue1 = ((chunkOffset[1] & 0x0f00) >> 8) * 0x080; uint16_t explicitPaletteValue2 = ((chunkOffset[1] & 0x00f0) >> 4) * 0x008; // The presence of 0x00f0 *probably* sets 0x10-sized palette addressing. if (explicitPaletteValue2) currentPoly.palPageSize = 0x10; // Apply the dynamic palette offset if its flag is set, otherwise stick with the fixed one if ((packet[1] & 0x0100)) { explicitPaletteValue1 = m_paletteState3d * 0x80; explicitPaletteValue2 = 0; // This is probably hiding somewhere in operation 0011 } currentPoly.palOffset += (explicitPaletteValue1 + explicitPaletteValue2); #if 0 if (((chunkOffset[2] & 0xc000) == 0x4000) && (m_screen->frame_number() & 1)) { // if (chunkOffset[2] == 0xd870) { currentPoly.debugColor = 0xffff0000; printf("%d (%08x) : %04x %04x %04x\n", k, address[k] * 3 * 2, chunkOffset[0], chunkOffset[1], chunkOffset[2]); } } #endif uint8_t chunkLength = 0; switch(chunkType) { /*///////////////////////// // CHUNK TYPE BITS - These are very likely incorrect. // x--- ---- - 1 = Has only 1 vertex (part of a triangle fan/strip) // -x-- ---- - // --x- ---- - // ---x ---- - // ---- x--- - // ---- -x-- - 1 = Has per-vert UVs // ---- --x- - // ---- ---x - 1 = Has per-vert normals /////////////////////////*/ // 33 word chunk, 3 vertices, per-vertex UVs & normals, per-face normal case 0x05: // 0000 0101 case 0x0f: // 0000 1111 for (int m = 0; m < 3; m++) { currentPoly.vert[m].worldCoords[0] = uToF(chunkOffset[3 + (9*m)]); currentPoly.vert[m].worldCoords[1] = uToF(chunkOffset[4 + (9*m)]); currentPoly.vert[m].worldCoords[2] = uToF(chunkOffset[5 + (9*m)]); currentPoly.vert[m].worldCoords[3] = 1.0f; currentPoly.n = 3; // chunkOffset[6 + (9*m)] is almost always 0080, but it's 0070 for the translucent globe in fatfurwa player select currentPoly.vert[m].texCoords[0] = uToF(chunkOffset[7 + (9*m)]); currentPoly.vert[m].texCoords[1] = uToF(chunkOffset[8 + (9*m)]); currentPoly.vert[m].texCoords[2] = 0.0f; currentPoly.vert[m].texCoords[3] = 1.0f; currentPoly.vert[m].normal[0] = uToF(chunkOffset[9 + (9*m)]); currentPoly.vert[m].normal[1] = uToF(chunkOffset[10 + (9*m)]); currentPoly.vert[m].normal[2] = uToF(chunkOffset[11 + (9*m)]); currentPoly.vert[m].normal[3] = 0.0f; } // Redundantly called, but it works... currentPoly.faceNormal[0] = uToF(chunkOffset[30]); currentPoly.faceNormal[1] = uToF(chunkOffset[31]); currentPoly.faceNormal[2] = uToF(chunkOffset[32]); currentPoly.faceNormal[3] = 0.0f; chunkLength = 33; break; // 24 word chunk, 3 vertices, per-vertex UVs case 0x04: // 0000 0100 case 0x0e: // 0000 1110 case 0x24: // 0010 0100 case 0x2e: // 0010 1110 for (int m = 0; m < 3; m++) { currentPoly.vert[m].worldCoords[0] = uToF(chunkOffset[3 + (6*m)]); currentPoly.vert[m].worldCoords[1] = uToF(chunkOffset[4 + (6*m)]); currentPoly.vert[m].worldCoords[2] = uToF(chunkOffset[5 + (6*m)]); currentPoly.vert[m].worldCoords[3] = 1.0f; currentPoly.n = 3; // chunkOffset[6 + (6*m)] is almost always 0080, but it's 0070 for the translucent globe in fatfurwa player select currentPoly.vert[m].texCoords[0] = uToF(chunkOffset[7 + (6*m)]); currentPoly.vert[m].texCoords[1] = uToF(chunkOffset[8 + (6*m)]); currentPoly.vert[m].texCoords[2] = 0.0f; currentPoly.vert[m].texCoords[3] = 1.0f; currentPoly.vert[m].normal[0] = uToF(chunkOffset[21]); currentPoly.vert[m].normal[1] = uToF(chunkOffset[22]); currentPoly.vert[m].normal[2] = uToF(chunkOffset[23]); currentPoly.vert[m].normal[3] = 0.0f; } // Redundantly called, but it works... currentPoly.faceNormal[0] = currentPoly.vert[2].normal[0]; currentPoly.faceNormal[1] = currentPoly.vert[2].normal[1]; currentPoly.faceNormal[2] = currentPoly.vert[2].normal[2]; currentPoly.faceNormal[3] = 0.0f; chunkLength = 24; break; // 15 word chunk, 1 vertex, per-vertex UVs & normals, face normal case 0x87: // 1000 0111 case 0x97: // 1001 0111 case 0xd7: // 1101 0111 case 0xc7: // 1100 0111 // Copy over the proper vertices from the previous triangle... memcpy(¤tPoly.vert[1], &lastPoly.vert[0], sizeof(polyVert)); memcpy(¤tPoly.vert[2], &lastPoly.vert[2], sizeof(polyVert)); // Fill in the appropriate data... currentPoly.vert[0].worldCoords[0] = uToF(chunkOffset[3]); currentPoly.vert[0].worldCoords[1] = uToF(chunkOffset[4]); currentPoly.vert[0].worldCoords[2] = uToF(chunkOffset[5]); currentPoly.vert[0].worldCoords[3] = 1.0f; currentPoly.n = 3; // chunkOffset[6] is almost always 0080, but it's 0070 for the translucent globe in fatfurwa player select currentPoly.vert[0].texCoords[0] = uToF(chunkOffset[7]); currentPoly.vert[0].texCoords[1] = uToF(chunkOffset[8]); currentPoly.vert[0].texCoords[2] = 0.0f; currentPoly.vert[0].texCoords[3] = 1.0f; currentPoly.vert[0].normal[0] = uToF(chunkOffset[9]); currentPoly.vert[0].normal[1] = uToF(chunkOffset[10]); currentPoly.vert[0].normal[2] = uToF(chunkOffset[11]); currentPoly.vert[0].normal[3] = 0.0f; currentPoly.faceNormal[0] = uToF(chunkOffset[12]); currentPoly.faceNormal[1] = uToF(chunkOffset[13]); currentPoly.faceNormal[2] = uToF(chunkOffset[14]); currentPoly.faceNormal[3] = 0.0f; chunkLength = 15; break; // 12 word chunk, 1 vertex, per-vertex UVs case 0x86: // 1000 0110 case 0x96: // 1001 0110 case 0xb6: // 1011 0110 case 0xc6: // 1100 0110 case 0xd6: // 1101 0110 // Copy over the proper vertices from the previous triangle... memcpy(¤tPoly.vert[1], &lastPoly.vert[0], sizeof(polyVert)); memcpy(¤tPoly.vert[2], &lastPoly.vert[2], sizeof(polyVert)); currentPoly.vert[0].worldCoords[0] = uToF(chunkOffset[3]); currentPoly.vert[0].worldCoords[1] = uToF(chunkOffset[4]); currentPoly.vert[0].worldCoords[2] = uToF(chunkOffset[5]); currentPoly.vert[0].worldCoords[3] = 1.0f; currentPoly.n = 3; // chunkOffset[6] is almost always 0080, but it's 0070 for the translucent globe in fatfurwa player select currentPoly.vert[0].texCoords[0] = uToF(chunkOffset[7]); currentPoly.vert[0].texCoords[1] = uToF(chunkOffset[8]); currentPoly.vert[0].texCoords[2] = 0.0f; currentPoly.vert[0].texCoords[3] = 1.0f; // This normal could be right, but I'm not entirely sure - there is no normal in the 18 bytes! currentPoly.vert[0].normal[0] = lastPoly.faceNormal[0]; currentPoly.vert[0].normal[1] = lastPoly.faceNormal[1]; currentPoly.vert[0].normal[2] = lastPoly.faceNormal[2]; currentPoly.vert[0].normal[3] = lastPoly.faceNormal[3]; currentPoly.faceNormal[0] = lastPoly.faceNormal[0]; currentPoly.faceNormal[1] = lastPoly.faceNormal[1]; currentPoly.faceNormal[2] = lastPoly.faceNormal[2]; currentPoly.faceNormal[3] = lastPoly.faceNormal[3]; // TODO: I'm not reading 3 necessary words here (maybe face normal) #if 0 // DEBUG printf("0x?6 : %08x (%d/%d)\n", address[k]*3*2, l, size[k]-1); for (int m = 0; m < 13; m++) printf("%04x ", chunkOffset[m]); printf("\n"); for (int m = 0; m < 13; m++) printf("%3.4f ", uToF(chunkOffset[m])); printf("\n\n"); #endif chunkLength = 12; break; default: printf("UNKNOWN geometry CHUNK TYPE : %02x\n", chunkType); chunkLength = 0; break; } currentPoly.visible = 1; // Backup the last polygon (for triangle fans [strips?]) memcpy(&lastPoly, ¤tPoly, sizeof(polygon)); //////////////////////////////////// // Project and clip // //////////////////////////////////// // Perform the world transformations... // TODO: We can eliminate this step with a matrix stack (maybe necessary?) // Note: fatfurwa's helicopter tracking in scene 3 of its intro shows one of these matrices isn't quite correct setIdentity(m_modelViewMatrix); if (m_mcu_type != SAMSHO_MCU) { // The sams64 games transform the geometry in front of a stationary camera. // This is fine in sams64_2, since it never calls the 'camera transformation' function // (thus using the identity matrix for this transform), but sams64 calls the // camera transformation function with rotation values. // It remains to be seen what those might do... matmul4(m_modelViewMatrix, m_modelViewMatrix, m_cameraMatrix); } matmul4(m_modelViewMatrix, m_modelViewMatrix, objectMatrix); // LIGHTING if (packet[1] & 0x0008 && m_lightStrength > 0.0f) { for (int v = 0; v < 3; v++) { float transformedNormal[4]; vecmatmul4(transformedNormal, objectMatrix, currentPoly.vert[v].normal); normalize(transformedNormal); normalize(m_lightVector); float intensity = vecDotProduct(transformedNormal, m_lightVector) * -1.0f; intensity = (intensity <= 0.0f) ? (0.0f) : (intensity); intensity *= m_lightStrength * 128.0f; // Turns 0x0100 into 1.0 intensity *= 128.0; // Maps intensity to the range [0.0, 2.0] if (intensity >= 255.0f) intensity = 255.0f; currentPoly.vert[v].light[0] = intensity; currentPoly.vert[v].light[1] = intensity; currentPoly.vert[v].light[2] = intensity; } } else { // Just clear out the light values for (int v = 0; v < 3; v++) { currentPoly.vert[v].light[0] = 0; currentPoly.vert[v].light[1] = 0; currentPoly.vert[v].light[2] = 0; } } // BACKFACE CULL // // (empirical evidence seems to show the hng64 hardware does not backface cull) // #if 0 float cullRay[4]; float cullNorm[4]; // Cast a ray out of the camera towards the polygon's point in eyespace. vecmatmul4(cullRay, modelViewMatrix, currentPoly.vert[0].worldCoords); normalize(cullRay); // Dot product that with the normal to see if you're negative... vecmatmul4(cullNorm, modelViewMatrix, currentPoly.faceNormal); const float backfaceCullResult = vecDotProduct(cullRay, cullNorm); if (backfaceCullResult < 0.0f) currentPoly.visible = 1; else currentPoly.visible = 0; #endif // BEHIND-THE-CAMERA CULL // float cullRay[4]; vecmatmul4(cullRay, m_modelViewMatrix, currentPoly.vert[0].worldCoords); if (cullRay[2] > 0.0f) // Camera is pointing down -Z { currentPoly.visible = 0; } // TRANSFORM THE TRIANGLE INTO HOMOGENEOUS SCREEN SPACE // if (currentPoly.visible) { hng64_clip_vertex clipVerts[10]; // Transform and project each vertex into pre-divided homogeneous coordinates for (int m = 0; m < currentPoly.n; m++) { float eyeCoords[4]; // World coordinates transformed by the modelViewMatrix vecmatmul4(eyeCoords, m_modelViewMatrix, currentPoly.vert[m].worldCoords); vecmatmul4(currentPoly.vert[m].clipCoords, m_projectionMatrix, eyeCoords); clipVerts[m].x = currentPoly.vert[m].clipCoords[0]; clipVerts[m].y = currentPoly.vert[m].clipCoords[1]; clipVerts[m].z = currentPoly.vert[m].clipCoords[2]; clipVerts[m].w = currentPoly.vert[m].clipCoords[3]; clipVerts[m].p[0] = currentPoly.vert[m].texCoords[0]; clipVerts[m].p[1] = currentPoly.vert[m].texCoords[1]; clipVerts[m].p[2] = currentPoly.vert[m].light[0]; clipVerts[m].p[3] = currentPoly.vert[m].light[1]; clipVerts[m].p[4] = currentPoly.vert[m].light[2]; } if (currentPoly.visible) { // Clip against all edges of the view frustum int num_vertices = frustum_clip_all(clipVerts, currentPoly.n, clipVerts); // Copy the results of currentPoly.n = num_vertices; for (int m = 0; m < num_vertices; m++) { currentPoly.vert[m].clipCoords[0] = clipVerts[m].x; currentPoly.vert[m].clipCoords[1] = clipVerts[m].y; currentPoly.vert[m].clipCoords[2] = clipVerts[m].z; currentPoly.vert[m].clipCoords[3] = clipVerts[m].w; currentPoly.vert[m].texCoords[0] = clipVerts[m].p[0]; currentPoly.vert[m].texCoords[1] = clipVerts[m].p[1]; currentPoly.vert[m].light[0] = clipVerts[m].p[2]; currentPoly.vert[m].light[1] = clipVerts[m].p[3]; currentPoly.vert[m].light[2] = clipVerts[m].p[4]; } const rectangle& visarea = m_screen->visible_area(); for (int m = 0; m < currentPoly.n; m++) { // Convert into normalized device coordinates... float ndCoords[4]; // Normalized device coordinates/clipCoordinates (x/w, y/w, z/w) ndCoords[0] = currentPoly.vert[m].clipCoords[0] / currentPoly.vert[m].clipCoords[3]; ndCoords[1] = currentPoly.vert[m].clipCoords[1] / currentPoly.vert[m].clipCoords[3]; ndCoords[2] = currentPoly.vert[m].clipCoords[2] / currentPoly.vert[m].clipCoords[3]; ndCoords[3] = currentPoly.vert[m].clipCoords[3]; // Final pixel values are garnered here : float windowCoords[4]; // Mapped ndCoordinates to screen space windowCoords[0] = (ndCoords[0]+1.0f) * ((float)(visarea.max_x) / 2.0f) + 0.0f; windowCoords[1] = (ndCoords[1]+1.0f) * ((float)(visarea.max_y) / 2.0f) + 0.0f; windowCoords[2] = (ndCoords[2]+1.0f) * 0.5f; // Flip Y windowCoords[1] = (float)visarea.max_y - windowCoords[1]; // Store the points in a list for later use... currentPoly.vert[m].clipCoords[0] = windowCoords[0]; currentPoly.vert[m].clipCoords[1] = windowCoords[1]; currentPoly.vert[m].clipCoords[2] = windowCoords[2]; currentPoly.vert[m].clipCoords[3] = ndCoords[3]; } } } // Advance to the next polygon chunk... chunkOffset += chunkLength; numPolys++; } } } // note 0x0102 packets are only 8 words, it appears they can be in either the upper or lower half of the 16 word packet. // We currently only draw 0x0102 packets where both halves contain 0x0102 (2 calls), but this causes graphics to vanish in // xrally because in some cases the 0x0102 packet only exists in the upper or lower half with another value (often 0x0000 - NOP) in the other. // If we also treat (0x0000 - NOP) as 8 word instead of 16 so that we can access a 0x0102 in the 2nd half of the 16 word packet // then we end up with other invalid packets in the 2nd half which should be ignored. // This would suggest our processing if flawed in other ways, or there is something else to indicate packet length. void hng64_state::hng64_command3d(const uint16_t* packet) { int numPolys = 0; //printf("packet type : %04x %04x|%04x %04x|%04x %04x|%04x %04x | %04x %04x %04x %04x %04x %04x %04x %04x\n", packet[0],packet[1],packet[2],packet[3],packet[4],packet[5],packet[6],packet[7], packet[8], packet[9], packet[10], packet[11], packet[12], packet[13], packet[14], packet[15]); switch (packet[0]) { case 0x0000: // Appears to be a NOP. break; case 0x0001: // Camera transformation. setCameraTransformation(packet); break; case 0x0010: // Lighting information. setLighting(packet); break; case 0x0011: // Palette / Model flags? set3dFlags(packet); break; case 0x0012: // Projection Matrix setCameraProjectionMatrix(packet); break; case 0x0100: case 0x0101: // Geometry with full transformations recoverPolygonBlock(packet, numPolys); break; case 0x0102: // Geometry with only translation // HACK. Give up on strange calls to 0102. if (packet[8] != 0x0102) { // It appears as though packet[7] might hold the magic # // Almost looks like there is a chain mode for these guys. Same for 0101? // printf("WARNING: "); printPacket(packet, 1); break; } // Split the packet and call recoverPolygonBlock on each half. uint16_t miniPacket[16]; memset(miniPacket, 0, sizeof(uint16_t)*16); for (int i = 0; i < 7; i++) miniPacket[i] = packet[i]; miniPacket[7] = 0x7fff; miniPacket[11] = 0x7fff; miniPacket[15] = 0x7fff; recoverPolygonBlock(miniPacket, numPolys); memset(miniPacket, 0, sizeof(uint16_t)*16); for (int i = 0; i < 7; i++) miniPacket[i] = packet[i+8]; miniPacket[7] = 0x7fff; miniPacket[11] = 0x7fff; miniPacket[15] = 0x7fff; recoverPolygonBlock(miniPacket, numPolys); break; case 0x1000: // Unknown: Some sort of global flags? //printPacket(packet, 1); printf("\n"); break; case 0x1001: // Unknown: Some sort of global flags? Almost always comes in a group of 4 with an index [0,3]. //printPacket(packet, 1); break; default: printf("HNG64: Unknown 3d command %04x.\n", packet[0]); break; } // If there are polygons, rasterize them into the display buffer for (int i = 0; i < numPolys; i++) { if (m_polys[i].visible) { m_poly_renderer->drawShaded(&m_polys[i]); } } m_poly_renderer->wait(); } void hng64_state::clear3d() { // Reset the buffers... const rectangle& visarea = m_screen->visible_area(); for (int i = 0; i < (visarea.max_x)*(visarea.max_y); i++) { m_poly_renderer->depthBuffer3d()[i] = 100.0f; } // Clear the 3d rasterizer buffer m_poly_renderer->colorBuffer3d().fill(0x00000000, m_screen->visible_area()); // Set some matrices to the identity... setIdentity(m_projectionMatrix); setIdentity(m_modelViewMatrix); setIdentity(m_cameraMatrix); } /* 3D/framebuffer video registers * ------------------------------ * * uint32_t | Bits | Use * | 3322 2222 2222 1111 1111 11 | * -------+-1098-7654-3210-9876-5432-1098-7654-3210-+---------------- * 0 | ---- --x- ---- ---- ---- ---- ---- ---- | Reads in Fatal Fury WA, if on then there isn't a 3d refresh (busy flag?). * 0 | ---- ---x ---- ---- ---- ---- ---- ---- | set at POST/service modes, almost likely fb disable * 0 | ???? ???? ???? ???? ccc? ???? ???? ???? | framebuffer color base, 0x311800 in Fatal Fury WA, 0x313800 in Buriki One * 1 | | * 2 | ???? ???? ???? ???? ???? ???? ???? ???? | camera / framebuffer global x/y? Actively used by Samurai Shodown 64 2 * 3 | ---- --?x ---- ---- ---- ---- ---- ---- | unknown, unsetted by Buriki One and set by Fatal Fury WA, buffering mode? * 4-11 | ---- ???? ---- ???? ---- ???? ---- ???? | Table filled with 0x0? data * */ ///////////////////// // 3D UTILITY CODE // ///////////////////// // 4x4 matrix multiplication void hng64_state::matmul4(float *product, const float *a, const float *b) { for (int i = 0; i < 4; i++) { const float ai0 = a[0 + i]; const float ai1 = a[4 + i]; const float ai2 = a[8 + i]; const float ai3 = a[12 + i]; product[0 + i] = ai0 * b[0 ] + ai1 * b[1 ] + ai2 * b[2 ] + ai3 * b[3 ]; product[4 + i] = ai0 * b[4 ] + ai1 * b[5 ] + ai2 * b[6 ] + ai3 * b[7 ]; product[8 + i] = ai0 * b[8 ] + ai1 * b[9 ] + ai2 * b[10] + ai3 * b[11]; product[12 + i] = ai0 * b[12] + ai1 * b[13] + ai2 * b[14] + ai3 * b[15]; } } // vector by 4x4 matrix multiply void hng64_state::vecmatmul4(float *product, const float *a, const float *b) { const float& bi0 = b[0]; const float& bi1 = b[1]; const float& bi2 = b[2]; const float& bi3 = b[3]; product[0] = bi0 * a[0] + bi1 * a[4] + bi2 * a[8 ] + bi3 * a[12]; product[1] = bi0 * a[1] + bi1 * a[5] + bi2 * a[9 ] + bi3 * a[13]; product[2] = bi0 * a[2] + bi1 * a[6] + bi2 * a[10] + bi3 * a[14]; product[3] = bi0 * a[3] + bi1 * a[7] + bi2 * a[11] + bi3 * a[15]; } float hng64_state::vecDotProduct(const float *a, const float *b) { return ((a[0]*b[0]) + (a[1]*b[1]) + (a[2]*b[2])); } void hng64_state::setIdentity(float *matrix) { for (int i = 0; i < 16; i++) { matrix[i] = 0.0f; } matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0f; } float hng64_state::uToF(uint16_t input) { float retVal; retVal = (float)((int16_t)input) / 32768.0f; return retVal; #if 0 if ((int16_t)input < 0) retVal = (float)((int16_t)input) / 32768.0f; else retVal = (float)((int16_t)input) / 32767.0f; #endif } void hng64_state::normalize(float* x) { double l2 = (x[0]*x[0]) + (x[1]*x[1]) + (x[2]*x[2]); double l = sqrt(l2); x[0] = (float)(x[0] / l); x[1] = (float)(x[1] / l); x[2] = (float)(x[2] / l); } //////////////////////////////// // POLYGON RASTERIZATION CODE // //////////////////////////////// void hng64_poly_renderer::render_scanline(int32_t scanline, const extent_t& extent, const hng64_poly_data& renderData, int threadid) { // Pull the parameters out of the extent structure float z = extent.param[0].start; float w = extent.param[1].start; float lightR = extent.param[2].start; float lightG = extent.param[3].start; float lightB = extent.param[4].start; float s = extent.param[5].start; float t = extent.param[6].start; const float dz = extent.param[0].dpdx; const float dw = extent.param[1].dpdx; const float dlightR = extent.param[2].dpdx; const float dlightG = extent.param[3].dpdx; const float dlightB = extent.param[4].dpdx; const float ds = extent.param[5].dpdx; const float dt = extent.param[6].dpdx; // Pointers to the pixel buffers uint32_t* colorBuffer = &m_colorBuffer3d.pix32(scanline, extent.startx); float* depthBuffer = &m_depthBuffer3d[(scanline * m_state.m_screen->visible_area().width()) + extent.startx]; const uint8_t *textureOffset = &m_state.m_texturerom[renderData.texIndex * 1024 * 1024]; // Step over each pixel in the horizontal span for(int x = extent.startx; x < extent.stopx; x++) { if (z < *depthBuffer) { // Multiply back through by w for everything that was interpolated perspective-correctly const float sCorrect = s / w; const float tCorrect = t / w; const float rCorrect = lightR / w; const float gCorrect = lightG / w; const float bCorrect = lightB / w; if ((renderData.debugColor & 0xff000000) == 0x01000000) { // ST color mode *colorBuffer = rgb_t(255, (uint8_t)(sCorrect*255.0f), (uint8_t)(tCorrect*255.0f), (uint8_t)(0)); } else if ((renderData.debugColor & 0xff000000) == 0x02000000) { // Lighting only *colorBuffer = rgb_t(255, (uint8_t)rCorrect, (uint8_t)gCorrect, (uint8_t)bCorrect); } else if ((renderData.debugColor & 0xff000000) == 0xff000000) { // Debug color mode *colorBuffer = renderData.debugColor; } else { float textureS = 0.0f; float textureT = 0.0f; // Standard & Half-Res textures if (renderData.texType == 0x0) { textureS = sCorrect * 1024.0f; textureT = tCorrect * 1024.0f; } else if (renderData.texType == 0x1) { textureS = sCorrect * 512.0f; textureT = tCorrect * 512.0f; } // Small-Page textures if (renderData.texPageSmall == 2) { textureT = fmod(textureT, 256.0f); textureS = fmod(textureS, 256.0f); textureT += (256.0f * (renderData.texPageHorizOffset>>1)); textureS += (256.0f * (renderData.texPageVertOffset>>1)); } else if (renderData.texPageSmall == 3) { textureT = fmod(textureT, 128.0f); textureS = fmod(textureS, 128.0f); textureT += (128.0f * (renderData.texPageHorizOffset>>0)); textureS += (128.0f * (renderData.texPageVertOffset>>0)); } uint8_t paletteEntry = textureOffset[((int)textureS)*1024 + (int)textureT]; // Naive Alpha Implementation (?) - don't draw if you're at texture index 0... if (paletteEntry != 0) { // The color out of the texture paletteEntry %= renderData.palPageSize; rgb_t color = m_state.m_palette->pen(renderData.palOffset + paletteEntry); // Apply the lighting float rIntensity = rCorrect / 255.0f; float gIntensity = gCorrect / 255.0f; float bIntensity = bCorrect / 255.0f; float red = color.r() * rIntensity; float green = color.g() * gIntensity; float blue = color.b() * bIntensity; // Clamp and finalize red = color.r() + red; green = color.g() + green; blue = color.b() + blue; if (red >= 255) red = 255; if (green >= 255) green = 255; if (blue >= 255) blue = 255; color = rgb_t(255, (uint8_t)red, (uint8_t)green, (uint8_t)blue); *colorBuffer = color; *depthBuffer = z; } } } z += dz; w += dw; lightR += dlightR; lightG += dlightG; lightB += dlightB; s += ds; t += dt; colorBuffer++; depthBuffer++; } } void hng64_poly_renderer::drawShaded(polygon *p) { // Polygon information for the rasterizer hng64_poly_data rOptions; rOptions.texType = p->texType; rOptions.texIndex = p->texIndex; rOptions.palOffset = p->palOffset; rOptions.palPageSize = p->palPageSize; rOptions.debugColor = p->debugColor; rOptions.texPageSmall = p->texPageSmall; rOptions.texPageHorizOffset = p->texPageHorizOffset; rOptions.texPageVertOffset = p->texPageVertOffset; // The perspective-correct texture divide... // Note: There is a very good chance the HNG64 hardware does not do perspective-correct texture-mapping - explore for (int j = 0; j < p->n; j++) { p->vert[j].clipCoords[3] = 1.0f / p->vert[j].clipCoords[3]; p->vert[j].light[0] = p->vert[j].light[0] * p->vert[j].clipCoords[3]; p->vert[j].light[1] = p->vert[j].light[1] * p->vert[j].clipCoords[3]; p->vert[j].light[2] = p->vert[j].light[2] * p->vert[j].clipCoords[3]; p->vert[j].texCoords[0] = p->vert[j].texCoords[0] * p->vert[j].clipCoords[3]; p->vert[j].texCoords[1] = p->vert[j].texCoords[1] * p->vert[j].clipCoords[3]; } // Rasterize the triangles for (int j = 1; j < p->n-1; j++) { // Build some MAME rasterizer vertices from the hng64 vertices vertex_t pVert[3]; const polyVert& pv0 = p->vert[0]; pVert[0].x = pv0.clipCoords[0]; pVert[0].y = pv0.clipCoords[1]; pVert[0].p[0] = pv0.clipCoords[2]; pVert[0].p[1] = pv0.clipCoords[3]; pVert[0].p[2] = pv0.light[0]; pVert[0].p[3] = pv0.light[1]; pVert[0].p[4] = pv0.light[2]; pVert[0].p[5] = pv0.texCoords[0]; pVert[0].p[6] = pv0.texCoords[1]; const polyVert& pvj = p->vert[j]; pVert[1].x = pvj.clipCoords[0]; pVert[1].y = pvj.clipCoords[1]; pVert[1].p[0] = pvj.clipCoords[2]; pVert[1].p[1] = pvj.clipCoords[3]; pVert[1].p[2] = pvj.light[0]; pVert[1].p[3] = pvj.light[1]; pVert[1].p[4] = pvj.light[2]; pVert[1].p[5] = pvj.texCoords[0]; pVert[1].p[6] = pvj.texCoords[1]; const polyVert& pvjp1 = p->vert[j+1]; pVert[2].x = pvjp1.clipCoords[0]; pVert[2].y = pvjp1.clipCoords[1]; pVert[2].p[0] = pvjp1.clipCoords[2]; pVert[2].p[1] = pvjp1.clipCoords[3]; pVert[2].p[2] = pvjp1.light[0]; pVert[2].p[3] = pvjp1.light[1]; pVert[2].p[4] = pvjp1.light[2]; pVert[2].p[5] = pvjp1.texCoords[0]; pVert[2].p[6] = pvjp1.texCoords[1]; // Pass the render data into the rasterizer hng64_poly_data& renderData = object_data_alloc(); renderData = rOptions; const rectangle& visibleArea = m_state.m_screen->visible_area(); render_triangle(visibleArea, render_delegate(&hng64_poly_renderer::render_scanline, this), 7, pVert[0], pVert[1], pVert[2]); } }