/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** mtrl.cpp: Material (MTRL) and custom material (CMTL) classes Primary Author: ****** Review Status: REVIEWED - any changes to this file must be reviewed! ***************************************************************************/ #include "soc.h" ASSERTNAME RTCLASS(MTRL) RTCLASS(CMTL) // REVIEW *****: kiclrBaseDefault and kcclrDefault are palette-specific const byte kiclrBaseDefault = 15; // base index of default color const byte kcclrDefault = 15; // count of shades in default color const br_ufraction kbrufKaDefault = BR_UFRACTION(0.10); const br_ufraction kbrufKdDefault = BR_UFRACTION(0.60); const br_ufraction kbrufKsDefault = BR_UFRACTION(0.60); const BRS krPowerDefault = BR_SCALAR(50); const byte kbOpaque = 0xff; PTMAP MTRL::_ptmapShadeTable = pvNil; // shade table for all MTRLs /*************************************************************************** Call this function to assign the global shade table. It is read from the given chunk. ***************************************************************************/ bool MTRL::FSetShadeTable(PCFL pcfl, CTG ctg, CNO cno) { AssertPo(pcfl, 0); ReleasePpo(&_ptmapShadeTable); _ptmapShadeTable = TMAP::PtmapRead(pcfl, ctg, cno); return (pvNil != _ptmapShadeTable); } /*************************************************************************** Create a new solid-color material ***************************************************************************/ PMTRL MTRL::PmtrlNew(long iclrBase, long cclr) { if (ivNil != iclrBase) AssertIn(iclrBase, 0, kbMax); if (ivNil != cclr) AssertIn(cclr, 0, kbMax - iclrBase); PMTRL pmtrl; pmtrl = NewObj MTRL; if (pvNil == pmtrl) return pvNil; // An arbitrary 4-character string is passed to BrMaterialAllocate (to // be stored in a string pointed to by _pbmtl->identifier). The // contents of the string are then replaced by the "this" pointer. pmtrl->_pbmtl = BrMaterialAllocate("1234"); if (pvNil == pmtrl->_pbmtl) { ReleasePpo(&pmtrl); return pvNil; } CopyPb(&pmtrl, pmtrl->_pbmtl->identifier, size(long)); pmtrl->_pbmtl->ka = kbrufKaDefault; pmtrl->_pbmtl->kd = kbrufKdDefault; pmtrl->_pbmtl->ks = kbrufKsDefault; pmtrl->_pbmtl->power = krPowerDefault; if (ivNil == iclrBase) pmtrl->_pbmtl->index_base = kiclrBaseDefault; else pmtrl->_pbmtl->index_base = (byte)iclrBase; if (ivNil == cclr) pmtrl->_pbmtl->index_range = kcclrDefault; else pmtrl->_pbmtl->index_range = (byte)cclr; pmtrl->_pbmtl->opacity = kbOpaque; // all socrates objects are opaque pmtrl->_pbmtl->flags = BR_MATF_LIGHT | BR_MATF_GOURAUD; BrMaterialAdd(pmtrl->_pbmtl); AssertPo(pmtrl, 0); return pmtrl; } /*************************************************************************** A PFNRPO to read MTRL objects. ***************************************************************************/ bool MTRL::FReadMtrl(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck, PBACO *ppbaco, long *pcb) { AssertPo(pcrf, 0); AssertPo(pblck, 0); AssertNilOrVarMem(ppbaco); AssertVarMem(pcb); PMTRL pmtrl; *pcb = size(MTRL); if (pvNil == ppbaco) return fTrue; pmtrl = NewObj MTRL; if (pvNil == pmtrl || !pmtrl->_FInit(pcrf, ctg, cno)) { TrashVar(ppbaco); TrashVar(pcb); ReleasePpo(&pmtrl); return fFalse; } AssertPo(pmtrl, 0); *ppbaco = pmtrl; return fTrue; } /*************************************************************************** Read the given MTRL chunk from file ***************************************************************************/ bool MTRL::_FInit(PCRF pcrf, CTG ctg, CNO cno) { AssertBaseThis(0); AssertPo(pcrf, 0); PCFL pcfl = pcrf->Pcfl(); BLCK blck; MTRLF mtrlf; KID kid; MTRL *pmtrlThis = this; // to get MTRL from BMTL PTMAP ptmap = pvNil; if (!pcfl->FFind(ctg, cno, &blck) || !blck.FUnpackData()) return fFalse; if (blck.Cb() < size(MTRLF)) return fFalse; if (!blck.FReadRgb(&mtrlf, size(MTRLF), 0)) return fFalse; if (kboOther == mtrlf.bo) SwapBytesBom(&mtrlf, kbomMtrlf); Assert(kboCur == mtrlf.bo, "bad MTRLF"); // An arbitrary 4-character string is passed to BrMaterialAllocate (to // be stored in a string pointed to by _pbmtl->identifier). The // contents of the string are then replaced by the "this" pointer. _pbmtl = BrMaterialAllocate("1234"); if (pvNil == _pbmtl) return fFalse; CopyPb(&pmtrlThis, _pbmtl->identifier, size(long)); _pbmtl->colour = mtrlf.brc; _pbmtl->ka = mtrlf.brufKa; _pbmtl->kd = mtrlf.brufKd; // Note: for socrates, mtrlf.brufKs should be zero _pbmtl->ks = mtrlf.brufKs; _pbmtl->power = mtrlf.rPower; _pbmtl->index_base = mtrlf.bIndexBase; _pbmtl->index_range = mtrlf.cIndexRange; _pbmtl->opacity = kbOpaque; // all socrates objects are opaque // REVIEW *****: also set the BR_MATF_PRELIT flag to use prelit models _pbmtl->flags = BR_MATF_LIGHT | BR_MATF_SMOOTH; // now read texture map, if any if (pcfl->FGetKidChidCtg(ctg, cno, 0, kctgTmap, &kid)) { ptmap = (PTMAP)pcrf->PbacoFetch(kid.cki.ctg, kid.cki.cno, TMAP::FReadTmap); if (pvNil == ptmap) return fFalse; _pbmtl->colour_map = ptmap->Pbpmp(); Assert((PTMAP)_pbmtl->colour_map->identifier == ptmap, "lost tmap!"); AssertPo(_ptmapShadeTable, 0); _pbmtl->index_shade = _ptmapShadeTable->Pbpmp(); _pbmtl->flags |= BR_MATF_MAP_COLOUR; _pbmtl->index_base = 0; _pbmtl->index_range = _ptmapShadeTable->Pbpmp()->height - 1; /* Look for a texture transform for the MTRL */ if (pcfl->FGetKidChidCtg(ctg, cno, 0, kctgTxxf, &kid)) { TXXFF txxff; if (!pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck) || !blck.FUnpackData()) goto LFail; if (blck.Cb() < size(TXXFF)) goto LFail; if (!blck.FReadRgb(&txxff, size(TXXFF), 0)) goto LFail; if (kboCur != txxff.bo) SwapBytesBom(&txxff, kbomTxxff); Assert(kboCur == txxff.bo, "bad TXXFF"); _pbmtl->map_transform = txxff.bmat23; } } BrMaterialAdd(_pbmtl); AssertThis(0); return fTrue; LFail: /* REVIEW ***** (peted): Only the code that I added uses this LFail case. It's my opinion that any API which can fail should clean up after itself. It happens that in the case of this MTRL class, when the caller releases this instance, the TMAP and BMTL are freed anyway, but I don't think that it's good to count on that */ ReleasePpo(&ptmap); _pbmtl->colour_map = pvNil; BrMaterialFree(_pbmtl); _pbmtl = pvNil; return fFalse; } /*************************************************************************** Read a PIX and build a PMTRL from it ***************************************************************************/ PMTRL MTRL::PmtrlNewFromPix(PFNI pfni) { AssertPo(pfni, ffniFile); STN stn; PMTRL pmtrl; PBMTL pbmtl; PTMAP ptmap; pmtrl = NewObj MTRL; if (pvNil == pmtrl) goto LFail; // An arbitrary 4-character string is passed to BrMaterialAllocate (to // be stored in a string pointed to by _pbmtl->identifier). The // contents of the string are then replaced by the "this" pointer. pmtrl->_pbmtl = BrMaterialAllocate("1234"); if (pvNil == pmtrl->_pbmtl) goto LFail; pbmtl = pmtrl->_pbmtl; CopyPb(&pmtrl, pbmtl->identifier, size(long)); pbmtl->colour = 0; // this field is ignored pbmtl->ka = kbrufKaDefault; pbmtl->kd = kbrufKdDefault; pbmtl->ks = kbrufKsDefault; pbmtl->power = krPowerDefault; pbmtl->opacity = kbOpaque; // all socrates objects are opaque pbmtl->flags = BR_MATF_LIGHT | BR_MATF_GOURAUD; pfni->GetStnPath(&stn); pbmtl->colour_map = BrPixelmapLoad(stn.Psz()); if (pvNil == pbmtl->colour_map) goto LFail; // Create a TMAP for this BPMP. We don't directly save // the ptmap...it's automagically attached to the // BPMP's identifier. ptmap = TMAP::PtmapNewFromBpmp(pbmtl->colour_map); if (pvNil == ptmap) { BrPixelmapFree(pbmtl->colour_map); goto LFail; } Assert((PTMAP)pbmtl->colour_map->identifier == ptmap, "lost our TMAP!"); AssertPo(_ptmapShadeTable, 0); pbmtl->index_shade = _ptmapShadeTable->Pbpmp(); pbmtl->flags |= BR_MATF_MAP_COLOUR; pbmtl->index_base = 0; pbmtl->index_range = _ptmapShadeTable->Pbpmp()->height - 1; AssertPo(pmtrl, 0); return pmtrl; LFail: ReleasePpo(&pmtrl); return pvNil; } /*************************************************************************** Read a BMP and build a PMTRL from it ***************************************************************************/ PMTRL MTRL::PmtrlNewFromBmp(PFNI pfni, PGL pglclr) { AssertPo(pfni, ffniFile); AssertPo(_ptmapShadeTable, 0); PMTRL pmtrl; PTMAP ptmap; pmtrl = PmtrlNew(); if (pvNil == pmtrl) return pvNil; ptmap = TMAP::PtmapReadNative(pfni, pglclr); if (pvNil == ptmap) { ReleasePpo(&pmtrl); return pvNil; } pmtrl->_pbmtl->index_base = 0; pmtrl->_pbmtl->index_range = _ptmapShadeTable->Pbpmp()->height - 1; pmtrl->_pbmtl->index_shade = _ptmapShadeTable->Pbpmp(); pmtrl->_pbmtl->flags |= BR_MATF_MAP_COLOUR; pmtrl->_pbmtl->colour_map = ptmap->Pbpmp(); // The reference for ptmap has been transfered to pmtrl by the previous // line, so I don't need to ReleasePpo(&ptmap) in this function. return pmtrl; } /*************************************************************************** Return a pointer to the MTRL that owns this BMTL ***************************************************************************/ PMTRL MTRL::PmtrlFromBmtl(PBMTL pbmtl) { AssertVarMem(pbmtl); PMTRL pmtrl = (PMTRL)*(long *)pbmtl->identifier; AssertPo(pmtrl, 0); return pmtrl; } /*************************************************************************** Return this MTRL's TMAP, or pvNil if it's a solid-color MTRL. Note: This function doesn't AssertThis because it gets called on objects which are not necessarily valid (e.g., from the destructor and from AssertThis()) ***************************************************************************/ PTMAP MTRL::Ptmap(void) { AssertBaseThis(0); if (pvNil == _pbmtl) return pvNil; else if (pvNil == _pbmtl->colour_map) return pvNil; else return (PTMAP)_pbmtl->colour_map->identifier; } /*************************************************************************** Write a MTRL to a chunky file ***************************************************************************/ bool MTRL::FWrite(PCFL pcfl, CTG ctg, CNO *pcno) { AssertThis(0); AssertPo(pcfl, 0); AssertVarMem(pcno); MTRLF mtrlf; CNO cnoChild; PTMAP ptmap; mtrlf.bo = kboCur; mtrlf.osk = koskCur; mtrlf.brc = _pbmtl->colour; mtrlf.brufKa = _pbmtl->ka; mtrlf.brufKd = _pbmtl->kd; mtrlf.brufKs = _pbmtl->ks; mtrlf.bIndexBase = _pbmtl->index_base; mtrlf.cIndexRange = _pbmtl->index_range; mtrlf.rPower = _pbmtl->power; if (!pcfl->FAddPv(&mtrlf, size(MTRLF), ctg, pcno)) return fFalse; ptmap = Ptmap(); if (pvNil != ptmap) { if (!ptmap->FWrite(pcfl, kctgTmap, &cnoChild)) { pcfl->Delete(ctg, *pcno); return fFalse; } if (!pcfl->FAdoptChild(ctg, *pcno, kctgTmap, cnoChild, 0)) { pcfl->Delete(kctgTmap, cnoChild); pcfl->Delete(ctg, *pcno); return fFalse; } } return fTrue; } /*************************************************************************** Free the MTRL ***************************************************************************/ MTRL::~MTRL(void) { AssertBaseThis(0); PTMAP ptmap; ptmap = Ptmap(); if (pvNil != ptmap) { ReleasePpo(&ptmap); _pbmtl->colour_map = pvNil; } BrMaterialRemove(_pbmtl); BrMaterialFree(_pbmtl); } #ifdef DEBUG /*************************************************************************** Assert the validity of the MTRL. ***************************************************************************/ void MTRL::AssertValid(ulong grf) { MTRL_PAR::AssertValid(fobjAllocated); AssertNilOrPo(Ptmap(), 0); Assert(pvNil != _ptmapShadeTable, "Why do we have MTRLs but no shade table?"); } /*************************************************************************** Mark memory used by the MTRL ***************************************************************************/ void MTRL::MarkMem(void) { AssertThis(0); PTMAP ptmap; MTRL_PAR::MarkMem(); ptmap = Ptmap(); if (pvNil != ptmap) MarkMemObj(ptmap); } /*************************************************************************** Mark memory used by the shade table ***************************************************************************/ void MTRL::MarkShadeTable(void) { MarkMemObj(_ptmapShadeTable); } #endif //DEBUG // // // // CMTL (custom material) stuff begins here // // // /*************************************************************************** Static function to see if the given chunk has MODL children ***************************************************************************/ bool CMTL::FHasModels(PCFL pcfl, CTG ctg, CNO cno) { AssertPo(pcfl, 0); KID kid; return pcfl->FGetKidChidCtg(ctg, cno, 0, kctgBmdl, &kid); } /*************************************************************************** Static function to see if the two given CMTLs have the same child MODLs ***************************************************************************/ bool CMTL::FEqualModels(PCFL pcfl, CNO cno1, CNO cno2) { AssertPo(pcfl, 0); CHID chid = 0; KID kid1; KID kid2; while (pcfl->FGetKidChidCtg(kctgCmtl, cno1, chid, kctgBmdl, &kid1)) { if (!pcfl->FGetKidChidCtg(kctgCmtl, cno2, chid, kctgBmdl, &kid2)) return fFalse; if (kid1.cki.cno != kid2.cki.cno) return fFalse; chid++; } // End of cno1's BMDLs...make sure cno2 doesn't have any more if (pcfl->FGetKidChidCtg(kctgCmtl, cno2, chid, kctgBmdl, &kid2)) return fFalse; return fTrue; } /*************************************************************************** Create a new custom material ***************************************************************************/ PCMTL CMTL::PcmtlNew(long ibset, long cbprt, PMTRL *prgpmtrl) { AssertPvCb(prgpmtrl, LwMul(cbprt, size(PMTRL))); PCMTL pcmtl; long imtrl; pcmtl = NewObj CMTL; if (pvNil == pcmtl) return pvNil; pcmtl->_ibset = ibset; pcmtl->_cbprt = cbprt; if (!FAllocPv((void **)&pcmtl->_prgpmtrl, LwMul(pcmtl->_cbprt, size(PMTRL)), fmemClear, mprNormal)) { ReleasePpo(&pcmtl); return pvNil; } if (!FAllocPv((void **)&pcmtl->_prgpmodl, LwMul(pcmtl->_cbprt, size(PMODL)), fmemClear, mprNormal)) { ReleasePpo(&pcmtl); return pvNil; } for (imtrl = 0; imtrl < cbprt; imtrl++) { AssertPo(prgpmtrl[imtrl], 0); pcmtl->_prgpmtrl[imtrl] = prgpmtrl[imtrl]; pcmtl->_prgpmtrl[imtrl]->AddRef(); } AssertPo(pcmtl, 0); return pcmtl; } /*************************************************************************** A PFNRPO to read CMTL objects. ***************************************************************************/ bool CMTL::FReadCmtl(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck, PBACO *ppbaco, long *pcb) { AssertPo(pcrf, 0); AssertPo(pblck, 0); AssertNilOrVarMem(ppbaco); AssertVarMem(pcb); PCMTL pcmtl; *pcb = size(CMTL); if (pvNil == ppbaco) return fTrue; pcmtl = NewObj CMTL; if (pvNil == pcmtl || !pcmtl->_FInit(pcrf, ctg, cno)) { ReleasePpo(&pcmtl); TrashVar(ppbaco); TrashVar(pcb); return fFalse; } AssertPo(pcmtl, 0); *ppbaco = pcmtl; *pcb += LwMul(size(PMTRL) + size(PMODL), pcmtl->_cbprt); return fTrue; } /*************************************************************************** Read a CMTL from file ***************************************************************************/ bool CMTL::_FInit(PCRF pcrf, CTG ctg, CNO cno) { AssertBaseThis(0); AssertPo(pcrf, 0); long ikid; long imtrl; KID kid; BLCK blck; PCFL pcfl = pcrf->Pcfl(); CMTLF cmtlf; if (!pcfl->FFind(ctg, cno, &blck) || !blck.FUnpackData()) return fFalse; if (blck.Cb() != size(CMTLF)) { Bug("bad CMTLF...you may need to update tmpls.chk"); return fFalse; } if (!blck.FReadRgb(&cmtlf, size(CMTLF), 0)) return fFalse; if (kboOther == cmtlf.bo) SwapBytesBom(&cmtlf, kbomCmtlf); Assert(kboCur == cmtlf.bo, "bad CMTLF"); _ibset = cmtlf.ibset; // Highest chid is number of body part sets - 1 _cbprt = 0; // note: there might be a faster way to compute _cbprt for (ikid = 0; pcfl->FGetKid(ctg, cno, ikid, &kid); ikid++) { if ((long)kid.chid > (_cbprt - 1)) _cbprt = kid.chid + 1; } if (!FAllocPv((void **)&_prgpmtrl, LwMul(_cbprt, size(PMTRL)), fmemClear, mprNormal)) { return fFalse; } if (!FAllocPv((void **)&_prgpmodl, LwMul(_cbprt, size(PMODL)), fmemClear, mprNormal)) { return fFalse; } for (imtrl = 0; imtrl < _cbprt; imtrl++) { if (pcfl->FGetKidChidCtg(ctg, cno, imtrl, kctgMtrl, &kid)) { _prgpmtrl[imtrl] = (MTRL *)pcrf->PbacoFetch(kid.cki.ctg, kid.cki.cno, MTRL::FReadMtrl); if (pvNil == _prgpmtrl[imtrl]) return fFalse; } if (pcfl->FGetKidChidCtg(ctg, cno, imtrl, kctgBmdl, &kid)) { _prgpmodl[imtrl] = (MODL *)pcrf->PbacoFetch(kid.cki.ctg, kid.cki.cno, MODL::FReadModl); if (pvNil == _prgpmodl[imtrl]) return fFalse; } } return fTrue; } /*************************************************************************** Free the CMTL ***************************************************************************/ CMTL::~CMTL(void) { AssertBaseThis(0); long imtrl; if (pvNil != _prgpmtrl) { for (imtrl = 0; imtrl < _cbprt; imtrl++) ReleasePpo(&_prgpmtrl[imtrl]); FreePpv((void **)&_prgpmtrl); } if (pvNil != _prgpmodl) { for (imtrl = 0; imtrl < _cbprt; imtrl++) ReleasePpo(&_prgpmodl[imtrl]); FreePpv((void **)&_prgpmodl); } } /*************************************************************************** Return ibmtl'th BMTL ***************************************************************************/ BMTL *CMTL::Pbmtl(long ibmtl) { AssertThis(0); AssertIn(ibmtl, 0, _cbprt); return _prgpmtrl[ibmtl]->Pbmtl(); } /*************************************************************************** Return imodl'th MODL ***************************************************************************/ PMODL CMTL::Pmodl(long imodl) { AssertThis(0); AssertIn(imodl, 0, _cbprt); AssertNilOrPo(_prgpmodl[imodl], 0); return _prgpmodl[imodl]; } /*************************************************************************** Returns whether this CMTL has any models attached ***************************************************************************/ bool CMTL::FHasModels(void) { AssertThis(0); long imodl; for (imodl = 0; imodl < _cbprt; imodl++) { if (pvNil != _prgpmodl[imodl]) return fTrue; } return fFalse; } #ifdef DEBUG /*************************************************************************** Assert the validity of the CMTL ***************************************************************************/ void CMTL::AssertValid(ulong grf) { long imtrl; MTRL_PAR::AssertValid(fobjAllocated); AssertPvCb(_prgpmtrl, LwMul(_cbprt, size(MTRL *))); AssertPvCb(_prgpmodl, LwMul(_cbprt, size(MODL *))); for (imtrl = 0; imtrl < _cbprt; imtrl++) { AssertPo(_prgpmtrl[imtrl], 0); AssertNilOrPo(_prgpmodl[imtrl], 0); } } /*************************************************************************** Mark memory used by the MTRL ***************************************************************************/ void CMTL::MarkMem(void) { AssertThis(0); long imtrl; MTRL_PAR::MarkMem(); MarkPv(_prgpmtrl); MarkPv(_prgpmodl); for (imtrl = 0; imtrl < _cbprt; imtrl++) { MarkMemObj(_prgpmtrl[imtrl]); MarkMemObj(_prgpmodl[imtrl]); } } #endif //DEBUG