using SharpDX; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace CodeWalker.GameFiles { [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapFile : GameFile, PackedFile { public Meta Meta { get; set; } public PsoFile Pso { get; set; } public RbfFile Rbf { get; set; } public CMapData _CMapData; public CMapData CMapData { get { return _CMapData; } set { _CMapData = value; } } public CEntityDef[] CEntityDefs { get; set; } public CMloInstanceDef[] CMloInstanceDefs { get; set; } public CCarGen[] CCarGens { get; set; } public CTimeCycleModifier[] CTimeCycleModifiers { get; set; } public MetaHash[] physicsDictionaries { get; set; } public Unk_975711773[] CBoxOccluders { get; set; } public Unk_2741784237[] COccludeModels { get; set; } public string[] Strings { get; set; } public YmapEntityDef[] AllEntities; public YmapEntityDef[] RootEntities; public YmapEntityDef[] MloEntities; public YmapFile[] ChildYmaps = null; public bool MergedWithParent = false; public YmapGrassInstanceBatch[] GrassInstanceBatches { get; set; } public YmapPropInstanceBatch[] PropInstanceBatches { get; set; } public YmapDistantLODLights DistantLODLights { get; set; } public YmapTimeCycleModifier[] TimeCycleModifiers { get; set; } public YmapCarGen[] CarGenerators { get; set; } public YmapBoxOccluder[] BoxOccluders { get; set; } public YmapOccludeModel[] OccludeModels { get; set; } //fields used by the editor: public bool HasChanged { get; set; } = false; public List SaveWarnings = null; public YmapFile() : base(null, GameFileType.Ymap) { } public YmapFile(RpfFileEntry entry) : base(entry, GameFileType.Ymap) { RpfFileEntry = entry; } public void Load(byte[] data) { //direct load from a raw, compressed ymap file (openIV-compatible format) RpfResourceFileEntry resentry = new RpfResourceFileEntry(); //hopefully this format has an RSC7 header... uint rsc7 = BitConverter.ToUInt32(data, 0); if (rsc7 == 0x37435352) //RSC7 header present! { int version = BitConverter.ToInt32(data, 4); resentry.SystemFlags = BitConverter.ToUInt32(data, 8); resentry.GraphicsFlags = BitConverter.ToUInt32(data, 12); if (data.Length > 16) { int newlen = data.Length - 16; //trim the header from the data passed to the next step. byte[] newdata = new byte[newlen]; Buffer.BlockCopy(data, 16, newdata, 0, newlen); data = newdata; } else { data = null; //shouldn't happen... empty.. } } else { //direct load from file without the rpf header.. //assume it's in resource meta format resentry.SystemFlags = RpfResourceFileEntry.GetFlagsFromSize(data.Length, 0); resentry.GraphicsFlags = RpfResourceFileEntry.GetFlagsFromSize(0, 2); //graphics type 2 for ymap } var oldresentry = RpfFileEntry as RpfResourceFileEntry; if (oldresentry != null) //update the existing entry with the new one { oldresentry.SystemFlags = resentry.SystemFlags; oldresentry.GraphicsFlags = resentry.GraphicsFlags; resentry.Name = oldresentry.Name; resentry.NameHash = oldresentry.NameHash; resentry.NameLower = oldresentry.NameLower; resentry.ShortNameHash = oldresentry.ShortNameHash; } else { RpfFileEntry = resentry; //just stick it in there for later... } data = ResourceBuilder.Decompress(data); Load(data, resentry); Loaded = true; } public void Load(byte[] data, RpfFileEntry entry) { Name = entry.Name; RpfFileEntry = entry; RpfResourceFileEntry resentry = entry as RpfResourceFileEntry; if (resentry == null) { NonMetaLoad(data); return; } ResourceDataReader rd = new ResourceDataReader(resentry, data); Meta = rd.ReadBlock(); CMapData = MetaTypes.GetTypedData(Meta, MetaName.CMapData); Strings = MetaTypes.GetStrings(Meta); if (Strings != null) { foreach (string str in Strings) { JenkIndex.Ensure(str); //just shove them in there } } physicsDictionaries = MetaTypes.GetHashArray(Meta, CMapData.physicsDictionaries); EnsureEntities(Meta); //load all the entity data and create the YmapEntityDefs EnsureInstances(Meta); EnsureLodLights(Meta); EnsureDistantLODLights(Meta); EnsureTimeCycleModifiers(Meta); EnsureCarGens(Meta); EnsureBoxOccluders(Meta); EnsureOccludeModels(Meta); EnsureContainerLods(Meta); #region data block test and old code //foreach (var block in Meta.DataBlocks) //{ // switch (block.StructureNameHash) // { // case MetaName.STRING: // case MetaName.POINTER: // case MetaName.HASH: // case MetaName.UINT: // case MetaName.VECTOR3: //distant lod lights uses this // case MetaName.CMapData: // case MetaName.CEntityDef: // case MetaName.CTimeCycleModifier: //these sections are handled already // case MetaName.CCarGen: // case MetaName.CLightAttrDef: // case MetaName.CMloInstanceDef: // case MetaName.CExtensionDefDoor: // case MetaName.CExtensionDefLightEffect: // case MetaName.CExtensionDefSpawnPointOverride: // case MetaName.rage__fwGrassInstanceListDef: //grass instance buffer // case MetaName.rage__fwGrassInstanceListDef__InstanceData: //grass instance buffer data // break; // case MetaName.PhVerletClothCustomBounds: //these sections still todo.. // case MetaName.SectionUNKNOWN1: // case MetaName.SectionUNKNOWN5://occlusion vertex data container // case MetaName.SectionUNKNOWN7://occlusion related? // break; // case (MetaName)17: //vertex data - occlusion related - SectionUNKNOWN5 // break; // case (MetaName)33: //what is this? maybe lodlights related // break; // default: // break; // } //} //MetaTypes.ParseMetaData(Meta); //string shortname = resentry.Name.Substring(0, resentry.Name.LastIndexOf('.')); //uint namehash = JenkHash.GenHash(shortname); //CLightAttrDefs = MetaTypes.GetTypedDataArray(Meta, MetaName.CLightAttrDef); //if (CLightAttrDefs != null) //{ } //var unk5s = MetaTypes.GetTypedDataArray(Meta, MetaName.SectionUNKNOWN5); //if (unk5s != null) //used in occlusion ymaps //{ // foreach (var unk5 in unk5s) // { // if ((unk5.verts.Ptr > 0) && (unk5.verts.Ptr <= (ulong)Meta.DataBlocks.Length)) // { // var indicesoffset = unk5.Unk_853977995; // var datablock = Meta.DataBlocks[((int)unk5.verts.Ptr) - 1]; // if (datablock != null) // { }//vertex data... occlusion mesh? // } // } //} //var unk7s = MetaTypes.GetTypedDataArray(Meta, MetaName.SectionUNKNOWN7); //if (unk7s != null) //{ } //used in occlusion ymaps //var unk10s = MetaTypes.GetTypedDataArray(Meta, MetaName.SectionUNKNOWN10); //if (unk10s != null) //{ } //entity pointer array.. //CDoors = MetaTypes.GetTypedDataArray(Meta, MetaName.CExtensionDefDoor); //if (CDoors != null) //{ } //needs work - doors can be different types? not enough bytes for one //CExtLightEffects = MetaTypes.GetTypedDataArray(Meta, MetaName.CExtensionDefLightEffect); //if (CExtLightEffects != null) //{ } //CSpawnOverrides = MetaTypes.GetTypedDataArray(Meta, MetaName.CExtensionDefSpawnPointOverride); //if (CSpawnOverrides != null) //{ } #endregion } private void NonMetaLoad(byte[] data) { //non meta not supported yet! but see what's in there... MemoryStream ms = new MemoryStream(data); if (RbfFile.IsRBF(ms)) { var Rbf = new RbfFile(); Rbf.Load(ms); } else if (PsoFile.IsPSO(ms)) { var Pso = new PsoFile(); Pso.Load(ms); //PsoTypes.EnsurePsoTypes(Pso); } else { } } private void EnsureEntities(Meta Meta) { //CMloInstanceDefs = MetaTypes.ConvertDataArray(Meta, MetaName.CMloInstanceDef, CMapData.entities); CMloInstanceDefs = MetaTypes.GetTypedDataArray(Meta, MetaName.CMloInstanceDef); if (CMloInstanceDefs != null) { } var eptrs = MetaTypes.GetPointerArray(Meta, CMapData.entities); //CEntityDefs = MetaTypes.ConvertDataArray(Meta, MetaName.CEntityDef, CMapData.entities); CEntityDefs = MetaTypes.GetTypedDataArray(Meta, MetaName.CEntityDef); if (CEntityDefs != null) { } int instcount = 0; if (CEntityDefs != null) instcount += CEntityDefs.Length; if (CMloInstanceDefs != null) instcount += CMloInstanceDefs.Length; if (instcount > 0) { //build the entity hierarchy. List roots = new List(instcount); List alldefs = new List(instcount); List mlodefs = null; if (CEntityDefs != null) { for (int i = 0; i < CEntityDefs.Length; i++) { YmapEntityDef d = new YmapEntityDef(this, i, ref CEntityDefs[i]); alldefs.Add(d); } } if (CMloInstanceDefs != null) { mlodefs = new List(); for (int i = 0; i < CMloInstanceDefs.Length; i++) { YmapEntityDef d = new YmapEntityDef(this, i, ref CMloInstanceDefs[i]); uint[] unkuints = MetaTypes.GetUintArray(Meta, CMloInstanceDefs[i].Unk_1407157833); if (d.MloInstance != null) { d.MloInstance.Unk_1407157833 = unkuints; } alldefs.Add(d); mlodefs.Add(d); } } for (int i = 0; i < alldefs.Count; i++) { YmapEntityDef d = alldefs[i]; int pind = d.CEntityDef.parentIndex; bool isroot = false; if ((pind < 0) || (pind >= alldefs.Count) || (pind >= i)) //index check? might be a problem { isroot = true; } else { YmapEntityDef p = alldefs[pind]; if ((p.CEntityDef.lodLevel <= d.CEntityDef.lodLevel) || ((p.CEntityDef.lodLevel == Unk_1264241711.LODTYPES_DEPTH_ORPHANHD) && (d.CEntityDef.lodLevel != Unk_1264241711.LODTYPES_DEPTH_ORPHANHD))) { isroot = true; p = null; } } if (isroot) { roots.Add(d); } else { YmapEntityDef p = alldefs[pind]; p.AddChild(d); } } for (int i = 0; i < alldefs.Count; i++) { alldefs[i].ChildListToArray(); } AllEntities = alldefs.ToArray(); RootEntities = roots.ToArray(); if (mlodefs != null) { MloEntities = mlodefs.ToArray(); } foreach (var ent in AllEntities) { ent.Extensions = MetaTypes.GetExtensions(Meta, ent.CEntityDef.extensions); } } } private void EnsureInstances(Meta Meta) { if (CMapData.instancedData.GrassInstanceList.Count1 != 0) { rage__fwGrassInstanceListDef[] batches = MetaTypes.ConvertDataArray(Meta, MetaName.rage__fwGrassInstanceListDef, CMapData.instancedData.GrassInstanceList); YmapGrassInstanceBatch[] gbatches = new YmapGrassInstanceBatch[batches.Length]; for (int i = 0; i < batches.Length; i++) { var batch = batches[i]; rage__fwGrassInstanceListDef__InstanceData[] instdatas = MetaTypes.ConvertDataArray(Meta, MetaName.rage__fwGrassInstanceListDef__InstanceData, batch.InstanceList); YmapGrassInstanceBatch gbatch = new YmapGrassInstanceBatch(); gbatch.Ymap = this; gbatch.Batch = batch; gbatch.Instances = instdatas; gbatch.Position = (batch.BatchAABB.min.XYZ() + batch.BatchAABB.max.XYZ()) * 0.5f; gbatch.Radius = (batch.BatchAABB.max.XYZ() - gbatch.Position).Length(); gbatch.AABBMin = (batch.BatchAABB.min.XYZ()); gbatch.AABBMax = (batch.BatchAABB.max.XYZ()); gbatches[i] = gbatch; } GrassInstanceBatches = gbatches; } if (CMapData.instancedData.PropInstanceList.Count1 != 0) { } } private void EnsureLodLights(Meta Meta) { //TODO! if (CMapData.LODLightsSOA.direction.Count1 != 0) { } } private void EnsureDistantLODLights(Meta Meta) { if (CMapData.DistantLODLightsSOA.position.Count1 != 0) { DistantLODLights = new YmapDistantLODLights(); DistantLODLights.Ymap = this; DistantLODLights.CDistantLODLight = CMapData.DistantLODLightsSOA; DistantLODLights.colours = MetaTypes.GetUintArray(Meta, CMapData.DistantLODLightsSOA.RGBI); DistantLODLights.positions = MetaTypes.ConvertDataArray(Meta, MetaName.VECTOR3, CMapData.DistantLODLightsSOA.position); DistantLODLights.CalcBB(); } } private void EnsureTimeCycleModifiers(Meta Meta) { CTimeCycleModifiers = MetaTypes.ConvertDataArray(Meta, MetaName.CTimeCycleModifier, CMapData.timeCycleModifiers); if (CTimeCycleModifiers != null) { TimeCycleModifiers = new YmapTimeCycleModifier[CTimeCycleModifiers.Length]; for (int i = 0; i < CTimeCycleModifiers.Length; i++) { YmapTimeCycleModifier tcm = new YmapTimeCycleModifier(); tcm.Ymap = this; tcm.CTimeCycleModifier = CTimeCycleModifiers[i]; tcm.BBMin = tcm.CTimeCycleModifier.minExtents; tcm.BBMax = tcm.CTimeCycleModifier.maxExtents; TimeCycleModifiers[i] = tcm; } } } private void EnsureCarGens(Meta Meta) { CCarGens = MetaTypes.ConvertDataArray(Meta, MetaName.CCarGen, CMapData.carGenerators); if (CCarGens != null) { //string str = MetaTypes.GetTypesInitString(resentry, Meta); //to generate structinfos and enuminfos CarGenerators = new YmapCarGen[CCarGens.Length]; for (int i = 0; i < CCarGens.Length; i++) { CarGenerators[i] = new YmapCarGen(this, CCarGens[i]); } } } private void EnsureBoxOccluders(Meta meta) { CBoxOccluders = MetaTypes.ConvertDataArray(Meta, (MetaName)975711773, CMapData.boxOccluders); if (CBoxOccluders != null) { BoxOccluders = new YmapBoxOccluder[CBoxOccluders.Length]; for (int i = 0; i < CBoxOccluders.Length; i++) { BoxOccluders[i] = new YmapBoxOccluder(this, CBoxOccluders[i]); } } } private void EnsureOccludeModels(Meta meta) { COccludeModels = MetaTypes.ConvertDataArray(Meta, (MetaName)2741784237, CMapData.occludeModels); if (COccludeModels != null) { OccludeModels = new YmapOccludeModel[COccludeModels.Length]; for (int i = 0; i < COccludeModels.Length; i++) { OccludeModels[i] = new YmapOccludeModel(this, COccludeModels[i]); } } } private void EnsureContainerLods(Meta meta) { //TODO: containerLods if (CMapData.containerLods.Count1 > 0) { //string str = MetaTypes.GetTypesInitString(Meta); //to generate structinfos and enuminfos } } public void BuildCEntityDefs() { //recreates the CEntityDefs array from AllEntities. if (AllEntities == null) { CEntityDefs = null; return; } int count = AllEntities.Length; CEntityDefs = new CEntityDef[count]; for (int i = 0; i < count; i++) { CEntityDefs[i] = AllEntities[i].CEntityDef; } //TODO: MloInstanceDefs! } public void BuildCCarGens() { //recreates the CCarGens array from CarGenerators. if (CarGenerators == null) { CCarGens = null; return; } int count = CarGenerators.Length; CCarGens = new CCarGen[count]; for (int i = 0; i < count; i++) { CCarGens[i] = CarGenerators[i].CCarGen; } } public byte[] Save() { //direct save to a raw, compressed ymap file (openIV-compatible format) //since Ymap object contents have been modified, need to recreate the arrays which are what is saved. BuildCEntityDefs(); BuildCCarGens(); //TODO: //BuildInstances(); //BuildLodLights(); //BuildDistantLodLights(); //BuildTimecycleModifiers(); //already being saved - update them.. //BuildBoxOccluders(); //BuildOccludeModels(); //BuildContainerLods(); MetaBuilder mb = new MetaBuilder(); var mdb = mb.EnsureBlock(MetaName.CMapData); CMapData mapdata = CMapData; if (CEntityDefs != null) { for (int i = 0; i < CEntityDefs.Length; i++) { var yent = AllEntities[i]; //save the extensions.. CEntityDefs[i].extensions = mb.AddWrapperArrayPtr(yent.Extensions); } } mapdata.entities = mb.AddItemPointerArrayPtr(MetaName.CEntityDef, CEntityDefs); mapdata.timeCycleModifiers = mb.AddItemArrayPtr(MetaName.CTimeCycleModifier, CTimeCycleModifiers); mapdata.physicsDictionaries = mb.AddHashArrayPtr(physicsDictionaries); mapdata.carGenerators = mb.AddItemArrayPtr(MetaName.CCarGen, CCarGens); if (CMloInstanceDefs != null) { LogSaveWarning("CMloInstanceDefs were present, may not save properly. (TODO!)"); } //clear everything out for now - TODO: fix if (mapdata.containerLods.Count1 != 0) LogSaveWarning("containerLods were not saved. (TODO!)"); if (mapdata.occludeModels.Count1 != 0) LogSaveWarning("occludeModels were not saved. (TODO!)"); if (mapdata.boxOccluders.Count1 != 0) LogSaveWarning("boxOccluders were not saved. (TODO!)"); if (mapdata.instancedData.GrassInstanceList.Count1 != 0) LogSaveWarning("instancedData.GrassInstanceList was not saved. (TODO!)"); if (mapdata.instancedData.PropInstanceList.Count1 != 0) LogSaveWarning("instancedData.PropInstanceList was not saved. (TODO!)"); if (mapdata.LODLightsSOA.direction.Count1 != 0) LogSaveWarning("LODLightsSOA was not saved. (TODO!)"); if (mapdata.DistantLODLightsSOA.position.Count1 != 0) LogSaveWarning("DistantLODLightsSOA was not saved. (TODO!)"); mapdata.containerLods = new Array_Structure(); mapdata.occludeModels = new Array_Structure(); mapdata.boxOccluders = new Array_Structure(); mapdata.instancedData = new rage__fwInstancedMapData(); mapdata.LODLightsSOA = new CLODLight(); mapdata.DistantLODLightsSOA = new CDistantLODLight(); var block = new CBlockDesc(); block.name = mb.AddStringPtr(Path.GetFileNameWithoutExtension(Name)); block.exportedBy = mb.AddStringPtr("CodeWalker"); block.time = mb.AddStringPtr(DateTime.UtcNow.ToString("dd MMMM yyyy HH:mm")); mapdata.block = block; string name = Path.GetFileNameWithoutExtension(Name); uint nameHash = JenkHash.GenHash(name); mapdata.name = new MetaHash(nameHash);//make sure name is upto date... mb.AddItem(MetaName.CMapData, mapdata); //make sure all the relevant structure and enum infos are present. mb.AddStructureInfo(MetaName.rage__fwInstancedMapData); mb.AddStructureInfo(MetaName.CLODLight); mb.AddStructureInfo(MetaName.CDistantLODLight); mb.AddStructureInfo(MetaName.CBlockDesc); mb.AddStructureInfo(MetaName.CMapData); mb.AddStructureInfo(MetaName.CEntityDef); mb.AddStructureInfo(MetaName.CTimeCycleModifier); if ((CCarGens != null) && (CCarGens.Length > 0)) { mb.AddStructureInfo(MetaName.CCarGen); } mb.AddEnumInfo((MetaName)1264241711); //LODTYPES_ mb.AddEnumInfo((MetaName)648413703); //PRI_ Meta meta = mb.GetMeta(); byte[] data = ResourceBuilder.Build(meta, 2); //ymap is version 2... return data; } private void LogSaveWarning(string w) { if (SaveWarnings == null) SaveWarnings = new List(); SaveWarnings.Add(w); } public void EnsureChildYmaps(GameFileCache gfc) { if (ChildYmaps == null) { //no children here... look for child ymap.... var node = gfc.GetMapNode(RpfFileEntry.ShortNameHash); if ((node != null) && (node.Children != null) && (node.Children.Length > 0)) { ChildYmaps = new YmapFile[node.Children.Length]; for (int i = 0; i < ChildYmaps.Length; i++) { var chash = node.Children[i].Name; ChildYmaps[i] = gfc.GetYmap(chash); if (ChildYmaps[i] == null) { //couldn't find child ymap.. } } } } bool needupd = false; if (ChildYmaps != null) { for (int i = 0; i < ChildYmaps.Length; i++) { var cmap = ChildYmaps[i]; if (cmap == null) continue; //nothing here.. if (!cmap.Loaded) { //ChildYmaps[i] = gfc.GetYmap(cmap.Hash); //incase no load was requested. cmap = gfc.GetYmap(cmap.Key.Hash); ChildYmaps[i] = cmap; } if ((cmap.Loaded) && (!cmap.MergedWithParent)) { needupd = true; } } } if ((ChildYmaps != null) && needupd) { List newroots = new List(RootEntities); for (int i = 0; i < ChildYmaps.Length; i++) { var cmap = ChildYmaps[i]; if (cmap == null) continue; //nothing here.. //cmap.EnsureChildYmaps(); if ((cmap.Loaded) && (!cmap.MergedWithParent)) { cmap.MergedWithParent = true; if (cmap.RootEntities != null) { foreach (var rcent in cmap.RootEntities) { int pind = rcent.CEntityDef.parentIndex; if (pind < 0) { if (rcent.CEntityDef.lodLevel != Unk_1264241711.LODTYPES_DEPTH_ORPHANHD) { } //pind = 0; } if ((pind >= 0) && (pind < AllEntities.Length)) { var pentity = AllEntities[pind]; pentity.AddChild(rcent); } else { //TODO: fix this!! //newroots.Add(rcent); //not sure this is the right approach. //////rcent.Owner = this; } } } } } if (AllEntities != null) { for (int i = 0; i < AllEntities.Length; i++) { AllEntities[i].ChildListToMergedArray(); } } RootEntities = newroots.ToArray(); } } public void AddEntity(YmapEntityDef ent) { //used by the editor to add to the ymap. List allents = new List(); if (AllEntities != null) allents.AddRange(AllEntities); ent.Index = allents.Count; ent.Ymap = this; allents.Add(ent); AllEntities = allents.ToArray(); if ((ent.Parent == null) || (ent.Parent.Ymap != this)) { //root entity, add to roots. List rootents = new List(); if (RootEntities != null) rootents.AddRange(RootEntities); rootents.Add(ent); RootEntities = rootents.ToArray(); } HasChanged = true; } public bool RemoveEntity(YmapEntityDef ent) { //used by the editor to remove from the ymap. if (ent == null) return false; var res = true; int idx = ent.Index; List newAllEntities = new List(); List newRootEntities = new List(); for (int i = 0; i < AllEntities.Length; i++) { var oent = AllEntities[i]; oent.Index = newAllEntities.Count; if (oent != ent) newAllEntities.Add(oent); else if (i != idx) { res = false; //indexes didn't match.. this shouldn't happen! } } for (int i = 0; i < RootEntities.Length; i++) { var oent = RootEntities[i]; if (oent != ent) newRootEntities.Add(oent); } if ((AllEntities.Length == newAllEntities.Count) || (RootEntities.Length == newRootEntities.Count)) { res = false; } AllEntities = newAllEntities.ToArray(); RootEntities = newRootEntities.ToArray(); HasChanged = true; return res; } public void AddCarGen(YmapCarGen cargen) { List cargens = new List(); if (CarGenerators != null) cargens.AddRange(CarGenerators); cargen.Ymap = this; cargens.Add(cargen); CarGenerators = cargens.ToArray(); HasChanged = true; } public bool RemoveCarGen(YmapCarGen cargen) { if (cargen == null) return false; List newcargens = new List(); if (CarGenerators != null) { for (int i = 0; i < CarGenerators.Length; i++) { var cg = CarGenerators[i]; if (cg != cargen) { newcargens.Add(cg); } } if (newcargens.Count == CarGenerators.Length) { return false; //nothing removed... wasn't present? } } CarGenerators = newcargens.ToArray(); HasChanged = true; return true; } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapEntityDef { public Archetype Archetype { get; set; } //cached by GameFileCache on loading... public Vector3 BBMin;//oriented archetype AABBmin public Vector3 BBMax;//oriented archetype AABBmax public Vector3 BSCenter; //oriented archetype BS center public float BSRadius;//cached from archetype public CEntityDef _CEntityDef; public CEntityDef CEntityDef { get { return _CEntityDef; } set { _CEntityDef = value; } } private List ChildList { get; set; } public YmapEntityDef[] Children { get; set; } public YmapEntityDef[] ChildrenMerged;// { get; set; } public Vector3 Position { get; set; } public Quaternion Orientation { get; set; } public Vector3 Scale { get; set; } public bool IsMlo { get; set; } public MloInstanceData MloInstance { get; set; } public YmapEntityDef MloParent { get; set; } public Vector3 MloRefPosition { get; set; } public Quaternion MloRefOrientation { get; set; } public MetaWrapper[] Extensions { get; set; } public bool ChildrenRendered; //used when rendering ymap mode to reduce LOD flashing... public int Index { get; set; } public Vector3 CamRel; //used for rendering... public float Distance; //used for rendering public bool IsVisible; //used for rendering public bool Rendered; //used for rendering public bool ChildRendered; //used for rendering public bool ChildrenVisible; //used for rendering public bool ChildrenLoading; //used for rendering public float LargestChildLodDist; //used for rendering public YmapEntityDef Parent { get; set; } //used for rendering public uint ParentGuid { get; set; } //used for rendering public MetaHash ParentName { get; set; } //used for rendering public YmapFile Ymap { get; set; } public Vector3 PivotPosition = Vector3.Zero; public Quaternion PivotOrientation = Quaternion.Identity; public Vector3 WidgetPosition = Vector3.Zero; public Quaternion WidgetOrientation = Quaternion.Identity; public string Name { get { return _CEntityDef.archetypeName.ToString(); } } public YmapEntityDef() { Scale = Vector3.One; Position = Vector3.One; Orientation = Quaternion.Identity; } public YmapEntityDef(YmapFile ymap, int index, ref CEntityDef def) { Ymap = ymap; Index = index; CEntityDef = def; Scale = new Vector3(new Vector2(CEntityDef.scaleXY), CEntityDef.scaleZ); Position = CEntityDef.position; Orientation = new Quaternion(CEntityDef.rotation); if (Orientation != Quaternion.Identity) { Orientation = Quaternion.Invert(Orientation); } IsMlo = false; UpdateWidgetPosition(); UpdateWidgetOrientation(); } public YmapEntityDef(YmapFile ymap, int index, ref CMloInstanceDef mlo) { Ymap = ymap; Index = index; CEntityDef = mlo.CEntityDef; Scale = new Vector3(new Vector2(CEntityDef.scaleXY), CEntityDef.scaleZ); Position = CEntityDef.position; Orientation = new Quaternion(CEntityDef.rotation); //if (Orientation != Quaternion.Identity) //{ // Orientation = Quaternion.Invert(Orientation); //} IsMlo = true; MloInstance = new MloInstanceData(); MloInstance.Instance = mlo; UpdateWidgetPosition(); UpdateWidgetOrientation(); } public void SetArchetype(Archetype arch) { Archetype = arch; if (Archetype != null) { float smax = Math.Max(Scale.X, Scale.Z); BSRadius = Archetype.BSRadius * smax; BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale; if (Orientation == Quaternion.Identity) { BBMin = (Archetype.BBMin * Scale) + Position; BBMax = (Archetype.BBMax * Scale) + Position; } else { BBMin = Position - BSRadius; BBMax = Position + BSRadius; ////not ideal: should transform all 8 corners! } if (Archetype.Type == MetaName.CMloArchetypeDef) { //transform interior entities into world space... var mloa = Archetype as MloArchetype; if (MloInstance == null) { MloInstance = new MloInstanceData(); } MloInstance.CreateYmapEntities(this, mloa); if (BSRadius == 0.0f) { BSRadius = CEntityDef.lodDist;//need something so it doesn't get culled... } } } } public void SetPosition(Vector3 pos) { if (MloParent != null) { //TODO: SetPosition for interior entities! Position = pos; var inst = MloParent.MloInstance; if (inst != null) { //transform world position into mlo space //MloRefPosition = ... //MloRefOrientation = ... } } else { Position = pos; _CEntityDef.position = pos; if (Archetype != null) { BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale; } if ((Archetype != null) && (Orientation == Quaternion.Identity)) { BBMin = (Archetype.BBMin * Scale) + Position; BBMax = (Archetype.BBMax * Scale) + Position; } else { BBMin = Position - (BSRadius); BBMax = Position + (BSRadius); ////not ideal: should transform all 8 corners! } UpdateWidgetPosition(); } if (MloInstance != null) { MloInstance.UpdateEntities(); } } public void SetOrientation(Quaternion ori) { Orientation = ori; Quaternion qinv = Quaternion.Normalize(Quaternion.Invert(ori)); _CEntityDef.rotation = new Vector4(qinv.X, qinv.Y, qinv.Z, qinv.W); if (Archetype != null) { BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale; } UpdateWidgetPosition(); UpdateWidgetOrientation(); } public void SetOrientationInv(Quaternion inv) { _CEntityDef.rotation = new Vector4(inv.X, inv.Y, inv.Z, inv.W); Orientation = Quaternion.Normalize(Quaternion.Invert(inv)); if (Archetype != null) { BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale; } UpdateWidgetPosition(); UpdateWidgetOrientation(); } public void SetScale(Vector3 s) { Scale = new Vector3(s.X, s.X, s.Z); _CEntityDef.scaleXY = s.X; _CEntityDef.scaleZ = s.Z; if (Archetype != null) { float smax = Math.Max(Scale.X, Scale.Z); BSRadius = Archetype.BSRadius * smax; } SetPosition(Position);//update the BB } public void SetPivotPosition(Vector3 pos) { PivotPosition = pos; UpdateWidgetPosition(); } public void SetPivotOrientation(Quaternion ori) { PivotOrientation = ori; UpdateWidgetOrientation(); } public void SetPositionFromWidget(Vector3 pos) { SetPosition(pos - Orientation.Multiply(PivotPosition)); } public void SetOrientationFromWidget(Quaternion ori) { var newori = Quaternion.Normalize(Quaternion.Multiply(ori, Quaternion.Invert(PivotOrientation))); var newpos = WidgetPosition - newori.Multiply(PivotPosition); SetOrientation(newori); SetPosition(newpos); } public void SetPivotPositionFromWidget(Vector3 pos) { var orinv = Quaternion.Invert(Orientation); SetPivotPosition(orinv.Multiply(pos - Position)); } public void SetPivotOrientationFromWidget(Quaternion ori) { var orinv = Quaternion.Invert(Orientation); SetPivotOrientation(Quaternion.Multiply(orinv, ori)); } public void UpdateWidgetPosition() { WidgetPosition = Position + Orientation.Multiply(PivotPosition); } public void UpdateWidgetOrientation() { WidgetOrientation = Quaternion.Multiply(Orientation, PivotOrientation); } public void AddChild(YmapEntityDef c) { if (ChildList == null) { ChildList = new List(); } c.Parent = this; c.ParentGuid = CEntityDef.guid; c.ParentName = CEntityDef.archetypeName; ChildList.Add(c); } public void ChildListToArray() { if (ChildList == null) return; //if (Children == null) //{ Children = ChildList.ToArray(); ChildrenMerged = Children;//include these by default in merged array //} //else //{ // List newc = new List(Children.Length + ChildList.Count); // newc.AddRange(Children); // newc.AddRange(ChildList); // Children = newc.ToArray(); //} ChildList.Clear(); ChildList = null; } public void ChildListToMergedArray() { if (ChildList == null) return; if (ChildrenMerged == null) { ChildrenMerged = ChildList.ToArray(); } else { List newc = new List(ChildrenMerged.Length + ChildList.Count); newc.AddRange(ChildrenMerged); newc.AddRange(ChildList); ChildrenMerged = newc.ToArray(); } ChildList.Clear(); ChildList = null; } public override string ToString() { return CEntityDef.ToString() + ((ChildList != null) ? (" (" + ChildList.Count.ToString() + " children) ") : " ") + CEntityDef.lodLevel.ToString(); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapGrassInstanceBatch { public Archetype Archetype { get; set; } //cached by GameFileCache on loading... public rage__fwGrassInstanceListDef Batch { get; set; } public rage__fwGrassInstanceListDef__InstanceData[] Instances { get; set; } public Vector3 Position { get; set; } //calculated from AABB public float Radius { get; set; } //calculated from AABB public Vector3 AABBMin { get; set; } public Vector3 AABBMax { get; set; } public Vector3 CamRel; //used for rendering... public float Distance; //used for rendering public YmapFile Ymap { get; set; } public override string ToString() { return Batch.ToString(); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapPropInstanceBatch { public YmapFile Ymap { get; set; } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapDistantLODLights { public CDistantLODLight CDistantLODLight { get; set; } public uint[] colours { get; set; } public MetaVECTOR3[] positions { get; set; } public Vector3 BBMin { get; set; } public Vector3 BBMax { get; set; } public YmapFile Ymap { get; set; } public void CalcBB() { if (positions != null) { Vector3 min = new Vector3(float.MaxValue); Vector3 max = new Vector3(float.MinValue); for (int i = 0; i < positions.Length; i++) { var p = positions[i]; Vector3 pv = p.ToVector3(); min = Vector3.Min(min, pv); max = Vector3.Max(max, pv); } BBMin = min; BBMax = max; } } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapTimeCycleModifier { public CTimeCycleModifier CTimeCycleModifier { get; set; } public World.TimecycleMod TimeCycleModData { get; set; } public Vector3 BBMin { get; set; } public Vector3 BBMax { get; set; } public YmapFile Ymap { get; set; } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapCarGen { public CCarGen _CCarGen; public CCarGen CCarGen { get { return _CCarGen; } set { _CCarGen = value; } } public Vector3 Position { get; set; } public Quaternion Orientation { get; set; } public Vector3 BBMin { get; set; } public Vector3 BBMax { get; set; } public YmapFile Ymap { get; set; } public YmapCarGen(YmapFile ymap, CCarGen cargen) { float hlen = cargen.perpendicularLength * 0.5f; Ymap = ymap; CCarGen = cargen; Position = cargen.position; CalcOrientation(); BBMin = new Vector3(-hlen); BBMax = new Vector3(hlen); } public void CalcOrientation() { float angl = (float)Math.Atan2(_CCarGen.orientY, _CCarGen.orientX); Orientation = Quaternion.RotationYawPitchRoll(0.0f, 0.0f, angl); } public void SetPosition(Vector3 pos) { Position = pos; _CCarGen.position = pos; } public void SetOrientation(Quaternion ori) { Orientation = ori; float len = Math.Max(_CCarGen.perpendicularLength * 1.5f, 5.0f); Vector3 v = new Vector3(len, 0, 0); Vector3 t = ori.Multiply(v); _CCarGen.orientX = t.X; _CCarGen.orientY = t.Y; } public void SetScale(Vector3 scale) { float s = scale.X; float hlen = s * 0.5f; _CCarGen.perpendicularLength = s; BBMin = new Vector3(-hlen); BBMax = new Vector3(hlen); } public void SetLength(float length) { _CCarGen.perpendicularLength = length; float hlen = length * 0.5f; BBMin = new Vector3(-hlen); BBMax = new Vector3(hlen); } public override string ToString() { return _CCarGen.carModel.ToString() + ", " + Position.ToString() + ", " + _CCarGen.popGroup.ToString() + ", " + _CCarGen.livery.ToString(); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapOccludeModel { public Unk_2741784237 _OccludeModel; public Unk_2741784237 OccludeModel { get { return _OccludeModel; } set { _OccludeModel = value; } } public YmapFile Ymap { get; set; } public YmapOccludeModel(YmapFile ymap, Unk_2741784237 model) { Ymap = ymap; _OccludeModel = model; } } [TypeConverter(typeof(ExpandableObjectConverter))] public class YmapBoxOccluder { public Unk_975711773 _Box; public Unk_975711773 Box { get { return _Box; } set { _Box = value; } } public YmapFile Ymap { get; set; } public YmapBoxOccluder(YmapFile ymap, Unk_975711773 box) { Ymap = ymap; _Box = box; } } }